Compare commits

..

9 Commits

Author SHA1 Message Date
f1e9a944ef Invoke on-reboot from cloud startup script to avoid racing with cron (#6579) (#6580)
automerge

(cherry picked from commit 0c14ca58c7)
2019-10-27 13:55:37 -07:00
4cb38ddf01 -a is optional 2019-10-26 22:50:08 -07:00
593fde628c Blocktree metrics (#6527) (#6577)
automerge
2019-10-26 17:00:38 -07:00
34fa025b17 Fix race in blocktree.insert_shreds (#6550) (#6576)
automerge
2019-10-26 04:46:01 -07:00
33843f824a Bootstrap leader's stake is now authorized to the bootstrap leader's identity key (#6571) (#6574)
(cherry picked from commit 68acfd36d0)
2019-10-26 00:05:39 -07:00
542bda0a6f Add NET_NUM_xyz variables 2019-10-25 22:59:54 -07:00
d8bdbbf291 optimize verify_instruction (#6539) (#6572)
automerge
2019-10-25 22:19:16 -07:00
168b0f71f5 Disable sigverify on blockstreamer node
This node get overloaded at high TPS trying to manage both a validator
and the blockexplorer.  Reduce it's workload by turning off sigverify,
which doesn't really matter since this node doesn't even vote
2019-10-25 21:33:32 -07:00
be79d97dde Increase node start stagger (#6566) (#6567)
automerge
2019-10-25 17:20:13 -07:00
540 changed files with 15534 additions and 37776 deletions

1
.gitignore vendored
View File

@ -15,7 +15,6 @@
# log files
*.log
log-*.txt
log-*/
# intellij files
/.idea/

View File

@ -19,6 +19,46 @@ pull_request_rules:
label:
add:
- automerge
- name: v0.16 backport
conditions:
- base=master
- label=v0.16
actions:
backport:
branches:
- v0.16
- name: v0.17 backport
conditions:
- base=master
- label=v0.17
actions:
backport:
branches:
- v0.17
- name: v0.18 backport
conditions:
- base=master
- label=v0.18
actions:
backport:
branches:
- v0.18
- name: v0.19 backport
conditions:
- base=master
- label=v0.19
actions:
backport:
branches:
- v0.19
- name: v0.20 backport
conditions:
- base=master
- label=v0.20
actions:
backport:
branches:
- v0.20
- name: v0.21 backport
conditions:
- base=master
@ -35,11 +75,3 @@ pull_request_rules:
backport:
branches:
- v0.22
- name: v0.23 backport
conditions:
- base=master
- label=v0.23
actions:
backport:
branches:
- v0.23

2217
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,50 +3,56 @@ members = [
"bench-exchange",
"bench-streamer",
"bench-tps",
"banking-bench",
"banking_bench",
"chacha-sys",
"client",
"core",
"drone",
"perf",
"validator",
"genesis",
"genesis-programs",
"genesis_programs",
"gossip",
"install",
"keygen",
"ledger",
"ledger-tool",
"local-cluster",
"local_cluster",
"logger",
"log-analyzer",
"merkle-tree",
"measure",
"metrics",
"net-shaper",
"programs/bpf_loader",
"programs/budget",
"programs/btc_spv",
"programs/bpf_loader_api",
"programs/bpf_loader_program",
"programs/budget_api",
"programs/budget_program",
"programs/btc_spv_program",
"programs/btc_spv_api",
"programs/btc_spv_bin",
"programs/config",
"programs/config_api",
"programs/config_program",
"programs/config_tests",
"programs/exchange",
"programs/failure",
"programs/noop",
"programs/ownable_api",
"programs/stake",
"programs/exchange_api",
"programs/exchange_program",
"programs/failure_program",
"programs/move_loader_api",
"programs/move_loader_program",
"programs/librapay_api",
"programs/noop_program",
"programs/stake_api",
"programs/stake_program",
"programs/stake_tests",
"programs/storage",
"programs/storage_tests",
"programs/vest",
"programs/vote",
"programs/storage_api",
"programs/storage_program",
"programs/vest_api",
"programs/vest_program",
"programs/vote_api",
"programs/vote_program",
"archiver",
"runtime",
"sdk",
"sdk-c",
"scripts",
"upload-perf",
"net-utils",
"netutil",
"fixed-buf",
"vote-signer",
"cli",
@ -55,6 +61,4 @@ members = [
exclude = [
"programs/bpf",
"programs/move_loader",
"programs/librapay_api",
]

View File

@ -78,7 +78,7 @@ $ source $HOME/.cargo/env
$ rustup component add rustfmt
```
If your rustc version is lower than 1.39.0, please update it:
If your rustc version is lower than 1.38.0, please update it:
```bash
$ rustup update

View File

@ -2,18 +2,17 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-archiver"
version = "0.21.2"
version = "0.20.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
clap = "2.33.0"
console = "0.9.1"
solana-clap-utils = { path = "../clap-utils", version = "0.21.2" }
solana-core = { path = "../core", version = "0.21.2" }
solana-logger = { path = "../logger", version = "0.21.2" }
solana-metrics = { path = "../metrics", version = "0.21.2" }
solana-net-utils = { path = "../net-utils", version = "0.21.2" }
solana-sdk = { path = "../sdk", version = "0.21.2" }
console = "0.9.0"
solana-core = { path = "../core", version = "0.20.0" }
solana-logger = { path = "../logger", version = "0.20.0" }
solana-metrics = { path = "../metrics", version = "0.20.0" }
solana-netutil = { path = "../netutil", version = "0.20.0" }
solana-sdk = { path = "../sdk", version = "0.20.0" }

View File

@ -1,30 +1,31 @@
use clap::{crate_description, crate_name, App, Arg};
use clap::{crate_description, crate_name, crate_version, App, Arg};
use console::style;
use solana_clap_utils::{
input_validators::is_keypair,
keypair::{
self, keypair_input, KeypairWithSource, ASK_SEED_PHRASE_ARG,
SKIP_SEED_PHRASE_VALIDATION_ARG,
},
};
use solana_core::{
archiver::Archiver,
cluster_info::{Node, VALIDATOR_PORT_RANGE},
contact_info::ContactInfo,
};
use solana_sdk::{commitment_config::CommitmentConfig, signature::KeypairUtil};
use std::{net::SocketAddr, path::PathBuf, process::exit, sync::Arc};
use solana_core::archiver::Archiver;
use solana_core::cluster_info::{Node, VALIDATOR_PORT_RANGE};
use solana_core::contact_info::ContactInfo;
use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil};
use std::net::SocketAddr;
use std::path::PathBuf;
use std::process::exit;
use std::sync::Arc;
// Return an error if a keypair file cannot be parsed.
fn is_keypair(string: String) -> Result<(), String> {
read_keypair_file(&string)
.map(|_| ())
.map_err(|err| format!("{:?}", err))
}
fn main() {
solana_logger::setup();
let matches = App::new(crate_name!())
.about(crate_description!())
.version(solana_clap_utils::version!())
.version(crate_version!())
.arg(
Arg::with_name("identity_keypair")
Arg::with_name("identity")
.short("i")
.long("identity-keypair")
.long("identity")
.value_name("PATH")
.takes_value(true)
.validator(is_keypair)
@ -37,7 +38,7 @@ fn main() {
.value_name("HOST:PORT")
.takes_value(true)
.required(true)
.validator(solana_net_utils::is_host_port)
.validator(solana_netutil::is_host_port)
.help("Rendezvous with the cluster at this entry point"),
)
.arg(
@ -55,80 +56,58 @@ fn main() {
.long("storage-keypair")
.value_name("PATH")
.takes_value(true)
.required(true)
.validator(is_keypair)
.help("File containing the storage account keypair"),
)
.arg(
Arg::with_name(ASK_SEED_PHRASE_ARG.name)
.long(ASK_SEED_PHRASE_ARG.long)
.value_name("KEYPAIR NAME")
.multiple(true)
.takes_value(true)
.possible_values(&["identity-keypair", "storage-keypair"])
.help(ASK_SEED_PHRASE_ARG.help),
)
.arg(
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
.requires(ASK_SEED_PHRASE_ARG.name)
.help(SKIP_SEED_PHRASE_VALIDATION_ARG.help),
)
.get_matches();
let ledger_path = PathBuf::from(matches.value_of("ledger").unwrap());
let identity_keypair = keypair_input(&matches, "identity_keypair")
.unwrap_or_else(|err| {
eprintln!("Identity keypair input failed: {}", err);
let keypair = if let Some(identity) = matches.value_of("identity") {
read_keypair_file(identity).unwrap_or_else(|err| {
eprintln!("{}: Unable to open keypair file: {}", err, identity);
exit(1);
})
.keypair;
let KeypairWithSource {
keypair: storage_keypair,
source: storage_keypair_source,
} = keypair_input(&matches, "storage_keypair").unwrap_or_else(|err| {
eprintln!("Storage keypair input failed: {}", err);
exit(1);
});
if storage_keypair_source == keypair::Source::Generated {
clap::Error::with_description(
"The `storage-keypair` argument was not found",
clap::ErrorKind::ArgumentNotFound,
)
.exit();
}
} else {
Keypair::new()
};
let storage_keypair = if let Some(storage_keypair) = matches.value_of("storage_keypair") {
read_keypair_file(storage_keypair).unwrap_or_else(|err| {
eprintln!("{}: Unable to open keypair file: {}", err, storage_keypair);
exit(1);
})
} else {
Keypair::new()
};
let entrypoint_addr = matches
.value_of("entrypoint")
.map(|entrypoint| {
solana_net_utils::parse_host_port(entrypoint)
.expect("failed to parse entrypoint address")
solana_netutil::parse_host_port(entrypoint).expect("failed to parse entrypoint address")
})
.unwrap();
let gossip_addr = {
let ip = solana_net_utils::get_public_ip_addr(&entrypoint_addr).unwrap();
let ip = solana_netutil::get_public_ip_addr(&entrypoint_addr).unwrap();
let mut addr = SocketAddr::new(ip, 0);
addr.set_ip(solana_net_utils::get_public_ip_addr(&entrypoint_addr).unwrap());
addr.set_ip(solana_netutil::get_public_ip_addr(&entrypoint_addr).unwrap());
addr
};
let node = Node::new_archiver_with_external_ip(
&identity_keypair.pubkey(),
&gossip_addr,
VALIDATOR_PORT_RANGE,
);
let node =
Node::new_archiver_with_external_ip(&keypair.pubkey(), &gossip_addr, VALIDATOR_PORT_RANGE);
println!(
"{} version {} (branch={}, commit={})",
style(crate_name!()).bold(),
solana_clap_utils::version!(),
crate_version!(),
option_env!("CI_BRANCH").unwrap_or("unknown"),
option_env!("CI_COMMIT").unwrap_or("unknown")
);
solana_metrics::set_host_id(identity_keypair.pubkey().to_string());
solana_metrics::set_host_id(keypair.pubkey().to_string());
println!(
"replicating the data with identity_keypair={:?} gossip_addr={:?}",
identity_keypair.pubkey(),
"replicating the data with keypair={:?} gossip_addr={:?}",
keypair.pubkey(),
gossip_addr
);
@ -137,9 +116,8 @@ fn main() {
&ledger_path,
node,
entrypoint_info,
Arc::new(identity_keypair),
Arc::new(keypair),
Arc::new(storage_keypair),
CommitmentConfig::recent(),
)
.unwrap();

View File

@ -1,20 +0,0 @@
[package]
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-banking-bench"
version = "0.21.2"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
log = "0.4.6"
rayon = "1.2.0"
solana-core = { path = "../core", version = "0.21.2" }
solana-ledger = { path = "../ledger", version = "0.21.2" }
solana-logger = { path = "../logger", version = "0.21.2" }
solana-runtime = { path = "../runtime", version = "0.21.2" }
solana-measure = { path = "../measure", version = "0.21.2" }
solana-sdk = { path = "../sdk", version = "0.21.2" }
rand = "0.6.5"
crossbeam-channel = "0.3"

20
banking_bench/Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-banking-bench"
version = "0.20.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
log = "0.4.6"
rayon = "1.2.0"
solana-core = { path = "../core", version = "0.20.0" }
solana-ledger = { path = "../ledger", version = "0.20.0" }
solana-logger = { path = "../logger", version = "0.20.0" }
solana-runtime = { path = "../runtime", version = "0.20.0" }
solana-measure = { path = "../measure", version = "0.20.0" }
solana-sdk = { path = "../sdk", version = "0.20.0" }
rand = "0.6.5"
crossbeam-channel = "0.3"

View File

@ -1,3 +1,7 @@
#[macro_use]
extern crate solana_ledger;
extern crate crossbeam_channel;
use crossbeam_channel::unbounded;
use log::*;
use rand::{thread_rng, Rng};
@ -5,12 +9,13 @@ use rayon::prelude::*;
use solana_core::banking_stage::{create_test_recorder, BankingStage};
use solana_core::cluster_info::ClusterInfo;
use solana_core::cluster_info::Node;
use solana_core::genesis_utils::{create_genesis_config, GenesisConfigInfo};
use solana_core::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use solana_core::packet::to_packets_chunked;
use solana_core::poh_recorder::PohRecorder;
use solana_core::poh_recorder::WorkingBankEntry;
use solana_core::service::Service;
use solana_ledger::bank_forks::BankForks;
use solana_ledger::{blocktree::Blocktree, get_tmp_ledger_path};
use solana_ledger::blocktree::{get_tmp_ledger_path, Blocktree};
use solana_measure::measure::Measure;
use solana_runtime::bank::Bank;
use solana_sdk::hash::Hash;
@ -20,6 +25,7 @@ use solana_sdk::signature::Signature;
use solana_sdk::system_transaction;
use solana_sdk::timing::{duration_as_us, timestamp};
use solana_sdk::transaction::Transaction;
use std::iter;
use std::sync::atomic::Ordering;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex, RwLock};
@ -97,21 +103,21 @@ fn main() {
const PACKETS_PER_BATCH: usize = 192;
let txes = PACKETS_PER_BATCH * num_threads * CHUNKS;
let mint_total = 1_000_000_000_000;
let GenesisConfigInfo {
genesis_config,
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_config(mint_total);
} = create_genesis_block(mint_total);
let (verified_sender, verified_receiver) = unbounded();
let (vote_sender, vote_receiver) = unbounded();
let bank0 = Bank::new(&genesis_config);
let bank0 = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank0);
let mut bank = bank_forks.working_bank();
info!("threads: {} txs: {}", num_threads, txes);
let mut transactions = make_accounts_txs(txes, &mint_keypair, genesis_config.hash());
let mut transactions = make_accounts_txs(txes, &mint_keypair, genesis_block.hash());
// fund all the accounts
transactions.iter().for_each(|tx| {
@ -119,7 +125,7 @@ fn main() {
&mint_keypair,
&tx.message.account_keys[0],
mint_total / txes as u64,
genesis_config.hash(),
genesis_block.hash(),
);
let x = bank.process_transaction(&fund);
x.unwrap();
@ -136,7 +142,13 @@ fn main() {
assert!(r.is_ok(), "sanity parallel execution");
}
bank.clear_signatures();
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH)
.into_iter()
.map(|x| {
let len = x.packets.len();
(x, iter::repeat(1).take(len).collect())
})
.collect();
let ledger_path = get_tmp_ledger_path!();
{
let blocktree = Arc::new(
@ -151,7 +163,6 @@ fn main() {
&poh_recorder,
verified_receiver,
vote_receiver,
None,
);
poh_recorder.lock().unwrap().set_bank(&bank);
@ -198,7 +209,7 @@ fn main() {
index,
);
for xv in v {
sent += xv.packets.len();
sent += xv.0.packets.len();
}
verified_sender.send(v.to_vec()).unwrap();
}
@ -277,7 +288,13 @@ fn main() {
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
tx.signatures[0] = Signature::new(&sig[0..64]);
}
verified = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
verified = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH)
.into_iter()
.map(|x| {
let len = x.packets.len();
(x, iter::repeat(1).take(len).collect())
})
.collect();
}
start += chunk_len;

View File

@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-exchange"
version = "0.21.2"
version = "0.20.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -13,29 +13,27 @@ bincode = "1.2.0"
bs58 = "0.3.0"
clap = "2.32.0"
env_logger = "0.7.1"
itertools = "0.8.2"
itertools = "0.8.0"
log = "0.4.8"
num-derive = "0.3"
num-traits = "0.2"
rand = "0.6.5"
rayon = "1.2.0"
serde = "1.0.102"
serde_derive = "1.0.102"
serde = "1.0.101"
serde_derive = "1.0.101"
serde_json = "1.0.41"
serde_yaml = "0.8.11"
solana-clap-utils = { path = "../clap-utils", version = "0.21.2" }
solana-core = { path = "../core", version = "0.21.2" }
solana-genesis = { path = "../genesis", version = "0.21.2" }
solana-client = { path = "../client", version = "0.21.2" }
solana-drone = { path = "../drone", version = "0.21.2" }
solana-exchange-program = { path = "../programs/exchange", version = "0.21.2" }
solana-logger = { path = "../logger", version = "0.21.2" }
solana-metrics = { path = "../metrics", version = "0.21.2" }
solana-net-utils = { path = "../net-utils", version = "0.21.2" }
solana-runtime = { path = "../runtime", version = "0.21.2" }
solana-sdk = { path = "../sdk", version = "0.21.2" }
# solana-runtime = { path = "../solana/runtime"}
solana-core = { path = "../core", version = "0.20.0" }
solana-genesis = { path = "../genesis", version = "0.20.0" }
solana-client = { path = "../client", version = "0.20.0" }
solana-drone = { path = "../drone", version = "0.20.0" }
solana-exchange-api = { path = "../programs/exchange_api", version = "0.20.0" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.20.0" }
solana-logger = { path = "../logger", version = "0.20.0" }
solana-metrics = { path = "../metrics", version = "0.20.0" }
solana-netutil = { path = "../netutil", version = "0.20.0" }
solana-runtime = { path = "../runtime", version = "0.20.0" }
solana-sdk = { path = "../sdk", version = "0.20.0" }
untrusted = "0.7.0"
ws = "0.9.1"
[dev-dependencies]
solana-local-cluster = { path = "../local-cluster", version = "0.21.2" }

View File

@ -360,7 +360,7 @@ The Matcher would initiate the following last swap:
- Row 1, To: Investor 1 trades 2 A token to 12 B tokens
- Row 1, From: Investor 2 trades 2 A token from 12 B tokens
- Matcher takes 2 B tokens as profit
- Matcher takes 4 B tokens as profit
Table becomes:

View File

@ -8,35 +8,31 @@ use rayon::prelude::*;
use solana_client::perf_utils::{sample_txs, SampleStats};
use solana_core::gen_keys::GenKeys;
use solana_drone::drone::request_airdrop_transaction;
use solana_exchange_program::{exchange_instruction, exchange_state::*, id};
use solana_exchange_api::exchange_instruction;
use solana_exchange_api::exchange_state::*;
use solana_exchange_api::id;
use solana_genesis::Base64Account;
use solana_metrics::datapoint_info;
use solana_sdk::{
client::{Client, SyncClient},
commitment_config::CommitmentConfig,
pubkey::Pubkey,
signature::{Keypair, KeypairUtil},
timing::{duration_as_ms, duration_as_s},
transaction::Transaction,
{system_instruction, system_program},
};
use std::{
cmp,
collections::{HashMap, VecDeque},
fs::File,
io::prelude::*,
mem,
net::SocketAddr,
path::Path,
process::exit,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
mpsc::{channel, Receiver, Sender},
Arc, RwLock,
},
thread::{sleep, Builder},
time::{Duration, Instant},
};
use solana_sdk::client::Client;
use solana_sdk::client::SyncClient;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::timing::{duration_as_ms, duration_as_s};
use solana_sdk::transaction::Transaction;
use solana_sdk::{system_instruction, system_program};
use std::cmp;
use std::collections::{HashMap, VecDeque};
use std::fs::File;
use std::io::prelude::*;
use std::mem;
use std::net::SocketAddr;
use std::path::Path;
use std::process::exit;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, RwLock};
use std::thread::{sleep, Builder};
use std::time::{Duration, Instant};
// TODO Chunk length as specified results in a bunch of failures, divide by 10 helps...
// Assume 4MB network buffers, and 512 byte packets
@ -178,28 +174,19 @@ where
info!("Generating {:?} account keys", total_keys);
let mut account_keypairs = generate_keypairs(total_keys);
let src_keypairs: Vec<_> = account_keypairs
let src_pubkeys: Vec<_> = account_keypairs
.drain(0..accounts_in_groups)
.map(|keypair| keypair)
.collect();
let src_pubkeys: Vec<Pubkey> = src_keypairs
.iter()
.map(|keypair| keypair.pubkey())
.collect();
let profit_keypairs: Vec<_> = account_keypairs
let profit_pubkeys: Vec<_> = account_keypairs
.drain(0..accounts_in_groups)
.map(|keypair| keypair)
.collect();
let profit_pubkeys: Vec<Pubkey> = profit_keypairs
.iter()
.map(|keypair| keypair.pubkey())
.collect();
info!("Create {:?} source token accounts", src_pubkeys.len());
create_token_accounts(client, &trader_signers, &src_keypairs);
create_token_accounts(client, &trader_signers, &src_pubkeys);
info!("Create {:?} profit token accounts", profit_pubkeys.len());
create_token_accounts(client, &swapper_signers, &profit_keypairs);
create_token_accounts(client, &swapper_signers, &profit_pubkeys);
// Collect the max transaction rate and total tx count seen (single node only)
let sample_stats = Arc::new(RwLock::new(Vec::new()));
@ -393,10 +380,7 @@ fn swapper<T>(
let mut tries = 0;
let mut trade_index = 0;
while client
.get_balance_with_commitment(
&trade_infos[trade_index].trade_account,
CommitmentConfig::recent(),
)
.get_balance(&trade_infos[trade_index].trade_account)
.unwrap_or(0)
== 0
{
@ -450,7 +434,7 @@ fn swapper<T>(
account_group = (account_group + 1) % account_groups as usize;
let (blockhash, _fee_calculator) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.get_recent_blockhash()
.expect("Failed to get blockhash");
let to_swap_txs: Vec<_> = to_swap
.par_iter()
@ -573,39 +557,27 @@ fn trader<T>(
trade_account: trade.pubkey(),
order_info,
});
trades.push((signer, trade, side, src));
trades.push((signer, trade.pubkey(), side, src));
}
account_group = (account_group + 1) % account_groups as usize;
let (blockhash, _fee_calculator) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.get_recent_blockhash()
.expect("Failed to get blockhash");
trades.chunks(chunk_size).for_each(|chunk| {
let trades_txs: Vec<_> = chunk
.par_iter()
.map(|(owner, trade, side, src)| {
let owner_pubkey = &owner.pubkey();
let trade_pubkey = &trade.pubkey();
.map(|(signer, trade, side, src)| {
let s: &Keypair = &signer;
let owner = &signer.pubkey();
let space = mem::size_of::<ExchangeState>() as u64;
Transaction::new_signed_instructions(
&[owner.as_ref(), trade],
&[s],
vec![
system_instruction::create_account(
owner_pubkey,
trade_pubkey,
1,
space,
&id(),
),
system_instruction::create_account(owner, trade, 1, space, &id()),
exchange_instruction::trade_request(
owner_pubkey,
trade_pubkey,
*side,
pair,
tokens,
price,
src,
owner, trade, *side, pair, tokens, price, src,
),
],
blockhash,
@ -666,9 +638,7 @@ where
T: SyncClient + ?Sized,
{
for s in &tx.signatures {
if let Ok(Some(r)) =
sync_client.get_signature_status_with_commitment(s, CommitmentConfig::recent())
{
if let Ok(Some(r)) = sync_client.get_signature_status(s) {
match r {
Ok(_) => {
return true;
@ -689,15 +659,12 @@ fn verify_funding_transfer<T: SyncClient + ?Sized>(
) -> bool {
if verify_transaction(client, tx) {
for a in &tx.message().account_keys[1..] {
if client
.get_balance_with_commitment(a, CommitmentConfig::recent())
.unwrap_or(0)
>= amount
{
if client.get_balance(a).unwrap_or(0) >= amount {
return true;
}
}
}
false
}
@ -775,9 +742,8 @@ pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc<Keypair>],
to_fund_txs.len(),
);
let (blockhash, _fee_calculator) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.expect("blockhash");
let (blockhash, _fee_calculator) =
client.get_recent_blockhash().expect("blockhash");
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
tx.sign(&[*k], blockhash);
});
@ -814,37 +780,27 @@ pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc<Keypair>],
});
funded.append(&mut new_funded);
funded.retain(|(k, b)| {
client
.get_balance_with_commitment(&k.pubkey(), CommitmentConfig::recent())
.unwrap_or(0)
> lamports
&& *b > lamports
client.get_balance(&k.pubkey()).unwrap_or(0) > lamports && *b > lamports
});
debug!(" Funded: {} left: {}", funded.len(), notfunded.len());
}
}
pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], accounts: &[Keypair]) {
let mut notfunded: Vec<(&Arc<Keypair>, &Keypair)> = signers.iter().zip(accounts).collect();
pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], accounts: &[Pubkey]) {
let mut notfunded: Vec<(&Arc<Keypair>, &Pubkey)> = signers.iter().zip(accounts).collect();
while !notfunded.is_empty() {
notfunded.chunks(FUND_CHUNK_LEN).for_each(|chunk| {
let mut to_create_txs: Vec<_> = chunk
.par_iter()
.map(|(from_keypair, new_keypair)| {
let owner_pubkey = &from_keypair.pubkey();
.map(|(signer, new)| {
let owner_pubkey = &signer.pubkey();
let space = mem::size_of::<ExchangeState>() as u64;
let create_ix = system_instruction::create_account(
owner_pubkey,
&new_keypair.pubkey(),
1,
space,
&id(),
);
let request_ix =
exchange_instruction::account_request(owner_pubkey, &new_keypair.pubkey());
let create_ix =
system_instruction::create_account(owner_pubkey, new, 1, space, &id());
let request_ix = exchange_instruction::account_request(owner_pubkey, new);
(
(from_keypair, new_keypair),
signer,
Transaction::new_unsigned_instructions(vec![create_ix, request_ix]),
)
})
@ -863,13 +819,12 @@ pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], acco
let mut retries = 0;
while !to_create_txs.is_empty() {
let (blockhash, _fee_calculator) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.get_recent_blockhash()
.expect("Failed to get blockhash");
to_create_txs
.par_iter_mut()
.for_each(|((from_keypair, to_keypair), tx)| {
tx.sign(&[from_keypair.as_ref(), to_keypair], blockhash);
});
to_create_txs.par_iter_mut().for_each(|(k, tx)| {
let kp: &Keypair = k;
tx.sign(&[kp], blockhash);
});
to_create_txs.iter().for_each(|(_, tx)| {
client.async_send_transaction(tx.clone()).expect("transfer");
});
@ -906,13 +861,9 @@ pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], acco
}
});
let mut new_notfunded: Vec<(&Arc<Keypair>, &Keypair)> = vec![];
let mut new_notfunded: Vec<(&Arc<Keypair>, &Pubkey)> = vec![];
for f in &notfunded {
if client
.get_balance_with_commitment(&f.1.pubkey(), CommitmentConfig::recent())
.unwrap_or(0)
== 0
{
if client.get_balance(&f.1).unwrap_or(0) == 0 {
new_notfunded.push(*f)
}
}
@ -969,7 +920,7 @@ fn generate_keypairs(num: u64) -> Vec<Keypair> {
}
pub fn airdrop_lamports(client: &dyn Client, drone_addr: &SocketAddr, id: &Keypair, amount: u64) {
let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent());
let balance = client.get_balance(&id.pubkey());
let balance = balance.unwrap_or(0);
if balance >= amount {
return;
@ -987,26 +938,19 @@ pub fn airdrop_lamports(client: &dyn Client, drone_addr: &SocketAddr, id: &Keypa
let mut tries = 0;
loop {
let (blockhash, _fee_calculator) = client
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.get_recent_blockhash()
.expect("Failed to get blockhash");
match request_airdrop_transaction(&drone_addr, &id.pubkey(), amount_to_drop, blockhash) {
Ok(transaction) => {
let signature = client.async_send_transaction(transaction).unwrap();
for _ in 0..30 {
if let Ok(Some(_)) = client.get_signature_status_with_commitment(
&signature,
CommitmentConfig::recent(),
) {
if let Ok(Some(_)) = client.get_signature_status(&signature) {
break;
}
sleep(Duration::from_millis(100));
}
if client
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent())
.unwrap_or(0)
>= amount
{
if client.get_balance(&id.pubkey()).unwrap_or(0) >= amount {
break;
}
}

View File

@ -1,4 +1,4 @@
use clap::{crate_description, crate_name, value_t, App, Arg, ArgMatches};
use clap::{crate_description, crate_name, crate_version, value_t, App, Arg, ArgMatches};
use solana_core::gen_keys::GenKeys;
use solana_drone::drone::DRONE_PORT;
use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil};
@ -44,10 +44,10 @@ impl Default for Config {
}
}
pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
pub fn build_args<'a, 'b>() -> App<'a, 'b> {
App::new(crate_name!())
.about(crate_description!())
.version(version)
.version(crate_version!())
.arg(
Arg::with_name("entrypoint")
.short("n")
@ -166,15 +166,13 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
let mut args = Config::default();
args.entrypoint_addr = solana_net_utils::parse_host_port(
matches.value_of("entrypoint").unwrap(),
)
.unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e);
exit(1)
});
args.entrypoint_addr = solana_netutil::parse_host_port(matches.value_of("entrypoint").unwrap())
.unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e);
exit(1)
});
args.drone_addr = solana_net_utils::parse_host_port(matches.value_of("drone").unwrap())
args.drone_addr = solana_netutil::parse_host_port(matches.value_of("drone").unwrap())
.unwrap_or_else(|e| {
eprintln!("failed to parse drone address: {}", e);
exit(1)

View File

@ -11,7 +11,7 @@ fn main() {
solana_logger::setup();
solana_metrics::set_panic_hook("bench-exchange");
let matches = cli::build_args(solana_clap_utils::version!()).get_matches();
let matches = cli::build_args().get_matches();
let cli_config = cli::extract_args(&matches);
let cli::Config {

View File

@ -1,7 +1,7 @@
use itertools::EitherOrBoth::{Both, Left, Right};
use itertools::Itertools;
use log::*;
use solana_exchange_program::exchange_state::*;
use solana_exchange_api::exchange_state::*;
use solana_sdk::pubkey::Pubkey;
use std::cmp::Ordering;
use std::collections::BinaryHeap;

View File

@ -2,14 +2,13 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-streamer"
version = "0.21.2"
version = "0.20.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
clap = "2.33.0"
solana-clap-utils = { path = "../clap-utils", version = "0.21.2" }
solana-core = { path = "../core", version = "0.21.2" }
solana-logger = { path = "../logger", version = "0.21.2" }
solana-net-utils = { path = "../net-utils", version = "0.21.2" }
solana-core = { path = "../core", version = "0.20.0" }
solana-logger = { path = "../logger", version = "0.20.0" }
solana-netutil = { path = "../netutil", version = "0.20.0" }

View File

@ -1,5 +1,6 @@
use clap::{crate_description, crate_name, App, Arg};
use solana_core::packet::{Packet, Packets, PacketsRecycler, PACKET_DATA_SIZE};
use clap::{crate_description, crate_name, crate_version, App, Arg};
use solana_core::packet::PacketsRecycler;
use solana_core::packet::{Packet, Packets, BLOB_SIZE, PACKET_DATA_SIZE};
use solana_core::result::Result;
use solana_core::streamer::{receiver, PacketReceiver};
use std::cmp::max;
@ -28,7 +29,7 @@ fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
let mut num = 0;
for p in &msgs.packets {
let a = p.meta.addr();
assert!(p.meta.size < PACKET_DATA_SIZE);
assert!(p.meta.size < BLOB_SIZE);
send.send_to(&p.data[..p.meta.size], &a).unwrap();
num += 1;
}
@ -53,7 +54,7 @@ fn main() -> Result<()> {
let matches = App::new(crate_name!())
.about(crate_description!())
.version(solana_clap_utils::version!())
.version(crate_version!())
.arg(
Arg::with_name("num-recv-sockets")
.long("num-recv-sockets")
@ -76,7 +77,7 @@ fn main() -> Result<()> {
let mut read_threads = Vec::new();
let recycler = PacketsRecycler::default();
for _ in 0..num_sockets {
let read = solana_net_utils::bind_to(port, false).unwrap();
let read = solana_netutil::bind_to(port, false).unwrap();
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
addr = read.local_addr().unwrap();

View File

@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-tps"
version = "0.21.2"
version = "0.20.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -12,28 +12,27 @@ bincode = "1.2.0"
clap = "2.33.0"
log = "0.4.8"
rayon = "1.2.0"
serde = "1.0.102"
serde_derive = "1.0.102"
serde = "1.0.101"
serde_derive = "1.0.101"
serde_json = "1.0.41"
serde_yaml = "0.8.11"
solana-clap-utils = { path = "../clap-utils", version = "0.21.2" }
solana-core = { path = "../core", version = "0.21.2" }
solana-genesis = { path = "../genesis", version = "0.21.2" }
solana-client = { path = "../client", version = "0.21.2" }
solana-drone = { path = "../drone", version = "0.21.2" }
solana-librapay-api = { path = "../programs/librapay_api", version = "0.21.2", optional = true }
solana-logger = { path = "../logger", version = "0.21.2" }
solana-metrics = { path = "../metrics", version = "0.21.2" }
solana-measure = { path = "../measure", version = "0.21.2" }
solana-net-utils = { path = "../net-utils", version = "0.21.2" }
solana-runtime = { path = "../runtime", version = "0.21.2" }
solana-sdk = { path = "../sdk", version = "0.21.2" }
solana-move-loader-program = { path = "../programs/move_loader", version = "0.21.2", optional = true }
solana-core = { path = "../core", version = "0.20.0" }
solana-genesis = { path = "../genesis", version = "0.20.0" }
solana-client = { path = "../client", version = "0.20.0" }
solana-drone = { path = "../drone", version = "0.20.0" }
solana-librapay-api = { path = "../programs/librapay_api", version = "0.20.0", optional = true }
solana-logger = { path = "../logger", version = "0.20.0" }
solana-metrics = { path = "../metrics", version = "0.20.0" }
solana-measure = { path = "../measure", version = "0.20.0" }
solana-netutil = { path = "../netutil", version = "0.20.0" }
solana-runtime = { path = "../runtime", version = "0.20.0" }
solana-sdk = { path = "../sdk", version = "0.20.0" }
solana-move-loader-program = { path = "../programs/move_loader_program", version = "0.20.0", optional = true }
solana-move-loader-api = { path = "../programs/move_loader_api", version = "0.20.0", optional = true }
[dev-dependencies]
serial_test = "0.2.0"
serial_test_derive = "0.2.0"
solana-local-cluster = { path = "../local-cluster", version = "0.21.2" }
[features]
move = ["solana-librapay-api", "solana-move-loader-program"]
move = ["solana-core/move", "solana-librapay-api", "solana-move-loader-program", "solana-move-loader-api"]

View File

@ -1,3 +1,5 @@
use solana_metrics;
use crate::cli::Config;
use log::*;
use rayon::prelude::*;
@ -5,19 +7,18 @@ use solana_client::perf_utils::{sample_txs, SampleStats};
use solana_core::gen_keys::GenKeys;
use solana_drone::drone::request_airdrop_transaction;
#[cfg(feature = "move")]
use solana_librapay_api::{create_genesis, upload_mint_script, upload_payment_script};
use solana_librapay_api::{create_genesis, upload_mint_program, upload_payment_program};
use solana_measure::measure::Measure;
use solana_metrics::{self, datapoint_debug};
use solana_metrics::datapoint_debug;
use solana_sdk::{
client::Client,
clock::{DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE},
commitment_config::CommitmentConfig,
fee_calculator::FeeCalculator,
hash::Hash,
pubkey::Pubkey,
signature::{Keypair, KeypairUtil},
system_instruction, system_transaction,
timing::{duration_as_ms, duration_as_s, duration_as_us, timestamp},
timing::{duration_as_ms, duration_as_s, timestamp},
transaction::Transaction,
};
use std::{
@ -54,7 +55,7 @@ type LibraKeys = (Keypair, Pubkey, Pubkey, Vec<Keypair>);
fn get_recent_blockhash<T: Client>(client: &T) -> (Hash, FeeCalculator) {
loop {
match client.get_recent_blockhash_with_commitment(CommitmentConfig::recent()) {
match client.get_recent_blockhash() {
Ok((blockhash, fee_calculator)) => return (blockhash, fee_calculator),
Err(err) => {
info!("Couldn't get recent blockhash: {:?}", err);
@ -157,13 +158,12 @@ where
let mut reclaim_lamports_back_to_source_account = false;
let mut i = keypair0_balance;
let mut blockhash = Hash::default();
let mut blockhash_time;
let mut blockhash_time = Instant::now();
while start.elapsed() < duration {
// ping-pong between source and destination accounts for each loop iteration
// this seems to be faster than trying to determine the balance of individual
// accounts
let len = tx_count as usize;
blockhash_time = Instant::now();
if let Ok((new_blockhash, _fee_calculator)) = client.get_new_blockhash(&blockhash) {
blockhash = new_blockhash;
} else {
@ -173,19 +173,13 @@ where
sleep(Duration::from_millis(100));
continue;
}
datapoint_debug!(
"bench-tps-get_blockhash",
("duration", duration_as_us(&blockhash_time.elapsed()), i64)
info!(
"Took {} ms for new blockhash",
duration_as_ms(&blockhash_time.elapsed())
);
blockhash_time = Instant::now();
let balance = client.get_balance(&id.pubkey()).unwrap_or(0);
metrics_submit_lamport_balance(balance);
datapoint_debug!(
"bench-tps-get_balance",
("duration", duration_as_us(&blockhash_time.elapsed()), i64)
);
generate_txs(
&shared_txs,
&blockhash,
@ -373,7 +367,7 @@ fn generate_txs(
);
datapoint_debug!(
"bench-tps-generate_txs",
("duration", duration_as_us(&duration), i64)
("duration", duration_as_ms(&duration), i64)
);
let sz = transactions.len() / threads;
@ -438,7 +432,7 @@ fn do_tx_transfers<T: Client>(
);
datapoint_debug!(
"bench-tps-do_tx_transfers",
("duration", duration_as_us(&transfer_start.elapsed()), i64),
("duration", duration_as_ms(&transfer_start.elapsed()), i64),
("count", tx_len, i64)
);
}
@ -450,11 +444,7 @@ fn do_tx_transfers<T: Client>(
fn verify_funding_transfer<T: Client>(client: &T, tx: &Transaction, amount: u64) -> bool {
for a in &tx.message().account_keys[1..] {
if client
.get_balance_with_commitment(a, CommitmentConfig::recent())
.unwrap_or(0)
>= amount
{
if client.get_balance(a).unwrap_or(0) >= amount {
return true;
}
}
@ -669,12 +659,10 @@ pub fn airdrop_lamports<T: Client>(
}
};
let current_balance = client
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent())
.unwrap_or_else(|e| {
info!("airdrop error {}", e);
starting_balance
});
let current_balance = client.get_balance(&id.pubkey()).unwrap_or_else(|e| {
info!("airdrop error {}", e);
starting_balance
});
info!("current balance {}...", current_balance);
metrics_submit_lamport_balance(current_balance);
@ -789,62 +777,62 @@ fn fund_move_keys<T: Client>(
total: u64,
libra_pay_program_id: &Pubkey,
libra_mint_program_id: &Pubkey,
libra_genesis_key: &Keypair,
libra_mint_key: &Keypair,
) {
let (mut blockhash, _fee_calculator) = get_recent_blockhash(client);
info!("creating the libra funding account..");
let libra_funding_key = Keypair::new();
let tx = librapay_transaction::create_account(funding_key, &libra_funding_key, 1, blockhash);
client
.send_message(&[funding_key, &libra_funding_key], tx.message)
.unwrap();
let tx = librapay_transaction::create_account(
funding_key,
&libra_funding_key.pubkey(),
1,
blockhash,
);
client.send_message(&[funding_key], tx.message).unwrap();
info!("minting to funding keypair");
let tx = librapay_transaction::mint_tokens(
&libra_mint_program_id,
funding_key,
libra_genesis_key,
libra_mint_key,
&libra_funding_key.pubkey(),
total,
blockhash,
);
client
.send_message(&[funding_key, libra_genesis_key], tx.message)
.send_message(&[funding_key, libra_mint_key], tx.message)
.unwrap();
info!("creating {} move accounts...", keypairs.len());
let total_len = keypairs.len();
let create_len = 5;
let create_len = 8;
let mut funding_time = Measure::start("funding_time");
for (i, keys) in keypairs.chunks(create_len).enumerate() {
if client
.get_balance_with_commitment(&keys[0].pubkey(), CommitmentConfig::recent())
.unwrap_or(0)
> 0
{
if client.get_balance(&keys[0].pubkey()).unwrap_or(0) > 0 {
// already created these accounts.
break;
}
let keypairs: Vec<_> = keys.iter().map(|k| k).collect();
let tx = librapay_transaction::create_accounts(funding_key, &keypairs, 1, blockhash);
let pubkeys: Vec<_> = keys.iter().map(|k| k.pubkey()).collect();
let tx = librapay_transaction::create_accounts(funding_key, &pubkeys, 1, blockhash);
let ser_size = bincode::serialized_size(&tx).unwrap();
let mut keys = vec![funding_key];
keys.extend(&keypairs);
client.send_message(&keys, tx.message).unwrap();
client.send_message(&[funding_key], tx.message).unwrap();
if i % 10 == 0 {
info!(
"created {} accounts of {} (size {})",
i,
total_len / create_len,
"size: {} created {} accounts of {}",
ser_size,
i,
(keypairs.len() / create_len),
);
}
}
funding_time.stop();
info!("funding accounts {}ms", funding_time.as_ms());
const NUM_FUNDING_KEYS: usize = 10;
const NUM_FUNDING_KEYS: usize = 4;
let funding_keys: Vec<_> = (0..NUM_FUNDING_KEYS).map(|_| Keypair::new()).collect();
let pubkey_amounts: Vec<_> = funding_keys
.iter()
@ -858,9 +846,7 @@ fn fund_move_keys<T: Client>(
client.send_message(&[funding_key], tx.message).unwrap();
let mut balance = 0;
for _ in 0..20 {
if let Ok(balance_) = client
.get_balance_with_commitment(&funding_keys[0].pubkey(), CommitmentConfig::recent())
{
if let Ok(balance_) = client.get_balance(&funding_keys[0].pubkey()) {
if balance_ > 0 {
balance = balance_;
break;
@ -869,21 +855,19 @@ fn fund_move_keys<T: Client>(
sleep(Duration::from_millis(100));
}
assert!(balance > 0);
info!(
"funded multiple funding accounts with {:?} lanports",
balance
);
info!("funded multiple funding accounts.. {:?}", balance);
let libra_funding_keys: Vec<_> = (0..NUM_FUNDING_KEYS).map(|_| Keypair::new()).collect();
for (i, key) in libra_funding_keys.iter().enumerate() {
let tx = librapay_transaction::create_account(&funding_keys[i], &key, 1, blockhash);
let tx =
librapay_transaction::create_account(&funding_keys[i], &key.pubkey(), 1, blockhash);
client
.send_message(&[&funding_keys[i], &key], tx.message)
.send_message(&[&funding_keys[i]], tx.message)
.unwrap();
let tx = librapay_transaction::transfer(
libra_pay_program_id,
&libra_genesis_key.pubkey(),
&libra_mint_key.pubkey(),
&funding_keys[i],
&libra_funding_key,
&key.pubkey(),
@ -903,7 +887,7 @@ fn fund_move_keys<T: Client>(
for (j, key) in keys.iter().enumerate() {
let tx = librapay_transaction::transfer(
libra_pay_program_id,
&libra_genesis_key.pubkey(),
&libra_mint_key.pubkey(),
&funding_keys[j],
&libra_funding_keys[j],
&key.pubkey(),
@ -916,6 +900,7 @@ fn fund_move_keys<T: Client>(
.expect("create_account in generate_and_fund_keypairs");
}
info!("sent... checking balance {}", i);
for (j, key) in keys.iter().enumerate() {
let mut times = 0;
loop {
@ -933,16 +918,11 @@ fn fund_move_keys<T: Client>(
}
}
info!(
"funded group {} of {}",
i + 1,
keypairs.len() / NUM_FUNDING_KEYS
);
info!("funded: {} of {}", i, keypairs.len() / NUM_FUNDING_KEYS);
blockhash = get_recent_blockhash(client).0;
}
funding_time.stop();
info!("done funding keys, took {} ms", funding_time.as_ms());
info!("done funding keys..");
}
pub fn generate_and_fund_keypairs<T: Client>(
@ -992,8 +972,8 @@ pub fn generate_and_fund_keypairs<T: Client>(
{
if use_move {
let libra_genesis_keypair = create_genesis(&funding_key, client, 10_000_000);
let libra_mint_program_id = upload_mint_script(&funding_key, client);
let libra_pay_program_id = upload_payment_script(&funding_key, client);
let libra_mint_program_id = upload_mint_program(&funding_key, client);
let libra_pay_program_id = upload_payment_program(&funding_key, client);
// Generate another set of keypairs for move accounts.
// Still fund the solana ones which will be used for fees.
@ -1044,7 +1024,7 @@ mod tests {
use solana_runtime::bank_client::BankClient;
use solana_sdk::client::SyncClient;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::genesis_config::create_genesis_config;
use solana_sdk::genesis_block::create_genesis_block;
#[test]
fn test_switch_directions() {
@ -1063,8 +1043,8 @@ mod tests {
#[test]
fn test_bench_tps_bank_client() {
let (genesis_config, id) = create_genesis_config(10_000);
let bank = Bank::new(&genesis_config);
let (genesis_block, id) = create_genesis_block(10_000);
let bank = Bank::new(&genesis_block);
let clients = vec![BankClient::new(bank)];
let mut config = Config::default();
@ -1081,8 +1061,8 @@ mod tests {
#[test]
fn test_bench_tps_fund_keys() {
let (genesis_config, id) = create_genesis_config(10_000);
let bank = Bank::new(&genesis_config);
let (genesis_block, id) = create_genesis_block(10_000);
let bank = Bank::new(&genesis_block);
let client = BankClient::new(bank);
let tx_count = 10;
let lamports = 20;
@ -1091,21 +1071,16 @@ mod tests {
generate_and_fund_keypairs(&client, None, &id, tx_count, lamports, false).unwrap();
for kp in &keypairs {
assert_eq!(
client
.get_balance_with_commitment(&kp.pubkey(), CommitmentConfig::recent())
.unwrap(),
lamports
);
assert_eq!(client.get_balance(&kp.pubkey()).unwrap(), lamports);
}
}
#[test]
fn test_bench_tps_fund_keys_with_fees() {
let (mut genesis_config, id) = create_genesis_config(10_000);
let (mut genesis_block, id) = create_genesis_block(10_000);
let fee_calculator = FeeCalculator::new(11, 0);
genesis_config.fee_calculator = fee_calculator;
let bank = Bank::new(&genesis_config);
genesis_block.fee_calculator = fee_calculator;
let bank = Bank::new(&genesis_block);
let client = BankClient::new(bank);
let tx_count = 10;
let lamports = 20;
@ -1114,7 +1089,7 @@ mod tests {
generate_and_fund_keypairs(&client, None, &id, tx_count, lamports, false).unwrap();
let max_fee = client
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.get_recent_blockhash()
.unwrap()
.1
.max_lamports_per_signature;

View File

@ -1,4 +1,4 @@
use clap::{crate_description, crate_name, App, Arg, ArgMatches};
use clap::{crate_description, crate_name, crate_version, App, Arg, ArgMatches};
use solana_drone::drone::DRONE_PORT;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil};
@ -21,7 +21,6 @@ pub struct Config {
pub write_to_client_file: bool,
pub read_from_client_file: bool,
pub target_lamports_per_signature: u64,
pub multi_client: bool,
pub use_move: bool,
pub num_lamports_per_account: u64,
}
@ -42,7 +41,6 @@ impl Default for Config {
write_to_client_file: false,
read_from_client_file: false,
target_lamports_per_signature: FeeCalculator::default().target_lamports_per_signature,
multi_client: true,
use_move: false,
num_lamports_per_account: NUM_LAMPORTS_PER_ACCOUNT_DEFAULT,
}
@ -50,9 +48,9 @@ impl Default for Config {
}
/// Defines and builds the CLI args for a run of the benchmark
pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
pub fn build_args<'a, 'b>() -> App<'a, 'b> {
App::new(crate_name!()).about(crate_description!())
.version(version)
.version(crate_version!())
.arg(
Arg::with_name("entrypoint")
.short("n")
@ -110,11 +108,6 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
.long("use-move")
.help("Use Move language transactions to perform transfers."),
)
.arg(
Arg::with_name("no-multi-client")
.long("no-multi-client")
.help("Disable multi-client support, only transact with the entrypoint."),
)
.arg(
Arg::with_name("tx_count")
.long("tx_count")
@ -174,14 +167,14 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
let mut args = Config::default();
if let Some(addr) = matches.value_of("entrypoint") {
args.entrypoint_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
args.entrypoint_addr = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e);
exit(1)
});
}
if let Some(addr) = matches.value_of("drone") {
args.drone_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
args.drone_addr = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| {
eprintln!("failed to parse drone address: {}", e);
exit(1)
});
@ -236,7 +229,6 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
}
args.use_move = matches.is_present("use-move");
args.multi_client = !matches.is_present("no-multi-client");
if let Some(v) = matches.value_of("num_lamports_per_account") {
args.num_lamports_per_account = v.to_string().parse().expect("can't parse lamports");

View File

@ -1,7 +1,7 @@
use log::*;
use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs, generate_keypairs};
use solana_bench_tps::cli;
use solana_core::gossip_service::{discover_cluster, get_client, get_multi_client};
use solana_core::gossip_service::{discover_cluster, get_multi_client};
use solana_genesis::Base64Account;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::signature::{Keypair, KeypairUtil};
@ -15,7 +15,7 @@ fn main() {
solana_logger::setup_with_filter("solana=info");
solana_metrics::set_panic_hook("bench-tps");
let matches = cli::build_args(solana_clap_utils::version!()).get_matches();
let matches = cli::build_args().get_matches();
let cli_config = cli::extract_args(&matches);
let cli::Config {
@ -29,7 +29,6 @@ fn main() {
read_from_client_file,
target_lamports_per_signature,
use_move,
multi_client,
num_lamports_per_account,
..
} = &cli_config;
@ -71,19 +70,15 @@ fn main() {
exit(1);
});
let client = if *multi_client {
let (client, num_clients) = get_multi_client(&nodes);
if nodes.len() < num_clients {
eprintln!(
"Error: Insufficient nodes discovered. Expecting {} or more",
num_nodes
);
exit(1);
}
client
} else {
get_client(&nodes)
};
let (client, num_clients) = get_multi_client(&nodes);
if nodes.len() < num_clients {
eprintln!(
"Error: Insufficient nodes discovered. Expecting {} or more",
num_nodes
);
exit(1);
}
let (keypairs, move_keypairs, keypair_balance) = if *read_from_client_file && !use_move {
let path = Path::new(&client_ids_and_stake_file);

View File

@ -7,7 +7,7 @@
| TVU | |
| | |
| .-------. .------------. .----+---. .---------. |
.------------. | | Shred | | Retransmit | | Replay | | Storage | |
.------------. | | Blob | | Retransmit | | Replay | | Storage | |
| Upstream +----->| Fetch +-->| Stage +-->| Stage +-->| Stage | |
| Validators | | | Stage | | | | | | | |
`------------` | `-------` `----+-------` `----+---` `---------` |

View File

@ -1,30 +1,30 @@
.---------------------------------------.
| Validator |
| |
.--------. | .-------------------. |
| |---->| | |
| Client | | | JSON RPC Service | |
| |<----| | |
`----+---` | `-------------------` |
| | ^ |
| | | .----------------. | .------------------.
| | | | Gossip Service |<-----------| Validators |
| | | `----------------` | | |
| | | ^ | | |
| | | | | | .------------. |
| | .---+---. .----+---. .------------. | | | | |
| | | Bank |<-+ Replay | | ShredFetch |<------+ Upstream | |
| | | Forks | | Stage | | Stage | | | | Validators | |
| | `-------` `--------` `--+---------` | | | | |
| | ^ ^ | | | `------------` |
| | | | v | | |
| | | .--+--------. | | |
| | | | Blocktree | | | |
| | | `-----------` | | .------------. |
| | | ^ | | | | |
| | | | | | | Downstream | |
| | .--+--. .-------+---. | | | Validators | |
`-------->| TPU +---->| Broadcast +---------------->| | |
| `-----` | Stage | | | `------------` |
| `-----------` | `------------------`
`---------------------------------------`
.--------------------------------------.
| Validator |
| |
.--------. | .-------------------. |
| |---->| | |
| Client | | | JSON RPC Service | |
| |<----| | |
`----+---` | `-------------------` |
| | ^ |
| | | .----------------. | .------------------.
| | | | Gossip Service |<----------| Validators |
| | | `----------------` | | |
| | | ^ | | |
| | | | | | .------------. |
| | .---+---. .----+---. .-----------. | | | | |
| | | Bank |<-+ Replay | | BlobFetch |<------+ Upstream | |
| | | Forks | | Stage | | Stage | | | | Validators | |
| | `-------` `--------` `--+--------` | | | | |
| | ^ ^ | | | `------------` |
| | | | v | | |
| | | .--+--------. | | |
| | | | Blocktree | | | |
| | | `-----------` | | .------------. |
| | | ^ | | | | |
| | | | | | | Downstream | |
| | .--+--. .-------+---. | | | Validators | |
`-------->| TPU +---->| Broadcast +--------------->| | |
| `-----` | Stage | | | `------------` |
| `-----------` | `------------------`
`--------------------------------------`

View File

@ -36,10 +36,6 @@
* [Troubleshooting](running-validator/validator-troubleshoot.md)
* [FAQ](running-validator/validator-faq.md)
* [Running an Archiver](running-archiver.md)
* [Paper Wallet](paper-wallet/README.md)
* [Installation](paper-wallet/installation.md)
* [Creating and Using a Seed Phrase](paper-wallet/keypair.md)
* [Paper Wallet Usage](paper-wallet/usage.md)
* [API Reference](api-reference/README.md)
* [Transaction](api-reference/transaction-api.md)
* [Instruction](api-reference/instruction-api.md)
@ -59,7 +55,6 @@
* [Inter-chain Transaction Verification](proposals/interchain-transaction-verification.md)
* [Snapshot Verification](proposals/snapshot-verification.md)
* [Bankless Leader](proposals/bankless-leader.md)
* [Durable Transaction Nonces](proposals/durable-tx-nonces.md)
* [Implemented Design Proposals](implemented-proposals/README.md)
* [Blocktree](implemented-proposals/blocktree.md)
* [Cluster Software Installation and Updates](implemented-proposals/installer.md)
@ -84,5 +79,6 @@
* [Reliable Vote Transmission](implemented-proposals/reliable-vote-transmission.md)
* [Repair Service](implemented-proposals/repair-service.md)
* [Testing Programs](implemented-proposals/testing-programs.md)
* [Credit-only Accounts](implemented-proposals/readonly-accounts.md)
* [Credit-only Accounts](implemented-proposals/credit-only-credit-debit-accounts.md)
* [Embedding the Move Langauge](implemented-proposals/embedding-move.md)

View File

@ -24,5 +24,5 @@ The stream will output a series of JSON objects:
* `s`, the slot height, as unsigned 64-bit integer
* `h`, the tick height, as unsigned 64-bit integer
* `l`, the slot leader id, as base-58 encoded string
* `hash`, the [blockhash](terminology.md#blockhash), as base-58 encoded string
* `id`, the block id, as base-58 encoded string

View File

@ -177,7 +177,7 @@ $ solana send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
## Usage
### solana-cli
```text
solana-cli 0.21.2
solana-cli 0.20.0
Blockchain, Rebuilt for Scale
USAGE:
@ -200,7 +200,7 @@ SUBCOMMANDS:
claim-storage-reward Redeem storage reward credits
cluster-version Get the version of the cluster entrypoint
confirm Confirm transaction by signature
create-archiver-storage-account Create an archiver storage account
create-archiver-storage-account Create an archiver storage account
create-stake-account Create a stake account
create-validator-storage-account Create a validator storage account
create-vote-account Create a vote account
@ -210,7 +210,7 @@ SUBCOMMANDS:
fees Display current cluster fees
get Get cli config settings
get-epoch-info Get information about the current epoch
get-genesis-hash Get the genesis hash
get-genesis-blockhash Get the genesis blockhash
get-slot Get current slot
get-transaction-count Get current transaction count
help Prints this message or the help of the given subcommand(s)
@ -236,7 +236,7 @@ SUBCOMMANDS:
#### solana-address
```text
solana-address
solana-address
Get your public key
USAGE:
@ -254,7 +254,7 @@ OPTIONS:
#### solana-airdrop
```text
solana-airdrop
solana-airdrop
Request lamports
USAGE:
@ -278,7 +278,7 @@ ARGS:
#### solana-balance
```text
solana-balance
solana-balance
Get your balance
USAGE:
@ -300,7 +300,7 @@ ARGS:
#### solana-cancel
```text
solana-cancel
solana-cancel
Cancel a transfer
USAGE:
@ -321,7 +321,7 @@ ARGS:
#### solana-claim-storage-reward
```text
solana-claim-storage-reward
solana-claim-storage-reward
Redeem storage reward credits
USAGE:
@ -343,7 +343,7 @@ ARGS:
#### solana-cluster-version
```text
solana-cluster-version
solana-cluster-version
Get the version of the cluster entrypoint
USAGE:
@ -361,7 +361,7 @@ OPTIONS:
#### solana-confirm
```text
solana-confirm
solana-confirm
Confirm transaction by signature
USAGE:
@ -382,7 +382,7 @@ ARGS:
#### solana-create-archiver-storage-account
```text
solana-create-archiver-storage-account
solana-create-archiver-storage-account
Create an archiver storage account
USAGE:
@ -398,13 +398,13 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STORAGE ACCOUNT OWNER PUBKEY>
<STORAGE ACCOUNT PUBKEY>
<STORAGE ACCOUNT OWNER PUBKEY>
<STORAGE ACCOUNT PUBKEY>
```
#### solana-create-stake-account
```text
solana-create-stake-account
solana-create-stake-account
Create a stake account
USAGE:
@ -432,7 +432,7 @@ ARGS:
#### solana-create-validator-storage-account
```text
solana-create-validator-storage-account
solana-create-validator-storage-account
Create a validator storage account
USAGE:
@ -448,13 +448,13 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STORAGE ACCOUNT OWNER PUBKEY>
<STORAGE ACCOUNT PUBKEY>
<STORAGE ACCOUNT OWNER PUBKEY>
<STORAGE ACCOUNT PUBKEY>
```
#### solana-create-vote-account
```text
solana-create-vote-account
solana-create-vote-account
Create a vote account
USAGE:
@ -467,7 +467,7 @@ FLAGS:
OPTIONS:
--authorized-voter <PUBKEY> Public key of the authorized voter (defaults to vote account)
--authorized-withdrawer <PUBKEY> Public key of the authorized withdrawer (defaults to cli config pubkey)
--commission <NUM> The commission taken on reward redemption (0-100), default: 0
--commission <NUM> The commission taken on reward redemption (0-255), default: 0
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
@ -480,7 +480,7 @@ ARGS:
#### solana-deactivate-stake
```text
solana-deactivate-stake
solana-deactivate-stake
Deactivate the delegated stake from the stake account
USAGE:
@ -501,7 +501,7 @@ ARGS:
#### solana-delegate-stake
```text
solana-delegate-stake
solana-delegate-stake
Delegate stake to a vote account
USAGE:
@ -523,7 +523,7 @@ ARGS:
#### solana-deploy
```text
solana-deploy
solana-deploy
Deploy a program
USAGE:
@ -544,7 +544,7 @@ ARGS:
#### solana-fees
```text
solana-fees
solana-fees
Display current cluster fees
USAGE:
@ -562,7 +562,7 @@ OPTIONS:
#### solana-get
```text
solana-get
solana-get
Get cli config settings
USAGE:
@ -583,7 +583,7 @@ ARGS:
#### solana-get-epoch-info
```text
solana-get-epoch-info
solana-get-epoch-info
Get information about the current epoch
USAGE:
@ -599,13 +599,13 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
```
#### solana-get-genesis-hash
#### solana-get-genesis-blockhash
```text
solana-get-genesis-hash
Get the genesis hash
solana-get-genesis-blockhash
Get the genesis blockhash
USAGE:
solana get-genesis-hash [OPTIONS]
solana get-genesis-blockhash [OPTIONS]
FLAGS:
-h, --help Prints help information
@ -619,7 +619,7 @@ OPTIONS:
#### solana-get-slot
```text
solana-get-slot
solana-get-slot
Get current slot
USAGE:
@ -637,7 +637,7 @@ OPTIONS:
#### solana-get-transaction-count
```text
solana-get-transaction-count
solana-get-transaction-count
Get current transaction count
USAGE:
@ -655,7 +655,7 @@ OPTIONS:
#### solana-help
```text
solana-help
solana-help
Prints this message or the help of the given subcommand(s)
USAGE:
@ -667,14 +667,14 @@ ARGS:
#### solana-pay
```text
solana-pay
solana-pay
Send a payment
USAGE:
solana pay [FLAGS] [OPTIONS] <PUBKEY> <AMOUNT> [--] [UNIT]
FLAGS:
--cancelable
--cancelable
-h, --help Prints help information
-V, --version Prints version information
@ -695,7 +695,7 @@ ARGS:
#### solana-ping
```text
solana-ping
solana-ping
Submit transactions sequentially
USAGE:
@ -716,7 +716,7 @@ OPTIONS:
#### solana-redeem-vote-credits
```text
solana-redeem-vote-credits
solana-redeem-vote-credits
Redeem credits in the stake account
USAGE:
@ -738,7 +738,7 @@ ARGS:
#### solana-send-signature
```text
solana-send-signature
solana-send-signature
Send a signature to authorize a transfer
USAGE:
@ -760,7 +760,7 @@ ARGS:
#### solana-send-timestamp
```text
solana-send-timestamp
solana-send-timestamp
Send a timestamp to unlock a transfer
USAGE:
@ -783,7 +783,7 @@ ARGS:
#### solana-set
```text
solana-set
solana-set
Set a cli config setting
USAGE:
@ -801,7 +801,7 @@ OPTIONS:
#### solana-show-account
```text
solana-show-account
solana-show-account
Show the contents of an account
USAGE:
@ -824,7 +824,7 @@ ARGS:
#### solana-show-stake-account
```text
solana-show-stake-account
solana-show-stake-account
Show the contents of a stake account
USAGE:
@ -846,7 +846,7 @@ ARGS:
#### solana-show-storage-account
```text
solana-show-storage-account
solana-show-storage-account
Show the contents of a storage account
USAGE:
@ -867,7 +867,7 @@ ARGS:
#### solana-show-validators
```text
solana-show-validators
solana-show-validators
Show information about the current validators
USAGE:
@ -886,7 +886,7 @@ OPTIONS:
#### solana-show-vote-account
```text
solana-show-vote-account
solana-show-vote-account
Show the contents of a vote account
USAGE:
@ -908,7 +908,7 @@ ARGS:
#### solana-stake-authorize-staker
```text
solana-stake-authorize-staker
solana-stake-authorize-staker
Authorize a new stake signing keypair for the given stake account
USAGE:
@ -930,7 +930,7 @@ ARGS:
#### solana-stake-authorize-withdrawer
```text
solana-stake-authorize-withdrawer
solana-stake-authorize-withdrawer
Authorize a new withdraw signing keypair for the given stake account
USAGE:
@ -952,7 +952,7 @@ ARGS:
#### solana-uptime
```text
solana-uptime
solana-uptime
Show the uptime of a validator, based on epoch voting history
USAGE:
@ -975,7 +975,7 @@ ARGS:
#### solana-validator-info
```text
solana-validator-info
solana-validator-info
Publish/get Validator info on Solana
USAGE:
@ -998,7 +998,7 @@ SUBCOMMANDS:
#### solana-vote-authorize-voter
```text
solana-vote-authorize-voter
solana-vote-authorize-voter
Authorize a new vote signing keypair for the given vote account
USAGE:
@ -1020,7 +1020,7 @@ ARGS:
#### solana-vote-authorize-withdrawer
```text
solana-vote-authorize-withdrawer
solana-vote-authorize-withdrawer
Authorize a new withdraw signing keypair for the given vote account
USAGE:
@ -1042,7 +1042,7 @@ ARGS:
#### solana-withdraw-stake
```text
solana-withdraw-stake
solana-withdraw-stake
Withdraw the unstaked lamports from the stake account
USAGE:
@ -1063,3 +1063,4 @@ ARGS:
<AMOUNT> The amount to withdraw from the stake account (default unit SOL)
<UNIT> Specify unit to use for request [possible values: SOL, lamports]
```

View File

@ -17,13 +17,11 @@ To interact with a Solana node inside a JavaScript application, use the [solana-
* [confirmTransaction](jsonrpc-api.md#confirmtransaction)
* [getAccountInfo](jsonrpc-api.md#getaccountinfo)
* [getBalance](jsonrpc-api.md#getbalance)
* [getBlockCommitment](jsonrpc-api.md#getblockcommitment)
* [getBlockTime](jsonrpc-api.md#getblocktime)
* [getBlockConfidence](jsonrpc-api.md#getblockconfidence)
* [getClusterNodes](jsonrpc-api.md#getclusternodes)
* [getConfirmedBlock](jsonrpc-api.md#getconfirmedblock)
* [getEpochInfo](jsonrpc-api.md#getepochinfo)
* [getEpochSchedule](jsonrpc-api.md#getepochschedule)
* [getGenesisHash](jsonrpc-api.md#getgenesishash)
* [getGenesisBlockhash](jsonrpc-api.md#getgenesisblockhash)
* [getLeaderSchedule](jsonrpc-api.md#getleaderschedule)
* [getMinimumBalanceForRentExemption](jsonrpc-api.md#getminimumbalanceforrentexemption)
* [getNumBlocksSinceSignatureConfirmation](jsonrpc-api.md#getnumblockssincesignatureconfirmation)
@ -80,31 +78,6 @@ Requests can be sent in batches by sending an array of JSON-RPC request objects
* Signature: An Ed25519 signature of a chunk of data.
* Transaction: A Solana instruction signed by a client key-pair.
## Configuring State Commitment
Solana nodes choose which bank state to query based on a commitment requirement
set by the client. Clients may specify either:
* `{"commitment":"max"}` - the node will query the most recent bank having reached `MAX_LOCKOUT_HISTORY` confirmations
* `{"commitment":"recent"}` - the node will query its most recent bank state
The commitment parameter should be included as the last element in the `params` array:
```bash
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri",{"commitment":"max"}]}' 192.168.1.88:8899
```
#### Default:
If commitment configuration is not provided, the node will default to `"commitment":"max"`
Only methods that query bank state accept the commitment parameter. They are indicated in the API Reference below.
#### RpcResponse Structure
Many methods that take a commitment parameter return an RpcResponse JSON object comprised of two parts:
* `context` : An RpcResponseContext JSON structure including a `slot` field at which the operation was evaluated.
* `value` : The value returned by the operation itself.
## JSON RPC API Reference
### confirmTransaction
@ -114,11 +87,10 @@ Returns a transaction receipt
#### Parameters:
* `string` - Signature of Transaction to confirm, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `RpcResponse<boolean>` - RpcResponse JSON object with `value` field set to Transaction status, boolean true if Transaction is confirmed
* `boolean` - Transaction status, true if Transaction is confirmed
#### Example:
@ -127,7 +99,7 @@ Returns a transaction receipt
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"confirmTransaction", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":true},"id":1}
{"jsonrpc":"2.0","result":true,"id":1}
```
### getAccountInfo
@ -137,13 +109,11 @@ Returns all information associated with the account of provided Pubkey
#### Parameters:
* `string` - Pubkey of account to query, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
The result value will be an RpcResponse JSON object containing an AccountInfo JSON object.
The result field will be a JSON object with the following sub fields:
* `RpcResponse<AccountInfo>`, RpcResponse JSON object with `value` field set to AccountInfo, a JSON object containing:
* `lamports`, number of lamports assigned to this account, as a signed 64-bit integer
* `owner`, array of 32 bytes representing the program this account has been assigned to
* `data`, array of bytes representing any data associated with the account
@ -156,7 +126,7 @@ The result value will be an RpcResponse JSON object containing an AccountInfo JS
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"executable":false,"owner":[1,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,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0.21.2,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]}},"id":1}
{"jsonrpc":"2.0","result":{"executable":false,"owner":[1,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,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"id":1}
```
### getBalance
@ -166,11 +136,10 @@ Returns the balance of the account of provided Pubkey
#### Parameters:
* `string` - Pubkey of account to query, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `RpcResponse<integer>` - RpcResponse JSON object with `value` field set to quantity, as a signed 64-bit integer
* `integer` - quantity, as a signed 64-bit integer
#### Example:
@ -179,12 +148,12 @@ Returns the balance of the account of provided Pubkey
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":0},"id":1}
{"jsonrpc":"2.0","result":0,"id":1}
```
### getBlockCommitment
### getBlockConfidence
Returns commitment for particular block
Returns confidence for particular block
#### Parameters:
@ -194,45 +163,20 @@ Returns commitment for particular block
The result field will be an array with two fields:
* Commitment
* Confidence
* `null` - Unknown block
* `object` - BlockCommitment
* `array` - commitment, array of u64 integers logging the amount of cluster stake in lamports that has voted on the block at each depth from 0 to `MAX_LOCKOUT_HISTORY`
* `object` - BankConfidence
* `array` - confidence, array of u64 integers logging the amount of cluster stake in lamports that has voted on the block at each depth from 0 to `MAX_LOCKOUT_HISTORY`
* 'integer' - total active stake, in lamports, of the current epoch
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getBlockCommitment","params":[5]}' http://localhost:8899
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getBlockConfidence","params":[5]}' http://localhost:8899
// Result
{"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}
{"jsonrpc":"2.0","result":[{"confidence":[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}
```
### getClusterNodes
@ -262,46 +206,13 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
{"jsonrpc":"2.0","result":[{"gossip":"10.239.6.48:8001","pubkey":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"}],"id":1}
```
### getConfirmedBlock
Returns identity and transaction information about a confirmed block in the ledger
#### Parameters:
* `integer` - slot, as u64 integer
#### Results:
The result field will be an object with the following fields:
* `blockhash` - the blockhash of this block
* `previousBlockhash` - the blockhash of this block's parent
* `parentSlot` - the slot index of this block's parent
* `transactions` - an array of tuples containing:
* [Transaction](transaction-api.md) object, in JSON format
* Transaction status object, containing:
* `status` - Transaction status:
* `"Ok": null` - Transaction was successful
* `"Err": <ERR>` - Transaction failed with TransactionError [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
* `fee` - fee this transaction was charged, as u64 integer
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430]}' localhost:8899
// Result
{"jsonrpc":"2.0","result":{"blockhash":[165,245,120,183,32,205,89,222,249,114,229,49,250,231,149,122,156,232,181,83,238,194,157,153,7,213,180,54,177,6,25,101],"parentSlot":429,"previousBlockhash":[21,108,181,90,139,241,212,203,45,78,232,29,161,31,159,188,110,82,81,11,250,74,47,140,188,28,23,96,251,164,208,166],"transactions":[[{"message":{"accountKeys":[[5],[219,181,202,40,52,148,34,136,186,59,137,160,250,225,234,17,244,160,88,116,24,176,30,227,68,11,199,38,141,68,131,228],[233,48,179,56,91,40,254,206,53,48,196,176,119,248,158,109,121,77,11,69,108,160,128,27,228,122,146,249,53,184,68,87],[6,167,213,23,25,47,10,175,198,242,101,227,251,119,204,122,218,130,197,41,208,190,59,19,110,45,0,85,32,0,0,0],[6,167,213,23,24,199,116,201,40,86,99,152,105,29,94,182,139,94,184,163,155,75,109,92,115,85,91,33,0,0,0,0],[7,97,72,29,53,116,116,187,124,77,118,36,235,211,189,179,216,53,94,115,209,16,67,252,13,163,83,128,0,0,0,0]],"header":{"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":3,"numRequiredSignatures":2},"instructions":[[1],{"accounts":[[3],1,2,3],"data":[[52],2,0,0,0,1,0,0,0,0,0,0,0,173,1,0,0,0,0,0,0,86,55,9,248,142,238,135,114,103,83,247,124,67,68,163,233,55,41,59,129,64,50,110,221,234,234,27,213,205,193,219,50],"program_id_index":4}],"recentBlockhash":[21,108,181,90,139,241,212,203,45,78,232,29,161,31,159,188,110,82,81,11,250,74,47,140,188,28,23,96,251,164,208,166]},"signatures":[[2],[119,9,95,108,35,95,7,1,69,101,65,45,5,204,61,114,172,88,123,238,32,201,135,229,57,50,13,21,106,216,129,183,238,43,37,101,148,81,56,232,88,136,80,65,46,189,39,106,94,13,238,54,186,48,118,186,0,62,121,122,172,171,66,5],[78,40,77,250,10,93,6,157,48,173,100,40,251,9,7,218,7,184,43,169,76,240,254,34,235,48,41,175,119,126,75,107,106,248,45,161,119,48,174,213,57,69,111,225,245,60,148,73,124,82,53,6,203,126,120,180,111,169,89,64,29,23,237,13]]},{"fee":100000,"status":{"Ok":null}}]]},"id":1}
```
### getEpochInfo
Returns information about the current epoch
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
None
#### Results:
@ -323,7 +234,7 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
### getEpochSchedule
Returns epoch schedule information from this cluster's genesis config
Returns epoch schedule information from this cluster's genesis block
#### Parameters:
@ -349,9 +260,9 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
{"jsonrpc":"2.0","result":{"first_normal_epoch":8,"first_normal_slot":8160,"leader_schedule_slot_offset":8192,"slots_per_epoch":8192,"warmup":true},"id":1}
```
### getGenesisHash
### getGenesisBlockhash
Returns the genesis hash
Returns the genesis block hash
#### Parameters:
@ -365,7 +276,7 @@ None
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getGenesisHash"}' http://localhost:8899
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getGenesisBlockhash"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
@ -377,7 +288,7 @@ Returns the leader schedule for the current epoch
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
None
#### Results:
@ -400,7 +311,6 @@ Returns minimum balance required to make account rent exempt.
#### Parameters:
* `integer` - account data length, as unsigned integer
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
@ -423,7 +333,6 @@ Returns the current number of blocks since signature has been confirmed.
#### Parameters:
* `string` - Signature of Transaction to confirm, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
@ -446,7 +355,6 @@ Returns all accounts owned by the provided program Pubkey
#### Parameters:
* `string` - Pubkey of program, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
@ -474,13 +382,12 @@ Returns a recent block hash from the ledger, and a fee schedule that can be used
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
None
#### Results:
An RpcResponse containing an array consisting of a string blockhash and FeeCalculator JSON object.
An array consisting of
* `RpcResponse<array>` - RpcResponse JSON object with `value` field set to an array including:
* `string` - a Hash as base-58 encoded string
* `FeeCalculator object` - the fee schedule for this block hash
@ -491,7 +398,7 @@ An RpcResponse containing an array consisting of a string blockhash and FeeCalcu
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC",{"lamportsPerSignature": 0}]},"id":1}
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC",{"lamportsPerSignature": 0}],"id":1}
```
### getSignatureStatus
@ -501,7 +408,6 @@ Returns the status of a given signature. This method is similar to [confirmTrans
#### Parameters:
* `string` - Signature of Transaction to confirm, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
@ -526,7 +432,7 @@ Returns the current slot the node is processing
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
None
#### Results:
@ -548,7 +454,7 @@ Returns the current slot leader
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
None
#### Results:
@ -570,7 +476,7 @@ Returns the current storage segment size in terms of slots
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
None
#### Results:
@ -636,7 +542,7 @@ Returns the current Transaction count from the ledger
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
None
#### Results:
@ -658,7 +564,7 @@ Returns the current total supply in Lamports
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
None
#### Results:
@ -703,7 +609,7 @@ Returns the account info and associated stake for all the voting accounts in the
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
None
#### Results:
@ -713,7 +619,7 @@ The result field will be a JSON object of `current` and `delinquent` accounts, e
* `nodePubkey` - Node public key, as base-58 encoded string
* `activatedStake` - the stake, in lamports, delegated to this vote account and active in this epoch
* `epochVoteAccount` - bool, whether the vote account is staked for this epoch
* `commission`, percentage (0-100) of rewards payout owed to the vote account
* `commission`, an 8-bit integer used as a fraction \(commission/MAX\_U8\) for rewards payout
* `lastVote` - Most recent slot voted on by this vote account
#### Example:
@ -734,7 +640,6 @@ Requests an airdrop of lamports to a Pubkey
* `string` - Pubkey of account to receive lamports, as base-58 encoded string
* `integer` - lamports, as a signed 64-bit integer
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) (used for retrieving blockhash and verifying airdrop success)
#### Results:
@ -824,7 +729,7 @@ Subscribe to an account to receive notifications when the lamports or data for a
#### Notification Format:
```bash
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"owner":[1,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,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0.21.2,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"owner":[1,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,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
```
### accountUnsubscribe
@ -882,7 +787,7 @@ Subscribe to a program to receive notifications when the lamports or data for a
* `object` - account info JSON object \(see [getAccountInfo](jsonrpc-api.md#getaccountinfo) for field details\)
```bash
{"jsonrpc":"2.0","method":"programNotification","params":{{"result":["8Rshv2oMkPu5E4opXTRyuyBeZBqQ4S477VG26wUTFxUM",{"executable":false,"lamports":1,"owner":[129,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,0,0],"data":[1,1,1,0,0,0,0,0,0,0.21.2,0,0,0,0,0,0,50,48,49,56,45,49,50,45,50,52,84,50,51,58,53,57,58,48,48,90,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,55,89,0,0,0,0,50,0,0,0,0,0,0,0,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,45,4,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,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}],"subscription":0}}
{"jsonrpc":"2.0","method":"programNotification","params":{{"result":["8Rshv2oMkPu5E4opXTRyuyBeZBqQ4S477VG26wUTFxUM",{"executable":false,"lamports":1,"owner":[129,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,0,0],"data":[1,1,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,49,56,45,49,50,45,50,52,84,50,51,58,53,57,58,48,48,90,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,55,89,0,0,0,0,50,0,0,0,0,0,0,0,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,45,4,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,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}],"subscription":0}}
```
### programUnsubscribe

View File

@ -14,7 +14,7 @@
* **num\_credit\_only\_signed\_accounts:** The last
`num_readonly_signed_accounts` signatures refer to signing
`num_credit_only_signed_accounts` signatures refer to signing
credit only accounts. Credit only accounts can be used concurrently
@ -24,7 +24,7 @@
* **num\_credit\_only\_unsigned\_accounts:** The last
`num_readonly_unsigned_accounts` public keys in `account_keys` refer
`num_credit_only_unsigned_accounts` public keys in `account_keys` refer
to non-signing credit only accounts
@ -60,3 +60,4 @@ A `Transaction` is signed by using an ed25519 keypair to sign the serialization
## Transaction Serialization
`Transaction`s \(and their `message`s\) are serialized and deserialized using the [bincode](https://crates.io/crates/bincode) crate with a non-standard vector serialization that uses only one byte for the length if it can be encoded in 7 bits, 2 bytes if it fits in 14 bits, or 3 bytes if it requires 15 or 16 bits. The vector serialization is defined by Solana's [short-vec](https://github.com/solana-labs/solana/blob/master/sdk/src/short_vec.rs).

View File

@ -4,7 +4,7 @@ A Solana cluster is a set of validators working together to serve client transac
## Creating a Cluster
Before starting any validators, one first needs to create a _genesis config_. The config references two public keys, a _mint_ and a _bootstrap leader_. The validator holding the bootstrap leader's private key is responsible for appending the first entries to the ledger. It initializes its internal state with the mint's account. That account will hold the number of native tokens defined by the genesis config. The second validator then contacts the bootstrap leader to register as a _validator_ or _archiver_. Additional validators then register with any registered member of the cluster.
Before starting any validators, one first needs to create a _genesis block_. The block contains entries referencing two public keys, a _mint_ and a _bootstrap leader_. The validator holding the bootstrap leader's private key is responsible for appending the first entries to the ledger. It initializes its internal state with the mint's account. That account will hold the number of native tokens defined by the genesis block. The second validator then contacts the bootstrap leader to register as a _validator_ or _archiver_. Additional validators then register with any registered member of the cluster.
A validator receives all entries from the leader and submits votes confirming those entries are valid. After voting, the validator is expected to store those entries until archiver nodes submit proofs that they have stored copies of it. Once the validator observes a sufficient number of copies exist, it deletes its copy.
@ -38,3 +38,4 @@ Solana rotates leaders at fixed intervals, called _slots_. Each leader may only
Next, transactions are broken into batches so that a node can send transactions to multiple parties without making multiple copies. If, for example, the leader needed to send 60 transactions to 6 nodes, it would break that collection of 60 into batches of 10 transactions and send one to each node. This allows the leader to put 60 transactions on the wire, not 60 transactions for each node. Each node then shares its batch with its peers. Once the node has collected all 6 batches, it reconstructs the original set of 60 transactions.
A batch of transactions can only be split so many times before it is so small that header information becomes the primary consumer of network bandwidth. At the time of this writing, the approach is scaling well up to about 150 validators. To scale up to hundreds of thousands of validators, each node can apply the same technique as the leader node to another set of nodes of equal size. We call the technique _data plane fanout_; learn more in the [data plan fanout](https://github.com/solana-labs/solana/tree/aacead62c0eb052068172eba6b53fc85874d6d54/book/src/data-plane-fanout.md) section.

View File

@ -12,7 +12,7 @@ Nodes take turns being leader and generating the PoH that encodes state changes.
2. Leader filters valid transactions.
3. Leader executes valid transactions updating its state.
4. Leader packages transactions into entries based off its current PoH slot.
5. Leader transmits the entries to validator nodes \(in signed shreds\) 1. The PoH stream includes ticks; empty entries that indicate liveness of
5. Leader transmits the entries to validator nodes \(in signed blobs\) 1. The PoH stream includes ticks; empty entries that indicate liveness of
the leader and the passage of time on the cluster.

View File

@ -40,7 +40,7 @@ After observing the cluster for a sufficient amount of time, the leader schedule
## Leader Schedule Generation at Genesis
The genesis config declares the first leader for the first epoch. This leader ends up scheduled for the first two epochs because the leader schedule is also generated at slot 0 for the next epoch. The length of the first two epochs can be specified in the genesis config as well. The minimum length of the first epochs must be greater than or equal to the maximum rollback depth as defined in [Tower BFT](../implemented-proposals/tower-bft.md).
The genesis block declares the first leader for the first epoch. This leader ends up scheduled for the first two epochs because the leader schedule is also generated at slot 0 for the next epoch. The length of the first two epochs can be specified in the genesis block as well. The minimum length of the first epochs must be greater than or equal to the maximum rollback depth as defined in [Tower BFT](../implemented-proposals/tower-bft.md).
## Leader Schedule Generation Algorithm
@ -73,7 +73,7 @@ The seed that is selected is predictable but unbiasable. There is no grinding at
A leader can bias the active set by censoring validator votes. Two possible ways exist for leaders to censor the active set:
* Ignore votes from validators
* Ignore votes from validators
* Refuse to vote for blocks with votes from validators
To reduce the likelihood of censorship, the active set is calculated at the leader schedule offset boundary over an _active set sampling duration_. The active set sampling duration is long enough such that votes will have been collected by multiple leaders.
@ -95,3 +95,4 @@ The lifetime of a leader schedule is called an _epoch_. The epoch is split into
A leader transmits entries during its slot. After `T` ticks, all the validators switch to the next scheduled leader. Validators must ignore entries sent outside a leader's assigned slot.
All `T` ticks must be observed by the next leader for it to build its own entries on. If entries are not observed \(leader is down\) or entries are invalid \(leader is buggy or malicious\), the next leader must produce ticks to fill the previous leader's slot. Note that the next leader should do repair requests in parallel, and postpone sending ticks until it is confident other validators also failed to observe the previous leader's entries. If a leader incorrectly builds on its own ticks, the leader following it must replace all its ticks.

View File

@ -16,7 +16,7 @@ The total stake allocated to a Vote account can be calculated by the sum of all
## Vote and Stake accounts
The rewards process is split into two on-chain programs. The Vote program solves the problem of making stakes slashable. The Stake program acts as custodian of the rewards pool and provides for passive delegation. The Stake program is responsible for paying rewards to staker and voter when shown that a staker's delegate has participated in validating the ledger.
The rewards process is split into two on-chain programs. The Vote program solves the problem of making stakes slashable. The Stake account acts as custodian of the rewards pool, and provides passive delegation. The Stake program is responsible for paying out each staker once the staker proves to the Stake program that its delegate has participated in validating the ledger.
### VoteState
@ -76,7 +76,7 @@ StakeState::Stake is the current delegation preference of the **staker** and con
* `deactivated` - the epoch at which this stake was de-activated, some cool down epochs are required before the account
```text
is fully deactivated, and the stake available for withdrawal
is fully deactivated, and the stake available for withdrawal
```
* `authorized_staker` - the pubkey of the entity that must sign delegation, activation, and deactivation transactions
@ -94,7 +94,7 @@ The Stakes and the RewardsPool are accounts that are owned by the same `Stake` p
### StakeInstruction::DelegateStake
The Stake account is moved from Ininitialized to StakeState::Stake form. This is how stakers choose their initial delegate validator node and activate their stake account lamports. The transaction must be signed by the stake's `authorized_staker`. If the stake account is already StakeState::Stake \(i.e. already activated\), the stake is re-delegated. Stakes may be re-delegated at any time, and updated stakes are reflected immediately, but only one re-delegation is permitted per epoch.
The Stake account is moved from Ininitialized to StakeState::Stake form. This is how stakers choose their initial delegate validator node and activate their stake account lamports. If the stake account is already StakeState::Stake \(i.e. already activated\), the stake is re-delegated The transaction must be signed by the stake's `authorized_staker`.
* `account[0]` - RW - The StakeState::Stake instance. `StakeState::Stake::credits_observed` is initialized to `VoteState::credits`, `StakeState::Stake::voter_pubkey` is initialized to `account[1]`. If this is the initial delegation of stake, `StakeState::Stake::stake` is initialized to the account's balance in lamports, `StakeState::Stake::activated` is initialized to the current Bank epoch, and `StakeState::Stake::deactivated` is initialized to std::u64::MAX
* `account[1]` - R - The VoteState instance.
@ -132,7 +132,7 @@ stake_state.credits_observed = vote_state.credits;
### StakeInstruction::Deactivate
A staker may wish to withdraw from the network. To do so he must first deactivate his stake, and wait for cool down.
A staker may wish to withdraw from the network. To do so he must first deactivate his stake, and wait for cool down.
The transaction must be signed by the stake's `authorized_staker`.
* `account[0]` - RW - The StakeState::Stake instance that is deactivating.
@ -228,4 +228,5 @@ Only lamports in excess of effective+activating stake may be withdrawn at any ti
### Lock-up
Stake accounts support the notion of lock-up, wherein the stake account balance is unavailable for withdrawal until a specified time. Lock-up is specified as an epoch height, i.e. the minimum epoch height that must be reached by the network before the stake account balance is available for withdrawal, unless the transaction is also signed by a specified custodian. This information is gathered when the stake account is created, and stored in the Lockup field of the stake account's state.
Stake accounts support the notion of lock-up, wherein the stake account balance is unavailable for withdrawal until a specified time. Lock-up is specified as a slot height, i.e. the minimum slot height that must be reached by the network before the stake account balance is available for withdrawal, except to a specified custodian. This information is gathered when the stake account is created.

View File

@ -36,59 +36,6 @@ Finally, the following diagram shows a two layer cluster with a Fanout of 2.
Currently, configuration is set when the cluster is launched. In the future, these parameters may be hosted on-chain, allowing modification on the fly as the cluster sizes change.
## Calcuating the required FEC rate
Turbine relies on retransmission of packets between validators. Due to
retransmission, any network wide packet loss is compounded, and the
probability of the packet failing to reach is destination increases
on each hop. The FEC rate needs to take into account the network wide
packet loss, and the propagation depth.
A shred group is the set of data and coding packets that can be used
to reconstruct each other. Each shred group has a chance of failure,
based on the likelyhood of the number of packets failing that exceeds
the FEC rate. If a validator fails to reconstruct the shred group,
then the block cannot be reconstructed, and the validator has to rely
on repair to fixup the blocks.
The probability of the shred group failing can be computed using the
binomial distribution. If the FEC rate is `16:4`, then the group size
is 20, and at least 4 of the shreds must fail for the group to fail.
Which is equal to the sum of the probability of 4 or more trails failing
out of 20.
Probability of a block succeeding in turbine:
* Probability of packet failure: `P = 1 - (1 - network_packet_loss_rate)^2`
* FEC rate: `K:M`
* Number of trials: `N = K + M`
* Shred group failure rate: `S = SUM of i=0 -> M for binomial(prob_failure = P, trials = N, failures = i)`
* Shreds per block: `G`
* Block success rate: `B = (1 - S) ^ (G / N) `
* Binomial distribution for exactly `i` results with probability of P in N trials is defined as `(N choose i) * P^i * (1 - P)^(N-i)`
For example:
* Network packet loss rate is 15%.
* 50kpts network generates 6400 shreds per second.
* FEC rate increases the total shres per block by the FEC ratio.
With a FEC rate: `16:4`
* `G = 8000`
* `P = 1 - 0.85 * 0.85 = 1 - 0.7225 = 0.2775`
* `S = SUM of i=0 -> 4 for binomial(prob_failure = 0.2775, trials = 20, failures = i) = 0.689414`
* `B = (1 - 0.689) ^ (8000 / 20) = 10^-203`
With FEC rate of `16:16`
* `G = 12800`
* `S = SUM of i=0 -> 32 for binomial(prob_failure = 0.2775, trials = 64, failures = i) = 0.002132`
* `B = (1 - 0.002132) ^ (12800 / 32) = 0.42583`
With FEC rate of `32:32`
* `G = 12800`
* `S = SUM of i=0 -> 32 for binomial(prob_failure = 0.2775, trials = 64, failures = i) = 0.000048`
* `B = (1 - 0.000048) ^ (12800 / 64) = 0.99045`
## Neighborhoods
The following diagram shows how two neighborhoods in different layers interact. To cripple a neighborhood, enough nodes \(erasure codes +1\) from the neighborhood above need to fail. Since each neighborhood receives shreds from multiple nodes in a neighborhood in the upper layer, we'd need a big network failure in the upper layers to end up with incomplete data.

View File

@ -27,17 +27,16 @@ $ git checkout $TAG
### Configuration Setup
Ensure important programs such as the vote program are built before any nodes are started. Note that we are using the release build here for good performance.
If you want the debug build, use just `cargo build` and omit the `NDEBUG=1` part of the command.
Ensure important programs such as the vote program are built before any nodes are started
```bash
$ cargo build --release
$ cargo build
```
The network is initialized with a genesis ledger generated by running the following script.
```bash
$ NDEBUG=1 ./multinode-demo/setup.sh
$ ./multinode-demo/setup.sh
```
### Drone
@ -47,7 +46,7 @@ In order for the validators and clients to work, we'll need to spin up a drone t
Start the drone with:
```bash
$ NDEBUG=1 ./multinode-demo/drone.sh
$ ./multinode-demo/drone.sh
```
### Singlenode Testnet
@ -57,7 +56,7 @@ Before you start a validator, make sure you know the IP address of the machine y
Now start the bootstrap leader in a separate shell:
```bash
$ NDEBUG=1 ./multinode-demo/bootstrap-leader.sh
$ ./multinode-demo/bootstrap-leader.sh
```
Wait a few seconds for the server to initialize. It will print "leader ready..." when it's ready to receive transactions. The leader will request some tokens from the drone if it doesn't have any. The drone does not need to be running for subsequent leader starts.
@ -67,15 +66,15 @@ Wait a few seconds for the server to initialize. It will print "leader ready..."
To run a multinode testnet, after starting a leader node, spin up some additional validators in separate shells:
```bash
$ NDEBUG=1 ./multinode-demo/validator-x.sh
$ ./multinode-demo/validator-x.sh
```
To run a performance-enhanced validator on Linux, [CUDA 10.0](https://developer.nvidia.com/cuda-downloads) must be installed on your system:
```bash
$ ./fetch-perf-libs.sh
$ NDEBUG=1 SOLANA_CUDA=1 ./multinode-demo/bootstrap-leader.sh
$ NDEBUG=1 SOLANA_CUDA=1 ./multinode-demo/validator.sh
$ SOLANA_CUDA=1 ./multinode-demo/bootstrap-leader.sh
$ SOLANA_CUDA=1 ./multinode-demo/validator.sh
```
### Testnet Client Demo
@ -85,7 +84,7 @@ Now that your singlenode or multinode testnet is up and running let's send it so
In a separate shell start the client:
```bash
$ NDEBUG=1 ./multinode-demo/bench-tps.sh # runs against localhost by default
$ ./multinode-demo/bench-tps.sh # runs against localhost by default
```
What just happened? The client demo spins up several threads to send 500,000 transactions to the testnet as quickly as it can. The client then pings the testnet periodically to see how many transactions it processed in that time. Take note that the demo intentionally floods the network with UDP packets, such that the network will almost certainly drop a bunch of them. This ensures the testnet has an opportunity to reach 710k TPS. The client demo completes after it has convinced itself the testnet won't process any additional transactions. You should see several TPS measurements printed to the screen. In the multinode variation, you'll see TPS measurements for each validator node as well.
@ -126,7 +125,7 @@ This will dump all the threads stack traces into gdb.txt
In this example the client connects to our public testnet. To run validators on the testnet you would need to open udp ports `8000-10000`.
```bash
$ NDEBUG=1 ./multinode-demo/bench-tps.sh --entrypoint testnet.solana.com:8001 --drone testnet.solana.com:9900 --duration 60 --tx_count 50
$ ./multinode-demo/bench-tps.sh --entrypoint testnet.solana.com:8001 --drone testnet.solana.com:9900 --duration 60 --tx_count 50
```
You can observe the effects of your client's transactions on our [dashboard](https://metrics.solana.com:3000/d/testnet/testnet-hud?orgId=2&from=now-30m&to=now&refresh=5s&var-testnet=testnet)

View File

@ -1,8 +1,8 @@
# Commitment
The commitment metric aims to give clients a measure of the network confirmation
and stake levels on a particular block. Clients can then use this information to
derive their own measures of commitment.
and stake levels on a particular block. Clients can then use this information to
derive their own measures of confidence.
# Calculation RPC

View File

@ -0,0 +1,105 @@
# Credit-only Accounts
This design covers the handling of credit-only and credit-debit accounts in the [runtime](../validator/runtime.md). Accounts already distinguish themselves as credit-only or credit-debit based on the program ID specified by the transaction's instruction. Programs must treat accounts that are not owned by them as credit-only.
To identify credit-only accounts by program id would require the account to be fetched and loaded from disk. This operation is expensive, and while it is occurring, the runtime would have to reject any transactions referencing the same account.
The proposal introduces a `num_readonly_accounts` field to the transaction structure, and removes the `program_ids` dedicated vector for program accounts.
This design doesn't change the runtime transaction processing rules. Programs still can't write or spend accounts that they do not own, but it allows the runtime to optimistically take the correct lock for each account specified in the transaction before loading the accounts from storage.
Accounts selected as credit-debit by the transaction can still be treated as credit-only by the instructions.
## Runtime handling
credit-only accounts have the following properties:
* Can be deposited into: Deposits can be implemented as a simple `atomic_add`.
* read-only access to account data.
Instructions that debit or modify the credit-only account data will fail.
## Account Lock Optimizations
The Accounts module keeps track of current locked accounts in the runtime, which separates credit-only accounts from the credit-debit accounts. The credit-only accounts can be cached in memory and shared between all the threads executing transactions.
The current runtime can't predict whether an account is credit-only or credit-debit when the transaction account keys are locked at the start of the transaction processing pipeline. Accounts referenced by the transaction have not been loaded from the disk yet.
An ideal design would cache the credit-only accounts while they are referenced by any transaction moving through the runtime, and release the cache when the last transaction exits the runtime.
## Credit-only accounts and read-only account data
Credit-only account data can be treated as read-only. Credit-debit account data is treated as read-write.
## Transaction changes
To enable the possibility of caching accounts only while they are in the runtime, the Transaction structure should be changed in the following way:
* `program_ids: Vec<Pubkey>` - This vector is removed. Program keys can be placed at the end of the `account_keys` vector within the `num_readonly_accounts` number set to the number of programs.
* `num_readonly_accounts: u8` - The number of keys from the **end** of the transaction's `account_keys` array that is credit-only.
The following possible accounts are present in an transaction:
* paying account
* RW accounts
* R accounts
* Program IDs
The paying account must be credit-debit, and program IDs must be credit-only. The first account in the `account_keys` array is always the account that pays for the transaction fee, therefore it cannot be credit-only. For these reasons the credit-only accounts are all grouped together at the end of the `account_keys` vector. Counting credit-only accounts from the end allow for the default `0` value to still be functionally correct, since a transaction will succeed with all credit-debit accounts.
Since accounts can only appear once in the transaction's `account_keys` array, an account can only be credit-only or credit-debit in a single transaction, not both. The runtime treats a transaction as one atomic unit of execution. If any instruction needs credit-debit access to an account, a copy needs to be made. The write lock is held for the entire time the transaction is being processed by the runtime.
## Starvation
Read locks for credit-only accounts can keep the runtime from executing transactions requesting a write lock to a credit-debit account.
When a request for a write lock is made while a read lock is open, the transaction requesting the write lock should be cached. Upon closing the read lock, the pending transactions can be pushed through the runtime.
While a pending write transaction exists, any additional read lock requests for that account should fail. It follows that any other write lock requests will also fail. Currently, clients must retransmit when a transaction fails because of a pending transaction. This approach would mimic that behavior as closely as possible while preventing write starvation.
## Program execution with credit-only accounts
Before handing off the accounts to program execution, the runtime can mark each account in each instruction as a credit-only account. The credit-only accounts can be passed as references without an extra copy. The transaction will abort on a write to credit-only.
An alternative is to detect writes to credit-only accounts and fail the transactions before commit.
## Alternative design
This design attempts to cache a credit-only account after loading without the use of a transaction-specified credit-only accounts list. Instead, the credit-only accounts are held in a reference-counted table inside the runtime as the transactions are processed.
1. Transaction accounts are locked.
a. If the account is present in the credit-only' table, the TX does not fail.
```text
The pending state for this TX is marked NeedReadLock.
```
2. Transaction accounts are loaded.
a. Transaction accounts that are credit-only increase their reference
```text
count in the `credit-only` table.
```
b. Transaction accounts that need a write lock and are present in the
```text
`credit-only` table fail.
```
3. Transaction accounts are unlocked.
a. Decrement the `credit-only` lock table reference count; remove if its 0
b. Remove from the `lock` set if the account is not in the `credit-only`
```text
table.
```
The downside with this approach is that if the `lock` set mutex is released between lock and load to allow better pipelining of transactions, a request for a credit-only account may fail. Therefore, this approach is not suitable for treating programs as credit-only accounts.
Holding the accounts lock mutex while fetching the account from disk would potentially have a significant performance hit on the runtime. Fetching from disk is expected to be slow, but can be parallelized between multiple disks.

View File

@ -20,7 +20,7 @@ Slot leaders and validators use a PoH Recorder for both estimating slot height a
### PoH Recorder when Validating
The PoH Recorder acts as a simple VDF when validating. It tells the validator when it needs to switch to the slot leader role. Every time the validator votes on a fork, it should use the fork's latest [blockhash](terminology.md#blockhash) to re-seed the VDF. Re-seeding solves two problems. First, it synchronizes its VDF to the leader's, allowing it to more accurately determine when its leader slot begins. Second, if the previous leader goes down, all wallclock time is accounted for in the next leader's PoH stream. For example, if one block is missing when the leader starts, the block it produces should have a PoH duration of two blocks. The longer duration ensures the following leader isn't attempting to snip all the transactions from the previous leader's slot.
The PoH Recorder acts as a simple VDF when validating. It tells the validator when it needs to switch to the slot leader role. Every time the validator votes on a fork, it should use the fork's latest block id to re-seed the VDF. Re-seeding solves two problems. First, it synchronizes its VDF to the leader's, allowing it to more accurately determine when its leader slot begins. Second, if the previous leader goes down, all wallclock time is accounted for in the next leader's PoH stream. For example, if one block is missing when the leader starts, the block it produces should have a PoH duration of two blocks. The longer duration ensures the following leader isn't attempting to snip all the transactions from the previous leader's slot.
### PoH Recorder when Leading

View File

@ -1,23 +0,0 @@
# Read-Only Accounts
This design covers the handling of readonly and writable accounts in the [runtime](../validator/runtime.md). Multiple transactions that modify the same account must be processed serially so that they are always replayed in the same order. Otherwise, this could introduce non-determinism to the ledger. Some transactions, however, only need to read, and not modify, the data in particular accounts. Multiple transactions that only read the same account can be processed in parallel, since replay order does not matter, providing a performance benefit.
In order to identify readonly accounts, the transaction MessageHeader structure contains `num_readonly_signed_accounts` and `num_readonly_unsigned_accounts`. Instruction `program_ids` are included in the account vector as readonly, unsigned accounts, since executable accounts likewise cannot be modified during instruction processing.
## Runtime handling
Runtime transaction processing rules need to be updated slightly. Programs still can't write or spend accounts that they do not own. But new runtime rules ensure that readonly accounts cannot be modified, even by the programs that own them.
Readonly accounts have the following property:
* Read-only access to all account fields, including lamports (cannot be credited or debited), and account data
Instructions that credit, debit, or modify the readonly account will fail.
## Account Lock Optimizations
The Accounts module keeps track of current locked accounts in the runtime, which separates readonly accounts from the writable accounts. The default account lock gives an account the "writable" designation, and can only be accessed by one processing thread at one time. Readonly accounts are locked by a separate mechanism, allowing for parallel reads.
Although not yet implemented, readonly accounts could be cached in memory and shared between all the threads executing transactions. An ideal design would hold this cache while a readonly account is referenced by any transaction moving through the runtime, and release the cache when the last transaction exits the runtime.
Readonly accounts could also be passed into the processor as references, saving an extra copy.

View File

@ -30,7 +30,7 @@ Gossip is designed for efficient propagation of state. Messages that are sent th
## Performance
1. Worst case propagation time to the next leader is Log\(N\) hops with a base depending on the fanout. With our current default fanout of 6, it is about 6 hops to 20k nodes.
2. The leader should receive 20k validation votes aggregated by gossip-push into MTU-sized shreds. Which would reduce the number of packets for 20k network to 80 shreds.
2. The leader should receive 20k validation votes aggregated by gossip-push into 64kb blobs. Which would reduce the number of packets for 20k network to 80 blobs.
3. Each validators votes is replicated across the entire network. To maintain a queue of 5 previous votes the Crds table would grow by 25 megabytes. `(20,000 nodes * 256 bytes * 5)`.
## Two step implementation rollout
@ -44,7 +44,7 @@ Initially the network can perform reliably with just 1 vote transmitted and main
3. Fanout of 6.
4. Worst case 256kb memory overhead per node.
5. Worst case 4 hops to propagate to every node.
6. Leader should receive the entire validator vote set in 4 push message shreds.
6. Leader should receive the entire validator vote set in 4 push message blobs.
### Sub 20k network
@ -55,5 +55,5 @@ Everything above plus the following:
3. Increase fanout to 20.
4. Worst case 25mb memory overhead per node.
5. Sub 4 hops worst case to deliver to the entire network.
6. 80 shreds received by the leader for all the validator messages.
6. 80 blobs received by the leader for all the validator messages.

View File

@ -4,7 +4,7 @@ Transactions currently include a fee field that indicates the maximum fee field
## Congestion-driven fees
Each validator uses _signatures per slot_ \(SPS\) to estimate network congestion and _SPS target_ to estimate the desired processing capacity of the cluster. The validator learns the SPS target from the genesis config, whereas it calculates SPS from recently processed transactions. The genesis config also defines a target `lamports_per_signature`, which is the fee to charge per signature when the cluster is operating at _SPS target_.
Each validator uses _signatures per slot_ \(SPS\) to estimate network congestion and _SPS target_ to estimate the desired processing capacity of the cluster. The validator learns the SPS target from the genesis block, whereas it calculates SPS from recently processed transactions. The genesis block also defines a target `lamports_per_signature`, which is the fee to charge per signature when the cluster is operating at _SPS target_.
## Calculating fees
@ -28,3 +28,4 @@ Future parameters might include:
### Hijacking the SPS Target
A group of validators can centralize the cluster if they can convince it to raise the SPS Target above a point where the rest of the validators can keep up. Raising the target will cause fees to drop, presumably creating more demand and therefore higher TPS. If the validator doesn't have hardware that can process that many transactions that fast, its confirmation votes will eventually get so long that the cluster will be forced to boot it.

View File

@ -34,4 +34,5 @@ A cluster is a set of computers that work together and can be viewed from the ou
## What are SOLs?
A SOL is the name of Solana's native token, which can be passed to nodes in a Solana cluster in exchange for running an on-chain program or validating its output. The system may perform micropayments of fractional SOLs, which are called _lamports_. They are named in honor of Solana's biggest technical influence, [Leslie Lamport](https://en.wikipedia.org/wiki/Leslie_Lamport). A lamport has a value of 0.000000001 SOL.
A SOL is the name of Solana's native token, which can be passed to nodes in a Solana cluster in exchange for running an on-chain program or validating its output. The system may perform micropayments of fractional SOLs and a SOL may be split as many as 34 times. The fractional sol is called a _lamport_. It is named in honor of Solana's biggest technical influence, [Leslie Lamport](https://en.wikipedia.org/wiki/Leslie_Lamport). A lamport has a value of approximately 0.0000000000582 sol \(2^-34\).

View File

@ -1,24 +0,0 @@
# Paper Wallet
This document describes how to create and use a paper wallet with the Solana CLI
tools.
{% hint style="info" %}
We do not intend to advise on how to *securely* create or manage paper wallets.
Please research the security concerns carefully.
{% endhint %}
## Overview
Solana provides a key generation tool to derive keys from BIP39 compliant seed
phrases. Solana CLI commands for running a validator and staking tokens all
support keypair input via seed phrases.
To learn more about the BIP39 standard, visit the Bitcoin BIPs Github repository
[here](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki).
{% page-ref page="installation.md" %}
{% page-ref page="keypair.md" %}
{% page-ref page="usage.md" %}

View File

@ -1,51 +0,0 @@
# Installation Guide
Follow this guide to setup Solana's key generation tool called `solana-keygen`
{% hint style="warn" %}
After installation, ensure your version is `0.21.2` or higher by running `solana-keygen -V`
{% endhint %}
## Download
First, download the latest release tarball from GitHub.
1. Setup download url
```bash
solana_downloads=https://github.com/solana-labs/solana/releases/latest/download
```
2. Specify the download file based on your machine
**MacOS**
```bash
solana_release=solana-release-x86_64-apple-darwin.tar.bz2
```
**Linux**
```bash
solana_release=solana-release-x86_64-unknown-linux-gnu.tar.bz2
```
3. Download
```bash
curl -L -sSf -o solana-release.tar.bz2 $solana_downloads/$solana_release
```
## Extract
Next, extract the tarball
```bash
tar xf solana-release.tar.bz2
```
## Add to "PATH"
Now add the tool to your PATH environment variable with the following command
```bash
export PATH="$(pwd)/solana-release/bin:${PATH}"
```
## Check
Finally, check that `solana-keygen` can be run by running
```bash
solana-keygen -V
```

View File

@ -1,70 +0,0 @@
# Creating a Paper Wallet
Using the `solana-keygen` tool, it is possible to generate new seed phrases as
well as derive a keypair from an existing seed phrase and (optional) passphrase.
The seed phrase and passphrase can be used together as a paper wallet. As long
as you keep your seed phrase and passphrase stored safely, you can use them to
access your account.
{% hint style="info" %}
For more information about how seed phrases work, review this
[Bitcoin Wiki page](https://en.bitcoin.it/wiki/Seed_phrase).
{% endhint %}
## Seed Phrase Generation
Generating a new keypair can be done using the `solana-keygen new` command. The
command will generate a random seed phrase, ask you to enter an optional
passphrase, and then will display the derived public key and the generated seed
phrase for your paper wallet.
```bash
solana-keygen new --no-outfile
```
{% hint style="warning" %}
If the `--no-outfile` flag is **omitted**, the default behavior is to write the
keypair to `~/.config/solana/id.json`
{% endhint %}
{% hint style="info" %}
For added security, increase the seed phrase word count using the `--word-count`
argument
{% endhint %}
For full usage details run:
```bash
solana-keygen new --help
```
## Public Key Derivation
Public keys can be derived from a seed phrase and a passphrase if you choose to
use one. This is useful for using using an offline-generated seed phrase to
derive a valid public key. The `solana-keygen pubkey` command will walk you
through entering your seed phrase and a passphrase if you chose to use one.
```bash
solana-keygen pubkey ASK
```
{% hint style="info" %}
Note that you could potentially use different passphrases for the same seed
phrase. Each unique passphrase will yield a different keypair.
{% endhint %}
The `solana-keygen` tool assumes the use of the BIP39 standard English word
list. If you chose to deviate from the word list or used a different language
for your seed phrase, you can still derive a valid public key but will need to
explicitly skip seed phrase validation.
```bash
solana-keygen pubkey ASK --skip-seed-phrase-validation
```
For full usage details run:
```bash
solana-keygen pubkey --help
```

View File

@ -1,73 +0,0 @@
# Paper Wallet Usage
Solana commands can be run without ever saving a keypair to disk on a machine.
If avoiding writing a private key to disk is a security concern of yours, you've
come to the right place.
{% hint style="warning" %}
Even using this secure input method, it's still possible that a private key gets
written to disk by unencrypted memory swaps. It is the user's responsibility to
protect against this scenario.
{% endhint %}
## Running a Validator
In order to run a validator, you will need to specify an "identity keypair"
which will be used to fund all of the vote transactions signed by your validator.
Rather than specifying a path with `--identity-keypair <PATH>` you can use the
`--ask-seed-phrase` option.
```bash
solana-validator --ask-seed-phrase identity-keypair --ledger ...
[identity-keypair] seed phrase: 🔒
[identity-keypair] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
```
The `--ask-seed-phrase` option accepts multiple keypairs. If you wish to use this
input method for your voting keypair as well you can do the following:
```bash
solana-validator --ask-seed-phrase identity-keypair voting-keypair --ledger ...
[identity-keypair] seed phrase: 🔒
[identity-keypair] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
[voting-keypair] seed phrase: 🔒
[voting-keypair] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
```
Refer to the following page for a comprehensive guide on running a validator:
{% page-ref page="../running-validator/README.md" %}
## Delegating Stake
Solana CLI tooling supports secure keypair input for stake delegation. To do so,
first create a stake account with some SOL. Use the special `ASK` keyword to
trigger a seed phrase input prompt for the stake account and use
`--ask-seed-phrase keypair` to securely input the funding keypair.
```bash
solana create-stake-account ASK 1 SOL --ask-seed-phrase keypair
[stake_account] seed phrase: 🔒
[stake_account] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
[keypair] seed phrase: 🔒
[keypair] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
```
Then, to delegate that stake to a validator, use `--ask-seed-phrase keypair` to
securely input the funding keypair.
```bash
solana delegate-stake --ask-seed-phrase keypair <STAKE_ACCOUNT_PUBKEY> <VOTE_ACCOUNT_PUBKEY>
[keypair] seed phrase: 🔒
[keypair] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
```
Refer to the following page for a comprehensive guide on delegating stake:
{% page-ref page="../running-validator/validator-stake.md" %}
---
{% page-ref page="../api-reference/cli.md" %}

View File

@ -63,7 +63,7 @@ fn pay_and_launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
}
```
where `process_instruction()` is built into Solana's runtime and responsible for routing the given instruction to the `token` program via the instruction's `program_id` field. Before invoking `pay()`, the runtime must also ensure that `acme` didn't modify any accounts owned by `token`. It does this by calling `runtime::verify_account_changes()` and then afterward updating all the `pre_*` variables to tentatively commit `acme`'s account modifications. After `pay()` completes, the runtime must again ensure that `token` didn't modify any accounts owned by `acme`. It should call `verify_account_changes()` again, but this time with the `token` program ID. Lastly, after `pay_and_launch_missiles()` completes, the runtime must call `verify_account_changes()` one more time, where it normally would, but using all updated `pre_*` variables. If executing `pay_and_launch_missiles()` up to `pay()` made no invalid account changes, `pay()` made no invalid changes, and executing from `pay()` until `pay_and_launch_missiles()` returns made no invalid changes, then the runtime can transitively assume `pay_and_launch_missiles()` as whole made no invalid account changes, and therefore commit all account modifications.
where `process_instruction()` is built into Solana's runtime and responsible for routing the given instruction to the `token` program via the instruction's `program_id` field. Before invoking `pay()`, the runtime must also ensure that `acme` didn't modify any accounts owned by `token`. It does this by calling `runtime::verify_instruction()` and then afterward updating all the `pre_*` variables to tentatively commit `acme`'s account modifications. After `pay()` completes, the runtime must again ensure that `token` didn't modify any accounts owned by `acme`. It should call `verify_instruction()` again, but this time with the `token` program ID. Lastly, after `pay_and_launch_missiles()` completes, the runtime must call `verify_instruction()` one more time, where it normally would, but using all updated `pre_*` variables. If executing `pay_and_launch_missiles()` up to `pay()` made no invalid account changes, `pay()` made no invalid changes, and executing from `pay()` until `pay_and_launch_missiles()` returns made no invalid changes, then the runtime can transitively assume `pay_and_launch_missiles()` as whole made no invalid account changes, and therefore commit all account modifications.
### Setting `KeyedAccount.is_signer`

View File

@ -1,123 +0,0 @@
# Durable Transaction Nonces
## Problem
To prevent replay, Solana transactions contain a nonce field populated with a
"recent" blockhash value. A transaction containing a blockhash that is too old
(~2min as of this writing) is rejected by the network as invalid. Unfortunately
certain use cases, such as custodial services, require more time to produce a
signature for the transaction. A mechanism is needed to enable these potentially
offline network participants.
## Requirements
1) The transaction's signature needs to cover the nonce value
2) The nonce must not be reusable, even in the case of signing key disclosure
## A Contract-based Solution
Here we describe a contract-based solution to the problem, whereby a client can
"stash" a nonce value for future use in a transaction's `recent_blockhash`
field. This approach is akin to the Compare and Swap atomic instruction,
implemented by some CPU ISAs.
When making use of a durable nonce, the client must first query its value from
account data. A transaction is now constructed in the normal way, but with the
following additional requirements:
1) The durable nonce value is used in the `recent_blockhash` field
2) A `Nonce` instruction is issued (first?)
3) The appropriate transaction flag is set, signaling that the usual
hash age check should be skipped and the previous requirements enforced. This
may be unnecessary, see [Runtime Support](#runtime-support) below
### Contract Mechanics
TODO: svgbob this into a flowchart
```text
Start
Create Account
state = Uninitialized
NonceInstruction
if state == Uninitialized
if account.balance < rent_exempt
error InsufficientFunds
state = Initialized
elif state != Initialized
error BadState
if sysvar.recent_blockhashes.is_empty()
error EmptyRecentBlockhashes
if !sysvar.recent_blockhashes.contains(stored_nonce)
error NotReady
stored_hash = sysvar.recent_blockhashes[0]
success
WithdrawInstruction(to, lamports)
if state == Uninitialized
if !signers.contains(owner)
error MissingRequiredSignatures
elif state == Initialized
if !sysvar.recent_blockhashes.contains(stored_nonce)
error NotReady
if lamports != account.balance && lamports + rent_exempt > account.balance
error InsufficientFunds
account.balance -= lamports
to.balance += lamports
success
```
A client wishing to use this feature starts by creating a nonce account and
depositing sufficient lamports as to make it rent-exempt. The resultant account
will be in the `Uninitialized` state with no stored hash and thus unusable.
The `Nonce` instruction is used to request that a new nonce be stored for the
calling account. The first `Nonce` instruction run on a newly created account
will drive the account's state to `Initialized`. As such, a `Nonce` instruction
MUST be issued before the account can be used.
To discard a `NonceAccount`, the client should issue a `Withdraw` instruction
which withdraws all lamports, leaving a zero balance and making the account
eligible for deletion.
`Nonce` and `Withdraw` instructions each will only succeed if the stored
blockhash is no longer resident in sysvar.recent_blockhashes.
### Runtime Support
The contract alone is not sufficient for implementing this feature. To enforce
an extant `recent_blockhash` on the transaction and prevent fee theft via
failed transaction replay, runtime modifications are necessary.
Any transaction failing the usual `check_hash_age` validation will be tested
for a Durable Transaction Nonce. This specifics of this test are undecided, some
options:
1) Require that the `Nonce` instruction be the first in the transaction
* + No ABI changes
* + Fast and simple
* - Sets a precedent that may lead to incompatible instruction combinations
2) Blind search for a `Nonce` instruction over all instructions in the
transaction
* + No ABI changes
* - Potentially slow
3) [2], but guarded by a transaction flag
* - ABI changes
* - Wire size increase
* + We'll probably end up with some sort of flags eventually anyway
Current prototyping will use [1]. If it is determined that a Durable Transaction
Nonce is in use, the runtime will take the following actions to validate the
transaction:
1) The `NonceAccount` specified in the `Nonce` instruction is loaded.
2) The `NonceState` is deserialized from the `NonceAccount`'s data field and
confirmed to be in the `Initialized` state.
3) The nonce value stored in the `NonceAccount` is tested to match against the
one specified in the transaction's `recent_blockhash` field.
If all three of the above checks succeed, the transaction is allowed to continue
validation.
### Open Questions
* Should this feature be restricted in the number of uses per transaction?

View File

@ -32,7 +32,7 @@ Collecting rent on an as-needed basis \(i.e. whenever accounts were loaded/acces
* accounts loaded as "credit only" for a transaction could very reasonably be expected to have rent due,
but would not be writable during any such transaction
but would not be debitable during any such transaction
* a mechanism to "beat the bushes" \(i.e. go find accounts that need to pay rent\) is desirable,

View File

@ -6,4 +6,4 @@ Snapshot verification of the account states is implemented, but the bank hash of
## Solution
While a validator is processing transactions to catch up to the cluster from the snapshot, use incoming vote transactions and the commitment calculator to confirm that the cluster is indeed building on the snapshotted bank hash. Once a threshold commitment level is reached, accept the snapshot as valid and start voting.
Use the simple payment verification (SPV) solution to verify the vote transactions which are on-chain voting for the bank hash value.

View File

@ -1,69 +0,0 @@
# Tick Verification
This design the criteria and validation of ticks in a slot. It also describes
error handling and slashing conditions encompassing how the system handles
transmissions that do not meet these requirements.
# Slot structure
Each slot must contain an expected `ticks_per_slot` number of ticks. The last
shred in a slot must contain only the entirety of the last tick, and nothing
else. The leader must also mark this shred containing the last tick with the
`LAST_SHRED_IN_SLOT` flag. Between ticks, there must be `hashes_per_tick`
number of hashes.
# Handling bad transmissions
Malicious transmissions `T` are handled in two ways:
1) If a leader can generate some erronenous transmission `T` and also some
alternate transmission `T'` for the same slot without violating any slashing
rules for duplicate transmissions (for instance if `T'` is a subset of `T`),
then the cluster must handle the possibility of both transmissions being live.
Thus this means we cannot mark the erronenous transmission `T` as dead because
the cluster may have reached consensus on `T'`. These cases necessitate a
slashing proof to punish this bad behavior.
2) Otherwise, we can simply mark the slot as dead and not playable. A slashing
proof may or may not be necessary depending on feasibility.
# Blocktree receiving shreds
When blocktree receives a new shred `s`, there are two cases:
1) `s` is marked as `LAST_SHRED_IN_SLOT`, then check if there exists a shred
`s'` in blocktree for that slot where `s'.index > s.index` If so, together `s`
and `s'` constitute a slashing proof.
2) Blocktree has already received a shred `s'` marked as `LAST_SHRED_IN_SLOT`
with index `i`. If `s.index > i`, then together `s` and `s'`constitute a
slashing proof. In this case, blocktree will also not insert `s`.
3) Duplicate shreds for the same index are ignored. Non-duplicate shreds for
the same index are a slashable condition. Details for this case are covered
in the `Leader Duplicate Block Slashing` section.
# Replaying and validating ticks
1) Replay stage replays entries from blocktree, keeping track of the number of
ticks it has seen per slot, and verifying there are `hashes_per_tick` number of
hashes between ticcks. After the tick from this last shred has been played,
replay stage then checks the total number of ticks.
Failure scenario 1: If ever there are two consecutive ticks between which the
number of hashes is `!= hashes_per_tick`, mark this slot as dead.
Failure scenario 2: If the number of ticks != `ticks_per_slot`, mark slot as
dead.
Failure scenario 3: If the number of ticks reaches `ticks_per_slot`, but we still
haven't seen the `LAST_SHRED_IN_SLOT`, mark this slot as dead.
2) When ReplayStage reaches a shred marked as the last shred, it checks if this
last shred is a tick.
Failure scenario: If the signed shred with the `LAST_SHRED_IN_SLOT` flag cannot
be deserialized into a tick (either fails to deserialize or deserializes into
an entry), mark this slot as dead.

View File

@ -26,7 +26,7 @@ We unwrap the many abstraction layers and build a single pipeline that can toggl
* TPU moves to new socket-free crate called solana-tpu.
* TPU's BankingStage absorbs ReplayStage
* TVU goes away
* New RepairStage absorbs Shred Fetch Stage and repair requests
* New RepairStage absorbs Blob Fetch Stage and repair requests
* JSON RPC Service is optional - used for debugging. It should instead be part
of a separate `solana-blockstreamer` executable.

View File

@ -29,7 +29,7 @@ Before starting an archiver node, sanity check that the cluster is accessible to
Fetch the current transaction count over JSON RPC:
```bash
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://testnet.solana.com:8899
$ curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://testnet.solana.com:8899
```
Inspect the blockexplorer at [http://testnet.solana.com/](http://testnet.solana.com/) for activity.
@ -47,13 +47,13 @@ The `solana-install` tool can be used to easily install and upgrade the cluster
#### Linux and mac OS
```bash
curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh -s
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh -s
```
Alternatively build the `solana-install` program from source and run the following command to obtain the same result:
```bash
solana-install init
$ solana-install init
```
#### Windows
@ -71,9 +71,9 @@ If you would rather not use `solana-install` to manage the install, you can manu
Download the binaries by navigating to [https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest), download **solana-release-x86\_64-unknown-linux-gnu.tar.bz2**, then extract the archive:
```bash
tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
cd solana-release/
export PATH=$PWD/bin:$PATH
$ tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
#### mac OS
@ -81,9 +81,9 @@ export PATH=$PWD/bin:$PATH
Download the binaries by navigating to [https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest), download **solana-release-x86\_64-apple-darwin.tar.bz2**, then extract the archive:
```bash
tar jxf solana-release-x86_64-apple-darwin.tar.bz2
cd solana-release/
export PATH=$PWD/bin:$PATH
$ tar jxf solana-release-x86_64-apple-darwin.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
#### Windows
@ -95,7 +95,7 @@ Download the binaries by navigating to [https://github.com/solana-labs/solana/re
Try running following command to join the gossip network and view all the other nodes in the cluster:
```bash
solana-gossip --entrypoint testnet.solana.com:8001 spy
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
# Press ^C to exit
```
@ -104,8 +104,8 @@ Now configure the keypairs for your archiver by running:
Navigate to the solana install location and open a cmd prompt
```bash
solana-keygen new -o archiver-keypair.json
solana-keygen new -o storage-keypair.json
$ solana-keygen new -o archiver-keypair.json
$ solana-keygen new -o storage-keypair.json
```
Use solana-keygen to show the public keys for each of the keypairs, they will be needed in the next step:
@ -114,23 +114,23 @@ Use solana-keygen to show the public keys for each of the keypairs, they will be
```bash
# The archiver's identity
solana-keygen pubkey archiver-keypair.json
solana-keygen pubkey storage-keypair.json
$ solana-keygen pubkey archiver-keypair.json
$ solana-keygen pubkey storage-keypair.json
```
* Linux and mac OS
\`\`\`bash
export ARCHIVER\_IDENTITY=$\(solana-keygen pubkey archiver-keypair.json\)
$ export ARCHIVER\_IDENTITY=$\(solana-keygen pubkey archiver-keypair.json\)
export STORAGE\_IDENTITY=$\(solana-keygen pubkey storage-keypair.json\)
$ export STORAGE\_IDENTITY=$\(solana-keygen pubkey storage-keypair.json\)
```text
Then set up the storage accounts for your archiver by running:
```bash
solana --keypair archiver-keypair.json airdrop 100000 lamports
solana --keypair archiver-keypair.json create-archiver-storage-account $ARCHIVER_IDENTITY $STORAGE_IDENTITY
$ solana --keypair archiver-keypair.json airdrop 100000 lamports
$ solana --keypair archiver-keypair.json create-archiver-storage-account $ARCHIVER_IDENTITY $STORAGE_IDENTITY
```
Note: Every time the testnet restarts, run the steps to setup the archiver accounts again.
@ -138,7 +138,7 @@ Note: Every time the testnet restarts, run the steps to setup the archiver accou
To start the archiver:
```bash
solana-archiver --entrypoint testnet.solana.com:8001 --identity-keypair archiver-keypair.json --storage-keypair storage-keypair.json --ledger archiver-ledger
$ solana-archiver --entrypoint testnet.solana.com:8001 --identity archiver-keypair.json --storage-keypair storage-keypair.json --ledger archiver-ledger
```
## Verify Archiver Setup
@ -146,11 +146,12 @@ solana-archiver --entrypoint testnet.solana.com:8001 --identity-keypair archiver
From another console, confirm the IP address and **identity pubkey** of your archiver is visible in the gossip network by running:
```bash
solana-gossip --entrypoint testnet.solana.com:8001 spy
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
```
Provide the **storage account pubkey** to the `solana show-storage-account` command to view the recent mining activity from your archiver:
```bash
solana --keypair storage-keypair.json show-storage-account $STORAGE_IDENTITY
$ solana --keypair storage-keypair.json show-storage-account $STORAGE_IDENTITY
```

View File

@ -7,13 +7,13 @@ You can publish your validator information to the chain to be publicly visible t
Run the solana CLI to populate a validator info account:
```bash
solana validator-info publish --keypair ~/validator-keypair.json <VALIDATOR_INFO_ARGS> <VALIDATOR_NAME>
$ solana validator-info publish --keypair ~/validator-keypair.json <VALIDATOR_INFO_ARGS> <VALIDATOR_NAME>
```
For details about optional fields for VALIDATOR\_INFO\_ARGS:
```bash
solana validator-info publish --help
$ solana validator-info publish --help
```
## Keybase
@ -33,3 +33,4 @@ Including a Keybase username allows client applications \(like the Solana Networ
3. Add or update your `solana validator-info` with your Keybase username. The
CLI will verify the `validator-<PUBKEY>` file

View File

@ -5,65 +5,70 @@
The **identity pubkey** for your validator can also be found by running:
```bash
solana-keygen pubkey ~/validator-keypair.json
$ solana-keygen pubkey ~/validator-keypair.json
```
From another console, confirm the IP address and **identity pubkey** of your validator is visible in the gossip network by running:
```bash
solana-gossip --entrypoint testnet.solana.com:8001 spy
```
## Monitoring Catch Up
It may take some time to catch up with the cluster after your validator boots.
Use the `catchup` command to monitor your validator through this process:
```bash
solana catchup ~/validator-keypair.json
```
Until your validator has caught up, it will not be able to vote successfully and
stake cannot be delegated to it.
Also if you find the cluster's slot advancing faster than yours, you will likely
never catch up. This typically implies some kind of networking issue between
your validator and the rest of the cluster.
## Check Your Balance
Your account balance should decrease by the transaction fee amount as your
validator submits votes, and increase after serving as the leader. Pass the
`--lamports` are to observe in finer detail:
```bash
solana balance --lamports
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
```
## Check Vote Activity
The `solana show-vote-account` command displays the recent voting activity from your validator:
The vote pubkey for the validator can be found by running:
```bash
solana show-vote-account ~/validator-vote-keypair.json
$ solana-keygen pubkey ~/validator-vote-keypair.json
```
Provide the **vote pubkey** to the `solana show-vote-account` command to view the recent voting activity from your validator:
```bash
$ solana show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
```
## Check Your Balance
Your account balance should decrease by the transaction fee amount as your validator submits votes, and increase after serving as the leader. Pass the `--lamports` are to observe in finer detail:
```bash
$ solana balance --lamports
```
## Check Slot Number
After your validator boots, it may take some time to catch up with the cluster. Use the `get-slot` command to view the current slot that the cluster is processing:
```bash
$ solana get-slot
```
The current slot that your validator is processing can then been seen with:
```bash
$ solana --url http://127.0.0.1:8899 get-slot
```
Until your validator has caught up, it will not be able to vote successfully and stake cannot be delegated to it.
Also if you find the cluster's slot advancing faster than yours, you will likely never catch up. This typically implies some kind of networking issue between your validator and the rest of the cluster.
## Get Cluster Info
There are several useful JSON-RPC endpoints for monitoring your validator on the cluster, as well as the health of the cluster:
```bash
# Similar to solana-gossip, you should see your validator in the list of cluster nodes
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getClusterNodes"}' http://testnet.solana.com:8899
$ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getClusterNodes"}' http://testnet.solana.com:8899
# If your validator is properly voting, it should appear in the list of `current` vote accounts. If staked, `stake` should be > 0
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVoteAccounts"}' http://testnet.solana.com:8899
$ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVoteAccounts"}' http://testnet.solana.com:8899
# Returns the current leader schedule
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLeaderSchedule"}' http://testnet.solana.com:8899
$ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLeaderSchedule"}' http://testnet.solana.com:8899
# Returns info about the current epoch. slotIndex should progress on subsequent calls.
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}' http://testnet.solana.com:8899
```
## Validator Metrics
Metrics are available for local monitoring of your validator.
@ -71,9 +76,9 @@ Metrics are available for local monitoring of your validator.
Docker must be installed and the current user added to the docker group. Then download `solana-metrics.tar.bz2` from the Github Release and run
```bash
tar jxf solana-metrics.tar.bz2
cd solana-metrics/
./start.sh
$ tar jxf solana-metrics.tar.bz2
$ cd solana-metrics/
$ ./start.sh
```
A local InfluxDB and Grafana instance is now running on your machine. Define `SOLANA_METRICS_CONFIG` in your environment as described at the end of the `start.sh` output and restart your validator.
@ -87,5 +92,6 @@ Log messages emitted by your validator include a timestamp. When sharing logs wi
To make it easier to compare logs between different sources we request that everybody use Pacific Time on their validator nodes. In Linux this can be accomplished by running:
```bash
sudo ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
$ sudo ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
```

View File

@ -5,13 +5,13 @@
The `solana-install` tool can be used to easily install and upgrade the validator software on Linux x86\_64 and mac OS systems.
```bash
curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh -s
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh -s
```
Alternatively build the `solana-install` program from source and run the following command to obtain the same result:
```bash
solana-install init
$ solana-install init
```
After a successful install, `solana-install update` may be used to easily update the cluster software to a newer version at any time.
@ -25,9 +25,9 @@ If you would rather not use `solana-install` to manage the install, you can manu
Download the binaries by navigating to [https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest), download **solana-release-x86\_64-unknown-linux-gnu.tar.bz2**, then extract the archive:
```bash
tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
cd solana-release/
export PATH=$PWD/bin:$PATH
$ tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
### mac OS
@ -35,9 +35,9 @@ export PATH=$PWD/bin:$PATH
Download the binaries by navigating to [https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest), download **solana-release-x86\_64-apple-darwin.tar.bz2**, then extract the archive:
```bash
tar jxf solana-release-x86_64-apple-darwin.tar.bz2
cd solana-release/
export PATH=$PWD/bin:$PATH
$ tar jxf solana-release-x86_64-apple-darwin.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
## Build From Source
@ -45,6 +45,7 @@ export PATH=$PWD/bin:$PATH
If you are unable to use the prebuilt binaries or prefer to build it yourself from source, navigate to [https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest), and download the **Source Code** archive. Extract the code and build the binaries with:
```bash
./scripts/cargo-install-all.sh .
export PATH=$PWD/bin:$PATH
$ ./scripts/cargo-install-all.sh .
$ export PATH=$PWD/bin:$PATH
```

View File

@ -7,28 +7,28 @@ Adding stake can be accomplished by using the `solana` CLI
First create a stake account keypair with `solana-keygen`:
```bash
solana-keygen new -o ~/validator-stake-keypair.json
$ solana-keygen new -o ~/validator-config/stake-keypair.json
```
and use the cli's `create-stake-account` and `delegate-stake` commands to stake your validator with 4242 lamports:
and use the cli's `create-stake-account` and `delegate-stake` commands to stake your validator with 42 lamports:
```bash
solana create-stake-account ~/validator-stake-keypair.json 4242 lamports
solana delegate-stake ~/validator-stake-keypair.json ~/validator-vote-keypair.json
$ solana create-stake-account ~/validator-config/stake-keypair.json 42 lamports
$ solana delegate-stake ~/validator-config/stake-keypair.json ~/validator-vote-keypair.json
```
Note that stakes need to warm up, and warmup increments are applied at Epoch boundaries, so it can take an hour or more for the change to fully take effect.
Stakes can be re-delegated to another node at any time with the same command, but only one re-delegation is permitted per epoch:
Stakes can be re-delegated to another node at any time with the same command:
```bash
solana delegate-stake ~/validator-stake-keypair.json ~/some-other-validator-vote-keypair.json
$ solana delegate-stake ~/validator-config/stake-keypair.json ~/some-other-validator-vote-keypair.json
```
Assuming the node is voting, now you're up and running and generating validator rewards. You'll want to periodically redeem/claim your rewards:
```bash
solana redeem-vote-credits ~/validator-stake-keypair.json ~/validator-vote-keypair.json
$ solana redeem-vote-credits ~/validator-config/stake-keypair.json ~/validator-vote-keypair.json
```
The rewards lamports earned are split between your stake account and the vote account according to the commission rate set in the vote account. Rewards can only be earned while the validator is up and running. Further, once staked, the validator becomes an important part of the network. In order to safely remove a validator from the network, first deactivate its stake.
@ -36,7 +36,7 @@ The rewards lamports earned are split between your stake account and the vote ac
Stake can be deactivated by running:
```bash
solana deactivate-stake ~/validator-stake-keypair.json
$ solana deactivate-stake ~/validator-config/stake-keypair.json
```
The stake will cool down, deactivate over time. While cooling down, your stake will continue to earn rewards. Only after stake cooldown is it safe to turn off your validator or withdraw it from the network. Cooldown may take several epochs to complete, depending on active stake and the size of your stake.
@ -44,3 +44,4 @@ The stake will cool down, deactivate over time. While cooling down, your stake w
Note that a stake account may only be used once, so after deactivation, use the cli's `withdraw-stake` command to recover the previously staked lamports.
Be sure and redeem your credits before withdrawing all your lamports. Once the account is fully withdrawn, the account is destroyed.

View File

@ -7,7 +7,7 @@ Before attaching a validator node, sanity check that the cluster is accessible t
Fetch the current transaction count over JSON RPC:
```bash
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://testnet.solana.com:8899
$ curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://testnet.solana.com:8899
```
Inspect the network explorer at [https://explorer.solana.com/](https://explorer.solana.com/) for activity.
@ -19,16 +19,16 @@ View the [metrics dashboard](https://metrics.solana.com:3000/d/testnet-beta/test
Sanity check that you are able to interact with the cluster by receiving a small airdrop of lamports from the testnet drone:
```bash
solana set --url http://testnet.solana.com:8899
solana get
solana airdrop 123 lamports
solana balance --lamports
$ solana set --url http://testnet.solana.com:8899
$ solana get
$ solana airdrop 123 lamports
$ solana balance --lamports
```
Also try running following command to join the gossip network and view all the other nodes in the cluster:
```bash
solana-gossip --entrypoint testnet.solana.com:8001 spy
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
# Press ^C to exit
```
@ -37,7 +37,7 @@ solana-gossip --entrypoint testnet.solana.com:8001 spy
Create an identity keypair for your validator by running:
```bash
solana-keygen new -o ~/validator-keypair.json
$ solana-keygen new -o ~/validator-keypair.json
```
### Wallet Configuration
@ -45,30 +45,30 @@ solana-keygen new -o ~/validator-keypair.json
You can set solana configuration to use your validator keypair for all following commands:
```bash
solana set --keypair ~/validator-keypair.json
$ solana set --keypair ~/validator-keypair.json
```
**All following solana commands assume you have set `--keypair` config to** your validator identity keypair.\*\* If you haven't, you will need to add the `--keypair` argument to each command, like:
```bash
solana --keypair ~/validator-keypair.json airdrop 10
$ solana --keypair ~/validator-keypair.json airdrop 1000 lamports
```
\(You can always override the set configuration by explicitly passing the `--keypair` argument with a command.\)
### Validator Start
Airdrop yourself some SOL to get started:
Airdrop yourself some lamports to get started:
```bash
solana airdrop 10
$ solana airdrop 1000 lamports
```
Your validator will need a vote account. Create it now with the following commands:
```bash
solana-keygen new -o ~/validator-vote-keypair.json
solana create-vote-account ~/validator-vote-keypair.json ~/validator-keypair.json
$ solana-keygen new -o ~/validator-vote-keypair.json
$ solana create-vote-account ~/validator-vote-keypair.json ~/validator-keypair.json 1 lamports
```
Then use one of the following commands, depending on your installation choice, to start the node:
@ -76,19 +76,19 @@ Then use one of the following commands, depending on your installation choice, t
If this is a `solana-install`-installation:
```bash
solana-validator --identity-keypair ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --ledger ~/validator-config --rpc-port 8899 --entrypoint testnet.solana.com:8001
$ solana-validator --identity ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --ledger ~/validator-config --rpc-port 8899 --entrypoint testnet.solana.com:8001
```
Alternatively, the `solana-install run` command can be used to run the validator node while periodically checking for and applying software updates:
```bash
solana-install run solana-validator -- --identity-keypair ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --ledger ~/validator-config --rpc-port 8899 --entrypoint testnet.solana.com:8001
$ solana-install run solana-validator -- --identity ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --ledger ~/validator-config --rpc-port 8899 --entrypoint testnet.solana.com:8001
```
If you built from source:
```bash
NDEBUG=1 USE_INSTALL=1 ./multinode-demo/validator.sh --identity-keypair ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --rpc-port 8899 --entrypoint testnet.solana.com:8001
$ NDEBUG=1 USE_INSTALL=1 ./multinode-demo/validator.sh --identity ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --rpc-port 8899 --entrypoint testnet.solana.com:8001
```
### Enabling CUDA
@ -98,7 +98,7 @@ If your machine has a GPU with CUDA installed \(Linux-only currently\), include
Or if you built from source, define the SOLANA\_CUDA flag in your environment _before_ running any of the previously mentioned commands
```bash
export SOLANA_CUDA=1
$ export SOLANA_CUDA=1
```
When your validator is started look for the following log message to indicate that CUDA is enabled: `"[<timestamp> solana::validator] CUDA is enabled"`
@ -110,3 +110,4 @@ By default the validator will dynamically select available network ports in the
### Limiting ledger size to conserve disk space
By default the validator will retain the full ledger. To conserve disk space start the validator with the `--limit-ledger-size`, which will instruct the validator to only retain the last couple hours of ledger.

View File

@ -15,7 +15,7 @@ Prior to mainnet, the testnets may be running different versions of solana softw
You can submit a JSON-RPC request to see the specific version of the cluster.
```bash
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' edge.testnet.solana.com:8899
$ curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' edge.testnet.solana.com:8899
{"jsonrpc":"2.0","result":{"solana-core":"0.18.0-pre1"},"id":1}
```
@ -28,17 +28,17 @@ This guide is written in the context of testnet.solana.com, our most stable clus
If you are bootstrapping with `solana-install`, you can specify the release tag or named channel to install to match your desired testnet.
```bash
curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh -s - 0.18.0
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh -s - 0.18.0
```
```bash
curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh -s - beta
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh -s - beta
```
Similarly, you can add this argument to the `solana-install` command if you've built the program from source:
```bash
solana-install init 0.18.0
$ solana-install init 0.18.0
```
If you are downloading pre-compiled binaries or building from source, simply choose the release matching your desired testnet.
@ -48,14 +48,14 @@ If you are downloading pre-compiled binaries or building from source, simply cho
The Solana CLI tool points at testnet.solana.com by default. Include a `--url` argument to point at a different testnet. For instance:
```bash
solana --url http://beta.testnet.solana.com:8899 balance
$ solana --url http://beta.testnet.solana.com:8899 balance
```
The solana cli includes `get` and `set` configuration commands to automatically set the `--url` argument for future cli commands. For example:
```bash
solana set --url http://beta.testnet.solana.com:8899
solana balance # Same result as command above
$ solana set --url http://beta.testnet.solana.com:8899
$ solana balance # Same result as command above
```
\(You can always override the set configuration by explicitly passing the `--url` argument with a command.\)
@ -63,11 +63,12 @@ solana balance # Same result as command above
Solana-gossip and solana-validator commands already require an explicit `--entrypoint` argument. Simply replace testnet.solana.com in the examples with an alternate url to interact with a different testnet. For example:
```bash
solana-validator --identity-keypair ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --ledger ~/validator-config --rpc-port 8899 beta.testnet.solana.com
$ solana-validator --identity ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --ledger ~/validator-config --rpc-port 8899 beta.testnet.solana.com
```
You can also submit JSON-RPC requests to a different testnet, like:
```bash
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://beta.testnet.solana.com:8899
$ curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://beta.testnet.solana.com:8899
```

View File

@ -18,13 +18,13 @@ The result of interpreting all programs on the ledger at a given [tick height](t
A contiguous set of [entries](terminology.md#entry) on the ledger covered by a [vote](terminology.md#ledger-vote). A [leader](terminology.md#leader) produces at most one block per [slot](terminology.md#slot).
## blockhash
A preimage resistant [hash](terminology.md#hash) of the [ledger](terminology.md#ledger) at a given [block height](terminology.md#block-height). Taken from the last [entry id](terminology.md#entry-id) in the slot
## block height
The number of [blocks](terminology.md#block) beneath the current block. The first block after the [genesis block](terminology.md#genesis-block) has height one.
The number of [blocks](terminology.md#block) beneath the current block. The first block after the [genesis block](terminology.md#genesis-block) has height zero.
## block id
The [entry id](terminology.md#entry-id) of the last entry in a [block](terminology.md#block).
## bootstrap leader
@ -72,13 +72,7 @@ An entry on the [ledger](terminology.md#ledger) either a [tick](terminology.md#t
## entry id
A preimage resistant [hash](terminology.md#hash) over the final contents of an entry, which acts as the [entry's](terminology.md#entry) globally unique identifier. The hash serves as evidence of:
* The entry being generated after a duration of time
* The specified [transactions](terminology.md#transaction) are those included in the entry
* The entry's position with respect to other entries in [ledger](terminology.md#ledger)
See [Proof of History](terminology.md#proof-of-history).
A globally unique identifier that is also a proof that the [entry](terminology.md#entry) was generated after a duration of time, all [transactions](terminology.md#transaction) included in the entry, and all previous entries on the [ledger](terminology.md#ledger). See [Proof of History](terminology.md#proof-of-history).
## epoch
@ -90,7 +84,7 @@ A proof which has the same format as a storage proof, but the sha state is actua
## fee account
The fee account in the transaction is the account pays for the cost of including the transaction in the ledger. This is the first account in the transaction. This account must be declared as Read-Write (writable) in the transaction since paying for the transaction reduces the account balance.
The fee account in the transaction is the account pays for the cost of including the transaction in the ledger. This is the first account in the transaction. This account must be declared as Credit-Debit in the transaction since paying for the transaction reduces the account balance.
## finality
@ -102,11 +96,7 @@ A [ledger](terminology.md#ledger) derived from common entries but then diverged.
## genesis block
The first [block](terminology.md#block) in the chain.
## genesis config
The configuration file that prepares the [ledger](terminology.md#ledger) for the [genesis block](terminology.md#genesis-block).
The configuration file that prepares the [ledger](terminology.md#ledger) for the first [block](terminology.md#block).
## hash
@ -122,7 +112,11 @@ A [public key](terminology.md#public-key) and corresponding [private key](termin
## lamport
A fractional [native token](terminology.md#native-token) with the value of 0.000000001 [sol](terminology.md#sol).
A fractional [native token](terminology.md#native-token) with the value of approximately 0.0000000000582 [sol](terminology.md#sol) \(2^-34\).
## loader
A [program](terminology.md#program) with the ability to interpret the binary encoding of other on-chain programs.
## leader
@ -148,10 +142,6 @@ A [hash](terminology.md#hash) of the [validator's state](terminology.md#bank-sta
A type of [client](terminology.md#client) that can verify it's pointing to a valid [cluster](terminology.md#cluster). It performs more ledger verification than a [thin client](terminology.md#thin-client) and less than a [validator](terminology.md#validator).
## loader
A [program](terminology.md#program) with the ability to interpret the binary encoding of other on-chain programs.
## lockout
The duration of time for which a [validator](terminology.md#validator) is unable to [vote](terminology.md#ledger-vote) on another [fork](terminology.md#fork).
@ -303,3 +293,4 @@ A reward tally for validators. A vote credit is awarded to a validator in its vo
## warmup period
Some number of epochs after stake has been delegated while it progressively becomes effective. During this period, the stake is considered to be "activating". More info about: [warmup and cooldown](cluster/stake-delegation-and-rewards.md#stake-warmup-cooldown-withdrawal)

View File

@ -1,12 +1,12 @@
# Anatomy of a Transaction
Transactions encode lists of instructions that are executed sequentially, and only committed if all the instructions complete successfully. All account updates are reverted upon the failure of a transaction. Each transaction details the accounts used, including which must sign and which are read only, a recent blockhash, the instructions, and any signatures.
Transactions encode lists of instructions that are executed sequentially, and only committed if all the instructions complete successfully. All account updates are reverted upon the failure of a transaction. Each transaction details the accounts used, including which must sign and which are credit only, a recent blockhash, the instructions, and any signatures.
## Accounts and Signatures
Each transaction explicitly lists all account public keys referenced by the transaction's instructions. A subset of those public keys are each accompanied by a transaction signature. Those signatures signal on-chain programs that the account holder has authorized the transaction. Typically, the program uses the authorization to permit debiting the account or modifying its data.
The transaction also marks some accounts as _read-only accounts_. The runtime permits read-only accounts to be read concurrently. If a program attempts to modify a read-only account, the transaction is rejected by the runtime.
The transaction also marks some accounts as _credit-only accounts_. The runtime permits credit-only accounts to be credited concurrently. If a program attempts to debit a credit-only account or modify its account data, the transaction is rejected by the runtime.
## Recent Blockhash
@ -15,3 +15,4 @@ A Transaction includes a recent blockhash to prevent duplication and to give tra
## Instructions
Each instruction specifies a single program account \(which must be marked executable\), a subset of the transaction's accounts that should be passed to the program, and a data byte array instruction that is passed to the program. The program interprets the data array and operates on the accounts specified by the instructions. The program can return successfully, or with an error code. An error return causes the entire transaction to fail immediately.

View File

@ -2,7 +2,7 @@
## The Runtime
The runtime is a concurrent transaction processor. Transactions specify their data dependencies upfront and dynamic memory allocation is explicit. By separating program code from the state it operates on, the runtime is able to choreograph concurrent access. Transactions accessing only read-only accounts are executed in parallel whereas transactions accessing writable accounts are serialized. The runtime interacts with the program through an entrypoint with a well-defined interface. The data stored in an account is an opaque type, an array of bytes. The program has full control over its contents.
The runtime is a concurrent transaction processor. Transactions specify their data dependencies upfront and dynamic memory allocation is explicit. By separating program code from the state it operates on, the runtime is able to choreograph concurrent access. Transactions accessing only credit-only accounts are executed in parallel whereas transactions accessing writable accounts are serialized. The runtime interacts with the program through an entrypoint with a well-defined interface. The data stored in an account is an opaque type, an array of bytes. The program has full control over its contents.
The transaction structure specifies a list of public keys and signatures for those keys and a sequential list of instructions that will operate over the states associated with the account keys. For the transaction to be committed all the instructions must execute successfully; if any abort the whole transaction fails to commit.
@ -28,7 +28,7 @@ The runtime enforces the following rules:
1. Only the _owner_ program may modify the contents of an account. This means that upon assignment data vector is guaranteed to be zero.
2. Total balances on all the accounts is equal before and after execution of a transaction.
3. After the transaction is executed, balances of read-only accounts must be equal to the balances before the transaction.
3. After the transaction is executed, balances of credit-only accounts must be greater than or equal to the balances before the transaction.
4. All instructions in the transaction executed atomically. If one fails, all account modifications are discarded.
Execution of the program involves mapping the program's public key to an entrypoint which takes a pointer to the transaction, and an array of loaded accounts.
@ -62,3 +62,4 @@ To pass messages between programs, the receiving program must accept the message
* \[Continuations and Signals for long running
Transactions\]\([https://github.com/solana-labs/solana/issues/1485](https://github.com/solana-labs/solana/issues/1485)\)

View File

@ -1,6 +1,6 @@
[package]
name = "solana-chacha-sys"
version = "0.21.2"
version = "0.20.0"
description = "Solana chacha-sys"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -9,4 +9,4 @@ license = "Apache-2.0"
edition = "2018"
[build-dependencies]
cc = "1.0.47"
cc = "1.0.46"

View File

@ -8,8 +8,6 @@
# ./affects-files.sh .rs -- also matches foo.rs.bar
# ./affects-files.sh ^snap/ -- anything under the snap/ subdirectory
# ./affects-files.sh snap/ -- also matches foo/snap/
# Any pattern starting with the ! character will be negated:
# ./affects-files.sh !^book/ -- anything *not* under the book/ subdirectory
#
set -e
cd "$(dirname "$0")"/..
@ -20,19 +18,11 @@ if [[ -n $CI_PULL_REQUEST ]]; then
IFS=':' read -ra files <<< "$affectedFiles"
for pattern in "$@"; do
if [[ ${pattern:0:1} = "!" ]]; then
for file in "${files[@]}"; do
if [[ ! $file =~ ${pattern:1} ]]; then
exit 0
fi
done
else
for file in "${files[@]}"; do
if [[ $file =~ $pattern ]]; then
exit 0
fi
done
fi
for file in "${files[@]}"; do
if [[ $file =~ $pattern ]]; then
exit 0
fi
done
done
exit 1

View File

@ -7,7 +7,7 @@ steps:
timeout_in_minutes: 5
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-checks.sh"
name: "checks"
timeout_in_minutes: 20
timeout_in_minutes: 35
- wait
- command: "ci/test-stable-perf.sh"
name: "stable-perf"
@ -17,21 +17,18 @@ steps:
- "queue=cuda"
- command: "ci/test-bench.sh"
name: "bench"
timeout_in_minutes: 30
timeout_in_minutes: 60
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-stable.sh"
name: "stable"
timeout_in_minutes: 40
artifact_paths: "log-*.txt"
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-move.sh"
name: "move"
timeout_in_minutes: 20
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-local-cluster.sh"
name: "local-cluster"
timeout_in_minutes: 30
timeout_in_minutes: 40
artifact_paths: "log-*.txt"
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
name: "coverage"
timeout_in_minutes: 30
timeout_in_minutes: 40
- wait
- trigger: "solana-secondary"
branches: "!pull/*"

View File

@ -1,4 +1,4 @@
FROM solanalabs/rust:1.39.0
FROM solanalabs/rust:1.38.0
ARG date
RUN set -x \

View File

@ -1,6 +1,6 @@
# Note: when the rust version is changed also modify
# ci/rust-version.sh to pick up the new image tag
FROM rust:1.39.0
FROM rust:1.38.0
# Add Google Protocol Buffers for Libra's metrics library.
ENV PROTOC_VERSION 3.8.0

View File

@ -75,7 +75,6 @@ source multinode-demo/common.sh
nodes=(
"multinode-demo/drone.sh"
"multinode-demo/bootstrap-leader.sh \
--no-restart \
--init-complete-file init-complete-node1.log \
--dynamic-port-range 8000-8050"
"multinode-demo/validator.sh \
@ -86,19 +85,17 @@ nodes=(
--rpc-port 18899"
)
if [[ extraNodes -gt 0 ]]; then
for i in $(seq 1 $extraNodes); do
portStart=$((8100 + i * 50))
portEnd=$((portStart + 49))
nodes+=(
"multinode-demo/validator.sh \
--no-restart \
--dynamic-port-range $portStart-$portEnd
--label dyn$i \
--init-complete-file init-complete-node$((2 + i)).log"
)
done
fi
for i in $(seq 1 $extraNodes); do
portStart=$((8100 + i * 50))
portEnd=$((portStart + 49))
nodes+=(
"multinode-demo/validator.sh \
--no-restart \
--dynamic-port-range $portStart-$portEnd
--label dyn$i \
--init-complete-file init-complete-node$((2 + i)).log"
)
done
numNodes=$((2 + extraNodes))
pids=()
@ -156,7 +153,7 @@ startNodes() {
addLogs=true
fi
initCompleteFiles=()
maybeExpectedGenesisHash=
maybeExpectedGenesisBlockhash=
for i in $(seq 0 $((${#nodes[@]} - 1))); do
declare cmd=${nodes[$i]}
@ -165,7 +162,7 @@ startNodes() {
rm -f "$initCompleteFile"
initCompleteFiles+=("$initCompleteFile")
fi
startNode "$i" "$cmd $maybeExpectedGenesisHash"
startNode "$i" "$cmd $maybeExpectedGenesisBlockhash"
if $addLogs; then
logs+=("$(getNodeLogFile "$i" "$cmd")")
fi
@ -179,9 +176,9 @@ startNodes() {
(
set -x
$solana_cli --keypair config/bootstrap-leader/identity-keypair.json \
--url http://127.0.0.1:8899 get-genesis-hash
) | tee genesis-hash.log
maybeExpectedGenesisHash="--expected-genesis-hash $(tail -n1 genesis-hash.log)"
--url http://127.0.0.1:8899 get-genesis-blockhash
) | tee genesis-blockhash.log
maybeExpectedGenesisBlockhash="--expected-genesis-blockhash $(tail -n1 genesis-blockhash.log)"
fi
done
@ -315,7 +312,7 @@ flag_error() {
if ! $skipSetup; then
clear_config_dir "$SOLANA_CONFIG_DIR"
multinode-demo/setup.sh --hashes-per-tick sleep
multinode-demo/setup.sh
else
verifyLedger
fi
@ -326,8 +323,8 @@ while [[ $iteration -le $iterations ]]; do
(
set -x
client_keypair=/tmp/client-id.json-$$
$solana_keygen new --no-passphrase -fso $client_keypair || exit $?
$solana_gossip spy -n 127.0.0.1:8001 --num-nodes-exactly $numNodes || exit $?
$solana_keygen new -f -o $client_keypair || exit $?
$solana_gossip spy --num-nodes-exactly $numNodes || exit $?
rm -rf $client_keypair
) || flag_error

View File

@ -21,13 +21,15 @@ declare print_free_tree=(
'core/src'
'drone/src'
'metrics/src'
'net-utils/src'
'netutil/src'
'runtime/src'
'sdk/bpf/rust/rust-utils'
'sdk/src'
'programs/bpf/rust'
'programs/stake/src'
'programs/vote/src'
'programs/stake_api/src'
'programs/stake_program/src'
'programs/vote_api/src'
'programs/vote_program/src'
)
if _ git --no-pager grep -n --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then
@ -48,13 +50,11 @@ fi
# marking up the code
#
# Ref: https://github.com/solana-labs/solana/issues/6474
#
# shellcheck disable=1001
declare useGithubIssueInsteadOf=(
X\XX
T\BD
F\IXME
#T\ODO # TODO: Disable TODOs once all other TODOs are purged
'XXX'
'TBD'
'FIXME'
#'TODO' # TODO: Uncomment this line to disable TODOs
)
if _ git --no-pager grep -n --max-depth=0 "${useGithubIssueInsteadOf[@]/#/-e }" -- '*.rs' '*.sh' '*.md'; then
@ -63,6 +63,6 @@ fi
# TODO: Remove this `git grep` once TODOs are banned above
# (this command is only used to highlight the current offenders)
_ git --no-pager grep -n --max-depth=0 "-e TODO" -- '*.rs' '*.sh' '*.md' || true
_ git --no-pager grep -n --max-depth=0 "-e TODO" -- '*.rs' '*.sh' || true
echo "^^^ +++"
# END TODO

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python2.7
#!/usr/bin/env python
#
# This script figures the order in which workspace crates must be published to
# crates.io. Along the way it also ensures there are no circular dependencies
@ -47,7 +47,7 @@ def get_packages():
while dependency_graph:
if max_iterations == 0:
# One day be more helpful and find the actual cycle for the user...
sys.exit('Error: Circular dependency suspected between these packages: {}\n'.format('\n '.join(dependency_graph.keys())))
sys.exit('Error: Circular dependency suspected between these packages: {}\n'.format(' '.join(dependency_graph.keys())))
max_iterations -= 1
for package, dependencies in dependency_graph.items():

View File

@ -67,11 +67,8 @@ echo --- Creating tarball
echo "target: $TARGET"
) > solana-release/version.yml
# Make CHANNEL available to include in the software version information
export CHANNEL
source ci/rust-version.sh stable
scripts/cargo-install-all.sh +"$rust_stable" --use-move solana-release
scripts/cargo-install-all.sh +"$rust_stable" solana-release
# Reduce the Windows archive size until
# https://github.com/appveyor/ci/issues/2997 is fixed

View File

@ -1,22 +0,0 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
rm -f config/run/init-completed
timeout 15 ./run.sh &
pid=$!
attempts=20
while [[ ! -f config/run/init-completed ]]; do
sleep 1
if ((--attempts == 0)); then
echo "Error: validator failed to boot"
exit 1
fi
done
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"validatorExit"}' http://localhost:8899
wait $pid

View File

@ -13,18 +13,8 @@
# $ source ci/rust-version.sh
#
if [[ -n $RUST_STABLE_VERSION ]]; then
stable_version="$RUST_STABLE_VERSION"
else
stable_version=1.39.0
fi
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
nightly_version="$RUST_NIGHTLY_VERSION"
else
nightly_version=2019-11-13
fi
stable_version=1.38.0
nightly_version=2019-10-03
export rust_stable="$stable_version"
export rust_stable_docker_image=solanalabs/rust:"$stable_version"

View File

@ -6,7 +6,13 @@ set -e
cd "$(dirname "$0")/.."
(
set -x
git ls-files -- '*.sh' ':(exclude)ci/semver_bash' \
| xargs ci/docker-run.sh koalaman/shellcheck@sha256:fe24ab9a9b6b62d3adb162f4a80e006b6a63cae8c6ffafbae45772bab85e7294 --color=always --external-sources --shell=bash
find . -name "*.sh" \
-not -regex ".*/ci/semver_bash/.*" \
-not -regex ".*/.cargo/.*" \
-not -regex ".*/node_modules/.*" \
-not -regex ".*/target/.*" \
-print0 \
| xargs -0 \
ci/docker-run.sh koalaman/shellcheck --color=always --external-sources --shell=bash
)
echo --- ok

View File

@ -68,7 +68,7 @@ _ cargo +$rust_nightly bench --manifest-path programs/bpf/Cargo.toml ${V:+--verb
-- -Z unstable-options --format=json --nocapture | tee -a "$BENCH_FILE"
# Run banking bench. Doesn't require nightly, but use since it is already built.
_ cargo +$rust_nightly run --release --manifest-path banking-bench/Cargo.toml ${V:+--verbose} | tee -a "$BENCH_FILE"
_ cargo +$rust_nightly run --release --manifest-path banking_bench/Cargo.toml ${V:+--verbose} | tee -a "$BENCH_FILE"
# `solana-upload-perf` disabled as it can take over 30 minutes to complete for some
# reason

View File

@ -1 +0,0 @@
test-stable.sh

View File

@ -10,18 +10,6 @@ annotate() {
}
}
# Run the appropriate test based on entrypoint
testName=$(basename "$0" .sh)
# Skip if only the book has been modified
ci/affects-files.sh \
\!^book/ \
|| {
annotate --style info \
"Skipped $testName as only book files were modified"
exit 0
}
source ci/rust-version.sh stable
export RUST_BACKTRACE=1
@ -38,13 +26,19 @@ test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
# Clear the BPF sysroot files, they are not automatically rebuilt
rm -rf target/xargo # Issue #3105
echo "Executing $testName"
# Run the appropriate test based on entrypoint
testName=$(basename "$0" .sh)
case $testName in
test-stable)
echo "Executing $testName"
_ cargo +"$rust_stable" build --tests --bins ${V:+--verbose}
_ cargo +"$rust_stable" test --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
_ cargo +"$rust_stable" test --manifest-path bench-tps/Cargo.toml --features=move ${V:+--verbose} test_bench_tps_local_cluster_move -- --nocapture
_ cargo +"$rust_stable" test --manifest-path local_cluster/Cargo.toml --features=move ${V:+--verbose} test_bench_tps_local_cluster_move -- --nocapture
;;
test-stable-perf)
echo "Executing $testName"
ci/affects-files.sh \
.rs$ \
Cargo.lock$ \
@ -59,7 +53,7 @@ test-stable-perf)
^sdk/ \
|| {
annotate --style info \
"Skipped $testName as no relevant files were modified"
"Skipped test-stable-perf as no relevant files were modified"
exit 0
}
@ -86,30 +80,10 @@ test-stable-perf)
fi
_ cargo +"$rust_stable" build --bins ${V:+--verbose}
_ cargo +"$rust_stable" test --package solana-perf --package solana-ledger --package solana-core --lib ${V:+--verbose} -- --nocapture
;;
test-move)
ci/affects-files.sh \
Cargo.lock$ \
Cargo.toml$ \
^ci/rust-version.sh \
^ci/test-stable.sh \
^ci/test-move.sh \
^programs/move_loader \
^programs/librapay_api \
^logger/ \
^runtime/ \
^sdk/ \
|| {
annotate --style info \
"Skipped $testName as no relevant files were modified"
exit 0
}
_ cargo +"$rust_stable" test --manifest-path programs/move_loader/Cargo.toml ${V:+--verbose} -- --nocapture
_ cargo +"$rust_stable" test --manifest-path programs/librapay_api/Cargo.toml ${V:+--verbose} -- --nocapture
exit 0
_ cargo +"$rust_stable" test --package solana-ledger --package solana-core --lib ${V:+--verbose} -- --nocapture
;;
test-local-cluster)
echo "Executing $testName"
_ cargo +"$rust_stable" build --release --bins ${V:+--verbose}
_ cargo +"$rust_stable" test --release --package solana-local-cluster ${V:+--verbose} -- --nocapture
exit 0
@ -119,11 +93,9 @@ test-local-cluster)
;;
esac
echo --- ci/localnet-sanity.sh
export CARGO_TOOLCHAIN=+"$rust_stable"
(
export CARGO_TOOLCHAIN=+"$rust_stable"
echo --- ci/localnet-sanity.sh
set -x
ci/localnet-sanity.sh -x
echo --- ci/run-sanity.sh
ci/run-sanity.sh -x
)

View File

@ -28,6 +28,7 @@ maybeDisableAirdrops=
maybeInternalNodesStakeLamports=
maybeInternalNodesLamports=
maybeExternalPrimordialAccountsFile=
maybeLamports=
maybeSlotsPerEpoch=
maybeTargetLamportsPerSignature=
maybeSlotsPerEpoch=
@ -74,17 +75,17 @@ Deploys a CD testnet
-S - Stop network software without tearing down nodes.
-f - Discard validator nodes that didn't bootup successfully
--no-airdrop
- If set, disables airdrops. Nodes must be funded in genesis config when airdrops are disabled.
- If set, disables airdrops. Nodes must be funded in genesis block when airdrops are disabled.
--internal-nodes-stake-lamports NUM_LAMPORTS
- Amount to stake internal nodes.
--internal-nodes-lamports NUM_LAMPORTS
- Amount to fund internal nodes in genesis config
- Amount to fund internal nodes in genesis block
--external-accounts-file FILE_PATH
- Path to external Primordial Accounts file, if it exists.
--hashes-per-tick NUM_HASHES|sleep|auto
- Override the default --hashes-per-tick for the cluster
--lamports NUM_LAMPORTS
- Specify the number of lamports to mint (default 500000000000000000)
- Specify the number of lamports to mint (default 100000000000000)
--skip-deploy-update
- If set, will skip software update deployment
--skip-remote-log-retrieval
@ -113,6 +114,9 @@ while [[ -n $1 ]]; do
elif [[ $1 = --slots-per-epoch ]]; then
maybeSlotsPerEpoch="$1 $2"
shift 2
elif [[ $1 = --lamports ]]; then
maybeLamports="$1 $2"
shift 2
elif [[ $1 = --target-lamports-per-signature ]]; then
maybeTargetLamportsPerSignature="$1 $2"
shift 2
@ -258,11 +262,6 @@ trap shutdown EXIT INT
set -x
# Fetch reusable testnet keypairs
if [[ ! -d net/keypairs ]]; then
git clone git@github.com:solana-labs/testnet-keypairs.git net/keypairs
fi
# Build a string to pass zone opts to $cloudProvider.sh: "-z zone1 -z zone2 ..."
zone_args=()
for val in "${zone[@]}"; do
@ -292,7 +291,6 @@ if ! $skipCreate; then
-c "$clientNodeCount"
-n "$additionalValidatorCount"
--dedicated
--self-destruct-hours 0
)
if [[ -n $bootstrapValidatorAddress ]]; then
@ -408,6 +406,7 @@ if ! $skipStart; then
$maybeInternalNodesStakeLamports
$maybeInternalNodesLamports
$maybeExternalPrimordialAccountsFile
$maybeLamports
$maybeSlotsPerEpoch
$maybeTargetLamportsPerSignature
$maybeNoSnapshot

View File

@ -216,7 +216,7 @@ maybe_deploy_software() {
(
echo "--- net.sh restart"
set -x
time net/net.sh restart --skip-setup -t "$CHANNEL_OR_TAG" --skip-poh-verify "$arg"
time net/net.sh restart --skip-setup -t "$CHANNEL_OR_TAG" --skip-ledger-verify "$arg"
) || ok=false
if ! $ok; then
net/net.sh logs
@ -246,7 +246,7 @@ sanity() {
(
set -x
NO_INSTALL_CHECK=1 \
ci/testnet-sanity.sh beta-testnet-solana-com gce -P us-west1-b
ci/testnet-sanity.sh beta-testnet-solana-com gce us-west1-b
maybe_deploy_software --deploy-if-newer
)
;;
@ -260,7 +260,7 @@ sanity() {
testnet)
(
set -x
ci/testnet-sanity.sh testnet-solana-com gce -P us-west1-b
ci/testnet-sanity.sh testnet-solana-com gce us-west1-b
)
;;
testnet-perf)
@ -386,7 +386,7 @@ deploy() {
(
echo "--- net.sh update"
set -x
time net/net.sh update -t "$CHANNEL_OR_TAG" --platform linux --platform osx --platform windows
time net/net.sh update -t edge --platform linux --platform osx --platform windows
)
;;
testnet-perf)
@ -479,7 +479,7 @@ deploy() {
fi
if [[ -z $INTERNAL_NODES_STAKE_LAMPORTS ]]; then
maybeInternalNodesStakeLamports="--internal-nodes-stake-lamports 1000000000" # 1 SOL
maybeInternalNodesStakeLamports="--internal-nodes-stake-lamports 17179869184" # 1 SOL
elif [[ $INTERNAL_NODES_STAKE_LAMPORTS == skip ]]; then
maybeInternalNodesStakeLamports=""
else
@ -487,7 +487,7 @@ deploy() {
fi
if [[ -z $INTERNAL_NODES_LAMPORTS ]]; then
maybeInternalNodesLamports="--internal-nodes-lamports 2000000000" # 2 SOL
maybeInternalNodesLamports="--internal-nodes-lamports 34359738368" # 2 SOL
elif [[ $INTERNAL_NODES_LAMPORTS == skip ]]; then
maybeInternalNodesLamports=""
else
@ -506,6 +506,14 @@ deploy() {
maybeExternalAccountsFile="--external-accounts-file ${EXTERNAL_ACCOUNTS_FILE}"
fi
if [[ -z $LAMPORTS ]]; then
maybeLamports="--lamports 8589934592000000000"
elif [[ $LAMPORTS == skip ]]; then
maybeLamports=""
else
maybeLamports="--lamports ${LAMPORTS}"
fi
if [[ -z $ADDITIONAL_DISK_SIZE_GB ]]; then
maybeAdditionalDisk="--validator-additional-disk-size-gb 32000"
elif [[ $ADDITIONAL_DISK_SIZE_GB == skip ]]; then
@ -514,6 +522,7 @@ deploy() {
maybeAdditionalDisk="--validator-additional-disk-size-gb ${ADDITIONAL_DISK_SIZE_GB}"
fi
# Multiple V100 GPUs are available in us-west1, us-central1 and europe-west4
# shellcheck disable=SC2068
# shellcheck disable=SC2086
@ -536,7 +545,8 @@ deploy() {
${maybeInternalNodesStakeLamports} \
${maybeInternalNodesLamports} \
${maybeExternalAccountsFile} \
--target-lamports-per-signature 0 \
${maybeLamports} \
--target-lamports-per-signature 1 \
--slots-per-epoch 4096 \
${maybeAdditionalDisk}
)

View File

@ -1,20 +0,0 @@
[package]
name = "solana-clap-utils"
version = "0.21.2"
description = "Solana utilities for the clap"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[dependencies]
clap = "2.33.0"
rpassword = "4.0"
semver = "0.9.0"
solana-sdk = { path = "../sdk", version = "0.21.2" }
tiny-bip39 = "0.6.2"
url = "2.1.0"
[lib]
name = "solana_clap_utils"

View File

@ -1,120 +0,0 @@
use crate::keypair::ASK_KEYWORD;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{read_keypair_file, Signature};
use std::str::FromStr;
// Return an error if a pubkey cannot be parsed.
pub fn is_pubkey(string: String) -> Result<(), String> {
match string.parse::<Pubkey>() {
Ok(_) => Ok(()),
Err(err) => Err(format!("{:?}", err)),
}
}
// 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.
pub fn is_keypair(string: String) -> Result<(), String> {
read_keypair_file(&string)
.map(|_| ())
.map_err(|err| format!("{:?}", err))
}
// Return an error if a keypair file cannot be parsed
pub fn is_keypair_or_ask_keyword(string: String) -> Result<(), String> {
if string.as_str() == ASK_KEYWORD {
return Ok(());
}
read_keypair_file(&string)
.map(|_| ())
.map_err(|err| format!("{:?}", err))
}
// Return an error if string cannot be parsed as pubkey string or keypair file location
pub fn is_pubkey_or_keypair(string: String) -> Result<(), 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.
pub fn is_url(string: String) -> Result<(), String> {
match url::Url::parse(&string) {
Ok(url) => {
if url.has_host() {
Ok(())
} else {
Err("no host provided".to_string())
}
}
Err(err) => Err(format!("{:?}", err)),
}
}
pub fn is_semver(semver: &str) -> Result<(), String> {
match semver::Version::parse(&semver) {
Ok(_) => Ok(()),
Err(err) => Err(format!("{:?}", err)),
}
}
pub fn is_release_channel(channel: &str) -> Result<(), String> {
match channel {
"edge" | "beta" | "stable" => Ok(()),
_ => Err(format!("Invalid release channel {}", channel)),
}
}
pub fn is_port(port: String) -> Result<(), String> {
port.parse::<u16>()
.map(|_| ())
.map_err(|e| format!("{:?}", e))
}
pub fn is_valid_percentage(percentage: String) -> Result<(), String> {
percentage
.parse::<u8>()
.map_err(|e| {
format!(
"Unable to parse input percentage, provided: {}, err: {:?}",
percentage, e
)
})
.and_then(|v| {
if v > 100 {
Err(format!(
"Percentage must be in range of 0 to 100, provided: {}",
v
))
} else {
Ok(())
}
})
}

View File

@ -1,167 +0,0 @@
use crate::ArgConstant;
use bip39::{Language, Mnemonic, Seed};
use clap::values_t;
use rpassword::prompt_password_stderr;
use solana_sdk::{
pubkey::Pubkey,
signature::{
keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair_file, Keypair,
KeypairUtil,
},
};
use std::{
error,
io::{stdin, stdout, Write},
process::exit,
};
// Keyword used to indicate that the user should be asked for a keypair seed phrase
pub const ASK_KEYWORD: &str = "ASK";
pub const ASK_SEED_PHRASE_ARG: ArgConstant<'static> = ArgConstant {
long: "ask-seed-phrase",
name: "ask_seed_phrase",
help: "Securely recover a keypair using a seed phrase and optional passphrase",
};
pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant {
long: "skip-seed-phrase-validation",
name: "skip_seed_phrase_validation",
help: "Skip validation of seed phrases. Use this if your phrase does not use the BIP39 official English word list",
};
#[derive(Debug, PartialEq)]
pub enum Source {
File,
Generated,
SeedPhrase,
}
pub struct KeypairWithSource {
pub keypair: Keypair,
pub source: Source,
}
impl KeypairWithSource {
fn new(keypair: Keypair, source: Source) -> Self {
Self { keypair, source }
}
}
/// Prompts user for a passphrase and then asks for confirmirmation to check for mistakes
pub fn prompt_passphrase(prompt: &str) -> Result<String, Box<dyn error::Error>> {
let passphrase = prompt_password_stderr(&prompt)?;
if !passphrase.is_empty() {
let confirmed = rpassword::prompt_password_stderr("Enter same passphrase again: ")?;
if confirmed != passphrase {
return Err("Passphrases did not match".into());
}
}
Ok(passphrase)
}
/// Reads user input from stdin to retrieve a seed phrase and passphrase for keypair derivation
/// Optionally skips validation of seed phrase
/// Optionally confirms recovered public key
pub fn keypair_from_seed_phrase(
keypair_name: &str,
skip_validation: bool,
confirm_pubkey: bool,
) -> Result<Keypair, Box<dyn error::Error>> {
let seed_phrase = prompt_password_stderr(&format!("[{}] seed phrase: ", keypair_name))?;
let seed_phrase = seed_phrase.trim();
let passphrase_prompt = format!(
"[{}] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue: ",
keypair_name,
);
let keypair = if skip_validation {
let passphrase = prompt_passphrase(&passphrase_prompt)?;
keypair_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase)?
} else {
let sanitized = sanitize_seed_phrase(seed_phrase);
let mnemonic = Mnemonic::from_phrase(sanitized, Language::English)?;
let passphrase = prompt_passphrase(&passphrase_prompt)?;
let seed = Seed::new(&mnemonic, &passphrase);
keypair_from_seed(seed.as_bytes())?
};
if confirm_pubkey {
let pubkey = Pubkey::new(keypair.public.as_ref());
print!("Recovered pubkey `{:?}`. Continue? (y/n): ", pubkey);
let _ignored = stdout().flush();
let mut input = String::new();
stdin().read_line(&mut input).expect("Unexpected input");
if input.to_lowercase().trim() != "y" {
println!("Exiting");
exit(1);
}
}
Ok(keypair)
}
/// Checks CLI arguments to determine whether a keypair should be:
/// - inputted securely via stdin,
/// - read in from a file,
/// - or newly generated
pub fn keypair_input(
matches: &clap::ArgMatches,
keypair_name: &str,
) -> Result<KeypairWithSource, Box<dyn error::Error>> {
let ask_seed_phrase_matches =
values_t!(matches.values_of(ASK_SEED_PHRASE_ARG.name), String).unwrap_or_default();
let keypair_match_name = keypair_name.replace('-', "_");
if ask_seed_phrase_matches
.iter()
.any(|s| s.as_str() == keypair_name)
{
if matches.value_of(keypair_match_name).is_some() {
clap::Error::with_description(
&format!(
"`--{} {}` cannot be used with `{} <PATH>`",
ASK_SEED_PHRASE_ARG.long, keypair_name, keypair_name
),
clap::ErrorKind::ArgumentConflict,
)
.exit();
}
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
keypair_from_seed_phrase(keypair_name, skip_validation, true)
.map(|keypair| KeypairWithSource::new(keypair, Source::SeedPhrase))
} else if let Some(keypair_file) = matches.value_of(keypair_match_name) {
read_keypair_file(keypair_file).map(|keypair| KeypairWithSource::new(keypair, Source::File))
} else {
Ok(KeypairWithSource::new(Keypair::new(), Source::Generated))
}
}
fn sanitize_seed_phrase(seed_phrase: &str) -> String {
seed_phrase
.split_whitespace()
.collect::<Vec<&str>>()
.join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
use clap::ArgMatches;
#[test]
fn test_keypair_input() {
let arg_matches = ArgMatches::default();
let KeypairWithSource { source, .. } = keypair_input(&arg_matches, "").unwrap();
assert_eq!(source, Source::Generated);
}
#[test]
fn test_sanitize_seed_phrase() {
let seed_phrase = " Mary had\ta\u{2009}little \n\t lamb";
assert_eq!(
"Mary had a little lamb".to_owned(),
sanitize_seed_phrase(seed_phrase)
);
}
}

View File

@ -1,28 +0,0 @@
#[macro_export]
macro_rules! version {
() => {
&*format!(
"{}{}",
env!("CARGO_PKG_VERSION"),
if option_env!("CI_TAG").unwrap_or("").is_empty() {
format!(
" [channel={} commit={}]",
option_env!("CHANNEL").unwrap_or("unknown"),
option_env!("CI_COMMIT").unwrap_or("unknown"),
)
} else {
"".to_string()
},
)
};
}
pub struct ArgConstant<'a> {
pub long: &'a str,
pub name: &'a str,
pub help: &'a str,
}
pub mod input_parsers;
pub mod input_validators;
pub mod keypair;

View File

@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-cli"
description = "Blockchain, Rebuilt for Scale"
version = "0.21.2"
version = "0.20.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -11,41 +11,38 @@ homepage = "https://solana.com/"
[dependencies]
bincode = "1.2.0"
bs58 = "0.3.0"
chrono = { version = "0.4.10", features = ["serde"] }
chrono = { version = "0.4.9", features = ["serde"] }
clap = "2.33.0"
criterion-stats = "0.3.0"
ctrlc = { version = "3.1.3", features = ["termination"] }
console = "0.9.1"
console = "0.9.0"
dirs = "2.0.2"
lazy_static = "1.4.0"
log = "0.4.8"
indicatif = "0.13.0"
num-traits = "0.2"
pretty-hex = "0.1.1"
reqwest = { version = "0.9.22", default-features = false, features = ["rustls-tls"] }
serde = "1.0.102"
serde_derive = "1.0.102"
serde = "1.0.101"
serde_derive = "1.0.101"
serde_json = "1.0.41"
serde_yaml = "0.8.11"
solana-budget-program = { path = "../programs/budget", version = "0.21.2" }
solana-clap-utils = { path = "../clap-utils", version = "0.21.2" }
solana-client = { path = "../client", version = "0.21.2" }
solana-config-program = { path = "../programs/config", version = "0.21.2" }
solana-drone = { path = "../drone", version = "0.21.2" }
solana-logger = { path = "../logger", version = "0.21.2" }
solana-net-utils = { path = "../net-utils", version = "0.21.2" }
solana-runtime = { path = "../runtime", version = "0.21.2" }
solana-sdk = { path = "../sdk", version = "0.21.2" }
solana-stake-program = { path = "../programs/stake", version = "0.21.2" }
solana-storage-program = { path = "../programs/storage", version = "0.21.2" }
solana-vote-program = { path = "../programs/vote", version = "0.21.2" }
solana-vote-signer = { path = "../vote-signer", version = "0.21.2" }
solana-budget-api = { path = "../programs/budget_api", version = "0.20.0" }
solana-client = { path = "../client", version = "0.20.0" }
solana-config-api = { path = "../programs/config_api", version = "0.20.0" }
solana-drone = { path = "../drone", version = "0.20.0" }
solana-logger = { path = "../logger", version = "0.20.0" }
solana-netutil = { path = "../netutil", version = "0.20.0" }
solana-runtime = { path = "../runtime", version = "0.20.0" }
solana-sdk = { path = "../sdk", version = "0.20.0" }
solana-stake-api = { path = "../programs/stake_api", version = "0.20.0" }
solana-storage-api = { path = "../programs/storage_api", version = "0.20.0" }
solana-vote-api = { path = "../programs/vote_api", version = "0.20.0" }
solana-vote-signer = { path = "../vote-signer", version = "0.20.0" }
url = "2.1.0"
[dev-dependencies]
solana-core = { path = "../core", version = "0.21.2" }
solana-budget-program = { path = "../programs/budget", version = "0.21.2" }
tempfile = "3.1.0"
solana-core = { path = "../core", version = "0.20.0" }
solana-budget-program = { path = "../programs/budget_program", version = "0.20.0" }
[[bin]]
name = "solana"

File diff suppressed because it is too large Load Diff

View File

@ -7,21 +7,16 @@ use crate::{
};
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
use console::{style, Emoji};
use indicatif::{ProgressBar, ProgressStyle};
use solana_clap_utils::{input_parsers::*, input_validators::*};
use serde_json::Value;
use solana_client::{rpc_client::RpcClient, rpc_request::RpcVoteAccountInfo};
use solana_sdk::{
clock::{self, Slot},
commitment_config::CommitmentConfig,
clock,
hash::Hash,
pubkey::Pubkey,
signature::{Keypair, KeypairUtil},
system_transaction,
};
use std::{
collections::VecDeque,
net::SocketAddr,
thread::sleep,
time::{Duration, Instant},
};
@ -36,70 +31,20 @@ pub trait ClusterQuerySubCommands {
impl ClusterQuerySubCommands for App<'_, '_> {
fn cluster_query_subcommands(self) -> Self {
self.subcommand(
SubCommand::with_name("catchup")
.about("Wait for a validator to catch up to the cluster")
.arg(
Arg::with_name("node_pubkey")
.index(1)
.takes_value(true)
.value_name("PUBKEY")
.validator(is_pubkey_or_keypair)
.required(true)
.help("Identity pubkey of the validator"),
),
)
.subcommand(
SubCommand::with_name("cluster-version")
.about("Get the version of the cluster entrypoint"),
)
.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::with_name("get-epoch-info")
.about("Get information about the current epoch")
.arg(
Arg::with_name("confirmed")
.long("confirmed")
.takes_value(false)
.help(
"Return information at maximum-lockout commitment level",
),
),
.about("Get information about the current epoch"),
)
.subcommand(
SubCommand::with_name("get-genesis-hash").about("Get the genesis hash"),
SubCommand::with_name("get-genesis-blockhash").about("Get the genesis blockhash"),
)
.subcommand(SubCommand::with_name("get-slot").about("Get current slot"))
.subcommand(
SubCommand::with_name("get-slot").about("Get current slot")
.arg(
Arg::with_name("confirmed")
.long("confirmed")
.takes_value(false)
.help(
"Return slot at maximum-lockout commitment level",
),
),
)
.subcommand(
SubCommand::with_name("get-transaction-count").about("Get current transaction count")
.arg(
Arg::with_name("confirmed")
.long("confirmed")
.takes_value(false)
.help(
"Return count at maximum-lockout commitment level",
),
),
SubCommand::with_name("get-transaction-count").about("Get current transaction count"),
)
.subcommand(
SubCommand::with_name("ping")
@ -121,36 +66,16 @@ impl ClusterQuerySubCommands for App<'_, '_> {
.takes_value(true)
.help("Stop after submitting count transactions"),
)
.arg(
Arg::with_name("lamports")
.long("lamports")
.value_name("NUMBER")
.takes_value(true)
.default_value("1")
.help("Number of lamports to transfer for each transaction"),
)
.arg(
Arg::with_name("timeout")
.short("t")
.long("timeout")
.value_name("SECONDS")
.takes_value(true)
.default_value("15")
.default_value("10")
.help("Wait up to timeout seconds for transaction confirmation"),
)
.arg(
Arg::with_name("confirmed")
.long("confirmed")
.takes_value(false)
.help(
"Wait until the transaction is confirmed at maximum-lockout commitment level",
),
),
)
.subcommand(
SubCommand::with_name("show-gossip")
.about("Show the current gossip network nodes"),
)
.subcommand(
SubCommand::with_name("show-validators")
.about("Show information about the current validators")
@ -164,16 +89,7 @@ impl ClusterQuerySubCommands for App<'_, '_> {
}
}
pub fn parse_catchup(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let node_pubkey = pubkey_of(matches, "node_pubkey").unwrap();
Ok(CliCommandInfo {
command: CliCommand::Catchup { node_pubkey },
require_keypair: false,
})
}
pub fn parse_cluster_ping(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let lamports = value_t_or_exit!(matches, "lamports", u64);
let interval = Duration::from_secs(value_t_or_exit!(matches, "interval", u64));
let count = if matches.is_present("count") {
Some(value_t_or_exit!(matches, "count", u64))
@ -181,67 +97,16 @@ pub fn parse_cluster_ping(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, Cl
None
};
let timeout = Duration::from_secs(value_t_or_exit!(matches, "timeout", u64));
let commitment_config = if matches.is_present("confirmed") {
CommitmentConfig::default()
} else {
CommitmentConfig::recent()
};
Ok(CliCommandInfo {
command: CliCommand::Ping {
lamports,
interval,
count,
timeout,
commitment_config,
},
require_keypair: true,
})
}
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> {
let commitment_config = if matches.is_present("confirmed") {
CommitmentConfig::default()
} else {
CommitmentConfig::recent()
};
Ok(CliCommandInfo {
command: CliCommand::GetEpochInfo { commitment_config },
require_keypair: false,
})
}
pub fn parse_get_slot(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let commitment_config = if matches.is_present("confirmed") {
CommitmentConfig::default()
} else {
CommitmentConfig::recent()
};
Ok(CliCommandInfo {
command: CliCommand::GetSlot { commitment_config },
require_keypair: false,
})
}
pub fn parse_get_transaction_count(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let commitment_config = if matches.is_present("confirmed") {
CommitmentConfig::default()
} else {
CommitmentConfig::recent()
};
Ok(CliCommandInfo {
command: CliCommand::GetTransactionCount { commitment_config },
require_keypair: false,
})
}
pub fn parse_show_validators(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let use_lamports_unit = matches.is_present("lamports");
@ -251,76 +116,21 @@ pub fn parse_show_validators(matches: &ArgMatches<'_>) -> Result<CliCommandInfo,
})
}
/// Creates a new process bar for processing that will take an unknown amount of time
fn new_spinner_progress_bar() -> ProgressBar {
let progress_bar = ProgressBar::new(42);
progress_bar
.set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}"));
progress_bar.enable_steady_tick(100);
progress_bar
}
pub fn process_catchup(rpc_client: &RpcClient, node_pubkey: &Pubkey) -> ProcessResult {
let cluster_nodes = rpc_client.get_cluster_nodes()?;
let rpc_addr = cluster_nodes
.iter()
.find(|contact_info| contact_info.pubkey == node_pubkey.to_string())
.ok_or_else(|| format!("Contact information not found for {}", node_pubkey))?
.rpc
.ok_or_else(|| format!("RPC service not found for {}", node_pubkey))?;
let progress_bar = new_spinner_progress_bar();
progress_bar.set_message("Connecting...");
let node_client = RpcClient::new_socket(rpc_addr);
let mut previous_rpc_slot = std::u64::MAX;
let mut previous_slot_distance = 0;
let sleep_interval = 5;
loop {
let rpc_slot = rpc_client.get_slot_with_commitment(CommitmentConfig::recent())?;
let node_slot = node_client.get_slot_with_commitment(CommitmentConfig::recent())?;
if node_slot > std::cmp::min(previous_rpc_slot, rpc_slot) {
progress_bar.finish_and_clear();
return Ok(format!(
"{} has caught up (us:{} them:{})",
node_pubkey, node_slot, rpc_slot,
));
}
let slot_distance = rpc_slot as i64 - node_slot as i64;
progress_bar.set_message(&format!(
"Validator is {} slots away (us:{} them:{}){}",
slot_distance,
node_slot,
rpc_slot,
if previous_rpc_slot == std::u64::MAX {
"".to_string()
} else {
let slots_per_second =
(previous_slot_distance - slot_distance) as f64 / f64::from(sleep_interval);
format!(
" and {} at {:.1} slots/second",
if slots_per_second < 0.0 {
"falling behind"
} else {
"gaining"
},
slots_per_second,
)
pub fn process_cluster_version(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
let remote_version: Value = serde_json::from_str(&rpc_client.get_version()?)?;
println!(
"{} {}",
style("Cluster versions from:").bold(),
config.json_rpc_url
);
if let Some(versions) = remote_version.as_object() {
for (key, value) in versions.iter() {
if let Some(value_string) = value.as_str() {
println_name_value(&format!("* {}:", key), &value_string);
}
));
sleep(Duration::from_secs(sleep_interval as u64));
previous_rpc_slot = rpc_slot;
previous_slot_distance = slot_distance;
}
}
}
pub fn process_cluster_version(rpc_client: &RpcClient) -> ProcessResult {
let remote_version = rpc_client.get_version()?;
Ok(remote_version.solana_core)
Ok("".to_string())
}
pub fn process_fees(rpc_client: &RpcClient) -> ProcessResult {
@ -332,16 +142,8 @@ 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(
rpc_client: &RpcClient,
commitment_config: &CommitmentConfig,
) -> ProcessResult {
let epoch_info = rpc_client.get_epoch_info_with_commitment(commitment_config.clone())?;
pub fn process_get_epoch_info(rpc_client: &RpcClient) -> ProcessResult {
let epoch_info = rpc_client.get_epoch_info()?;
println!();
println_name_value("Current epoch:", &epoch_info.epoch.to_string());
println_name_value("Current slot:", &epoch_info.absolute_slot.to_string());
@ -369,36 +171,27 @@ pub fn process_get_epoch_info(
Ok("".to_string())
}
pub fn process_get_genesis_hash(rpc_client: &RpcClient) -> ProcessResult {
let genesis_hash = rpc_client.get_genesis_hash()?;
Ok(genesis_hash.to_string())
pub fn process_get_genesis_blockhash(rpc_client: &RpcClient) -> ProcessResult {
let genesis_blockhash = rpc_client.get_genesis_blockhash()?;
Ok(genesis_blockhash.to_string())
}
pub fn process_get_slot(
rpc_client: &RpcClient,
commitment_config: &CommitmentConfig,
) -> ProcessResult {
let slot = rpc_client.get_slot_with_commitment(commitment_config.clone())?;
pub fn process_get_slot(rpc_client: &RpcClient) -> ProcessResult {
let slot = rpc_client.get_slot()?;
Ok(slot.to_string())
}
pub fn process_get_transaction_count(
rpc_client: &RpcClient,
commitment_config: &CommitmentConfig,
) -> ProcessResult {
let transaction_count =
rpc_client.get_transaction_count_with_commitment(commitment_config.clone())?;
pub fn process_get_transaction_count(rpc_client: &RpcClient) -> ProcessResult {
let transaction_count = rpc_client.get_transaction_count()?;
Ok(transaction_count.to_string())
}
pub fn process_ping(
rpc_client: &RpcClient,
config: &CliConfig,
lamports: u64,
interval: &Duration,
count: &Option<u64>,
timeout: &Duration,
commitment_config: &CommitmentConfig,
) -> ProcessResult {
let to = Keypair::new().pubkey();
@ -421,18 +214,14 @@ pub fn process_ping(
let (recent_blockhash, fee_calculator) = rpc_client.get_new_blockhash(&last_blockhash)?;
last_blockhash = recent_blockhash;
let transaction =
system_transaction::transfer(&config.keypair, &to, lamports, recent_blockhash);
let transaction = system_transaction::transfer(&config.keypair, &to, 1, recent_blockhash);
check_account_for_fee(rpc_client, config, &fee_calculator, &transaction.message)?;
match rpc_client.send_transaction(&transaction) {
Ok(signature) => {
let transaction_sent = Instant::now();
loop {
let signature_status = rpc_client.get_signature_status_with_commitment(
&signature,
commitment_config.clone(),
)?;
let signature_status = rpc_client.get_signature_status(&signature)?;
let elapsed_time = Instant::now().duration_since(transaction_sent);
if let Some(transaction_status) = signature_status {
match transaction_status {
@ -440,8 +229,8 @@ pub fn process_ping(
let elapsed_time_millis = elapsed_time.as_millis() as u64;
confirmation_time.push_back(elapsed_time_millis);
println!(
"{}{} lamport(s) transferred: seq={:<3} time={:>4}ms signature={}",
CHECK_MARK, lamports, seq, elapsed_time_millis, signature
"{}1 lamport transferred: seq={:<3} time={:>4}ms signature={}",
CHECK_MARK, seq, elapsed_time_millis, signature
);
confirmed_count += 1;
}
@ -466,7 +255,8 @@ pub fn process_ping(
// Sleep for half a slot
if signal_receiver
.recv_timeout(Duration::from_millis(
500 * clock::DEFAULT_TICKS_PER_SLOT / clock::DEFAULT_TICKS_PER_SECOND,
500 * solana_sdk::clock::DEFAULT_TICKS_PER_SLOT
/ solana_sdk::clock::DEFAULT_TICKS_PER_SECOND,
))
.is_ok()
{
@ -512,42 +302,6 @@ pub fn process_ping(
Ok("".to_string())
}
pub fn process_show_gossip(rpc_client: &RpcClient) -> ProcessResult {
let cluster_nodes = rpc_client.get_cluster_nodes()?;
fn format_port(addr: Option<SocketAddr>) -> String {
addr.map(|addr| addr.port().to_string())
.unwrap_or_else(|| "none".to_string())
}
let s: Vec<_> = cluster_nodes
.iter()
.map(|node| {
format!(
"{:15} | {:44} | {:6} | {:5} | {:5}",
node.gossip
.map(|addr| addr.ip().to_string())
.unwrap_or_else(|| "none".to_string()),
node.pubkey,
format_port(node.gossip),
format_port(node.tpu),
format_port(node.rpc),
)
})
.collect();
Ok(format!(
"IP Address | Node identifier \
| Gossip | TPU | RPC\n\
----------------+----------------------------------------------+\
--------+-------+-------\n\
{}\n\
Nodes: {}",
s.join("\n"),
s.len(),
))
}
pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool) -> ProcessResult {
let vote_accounts = rpc_client.get_vote_accounts()?;
let total_active_stake = vote_accounts
@ -591,7 +345,7 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
println!(
"{}",
style(format!(
" {:<44} {:<44} {} {} {} {}",
" {:<44} {:<44} {:<11} {:>10} {:>11} {}",
"Identity Pubkey",
"Vote Account Pubkey",
"Commission",
@ -616,7 +370,7 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
}
}
println!(
"{} {:<44} {:<44} {:>9}% {:>8} {:>10} {:>12}",
"{} {:<44} {:<44} {:>3} ({:>4.1}%) {:>10} {:>11} {:>11}",
if delinquent {
WARNING.to_string()
} else {
@ -625,6 +379,7 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
vote_account.node_pubkey,
vote_account.vote_pubkey,
vote_account.commission,
f64::from(vote_account.commission) * 100.0 / f64::from(std::u8::MAX),
non_zero_or_dash(vote_account.last_vote),
non_zero_or_dash(vote_account.root_slot),
if vote_account.activated_stake > 0 {
@ -678,40 +433,24 @@ 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
.clone()
.get_matches_from(vec!["test", "get-epoch-info"]);
assert_eq!(
parse_command(&test_get_epoch_info).unwrap(),
CliCommandInfo {
command: CliCommand::GetEpochInfo {
commitment_config: CommitmentConfig::recent(),
},
command: CliCommand::GetEpochInfo,
require_keypair: false
}
);
let test_get_genesis_hash = test_commands
let test_get_genesis_blockhash = test_commands
.clone()
.get_matches_from(vec!["test", "get-genesis-hash"]);
.get_matches_from(vec!["test", "get-genesis-blockhash"]);
assert_eq!(
parse_command(&test_get_genesis_hash).unwrap(),
parse_command(&test_get_genesis_blockhash).unwrap(),
CliCommandInfo {
command: CliCommand::GetGenesisHash,
command: CliCommand::GetGenesisBlockhash,
require_keypair: false
}
);
@ -722,9 +461,7 @@ mod tests {
assert_eq!(
parse_command(&test_get_slot).unwrap(),
CliCommandInfo {
command: CliCommand::GetSlot {
commitment_config: CommitmentConfig::recent(),
},
command: CliCommand::GetSlot,
require_keypair: false
}
);
@ -735,36 +472,25 @@ mod tests {
assert_eq!(
parse_command(&test_transaction_count).unwrap(),
CliCommandInfo {
command: CliCommand::GetTransactionCount {
commitment_config: CommitmentConfig::recent(),
},
command: CliCommand::GetTransactionCount,
require_keypair: false
}
);
let test_ping = test_commands.clone().get_matches_from(vec![
"test",
"ping",
"-i",
"1",
"-c",
"2",
"-t",
"3",
"--confirmed",
]);
let test_ping = test_commands
.clone()
.get_matches_from(vec!["test", "ping", "-i", "1", "-c", "2", "-t", "3"]);
assert_eq!(
parse_command(&test_ping).unwrap(),
CliCommandInfo {
command: CliCommand::Ping {
lamports: 1,
interval: Duration::from_secs(1),
count: Some(2),
timeout: Duration::from_secs(3),
commitment_config: CommitmentConfig::default(),
},
require_keypair: true
}
);
}
// TODO: Add process tests
}

View File

@ -16,14 +16,14 @@ lazy_static! {
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
pub struct Config {
pub url: String,
pub keypair_path: String,
pub keypair: String,
}
impl Config {
pub fn new(url: &str, keypair_path: &str) -> Self {
pub fn new(url: &str, keypair: &str) -> Self {
Self {
url: url.to_string(),
keypair_path: keypair_path.to_string(),
keypair: keypair.to_string(),
}
}

View File

@ -1,5 +1,4 @@
use console::style;
use solana_sdk::transaction::Transaction;
// Pretty print a "name value"
pub fn println_name_value(name: &str, value: &str) {
@ -23,14 +22,3 @@ pub fn println_name_value_or(name: &str, value: &str, default_value: &str) {
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!();
}

View File

@ -1,11 +1,9 @@
use crate::keypair::{keypair_from_seed_phrase, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG};
use clap::ArgMatches;
use solana_sdk::{
native_token::sol_to_lamports,
pubkey::Pubkey,
signature::{read_keypair_file, Keypair, KeypairUtil, Signature},
signature::{read_keypair_file, Keypair, KeypairUtil},
};
use std::str::FromStr;
// Return parsed values from matches at `name`
pub fn values_of<T>(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<T>>
@ -34,12 +32,7 @@ where
// Return the keypair for an argument with filename `name` or None if not present.
pub fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option<Keypair> {
if let Some(value) = matches.value_of(name) {
if value == ASK_KEYWORD {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
keypair_from_seed_phrase(name, skip_validation, true).ok()
} else {
read_keypair_file(value).ok()
}
read_keypair_file(value).ok()
} else {
None
}
@ -51,20 +44,6 @@ pub fn pubkey_of(matches: &ArgMatches<'_>, name: &str) -> Option<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> {
if matches.value_of(unit) == Some("lamports") {
value_of(matches, name)
@ -146,17 +125,14 @@ mod tests {
let matches = app()
.clone()
.get_matches_from(vec!["test", "--single", &outfile]);
assert_eq!(
keypair_of(&matches, "single").unwrap().pubkey(),
keypair.pubkey()
);
assert!(keypair_of(&matches, "multiple").is_none());
assert_eq!(keypair_of(&matches, "single"), Some(keypair));
assert_eq!(keypair_of(&matches, "multiple"), None);
let matches =
app()
.clone()
.get_matches_from(vec!["test", "--single", "random_keypair_file.json"]);
assert!(keypair_of(&matches, "single").is_none());
assert_eq!(keypair_of(&matches, "single"), None);
fs::remove_file(&outfile).unwrap();
}
@ -187,25 +163,4 @@ mod tests {
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)])
);
}
}

View File

@ -0,0 +1,36 @@
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::read_keypair_file;
// Return an error if a pubkey cannot be parsed.
pub fn is_pubkey(string: String) -> Result<(), String> {
match string.parse::<Pubkey>() {
Ok(_) => Ok(()),
Err(err) => Err(format!("{:?}", err)),
}
}
// Return an error if a keypair file cannot be parsed.
pub fn is_keypair(string: String) -> Result<(), String> {
read_keypair_file(&string)
.map(|_| ())
.map_err(|err| format!("{:?}", err))
}
// Return an error if string cannot be parsed as pubkey string or keypair file location
pub fn is_pubkey_or_keypair(string: String) -> Result<(), String> {
is_pubkey(string.clone()).or_else(|_| is_keypair(string))
}
// Return an error if a url cannot be parsed.
pub fn is_url(string: String) -> Result<(), String> {
match url::Url::parse(&string) {
Ok(url) => {
if url.has_host() {
Ok(())
} else {
Err("no host provided".to_string())
}
}
Err(err) => Err(format!("{:?}", err)),
}
}

View File

@ -5,6 +5,8 @@ pub mod cli;
pub mod cluster_query;
pub mod config;
pub mod display;
pub mod input_parsers;
pub mod input_validators;
pub mod stake;
pub mod storage;
pub mod validator_info;

View File

@ -1,45 +1,34 @@
use clap::{crate_description, crate_name, Arg, ArgGroup, ArgMatches, SubCommand};
use clap::{crate_description, crate_name, crate_version, Arg, ArgGroup, ArgMatches, SubCommand};
use console::style;
use solana_clap_utils::{
input_validators::is_url,
keypair::{
self, keypair_input, KeypairWithSource, ASK_SEED_PHRASE_ARG,
SKIP_SEED_PHRASE_VALIDATION_ARG,
},
};
use solana_cli::{
cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliError},
config::{self, Config},
display::{println_name_value, println_name_value_or},
input_validators::is_url,
};
use solana_sdk::signature::read_keypair_file;
use std::error;
fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error>> {
let parse_args = match matches.subcommand() {
("get", Some(subcommand_matches)) => {
if let Some(config_file) = matches.value_of("config_file") {
let default_cli_config = CliConfig::default();
let config = Config::load(config_file).unwrap_or_default();
if let Some(field) = subcommand_matches.value_of("specific_setting") {
let (value, default_value) = match field {
"url" => (config.url, CliConfig::default_json_rpc_url()),
"keypair" => (config.keypair_path, CliConfig::default_keypair_path()),
"url" => (config.url, default_cli_config.json_rpc_url),
"keypair" => (config.keypair, default_cli_config.keypair_path.unwrap()),
_ => unreachable!(),
};
println_name_value_or(&format!("* {}:", field), &value, &default_value);
} else {
println_name_value("Wallet Config:", config_file);
println_name_value_or(
"* url:",
&config.url,
&CliConfig::default_json_rpc_url(),
);
println_name_value_or("* url:", &config.url, &default_cli_config.json_rpc_url);
println_name_value_or(
"* keypair:",
&config.keypair_path,
&CliConfig::default_keypair_path(),
&config.keypair,
&default_cli_config.keypair_path.unwrap(),
);
}
} else {
@ -57,12 +46,12 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
config.url = url.to_string();
}
if let Some(keypair) = subcommand_matches.value_of("keypair") {
config.keypair_path = keypair.to_string();
config.keypair = keypair.to_string();
}
config.save(config_file)?;
println_name_value("Wallet Config Updated:", config_file);
println_name_value("* url:", &config.url);
println_name_value("* keypair:", &config.keypair_path);
println_name_value("* keypair:", &config.keypair);
} else {
println!(
"{} Either provide the `--config` arg or ensure home directory exists to use the default config location",
@ -97,138 +86,101 @@ pub fn parse_args(matches: &ArgMatches<'_>) -> Result<CliConfig, Box<dyn error::
} = parse_command(&matches)?;
let (keypair, keypair_path) = if require_keypair {
let KeypairWithSource { keypair, source } = keypair_input(&matches, "keypair")?;
match source {
keypair::Source::File => (
keypair,
Some(matches.value_of("keypair").unwrap().to_string()),
),
keypair::Source::SeedPhrase => (keypair, None),
keypair::Source::Generated => {
let keypair_path = if config.keypair_path != "" {
config.keypair_path
} else {
let default_keypair_path = CliConfig::default_keypair_path();
if !std::path::Path::new(&default_keypair_path).exists() {
return Err(CliError::KeypairFileNotFound(
"Generate a new keypair with `solana-keygen new`".to_string(),
)
.into());
}
default_keypair_path
};
let keypair = read_keypair_file(&keypair_path).or_else(|err| {
Err(CliError::BadParameter(format!(
"{}: Unable to open keypair file: {}",
err, keypair_path
)))
})?;
(keypair, Some(keypair_path))
let keypair_path = if matches.is_present("keypair") {
matches.value_of("keypair").unwrap().to_string()
} else if config.keypair != "" {
config.keypair
} else {
let default = CliConfig::default();
let maybe_keypair_path = default.keypair_path.unwrap();
if !std::path::Path::new(&maybe_keypair_path).exists() {
return Err(CliError::KeypairFileNotFound(
"Generate a new keypair with `solana-keygen new`".to_string(),
)
.into());
}
}
maybe_keypair_path
};
let keypair = read_keypair_file(&keypair_path).or_else(|err| {
Err(CliError::BadParameter(format!(
"{}: Unable to open keypair file: {}",
err, keypair_path
)))
})?;
(keypair, Some(keypair_path.to_string()))
} else {
let default = CliConfig::default();
(default.keypair, None)
};
let print_header = !matches.is_present("no_header");
Ok(CliConfig {
command,
json_rpc_url,
keypair,
keypair_path,
rpc_client: None,
print_header,
})
}
fn main() -> Result<(), Box<dyn error::Error>> {
solana_logger::setup();
let matches = app(
crate_name!(),
crate_description!(),
solana_clap_utils::version!(),
)
.arg({
let arg = Arg::with_name("config_file")
.short("C")
.long("config")
.value_name("PATH")
.takes_value(true)
.global(true)
.help("Configuration file to use");
if let Some(ref config_file) = *config::CONFIG_FILE {
arg.default_value(&config_file)
} else {
arg
}
})
.arg(
Arg::with_name("json_rpc_url")
.short("u")
.long("url")
.value_name("URL")
.takes_value(true)
.global(true)
.validator(is_url)
.help("JSON RPC URL for the solana cluster"),
)
.arg(
Arg::with_name("keypair")
.short("k")
.long("keypair")
.value_name("PATH")
.global(true)
.takes_value(true)
.help("/path/to/id.json"),
)
.arg(
Arg::with_name("no_header")
.long("no-header")
.global(true)
.help("Disable information header"),
)
.arg(
Arg::with_name(ASK_SEED_PHRASE_ARG.name)
.long(ASK_SEED_PHRASE_ARG.long)
.value_name("KEYPAIR NAME")
.global(true)
.takes_value(true)
.possible_values(&["keypair"])
.help(ASK_SEED_PHRASE_ARG.help),
)
.arg(
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
.global(true)
.help(SKIP_SEED_PHRASE_VALIDATION_ARG.help),
)
.subcommand(
SubCommand::with_name("get")
.about("Get cli config settings")
.arg(
Arg::with_name("specific_setting")
.index(1)
.value_name("CONFIG_FIELD")
.takes_value(true)
.possible_values(&["url", "keypair"])
.help("Return a specific config setting"),
),
)
.subcommand(
SubCommand::with_name("set")
.about("Set a cli config setting")
.group(
ArgGroup::with_name("config_settings")
.args(&["json_rpc_url", "keypair"])
.multiple(true)
.required(true),
),
)
.get_matches();
let matches = app(crate_name!(), crate_description!(), crate_version!())
.arg({
let arg = Arg::with_name("config_file")
.short("C")
.long("config")
.value_name("PATH")
.takes_value(true)
.global(true)
.help("Configuration file to use");
if let Some(ref config_file) = *config::CONFIG_FILE {
arg.default_value(&config_file)
} else {
arg
}
})
.arg(
Arg::with_name("json_rpc_url")
.short("u")
.long("url")
.value_name("URL")
.takes_value(true)
.global(true)
.validator(is_url)
.help("JSON RPC URL for the solana cluster"),
)
.arg(
Arg::with_name("keypair")
.short("k")
.long("keypair")
.value_name("PATH")
.global(true)
.takes_value(true)
.help("/path/to/id.json"),
)
.subcommand(
SubCommand::with_name("get")
.about("Get cli config settings")
.arg(
Arg::with_name("specific_setting")
.index(1)
.value_name("CONFIG_FIELD")
.takes_value(true)
.possible_values(&["url", "keypair"])
.help("Return a specific config setting"),
),
)
.subcommand(
SubCommand::with_name("set")
.about("Set a cli config setting")
.group(
ArgGroup::with_name("config_settings")
.args(&["json_rpc_url", "keypair"])
.multiple(true)
.required(true),
),
)
.get_matches();
if parse_settings(&matches)? {
let config = parse_args(&matches)?;

View File

@ -1,31 +1,28 @@
use crate::cli::{
build_balance_message, check_account_for_fee, check_unique_pubkeys,
get_blockhash_fee_calculator, log_instruction_custom_error, replace_signatures, return_signers,
CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
use crate::{
cli::{
build_balance_message, check_account_for_fee, check_unique_pubkeys,
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
ProcessResult,
},
input_parsers::*,
input_validators::*,
};
use clap::{App, Arg, ArgMatches, SubCommand};
use console::style;
use solana_clap_utils::{input_parsers::*, input_validators::*};
use solana_client::rpc_client::RpcClient;
use solana_sdk::signature::{Keypair, Signature};
use solana_sdk::{
account_utils::State,
hash::Hash,
pubkey::Pubkey,
signature::KeypairUtil,
system_instruction::SystemError,
sysvar::{
stake_history::{self, StakeHistory},
Sysvar,
},
sysvar::stake_history::{self, StakeHistory},
transaction::Transaction,
};
use solana_stake_program::stake_state::Meta;
use solana_stake_program::{
use solana_stake_api::{
stake_instruction::{self, StakeError},
stake_state::{Authorized, Lockup, StakeAuthorize, StakeState},
};
use solana_vote_program::vote_state::VoteState;
use solana_vote_api::vote_state::VoteState;
use std::ops::Deref;
pub trait StakeSubCommands {
@ -38,13 +35,13 @@ impl StakeSubCommands for App<'_, '_> {
SubCommand::with_name("create-stake-account")
.about("Create a stake account")
.arg(
Arg::with_name("stake_account")
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKE ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_keypair_or_ask_keyword)
.help("Keypair of the stake account to fund")
.validator(is_pubkey_or_keypair)
.help("Address of the stake account to fund (pubkey or keypair)")
)
.arg(
Arg::with_name("amount")
@ -122,29 +119,6 @@ impl StakeSubCommands for App<'_, '_> {
.validator(is_pubkey_or_keypair)
.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::with_name("stake-authorize-staker")
@ -201,29 +175,6 @@ impl StakeSubCommands for App<'_, '_> {
.required(true)
.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::with_name("withdraw-stake")
@ -318,8 +269,8 @@ impl StakeSubCommands for App<'_, '_> {
}
pub fn parse_stake_create_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let stake_account = keypair_of(matches, "stake_account").unwrap();
let epoch = value_of(&matches, "lockup").unwrap_or(0);
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let slot = value_of(&matches, "lockup").unwrap_or(0);
let custodian = pubkey_of(matches, "custodian").unwrap_or_default();
let staker = pubkey_of(matches, "authorized_staker");
let withdrawer = pubkey_of(matches, "authorized_withdrawer");
@ -327,10 +278,10 @@ pub fn parse_stake_create_account(matches: &ArgMatches<'_>) -> Result<CliCommand
Ok(CliCommandInfo {
command: CliCommand::CreateStakeAccount {
stake_account: stake_account.into(),
stake_account_pubkey,
staker,
withdrawer,
lockup: Lockup { custodian, epoch },
lockup: Lockup { custodian, slot },
lamports,
},
require_keypair: true,
@ -341,20 +292,10 @@ pub fn parse_stake_delegate_stake(matches: &ArgMatches<'_>) -> Result<CliCommand
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
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 {
command: CliCommand::DelegateStake {
stake_account_pubkey,
vote_account_pubkey,
force,
sign_only,
signers,
blockhash,
},
require_keypair: !sign_only,
command: CliCommand::DelegateStake(stake_account_pubkey, vote_account_pubkey, force),
require_keypair: true,
})
}
@ -386,17 +327,9 @@ pub fn parse_redeem_vote_credits(matches: &ArgMatches<'_>) -> Result<CliCommandI
pub fn parse_stake_deactivate_stake(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
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 {
command: CliCommand::DeactivateStake {
stake_account_pubkey,
sign_only,
signers,
blockhash,
},
require_keypair: !sign_only,
command: CliCommand::DeactivateStake(stake_account_pubkey),
require_keypair: true,
})
}
@ -438,16 +371,15 @@ pub fn parse_show_stake_history(matches: &ArgMatches<'_>) -> Result<CliCommandIn
pub fn process_create_stake_account(
rpc_client: &RpcClient,
config: &CliConfig,
stake_account: &Keypair,
stake_account_pubkey: &Pubkey,
staker: &Option<Pubkey>,
withdrawer: &Option<Pubkey>,
lockup: &Lockup,
lamports: u64,
) -> ProcessResult {
let stake_account_pubkey = stake_account.pubkey();
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
(&stake_account_pubkey, "stake_account_pubkey".to_string()),
(stake_account_pubkey, "stake_account_pubkey".to_string()),
)?;
if rpc_client.get_account(&stake_account_pubkey).is_ok() {
@ -477,7 +409,7 @@ pub fn process_create_stake_account(
let ixs = stake_instruction::create_stake_account_with_lockup(
&config.keypair.pubkey(),
&stake_account_pubkey,
stake_account_pubkey,
&authorized,
lockup,
lamports,
@ -486,12 +418,11 @@ pub fn process_create_stake_account(
let mut tx = Transaction::new_signed_with_payer(
ixs,
Some(&config.keypair.pubkey()),
&[&config.keypair, stake_account],
&[&config.keypair],
recent_blockhash,
);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result =
rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, stake_account]);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<SystemError>(result)
}
@ -529,12 +460,8 @@ pub fn process_deactivate_stake_account(
rpc_client: &RpcClient,
config: &CliConfig,
stake_account_pubkey: &Pubkey,
sign_only: bool,
signers: &Option<Vec<(Pubkey, Signature)>>,
blockhash: Option<Hash>,
) -> ProcessResult {
let (recent_blockhash, fee_calculator) =
get_blockhash_fee_calculator(rpc_client, sign_only, blockhash)?;
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![stake_instruction::deactivate_stake(
stake_account_pubkey,
&config.keypair.pubkey(),
@ -545,16 +472,9 @@ pub fn process_deactivate_stake_account(
&[&config.keypair],
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)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<StakeError>(result)
}
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<StakeError>(result)
}
pub fn process_withdraw_stake(
@ -613,7 +533,7 @@ pub fn process_show_stake_account(
use_lamports_unit: bool,
) -> ProcessResult {
let stake_account = rpc_client.get_account(stake_account_pubkey)?;
if stake_account.owner != solana_stake_program::id() {
if stake_account.owner != solana_stake_api::id() {
return Err(CliError::RpcRequestError(
format!("{:?} is not a stake account", stake_account_pubkey).to_string(),
)
@ -624,16 +544,11 @@ pub fn process_show_stake_account(
println!("authorized withdrawer: {}", authorized.staker);
}
fn show_lockup(lockup: &Lockup) {
println!("lockup epoch: {}", lockup.epoch);
println!("lockup slot: {}", lockup.slot);
println!("lockup custodian: {}", lockup.custodian);
}
match stake_account.state() {
Ok(StakeState::Stake(
Meta {
authorized, lockup, ..
},
stake,
)) => {
Ok(StakeState::Stake(authorized, lockup, stake)) => {
println!(
"total stake: {}",
build_balance_message(stake_account.lamports, use_lamports_unit, true)
@ -641,23 +556,19 @@ pub fn process_show_stake_account(
println!("credits observed: {}", stake.credits_observed);
println!(
"delegated stake: {}",
build_balance_message(stake.delegation.stake, use_lamports_unit, true)
build_balance_message(stake.stake, use_lamports_unit, true)
);
if stake.delegation.voter_pubkey != Pubkey::default() {
println!("delegated voter pubkey: {}", stake.delegation.voter_pubkey);
if stake.voter_pubkey != Pubkey::default() {
println!("delegated voter pubkey: {}", stake.voter_pubkey);
}
println!(
"stake activates starting from epoch: {}",
if stake.delegation.activation_epoch < std::u64::MAX {
stake.delegation.activation_epoch
} else {
0
}
stake.activation_epoch
);
if stake.delegation.deactivation_epoch < std::u64::MAX {
if stake.deactivation_epoch < std::u64::MAX {
println!(
"stake deactivates starting from epoch: {}",
stake.delegation.deactivation_epoch
stake.deactivation_epoch
);
}
show_authorized(&authorized);
@ -666,9 +577,7 @@ pub fn process_show_stake_account(
}
Ok(StakeState::RewardsPool) => Ok("Stake account is a rewards pool".to_string()),
Ok(StakeState::Uninitialized) => Ok("Stake account is uninitialized".to_string()),
Ok(StakeState::Initialized(Meta {
authorized, lockup, ..
})) => {
Ok(StakeState::Initialized(authorized, lockup)) => {
println!("Stake account is undelegated");
show_authorized(&authorized);
show_lockup(&lockup);
@ -721,9 +630,6 @@ pub fn process_delegate_stake(
stake_account_pubkey: &Pubkey,
vote_account_pubkey: &Pubkey,
force: bool,
sign_only: bool,
signers: &Option<Vec<(Pubkey, Signature)>>,
blockhash: Option<Hash>,
) -> ProcessResult {
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
@ -770,8 +676,7 @@ pub fn process_delegate_stake(
}
}
let (recent_blockhash, fee_calculator) =
get_blockhash_fee_calculator(rpc_client, sign_only, blockhash)?;
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![stake_instruction::delegate_stake(
stake_account_pubkey,
@ -785,70 +690,45 @@ pub fn process_delegate_stake(
&[&config.keypair],
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)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<StakeError>(result)
}
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<StakeError>(result)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cli::{app, parse_command};
use solana_sdk::signature::write_keypair;
use tempfile::NamedTempFile;
fn make_tmp_file() -> (String, NamedTempFile) {
let tmp_file = NamedTempFile::new().unwrap();
(String::from(tmp_file.path().to_str().unwrap()), tmp_file)
}
#[test]
fn test_parse_command() {
let test_commands = app("test", "desc", "version");
let (keypair_file, mut tmp_file) = make_tmp_file();
let stake_account_keypair = Keypair::new();
write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
let stake_account_pubkey = stake_account_keypair.pubkey();
let stake_account_string = stake_account_pubkey.to_string();
let pubkey = Pubkey::new_rand();
let pubkey_string = format!("{}", pubkey);
let test_authorize_staker = test_commands.clone().get_matches_from(vec![
"test",
"stake-authorize-staker",
&stake_account_string,
&stake_account_string,
&pubkey_string,
&pubkey_string,
]);
assert_eq!(
parse_command(&test_authorize_staker).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize(
stake_account_pubkey,
stake_account_pubkey,
StakeAuthorize::Staker
),
command: CliCommand::StakeAuthorize(pubkey, pubkey, StakeAuthorize::Staker),
require_keypair: true
}
);
let test_authorize_withdrawer = test_commands.clone().get_matches_from(vec![
"test",
"stake-authorize-withdrawer",
&stake_account_string,
&stake_account_string,
&pubkey_string,
&pubkey_string,
]);
assert_eq!(
parse_command(&test_authorize_withdrawer).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize(
stake_account_pubkey,
stake_account_pubkey,
StakeAuthorize::Withdrawer
),
command: CliCommand::StakeAuthorize(pubkey, pubkey, StakeAuthorize::Withdrawer),
require_keypair: true
}
);
@ -861,7 +741,7 @@ mod tests {
let test_create_stake_account = test_commands.clone().get_matches_from(vec![
"test",
"create-stake-account",
&keypair_file,
&pubkey_string,
"50",
"--authorized-staker",
&authorized_string,
@ -877,11 +757,11 @@ mod tests {
parse_command(&test_create_stake_account).unwrap(),
CliCommandInfo {
command: CliCommand::CreateStakeAccount {
stake_account: stake_account_keypair.into(),
stake_account_pubkey: pubkey,
staker: Some(authorized),
withdrawer: Some(authorized),
lockup: Lockup {
epoch: 43,
slot: 43,
custodian,
},
lamports: 50
@ -889,29 +769,24 @@ mod tests {
require_keypair: true
}
);
let (keypair_file, mut tmp_file) = make_tmp_file();
let stake_account_keypair = Keypair::new();
write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
let stake_account_pubkey = stake_account_keypair.pubkey();
let stake_account_string = stake_account_pubkey.to_string();
let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
"test",
"create-stake-account",
&keypair_file,
&pubkey_string,
"50",
"lamports",
]);
assert_eq!(
parse_command(&test_create_stake_account2).unwrap(),
CliCommandInfo {
command: CliCommand::CreateStakeAccount {
stake_account: stake_account_keypair.into(),
stake_account_pubkey: pubkey,
staker: None,
withdrawer: None,
lockup: Lockup::default(),
lockup: Lockup {
slot: 0,
custodian: Pubkey::default(),
},
lamports: 50
},
require_keypair: true
@ -919,25 +794,18 @@ mod tests {
);
// Test DelegateStake Subcommand
let vote_account_pubkey = Pubkey::new_rand();
let vote_account_string = vote_account_pubkey.to_string();
let stake_pubkey = Pubkey::new_rand();
let stake_pubkey_string = stake_pubkey.to_string();
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
"test",
"delegate-stake",
&stake_account_string,
&vote_account_string,
&stake_pubkey_string,
&pubkey_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: None
},
command: CliCommand::DelegateStake(stake_pubkey, pubkey, false),
require_keypair: true
}
);
@ -946,124 +814,13 @@ mod tests {
"test",
"delegate-stake",
"--force",
&stake_account_string,
&vote_account_string,
&stake_pubkey_string,
&pubkey_string,
]);
assert_eq!(
parse_command(&test_delegate_stake).unwrap(),
CliCommandInfo {
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
},
command: CliCommand::DelegateStake(stake_pubkey, pubkey, true),
require_keypair: true
}
);
@ -1072,8 +829,8 @@ mod tests {
let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
"test",
"withdraw-stake",
&stake_account_string,
&stake_account_string,
&stake_pubkey_string,
&pubkey_string,
"42",
"lamports",
]);
@ -1081,7 +838,7 @@ mod tests {
assert_eq!(
parse_command(&test_withdraw_stake).unwrap(),
CliCommandInfo {
command: CliCommand::WithdrawStake(stake_account_pubkey, stake_account_pubkey, 42),
command: CliCommand::WithdrawStake(stake_pubkey, pubkey, 42),
require_keypair: true
}
);
@ -1090,111 +847,15 @@ mod tests {
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
"test",
"deactivate-stake",
&stake_account_string,
&stake_pubkey_string,
]);
assert_eq!(
parse_command(&test_deactivate_stake).unwrap(),
CliCommandInfo {
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
},
command: CliCommand::DeactivateStake(stake_pubkey),
require_keypair: true
}
);
}
// TODO: Add process tests
}

View File

@ -1,16 +1,18 @@
use crate::cli::{
check_account_for_fee, check_unique_pubkeys, log_instruction_custom_error, CliCommand,
CliCommandInfo, CliConfig, CliError, ProcessResult,
use crate::{
cli::{
check_account_for_fee, check_unique_pubkeys, log_instruction_custom_error, CliCommand,
CliCommandInfo, CliConfig, CliError, ProcessResult,
},
input_parsers::*,
input_validators::*,
};
use clap::{App, Arg, ArgMatches, SubCommand};
use solana_clap_utils::{input_parsers::*, input_validators::*};
use solana_client::rpc_client::RpcClient;
use solana_sdk::signature::Keypair;
use solana_sdk::{
account_utils::State, message::Message, pubkey::Pubkey, signature::KeypairUtil,
system_instruction::SystemError, transaction::Transaction,
};
use solana_storage_program::storage_instruction::{self, StorageAccountType};
use solana_storage_api::storage_instruction::{self, StorageAccountType};
pub trait StorageSubCommands {
fn storage_subcommands(self) -> Self;
@ -30,12 +32,12 @@ impl StorageSubCommands for App<'_, '_> {
.validator(is_pubkey_or_keypair),
)
.arg(
Arg::with_name("storage_account")
Arg::with_name("storage_account_pubkey")
.index(2)
.value_name("STORAGE ACCOUNT")
.value_name("STORAGE ACCOUNT PUBKEY")
.takes_value(true)
.required(true)
.validator(is_keypair_or_ask_keyword),
.validator(is_pubkey_or_keypair),
),
)
.subcommand(
@ -50,12 +52,12 @@ impl StorageSubCommands for App<'_, '_> {
.validator(is_pubkey_or_keypair),
)
.arg(
Arg::with_name("storage_account")
Arg::with_name("storage_account_pubkey")
.index(2)
.value_name("STORAGE ACCOUNT")
.value_name("STORAGE ACCOUNT PUBKEY")
.takes_value(true)
.required(true)
.validator(is_keypair_or_ask_keyword),
.validator(is_pubkey_or_keypair),
),
)
.subcommand(
@ -100,11 +102,11 @@ pub fn parse_storage_create_archiver_account(
matches: &ArgMatches<'_>,
) -> Result<CliCommandInfo, CliError> {
let account_owner = pubkey_of(matches, "storage_account_owner").unwrap();
let storage_account = keypair_of(matches, "storage_account").unwrap();
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
Ok(CliCommandInfo {
command: CliCommand::CreateStorageAccount {
account_owner,
storage_account: storage_account.into(),
storage_account_pubkey,
account_type: StorageAccountType::Archiver,
},
require_keypair: true,
@ -115,11 +117,11 @@ pub fn parse_storage_create_validator_account(
matches: &ArgMatches<'_>,
) -> Result<CliCommandInfo, CliError> {
let account_owner = pubkey_of(matches, "storage_account_owner").unwrap();
let storage_account = keypair_of(matches, "storage_account").unwrap();
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
Ok(CliCommandInfo {
command: CliCommand::CreateStorageAccount {
account_owner,
storage_account: storage_account.into(),
storage_account_pubkey,
account_type: StorageAccountType::Validator,
},
require_keypair: true,
@ -152,10 +154,9 @@ pub fn process_create_storage_account(
rpc_client: &RpcClient,
config: &CliConfig,
account_owner: &Pubkey,
storage_account: &Keypair,
storage_account_pubkey: &Pubkey,
account_type: StorageAccountType,
) -> ProcessResult {
let storage_account_pubkey = storage_account.pubkey();
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
(
@ -167,18 +168,13 @@ pub fn process_create_storage_account(
let ixs = storage_instruction::create_storage_account(
&config.keypair.pubkey(),
&account_owner,
&storage_account_pubkey,
storage_account_pubkey,
1,
account_type,
);
let mut tx = Transaction::new_signed_instructions(
&[&config.keypair, &storage_account],
ixs,
recent_blockhash,
);
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result =
rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, &storage_account]);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<SystemError>(result)
}
@ -208,14 +204,14 @@ pub fn process_show_storage_account(
) -> ProcessResult {
let account = rpc_client.get_account(storage_account_pubkey)?;
if account.owner != solana_storage_program::id() {
if account.owner != solana_storage_api::id() {
return Err(CliError::RpcRequestError(
format!("{:?} is not a storage account", storage_account_pubkey).to_string(),
)
.into());
}
use solana_storage_program::storage_contract::StorageContract;
use solana_storage_api::storage_contract::StorageContract;
let storage_contract: StorageContract = account.state().map_err(|err| {
CliError::RpcRequestError(
format!("Unable to deserialize storage account: {:?}", err).to_string(),
@ -230,60 +226,45 @@ pub fn process_show_storage_account(
mod tests {
use super::*;
use crate::cli::{app, parse_command};
use solana_sdk::signature::write_keypair;
use tempfile::NamedTempFile;
fn make_tmp_file() -> (String, NamedTempFile) {
let tmp_file = NamedTempFile::new().unwrap();
(String::from(tmp_file.path().to_str().unwrap()), tmp_file)
}
#[test]
fn test_parse_command() {
let test_commands = app("test", "desc", "version");
let pubkey = Pubkey::new_rand();
let pubkey_string = pubkey.to_string();
let (keypair_file, mut tmp_file) = make_tmp_file();
let storage_account_keypair = Keypair::new();
write_keypair(&storage_account_keypair, tmp_file.as_file_mut()).unwrap();
let storage_account_pubkey = Pubkey::new_rand();
let storage_account_string = storage_account_pubkey.to_string();
let test_create_archiver_storage_account = test_commands.clone().get_matches_from(vec![
"test",
"create-archiver-storage-account",
&pubkey_string,
&keypair_file,
&storage_account_string,
]);
assert_eq!(
parse_command(&test_create_archiver_storage_account).unwrap(),
CliCommandInfo {
command: CliCommand::CreateStorageAccount {
account_owner: pubkey,
storage_account: storage_account_keypair.into(),
storage_account_pubkey,
account_type: StorageAccountType::Archiver,
},
require_keypair: true
}
);
let (keypair_file, mut tmp_file) = make_tmp_file();
let storage_account_keypair = Keypair::new();
write_keypair(&storage_account_keypair, tmp_file.as_file_mut()).unwrap();
let storage_account_pubkey = storage_account_keypair.pubkey();
let storage_account_string = storage_account_pubkey.to_string();
let test_create_validator_storage_account = test_commands.clone().get_matches_from(vec![
"test",
"create-validator-storage-account",
&pubkey_string,
&keypair_file,
&storage_account_string,
]);
assert_eq!(
parse_command(&test_create_validator_storage_account).unwrap(),
CliCommandInfo {
command: CliCommand::CreateStorageAccount {
account_owner: pubkey,
storage_account: storage_account_keypair.into(),
storage_account_pubkey,
account_type: StorageAccountType::Validator,
},
require_keypair: true
@ -307,4 +288,5 @@ mod tests {
}
);
}
// TODO: Add process tests
}

View File

@ -1,35 +1,37 @@
use crate::{
cli::{check_account_for_fee, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
display::println_name_value,
input_parsers::pubkey_of,
input_validators::{is_pubkey, is_url},
};
use bincode::deserialize;
use clap::{App, Arg, ArgMatches, SubCommand};
use reqwest::Client;
use serde_derive::{Deserialize, Serialize};
use serde_json::{Map, Value};
use solana_clap_utils::{
input_parsers::pubkey_of,
input_validators::{is_pubkey, is_url},
};
use solana_client::rpc_client::RpcClient;
use solana_config_program::{config_instruction, get_config_data, ConfigKeys, ConfigState};
use solana_sdk::{
account::Account,
commitment_config::CommitmentConfig,
message::Message,
pubkey::Pubkey,
signature::{Keypair, KeypairUtil},
transaction::Transaction,
};
use solana_config_api::{config_instruction, get_config_data, ConfigKeys, ConfigState};
use solana_sdk::account::Account;
use solana_sdk::message::Message;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::transaction::Transaction;
use std::error;
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
pub const MAX_LONG_FIELD_LENGTH: usize = 300;
pub const MAX_VALIDATOR_INFO: u64 = 576;
solana_sdk::declare_id!("Va1idator1nfo111111111111111111111111111111");
// Config account key: Va1idator1nfo111111111111111111111111111111
pub const REGISTER_CONFIG_KEY: [u8; 32] = [
7, 81, 151, 1, 116, 72, 242, 172, 93, 194, 60, 158, 188, 122, 199, 140, 10, 39, 37, 122, 198,
20, 69, 141, 224, 164, 241, 111, 128, 0, 0, 0,
];
solana_sdk::solana_name_id!(
REGISTER_CONFIG_KEY,
"Va1idator1nfo111111111111111111111111111111"
);
#[derive(Debug, Deserialize, PartialEq, Serialize, Default)]
pub struct ValidatorInfo {
@ -128,7 +130,7 @@ fn parse_validator_info(
pubkey: &Pubkey,
account: &Account,
) -> Result<(Pubkey, Map<String, serde_json::value::Value>), Box<dyn error::Error>> {
if account.owner != solana_config_program::id() {
if account.owner != solana_config_api::id() {
return Err(format!("{} is not a validator info account", pubkey).into());
}
let key_list: ConfigKeys = deserialize(&account.data)?;
@ -272,7 +274,7 @@ pub fn process_set_validator_info(
info: validator_string,
};
// Check for existing validator-info account
let all_config = rpc_client.get_program_accounts(&solana_config_program::id())?;
let all_config = rpc_client.get_program_accounts(&solana_config_api::id())?;
let existing_account = all_config
.iter()
.filter(|(_, account)| {
@ -295,9 +297,7 @@ pub fn process_set_validator_info(
};
// Check existence of validator-info account
let balance = rpc_client
.poll_get_balance_with_commitment(&info_pubkey, CommitmentConfig::default())
.unwrap_or(0);
let balance = rpc_client.poll_get_balance(&info_pubkey).unwrap_or(0);
let keys = vec![(id(), false), (config.keypair.pubkey(), true)];
let (message, signers): (Message, Vec<&Keypair>) = if balance == 0 {
@ -362,7 +362,7 @@ pub fn process_get_validator_info(rpc_client: &RpcClient, pubkey: Option<Pubkey>
rpc_client.get_account(&validator_info_pubkey)?,
)]
} else {
let all_config = rpc_client.get_program_accounts(&solana_config_program::id())?;
let all_config = rpc_client.get_program_accounts(&solana_config_api::id())?;
all_config
.into_iter()
.filter(|(_, validator_info_account)| {
@ -485,7 +485,7 @@ mod tests {
parse_validator_info(
&Pubkey::default(),
&Account {
owner: solana_config_program::id(),
owner: solana_config_api::id(),
data,
..Account::default()
}

View File

@ -1,16 +1,19 @@
use crate::cli::{
build_balance_message, check_account_for_fee, check_unique_pubkeys,
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
use crate::{
cli::{
build_balance_message, check_account_for_fee, check_unique_pubkeys,
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
ProcessResult,
},
input_parsers::*,
input_validators::*,
};
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
use solana_clap_utils::{input_parsers::*, input_validators::*};
use solana_client::rpc_client::RpcClient;
use solana_sdk::signature::Keypair;
use solana_sdk::{
account::Account, pubkey::Pubkey, signature::KeypairUtil, system_instruction::SystemError,
transaction::Transaction,
};
use solana_vote_program::{
use solana_vote_api::{
vote_instruction::{self, VoteError},
vote_state::{VoteAuthorize, VoteInit, VoteState},
};
@ -25,13 +28,13 @@ impl VoteSubCommands for App<'_, '_> {
SubCommand::with_name("create-vote-account")
.about("Create a vote account")
.arg(
Arg::with_name("vote_account")
Arg::with_name("vote_account_pubkey")
.index(1)
.value_name("VOTE ACCOUNT KEYPAIR")
.value_name("VOTE ACCOUNT PUBKEY")
.takes_value(true)
.required(true)
.validator(is_keypair_or_ask_keyword)
.help("Vote account keypair to fund"),
.validator(is_pubkey_or_keypair)
.help("Vote account address to fund"),
)
.arg(
Arg::with_name("node_pubkey")
@ -47,7 +50,7 @@ impl VoteSubCommands for App<'_, '_> {
.long("commission")
.value_name("NUM")
.takes_value(true)
.help("The commission taken on reward redemption (0-100), default: 0"),
.help("The commission taken on reward redemption (0-255), default: 0"),
)
.arg(
Arg::with_name("authorized_voter")
@ -158,7 +161,7 @@ impl VoteSubCommands for App<'_, '_> {
}
pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let vote_account = keypair_of(matches, "vote_account").unwrap();
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
let node_pubkey = pubkey_of(matches, "node_pubkey").unwrap();
let commission = value_of(&matches, "commission").unwrap_or(0);
let authorized_voter = pubkey_of(matches, "authorized_voter");
@ -166,7 +169,7 @@ pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result<CliCommandI
Ok(CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: vote_account.into(),
vote_account_pubkey,
node_pubkey,
authorized_voter,
authorized_withdrawer,
@ -228,20 +231,19 @@ pub fn parse_vote_uptime_command(matches: &ArgMatches<'_>) -> Result<CliCommandI
pub fn process_create_vote_account(
rpc_client: &RpcClient,
config: &CliConfig,
vote_account: &Keypair,
vote_account_pubkey: &Pubkey,
node_pubkey: &Pubkey,
authorized_voter: &Option<Pubkey>,
authorized_withdrawer: &Option<Pubkey>,
commission: u8,
) -> ProcessResult {
let vote_account_pubkey = vote_account.pubkey();
check_unique_pubkeys(
(&vote_account_pubkey, "vote_account_pubkey".to_string()),
(vote_account_pubkey, "vote_account_pubkey".to_string()),
(&node_pubkey, "node_pubkey".to_string()),
)?;
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
(&vote_account_pubkey, "vote_account_pubkey".to_string()),
(vote_account_pubkey, "vote_account_pubkey".to_string()),
)?;
let required_balance =
rpc_client.get_minimum_balance_for_rent_exemption(VoteState::size_of())?;
@ -252,24 +254,20 @@ pub fn process_create_vote_account(
};
let vote_init = VoteInit {
node_pubkey: *node_pubkey,
authorized_voter: authorized_voter.unwrap_or(vote_account_pubkey),
authorized_voter: authorized_voter.unwrap_or(*vote_account_pubkey),
authorized_withdrawer: authorized_withdrawer.unwrap_or(config.keypair.pubkey()),
commission,
};
let ixs = vote_instruction::create_account(
&config.keypair.pubkey(),
&vote_account_pubkey,
vote_account_pubkey,
&vote_init,
lamports,
);
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let mut tx = Transaction::new_signed_instructions(
&[&config.keypair, vote_account],
ixs,
recent_blockhash,
);
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, vote_account]);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<SystemError>(result)
}
@ -309,7 +307,7 @@ fn get_vote_account(
) -> Result<(Account, VoteState), Box<dyn std::error::Error>> {
let vote_account = rpc_client.get_account(vote_account_pubkey)?;
if vote_account.owner != solana_vote_program::id() {
if vote_account.owner != solana_vote_api::id() {
return Err(CliError::RpcRequestError(
format!("{:?} is not a vote account", vote_account_pubkey).to_string(),
)
@ -345,7 +343,10 @@ pub fn process_show_vote_account(
vote_state.authorized_withdrawer
);
println!("credits: {}", vote_state.credits());
println!("commission: {}%", vote_state.commission);
println!(
"commission: {}%",
f64::from(vote_state.commission) / f64::from(std::u32::MAX)
);
println!(
"root slot: {}",
match vote_state.root_slot {
@ -429,19 +430,11 @@ pub fn process_uptime(
mod tests {
use super::*;
use crate::cli::{app, parse_command};
use solana_sdk::signature::write_keypair;
use tempfile::NamedTempFile;
fn make_tmp_file() -> (String, NamedTempFile) {
let tmp_file = NamedTempFile::new().unwrap();
(String::from(tmp_file.path().to_str().unwrap()), tmp_file)
}
#[test]
fn test_parse_command() {
let test_commands = app("test", "desc", "version");
let keypair = Keypair::new();
let pubkey = keypair.pubkey();
let pubkey = Pubkey::new_rand();
let pubkey_string = pubkey.to_string();
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
@ -458,16 +451,13 @@ mod tests {
}
);
let (keypair_file, mut tmp_file) = make_tmp_file();
let keypair = Keypair::new();
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
// Test CreateVoteAccount SubCommand
let node_pubkey = Pubkey::new_rand();
let node_pubkey_string = format!("{}", node_pubkey);
let test_create_vote_account = test_commands.clone().get_matches_from(vec![
"test",
"create-vote-account",
&keypair_file,
&pubkey_string,
&node_pubkey_string,
"--commission",
"10",
@ -476,7 +466,7 @@ mod tests {
parse_command(&test_create_vote_account).unwrap(),
CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: keypair.into(),
vote_account_pubkey: pubkey,
node_pubkey,
authorized_voter: None,
authorized_withdrawer: None,
@ -485,22 +475,17 @@ mod tests {
require_keypair: true
}
);
let (keypair_file, mut tmp_file) = make_tmp_file();
let keypair = Keypair::new();
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![
"test",
"create-vote-account",
&keypair_file,
&pubkey_string,
&node_pubkey_string,
]);
assert_eq!(
parse_command(&test_create_vote_account2).unwrap(),
CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: keypair.into(),
vote_account_pubkey: pubkey,
node_pubkey,
authorized_voter: None,
authorized_withdrawer: None,
@ -509,17 +494,12 @@ mod tests {
require_keypair: true
}
);
// test init with an authed voter
let authed = Pubkey::new_rand();
let (keypair_file, mut tmp_file) = make_tmp_file();
let keypair = Keypair::new();
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
let test_create_vote_account3 = test_commands.clone().get_matches_from(vec![
"test",
"create-vote-account",
&keypair_file,
&pubkey_string,
&node_pubkey_string,
"--authorized-voter",
&authed.to_string(),
@ -528,7 +508,7 @@ mod tests {
parse_command(&test_create_vote_account3).unwrap(),
CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: keypair.into(),
vote_account_pubkey: pubkey,
node_pubkey,
authorized_voter: Some(authed),
authorized_withdrawer: None,
@ -537,15 +517,11 @@ mod tests {
require_keypair: true
}
);
let (keypair_file, mut tmp_file) = make_tmp_file();
let keypair = Keypair::new();
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
// test init with an authed withdrawer
let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![
"test",
"create-vote-account",
&keypair_file,
&pubkey_string,
&node_pubkey_string,
"--authorized-withdrawer",
&authed.to_string(),
@ -554,7 +530,7 @@ mod tests {
parse_command(&test_create_vote_account4).unwrap(),
CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: keypair.into(),
vote_account_pubkey: pubkey,
node_pubkey,
authorized_voter: None,
authorized_withdrawer: Some(authed),
@ -586,4 +562,5 @@ mod tests {
}
);
}
// TODO: Add process tests
}

View File

@ -1,16 +1,14 @@
use serde_json::Value;
use serde_json::{json, Value};
use solana_cli::cli::{process_command, CliCommand, CliConfig};
use solana_client::rpc_client::RpcClient;
use solana_client::rpc_request::RpcRequest;
use solana_core::validator::new_validator_for_tests;
use solana_drone::drone::run_local_drone;
use solana_sdk::{bpf_loader, pubkey::Pubkey};
use std::{
fs::{remove_dir_all, File},
io::Read,
path::PathBuf,
str::FromStr,
sync::mpsc::channel,
};
use solana_sdk::bpf_loader;
use std::fs::{remove_dir_all, File};
use std::io::Read;
use std::path::PathBuf;
use std::sync::mpsc::channel;
#[test]
fn test_cli_deploy_program() {
@ -58,17 +56,35 @@ fn test_cli_deploy_program() {
.unwrap()
.as_str()
.unwrap();
let program_id = Pubkey::from_str(&program_id_str).unwrap();
let account = rpc_client.get_account(&program_id).unwrap();
assert_eq!(account.lamports, minimum_balance_for_rent_exemption);
assert_eq!(account.owner, bpf_loader::id());
assert_eq!(account.executable, true);
let params = json!([program_id_str]);
let account_info = rpc_client
.retry_make_rpc_request(&RpcRequest::GetAccountInfo, Some(params), 0)
.unwrap();
let account_info_obj = account_info.as_object().unwrap();
assert_eq!(
account_info_obj.get("lamports").unwrap().as_u64().unwrap(),
minimum_balance_for_rent_exemption
);
let owner_array = account_info.get("owner").unwrap();
assert_eq!(owner_array, &json!(bpf_loader::id()));
assert_eq!(
account_info_obj
.get("executable")
.unwrap()
.as_bool()
.unwrap(),
true
);
let mut file = File::open(pathbuf.to_str().unwrap().to_string()).unwrap();
let mut elf = Vec::new();
file.read_to_end(&mut elf).unwrap();
assert_eq!(account.data, elf);
assert_eq!(
account_info_obj.get("data").unwrap().as_array().unwrap(),
&elf
);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();

View File

@ -3,9 +3,9 @@ use serde_json::Value;
use solana_cli::cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig};
use solana_client::rpc_client::RpcClient;
use solana_drone::drone::run_local_drone;
use solana_sdk::{hash::Hash, pubkey::Pubkey, signature::KeypairUtil, signature::Signature};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::KeypairUtil;
use std::fs::remove_dir_all;
use std::str::FromStr;
use std::sync::mpsc::channel;
#[cfg(test)]
@ -71,9 +71,6 @@ fn test_cli_timestamp_tx() {
timestamp_pubkey: Some(config_witness.keypair.pubkey()),
witnesses: None,
cancelable: false,
sign_only: false,
signers: None,
blockhash: None,
};
let sig_response = process_command(&config_payer);
@ -141,9 +138,6 @@ fn test_cli_witness_tx() {
timestamp_pubkey: None,
witnesses: Some(vec![config_witness.keypair.pubkey()]),
cancelable: false,
sign_only: false,
signers: None,
blockhash: None,
};
let sig_response = process_command(&config_payer);
@ -204,9 +198,6 @@ fn test_cli_cancel_tx() {
timestamp_pubkey: None,
witnesses: Some(vec![config_witness.keypair.pubkey()]),
cancelable: true,
sign_only: false,
signers: None,
blockhash: None,
};
let sig_response = process_command(&config_payer).unwrap();
@ -232,94 +223,3 @@ fn test_cli_cancel_tx() {
server.close().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();
}

View File

@ -1,6 +1,6 @@
[package]
name = "solana-client"
version = "0.21.2"
version = "0.20.0"
description = "Solana Client"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -16,13 +16,13 @@ log = "0.4.8"
rand = "0.6.5"
rayon = "1.2.0"
reqwest = { version = "0.9.22", default-features = false, features = ["rustls-tls"] }
serde = "1.0.102"
serde_derive = "1.0.102"
serde = "1.0.101"
serde_derive = "1.0.101"
serde_json = "1.0.41"
solana-net-utils = { path = "../net-utils", version = "0.21.2" }
solana-sdk = { path = "../sdk", version = "0.21.2" }
solana-netutil = { path = "../netutil", version = "0.20.0" }
solana-sdk = { path = "../sdk", version = "0.20.0" }
[dev-dependencies]
jsonrpc-core = "14.0.3"
jsonrpc-http-server = "14.0.3"
solana-logger = { path = "../logger", version = "0.21.2" }
jsonrpc-http-server = "14.0.1"
solana-logger = { path = "../logger", version = "0.20.0" }

Some files were not shown because too many files have changed in this diff Show More