Compare commits

...

103 Commits

Author SHA1 Message Date
18b55bbfe1 Remove loop (#8495) 2020-02-27 02:42:42 -08:00
85637e8f63 Rename snapshot.tar.bz2 to snapshot-<slot>-<hash>.tar.bz2 (bp #8482) (#8500)
automerge
2020-02-27 00:04:14 -08:00
eb3b5d7382 Update voting simulation (#8460) (#8488)
automerge
2020-02-26 21:00:38 -08:00
1a4de4d3c4 v0.23: backport cli refactoring and remote-wallet signing integration (#8487)
* CLI: dynamic signing reboot (#8384)

* Add keypair_util_from_path helper

* Cli: impl config.keypair as a trait object

* SDK: Add Debug and PartialEq for dyn Signer

* ClapUtils: Arg parsing from pubkey+signers to Presigner

* Impl Signers for &dyn Signer collections

* CLI: Add helper for getting signers from args

* CLI: Replace SigningAuthority with Signer trait-objs

* CLI: Drop disused signers command field

* CLI: Drop redundant tests

* Add clap validator that handles all current signer types

* clap_utils: Factor Presigner resolution to helper

* SDK: `From` for boxing Signer implementors to trait objects

* SDK: Derive `Clone` for `Presigner`

* Remove panic

* Cli: dedup signers in transfer for remote-wallet ergonomics

* Update docs vis-a-vis ASK changes

* Cli: update transaction types to use new dynamic-signer methods

* CLI: Fix tests No. 1

what to do about write_keypair outstanding

* Work around `CliConfig`'s signer not necessarily being a `Keypair`

* CLI: Fix tests No. 2

* Remove unused arg

* Remove unused methods

* Move offline arg constants upstream

* Make cli signing fallible

Co-authored-by: Trent Nelson <trent.a.b.nelson@gmail.com>

* Reinstate `create-stale-account` w/ seed test (#8401)

automerge

* CLI: collect and deduplicate signers (#8398)

* Rename (keypair util is not a thing)

* Add method to generate_unique_signers

* Cli: refactor signer handling and remote-wallet init

* Fixup unit tests

* Fixup intergation tests

* Update keypair path print statement

* Remove &None

* Use deterministic key in test

* Retain storage-account as index

* Make signer index-handling less brittle

* Cache pubkey on RemoteKeypair::new

* Make signer_of consistent + return pubkey

* Remove &matches double references

* Nonce authorities need special handling

* Make solana root key accessible on Ledger (#8421)

* Use 44/501 key as ledger id

* Add error codes

* Ledger key path rework (#8453)

automerge

* Ledger hardware wallet docs (#8472)

* Update protocol documentation

* Correct app-version command const

* Rough initial Ledger docs

* Add more docs

* Cleanup

* Add remote-wallet to docs TOC

Co-authored-by: Greg Fitzgerald <greg@solana.com>

* Add flag to confirm key on device

Co-authored-by: Trent Nelson <trent.a.b.nelson@gmail.com>
Co-authored-by: Greg Fitzgerald <greg@solana.com>
2020-02-26 17:59:41 -07:00
100a11f061 Limit leader schedule search space (#8468) (#8485)
automerge
2020-02-26 15:49:31 -08:00
6d431b8210 Refactor select_fork() to avoid clones and for clarity (#8081) (#8483)
automerge
2020-02-26 15:45:15 -08:00
242afa7e6b Validate the genesis config downloaded over RPC before accepting it (bp #8474) (#8480)
automerge
2020-02-26 14:54:01 -08:00
536e01121f Tower tests (bp #7974) (#8463)
automerge
2020-02-26 13:25:08 -08:00
93bcb65d8b Promote dangerous cond. from just warning to panic (#8439) (#8448)
automerge
2020-02-25 19:12:07 -08:00
0743334486 Allow withdrawer to change the authorized stake key (#8456) (#8461)
automerge
2020-02-25 19:11:20 -08:00
93027fa067 Add --no-check-vote-account argument (#8430) (#8434)
automerge
2020-02-25 00:13:18 -08:00
8548c83e48 validator: snapshot fetching cleanup (bp #8413) (#8415)
automerge
2020-02-24 14:37:24 -08:00
80dd5db628 Add support for large transactions with Ledger Wallet (#8394) (#8395)
automerge
2020-02-22 09:39:48 -08:00
2d0ee7e3dc Add --trusted-validator support for snapshot hash validation (#8390) (#8393)
automerge
2020-02-21 19:46:43 -08:00
df2b4d31b7 Bump version to 0.23.7 2020-02-21 20:01:54 -07:00
45612bc988 \ 2020-02-21 18:10:11 -07:00
51ce98badd Add --enable-warmup-epochs flag
(cherry picked from commit ea5b00364f)
2020-02-21 17:03:35 -07:00
075e784bef Promote some datapoints to info to fix dashboard (#8381)
automerge

(cherry picked from commit aa80f69171)
2020-02-21 17:00:42 -07:00
c1b587c6e4 4x DEFAULT_MAX_LEDGER_SLOTS to give nodes 3 hours of slots to repair from (#8388)
automerge

(cherry picked from commit fb98df76b7)
2020-02-21 17:00:35 -07:00
94e2d0b5c2 Update unlocks (bp #8363) (#8382)
automerge
2020-02-21 11:40:27 -08:00
c2be9fdf0e Add handling for fallible signers (#8367) (#8374)
automerge
2020-02-21 01:45:13 -08:00
c2b17c7d3f Correct missing entry handling to avoid bad warns (bp #8339) (#8378)
automerge
2020-02-20 23:35:21 -08:00
31544f2a82 If the node was loaded from a snapshot, advertise it in gossip (#8364) (#8373)
automerge
2020-02-20 19:57:43 -08:00
3a88190e4e Enable BPF in TdS epoch 17 (#8370)
automerge
2020-02-20 18:11:58 -08:00
3f30354d1a Add non-bz2 snapshot for faster creation for dev. (#8350)
* Add non-bz2 snapshot for faster creation for dev.

* Fix tests..

* Revert and always just use snapshot.tar.bz2

(cherry picked from commit 5ef06a9d36)
2020-02-20 18:51:16 -07:00
11f15c0708 Flip Stable and Preview enum values
(cherry picked from commit 2d665da3e1)
2020-02-20 18:29:04 -07:00
a83bf85bb3 Search for the validator with the highest snapshot (#8368)
automerge
2020-02-20 17:24:23 -08:00
02877814fa Rename KeypairUtil to Signer (#8360) (#8366)
automerge
2020-02-20 16:30:43 -08:00
29cdfd6bc9 Book: Add instructions for verifying a paper wallet keypair (#8357) (#8365)
automerge
2020-02-20 14:33:18 -08:00
9dffc3abe4 Support transaction signing by heterogenous lists of keypairs (bp #8342) (#8362)
automerge
2020-02-20 14:02:14 -08:00
b4eb81546e Snapshot hash gossip changes (#8358) (#8359)
automerge
2020-02-20 12:52:04 -08:00
489fd3058f Do not compress small incomplete slot list (#8355) (#8356)
automerge
2020-02-20 10:48:47 -08:00
e5872ef1c1 Bitwise compress incomplete epoch slots (#8341) 2020-02-20 05:49:17 -08:00
cb9d18316a Update epoch slots to include all missing slots (#8276)
* Update epoch slots to include all missing slots

* new test for compress/decompress

* address review comments

* limit cache based on size, instead of comparing roots
2020-02-20 05:49:17 -08:00
c3ac85828b Process Gossip in parallel and add an upper limit (#8328) (#8345)
automerge
2020-02-19 21:54:52 -08:00
5fbddd5894 Add Preview operating mode, rename SoftLaunch operating mode to Stable (#8331) (#8340)
automerge
2020-02-19 18:13:41 -08:00
90af35737d Use correct static IP address 2020-02-19 18:15:54 -07:00
58cb21402b Remove validators from genesis (#8330) (#8333)
automerge
2020-02-19 15:42:25 -08:00
824b894977 More testnet->devnet 2020-02-19 16:15:58 -07:00
2295a5e512 Update README.md 2020-02-19 15:53:17 -07:00
83a322a211 rename testnet.solana.com to devnet.solana.com
(cherry picked from commit e3cebcf82d)

# Conflicts:
#	README.md
2020-02-19 15:53:17 -07:00
a008748d9d grooming: use cleanup, remove some dead code (bp #8324) (#8326)
automerge
2020-02-18 21:06:56 -08:00
72cb0b7c9e Add --fee-burn-percentage (#8323)
automerge
2020-02-18 19:03:41 -08:00
ede3781f91 Just define BnakSlotDelta type alias (bp #8186) (#8320)
automerge
2020-02-18 16:31:38 -08:00
e3ac6fac1e Factor out creating genesis with vote accounts into a utility function (bp #8315) (#8317)
automerge
2020-02-18 13:12:53 -08:00
e30561f8a0 CLI: Add optional airdrop recipient (#8291) (#8310)
automerge
2020-02-16 11:32:06 -08:00
8d59bef561 Cli: Remove units from various subcommands (#8301) (#8306)
automerge
2020-02-15 12:45:02 -08:00
897e1fc5d6 Bump version to 0.23.6 2020-02-14 22:38:14 -07:00
cb84099b2e Add storage rewards pools in development mode only (#8300)
automerge
2020-02-14 21:15:11 -08:00
c89b35545c install: support vX.Y.Z in addition to X.Y.Z (#8297)
automerge

(cherry picked from commit 335675c51c)
2020-02-14 21:30:18 -07:00
370716edd3 ledger-tool: Add print-accounts command
(cherry picked from commit 1bf2285fa2)
2020-02-14 21:29:24 -07:00
1dbcd5c298 Presigner KeypairUtil implementer (#8269) (#8271)
automerge
2020-02-14 12:50:00 -08:00
ca770d5e74 Datapoints overwhelm the metrics queue and blow up ram usage. (#8272) (#8283)
automerge
2020-02-14 12:42:45 -08:00
51a8d0356f Make generate_remote_keypair more generic for potential other remote-wallets (#8274) (#8275)
automerge
2020-02-14 09:37:39 -08:00
79e340c499 Enable remote-wallet signing in solana-keygen (#8267) (#8268)
automerge
2020-02-13 13:57:11 -08:00
00f92f520f get_confirmed_block: expect() less (#8266)
automerge
2020-02-13 10:43:55 -08:00
b90049aafb Retain signature subscriptions that haven't been notified (#8261) (#8265)
automerge
2020-02-13 09:58:16 -08:00
c1d66b46fa Retry to curl to codecov.io unfortunately (#8263) (#8264)
automerge
2020-02-13 09:15:55 -08:00
c377d1cbbd CLI: Offline-ify remaining stake ops (#8257) (#8259)
automerge
2020-02-12 23:00:16 -08:00
bbdb4129cf Add CliCommand::StakeSetLockup (#8248) (#8254)
automerge
2020-02-12 17:23:53 -08:00
0ecf823986 Cargo.lock 2020-02-12 17:53:27 -07:00
43ac961637 Simplify remote wallet (#8249) (#8251)
automerge
2020-02-12 16:19:14 -08:00
57e6213528 Plumb --enable-rpc-get-confirmed-block flag 2020-02-12 17:09:03 -07:00
acafb89ff2 Wrap ed25519_dalek::Keypair (#8247) (#8250)
automerge
2020-02-12 16:01:30 -08:00
ec319a6043 Bump version to 0.23.5 2020-02-12 13:58:37 -07:00
f389d434f8 Bump version to 0.23.4 2020-02-12 13:56:59 -07:00
1b600a7f37 Update cluster_info.rs 2020-02-12 12:50:45 -07:00
798b457b27 Avoid assigning the serve repair port to the storage port
(cherry picked from commit d0a4686990)

# Conflicts:
#	core/src/cluster_info.rs
2020-02-12 12:50:45 -07:00
132d012842 Quash 'repair listener error: Err(RecvTimeoutError(Timeout))' log spam (#8238)
automerge
2020-02-12 10:29:26 -08:00
e16f9ad961 Fix accounts_db store counts in purging accounts logic (#8218) (#8236)
automerge
2020-02-12 09:45:06 -08:00
66f006108c CLI: Don't hide errors when fees are disabled (#8204)
automerge

(cherry picked from commit ed87229cec)
2020-02-11 23:33:49 -07:00
47f887bda0 The getConfirmedBlock RPC API is now disabled by default (#8230)
automerge
2020-02-11 22:18:22 -08:00
bb64c73aa2 set_read_timeout() can fail, don't expect() it not to
(cherry picked from commit 36c0cb052b)
2020-02-11 21:08:07 -07:00
1f30d1e77a solana-install init edge when "edge" is not currently installed now works
(cherry picked from commit ed58bcda4c)
2020-02-11 21:08:07 -07:00
04dab9b274 Fix RPC pub sub unsubscribe (#8208) (#8228)
automerge
2020-02-11 18:40:21 -08:00
fb4e102670 Report validator rewards in getConfirmedBlock JSON RPC (#8226)
automerge
2020-02-11 18:20:16 -08:00
67e0ba0356 Add method to sign raw data, enabling easier device app testing (#8221) (#8225)
automerge
2020-02-11 17:59:08 -08:00
22bb4e6462 Factor repair from gossip (#8044) (#8220)
automerge
2020-02-11 14:18:45 -08:00
79035bdbed Upgrade to rust 1.41.0 (bp #8202) (#8219)
automerge
2020-02-11 13:56:58 -08:00
70089a5258 Fixup sign_transaction; pass derivation_path by reference (#8194) (#8217)
automerge
2020-02-11 12:38:31 -08:00
34238d5f1e Reliably track proc macro & build.rs code coverage (#8210) (#8213)
automerge
2020-02-11 09:13:21 -08:00
cab6917cbd Fix nightly clippy warnings (#8199) (#8212)
automerge
2020-02-11 08:43:12 -08:00
2951ee5b1d Channel installs no longer re-download the same release. (#8211)
automerge
2020-02-11 08:24:17 -08:00
fb16a15900 CLI: Add fee-payer parame to stake-split subcommand (#8201) (#8205)
automerge
2020-02-11 01:32:07 -08:00
76b52f4c5d CLI: transfer fix checks pubkeys (#8198) (#8203)
automerge
2020-02-11 00:26:56 -08:00
21a2e643c2 CLI: Harden offline signing and tests (#8052) (#8197)
automerge
2020-02-10 19:23:22 -08:00
733d9cb026 Remove repairman as its spamming cluster with unwanted repairs (#8193) (#8195)
automerge
2020-02-10 17:56:45 -08:00
2f54f57b7a Fix larger than necessary allocations in streamer (#8187) (#8192)
automerge
2020-02-10 13:06:28 -08:00
7bd95019ef Minor logging improvements (bp #8140) (#8190)
automerge
2020-02-10 11:22:26 -08:00
33557c3271 Check for AVX512 at runtime to avoid invalid opcode trap (#8166)
automerge

(cherry picked from commit ef5fb6fa46)
2020-02-07 17:07:10 -07:00
c65b9cd88d Filter old CrdsValues received via Pull Responses in Gossip (#8150) (#8171)
automerge
2020-02-07 14:11:48 -08:00
038db8167f CLI: Implement transfer command (#8108) (#8170)
automerge
2020-02-07 13:18:35 -08:00
030498ced5 Ledger hardware wallet integration (#8068) (#8169)
automerge
2020-02-07 12:14:41 -08:00
28eb8b662a Remove unwanted println 2020-02-07 12:59:44 -07:00
de752eaf80 Lock snapshot version to 0.23.2 (#8167)
automerge
2020-02-07 11:35:54 -08:00
9c5ef19d80 Surface shred version more in tools (#8163) (#8165)
automerge
2020-02-07 10:10:00 -08:00
235bd0a46b CLI: Support offline fee payers (#8009) (#8164)
automerge
2020-02-07 09:41:35 -08:00
465d71a3a3 De-replicode Tower constructors (#8153) (#8154)
automerge
2020-02-06 19:38:47 -08:00
14e6029fae Add libudev-dev to docker image to build remote-wallet (#8149) (#8152)
automerge
2020-02-06 16:51:44 -08:00
75434158ee Ignore flaky test_exchange_local_cluster (#8146) (#8147)
automerge
2020-02-06 12:24:42 -08:00
1cae9fd893 Better surface bank hash verification failures (#8134)
automerge
2020-02-05 12:04:34 -08:00
bea34a812c CLI cosmetic: make config get and verbose prints consistent (#8119) (#8133)
automerge
2020-02-05 11:31:29 -08:00
41a28d7322 Bump version to 0.23.3 2020-02-03 21:10:30 -07:00
284 changed files with 12448 additions and 6611 deletions

821
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,7 @@ members = [
"archiver",
"archiver-lib",
"archiver-utils",
"remote-wallet",
"runtime",
"sdk",
"sdk-c",

View File

@ -126,10 +126,7 @@ Remote Testnets
---
We maintain several testnets:
* `testnet` - public stable testnet accessible via testnet.solana.com. Runs 24/7
* `testnet-beta` - public beta channel testnet accessible via beta.testnet.solana.com. Runs 24/7
* `testnet-edge` - public edge channel testnet accessible via edge.testnet.solana.com. Runs 24/7
* `testnet` - public stable testnet accessible via devnet.solana.com. Runs 24/7
## Deploy process

View File

@ -140,9 +140,9 @@ TODO: Documentation update procedure is WIP as we move to gitbook
Document the new recommended version by updating `book/src/running-archiver.md` and `book/src/validator-testnet.md` on the release (beta) branch to point at the `solana-install` for the upcoming release version.
### Update software on testnet.solana.com
### Update software on devnet.solana.com
The testnet running on testnet.solana.com is set to use a fixed release tag
The testnet running on devnet.solana.com is set to use a fixed release tag
which is set in the Buildkite testnet-management pipeline.
This tag needs to be updated and the testnet restarted after a new release
tag is created.
@ -182,4 +182,4 @@ TESTNET_OP=create-and-start
### Alert the community
Notify Discord users on #validator-support that a new release for
testnet.solana.com is available
devnet.solana.com is available

View File

@ -1,6 +1,6 @@
[package]
name = "solana-archiver-lib"
version = "0.23.2"
version = "0.23.7"
description = "Solana Archiver Library"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -15,22 +15,22 @@ ed25519-dalek = "=1.0.0-pre.1"
log = "0.4.8"
rand = "0.6.5"
rand_chacha = "0.1.1"
solana-client = { path = "../client", version = "0.23.2" }
solana-storage-program = { path = "../programs/storage", version = "0.23.2" }
solana-client = { path = "../client", version = "0.23.7" }
solana-storage-program = { path = "../programs/storage", version = "0.23.7" }
thiserror = "1.0"
serde = "1.0.104"
serde_json = "1.0.44"
serde_derive = "1.0.103"
solana-net-utils = { path = "../net-utils", version = "0.23.2" }
solana-chacha = { path = "../chacha", version = "0.23.2" }
solana-chacha-sys = { path = "../chacha-sys", version = "0.23.2" }
solana-ledger = { path = "../ledger", version = "0.23.2" }
solana-logger = { path = "../logger", version = "0.23.2" }
solana-perf = { path = "../perf", version = "0.23.2" }
solana-sdk = { path = "../sdk", version = "0.23.2" }
solana-core = { path = "../core", version = "0.23.2" }
solana-archiver-utils = { path = "../archiver-utils", version = "0.23.2" }
solana-metrics = { path = "../metrics", version = "0.23.2" }
solana-net-utils = { path = "../net-utils", version = "0.23.7" }
solana-chacha = { path = "../chacha", version = "0.23.7" }
solana-chacha-sys = { path = "../chacha-sys", version = "0.23.7" }
solana-ledger = { path = "../ledger", version = "0.23.7" }
solana-logger = { path = "../logger", version = "0.23.7" }
solana-perf = { path = "../perf", version = "0.23.7" }
solana-sdk = { path = "../sdk", version = "0.23.7" }
solana-core = { path = "../core", version = "0.23.7" }
solana-archiver-utils = { path = "../archiver-utils", version = "0.23.7" }
solana-metrics = { path = "../metrics", version = "0.23.7" }
[dev-dependencies]
hex = "0.4.0"

View File

@ -1,6 +1,5 @@
use crate::result::ArchiverError;
use crossbeam_channel::unbounded;
use ed25519_dalek;
use rand::{thread_rng, Rng, SeedableRng};
use rand_chacha::ChaChaRng;
use solana_archiver_utils::sample_file;
@ -16,6 +15,7 @@ use solana_core::{
packet::{limited_deserialize, PACKET_DATA_SIZE},
repair_service,
repair_service::{RepairService, RepairSlotRange, RepairStrategy},
serve_repair::ServeRepair,
shred_fetch_stage::ShredFetchStage,
sigverify_stage::{DisabledSigVerifier, SigVerifyStage},
storage_stage::NUM_STORAGE_SAMPLES,
@ -36,7 +36,7 @@ use solana_sdk::{
commitment_config::CommitmentConfig,
hash::Hash,
message::Message,
signature::{Keypair, KeypairUtil, Signature},
signature::{Keypair, Signature, Signer},
timing::timestamp,
transaction::Transaction,
transport::TransportError,
@ -87,11 +87,11 @@ struct ArchiverMeta {
}
fn get_slot_from_signature(
signature: &ed25519_dalek::Signature,
signature: &Signature,
storage_turn: u64,
slots_per_segment: u64,
) -> u64 {
let signature_vec = signature.to_bytes();
let signature_vec = signature.as_ref();
let mut segment_index = u64::from(signature_vec[0])
| (u64::from(signature_vec[1]) << 8)
| (u64::from(signature_vec[1]) << 16)
@ -195,13 +195,7 @@ impl Archiver {
Blockstore::open(ledger_path).expect("Expected to be able to open database ledger"),
);
let gossip_service = GossipService::new(
&cluster_info,
Some(blockstore.clone()),
None,
node.sockets.gossip,
&exit,
);
let gossip_service = GossipService::new(&cluster_info, None, node.sockets.gossip, &exit);
info!("Connecting to the cluster via {:?}", cluster_entrypoint);
let (nodes, _) =
@ -390,7 +384,7 @@ impl Archiver {
);
let message =
Message::new_with_payer(vec![ix], Some(&archiver_keypair.pubkey()));
if let Err(e) = client.send_message(&[&archiver_keypair], message) {
if let Err(e) = client.send_message(&[archiver_keypair.as_ref()], message) {
error!("unable to redeem reward, tx failed: {:?}", e);
} else {
info!(
@ -443,13 +437,13 @@ impl Archiver {
return Err(e);
}
};
let signature = storage_keypair.sign(segment_blockhash.as_ref());
let signature = storage_keypair.sign_message(segment_blockhash.as_ref());
let slot = get_slot_from_signature(&signature, segment_slot, slots_per_segment);
info!("replicating slot: {}", slot);
slot_sender.send(slot)?;
meta.slot = slot;
meta.slots_per_segment = slots_per_segment;
meta.signature = Signature::new(&signature.to_bytes());
meta.signature = signature;
meta.blockhash = segment_blockhash;
let mut repair_slot_range = RepairSlotRange::default();
@ -677,7 +671,7 @@ impl Archiver {
blockhash,
);
if let Err(err) = client.send_and_confirm_transaction(
&[&archiver_keypair, &storage_keypair],
&[archiver_keypair.as_ref(), storage_keypair.as_ref()],
&mut transaction,
10,
0,
@ -814,7 +808,7 @@ impl Archiver {
/// It is recommended to use a temporary blockstore for this since the download will not verify
/// shreds received and might impact the chaining of shreds across slots
pub fn download_from_archiver(
cluster_info: &Arc<RwLock<ClusterInfo>>,
serve_repair: &ServeRepair,
archiver_info: &ContactInfo,
blockstore: &Arc<Blockstore>,
slots_per_segment: u64,
@ -834,10 +828,10 @@ impl Archiver {
Recycler::default(),
"archiver_reeciver",
);
let id = cluster_info.read().unwrap().id();
let id = serve_repair.keypair().pubkey();
info!(
"Sending repair requests from: {} to: {}",
cluster_info.read().unwrap().my_data().id,
serve_repair.my_info().id,
archiver_info.gossip
);
let repair_slot_range = RepairSlotRange {
@ -857,9 +851,7 @@ impl Archiver {
let reqs: Vec<_> = repairs
.into_iter()
.filter_map(|repair_request| {
cluster_info
.read()
.unwrap()
serve_repair
.map_repair_request(&repair_request)
.map(|result| ((archiver_info.gossip, result), repair_request))
.ok()

View File

@ -1,6 +1,6 @@
[package]
name = "solana-archiver-utils"
version = "0.23.2"
version = "0.23.7"
description = "Solana Archiver Utils"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -12,12 +12,12 @@ edition = "2018"
log = "0.4.8"
rand = "0.6.5"
rand_chacha = "0.1.1"
solana-chacha = { path = "../chacha", version = "0.23.2" }
solana-chacha-sys = { path = "../chacha-sys", version = "0.23.2" }
solana-ledger = { path = "../ledger", version = "0.23.2" }
solana-logger = { path = "../logger", version = "0.23.2" }
solana-perf = { path = "../perf", version = "0.23.2" }
solana-sdk = { path = "../sdk", version = "0.23.2" }
solana-chacha = { path = "../chacha", version = "0.23.7" }
solana-chacha-sys = { path = "../chacha-sys", version = "0.23.7" }
solana-ledger = { path = "../ledger", version = "0.23.7" }
solana-logger = { path = "../logger", version = "0.23.7" }
solana-perf = { path = "../perf", version = "0.23.7" }
solana-sdk = { path = "../sdk", version = "0.23.7" }
[dev-dependencies]
hex = "0.4.0"

View File

@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-archiver"
version = "0.23.2"
version = "0.23.7"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -10,11 +10,11 @@ homepage = "https://solana.com/"
[dependencies]
clap = "2.33.0"
console = "0.9.1"
solana-clap-utils = { path = "../clap-utils", version = "0.23.2" }
solana-core = { path = "../core", version = "0.23.2" }
solana-logger = { path = "../logger", version = "0.23.2" }
solana-metrics = { path = "../metrics", version = "0.23.2" }
solana-archiver-lib = { path = "../archiver-lib", version = "0.23.2" }
solana-net-utils = { path = "../net-utils", version = "0.23.2" }
solana-sdk = { path = "../sdk", version = "0.23.2" }
solana-clap-utils = { path = "../clap-utils", version = "0.23.7" }
solana-core = { path = "../core", version = "0.23.7" }
solana-logger = { path = "../logger", version = "0.23.7" }
solana-metrics = { path = "../metrics", version = "0.23.7" }
solana-archiver-lib = { path = "../archiver-lib", version = "0.23.7" }
solana-net-utils = { path = "../net-utils", version = "0.23.7" }
solana-sdk = { path = "../sdk", version = "0.23.7" }

View File

@ -12,7 +12,7 @@ use solana_core::{
cluster_info::{Node, VALIDATOR_PORT_RANGE},
contact_info::ContactInfo,
};
use solana_sdk::{commitment_config::CommitmentConfig, signature::KeypairUtil};
use solana_sdk::{commitment_config::CommitmentConfig, signature::Signer};
use std::{net::SocketAddr, path::PathBuf, process::exit, sync::Arc};
fn main() {

View File

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

View File

@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-exchange"
version = "0.23.2"
version = "0.23.7"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -23,19 +23,19 @@ serde = "1.0.104"
serde_derive = "1.0.103"
serde_json = "1.0.44"
serde_yaml = "0.8.11"
solana-clap-utils = { path = "../clap-utils", version = "0.23.2" }
solana-core = { path = "../core", version = "0.23.2" }
solana-genesis = { path = "../genesis", version = "0.23.2" }
solana-client = { path = "../client", version = "0.23.2" }
solana-faucet = { path = "../faucet", version = "0.23.2" }
solana-exchange-program = { path = "../programs/exchange", version = "0.23.2" }
solana-logger = { path = "../logger", version = "0.23.2" }
solana-metrics = { path = "../metrics", version = "0.23.2" }
solana-net-utils = { path = "../net-utils", version = "0.23.2" }
solana-runtime = { path = "../runtime", version = "0.23.2" }
solana-sdk = { path = "../sdk", version = "0.23.2" }
solana-clap-utils = { path = "../clap-utils", version = "0.23.7" }
solana-core = { path = "../core", version = "0.23.7" }
solana-genesis = { path = "../genesis", version = "0.23.7" }
solana-client = { path = "../client", version = "0.23.7" }
solana-faucet = { path = "../faucet", version = "0.23.7" }
solana-exchange-program = { path = "../programs/exchange", version = "0.23.7" }
solana-logger = { path = "../logger", version = "0.23.7" }
solana-metrics = { path = "../metrics", version = "0.23.7" }
solana-net-utils = { path = "../net-utils", version = "0.23.7" }
solana-runtime = { path = "../runtime", version = "0.23.7" }
solana-sdk = { path = "../sdk", version = "0.23.7" }
untrusted = "0.7.0"
ws = "0.9.1"
[dev-dependencies]
solana-local-cluster = { path = "../local-cluster", version = "0.23.2" }
solana-local-cluster = { path = "../local-cluster", version = "0.23.7" }

View File

@ -15,7 +15,7 @@ use solana_sdk::{
client::{Client, SyncClient},
commitment_config::CommitmentConfig,
pubkey::Pubkey,
signature::{Keypair, KeypairUtil},
signature::{Keypair, Signer},
timing::{duration_as_ms, duration_as_s},
transaction::Transaction,
{system_instruction, system_program},
@ -701,7 +701,7 @@ fn verify_funding_transfer<T: SyncClient + ?Sized>(
false
}
pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
let total = lamports * (dests.len() as u64 + 1);
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
let mut notfunded: Vec<&Arc<Keypair>> = dests.iter().collect();
@ -824,7 +824,11 @@ pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc<Keypair>],
}
}
pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], accounts: &[Keypair]) {
pub fn create_token_accounts<T: Client>(
client: &T,
signers: &[Arc<Keypair>],
accounts: &[Keypair],
) {
let mut notfunded: Vec<(&Arc<Keypair>, &Keypair)> = signers.iter().zip(accounts).collect();
while !notfunded.is_empty() {
@ -968,7 +972,12 @@ fn generate_keypairs(num: u64) -> Vec<Keypair> {
rnd.gen_n_keypairs(num)
}
pub fn airdrop_lamports(client: &dyn Client, faucet_addr: &SocketAddr, id: &Keypair, amount: u64) {
pub fn airdrop_lamports<T: Client>(
client: &T,
faucet_addr: &SocketAddr,
id: &Keypair,
amount: u64,
) {
let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent());
let balance = balance.unwrap_or(0);
if balance >= amount {

View File

@ -1,7 +1,7 @@
use clap::{crate_description, crate_name, value_t, App, Arg, ArgMatches};
use solana_core::gen_keys::GenKeys;
use solana_faucet::faucet::FAUCET_PORT;
use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil};
use solana_sdk::signature::{read_keypair_file, Keypair};
use std::net::SocketAddr;
use std::process::exit;
use std::time::Duration;

View File

@ -5,7 +5,7 @@ pub mod order_book;
use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_exchange, Config};
use log::*;
use solana_core::gossip_service::{discover_cluster, get_multi_client};
use solana_sdk::signature::KeypairUtil;
use solana_sdk::signature::Signer;
fn main() {
solana_logger::setup();

View File

@ -10,12 +10,13 @@ use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::genesis_config::create_genesis_config;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
use std::process::exit;
use std::sync::mpsc::channel;
use std::time::Duration;
#[test]
#[ignore]
fn test_exchange_local_cluster() {
solana_logger::setup();

View File

@ -2,14 +2,14 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-streamer"
version = "0.23.2"
version = "0.23.7"
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.23.2" }
solana-core = { path = "../core", version = "0.23.2" }
solana-logger = { path = "../logger", version = "0.23.2" }
solana-net-utils = { path = "../net-utils", version = "0.23.2" }
solana-clap-utils = { path = "../clap-utils", version = "0.23.7" }
solana-core = { path = "../core", version = "0.23.7" }
solana-logger = { path = "../logger", version = "0.23.7" }
solana-net-utils = { path = "../net-utils", version = "0.23.7" }

View File

@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-tps"
version = "0.23.2"
version = "0.23.7"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -16,24 +16,24 @@ serde = "1.0.104"
serde_derive = "1.0.103"
serde_json = "1.0.44"
serde_yaml = "0.8.11"
solana-clap-utils = { path = "../clap-utils", version = "0.23.2" }
solana-core = { path = "../core", version = "0.23.2" }
solana-genesis = { path = "../genesis", version = "0.23.2" }
solana-client = { path = "../client", version = "0.23.2" }
solana-faucet = { path = "../faucet", version = "0.23.2" }
solana-librapay = { path = "../programs/librapay", version = "0.23.2", optional = true }
solana-logger = { path = "../logger", version = "0.23.2" }
solana-metrics = { path = "../metrics", version = "0.23.2" }
solana-measure = { path = "../measure", version = "0.23.2" }
solana-net-utils = { path = "../net-utils", version = "0.23.2" }
solana-runtime = { path = "../runtime", version = "0.23.2" }
solana-sdk = { path = "../sdk", version = "0.23.2" }
solana-move-loader-program = { path = "../programs/move_loader", version = "0.23.2", optional = true }
solana-clap-utils = { path = "../clap-utils", version = "0.23.7" }
solana-core = { path = "../core", version = "0.23.7" }
solana-genesis = { path = "../genesis", version = "0.23.7" }
solana-client = { path = "../client", version = "0.23.7" }
solana-faucet = { path = "../faucet", version = "0.23.7" }
solana-librapay = { path = "../programs/librapay", version = "0.23.7", optional = true }
solana-logger = { path = "../logger", version = "0.23.7" }
solana-metrics = { path = "../metrics", version = "0.23.7" }
solana-measure = { path = "../measure", version = "0.23.7" }
solana-net-utils = { path = "../net-utils", version = "0.23.7" }
solana-runtime = { path = "../runtime", version = "0.23.7" }
solana-sdk = { path = "../sdk", version = "0.23.7" }
solana-move-loader-program = { path = "../programs/move_loader", version = "0.23.7", optional = true }
[dev-dependencies]
serial_test = "0.3.2"
serial_test_derive = "0.3.1"
solana-local-cluster = { path = "../local-cluster", version = "0.23.2" }
solana-local-cluster = { path = "../local-cluster", version = "0.23.7" }
[features]
move = ["solana-librapay", "solana-move-loader-program"]

View File

@ -7,7 +7,7 @@ use solana_faucet::faucet::request_airdrop_transaction;
#[cfg(feature = "move")]
use solana_librapay::{create_genesis, upload_mint_script, upload_payment_script};
use solana_measure::measure::Measure;
use solana_metrics::{self, datapoint_debug};
use solana_metrics::{self, datapoint_info};
use solana_sdk::{
client::Client,
clock::{DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE},
@ -15,7 +15,7 @@ use solana_sdk::{
fee_calculator::FeeCalculator,
hash::Hash,
pubkey::Pubkey,
signature::{Keypair, KeypairUtil},
signature::{Keypair, Signer},
system_instruction, system_transaction,
timing::{duration_as_ms, duration_as_s, duration_as_us, timestamp},
transaction::Transaction,
@ -244,7 +244,7 @@ where
fn metrics_submit_lamport_balance(lamport_balance: u64) {
info!("Token balance: {}", lamport_balance);
datapoint_debug!(
datapoint_info!(
"bench-tps-lamport_balance",
("balance", lamport_balance, i64)
);
@ -375,7 +375,7 @@ fn generate_txs(
duration_as_ms(&duration),
blockhash,
);
datapoint_debug!(
datapoint_info!(
"bench-tps-generate_txs",
("duration", duration_as_us(&duration), i64)
);
@ -481,7 +481,7 @@ fn do_tx_transfers<T: Client>(
duration_as_ms(&transfer_start.elapsed()),
tx_len as f32 / duration_as_s(&transfer_start.elapsed()),
);
datapoint_debug!(
datapoint_info!(
"bench-tps-do_tx_transfers",
("duration", duration_as_us(&transfer_start.elapsed()), i64),
("count", tx_len, i64)

View File

@ -1,7 +1,7 @@
use clap::{crate_description, crate_name, App, Arg, ArgMatches};
use solana_faucet::faucet::FAUCET_PORT;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil};
use solana_sdk::signature::{read_keypair_file, Keypair};
use std::{net::SocketAddr, process::exit, time::Duration};
const NUM_LAMPORTS_PER_ACCOUNT_DEFAULT: u64 = solana_sdk::native_token::LAMPORTS_PER_SOL;

View File

@ -4,7 +4,7 @@ use solana_bench_tps::cli;
use solana_core::gossip_service::{discover_cluster, get_client, get_multi_client};
use solana_genesis::Base64Account;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::system_program;
use std::{collections::HashMap, fs::File, io::prelude::*, path::Path, process::exit, sync::Arc};

View File

@ -8,7 +8,7 @@ use solana_faucet::faucet::run_local_faucet;
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
#[cfg(feature = "move")]
use solana_sdk::move_loader::solana_move_loader_program;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
use std::sync::{mpsc::channel, Arc};
use std::time::Duration;

View File

@ -3,6 +3,8 @@
* [Introduction](introduction.md)
* [Using Solana from the Command-line](cli/README.md)
* [Command-line Usage](cli/usage.md)
* [Remote Wallet](remote-wallet/README.md)
* [Ledger Hardware Wallet](remote-wallet/ledger.md)
* [Paper Wallet](paper-wallet/README.md)
* [Installation](paper-wallet/installation.md)
* [Paper Wallet Usage](paper-wallet/usage.md)

View File

@ -303,6 +303,9 @@ The result field will be an object with the following fields:
* `fee: <u64>` - fee this transaction was charged, as u64 integer
* `preBalances: <array>` - array of u64 account balances from before the transaction was processed
* `postBalances: <array>` - array of u64 account balances after the transaction was processed
* `rewards: <array>` - an array of JSON objects containing:
* `pubkey: <string>` - The public key, as base-58 encoded string, of the account that received the reward
* `lamports: <i64>`- number of reward lamports credited or debited by the account, as a i64
#### Example:
@ -827,7 +830,7 @@ The result field will be a JSON object with the following fields:
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"solana-core": "0.23.2"},"id":1}
{"jsonrpc":"2.0","result":{"solana-core": "0.23.7"},"id":1}
```
### getVoteAccounts

View File

@ -154,7 +154,7 @@ The stream will output a series of JSON objects:
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 --faucet testnet.solana.com:9900 --duration 60 --tx_count 50
$ NDEBUG=1 ./multinode-demo/bench-tps.sh --entrypoint devnet.solana.com:8001 --faucet devnet.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

@ -22,12 +22,6 @@ $ solana airdrop 2
// Return
"2.00000000 SOL"
// Command
$ solana airdrop 123 --lamports
// Return
"123 lamports"
```
### Get Balance

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.23.2 [channel=unknown commit=unknown]
solana-cli 0.23.7 [channel=unknown commit=unknown]
Blockchain, Rebuilt for Scale
USAGE:
@ -191,12 +191,17 @@ FLAGS:
-v, --verbose Show extra information header
OPTIONS:
--ask-seed-phrase <KEYPAIR NAME> Recover a keypair using a seed phrase and optional passphrase [possible
values: keypair]
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
--ask-seed-phrase <KEYPAIR NAME>
Recover a keypair using a seed phrase and optional passphrase [possible values: keypair]
-C, --config <PATH>
Configuration file to use [default: ~/.config/solana/cli/config.yml]
--derivation-path <ACCOUNT or ACCOUNT/CHANGE>
Derivation path to use: m/44'/501'/ACCOUNT'/CHANGE'; default key is device base pubkey: m/44'/501'/0'
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
SUBCOMMANDS:
account Show the contents of an account
@ -226,6 +231,8 @@ SUBCOMMANDS:
genesis-hash Get the genesis hash
gossip Show the current gossip network nodes
help Prints this message or the help of the given subcommand(s)
leader-schedule Display leader schedule
live-slots Show information about the current slot progression
new-nonce Generate a new nonce, rendering the existing nonce useless
nonce Get the current nonce value
nonce-account Show the contents of a nonce account
@ -233,14 +240,17 @@ SUBCOMMANDS:
ping Submit transactions sequentially
send-signature Send a signature to authorize a transfer
send-timestamp Send a timestamp to unlock a transfer
show-stake-account Show the contents of a stake account
slot Get current slot
split-stake Split a stake account
stake-account Show the contents of a stake account
stake-authorize-staker Authorize a new stake signing keypair for the given stake account
stake-authorize-withdrawer Authorize a new withdraw signing keypair for the given stake account
stake-history Show the stake history
stake-set-lockup Set Lockup for the stake account
stakes Show stake account information
storage-account Show the contents of a storage account
transaction-count Get current transaction count
transfer Transfer funds between system accounts
validator-info Publish/get Validator info on Solana
validators Show summary information about the current validators
vote-account Show the contents of a vote account
@ -273,7 +283,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
-o, --output <FILE> Write the account data to this file
ARGS:
@ -301,7 +311,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
```
#### solana-airdrop
@ -310,7 +320,7 @@ solana-airdrop
Request lamports
USAGE:
solana airdrop [FLAGS] [OPTIONS] <AMOUNT> [UNIT]
solana airdrop [FLAGS] [OPTIONS] <AMOUNT> [PUBKEY]
FLAGS:
-h, --help Prints help information
@ -327,11 +337,11 @@ OPTIONS:
--faucet-host <HOST> Faucet host to use [default: the --url host]
--faucet-port <PORT> Faucet port to use [default: 9900]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<AMOUNT> The airdrop amount to request (default unit SOL)
<UNIT> Specify unit to use for request and balance display [possible values: SOL, lamports]
<AMOUNT> The airdrop amount to request, in SOL
<PUBKEY> The pubkey of airdrop recipient
```
#### solana-authorize-nonce-account
@ -357,7 +367,7 @@ OPTIONS:
Configuration file to use [default: ~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--nonce-authority <KEYPAIR or PUBKEY>
Provide the nonce authority keypair to use when signing a nonced transaction
@ -392,7 +402,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<PUBKEY> The public key of the balance to check
@ -420,7 +430,7 @@ OPTIONS:
~/.config/solana/cli/config.yml]
--epoch <epoch> Epoch to show block production for [default: current epoch]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--slot-limit <slot_limit> Limit results to this many slots from the end of the epoch [default: full
epoch]
```
@ -446,7 +456,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<SLOT> Slot number of the block to query
@ -473,7 +483,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<PROCESS ID> The process id of the transfer to cancel
@ -500,7 +510,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<PUBKEY> Identity pubkey of the validator
@ -527,7 +537,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<NODE PUBKEY> The node account to credit the rewards to
@ -555,7 +565,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
```
#### solana-config
@ -579,7 +589,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
SUBCOMMANDS:
get Get current config settings
@ -608,7 +618,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<SIGNATURE> The transaction signature to confirm
@ -636,7 +646,7 @@ OPTIONS:
~/.config/solana/cli/config.yml]
--from <PUBKEY> From (base) key, defaults to client keypair.
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<SEED_STRING> The seed. Must not take more than 32 bytes to encode as utf-8
@ -665,7 +675,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<STORAGE ACCOUNT OWNER PUBKEY>
@ -678,7 +688,7 @@ solana-create-nonce-account
Create a nonce account
USAGE:
solana create-nonce-account [FLAGS] [OPTIONS] <NONCE ACCOUNT> <AMOUNT> [UNIT]
solana create-nonce-account [FLAGS] [OPTIONS] <NONCE ACCOUNT> <AMOUNT>
FLAGS:
-h, --help Prints help information
@ -693,13 +703,12 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--nonce-authority <BASE58_PUBKEY> Assign noncing authority to another entity
ARGS:
<NONCE ACCOUNT> Keypair of the nonce account to fund
<AMOUNT> The amount to load the nonce account with (default unit SOL)
<UNIT> Specify unit to use for request [possible values: SOL, lamports]
<AMOUNT> The amount to load the nonce account with, in SOL
```
#### solana-create-stake-account
@ -708,34 +717,56 @@ solana-create-stake-account
Create a stake account
USAGE:
solana create-stake-account [FLAGS] [OPTIONS] <STAKE ACCOUNT> <AMOUNT> [UNIT]
solana create-stake-account [FLAGS] [OPTIONS] <STAKE ACCOUNT> <AMOUNT>
FLAGS:
-h, --help Prints help information
--sign-only Sign the transaction offline
--skip-seed-phrase-validation Skip validation of seed phrases. Use this if your phrase does not use the BIP39
official English word list
-V, --version Prints version information
-v, --verbose Show extra information header
OPTIONS:
--ask-seed-phrase <KEYPAIR NAME> Recover a keypair using a seed phrase and optional passphrase [possible
values: keypair]
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
--custodian <PUBKEY> Identity of the custodian (can withdraw before lockup expires)
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
--lockup-date <RFC3339 DATE TIME> The date and time at which this account will be available for withdrawal
--lockup-epoch <EPOCH> The epoch height at which this account will be available for withdrawal
--seed <SEED STRING> Seed for address generation; if specified, the resulting account will be at
a derived address of the STAKE ACCOUNT pubkey
--stake-authority <PUBKEY> Public key of authorized staker (defaults to cli config pubkey)
--withdraw-authority <PUBKEY> Public key of authorized withdrawer (defaults to cli config pubkey)
--ask-seed-phrase <KEYPAIR NAME>
Recover a keypair using a seed phrase and optional passphrase [possible values: keypair]
--blockhash <BLOCKHASH> Use the supplied blockhash
-C, --config <PATH>
Configuration file to use [default: ~/.config/solana/cli/config.yml]
--custodian <KEYPAIR or PUBKEY> Identity of the custodian (can withdraw before lockup expires)
--fee-payer <KEYPAIR or PUBKEY>
Specify the fee-payer account. This may be a keypair file, the ASK keyword
or the pubkey of an offline signer, provided an appropriate --signer argument
is also passed. Defaults to the client keypair.
--from <KEYPAIR or PUBKEY> Source account of funds (if different from client local account)
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--lockup-date <RFC3339 DATE TIME>
The date and time at which this account will be available for withdrawal
--lockup-epoch <EPOCH>
The epoch height at which this account will be available for withdrawal
--nonce <PUBKEY>
Provide the nonce account to use when creating a nonced
transaction. Nonced transactions are useful when a transaction
requires a lengthy signing process. Learn more about nonced
transactions at https://docs.solana.com/offline-signing/durable-nonce
--nonce-authority <KEYPAIR or PUBKEY>
Provide the nonce authority keypair to use when signing a nonced transaction
--seed <SEED STRING>
Seed for address generation; if specified, the resulting account will be at a derived address of the STAKE
ACCOUNT pubkey
--signer <BASE58_PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
--stake-authority <PUBKEY> Public key of authorized staker (defaults to cli config pubkey)
--withdraw-authority <PUBKEY> Public key of authorized withdrawer (defaults to cli config pubkey)
ARGS:
<STAKE ACCOUNT> Keypair of the stake account to fund
<AMOUNT> The amount of send to the vote account (default unit SOL)
<UNIT> Specify unit to use for request [possible values: SOL, lamports]
<STAKE ACCOUNT> Signing authority of the stake address to fund
<AMOUNT> The amount of send to the vote account, in SOL
```
#### solana-create-validator-storage-account
@ -759,7 +790,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<STORAGE ACCOUNT OWNER PUBKEY>
@ -790,7 +821,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--seed <SEED STRING> Seed for address generation; if specified, the resulting account will be at
a derived address of the VOTE ACCOUNT pubkey
@ -819,12 +850,16 @@ OPTIONS:
--ask-seed-phrase <KEYPAIR NAME>
Recover a keypair using a seed phrase and optional passphrase [possible values: keypair]
--blockhash <BLOCKHASH> Use the supplied blockhash
--blockhash <BLOCKHASH> Use the supplied blockhash
-C, --config <PATH>
Configuration file to use [default: ~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
--fee-payer <KEYPAIR or PUBKEY>
Specify the fee-payer account. This may be a keypair file, the ASK keyword
or the pubkey of an offline signer, provided an appropriate --signer argument
is also passed. Defaults to the client keypair.
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--nonce <PUBKEY>
Provide the nonce account to use when creating a nonced
transaction. Nonced transactions are useful when a transaction
@ -833,8 +868,8 @@ OPTIONS:
--nonce-authority <KEYPAIR or PUBKEY>
Provide the nonce authority keypair to use when signing a nonced transaction
--signer <PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
--stake-authority <KEYPAIR of PUBKEY> Public key of authorized staker (defaults to cli config pubkey)
--signer <BASE58_PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
--stake-authority <KEYPAIR or PUBKEY> Public key of authorized staker (defaults to cli config pubkey)
ARGS:
<STAKE ACCOUNT> Stake account to be deactivated.
@ -860,12 +895,16 @@ OPTIONS:
--ask-seed-phrase <KEYPAIR NAME>
Recover a keypair using a seed phrase and optional passphrase [possible values: keypair]
--blockhash <BLOCKHASH> Use the supplied blockhash
--blockhash <BLOCKHASH> Use the supplied blockhash
-C, --config <PATH>
Configuration file to use [default: ~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
--fee-payer <KEYPAIR or PUBKEY>
Specify the fee-payer account. This may be a keypair file, the ASK keyword
or the pubkey of an offline signer, provided an appropriate --signer argument
is also passed. Defaults to the client keypair.
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--nonce <PUBKEY>
Provide the nonce account to use when creating a nonced
transaction. Nonced transactions are useful when a transaction
@ -874,8 +913,8 @@ OPTIONS:
--nonce-authority <KEYPAIR or PUBKEY>
Provide the nonce authority keypair to use when signing a nonced transaction
--signer <PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
--stake-authority <KEYPAIR of PUBKEY> Public key of authorized staker (defaults to cli config pubkey)
--signer <BASE58_PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
--stake-authority <KEYPAIR or PUBKEY> Public key of authorized staker (defaults to cli config pubkey)
ARGS:
<STAKE ACCOUNT> Stake account to delegate
@ -903,7 +942,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<PATH TO BPF PROGRAM> /path/to/program.o
@ -931,7 +970,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
```
#### solana-fees
@ -955,7 +994,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
```
#### solana-genesis-hash
@ -979,7 +1018,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
```
#### solana-gossip
@ -1003,7 +1042,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
```
#### solana-help
@ -1018,6 +1057,55 @@ ARGS:
<subcommand>... The subcommand whose help message to display
```
#### solana-leader-schedule
```text
solana-leader-schedule
Display leader schedule
USAGE:
solana leader-schedule [FLAGS] [OPTIONS]
FLAGS:
-h, --help Prints help information
--skip-seed-phrase-validation Skip validation of seed phrases. Use this if your phrase does not use the BIP39
official English word list
-V, --version Prints version information
-v, --verbose Show extra information header
OPTIONS:
--ask-seed-phrase <KEYPAIR NAME> Recover a keypair using a seed phrase and optional passphrase [possible
values: keypair]
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
```
#### solana-live-slots
```text
solana-live-slots
Show information about the current slot progression
USAGE:
solana live-slots [FLAGS] [OPTIONS]
FLAGS:
-h, --help Prints help information
--skip-seed-phrase-validation Skip validation of seed phrases. Use this if your phrase does not use the BIP39
official English word list
-V, --version Prints version information
-v, --verbose Show extra information header
OPTIONS:
--ask-seed-phrase <KEYPAIR NAME> Recover a keypair using a seed phrase and optional passphrase [possible
values: keypair]
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
-w, --ws <URL> WebSocket URL for PubSub RPC connection [default: ws://127.0.0.1:8900]
```
#### solana-new-nonce
```text
solana-new-nonce
@ -1041,7 +1129,7 @@ OPTIONS:
Configuration file to use [default: ~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--nonce-authority <KEYPAIR or PUBKEY>
Provide the nonce authority keypair to use when signing a nonced transaction
@ -1071,7 +1159,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<NONCE ACCOUNT> Address of the nonce account to display
@ -1099,7 +1187,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<NONCE ACCOUNT> Address of the nonce account to display
@ -1111,7 +1199,7 @@ solana-pay
Send a payment
USAGE:
solana pay [FLAGS] [OPTIONS] <TO PUBKEY> <AMOUNT> [--] [UNIT]
solana pay [FLAGS] [OPTIONS] <TO PUBKEY> <AMOUNT>
FLAGS:
--cancelable
@ -1126,12 +1214,12 @@ OPTIONS:
--ask-seed-phrase <KEYPAIR NAME>
Recover a keypair using a seed phrase and optional passphrase [possible values: keypair]
--blockhash <BLOCKHASH> Use the supplied blockhash
--blockhash <BLOCKHASH> Use the supplied blockhash
-C, --config <PATH>
Configuration file to use [default: ~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--nonce <PUBKEY>
Provide the nonce account to use when creating a nonced
transaction. Nonced transactions are useful when a transaction
@ -1140,15 +1228,14 @@ OPTIONS:
--nonce-authority <KEYPAIR or PUBKEY>
Provide the nonce authority keypair to use when signing a nonced transaction
--signer <PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
--after <DATETIME> A timestamp after which transaction will execute
--require-timestamp-from <PUBKEY> Require timestamp from this third party
--require-signature-from <PUBKEY>... Any third party signatures required to unlock the lamports
--signer <BASE58_PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
--after <DATETIME> A timestamp after which transaction will execute
--require-timestamp-from <PUBKEY> Require timestamp from this third party
--require-signature-from <PUBKEY>... Any third party signatures required to unlock the lamports
ARGS:
<TO PUBKEY> The pubkey of recipient
<AMOUNT> The amount to send (default unit SOL)
<UNIT> Specify unit to use for request [possible values: SOL, lamports]
<AMOUNT> The amount to send, in SOL
```
#### solana-ping
@ -1175,7 +1262,7 @@ OPTIONS:
-c, --count <NUMBER> Stop after submitting count transactions
-i, --interval <SECONDS> Wait interval seconds between submitting the next transaction [default: 2]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--lamports <NUMBER> Number of lamports to transfer for each transaction [default: 1]
-t, --timeout <SECONDS> Wait up to timeout seconds for transaction confirmation [default: 15]
```
@ -1201,7 +1288,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<PUBKEY> The pubkey of recipient
@ -1230,41 +1317,13 @@ OPTIONS:
~/.config/solana/cli/config.yml]
--date <DATETIME> Optional arbitrary timestamp to apply
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<PUBKEY> The pubkey of recipient
<PROCESS ID> The process id of the transfer to unlock
```
#### solana-show-stake-account
```text
solana-show-stake-account
Show the contents of a stake account
USAGE:
solana show-stake-account [FLAGS] [OPTIONS] <STAKE ACCOUNT>
FLAGS:
-h, --help Prints help information
--lamports Display balance in lamports instead of SOL
--skip-seed-phrase-validation Skip validation of seed phrases. Use this if your phrase does not use the BIP39
official English word list
-V, --version Prints version information
-v, --verbose Show extra information header
OPTIONS:
--ask-seed-phrase <KEYPAIR NAME> Recover a keypair using a seed phrase and optional passphrase [possible
values: keypair]
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT> Address of the stake account to display
```
#### solana-slot
```text
solana-slot
@ -1287,7 +1346,85 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
```
#### solana-split-stake
```text
solana-split-stake
Split a stake account
USAGE:
solana split-stake [FLAGS] [OPTIONS] <STAKE ACCOUNT> <SPLIT STAKE ACCOUNT> <AMOUNT>
FLAGS:
-h, --help Prints help information
--sign-only Sign the transaction offline
--skip-seed-phrase-validation Skip validation of seed phrases. Use this if your phrase does not use the BIP39
official English word list
-V, --version Prints version information
-v, --verbose Show extra information header
OPTIONS:
--ask-seed-phrase <KEYPAIR NAME>
Recover a keypair using a seed phrase and optional passphrase [possible values: keypair]
--blockhash <BLOCKHASH> Use the supplied blockhash
-C, --config <PATH>
Configuration file to use [default: ~/.config/solana/cli/config.yml]
--fee-payer <KEYPAIR or PUBKEY>
Specify the fee-payer account. This may be a keypair file, the ASK keyword
or the pubkey of an offline signer, provided an appropriate --signer argument
is also passed. Defaults to the client keypair.
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--nonce <PUBKEY>
Provide the nonce account to use when creating a nonced
transaction. Nonced transactions are useful when a transaction
requires a lengthy signing process. Learn more about nonced
transactions at https://docs.solana.com/offline-signing/durable-nonce
--nonce-authority <KEYPAIR or PUBKEY>
Provide the nonce authority keypair to use when signing a nonced transaction
--seed <SEED STRING>
Seed for address generation; if specified, the resulting account will be at a derived address of the SPLIT
STAKE ACCOUNT pubkey
--signer <BASE58_PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
--stake-authority <KEYPAIR or PUBKEY> Public key of authorized staker (defaults to cli config pubkey)
ARGS:
<STAKE ACCOUNT> Stake account to be split
<SPLIT STAKE ACCOUNT> Keypair of the new stake account to split funds into
<AMOUNT> The amount to move into the new stake account, in unit SOL
```
#### solana-stake-account
```text
solana-stake-account
Show the contents of a stake account
USAGE:
solana stake-account [FLAGS] [OPTIONS] <STAKE ACCOUNT>
FLAGS:
-h, --help Prints help information
--lamports Display balance in lamports instead of SOL
--skip-seed-phrase-validation Skip validation of seed phrases. Use this if your phrase does not use the BIP39
official English word list
-V, --version Prints version information
-v, --verbose Show extra information header
OPTIONS:
--ask-seed-phrase <KEYPAIR NAME> Recover a keypair using a seed phrase and optional passphrase [possible
values: keypair]
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<STAKE ACCOUNT> Address of the stake account to display
```
#### solana-stake-authorize-staker
@ -1310,12 +1447,16 @@ OPTIONS:
--ask-seed-phrase <KEYPAIR NAME>
Recover a keypair using a seed phrase and optional passphrase [possible values: keypair]
--blockhash <BLOCKHASH> Use the supplied blockhash
--blockhash <BLOCKHASH> Use the supplied blockhash
-C, --config <PATH>
Configuration file to use [default: ~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
--fee-payer <KEYPAIR or PUBKEY>
Specify the fee-payer account. This may be a keypair file, the ASK keyword
or the pubkey of an offline signer, provided an appropriate --signer argument
is also passed. Defaults to the client keypair.
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--nonce <PUBKEY>
Provide the nonce account to use when creating a nonced
transaction. Nonced transactions are useful when a transaction
@ -1324,8 +1465,8 @@ OPTIONS:
--nonce-authority <KEYPAIR or PUBKEY>
Provide the nonce authority keypair to use when signing a nonced transaction
--signer <PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
--stake-authority <KEYPAIR of PUBKEY> Public key of authorized staker (defaults to cli config pubkey)
--signer <BASE58_PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
--stake-authority <KEYPAIR or PUBKEY> Public key of authorized staker (defaults to cli config pubkey)
ARGS:
<STAKE ACCOUNT> Stake account in which to set the authorized staker
@ -1356,8 +1497,12 @@ OPTIONS:
-C, --config <PATH>
Configuration file to use [default: ~/.config/solana/cli/config.yml]
--fee-payer <KEYPAIR or PUBKEY>
Specify the fee-payer account. This may be a keypair file, the ASK keyword
or the pubkey of an offline signer, provided an appropriate --signer argument
is also passed. Defaults to the client keypair.
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--nonce <PUBKEY>
Provide the nonce account to use when creating a nonced
transaction. Nonced transactions are useful when a transaction
@ -1366,7 +1511,7 @@ OPTIONS:
--nonce-authority <KEYPAIR or PUBKEY>
Provide the nonce authority keypair to use when signing a nonced transaction
--signer <PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
--signer <BASE58_PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
--withdraw-authority <KEYPAIR or PUBKEY> Public key of authorized withdrawer (defaults to cli config pubkey)
ARGS:
@ -1396,7 +1541,61 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
```
#### solana-stake-set-lockup
```text
solana-stake-set-lockup
Set Lockup for the stake account
USAGE:
solana stake-set-lockup [FLAGS] [OPTIONS] <STAKE ACCOUNT>
FLAGS:
-h, --help Prints help information
--sign-only Sign the transaction offline
--skip-seed-phrase-validation Skip validation of seed phrases. Use this if your phrase does not use the BIP39
official English word list
-V, --version Prints version information
-v, --verbose Show extra information header
OPTIONS:
--ask-seed-phrase <KEYPAIR NAME>
Recover a keypair using a seed phrase and optional passphrase [possible values: keypair]
--blockhash <BLOCKHASH> Use the supplied blockhash
-C, --config <PATH>
Configuration file to use [default: ~/.config/solana/cli/config.yml]
--custodian <KEYPAIR or PUBKEY> Public key of signing custodian (defaults to cli config pubkey)
--fee-payer <KEYPAIR or PUBKEY>
Specify the fee-payer account. This may be a keypair file, the ASK keyword
or the pubkey of an offline signer, provided an appropriate --signer argument
is also passed. Defaults to the client keypair.
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--lockup-date <RFC3339 DATE TIME>
The date and time at which this account will be available for withdrawal
--lockup-epoch <EPOCH>
The epoch height at which this account will be available for withdrawal
--new-custodian <KEYPAIR or PUBKEY>
Identity of the new lockup custodian (can withdraw before lockup expires)
--nonce <PUBKEY>
Provide the nonce account to use when creating a nonced
transaction. Nonced transactions are useful when a transaction
requires a lengthy signing process. Learn more about nonced
transactions at https://docs.solana.com/offline-signing/durable-nonce
--nonce-authority <KEYPAIR or PUBKEY>
Provide the nonce authority keypair to use when signing a nonced transaction
--signer <BASE58_PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
ARGS:
<STAKE ACCOUNT> Stake account for which to set Lockup
```
#### solana-stakes
@ -1421,7 +1620,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<VOTE ACCOUNT PUBKEYS>... Only show stake accounts delegated to the provided vote accounts
@ -1448,7 +1647,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<STORAGE ACCOUNT PUBKEY> Storage account pubkey
@ -1476,7 +1675,53 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
```
#### solana-transfer
```text
solana-transfer
Transfer funds between system accounts
USAGE:
solana transfer [FLAGS] [OPTIONS] <TO PUBKEY> <AMOUNT>
FLAGS:
-h, --help Prints help information
--sign-only Sign the transaction offline
--skip-seed-phrase-validation Skip validation of seed phrases. Use this if your phrase does not use the BIP39
official English word list
-V, --version Prints version information
-v, --verbose Show extra information header
OPTIONS:
--ask-seed-phrase <KEYPAIR NAME>
Recover a keypair using a seed phrase and optional passphrase [possible values: keypair]
--blockhash <BLOCKHASH> Use the supplied blockhash
-C, --config <PATH>
Configuration file to use [default: ~/.config/solana/cli/config.yml]
--fee-payer <KEYPAIR or PUBKEY>
Specify the fee-payer account. This may be a keypair file, the ASK keyword
or the pubkey of an offline signer, provided an appropriate --signer argument
is also passed. Defaults to the client keypair.
--from <KEYPAIR or PUBKEY> Source account of funds (if different from client local account)
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--nonce <PUBKEY>
Provide the nonce account to use when creating a nonced
transaction. Nonced transactions are useful when a transaction
requires a lengthy signing process. Learn more about nonced
transactions at https://docs.solana.com/offline-signing/durable-nonce
--nonce-authority <KEYPAIR or PUBKEY>
Provide the nonce authority keypair to use when signing a nonced transaction
--signer <BASE58_PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
ARGS:
<TO PUBKEY> The pubkey of recipient
<AMOUNT> The amount to send, in SOL
```
#### solana-validator-info
@ -1500,7 +1745,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
SUBCOMMANDS:
get Get and parse Solana Validator info
@ -1530,7 +1775,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
```
#### solana-vote-account
@ -1555,7 +1800,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<VOTE ACCOUNT PUBKEY> Vote account pubkey
@ -1582,7 +1827,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<VOTE ACCOUNT PUBKEY> Vote account in which to set the authorized voter
@ -1610,7 +1855,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<VOTE ACCOUNT PUBKEY> Vote account in which to set the authorized withdrawer
@ -1638,7 +1883,7 @@ OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
ARGS:
<VOTE ACCOUNT PUBKEY> Vote account to update
@ -1652,7 +1897,7 @@ solana-withdraw-from-nonce-account
Withdraw lamports from the nonce account
USAGE:
solana withdraw-from-nonce-account [FLAGS] [OPTIONS] <NONCE ACCOUNT> <DESTINATION ACCOUNT> <AMOUNT> [UNIT]
solana withdraw-from-nonce-account [FLAGS] [OPTIONS] <NONCE ACCOUNT> <DESTINATION ACCOUNT> <AMOUNT>
FLAGS:
-h, --help Prints help information
@ -1669,7 +1914,7 @@ OPTIONS:
Configuration file to use [default: ~/.config/solana/cli/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--nonce-authority <KEYPAIR or PUBKEY>
Provide the nonce authority keypair to use when signing a nonced transaction
@ -1677,8 +1922,7 @@ OPTIONS:
ARGS:
<NONCE ACCOUNT> Nonce account from to withdraw from
<DESTINATION ACCOUNT> The account to which the lamports should be transferred
<AMOUNT> The amount to withdraw from the nonce account (default unit SOL)
<UNIT> Specify unit to use for request [possible values: SOL, lamports]
<AMOUNT> The amount to withdraw from the nonce account, in SOL
```
#### solana-withdraw-stake
@ -1687,10 +1931,11 @@ solana-withdraw-stake
Withdraw the unstaked lamports from the stake account
USAGE:
solana withdraw-stake [FLAGS] [OPTIONS] <STAKE ACCOUNT> <DESTINATION ACCOUNT> <AMOUNT> [UNIT]
solana withdraw-stake [FLAGS] [OPTIONS] <STAKE ACCOUNT> <DESTINATION ACCOUNT> <AMOUNT>
FLAGS:
-h, --help Prints help information
--sign-only Sign the transaction offline
--skip-seed-phrase-validation Skip validation of seed phrases. Use this if your phrase does not use the BIP39
official English word list
-V, --version Prints version information
@ -1700,17 +1945,29 @@ OPTIONS:
--ask-seed-phrase <KEYPAIR NAME>
Recover a keypair using a seed phrase and optional passphrase [possible values: keypair]
--blockhash <BLOCKHASH> Use the supplied blockhash
-C, --config <PATH>
Configuration file to use [default: ~/.config/solana/cli/config.yml]
--fee-payer <KEYPAIR or PUBKEY>
Specify the fee-payer account. This may be a keypair file, the ASK keyword
or the pubkey of an offline signer, provided an appropriate --signer argument
is also passed. Defaults to the client keypair.
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
-k, --keypair <PATH> /path/to/id.json or usb://remote/wallet/path
--nonce <PUBKEY>
Provide the nonce account to use when creating a nonced
transaction. Nonced transactions are useful when a transaction
requires a lengthy signing process. Learn more about nonced
transactions at https://docs.solana.com/offline-signing/durable-nonce
--nonce-authority <KEYPAIR or PUBKEY>
Provide the nonce authority keypair to use when signing a nonced transaction
--signer <BASE58_PUBKEY=BASE58_SIG>... Provide a public-key/signature pair for the transaction
--withdraw-authority <KEYPAIR or PUBKEY> Public key of authorized withdrawer (defaults to cli config pubkey)
ARGS:
<STAKE ACCOUNT> Stake account from which to withdraw
<DESTINATION ACCOUNT> The account to which the lamports should be transferred
<AMOUNT> The amount to withdraw from the stake account (default unit SOL)
<UNIT> Specify unit to use for request [possible values: SOL, lamports]
<AMOUNT> The amount to withdraw from the stake account, in SOL
```

View File

@ -154,7 +154,7 @@ FLAGS:
OPTIONS:
-d, --data_dir <PATH> Directory to store install data [default: .../Library/Application Support/solana]
-u, --url <URL> JSON RPC URL for the solana cluster [default: http://testnet.solana.com:8899]
-u, --url <URL> JSON RPC URL for the solana cluster [default: http://devnet.solana.com:8899]
-p, --pubkey <PUBKEY> Public key of the update manifest [default: 9XX329sPuskWhH4DQh6k16c87dHKhXLBZTL3Gxmve8Gp]
```

View File

@ -22,7 +22,7 @@ At present, the following commands support offline signing:
To sign a transaction offline, pass the following arguments on the command line
1) `--sign-only`, prevents the client from submitting the signed transaction
to the network. Instead, the pubkey/signature pairs are printed to stdout.
to the network. Instead, the pubkey/signature pairs are printed to stdout.
2) `--blockhash BASE58_HASH`, allows the caller to specify the value used to
fill the transaction's `recent_blockhash` field. This serves a number of
purposes, namely:
@ -37,7 +37,7 @@ Command
```bash
solana@offline$ solana pay --sign-only --blockhash 5Tx8F3jgSHx21CbtjwmdaKPLM5tWmreWAnPrbqHomSJF \
recipient-keypair.json 1 SOL
recipient-keypair.json 1
```
Output
@ -67,7 +67,7 @@ Command
```bash
solana@online$ solana pay --blockhash 5Tx8F3jgSHx21CbtjwmdaKPLM5tWmreWAnPrbqHomSJF \
--signer FhtzLVsmcV7S5XqGD79ErgoseCLhZYmEZnz9kQg1Rp7j=4vC38p4bz7XyiXrk6HtaooUqwxTWKocf45cstASGtmrD398biNJnmTcUCVEojE7wVQvgdYbjHJqRFZPpzfCQpmUN
recipient-keypair.json 1 SOL
recipient-keypair.json 1
```
Output

View File

@ -36,7 +36,7 @@ A nonce account is created by first generating a new keypair, then create the ac
```bash
solana-keygen new -o nonce-keypair.json
solana create-nonce-account nonce-keypair.json 1 SOL
solana create-nonce-account nonce-keypair.json 1
```
- Output
@ -64,7 +64,7 @@ presently stored nonce value with
- Command
```bash
solana nonce nonce-keypair.json
solana nonce nonce-keypair.json
```
- Output
@ -105,7 +105,7 @@ Inspect a nonce account in a more human friendly format with
- Command
```bash
solana nonce-account nonce-keypair.json
solana nonce-account nonce-keypair.json
```
- Output
@ -127,7 +127,7 @@ Withdraw funds from a nonce account with
- Command
```bash
solana withdraw-from-nonce-account nonce-keypair.json ~/.config/solana/id.json 0.5 SOL
solana withdraw-from-nonce-account nonce-keypair.json ~/.config/solana/id.json 0.5
```
- Output
@ -151,7 +151,7 @@ Reassign the authority of a nonce account after creation with
- Command
```bash
solana authorize-nonce-account nonce-keypair.json nonce-authority.json
solana authorize-nonce-account nonce-keypair.json nonce-authority.json
```
- Output
@ -197,7 +197,7 @@ Alice will need some funds to create a nonce account and send to Bob. Airdrop
her some SOL
```bash
$ solana airdrop -k alice.json 10 SOL
$ solana airdrop -k alice.json 10
10 SOL
```
@ -211,7 +211,7 @@ has full authority over the nonce account
{% endhint %}
```bash
$ solana create-nonce-account -k alice.json nonce.json 1 SOL
$ solana create-nonce-account -k alice.json nonce.json 1
3KPZr96BTsL3hqera9up82KAU462Gz31xjqJ6eHUAjF935Yf8i1kmfEbo6SVbNaACKE5z6gySrNjVRvmS8DcPuwV
```
@ -221,7 +221,7 @@ Alice attempts to pay Bob, but takes too long to sign. The specified blockhash
expires and the transaction fails
```bash
$ solana pay -k alice.json --blockhash expiredDTaxfagttWjQweib42b6ZHADSx94Tw8gHx3W7 bob.json 1 SOL
$ solana pay -k alice.json --blockhash expiredDTaxfagttWjQweib42b6ZHADSx94Tw8gHx3W7 bob.json 1
[2020-01-02T18:48:28.462911000Z ERROR solana_cli::cli] Io(Custom { kind: Other, error: "Transaction \"33gQQaoPc9jWePMvDAeyJpcnSPiGUAdtVg8zREWv4GiKjkcGNufgpcbFyRKRrA25NkgjZySEeKue5rawyeH5TzsV\" failed: None" })
Error: Io(Custom { kind: Other, error: "Transaction \"33gQQaoPc9jWePMvDAeyJpcnSPiGUAdtVg8zREWv4GiKjkcGNufgpcbFyRKRrA25NkgjZySEeKue5rawyeH5TzsV\" failed: None" })
```
@ -236,13 +236,13 @@ Remember, `alice.json` is the [nonce authority](#nonce-authority) in this exampl
{% endhint %}
```bash
$ solana nonce-account nonce.json
$ solana nonce-account nonce.json
balance: 1 SOL
minimum balance required: 0.00136416 SOL
nonce: F7vmkY3DTaxfagttWjQweib42b6ZHADSx94Tw8gHx3W7
```
```bash
$ solana pay -k alice.json --blockhash F7vmkY3DTaxfagttWjQweib42b6ZHADSx94Tw8gHx3W7 --nonce nonce.json bob.json 1 SOL
$ solana pay -k alice.json --blockhash F7vmkY3DTaxfagttWjQweib42b6ZHADSx94Tw8gHx3W7 --nonce nonce.json bob.json 1
HR1368UKHVZyenmH7yVz5sBAijV6XAPeWbEiXEGVYQorRMcoijeNAbzZqEZiH8cDB8tk65ckqeegFjK8dHwNFgQ
```
@ -256,7 +256,7 @@ $ solana balance -k bob.json
1 SOL
```
```bash
$ solana nonce-account nonce.json
$ solana nonce-account nonce.json
balance: 1 SOL
minimum balance required: 0.00136416 SOL
nonce: 6bjroqDcZgTv6Vavhqf81oBHTv3aMnX19UTB51YhAZnN

View File

@ -90,6 +90,91 @@ For full usage details run:
```bash
solana-keygen pubkey --help
```
## Verifying the Keypair
A keypair can be verified by following a variation on the
[offline signing](../offline-signing/README.md) procedure with a dummy transaction.
### Create and Sign a Dummy Transaction
Use offline signing to acquire the signature of a dummy transaction that can
be verified in the next step. A 0 Lamport [transfer](../cli/usage.md#solana-transfer)
is used to prevent inadvertent loss of funds. Additionally, an improbable _blockhash_
value is specified, as well as using the address of the _system program_ for the `TO`
argument, to ensure the transaction would be rejected by the _cluster_ should
it be submitted in error.
Command
```text
solana transfer 11111111111111111111111111111111 0 --sign-only \
--ask-seed-phrase keypair --blockhash 11111111111111111111111111111111
```
Prompt for seed phrase
```text
[keypair] seed phrase:
[keypair] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
Recovered pubkey `AjTz9EX6vXB6EboKpFm7SwrbDannb6icjvEE632D3rfi`. Continue? (y/n): y
```
Output
```text
Blockhash: 11111111111111111111111111111111
Signers (Pubkey=Signature):
AjTz9EX6vXB6EboKpFm7SwrbDannb6icjvEE632D3rfi=3uZndChSmPoYfaCihC993E7EAHKDsuu53Ge6Dk1K6ULwhJkgcgiHNm9J1Geqq2azW6PKxQTFjC8rMm5bGxRcYWA
{"blockhash":"11111111111111111111111111111111","signers":["AjTz9EX6vXB6EboKpFm7SwrbDannb6icjvEE632D3rfi=3uZndChSmPoYfaCihC993E7EAHKDsuu53Ge6Dk1K6ULwhJkgcgiHNm9J1Geqq2azW6PKxQTFjC8rMm5bGxRcYWA"]}
```
### Verify the Signature
Using the _Signers_ output from the [previous step](#create-and-sign-a-dummy-transaction)
to reconstruct the transaction, this time specifying the _pubkey_ and _signature_
as in the submission step of [offline signing](../offline-signing/README.md). That is, the `--from` and
`--fee-payer` are explicitly set to the _pubkey_ rather than being taken from
the keypair (which is not queried this time).
Command
```text
solana transfer 11111111111111111111111111111111 0 --sign-only --from AjTz9EX6vXB6EboKpFm7SwrbDannb6icjvEE632D3rfi \
--signer AjTz9EX6vXB6EboKpFm7SwrbDannb6icjvEE632D3rfi=3uZndChSmPoYfaCihC993E7EAHKDsuu53Ge6Dk1K6ULwhJkgcgiHNm9J1Geqq2azW6PKxQTFjC8rMm5bGxRcYWA \
--blockhash 11111111111111111111111111111111 --fee-payer AjTz9EX6vXB6EboKpFm7SwrbDannb6icjvEE632D3rfi
```
Output
```text
Blockhash: 11111111111111111111111111111111
Signers (Pubkey=Signature):
AjTz9EX6vXB6EboKpFm7SwrbDannb6icjvEE632D3rfi=3uZndChSmPoYfaCihC993E7EAHKDsuu53Ge6Dk1K6ULwhJkgcgiHNm9J1Geqq2azW6PKxQTFjC8rMm5bGxRcYWA
{"blockhash":"11111111111111111111111111111111","signers":["AjTz9EX6vXB6EboKpFm7SwrbDannb6icjvEE632D3rfi=3uZndChSmPoYfaCihC993E7EAHKDsuu53Ge6Dk1K6ULwhJkgcgiHNm9J1Geqq2azW6PKxQTFjC8rMm5bGxRcYWA"]}
```
### An Example of Failure
To simulate an error the [verification step](#verify-the-signature) is repeated,
but with a corrupted _signature_ (the last letter is changed from "A" to "B").
Command
```text
solana transfer 11111111111111111111111111111111 0 --sign-only --from AjTz9EX6vXB6EboKpFm7SwrbDannb6icjvEE632D3rfi \
--signer AjTz9EX6vXB6EboKpFm7SwrbDannb6icjvEE632D3rfi=3uZndChSmPoYfaCihC993E7EAHKDsuu53Ge6Dk1K6ULwhJkgcgiHNm9J1Geqq2azW6PKxQTFjC8rMm5bGxRcYWB \
--blockhash 11111111111111111111111111111111 --fee-payer AjTz9EX6vXB6EboKpFm7SwrbDannb6icjvEE632D3rfi
```
Output (Error)
```text
Error: BadParameter("Transaction construction failed, incorrect signature or public key provided")
```
## Checking Account Balance
All that is needed to check an account balance is the public key of an account.
@ -102,7 +187,7 @@ networked machine.
Next, configure the `solana` CLI tool to connect to a particular cluster:
```bash
solana config set --url <CLUSTER URL> # (i.e. http://testnet.solana.com:8899)
solana config set --url <CLUSTER URL> # (i.e. http://devnet.solana.com:8899)
```
Finally, to check the balance, run the following command:
@ -162,10 +247,10 @@ Refer to the following page for a comprehensive guide on running a validator:
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.
`--keypair ASK` to securely input the funding keypair.
```bash
solana create-stake-account ASK 1 SOL --ask-seed-phrase keypair
solana create-stake-account ASK 1 --keypair ASK
[stake_account] seed phrase: 🔒
[stake_account] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
@ -173,11 +258,11 @@ solana create-stake-account ASK 1 SOL --ask-seed-phrase keypair
[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
Then, to delegate that stake to a validator, use `--keypair ASK` to
securely input the funding keypair.
```bash
solana delegate-stake --ask-seed-phrase keypair <STAKE_ACCOUNT_PUBKEY> <VOTE_ACCOUNT_PUBKEY>
solana delegate-stake --keypair ASK <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:

View File

@ -22,7 +22,7 @@ Each CTF test starts with an opaque entry point and a funded keypair. The test s
```text
use crate::contact_info::ContactInfo;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
pub fn test_this_behavior(
entry_point_info: &ContactInfo,
funding_keypair: &Keypair,

View File

@ -0,0 +1,12 @@
# Remote Wallet
This document describes how to use a remote wallet with the Solana CLI
tools. Currently, Solana supports:
- Ledger hardware wallet (Nano S)
## Overview
Solana's remote-wallet integration provides Solana CLI methods to query a
device's BIP32 public keys and send messages to the device for secure signing.
{% page-ref page="ledger.md" %}

View File

@ -0,0 +1,132 @@
# Ledger Hardware Wallet
The Ledger Nano S hardware wallet offers secure storage of your Solana private
keys. The Solana Ledger app enables derivation of essentially infinite keys, and
secure transaction signing.
## Before You Begin
- [Initialize your Ledger Nano S](https://support.ledger.com/hc/en-us/articles/360000613793)
- [Install the latest device firmware](https://support.ledgerwallet.com/hc/en-us/articles/360002731113-Update-Ledger-Nano-S-firmware)
- [Install Ledger Live](https://support.ledger.com/hc/en-us/articles/360006395553/) software on your computer
## Install the Solana App on Ledger Nano S
1. Open the Manager in Ledger Live
2. Connect and unlock your Ledger Nano S
3. If asked, allow the manager on your device by pressing the right button
4. Find Solana in the app catalog and click Install
5. An installation window appears and your device will display Processing…
6. The app installation is confirmed
## Use Ledger Device with Solana CLI
1. Plug your Ledger device into your computer's USB port
2. Enter your pin and start the Solana app on the Ledger device
3. On your computer, run:
```text
solana address --keypair usb://ledger
```
This confirms your Ledger device is connected properly and in the correct state
to interact with the Solana CLI. The command returns your Ledger's unique
*wallet key*. When you have multiple Nano S devices connected to the same
computer, you can use your wallet key to specify which Ledger hardware wallet
you want to use. Run the same command again, but this time, with its fully
qualified URL:
```text
solana address --keypair usb://ledger/nano-s/<WALLET_KEY>
```
Confirm it prints the same key as when you entered just `usb://ledger`.
### Ledger Device URLs
Solana defines a format for the URL protocol "usb://" to uniquely locate any Solana key on
any remote wallet connected to your computer.
The URL has the form, where square brackets denote optional fields:
```text
usb://ledger[/<LEDGER_TYPE>[/<WALLET_KEY>]][?key=<DERIVATION_PATH>]
```
`LEDGER_TYPE` is optional and defaults to the value "nano-s". If the value is provided,
it must be "nano-s" without quotes, the only supported Ledger device at this time.
`WALLET_KEY` is used to disambiguate multiple Nano S devices. Every Ledger has
a unique master key and from that key derives a separate unique key per app.
`DERVIATION_PATH` is used to navigate to Solana keys within your Ledger hardware
wallet. The path has the form `<ACCOUNT>[/<CHANGE>]`, where each `ACCOUNT` and
`CHANGE` are postive integers.
All derivation paths implicitly include the prefix `44'/501'`, which indicates
the path follows the [BIP44 specifications](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)
and that any dervied keys are Solana keys (Coin type 501). The single quote
indicates a "hardened" derivation. Because Solana uses Ed25519 keypairs, all
derivations are hardened and therefore adding the quote is optional and
unnecessary.
For example, a complete Ledger device path might be:
```text
usb://ledger/nano-s/BsNsvfXqQTtJnagwFWdBS7FBXgnsK8VZ5CmuznN85swK?key=0/0
```
### Set CLI Configuration
If you want to set a Ledger key as the default signer for CLI commands, use the
[CLI configuration settings](../cli/usage.md#solana-config):
```text
solana config set --keypair <LEDGER_URL>
```
For example:
```text
solana config set --keypair usb://ledger?key=0
```
### Check Account Balance
```text
solana balance --keypair usb://ledger?key=12345
```
Or with the default signer:
```text
solana balance
```
### Send SOL via Ledger Device
```text
solana transfer <RECIPIENT> <AMOUNT> --from <LEDGER_URL>
```
Or with the default signer:
```text
solana transfer <RECIPIENT> <AMOUNT>
```
### Delegate Stake with Ledger Device
```text
solana delegate-stake <STAKE_ACCOUNT> <VOTER_ID> --keypair <LEDGER_URL>
```
Or with the default signer:
```text
solana delegate-stake <STAKE_ACCOUNT> <VOTER_ID>
```
## Support
Email maintainers@solana.com

View File

@ -8,9 +8,9 @@ Please note some of the information and instructions described here may change i
Archivers are specialized light clients. They download a part of the ledger \(a.k.a Segment\) and store it. They earn rewards for storing segments.
The testnet features a validator running at testnet.solana.com, which serves as the entrypoint to the cluster for your archiver node.
The testnet features a validator running at devnet.solana.com, which serves as the entrypoint to the cluster for your archiver node.
Additionally there is a blockexplorer available at [http://testnet.solana.com/](http://testnet.solana.com/).
Additionally there is a blockexplorer available at [http://devnet.solana.com/](http://devnet.solana.com/).
The testnet is configured to reset the ledger daily, or sooner should the hourly automated cluster sanity test fail.
@ -29,10 +29,10 @@ 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://devnet.solana.com:8899
```
Inspect the blockexplorer at [http://testnet.solana.com/](http://testnet.solana.com/) for activity.
Inspect the blockexplorer at [http://devnet.solana.com/](http://devnet.solana.com/) for activity.
View the [metrics dashboard](https://metrics.solana.com:3000/d/testnet-beta/testnet-monitor-beta?var-testnet=testnet) for more detail on cluster activity.
@ -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 spy --entrypoint testnet.solana.com:8001
solana-gossip spy --entrypoint devnet.solana.com:8001
# Press ^C to exit
```
@ -129,7 +129,7 @@ Use solana-keygen to show the public keys for each of the keypairs, they will be
```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 airdrop .0001
solana --keypair archiver-keypair.json create-archiver-storage-account $ARCHIVER_IDENTITY $STORAGE_IDENTITY
```
@ -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 devnet.solana.com:8001 --identity-keypair archiver-keypair.json --storage-keypair storage-keypair.json --ledger archiver-ledger
```
## Verify Archiver Setup
@ -146,7 +146,7 @@ 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 spy --entrypoint testnet.solana.com:8001
solana-gossip spy --entrypoint devnet.solana.com:8001
```
Provide the **storage account pubkey** to the `solana storage-account` command to view the recent mining activity from your archiver:

View File

@ -13,9 +13,7 @@ serve as the entrypoint to the cluster for your validator.
Current testnet entrypoints:
* Stable, testnet.solana.com
* Beta, beta.testnet.solana.com
* Edge, edge.testnet.solana.com
* Developer testnet, devnet.solana.com
Solana may launch special testnets for validator participation; we will provide
you with a specific entrypoint URL to use.

View File

@ -6,7 +6,7 @@ Confirm the IP address and **identity pubkey** of your validator is visible in
the gossip network by running:
```bash
solana-gossip spy --entrypoint testnet.solana.com:8001
solana-gossip spy --entrypoint devnet.solana.com:8001
```
## Check Your Balance
@ -35,13 +35,13 @@ 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://devnet.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://devnet.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://devnet.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
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}' http://devnet.solana.com:8899
```

View File

@ -35,7 +35,7 @@ solana-keygen new -o ~/validator-stake-keypair.json
Now delegate 1 SOL to your validator by first creating your stake account:
```bash
solana create-stake-account ~/validator-stake-keypair.json 1 SOL
solana create-stake-account ~/validator-stake-keypair.json 1
```
and then delegating that stake to your validator:

View File

@ -6,11 +6,11 @@ The solana cli includes `get` and `set` configuration commands to automatically
set the `--url` argument for cli commands. For example:
```bash
solana config set --url http://testnet.solana.com:8899
solana config set --url http://devnet.solana.com:8899
```
\(You can always override the set configuration by explicitly passing the
`--url` argument with a command, eg: `solana --url http://beta.testnet.solana.com:8899 balance`\)
`--url` argument with a command, eg: `solana --url http://beta.devnet.solana.com:8899 balance`\)
## Confirm The Testnet Is Reachable
@ -33,7 +33,7 @@ Try running following command to join the gossip network and view all the other
nodes in the cluster:
```bash
solana-gossip spy --entrypoint testnet.solana.com:8001
solana-gossip spy --entrypoint devnet.solana.com:8001
# Press ^C to exit
```
@ -107,7 +107,7 @@ You should see the following output:
```text
Wallet Config Updated: /home/solana/.config/solana/wallet/config.yml
* url: http://testnet.solana.com:8899
* url: http://devnet.solana.com:8899
* keypair: /home/solana/validator-keypair.json
```
@ -155,7 +155,7 @@ Connect to a testnet cluster by running:
```bash
solana-validator --identity-keypair ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json \
--ledger ~/validator-ledger --rpc-port 8899 --entrypoint testnet.solana.com:8001 \
--ledger ~/validator-ledger --rpc-port 8899 --entrypoint devnet.solana.com:8001 \
--limit-ledger-size
```
@ -166,7 +166,7 @@ Confirm your validator connected to the network by opening a new terminal and
running:
```bash
solana-gossip spy --entrypoint testnet.solana.com:8001
solana-gossip spy --entrypoint devnet.solana.com:8001
```
If your validator is connected, its public key and IP address will appear in the list.

View File

@ -5,8 +5,7 @@ that serves as an entrypoint to the cluster.
Current testnet entrypoints:
* Stable: testnet.solana.com
* Beta: beta.testnet.solana.com
* Stable: devnet.solana.com
Application developers should target the Stable testnet. Key differences
between the Stable testnet and what will be mainnet:
@ -28,13 +27,13 @@ You can submit a JSON-RPC request to see the specific software version of the
cluster. Use this to specify [the software version to install](validator-software.md).
```bash
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' testnet.solana.com:8899
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' devnet.solana.com:8899
```
Example result:
`{"jsonrpc":"2.0","result":{"solana-core":"0.21.0"},"id":1}`
## Using a Different Testnet
This guide is written in the context of testnet.solana.com, our most stable
This guide is written in the context of devnet.solana.com, our most stable
cluster. To participate in another testnet, modify the commands in the following
pages, replacing `testnet.solana.com` with your desired testnet.
pages, replacing `devnet.solana.com` with your desired testnet.

View File

@ -1,6 +1,6 @@
[package]
name = "solana-chacha-cuda"
version = "0.23.2"
version = "0.23.7"
description = "Solana Chacha Cuda APIs"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -10,12 +10,12 @@ edition = "2018"
[dependencies]
log = "0.4.8"
solana-archiver-utils = { path = "../archiver-utils", version = "0.23.2" }
solana-chacha = { path = "../chacha", version = "0.23.2" }
solana-ledger = { path = "../ledger", version = "0.23.2" }
solana-logger = { path = "../logger", version = "0.23.2" }
solana-perf = { path = "../perf", version = "0.23.2" }
solana-sdk = { path = "../sdk", version = "0.23.2" }
solana-archiver-utils = { path = "../archiver-utils", version = "0.23.7" }
solana-chacha = { path = "../chacha", version = "0.23.7" }
solana-ledger = { path = "../ledger", version = "0.23.7" }
solana-logger = { path = "../logger", version = "0.23.7" }
solana-perf = { path = "../perf", version = "0.23.7" }
solana-sdk = { path = "../sdk", version = "0.23.7" }
[dev-dependencies]
hex-literal = "0.2.1"

View File

@ -118,7 +118,7 @@ mod tests {
use solana_ledger::entry::create_ticks;
use solana_ledger::get_tmp_ledger_path;
use solana_sdk::clock::DEFAULT_SLOTS_PER_SEGMENT;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::Keypair;
use std::fs::{remove_dir_all, remove_file};
use std::path::Path;

View File

@ -1,6 +1,6 @@
[package]
name = "solana-chacha-sys"
version = "0.23.2"
version = "0.23.7"
description = "Solana chacha-sys"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"

View File

@ -1,6 +1,6 @@
[package]
name = "solana-chacha"
version = "0.23.2"
version = "0.23.7"
description = "Solana Chacha APIs"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -12,11 +12,11 @@ edition = "2018"
log = "0.4.8"
rand = "0.6.5"
rand_chacha = "0.1.1"
solana-chacha-sys = { path = "../chacha-sys", version = "0.23.2" }
solana-ledger = { path = "../ledger", version = "0.23.2" }
solana-logger = { path = "../logger", version = "0.23.2" }
solana-perf = { path = "../perf", version = "0.23.2" }
solana-sdk = { path = "../sdk", version = "0.23.2" }
solana-chacha-sys = { path = "../chacha-sys", version = "0.23.7" }
solana-ledger = { path = "../ledger", version = "0.23.7" }
solana-logger = { path = "../logger", version = "0.23.7" }
solana-perf = { path = "../perf", version = "0.23.7" }
solana-sdk = { path = "../sdk", version = "0.23.7" }
[dev-dependencies]
hex-literal = "0.2.1"

View File

@ -81,7 +81,7 @@ mod tests {
use solana_ledger::get_tmp_ledger_path;
use solana_sdk::hash::{hash, Hash, Hasher};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::system_transaction;
use std::fs::remove_file;
use std::fs::File;

View File

@ -78,7 +78,9 @@ ARGS+=(
# Also propagate environment variables needed for codecov
# https://docs.codecov.io/docs/testing-with-docker#section-codecov-inside-docker
# We normalize CI to `1`; but codecov expects it to be `true` to detect Buildkite...
CODECOV_ENVS=$(CI=true bash <(curl -s https://codecov.io/env))
# Unfortunately, codecov.io fails sometimes:
# curl: (7) Failed to connect to codecov.io port 443: Connection timed out
CODECOV_ENVS=$(CI=true bash <(while ! curl -sS --retry 5 --retry-delay 2 --retry-connrefused https://codecov.io/env; do sleep 10; done))
if $INTERACTIVE; then
if [[ -n $1 ]]; then

View File

@ -1,4 +1,4 @@
FROM solanalabs/rust:1.40.0
FROM solanalabs/rust:1.41.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.40.0
FROM rust:1.41.0
# Add Google Protocol Buffers for Libra's metrics library.
ENV PROTOC_VERSION 3.8.0
@ -17,6 +17,7 @@ RUN set -x \
clang-7 \
cmake \
lcov \
libudev-dev \
libclang-common-7-dev \
mscgen \
net-tools \

View File

@ -16,13 +16,13 @@
if [[ -n $RUST_STABLE_VERSION ]]; then
stable_version="$RUST_STABLE_VERSION"
else
stable_version=1.40.0
stable_version=1.41.0
fi
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
nightly_version="$RUST_NIGHTLY_VERSION"
else
nightly_version=2019-12-19
nightly_version=2020-02-06
fi

View File

@ -42,7 +42,9 @@ if [[ -z "$CODECOV_TOKEN" ]]; then
echo CODECOV_TOKEN undefined, codecov.io upload skipped
else
# We normalize CI to `1`; but codecov expects it to be `true` to detect Buildkite...
CI=true bash <(curl -s https://codecov.io/bash) -X gcov -f target/cov/lcov.info
# Unfortunately, codecov.io fails sometimes:
# curl: (7) Failed to connect to codecov.io port 443: Connection timed out
CI=true bash <(while ! curl -sS --retry 5 --retry-delay 2 --retry-connrefused https://codecov.io/bash; do sleep 10; done) -Z -X gcov -f target/cov/lcov.info
annotate --style success --context codecov.io \
"CodeCov report: https://codecov.io/github/solana-labs/solana/commit/${CI_COMMIT:0:9}"

View File

@ -234,7 +234,7 @@ sanity() {
(
set -x
NO_INSTALL_CHECK=1 \
ci/testnet-sanity.sh edge-testnet-solana-com gce -P us-west1-b
ci/testnet-sanity.sh edge-devnet-solana-com gce -P us-west1-b
maybe_deploy_software
)
;;
@ -249,7 +249,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-devnet-solana-com gce -P us-west1-b
maybe_deploy_software --deploy-if-newer
)
;;
@ -263,7 +263,7 @@ sanity() {
testnet)
(
set -x
ci/testnet-sanity.sh testnet-solana-com gce -P us-west1-b
ci/testnet-sanity.sh devnet-solana-com gce -P us-west1-b
)
;;
testnet-perf)
@ -327,9 +327,9 @@ deploy() {
testnet-edge)
(
set -x
ci/testnet-deploy.sh -p edge-testnet-solana-com -C gce -z us-west1-b \
ci/testnet-deploy.sh -p edge-devnet-solana-com -C gce -z us-west1-b \
-t "$CHANNEL_OR_TAG" -n 3 -c 0 -u -P \
-a edge-testnet-solana-com --letsencrypt edge.testnet.solana.com \
-a edge-devnet-solana-com --letsencrypt edge.devnet.solana.com \
--limit-ledger-size \
${skipCreate:+-e} \
${skipStart:+-s} \
@ -352,9 +352,9 @@ deploy() {
testnet-beta)
(
set -x
ci/testnet-deploy.sh -p beta-testnet-solana-com -C gce -z us-west1-b \
ci/testnet-deploy.sh -p beta-devnet-solana-com -C gce -z us-west1-b \
-t "$CHANNEL_OR_TAG" -n 3 -c 0 -u -P \
-a beta-testnet-solana-com --letsencrypt beta.testnet.solana.com \
-a beta-devnet-solana-com --letsencrypt beta.devnet.solana.com \
--limit-ledger-size \
${skipCreate:+-e} \
${skipStart:+-s} \
@ -377,9 +377,9 @@ deploy() {
testnet)
(
set -x
ci/testnet-deploy.sh -p testnet-solana-com -C gce -z us-west1-b \
ci/testnet-deploy.sh -p devnet-solana-com -C gce -z us-west1-b \
-t "$CHANNEL_OR_TAG" -n 0 -c 0 -u -P \
-a testnet-solana-com --letsencrypt testnet.solana.com \
-a testnet-solana-com --letsencrypt devnet.solana.com \
--limit-ledger-size \
${skipCreate:+-e} \
${skipStart:+-s} \

View File

@ -1,6 +1,6 @@
[package]
name = "solana-clap-utils"
version = "0.23.2"
version = "0.23.7"
description = "Solana utilities for the clap"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -11,8 +11,8 @@ edition = "2018"
[dependencies]
clap = "2.33.0"
rpassword = "4.0"
semver = "0.9.0"
solana-sdk = { path = "../sdk", version = "0.23.2" }
solana-remote-wallet = { path = "../remote-wallet", version = "0.23.7" }
solana-sdk = { path = "../sdk", version = "0.23.7" }
tiny-bip39 = "0.7.0"
url = "2.1.0"
chrono = "0.4"

View File

@ -1,13 +1,16 @@
use crate::keypair::{keypair_from_seed_phrase, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG};
use crate::keypair::{
keypair_from_seed_phrase, signer_from_path, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG,
};
use chrono::DateTime;
use clap::ArgMatches;
use solana_remote_wallet::remote_wallet::{DerivationPath, RemoteWalletManager};
use solana_sdk::{
clock::UnixTimestamp,
native_token::sol_to_lamports,
pubkey::Pubkey,
signature::{read_keypair_file, Keypair, KeypairUtil, Signature},
signature::{read_keypair_file, Keypair, Signature, Signer},
};
use std::str::FromStr;
use std::{str::FromStr, sync::Arc};
// Return parsed values from matches at `name`
pub fn values_of<T>(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<T>>
@ -92,14 +95,36 @@ pub fn pubkeys_sigs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<(Pubk
})
}
pub fn amount_of(matches: &ArgMatches<'_>, name: &str, unit: &str) -> Option<u64> {
if matches.value_of(unit) == Some("lamports") {
value_of(matches, name)
// Return a signer from matches at `name`
#[allow(clippy::type_complexity)]
pub fn signer_of(
matches: &ArgMatches<'_>,
name: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<(Option<Box<dyn Signer>>, Option<Pubkey>), Box<dyn std::error::Error>> {
if let Some(location) = matches.value_of(name) {
let signer = signer_from_path(matches, location, name, wallet_manager)?;
let signer_pubkey = signer.pubkey();
Ok((Some(signer), Some(signer_pubkey)))
} else {
value_of(matches, name).map(sol_to_lamports)
Ok((None, None))
}
}
pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> {
value_of(matches, name).map(sol_to_lamports)
}
pub fn derivation_of(matches: &ArgMatches<'_>, name: &str) -> Option<DerivationPath> {
matches.value_of(name).map(|derivation_str| {
let derivation_str = derivation_str.replace("'", "");
let mut parts = derivation_str.split('/');
let account = parts.next().map(|account| account.parse::<u32>().unwrap());
let change = parts.next().map(|change| change.parse::<u32>().unwrap());
DerivationPath { account, change }
})
}
#[cfg(test)]
mod tests {
use super::*;
@ -258,23 +283,56 @@ mod tests {
}
#[test]
fn test_amount_of() {
fn test_lamports_of_sol() {
let matches = app()
.clone()
.get_matches_from(vec!["test", "--single", "50", "--unit", "lamports"]);
assert_eq!(amount_of(&matches, "single", "unit"), Some(50));
assert_eq!(amount_of(&matches, "multiple", "unit"), None);
.get_matches_from(vec!["test", "--single", "50"]);
assert_eq!(lamports_of_sol(&matches, "single"), Some(50000000000));
assert_eq!(lamports_of_sol(&matches, "multiple"), None);
let matches = app()
.clone()
.get_matches_from(vec!["test", "--single", "50", "--unit", "SOL"]);
assert_eq!(amount_of(&matches, "single", "unit"), Some(50000000000));
.get_matches_from(vec!["test", "--single", "1.5"]);
assert_eq!(lamports_of_sol(&matches, "single"), Some(1500000000));
assert_eq!(lamports_of_sol(&matches, "multiple"), None);
let matches = app()
.clone()
.get_matches_from(vec!["test", "--single", "1.5", "--unit", "SOL"]);
assert_eq!(amount_of(&matches, "single", "unit"), Some(1500000000));
.get_matches_from(vec!["test", "--single", "0.03"]);
assert_eq!(lamports_of_sol(&matches, "single"), Some(30000000));
}
#[test]
fn test_derivation_of() {
let matches = app()
.clone()
.get_matches_from(vec!["test", "--single", "1.5", "--unit", "lamports"]);
assert_eq!(amount_of(&matches, "single", "unit"), None);
.get_matches_from(vec!["test", "--single", "2/3"]);
assert_eq!(
derivation_of(&matches, "single"),
Some(DerivationPath {
account: Some(2),
change: Some(3)
})
);
assert_eq!(derivation_of(&matches, "another"), None);
let matches = app()
.clone()
.get_matches_from(vec!["test", "--single", "2"]);
assert_eq!(
derivation_of(&matches, "single"),
Some(DerivationPath {
account: Some(2),
change: None
})
);
assert_eq!(derivation_of(&matches, "another"), None);
let matches = app()
.clone()
.get_matches_from(vec!["test", "--single", "2'/3'"]);
assert_eq!(
derivation_of(&matches, "single"),
Some(DerivationPath {
account: Some(2),
change: Some(3)
})
);
}
}

View File

@ -1,8 +1,10 @@
use crate::keypair::ASK_KEYWORD;
use crate::keypair::{parse_keypair_path, KeypairUrl, ASK_KEYWORD};
use chrono::DateTime;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{read_keypair_file, Signature};
use solana_sdk::{
hash::Hash,
pubkey::Pubkey,
signature::{read_keypair_file, Signature},
};
use std::str::FromStr;
// Return an error if a pubkey cannot be parsed.
@ -48,6 +50,13 @@ pub fn is_pubkey_or_keypair_or_ask_keyword(string: String) -> Result<(), String>
is_pubkey(string.clone()).or_else(|_| is_keypair_or_ask_keyword(string))
}
pub fn is_valid_signer(string: String) -> Result<(), String> {
match parse_keypair_path(&string) {
KeypairUrl::Filepath(path) => is_keypair(path),
_ => Ok(()),
}
}
// 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('=');
@ -84,20 +93,6 @@ pub fn is_url(string: String) -> Result<(), String> {
}
}
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(|_| ())
@ -141,3 +136,48 @@ pub fn is_rfc3339_datetime(value: String) -> Result<(), String> {
.map(|_| ())
.map_err(|e| format!("{:?}", e))
}
pub fn is_derivation(value: String) -> Result<(), String> {
let value = value.replace("'", "");
let mut parts = value.split('/');
let account = parts.next().unwrap();
account
.parse::<u32>()
.map_err(|e| {
format!(
"Unable to parse derivation, provided: {}, err: {:?}",
account, e
)
})
.and_then(|_| {
if let Some(change) = parts.next() {
change.parse::<u32>().map_err(|e| {
format!(
"Unable to parse derivation, provided: {}, err: {:?}",
change, e
)
})
} else {
Ok(0)
}
})
.map(|_| ())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_derivation() {
assert_eq!(is_derivation("2".to_string()), Ok(()));
assert_eq!(is_derivation("0".to_string()), Ok(()));
assert_eq!(is_derivation("65537".to_string()), Ok(()));
assert_eq!(is_derivation("0/2".to_string()), Ok(()));
assert_eq!(is_derivation("0'/2'".to_string()), Ok(()));
assert!(is_derivation("a".to_string()).is_err());
assert!(is_derivation("4294967296".to_string()).is_err());
assert!(is_derivation("a/b".to_string()).is_err());
assert!(is_derivation("0/4294967296".to_string()).is_err());
}
}

View File

@ -1,20 +1,114 @@
use crate::ArgConstant;
use crate::{
input_parsers::{derivation_of, pubkeys_sigs_of},
offline::SIGNER_ARG,
ArgConstant,
};
use bip39::{Language, Mnemonic, Seed};
use clap::values_t;
use clap::{values_t, ArgMatches, Error, ErrorKind};
use rpassword::prompt_password_stderr;
use solana_remote_wallet::{
remote_keypair::generate_remote_keypair,
remote_wallet::{RemoteWalletError, RemoteWalletManager},
};
use solana_sdk::{
pubkey::Pubkey,
signature::{
keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair_file, Keypair,
KeypairUtil,
keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair,
read_keypair_file, Keypair, Presigner, Signature, Signer,
},
};
use std::{
error,
io::{stdin, stdout, Write},
process::exit,
str::FromStr,
sync::Arc,
};
pub enum KeypairUrl {
Ask,
Filepath(String),
Usb(String),
Stdin,
Pubkey(Pubkey),
}
pub fn parse_keypair_path(path: &str) -> KeypairUrl {
if path == "-" {
KeypairUrl::Stdin
} else if path == ASK_KEYWORD {
KeypairUrl::Ask
} else if path.starts_with("usb://") {
KeypairUrl::Usb(path.to_string())
} else if let Ok(pubkey) = Pubkey::from_str(path) {
KeypairUrl::Pubkey(pubkey)
} else {
KeypairUrl::Filepath(path.to_string())
}
}
pub fn presigner_from_pubkey_sigs(
pubkey: &Pubkey,
signers: &[(Pubkey, Signature)],
) -> Option<Presigner> {
signers.iter().find_map(|(signer, sig)| {
if *signer == *pubkey {
Some(Presigner::new(signer, sig))
} else {
None
}
})
}
pub fn signer_from_path(
matches: &ArgMatches,
path: &str,
keypair_name: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<Box<dyn Signer>, Box<dyn error::Error>> {
match parse_keypair_path(path) {
KeypairUrl::Ask => {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
Ok(Box::new(keypair_from_seed_phrase(
keypair_name,
skip_validation,
false,
)?))
}
KeypairUrl::Filepath(path) => Ok(Box::new(read_keypair_file(&path)?)),
KeypairUrl::Stdin => {
let mut stdin = std::io::stdin();
Ok(Box::new(read_keypair(&mut stdin)?))
}
KeypairUrl::Usb(path) => {
if let Some(wallet_manager) = wallet_manager {
Ok(Box::new(generate_remote_keypair(
path,
derivation_of(matches, "derivation_path"),
wallet_manager,
matches.is_present("confirm_key"),
)?))
} else {
Err(RemoteWalletError::NoDeviceFound.into())
}
}
KeypairUrl::Pubkey(pubkey) => {
let presigner = pubkeys_sigs_of(matches, SIGNER_ARG.name)
.as_ref()
.and_then(|presigners| presigner_from_pubkey_sigs(&pubkey, presigners));
if let Some(presigner) = presigner {
Ok(Box::new(presigner))
} else {
Err(Error::with_description(
"Missing signature for supplied pubkey",
ErrorKind::MissingRequiredArgument,
)
.into())
}
}
}
}
// Keyword used to indicate that the user should be asked for a keypair seed phrase
pub const ASK_KEYWORD: &str = "ASK";
@ -32,8 +126,8 @@ pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant {
#[derive(Debug, PartialEq)]
pub enum Source {
File,
Generated,
Path,
SeedPhrase,
}
@ -87,7 +181,7 @@ pub fn keypair_from_seed_phrase(
};
if confirm_pubkey {
let pubkey = Pubkey::new(keypair.public.as_ref());
let pubkey = keypair.pubkey();
print!("Recovered pubkey `{:?}`. Continue? (y/n): ", pubkey);
let _ignored = stdout().flush();
let mut input = String::new();
@ -131,7 +225,12 @@ pub fn keypair_input(
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))
if keypair_file.starts_with("usb://") {
Ok(KeypairWithSource::new(Keypair::new(), Source::Path))
} else {
read_keypair_file(keypair_file)
.map(|keypair| KeypairWithSource::new(keypair, Source::Path))
}
} else {
Ok(KeypairWithSource::new(Keypair::new(), Source::Generated))
}

View File

@ -26,3 +26,4 @@ pub struct ArgConstant<'a> {
pub mod input_parsers;
pub mod input_validators;
pub mod keypair;
pub mod offline;

19
clap-utils/src/offline.rs Normal file
View File

@ -0,0 +1,19 @@
use crate::ArgConstant;
pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant {
name: "blockhash",
long: "blockhash",
help: "Use the supplied blockhash",
};
pub const SIGN_ONLY_ARG: ArgConstant<'static> = ArgConstant {
name: "sign_only",
long: "sign-only",
help: "Sign the transaction offline",
};
pub const SIGNER_ARG: ArgConstant<'static> = ArgConstant {
name: "signer",
long: "signer",
help: "Provide a public-key/signature pair for the transaction",
};

View File

@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-cli-config"
description = "Blockchain, Rebuilt for Scale"
version = "0.23.2"
version = "0.23.7"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"

View File

@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-cli"
description = "Blockchain, Rebuilt for Scale"
version = "0.23.2"
version = "0.23.7"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -26,26 +26,27 @@ reqwest = { version = "0.10.1", default-features = false, features = ["blocking"
serde = "1.0.104"
serde_derive = "1.0.103"
serde_json = "1.0.44"
serde_yaml = "0.8.11"
solana-budget-program = { path = "../programs/budget", version = "0.23.2" }
solana-clap-utils = { path = "../clap-utils", version = "0.23.2" }
solana-cli-config = { path = "../cli-config", version = "0.23.2" }
solana-client = { path = "../client", version = "0.23.2" }
solana-config-program = { path = "../programs/config", version = "0.23.2" }
solana-faucet = { path = "../faucet", version = "0.23.2" }
solana-logger = { path = "../logger", version = "0.23.2" }
solana-net-utils = { path = "../net-utils", version = "0.23.2" }
solana-runtime = { path = "../runtime", version = "0.23.2" }
solana-sdk = { path = "../sdk", version = "0.23.2" }
solana-stake-program = { path = "../programs/stake", version = "0.23.2" }
solana-storage-program = { path = "../programs/storage", version = "0.23.2" }
solana-vote-program = { path = "../programs/vote", version = "0.23.2" }
solana-vote-signer = { path = "../vote-signer", version = "0.23.2" }
solana-budget-program = { path = "../programs/budget", version = "0.23.7" }
solana-clap-utils = { path = "../clap-utils", version = "0.23.7" }
solana-cli-config = { path = "../cli-config", version = "0.23.7" }
solana-client = { path = "../client", version = "0.23.7" }
solana-config-program = { path = "../programs/config", version = "0.23.7" }
solana-faucet = { path = "../faucet", version = "0.23.7" }
solana-logger = { path = "../logger", version = "0.23.7" }
solana-net-utils = { path = "../net-utils", version = "0.23.7" }
solana-remote-wallet = { path = "../remote-wallet", version = "0.23.7" }
solana-runtime = { path = "../runtime", version = "0.23.7" }
solana-sdk = { path = "../sdk", version = "0.23.7" }
solana-stake-program = { path = "../programs/stake", version = "0.23.7" }
solana-storage-program = { path = "../programs/storage", version = "0.23.7" }
solana-vote-program = { path = "../programs/vote", version = "0.23.7" }
solana-vote-signer = { path = "../vote-signer", version = "0.23.7" }
titlecase = "1.1.0"
url = "2.1.1"
[dev-dependencies]
solana-core = { path = "../core", version = "0.23.2" }
solana-budget-program = { path = "../programs/budget", version = "0.23.2" }
solana-core = { path = "../core", version = "0.23.7" }
solana-budget-program = { path = "../programs/budget", version = "0.23.7" }
tempfile = "3.1.0"
[[bin]]

File diff suppressed because it is too large Load Diff

View File

@ -8,21 +8,25 @@ use crate::{
use clap::{value_t, 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 solana_clap_utils::{input_parsers::*, input_validators::*, keypair::signer_from_path};
use solana_client::{rpc_client::RpcClient, rpc_response::RpcVoteAccountInfo};
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account_utils::StateMut,
clock::{self, Slot},
commitment_config::CommitmentConfig,
epoch_schedule::Epoch,
hash::Hash,
message::Message,
pubkey::Pubkey,
signature::{Keypair, KeypairUtil},
system_transaction,
signature::{Keypair, Signer},
system_instruction,
transaction::Transaction,
};
use std::{
collections::{HashMap, VecDeque},
net::SocketAddr,
sync::Arc,
thread::sleep,
time::{Duration, Instant},
};
@ -216,11 +220,15 @@ pub fn parse_catchup(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliErro
let node_pubkey = pubkey_of(matches, "node_pubkey").unwrap();
Ok(CliCommandInfo {
command: CliCommand::Catchup { node_pubkey },
require_keypair: false,
signers: vec![],
})
}
pub fn parse_cluster_ping(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
pub fn parse_cluster_ping(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> 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") {
@ -242,7 +250,12 @@ pub fn parse_cluster_ping(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, Cl
timeout,
commitment_config,
},
require_keypair: true,
signers: vec![signer_from_path(
matches,
default_signer_path,
"keypair",
wallet_manager,
)?],
})
}
@ -250,7 +263,7 @@ pub fn parse_get_block_time(matches: &ArgMatches<'_>) -> Result<CliCommandInfo,
let slot = value_t_or_exit!(matches, "slot", u64);
Ok(CliCommandInfo {
command: CliCommand::GetBlockTime { slot },
require_keypair: false,
signers: vec![],
})
}
@ -262,7 +275,7 @@ pub fn parse_get_epoch_info(matches: &ArgMatches<'_>) -> Result<CliCommandInfo,
};
Ok(CliCommandInfo {
command: CliCommand::GetEpochInfo { commitment_config },
require_keypair: false,
signers: vec![],
})
}
@ -274,7 +287,7 @@ pub fn parse_get_slot(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliErr
};
Ok(CliCommandInfo {
command: CliCommand::GetSlot { commitment_config },
require_keypair: false,
signers: vec![],
})
}
@ -286,7 +299,7 @@ pub fn parse_get_transaction_count(matches: &ArgMatches<'_>) -> Result<CliComman
};
Ok(CliCommandInfo {
command: CliCommand::GetTransactionCount { commitment_config },
require_keypair: false,
signers: vec![],
})
}
@ -299,7 +312,7 @@ pub fn parse_show_stakes(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, Cli
use_lamports_unit,
vote_account_pubkeys,
},
require_keypair: false,
signers: vec![],
})
}
@ -308,7 +321,7 @@ pub fn parse_show_validators(matches: &ArgMatches<'_>) -> Result<CliCommandInfo,
Ok(CliCommandInfo {
command: CliCommand::ShowValidators { use_lamports_unit },
require_keypair: false,
signers: vec![],
})
}
@ -451,11 +464,11 @@ pub fn process_get_epoch_info(
let start_slot = epoch_info.absolute_slot - epoch_info.slot_index;
let end_slot = start_slot + epoch_info.slots_in_epoch;
println_name_value(
"Epoch slot range:",
"Epoch Slot Range:",
&format!("[{}..{})", start_slot, end_slot),
);
println_name_value(
"Epoch completed percent:",
"Epoch Completed Percent:",
&format!(
"{:>3.3}%",
epoch_info.slot_index as f64 / epoch_info.slots_in_epoch as f64 * 100_f64
@ -463,14 +476,14 @@ pub fn process_get_epoch_info(
);
let remaining_slots_in_epoch = epoch_info.slots_in_epoch - epoch_info.slot_index;
println_name_value(
"Epoch completed slots:",
"Epoch Completed Slots:",
&format!(
"{}/{} ({} remaining)",
epoch_info.slot_index, epoch_info.slots_in_epoch, remaining_slots_in_epoch
),
);
println_name_value(
"Epoch completed time:",
"Epoch Completed Time:",
&format!(
"{}/{} ({} remaining)",
slot_to_human_time(epoch_info.slot_index),
@ -500,7 +513,7 @@ pub fn parse_show_block_production(matches: &ArgMatches<'_>) -> Result<CliComman
Ok(CliCommandInfo {
command: CliCommand::ShowBlockProduction { epoch, slot_limit },
require_keypair: false,
signers: vec![],
})
}
@ -695,8 +708,8 @@ pub fn process_ping(
) -> ProcessResult {
let to = Keypair::new().pubkey();
println_name_value("Source account:", &config.keypair.pubkey().to_string());
println_name_value("Destination account:", &to.to_string());
println_name_value("Source Account:", &config.signers[0].pubkey().to_string());
println_name_value("Destination Account:", &to.to_string());
println!();
let (signal_sender, signal_receiver) = std::sync::mpsc::channel();
@ -714,11 +727,13 @@ 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 ix = system_instruction::transfer(&config.signers[0].pubkey(), &to, lamports);
let message = Message::new(vec![ix]);
let mut transaction = Transaction::new_unsigned(message);
transaction.try_sign(&config.signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&fee_calculator,
&transaction.message,
)?;
@ -1015,28 +1030,38 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
mod tests {
use super::*;
use crate::cli::{app, parse_command};
use solana_sdk::signature::{write_keypair, 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 default_keypair = Keypair::new();
let (default_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
let test_cluster_version = test_commands
.clone()
.get_matches_from(vec!["test", "cluster-version"]);
assert_eq!(
parse_command(&test_cluster_version).unwrap(),
parse_command(&test_cluster_version, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::ClusterVersion,
require_keypair: false
signers: vec![],
}
);
let test_fees = test_commands.clone().get_matches_from(vec!["test", "fees"]);
assert_eq!(
parse_command(&test_fees).unwrap(),
parse_command(&test_fees, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::Fees,
require_keypair: false
signers: vec![],
}
);
@ -1046,10 +1071,10 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "block-time", &slot.to_string()]);
assert_eq!(
parse_command(&test_get_block_time).unwrap(),
parse_command(&test_get_block_time, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::GetBlockTime { slot },
require_keypair: false
signers: vec![],
}
);
@ -1057,12 +1082,12 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "epoch-info"]);
assert_eq!(
parse_command(&test_get_epoch_info).unwrap(),
parse_command(&test_get_epoch_info, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::GetEpochInfo {
commitment_config: CommitmentConfig::recent(),
},
require_keypair: false
signers: vec![],
}
);
@ -1070,21 +1095,21 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "genesis-hash"]);
assert_eq!(
parse_command(&test_get_genesis_hash).unwrap(),
parse_command(&test_get_genesis_hash, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::GetGenesisHash,
require_keypair: false
signers: vec![],
}
);
let test_get_slot = test_commands.clone().get_matches_from(vec!["test", "slot"]);
assert_eq!(
parse_command(&test_get_slot).unwrap(),
parse_command(&test_get_slot, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::GetSlot {
commitment_config: CommitmentConfig::recent(),
},
require_keypair: false
signers: vec![],
}
);
@ -1092,12 +1117,12 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "transaction-count"]);
assert_eq!(
parse_command(&test_transaction_count).unwrap(),
parse_command(&test_transaction_count, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::GetTransactionCount {
commitment_config: CommitmentConfig::recent(),
},
require_keypair: false
signers: vec![],
}
);
@ -1113,7 +1138,7 @@ mod tests {
"--confirmed",
]);
assert_eq!(
parse_command(&test_ping).unwrap(),
parse_command(&test_ping, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::Ping {
lamports: 1,
@ -1122,7 +1147,7 @@ mod tests {
timeout: Duration::from_secs(3),
commitment_config: CommitmentConfig::default(),
},
require_keypair: true
signers: vec![default_keypair.into()],
}
);
}

View File

@ -2,20 +2,17 @@ use clap::{crate_description, crate_name, AppSettings, Arg, ArgGroup, ArgMatches
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,
},
input_parsers::derivation_of,
input_validators::{is_derivation, is_url},
keypair::SKIP_SEED_PHRASE_VALIDATION_ARG,
};
use solana_cli::{
cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliError},
cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners},
display::{println_name_value, println_name_value_or},
};
use solana_cli_config::config::{Config, CONFIG_FILE};
use solana_sdk::signature::read_keypair_file;
use std::error;
use solana_remote_wallet::remote_wallet::{maybe_wallet_manager, RemoteWalletManager};
use std::{error, sync::Arc};
fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error>> {
let parse_args = match matches.subcommand() {
@ -24,21 +21,25 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
if let Some(config_file) = matches.value_of("config_file") {
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()),
let (field_name, value, default_value) = match field {
"url" => ("RPC URL", config.url, CliConfig::default_json_rpc_url()),
"keypair" => (
"Key Path",
config.keypair_path,
CliConfig::default_keypair_path(),
),
_ => unreachable!(),
};
println_name_value_or(&format!("* {}:", field), &value, &default_value);
println_name_value_or(&format!("{}:", field_name), &value, &default_value);
} else {
println_name_value("Wallet Config:", config_file);
println_name_value("Config File:", config_file);
println_name_value_or(
"* url:",
"RPC URL:",
&config.url,
&CliConfig::default_json_rpc_url(),
);
println_name_value_or(
"* keypair:",
"Keypair Path:",
&config.keypair_path,
&CliConfig::default_keypair_path(),
);
@ -61,9 +62,9 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
config.keypair_path = 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("Config File:", config_file);
println_name_value("RPC URL:", &config.url);
println_name_value("Keypair Path:", &config.keypair_path);
} else {
println!(
"{} Either provide the `--config` arg or ensure home directory exists to use the default config location",
@ -79,7 +80,10 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
Ok(parse_args)
}
pub fn parse_args(matches: &ArgMatches<'_>) -> Result<CliConfig, Box<dyn error::Error>> {
pub fn parse_args<'a>(
matches: &ArgMatches<'_>,
wallet_manager: Option<Arc<RemoteWalletManager>>,
) -> Result<(CliConfig<'a>, CliSigners), Box<dyn error::Error>> {
let config = if let Some(config_file) = matches.value_of("config_file") {
Config::load(config_file).unwrap_or_default()
} else {
@ -94,57 +98,29 @@ pub fn parse_args(matches: &ArgMatches<'_>) -> Result<CliConfig, Box<dyn error::
default.json_rpc_url
};
let CliCommandInfo {
command,
require_keypair,
} = 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(format!(
"Generate a new keypair at {} with `solana-keygen new`",
default_keypair_path
))
.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 default_signer_path = if matches.is_present("keypair") {
matches.value_of("keypair").unwrap().to_string()
} else if config.keypair_path != "" {
config.keypair_path
} else {
let default = CliConfig::default();
(default.keypair, None)
CliConfig::default_keypair_path()
};
Ok(CliConfig {
command,
json_rpc_url,
keypair,
keypair_path,
rpc_client: None,
verbose: matches.is_present("verbose"),
})
let CliCommandInfo { command, signers } =
parse_command(&matches, &default_signer_path, wallet_manager.as_ref())?;
Ok((
CliConfig {
command,
json_rpc_url,
signers: vec![],
keypair_path: default_signer_path,
derivation_path: derivation_of(matches, "derivation_path"),
rpc_client: None,
verbose: matches.is_present("verbose"),
},
signers,
))
}
fn main() -> Result<(), Box<dyn error::Error>> {
@ -185,7 +161,16 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.value_name("PATH")
.global(true)
.takes_value(true)
.help("/path/to/id.json"),
.help("/path/to/id.json or usb://remote/wallet/path"),
)
.arg(
Arg::with_name("derivation_path")
.long("derivation-path")
.value_name("ACCOUNT or ACCOUNT/CHANGE")
.global(true)
.takes_value(true)
.validator(is_derivation)
.help("Derivation path to use: m/44'/501'/ACCOUNT'/CHANGE'; default key is device base pubkey: m/44'/501'/0'")
)
.arg(
Arg::with_name("verbose")
@ -194,15 +179,6 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.global(true)
.help("Show extra 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)
@ -240,7 +216,10 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.get_matches();
if parse_settings(&matches)? {
let config = parse_args(&matches)?;
let wallet_manager = maybe_wallet_manager()?;
let (mut config, signers) = parse_args(&matches, wallet_manager)?;
config.signers = signers.iter().map(|s| s.as_ref()).collect();
let result = process_command(&config)?;
println!("{}", result);
}

View File

@ -1,19 +1,21 @@
use crate::cli::{
build_balance_message, check_account_for_fee, check_unique_pubkeys,
log_instruction_custom_error, required_lamports_from, CliCommand, CliCommandInfo, CliConfig,
CliError, ProcessResult, SigningAuthority,
build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
SignerIndex,
};
use crate::offline::BLOCKHASH_ARG;
use clap::{App, Arg, ArgMatches, SubCommand};
use solana_clap_utils::{input_parsers::*, input_validators::*, ArgConstant};
use solana_clap_utils::{
input_parsers::*, input_validators::*, offline::BLOCKHASH_ARG, ArgConstant,
};
use solana_client::rpc_client::RpcClient;
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account::Account,
account_utils::StateMut,
hash::Hash,
message::Message,
nonce_state::{Meta, NonceState},
pubkey::Pubkey,
signature::{Keypair, KeypairUtil},
system_instruction::{
advance_nonce_account, authorize_nonce_account, create_address_with_seed,
create_nonce_account, create_nonce_account_with_seed, withdraw_nonce_account, NonceError,
@ -22,6 +24,7 @@ use solana_sdk::{
system_program,
transaction::Transaction,
};
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq)]
pub enum CliNonceError {
@ -65,8 +68,8 @@ pub fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(NONCE_AUTHORITY_ARG.name)
.long(NONCE_AUTHORITY_ARG.long)
.takes_value(true)
.value_name("KEYPAIR or PUBKEY")
.validator(is_pubkey_or_keypair_or_ask_keyword)
.value_name("KEYPAIR or PUBKEY or REMOTE WALLET PATH")
.validator(is_valid_signer)
.help(NONCE_AUTHORITY_ARG.help)
}
@ -121,15 +124,7 @@ impl NonceSubCommands for App<'_, '_> {
.takes_value(true)
.required(true)
.validator(is_amount)
.help("The amount to load the nonce account with (default unit SOL)"),
)
.arg(
Arg::with_name("unit")
.index(3)
.value_name("UNIT")
.takes_value(true)
.possible_values(&["SOL", "lamports"])
.help("Specify unit to use for request"),
.help("The amount to load the nonce account with, in SOL"),
)
.arg(
Arg::with_name(NONCE_AUTHORITY_ARG.name)
@ -216,51 +211,68 @@ impl NonceSubCommands for App<'_, '_> {
.takes_value(true)
.required(true)
.validator(is_amount)
.help("The amount to withdraw from the nonce account (default unit SOL)"),
)
.arg(
Arg::with_name("unit")
.index(4)
.value_name("UNIT")
.takes_value(true)
.possible_values(&["SOL", "lamports"])
.help("Specify unit to use for request"),
.help("The amount to withdraw from the nonce account, in SOL"),
)
.arg(nonce_authority_arg()),
)
}
}
pub fn parse_authorize_nonce_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
pub fn parse_authorize_nonce_account(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap();
let new_authority = pubkey_of(matches, "new_authority").unwrap();
let nonce_authority =
SigningAuthority::new_from_matches(&matches, NONCE_AUTHORITY_ARG.name, None)?;
let (nonce_authority, nonce_authority_pubkey) =
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
vec![payer_provided, nonce_authority],
matches,
default_signer_path,
wallet_manager,
)?;
Ok(CliCommandInfo {
command: CliCommand::AuthorizeNonceAccount {
nonce_account,
nonce_authority,
nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
new_authority,
},
require_keypair: true,
signers: signer_info.signers,
})
}
pub fn parse_nonce_create_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let nonce_account = keypair_of(matches, "nonce_account_keypair").unwrap();
pub fn parse_nonce_create_account(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let (nonce_account, nonce_account_pubkey) =
signer_of(matches, "nonce_account_keypair", wallet_manager)?;
let seed = matches.value_of("seed").map(|s| s.to_string());
let lamports = required_lamports_from(matches, "amount", "unit")?;
let lamports = lamports_of_sol(matches, "amount").unwrap();
let nonce_authority = pubkey_of(matches, NONCE_AUTHORITY_ARG.name);
let payer_provided = None;
let signer_info = generate_unique_signers(
vec![payer_provided, nonce_account],
matches,
default_signer_path,
wallet_manager,
)?;
Ok(CliCommandInfo {
command: CliCommand::CreateNonceAccount {
nonce_account: nonce_account.into(),
nonce_account: signer_info.index_of(nonce_account_pubkey).unwrap(),
seed,
nonce_authority,
lamports,
},
require_keypair: true,
signers: signer_info.signers,
})
}
@ -269,21 +281,33 @@ pub fn parse_get_nonce(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliEr
Ok(CliCommandInfo {
command: CliCommand::GetNonce(nonce_account_pubkey),
require_keypair: false,
signers: vec![],
})
}
pub fn parse_new_nonce(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
pub fn parse_new_nonce(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap();
let nonce_authority =
SigningAuthority::new_from_matches(&matches, NONCE_AUTHORITY_ARG.name, None)?;
let (nonce_authority, nonce_authority_pubkey) =
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
vec![payer_provided, nonce_authority],
matches,
default_signer_path,
wallet_manager,
)?;
Ok(CliCommandInfo {
command: CliCommand::NewNonce {
nonce_account,
nonce_authority,
nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
},
require_keypair: true,
signers: signer_info.signers,
})
}
@ -296,27 +320,37 @@ pub fn parse_show_nonce_account(matches: &ArgMatches<'_>) -> Result<CliCommandIn
nonce_account_pubkey,
use_lamports_unit,
},
require_keypair: false,
signers: vec![],
})
}
pub fn parse_withdraw_from_nonce_account(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap();
let destination_account_pubkey = pubkey_of(matches, "destination_account_pubkey").unwrap();
let lamports = required_lamports_from(matches, "amount", "unit")?;
let nonce_authority =
SigningAuthority::new_from_matches(&matches, NONCE_AUTHORITY_ARG.name, None)?;
let lamports = lamports_of_sol(matches, "amount").unwrap();
let (nonce_authority, nonce_authority_pubkey) =
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
vec![payer_provided, nonce_authority],
matches,
default_signer_path,
wallet_manager,
)?;
Ok(CliCommandInfo {
command: CliCommand::WithdrawFromNonceAccount {
nonce_account,
nonce_authority,
nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
destination_account_pubkey,
lamports,
},
require_keypair: true,
signers: signer_info.signers,
})
}
@ -352,41 +386,36 @@ pub fn process_authorize_nonce_account(
rpc_client: &RpcClient,
config: &CliConfig,
nonce_account: &Pubkey,
nonce_authority: Option<&SigningAuthority>,
nonce_authority: SignerIndex,
new_authority: &Pubkey,
) -> ProcessResult {
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let nonce_authority = nonce_authority
.map(|a| a.keypair())
.unwrap_or(&config.keypair);
let nonce_authority = config.signers[nonce_authority];
let ix = authorize_nonce_account(nonce_account, &nonce_authority.pubkey(), new_authority);
let mut tx = Transaction::new_signed_with_payer(
vec![ix],
Some(&config.keypair.pubkey()),
&[&config.keypair, nonce_authority],
recent_blockhash,
);
let message = Message::new_with_payer(vec![ix], Some(&config.signers[0].pubkey()));
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&config.signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&fee_calculator,
&tx.message,
)?;
let result =
rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, nonce_authority]);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
log_instruction_custom_error::<NonceError>(result)
}
pub fn process_create_nonce_account(
rpc_client: &RpcClient,
config: &CliConfig,
nonce_account: &Keypair,
nonce_account: SignerIndex,
seed: Option<String>,
nonce_authority: Option<Pubkey>,
lamports: u64,
) -> ProcessResult {
let nonce_account_pubkey = nonce_account.pubkey();
let nonce_account_pubkey = config.signers[nonce_account].pubkey();
let nonce_account_address = if let Some(seed) = seed.clone() {
create_address_with_seed(&nonce_account_pubkey, &seed, &system_program::id())?
} else {
@ -394,7 +423,7 @@ pub fn process_create_nonce_account(
};
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
(&config.signers[0].pubkey(), "cli keypair".to_string()),
(&nonce_account_address, "nonce_account".to_string()),
)?;
@ -421,20 +450,20 @@ pub fn process_create_nonce_account(
.into());
}
let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.pubkey());
let nonce_authority = nonce_authority.unwrap_or_else(|| config.signers[0].pubkey());
let ixs = if let Some(seed) = seed {
create_nonce_account_with_seed(
&config.keypair.pubkey(), // from
&nonce_account_address, // to
&nonce_account_pubkey, // base
&seed, // seed
&config.signers[0].pubkey(), // from
&nonce_account_address, // to
&nonce_account_pubkey, // base
&seed, // seed
&nonce_authority,
lamports,
)
} else {
create_nonce_account(
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&nonce_account_pubkey,
&nonce_authority,
lamports,
@ -443,25 +472,17 @@ pub fn process_create_nonce_account(
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let signers = if nonce_account_pubkey != config.keypair.pubkey() {
vec![&config.keypair, nonce_account] // both must sign if `from` and `to` differ
} else {
vec![&config.keypair] // when stake_account == config.keypair and there's a seed, we only need one signature
};
let message = Message::new_with_payer(ixs, Some(&config.signers[0].pubkey()));
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&config.signers, recent_blockhash)?;
let mut tx = Transaction::new_signed_with_payer(
ixs,
Some(&config.keypair.pubkey()),
&signers,
recent_blockhash,
);
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&fee_calculator,
&tx.message,
)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &signers);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
log_instruction_custom_error::<SystemError>(result)
}
@ -489,10 +510,10 @@ pub fn process_new_nonce(
rpc_client: &RpcClient,
config: &CliConfig,
nonce_account: &Pubkey,
nonce_authority: Option<&SigningAuthority>,
nonce_authority: SignerIndex,
) -> ProcessResult {
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
(&config.signers[0].pubkey(), "cli keypair".to_string()),
(&nonce_account, "nonce_account_pubkey".to_string()),
)?;
@ -503,25 +524,20 @@ pub fn process_new_nonce(
.into());
}
let nonce_authority = nonce_authority
.map(|a| a.keypair())
.unwrap_or(&config.keypair);
let nonce_authority = config.signers[nonce_authority];
let ix = advance_nonce_account(&nonce_account, &nonce_authority.pubkey());
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let mut tx = Transaction::new_signed_with_payer(
vec![ix],
Some(&config.keypair.pubkey()),
&[&config.keypair, nonce_authority],
recent_blockhash,
);
let message = Message::new_with_payer(vec![ix], Some(&config.signers[0].pubkey()));
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&config.signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&fee_calculator,
&tx.message,
)?;
let result =
rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, nonce_authority]);
rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0], nonce_authority]);
log_instruction_custom_error::<SystemError>(result)
}
@ -540,11 +556,11 @@ pub fn process_show_nonce_account(
}
let print_account = |data: Option<(Meta, Hash)>| {
println!(
"balance: {}",
"Balance: {}",
build_balance_message(nonce_account.lamports, use_lamports_unit, true)
);
println!(
"minimum balance required: {}",
"Minimum Balance Required: {}",
build_balance_message(
rpc_client.get_minimum_balance_for_rent_exemption(NonceState::size())?,
use_lamports_unit,
@ -553,12 +569,12 @@ pub fn process_show_nonce_account(
);
match data {
Some((meta, hash)) => {
println!("nonce: {}", hash);
println!("authority: {}", meta.nonce_authority);
println!("Nonce: {}", hash);
println!("Authority: {}", meta.nonce_authority);
}
None => {
println!("nonce: uninitialized");
println!("authority: uninitialized");
println!("Nonce: uninitialized");
println!("Authority: uninitialized");
}
}
Ok("".to_string())
@ -578,35 +594,29 @@ pub fn process_withdraw_from_nonce_account(
rpc_client: &RpcClient,
config: &CliConfig,
nonce_account: &Pubkey,
nonce_authority: Option<&SigningAuthority>,
nonce_authority: SignerIndex,
destination_account_pubkey: &Pubkey,
lamports: u64,
) -> ProcessResult {
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let nonce_authority = nonce_authority
.map(|a| a.keypair())
.unwrap_or(&config.keypair);
let nonce_authority = config.signers[nonce_authority];
let ix = withdraw_nonce_account(
nonce_account,
&nonce_authority.pubkey(),
destination_account_pubkey,
lamports,
);
let mut tx = Transaction::new_signed_with_payer(
vec![ix],
Some(&config.keypair.pubkey()),
&[&config.keypair, nonce_authority],
recent_blockhash,
);
let message = Message::new_with_payer(vec![ix], Some(&config.signers[0].pubkey()));
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&config.signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&fee_calculator,
&tx.message,
)?;
let result =
rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, nonce_authority]);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
log_instruction_custom_error::<NonceError>(result)
}
@ -618,7 +628,7 @@ mod tests {
account::Account,
hash::hash,
nonce_state::{Meta as NonceMeta, NonceState},
signature::{read_keypair_file, write_keypair},
signature::{read_keypair_file, write_keypair, Keypair, Signer},
system_program,
};
use tempfile::NamedTempFile;
@ -631,6 +641,9 @@ mod tests {
#[test]
fn test_parse_command() {
let test_commands = app("test", "desc", "version");
let default_keypair = Keypair::new();
let (default_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
let (keypair_file, mut tmp_file) = make_tmp_file();
let nonce_account_keypair = Keypair::new();
write_keypair(&nonce_account_keypair, tmp_file.as_file_mut()).unwrap();
@ -649,14 +662,14 @@ mod tests {
&Pubkey::default().to_string(),
]);
assert_eq!(
parse_command(&test_authorize_nonce_account).unwrap(),
parse_command(&test_authorize_nonce_account, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::AuthorizeNonceAccount {
nonce_account: nonce_account_pubkey,
nonce_authority: None,
nonce_authority: 0,
new_authority: Pubkey::default(),
},
require_keypair: true,
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
);
@ -670,16 +683,17 @@ mod tests {
&authority_keypair_file,
]);
assert_eq!(
parse_command(&test_authorize_nonce_account).unwrap(),
parse_command(&test_authorize_nonce_account, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::AuthorizeNonceAccount {
nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
nonce_authority: Some(
read_keypair_file(&authority_keypair_file).unwrap().into()
),
nonce_authority: 1,
new_authority: Pubkey::default(),
},
require_keypair: true,
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
read_keypair_file(&authority_keypair_file).unwrap().into()
],
}
);
@ -689,18 +703,20 @@ mod tests {
"create-nonce-account",
&keypair_file,
"50",
"lamports",
]);
assert_eq!(
parse_command(&test_create_nonce_account).unwrap(),
parse_command(&test_create_nonce_account, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateNonceAccount {
nonce_account: read_keypair_file(&keypair_file).unwrap().into(),
nonce_account: 1,
seed: None,
nonce_authority: None,
lamports: 50,
lamports: 50_000_000_000,
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
read_keypair_file(&keypair_file).unwrap().into()
],
}
);
@ -710,22 +726,22 @@ mod tests {
"create-nonce-account",
&keypair_file,
"50",
"lamports",
"--nonce-authority",
&authority_keypair_file,
]);
assert_eq!(
parse_command(&test_create_nonce_account).unwrap(),
parse_command(&test_create_nonce_account, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateNonceAccount {
nonce_account: read_keypair_file(&keypair_file).unwrap().into(),
nonce_account: 1,
seed: None,
nonce_authority: Some(
read_keypair_file(&authority_keypair_file).unwrap().pubkey()
),
lamports: 50,
nonce_authority: Some(nonce_authority_keypair.pubkey()),
lamports: 50_000_000_000,
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
read_keypair_file(&keypair_file).unwrap().into()
],
}
);
@ -736,10 +752,10 @@ mod tests {
&nonce_account_string,
]);
assert_eq!(
parse_command(&test_get_nonce).unwrap(),
parse_command(&test_get_nonce, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::GetNonce(nonce_account_keypair.pubkey(),),
require_keypair: false
command: CliCommand::GetNonce(nonce_account_keypair.pubkey()),
signers: vec![],
}
);
@ -750,13 +766,13 @@ mod tests {
.get_matches_from(vec!["test", "new-nonce", &keypair_file]);
let nonce_account = read_keypair_file(&keypair_file).unwrap();
assert_eq!(
parse_command(&test_new_nonce).unwrap(),
parse_command(&test_new_nonce, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::NewNonce {
nonce_account: nonce_account.pubkey(),
nonce_authority: None,
nonce_authority: 0,
},
require_keypair: true
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
);
@ -770,15 +786,16 @@ mod tests {
]);
let nonce_account = read_keypair_file(&keypair_file).unwrap();
assert_eq!(
parse_command(&test_new_nonce).unwrap(),
parse_command(&test_new_nonce, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::NewNonce {
nonce_account: nonce_account.pubkey(),
nonce_authority: Some(
read_keypair_file(&authority_keypair_file).unwrap().into()
),
nonce_authority: 1,
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
read_keypair_file(&authority_keypair_file).unwrap().into()
],
}
);
@ -789,13 +806,13 @@ mod tests {
&nonce_account_string,
]);
assert_eq!(
parse_command(&test_show_nonce_account).unwrap(),
parse_command(&test_show_nonce_account, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::ShowNonceAccount {
nonce_account_pubkey: nonce_account_keypair.pubkey(),
use_lamports_unit: false,
},
require_keypair: false
signers: vec![],
}
);
@ -806,18 +823,22 @@ mod tests {
&keypair_file,
&nonce_account_string,
"42",
"lamports",
]);
assert_eq!(
parse_command(&test_withdraw_from_nonce_account).unwrap(),
parse_command(
&test_withdraw_from_nonce_account,
&default_keypair_file,
None
)
.unwrap(),
CliCommandInfo {
command: CliCommand::WithdrawFromNonceAccount {
nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
nonce_authority: None,
nonce_authority: 0,
destination_account_pubkey: nonce_account_pubkey,
lamports: 42
lamports: 42_000_000_000
},
require_keypair: true
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
);
@ -827,18 +848,22 @@ mod tests {
&keypair_file,
&nonce_account_string,
"42",
"SOL",
]);
assert_eq!(
parse_command(&test_withdraw_from_nonce_account).unwrap(),
parse_command(
&test_withdraw_from_nonce_account,
&default_keypair_file,
None
)
.unwrap(),
CliCommandInfo {
command: CliCommand::WithdrawFromNonceAccount {
nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
nonce_authority: None,
nonce_authority: 0,
destination_account_pubkey: nonce_account_pubkey,
lamports: 42000000000
},
require_keypair: true
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
);
@ -849,22 +874,27 @@ mod tests {
&keypair_file,
&nonce_account_string,
"42",
"lamports",
"--nonce-authority",
&authority_keypair_file,
]);
assert_eq!(
parse_command(&test_withdraw_from_nonce_account).unwrap(),
parse_command(
&test_withdraw_from_nonce_account,
&default_keypair_file,
None
)
.unwrap(),
CliCommandInfo {
command: CliCommand::WithdrawFromNonceAccount {
nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
nonce_authority: Some(
read_keypair_file(&authority_keypair_file).unwrap().into()
),
nonce_authority: 1,
destination_account_pubkey: nonce_account_pubkey,
lamports: 42
lamports: 42_000_000_000
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
read_keypair_file(&authority_keypair_file).unwrap().into()
],
}
);
}

View File

@ -3,30 +3,12 @@ use serde_json::Value;
use solana_clap_utils::{
input_parsers::value_of,
input_validators::{is_hash, is_pubkey_sig},
ArgConstant,
offline::{BLOCKHASH_ARG, SIGNER_ARG, SIGN_ONLY_ARG},
};
use solana_client::rpc_client::RpcClient;
use solana_sdk::{fee_calculator::FeeCalculator, hash::Hash, pubkey::Pubkey, signature::Signature};
use std::str::FromStr;
pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant {
name: "blockhash",
long: "blockhash",
help: "Use the supplied blockhash",
};
pub const SIGN_ONLY_ARG: ArgConstant<'static> = ArgConstant {
name: "sign_only",
long: "sign-only",
help: "Sign the transaction offline",
};
pub const SIGNER_ARG: ArgConstant<'static> = ArgConstant {
name: "signer",
long: "signer",
help: "Provid a public-key/signature pair for the transaction",
};
#[derive(Clone, Debug, PartialEq)]
pub enum BlockhashQuery {
None(Hash, FeeCalculator),

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,17 @@
use crate::cli::{
check_account_for_fee, check_unique_pubkeys, log_instruction_custom_error, CliCommand,
CliCommandInfo, CliConfig, CliError, ProcessResult,
CliCommandInfo, CliConfig, CliError, ProcessResult, SignerIndex,
};
use clap::{App, Arg, ArgMatches, SubCommand};
use solana_clap_utils::{input_parsers::*, input_validators::*};
use solana_clap_utils::{input_parsers::*, input_validators::*, keypair::signer_from_path};
use solana_client::rpc_client::RpcClient;
use solana_sdk::signature::Keypair;
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account_utils::StateMut, message::Message, pubkey::Pubkey, signature::KeypairUtil,
system_instruction::SystemError, transaction::Transaction,
account_utils::StateMut, message::Message, pubkey::Pubkey, system_instruction::SystemError,
transaction::Transaction,
};
use solana_storage_program::storage_instruction::{self, StorageAccountType};
use std::sync::Arc;
pub trait StorageSubCommands {
fn storage_subcommands(self) -> Self;
@ -99,35 +100,49 @@ impl StorageSubCommands for App<'_, '_> {
pub fn parse_storage_create_archiver_account(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let account_owner = pubkey_of(matches, "storage_account_owner").unwrap();
let storage_account = keypair_of(matches, "storage_account").unwrap();
Ok(CliCommandInfo {
command: CliCommand::CreateStorageAccount {
account_owner,
storage_account: storage_account.into(),
storage_account: 1,
account_type: StorageAccountType::Archiver,
},
require_keypair: true,
signers: vec![
signer_from_path(matches, default_signer_path, "keypair", wallet_manager)?,
storage_account.into(),
],
})
}
pub fn parse_storage_create_validator_account(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let account_owner = pubkey_of(matches, "storage_account_owner").unwrap();
let storage_account = keypair_of(matches, "storage_account").unwrap();
Ok(CliCommandInfo {
command: CliCommand::CreateStorageAccount {
account_owner,
storage_account: storage_account.into(),
storage_account: 1,
account_type: StorageAccountType::Validator,
},
require_keypair: true,
signers: vec![
signer_from_path(matches, default_signer_path, "keypair", wallet_manager)?,
storage_account.into(),
],
})
}
pub fn parse_storage_claim_reward(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
pub fn parse_storage_claim_reward(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let node_account_pubkey = pubkey_of(matches, "node_account_pubkey").unwrap();
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
Ok(CliCommandInfo {
@ -135,7 +150,12 @@ pub fn parse_storage_claim_reward(matches: &ArgMatches<'_>) -> Result<CliCommand
node_account_pubkey,
storage_account_pubkey,
},
require_keypair: true,
signers: vec![signer_from_path(
matches,
default_signer_path,
"keypair",
wallet_manager,
)?],
})
}
@ -145,20 +165,21 @@ pub fn parse_storage_get_account_command(
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
Ok(CliCommandInfo {
command: CliCommand::ShowStorageAccount(storage_account_pubkey),
require_keypair: false,
signers: vec![],
})
}
pub fn process_create_storage_account(
rpc_client: &RpcClient,
config: &CliConfig,
storage_account: SignerIndex,
account_owner: &Pubkey,
storage_account: &Keypair,
account_type: StorageAccountType,
) -> ProcessResult {
let storage_account = config.signers[storage_account];
let storage_account_pubkey = storage_account.pubkey();
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
(&config.signers[0].pubkey(), "cli keypair".to_string()),
(
&storage_account_pubkey,
"storage_account_pubkey".to_string(),
@ -183,7 +204,7 @@ pub fn process_create_storage_account(
.max(1);
let ixs = storage_instruction::create_storage_account(
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&account_owner,
&storage_account_pubkey,
required_balance,
@ -191,19 +212,16 @@ pub fn process_create_storage_account(
);
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let mut tx = Transaction::new_signed_instructions(
&[&config.keypair, &storage_account],
ixs,
recent_blockhash,
);
let message = Message::new(ixs);
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&config.signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&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.signers);
log_instruction_custom_error::<SystemError>(result)
}
@ -217,13 +235,13 @@ pub fn process_claim_storage_reward(
let instruction =
storage_instruction::claim_reward(node_account_pubkey, storage_account_pubkey);
let signers = [&config.keypair];
let signers = [config.signers[0]];
let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey()));
let mut tx = Transaction::new(&signers, message, recent_blockhash);
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&fee_calculator,
&tx.message,
)?;
@ -251,7 +269,7 @@ pub fn process_show_storage_account(
CliError::RpcRequestError(format!("Unable to deserialize storage account: {:?}", err))
})?;
println!("{:#?}", storage_contract);
println!("account lamports: {}", account.lamports);
println!("Account Lamports: {}", account.lamports);
Ok("".to_string())
}
@ -259,7 +277,7 @@ pub fn process_show_storage_account(
mod tests {
use super::*;
use crate::cli::{app, parse_command};
use solana_sdk::signature::write_keypair;
use solana_sdk::signature::{read_keypair_file, write_keypair, Keypair, Signer};
use tempfile::NamedTempFile;
fn make_tmp_file() -> (String, NamedTempFile) {
@ -273,6 +291,10 @@ mod tests {
let pubkey = Pubkey::new_rand();
let pubkey_string = pubkey.to_string();
let default_keypair = Keypair::new();
let (default_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
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();
@ -284,14 +306,22 @@ mod tests {
&keypair_file,
]);
assert_eq!(
parse_command(&test_create_archiver_storage_account).unwrap(),
parse_command(
&test_create_archiver_storage_account,
&default_keypair_file,
None
)
.unwrap(),
CliCommandInfo {
command: CliCommand::CreateStorageAccount {
account_owner: pubkey,
storage_account: storage_account_keypair.into(),
storage_account: 1,
account_type: StorageAccountType::Archiver,
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
storage_account_keypair.into()
],
}
);
@ -308,14 +338,22 @@ mod tests {
&keypair_file,
]);
assert_eq!(
parse_command(&test_create_validator_storage_account).unwrap(),
parse_command(
&test_create_validator_storage_account,
&default_keypair_file,
None
)
.unwrap(),
CliCommandInfo {
command: CliCommand::CreateStorageAccount {
account_owner: pubkey,
storage_account: storage_account_keypair.into(),
storage_account: 1,
account_type: StorageAccountType::Validator,
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
storage_account_keypair.into()
],
}
);
@ -326,13 +364,13 @@ mod tests {
&storage_account_string,
]);
assert_eq!(
parse_command(&test_claim_storage_reward).unwrap(),
parse_command(&test_claim_storage_reward, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::ClaimStorageReward {
node_account_pubkey: pubkey,
storage_account_pubkey,
},
require_keypair: true
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
);
}

View File

@ -11,19 +11,21 @@ use serde_json::{Map, Value};
use solana_clap_utils::{
input_parsers::pubkey_of,
input_validators::{is_pubkey, is_url},
keypair::signer_from_path,
};
use solana_client::rpc_client::RpcClient;
use solana_config_program::{config_instruction, get_config_data, ConfigKeys, ConfigState};
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account::Account,
commitment_config::CommitmentConfig,
message::Message,
pubkey::Pubkey,
signature::{Keypair, KeypairUtil},
signature::{Keypair, Signer},
transaction::Transaction,
};
use std::error;
use std::{error, sync::Arc};
use titlecase::titlecase;
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
pub const MAX_LONG_FIELD_LENGTH: usize = 300;
@ -224,17 +226,26 @@ impl ValidatorInfoSubCommands for App<'_, '_> {
}
}
pub fn parse_validator_info_command(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
pub fn parse_validator_info_command(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let info_pubkey = pubkey_of(matches, "info_pubkey");
// Prepare validator info
let validator_info = parse_args(&matches);
let validator_info = parse_args(matches);
Ok(CliCommandInfo {
command: CliCommand::SetValidatorInfo {
validator_info,
force_keybase: matches.is_present("force"),
info_pubkey,
},
require_keypair: true,
signers: vec![signer_from_path(
matches,
default_signer_path,
"keypair",
wallet_manager,
)?],
})
}
@ -244,7 +255,7 @@ pub fn parse_get_validator_info_command(
let info_pubkey = pubkey_of(matches, "info_pubkey");
Ok(CliCommandInfo {
command: CliCommand::GetValidatorInfo(info_pubkey),
require_keypair: false,
signers: vec![],
})
}
@ -257,7 +268,7 @@ pub fn process_set_validator_info(
) -> ProcessResult {
// Validate keybase username
if let Some(string) = validator_info.get("keybaseUsername") {
let result = verify_keybase(&config.keypair.pubkey(), &string);
let result = verify_keybase(&config.signers[0].pubkey(), &string);
if result.is_err() {
if force_keybase {
println!("--force supplied, ignoring: {:?}", result);
@ -282,7 +293,7 @@ pub fn process_set_validator_info(
})
.find(|(pubkey, account)| {
let (validator_pubkey, _) = parse_validator_info(&pubkey, &account).unwrap();
validator_pubkey == config.keypair.pubkey()
validator_pubkey == config.signers[0].pubkey()
});
// Create validator-info keypair to use if info_pubkey not provided or does not exist
@ -300,8 +311,8 @@ pub fn process_set_validator_info(
.poll_get_balance_with_commitment(&info_pubkey, CommitmentConfig::default())
.unwrap_or(0);
let keys = vec![(id(), false), (config.keypair.pubkey(), true)];
let (message, signers): (Message, Vec<&Keypair>) = if balance == 0 {
let keys = vec![(id(), false), (config.signers[0].pubkey(), true)];
let (message, signers): (Message, Vec<&dyn Signer>) = if balance == 0 {
if info_pubkey != info_keypair.pubkey() {
println!(
"Account {:?} does not exist. Generating new keypair...",
@ -311,12 +322,12 @@ pub fn process_set_validator_info(
}
println!(
"Publishing info for Validator {:?}",
config.keypair.pubkey()
config.signers[0].pubkey()
);
let lamports = rpc_client
.get_minimum_balance_for_rent_exemption(ValidatorInfo::max_space() as usize)?;
let mut instructions = config_instruction::create_account::<ValidatorInfo>(
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&info_keypair.pubkey(),
lamports,
keys.clone(),
@ -327,13 +338,13 @@ pub fn process_set_validator_info(
keys,
&validator_info,
)]);
let signers = vec![&config.keypair, &info_keypair];
let signers = vec![config.signers[0], &info_keypair];
let message = Message::new(instructions);
(message, signers)
} else {
println!(
"Updating Validator {:?} info at: {:?}",
config.keypair.pubkey(),
config.signers[0].pubkey(),
info_pubkey
);
let instructions = vec![config_instruction::store(
@ -342,17 +353,18 @@ pub fn process_set_validator_info(
keys,
&validator_info,
)];
let message = Message::new_with_payer(instructions, Some(&config.keypair.pubkey()));
let signers = vec![&config.keypair];
let message = Message::new_with_payer(instructions, Some(&config.signers[0].pubkey()));
let signers = vec![config.signers[0]];
(message, signers)
};
// Submit transaction
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let mut tx = Transaction::new(&signers, message, recent_blockhash);
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&fee_calculator,
&tx.message,
)?;
@ -390,9 +402,12 @@ pub fn process_get_validator_info(rpc_client: &RpcClient, pubkey: Option<Pubkey>
parse_validator_info(&validator_info_pubkey, &validator_info_account)?;
println!();
println_name_value("Validator Identity Pubkey:", &validator_pubkey.to_string());
println_name_value(" info pubkey:", &validator_info_pubkey.to_string());
println_name_value(" Info Pubkey:", &validator_info_pubkey.to_string());
for (key, value) in validator_info.iter() {
println_name_value(&format!(" {}:", key), &value.as_str().unwrap_or("?"));
println_name_value(
&format!(" {}:", titlecase(key)),
&value.as_str().unwrap_or("?"),
);
}
}

View File

@ -1,15 +1,16 @@
use crate::cli::{
build_balance_message, check_account_for_fee, check_unique_pubkeys,
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, CliSignerInfo,
ProcessResult,
};
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_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account::Account,
message::Message,
pubkey::Pubkey,
signature::Keypair,
signature::KeypairUtil,
system_instruction::{create_address_with_seed, SystemError},
transaction::Transaction,
};
@ -17,6 +18,7 @@ use solana_vote_program::{
vote_instruction::{self, VoteError},
vote_state::{VoteAuthorize, VoteInit, VoteState},
};
use std::sync::Arc;
pub trait VoteSubCommands {
fn vote_subcommands(self) -> Self;
@ -175,56 +177,88 @@ impl VoteSubCommands for App<'_, '_> {
}
}
pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let vote_account = keypair_of(matches, "vote_account").unwrap();
pub fn parse_vote_create_account(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let (vote_account, _) = signer_of(matches, "vote_account", wallet_manager)?;
let seed = matches.value_of("seed").map(|s| s.to_string());
let identity_pubkey = pubkey_of(matches, "identity_pubkey").unwrap();
let commission = value_t_or_exit!(matches, "commission", u8);
let authorized_voter = pubkey_of(matches, "authorized_voter");
let authorized_withdrawer = pubkey_of(matches, "authorized_withdrawer");
let payer_provided = None;
let CliSignerInfo { signers } = generate_unique_signers(
vec![payer_provided, vote_account],
matches,
default_signer_path,
wallet_manager,
)?;
Ok(CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: vote_account.into(),
seed,
node_pubkey: identity_pubkey,
authorized_voter,
authorized_withdrawer,
commission,
},
require_keypair: true,
signers,
})
}
pub fn parse_vote_authorize(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
vote_authorize: VoteAuthorize,
) -> Result<CliCommandInfo, CliError> {
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
let new_authorized_pubkey = pubkey_of(matches, "new_authorized_pubkey").unwrap();
let authorized_voter_provided = None;
let CliSignerInfo { signers } = generate_unique_signers(
vec![authorized_voter_provided],
matches,
default_signer_path,
wallet_manager,
)?;
Ok(CliCommandInfo {
command: CliCommand::VoteAuthorize {
vote_account_pubkey,
new_authorized_pubkey,
vote_authorize,
},
require_keypair: true,
signers,
})
}
pub fn parse_vote_update_validator(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
pub fn parse_vote_update_validator(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
let new_identity_pubkey = pubkey_of(matches, "new_identity_pubkey").unwrap();
let authorized_voter = keypair_of(matches, "authorized_voter").unwrap();
let (authorized_voter, _) = signer_of(matches, "authorized_voter", wallet_manager)?;
let payer_provided = None;
let CliSignerInfo { signers } = generate_unique_signers(
vec![payer_provided, authorized_voter],
matches,
default_signer_path,
wallet_manager,
)?;
Ok(CliCommandInfo {
command: CliCommand::VoteUpdateValidator {
vote_account_pubkey,
new_identity_pubkey,
authorized_voter: authorized_voter.into(),
},
require_keypair: true,
signers,
})
}
@ -238,20 +272,20 @@ pub fn parse_vote_get_account_command(
pubkey: vote_account_pubkey,
use_lamports_unit,
},
require_keypair: false,
signers: vec![],
})
}
pub fn process_create_vote_account(
rpc_client: &RpcClient,
config: &CliConfig,
vote_account: &Keypair,
seed: &Option<String>,
identity_pubkey: &Pubkey,
authorized_voter: &Option<Pubkey>,
authorized_withdrawer: &Option<Pubkey>,
commission: u8,
) -> ProcessResult {
let vote_account = config.signers[1];
let vote_account_pubkey = vote_account.pubkey();
let vote_account_address = if let Some(seed) = seed {
create_address_with_seed(&vote_account_pubkey, &seed, &solana_vote_program::id())?
@ -259,7 +293,7 @@ pub fn process_create_vote_account(
vote_account_pubkey
};
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
(&config.signers[0].pubkey(), "cli keypair".to_string()),
(&vote_account_address, "vote_account".to_string()),
)?;
@ -293,16 +327,16 @@ pub fn process_create_vote_account(
let ixs = if let Some(seed) = seed {
vote_instruction::create_account_with_seed(
&config.keypair.pubkey(), // from
&vote_account_address, // to
&vote_account_pubkey, // base
seed, // seed
&config.signers[0].pubkey(), // from
&vote_account_address, // to
&vote_account_pubkey, // base
seed, // seed
&vote_init,
required_balance,
)
} else {
vote_instruction::create_account(
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&vote_account_pubkey,
&vote_init,
required_balance,
@ -310,20 +344,16 @@ pub fn process_create_vote_account(
};
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let signers = if vote_account_pubkey != config.keypair.pubkey() {
vec![&config.keypair, vote_account] // both must sign if `from` and `to` differ
} else {
vec![&config.keypair] // when stake_account == config.keypair and there's a seed, we only need one signature
};
let mut tx = Transaction::new_signed_instructions(&signers, ixs, recent_blockhash);
let message = Message::new(ixs);
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&config.signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&fee_calculator,
&tx.message,
)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &signers);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
log_instruction_custom_error::<SystemError>(result)
}
@ -340,25 +370,22 @@ pub fn process_vote_authorize(
)?;
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![vote_instruction::authorize(
vote_account_pubkey, // vote account to update
&config.keypair.pubkey(), // current authorized voter
new_authorized_pubkey, // new vote signer/withdrawer
vote_authorize, // vote or withdraw
vote_account_pubkey, // vote account to update
&config.signers[0].pubkey(), // current authorized voter
new_authorized_pubkey, // new vote signer/withdrawer
vote_authorize, // vote or withdraw
)];
let mut tx = Transaction::new_signed_with_payer(
ixs,
Some(&config.keypair.pubkey()),
&[&config.keypair],
recent_blockhash,
);
let message = Message::new_with_payer(ixs, Some(&config.signers[0].pubkey()));
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&config.signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&fee_calculator,
&tx.message,
)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0]]);
log_instruction_custom_error::<VoteError>(result)
}
@ -367,8 +394,8 @@ pub fn process_vote_update_validator(
config: &CliConfig,
vote_account_pubkey: &Pubkey,
new_identity_pubkey: &Pubkey,
authorized_voter: &Keypair,
) -> ProcessResult {
let authorized_voter = config.signers[1];
check_unique_pubkeys(
(vote_account_pubkey, "vote_account_pubkey".to_string()),
(new_identity_pubkey, "new_identity_pubkey".to_string()),
@ -380,19 +407,16 @@ pub fn process_vote_update_validator(
new_identity_pubkey,
)];
let mut tx = Transaction::new_signed_with_payer(
ixs,
Some(&config.keypair.pubkey()),
&[&config.keypair, authorized_voter],
recent_blockhash,
);
let message = Message::new_with_payer(ixs, Some(&config.signers[0].pubkey()));
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&config.signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&fee_calculator,
&tx.message,
)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
log_instruction_custom_error::<VoteError>(result)
}
@ -429,25 +453,25 @@ pub fn process_show_vote_account(
let epoch_schedule = rpc_client.get_epoch_schedule()?;
println!(
"account balance: {}",
"Account Balance: {}",
build_balance_message(vote_account.lamports, use_lamports_unit, true)
);
println!("validator identity: {}", vote_state.node_pubkey);
println!("authorized voter: {}", vote_state.authorized_voter);
println!("Validator Identity: {}", vote_state.node_pubkey);
println!("Authorized Voter: {}", vote_state.authorized_voter);
println!(
"authorized withdrawer: {}",
"Authorized Withdrawer: {}",
vote_state.authorized_withdrawer
);
println!("credits: {}", vote_state.credits());
println!("commission: {}%", vote_state.commission);
println!("Credits: {}", vote_state.credits());
println!("Commission: {}%", vote_state.commission);
println!(
"root slot: {}",
"Root Slot: {}",
match vote_state.root_slot {
Some(slot) => slot.to_string(),
None => "~".to_string(),
}
);
println!("recent timestamp: {:?}", vote_state.last_timestamp);
println!("Recent Timestamp: {:?}", vote_state.last_timestamp);
if !vote_state.votes.is_empty() {
println!("recent votes:");
for vote in &vote_state.votes {
@ -457,7 +481,7 @@ pub fn process_show_vote_account(
);
}
println!("epoch voting history:");
println!("Epoch Voting History:");
for (epoch, credits, prev_credits) in vote_state.epoch_credits() {
let credits_earned = credits - prev_credits;
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(*epoch);
@ -474,7 +498,7 @@ pub fn process_show_vote_account(
mod tests {
use super::*;
use crate::cli::{app, parse_command};
use solana_sdk::signature::write_keypair;
use solana_sdk::signature::{read_keypair_file, write_keypair, Keypair, Signer};
use tempfile::NamedTempFile;
fn make_tmp_file() -> (String, NamedTempFile) {
@ -492,6 +516,10 @@ mod tests {
let pubkey2 = keypair2.pubkey();
let pubkey2_string = pubkey2.to_string();
let default_keypair = Keypair::new();
let (default_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
"test",
"vote-authorize-voter",
@ -499,14 +527,14 @@ mod tests {
&pubkey2_string,
]);
assert_eq!(
parse_command(&test_authorize_voter).unwrap(),
parse_command(&test_authorize_voter, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::VoteAuthorize {
vote_account_pubkey: pubkey,
new_authorized_pubkey: pubkey2,
vote_authorize: VoteAuthorize::Voter
},
require_keypair: true
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
);
@ -525,17 +553,19 @@ mod tests {
"10",
]);
assert_eq!(
parse_command(&test_create_vote_account).unwrap(),
parse_command(&test_create_vote_account, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: keypair.into(),
seed: None,
node_pubkey,
authorized_voter: None,
authorized_withdrawer: None,
commission: 10,
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(keypair)
],
}
);
@ -550,17 +580,19 @@ mod tests {
&node_pubkey_string,
]);
assert_eq!(
parse_command(&test_create_vote_account2).unwrap(),
parse_command(&test_create_vote_account2, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: keypair.into(),
seed: None,
node_pubkey,
authorized_voter: None,
authorized_withdrawer: None,
commission: 100,
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(keypair)
],
}
);
@ -579,17 +611,19 @@ mod tests {
&authed.to_string(),
]);
assert_eq!(
parse_command(&test_create_vote_account3).unwrap(),
parse_command(&test_create_vote_account3, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: keypair.into(),
seed: None,
node_pubkey,
authorized_voter: Some(authed),
authorized_withdrawer: None,
commission: 100
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(keypair)
],
}
);
@ -606,17 +640,19 @@ mod tests {
&authed.to_string(),
]);
assert_eq!(
parse_command(&test_create_vote_account4).unwrap(),
parse_command(&test_create_vote_account4, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: keypair.into(),
seed: None,
node_pubkey,
authorized_voter: None,
authorized_withdrawer: Some(authed),
commission: 100
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(keypair)
],
}
);
@ -628,16 +664,16 @@ mod tests {
&keypair_file,
]);
assert_eq!(
parse_command(&test_update_validator).unwrap(),
parse_command(&test_update_validator, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::VoteUpdateValidator {
vote_account_pubkey: pubkey,
new_identity_pubkey: pubkey2,
authorized_voter: solana_sdk::signature::read_keypair_file(&keypair_file)
.unwrap()
.into(),
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(read_keypair_file(&keypair_file).unwrap())
],
}
);
}

View File

@ -3,7 +3,7 @@ use solana_cli::cli::{process_command, CliCommand, CliConfig};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::new_validator_for_tests;
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::{bpf_loader, pubkey::Pubkey};
use solana_sdk::{bpf_loader, pubkey::Pubkey, signature::Keypair};
use std::{
fs::{remove_dir_all, File},
io::Read,
@ -38,13 +38,15 @@ fn test_cli_deploy_program() {
.unwrap();
let mut config = CliConfig::default();
let keypair = Keypair::new();
config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
config.command = CliCommand::Airdrop {
faucet_host: None,
faucet_port: faucet_addr.port(),
pubkey: None,
lamports: minimum_balance_for_rent_exemption + 1, // min balance for rent exemption + leftover for tx processing
use_lamports_unit: true,
};
config.signers = vec![&keypair];
process_command(&config).unwrap();
config.command = CliCommand::Deploy(pathbuf.to_str().unwrap().to_string());

View File

@ -1,29 +1,17 @@
use solana_cli::cli::{
process_command, request_and_confirm_airdrop, CliCommand, CliConfig, SigningAuthority,
};
use solana_cli::cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig};
use solana_client::rpc_client::RpcClient;
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::{
hash::Hash,
pubkey::Pubkey,
signature::{read_keypair_file, write_keypair, Keypair, KeypairUtil},
signature::{keypair_from_seed, Keypair, Signer},
system_instruction::create_address_with_seed,
system_program,
};
use std::fs::remove_dir_all;
use std::sync::mpsc::channel;
use std::{fs::remove_dir_all, sync::mpsc::channel, thread::sleep, time::Duration};
#[cfg(test)]
use solana_core::validator::new_validator_for_tests;
use std::thread::sleep;
use std::time::Duration;
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)
}
fn check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) {
(0..5).for_each(|tries| {
@ -46,26 +34,9 @@ fn test_nonce() {
let faucet_addr = receiver.recv().unwrap();
let rpc_client = RpcClient::new_socket(leader_data.rpc);
let json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
let mut config_payer = CliConfig::default();
config_payer.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
let mut config_nonce = CliConfig::default();
config_nonce.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
let (keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&config_nonce.keypair, tmp_file.as_file_mut()).unwrap();
full_battery_tests(
&rpc_client,
&faucet_addr,
&mut config_payer,
&mut config_nonce,
&keypair_file,
None,
None,
);
full_battery_tests(&rpc_client, &faucet_addr, json_rpc_url, None, false);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
@ -79,25 +50,14 @@ fn test_nonce_with_seed() {
let faucet_addr = receiver.recv().unwrap();
let rpc_client = RpcClient::new_socket(leader_data.rpc);
let mut config_payer = CliConfig::default();
config_payer.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
let mut config_nonce = CliConfig::default();
config_nonce.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
let (keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&config_nonce.keypair, tmp_file.as_file_mut()).unwrap();
let json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
full_battery_tests(
&rpc_client,
&faucet_addr,
&mut config_payer,
&mut config_nonce,
&keypair_file,
json_rpc_url,
Some(String::from("seed")),
None,
false,
);
server.close().unwrap();
@ -112,78 +72,73 @@ fn test_nonce_with_authority() {
let faucet_addr = receiver.recv().unwrap();
let rpc_client = RpcClient::new_socket(leader_data.rpc);
let json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
let mut config_payer = CliConfig::default();
config_payer.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
let mut config_nonce = CliConfig::default();
config_nonce.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
let (nonce_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&config_nonce.keypair, tmp_file.as_file_mut()).unwrap();
let nonce_authority = Keypair::new();
let (authority_keypair_file, mut tmp_file2) = make_tmp_file();
write_keypair(&nonce_authority, tmp_file2.as_file_mut()).unwrap();
full_battery_tests(
&rpc_client,
&faucet_addr,
&mut config_payer,
&mut config_nonce,
&nonce_keypair_file,
None,
Some(&authority_keypair_file),
);
full_battery_tests(&rpc_client, &faucet_addr, json_rpc_url, None, true);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
}
fn read_keypair_from_option(keypair_file: &Option<&str>) -> Option<SigningAuthority> {
keypair_file.map(|akf| read_keypair_file(&akf).unwrap().into())
}
fn full_battery_tests(
rpc_client: &RpcClient,
faucet_addr: &std::net::SocketAddr,
config_payer: &mut CliConfig,
config_nonce: &mut CliConfig,
nonce_keypair_file: &str,
json_rpc_url: String,
seed: Option<String>,
authority_keypair_file: Option<&str>,
use_nonce_authority: bool,
) {
let mut config_payer = CliConfig::default();
config_payer.json_rpc_url = json_rpc_url.clone();
let payer = Keypair::new();
config_payer.signers = vec![&payer];
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config_payer.keypair.pubkey(),
&config_payer.signers[0].pubkey(),
2000,
)
.unwrap();
check_balance(2000, &rpc_client, &config_payer.keypair.pubkey());
check_balance(2000, &rpc_client, &config_payer.signers[0].pubkey());
let mut config_nonce = CliConfig::default();
config_nonce.json_rpc_url = json_rpc_url;
let nonce_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
config_nonce.signers = vec![&nonce_keypair];
let nonce_account = if let Some(seed) = seed.as_ref() {
create_address_with_seed(&config_nonce.keypair.pubkey(), seed, &system_program::id())
.unwrap()
create_address_with_seed(
&config_nonce.signers[0].pubkey(),
seed,
&system_program::id(),
)
.unwrap()
} else {
config_nonce.keypair.pubkey()
nonce_keypair.pubkey()
};
let nonce_authority = Keypair::new();
let optional_authority = if use_nonce_authority {
Some(nonce_authority.pubkey())
} else {
None
};
// Create nonce account
config_payer.signers.push(&nonce_keypair);
config_payer.command = CliCommand::CreateNonceAccount {
nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().into(),
nonce_account: 1,
seed,
nonce_authority: read_keypair_from_option(&authority_keypair_file)
.map(|na: SigningAuthority| na.pubkey()),
nonce_authority: optional_authority,
lamports: 1000,
};
process_command(&config_payer).unwrap();
check_balance(1000, &rpc_client, &config_payer.keypair.pubkey());
check_balance(1000, &rpc_client, &config_payer.signers[0].pubkey());
check_balance(1000, &rpc_client, &nonce_account);
// Get nonce
config_payer.signers.pop();
config_payer.command = CliCommand::GetNonce(nonce_account);
let first_nonce_string = process_command(&config_payer).unwrap();
let first_nonce = first_nonce_string.parse::<Hash>().unwrap();
@ -195,14 +150,24 @@ fn full_battery_tests(
assert_eq!(first_nonce, second_nonce);
let mut authorized_signers: Vec<&dyn Signer> = vec![&payer];
let index = if use_nonce_authority {
authorized_signers.push(&nonce_authority);
1
} else {
0
};
// New nonce
config_payer.signers = authorized_signers.clone();
config_payer.command = CliCommand::NewNonce {
nonce_account,
nonce_authority: read_keypair_from_option(&authority_keypair_file),
nonce_authority: index,
};
process_command(&config_payer).unwrap();
// Get nonce
config_payer.signers = vec![&payer];
config_payer.command = CliCommand::GetNonce(nonce_account);
let third_nonce_string = process_command(&config_payer).unwrap();
let third_nonce = third_nonce_string.parse::<Hash>().unwrap();
@ -211,14 +176,15 @@ fn full_battery_tests(
// Withdraw from nonce account
let payee_pubkey = Pubkey::new_rand();
config_payer.signers = authorized_signers;
config_payer.command = CliCommand::WithdrawFromNonceAccount {
nonce_account,
nonce_authority: read_keypair_from_option(&authority_keypair_file),
nonce_authority: index,
destination_account_pubkey: payee_pubkey,
lamports: 100,
};
process_command(&config_payer).unwrap();
check_balance(1000, &rpc_client, &config_payer.keypair.pubkey());
check_balance(1000, &rpc_client, &config_payer.signers[0].pubkey());
check_balance(900, &rpc_client, &nonce_account);
check_balance(100, &rpc_client, &payee_pubkey);
@ -231,48 +197,37 @@ fn full_battery_tests(
// Set new authority
let new_authority = Keypair::new();
let (new_authority_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&new_authority, tmp_file.as_file_mut()).unwrap();
config_payer.command = CliCommand::AuthorizeNonceAccount {
nonce_account,
nonce_authority: read_keypair_from_option(&authority_keypair_file),
new_authority: read_keypair_file(&new_authority_keypair_file)
.unwrap()
.pubkey(),
nonce_authority: index,
new_authority: new_authority.pubkey(),
};
process_command(&config_payer).unwrap();
// Old authority fails now
config_payer.command = CliCommand::NewNonce {
nonce_account,
nonce_authority: read_keypair_from_option(&authority_keypair_file),
nonce_authority: index,
};
process_command(&config_payer).unwrap_err();
// New authority can advance nonce
config_payer.signers = vec![&payer, &new_authority];
config_payer.command = CliCommand::NewNonce {
nonce_account,
nonce_authority: Some(
read_keypair_file(&new_authority_keypair_file)
.unwrap()
.into(),
),
nonce_authority: 1,
};
process_command(&config_payer).unwrap();
// New authority can withdraw from nonce account
config_payer.command = CliCommand::WithdrawFromNonceAccount {
nonce_account,
nonce_authority: Some(
read_keypair_file(&new_authority_keypair_file)
.unwrap()
.into(),
),
nonce_authority: 1,
destination_account_pubkey: payee_pubkey,
lamports: 100,
};
process_command(&config_payer).unwrap();
check_balance(1000, &rpc_client, &config_payer.keypair.pubkey());
check_balance(1000, &rpc_client, &config_payer.signers[0].pubkey());
check_balance(800, &rpc_client, &nonce_account);
check_balance(200, &rpc_client, &payee_pubkey);
}

View File

@ -1,5 +1,6 @@
use chrono::prelude::*;
use serde_json::Value;
use solana_clap_utils::keypair::presigner_from_pubkey_sigs;
use solana_cli::{
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig, PayCommand},
offline::{parse_sign_only_reply_string, BlockhashQuery},
@ -11,21 +12,12 @@ use solana_sdk::{
fee_calculator::FeeCalculator,
nonce_state::NonceState,
pubkey::Pubkey,
signature::{read_keypair_file, write_keypair, Keypair, KeypairUtil},
signature::{Keypair, Signer},
};
use std::fs::remove_dir_all;
use std::sync::mpsc::channel;
use std::{fs::remove_dir_all, sync::mpsc::channel, thread::sleep, time::Duration};
#[cfg(test)]
use solana_core::validator::new_validator_for_tests;
use std::thread::sleep;
use std::time::Duration;
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)
}
fn check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) {
(0..5).for_each(|tries| {
@ -50,32 +42,36 @@ fn test_cli_timestamp_tx() {
let faucet_addr = receiver.recv().unwrap();
let rpc_client = RpcClient::new_socket(leader_data.rpc);
let default_signer0 = Keypair::new();
let default_signer1 = Keypair::new();
let mut config_payer = CliConfig::default();
config_payer.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
config_payer.signers = vec![&default_signer0];
let mut config_witness = CliConfig::default();
config_witness.json_rpc_url = config_payer.json_rpc_url.clone();
config_witness.signers = vec![&default_signer1];
assert_ne!(
config_payer.keypair.pubkey(),
config_witness.keypair.pubkey()
config_payer.signers[0].pubkey(),
config_witness.signers[0].pubkey()
);
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config_payer.keypair.pubkey(),
&config_payer.signers[0].pubkey(),
50,
)
.unwrap();
check_balance(50, &rpc_client, &config_payer.keypair.pubkey());
check_balance(50, &rpc_client, &config_payer.signers[0].pubkey());
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config_witness.keypair.pubkey(),
&config_witness.signers[0].pubkey(),
1,
)
.unwrap();
@ -87,7 +83,7 @@ fn test_cli_timestamp_tx() {
lamports: 10,
to: bob_pubkey,
timestamp: Some(dt),
timestamp_pubkey: Some(config_witness.keypair.pubkey()),
timestamp_pubkey: Some(config_witness.signers[0].pubkey()),
..PayCommand::default()
});
let sig_response = process_command(&config_payer);
@ -99,7 +95,7 @@ fn test_cli_timestamp_tx() {
.expect("base58-encoded public key");
let process_id = Pubkey::new(&process_id_vec);
check_balance(40, &rpc_client, &config_payer.keypair.pubkey()); // config_payer balance
check_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance
check_balance(10, &rpc_client, &process_id); // contract balance
check_balance(0, &rpc_client, &bob_pubkey); // recipient balance
@ -107,7 +103,7 @@ fn test_cli_timestamp_tx() {
config_witness.command = CliCommand::TimeElapsed(bob_pubkey, process_id, dt);
process_command(&config_witness).unwrap();
check_balance(40, &rpc_client, &config_payer.keypair.pubkey()); // config_payer balance
check_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance
check_balance(0, &rpc_client, &process_id); // contract balance
check_balance(10, &rpc_client, &bob_pubkey); // recipient balance
@ -125,30 +121,34 @@ fn test_cli_witness_tx() {
let faucet_addr = receiver.recv().unwrap();
let rpc_client = RpcClient::new_socket(leader_data.rpc);
let default_signer0 = Keypair::new();
let default_signer1 = Keypair::new();
let mut config_payer = CliConfig::default();
config_payer.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
config_payer.signers = vec![&default_signer0];
let mut config_witness = CliConfig::default();
config_witness.json_rpc_url = config_payer.json_rpc_url.clone();
config_witness.signers = vec![&default_signer1];
assert_ne!(
config_payer.keypair.pubkey(),
config_witness.keypair.pubkey()
config_payer.signers[0].pubkey(),
config_witness.signers[0].pubkey()
);
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config_payer.keypair.pubkey(),
&config_payer.signers[0].pubkey(),
50,
)
.unwrap();
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config_witness.keypair.pubkey(),
&config_witness.signers[0].pubkey(),
1,
)
.unwrap();
@ -157,7 +157,7 @@ fn test_cli_witness_tx() {
config_payer.command = CliCommand::Pay(PayCommand {
lamports: 10,
to: bob_pubkey,
witnesses: Some(vec![config_witness.keypair.pubkey()]),
witnesses: Some(vec![config_witness.signers[0].pubkey()]),
..PayCommand::default()
});
let sig_response = process_command(&config_payer);
@ -169,7 +169,7 @@ fn test_cli_witness_tx() {
.expect("base58-encoded public key");
let process_id = Pubkey::new(&process_id_vec);
check_balance(40, &rpc_client, &config_payer.keypair.pubkey()); // config_payer balance
check_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance
check_balance(10, &rpc_client, &process_id); // contract balance
check_balance(0, &rpc_client, &bob_pubkey); // recipient balance
@ -177,7 +177,7 @@ fn test_cli_witness_tx() {
config_witness.command = CliCommand::Witness(bob_pubkey, process_id);
process_command(&config_witness).unwrap();
check_balance(40, &rpc_client, &config_payer.keypair.pubkey()); // config_payer balance
check_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance
check_balance(0, &rpc_client, &process_id); // contract balance
check_balance(10, &rpc_client, &bob_pubkey); // recipient balance
@ -195,23 +195,27 @@ fn test_cli_cancel_tx() {
let faucet_addr = receiver.recv().unwrap();
let rpc_client = RpcClient::new_socket(leader_data.rpc);
let default_signer0 = Keypair::new();
let default_signer1 = Keypair::new();
let mut config_payer = CliConfig::default();
config_payer.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
config_payer.signers = vec![&default_signer0];
let mut config_witness = CliConfig::default();
config_witness.json_rpc_url = config_payer.json_rpc_url.clone();
config_witness.signers = vec![&default_signer1];
assert_ne!(
config_payer.keypair.pubkey(),
config_witness.keypair.pubkey()
config_payer.signers[0].pubkey(),
config_witness.signers[0].pubkey()
);
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config_payer.keypair.pubkey(),
&config_payer.signers[0].pubkey(),
50,
)
.unwrap();
@ -220,7 +224,7 @@ fn test_cli_cancel_tx() {
config_payer.command = CliCommand::Pay(PayCommand {
lamports: 10,
to: bob_pubkey,
witnesses: Some(vec![config_witness.keypair.pubkey()]),
witnesses: Some(vec![config_witness.signers[0].pubkey()]),
cancelable: true,
..PayCommand::default()
});
@ -233,7 +237,7 @@ fn test_cli_cancel_tx() {
.expect("base58-encoded public key");
let process_id = Pubkey::new(&process_id_vec);
check_balance(40, &rpc_client, &config_payer.keypair.pubkey()); // config_payer balance
check_balance(40, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance
check_balance(10, &rpc_client, &process_id); // contract balance
check_balance(0, &rpc_client, &bob_pubkey); // recipient balance
@ -241,7 +245,7 @@ fn test_cli_cancel_tx() {
config_payer.command = CliCommand::Cancel(process_id);
process_command(&config_payer).unwrap();
check_balance(50, &rpc_client, &config_payer.keypair.pubkey()); // config_payer balance
check_balance(50, &rpc_client, &config_payer.signers[0].pubkey()); // config_payer balance
check_balance(0, &rpc_client, &process_id); // contract balance
check_balance(0, &rpc_client, &bob_pubkey); // recipient balance
@ -259,22 +263,26 @@ fn test_offline_pay_tx() {
let faucet_addr = receiver.recv().unwrap();
let rpc_client = RpcClient::new_socket(leader_data.rpc);
let default_signer = Keypair::new();
let default_offline_signer = Keypair::new();
let mut config_offline = CliConfig::default();
config_offline.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
config_offline.signers = vec![&default_offline_signer];
let mut config_online = CliConfig::default();
config_online.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
config_online.signers = vec![&default_signer];
assert_ne!(
config_offline.keypair.pubkey(),
config_online.keypair.pubkey()
config_offline.signers[0].pubkey(),
config_online.signers[0].pubkey()
);
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config_offline.keypair.pubkey(),
&config_offline.signers[0].pubkey(),
50,
)
.unwrap();
@ -282,12 +290,12 @@ fn test_offline_pay_tx() {
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config_online.keypair.pubkey(),
&config_online.signers[0].pubkey(),
50,
)
.unwrap();
check_balance(50, &rpc_client, &config_offline.keypair.pubkey());
check_balance(50, &rpc_client, &config_online.keypair.pubkey());
check_balance(50, &rpc_client, &config_offline.signers[0].pubkey());
check_balance(50, &rpc_client, &config_online.signers[0].pubkey());
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
config_offline.command = CliCommand::Pay(PayCommand {
@ -299,22 +307,25 @@ fn test_offline_pay_tx() {
});
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(50, &rpc_client, &config_offline.signers[0].pubkey());
check_balance(50, &rpc_client, &config_online.signers[0].pubkey());
check_balance(0, &rpc_client, &bob_pubkey);
let (blockhash, signers) = parse_sign_only_reply_string(&sig_response);
let offline_presigner =
presigner_from_pubkey_sigs(&config_offline.signers[0].pubkey(), &signers).unwrap();
let online_pubkey = config_online.signers[0].pubkey();
config_online.signers = vec![&offline_presigner];
config_online.command = CliCommand::Pay(PayCommand {
lamports: 10,
to: bob_pubkey,
signers: Some(signers),
blockhash_query: BlockhashQuery::FeeCalculator(blockhash),
..PayCommand::default()
});
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(40, &rpc_client, &config_offline.signers[0].pubkey());
check_balance(50, &rpc_client, &online_pubkey);
check_balance(10, &rpc_client, &bob_pubkey);
server.close().unwrap();
@ -331,9 +342,11 @@ fn test_nonced_pay_tx() {
let faucet_addr = receiver.recv().unwrap();
let rpc_client = RpcClient::new_socket(leader_data.rpc);
let default_signer = Keypair::new();
let mut config = CliConfig::default();
config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
config.signers = vec![&default_signer];
let minimum_nonce_balance = rpc_client
.get_minimum_balance_for_rent_exemption(NonceState::size())
@ -342,29 +355,28 @@ fn test_nonced_pay_tx() {
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
50 + minimum_nonce_balance,
)
.unwrap();
check_balance(
50 + minimum_nonce_balance,
&rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
);
// Create nonce account
let nonce_account = Keypair::new();
let (nonce_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap();
config.command = CliCommand::CreateNonceAccount {
nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().into(),
nonce_account: 1,
seed: None,
nonce_authority: Some(config.keypair.pubkey()),
nonce_authority: Some(config.signers[0].pubkey()),
lamports: minimum_nonce_balance,
};
config.signers.push(&nonce_account);
process_command(&config).unwrap();
check_balance(50, &rpc_client, &config.keypair.pubkey());
check_balance(50, &rpc_client, &config.signers[0].pubkey());
check_balance(minimum_nonce_balance, &rpc_client, &nonce_account.pubkey());
// Fetch nonce hash
@ -376,6 +388,7 @@ fn test_nonced_pay_tx() {
};
let bob_pubkey = Pubkey::new_rand();
config.signers = vec![&default_signer];
config.command = CliCommand::Pay(PayCommand {
lamports: 10,
to: bob_pubkey,
@ -385,7 +398,7 @@ fn test_nonced_pay_tx() {
});
process_command(&config).expect("failed to process pay command");
check_balance(40, &rpc_client, &config.keypair.pubkey());
check_balance(40, &rpc_client, &config.signers[0].pubkey());
check_balance(10, &rpc_client, &bob_pubkey);
// Verify that nonce has been used

View File

@ -2,9 +2,8 @@ use solana_cli::cli::{process_command, CliCommand, CliConfig};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::new_validator_for_tests;
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::signature::KeypairUtil;
use std::fs::remove_dir_all;
use std::sync::mpsc::channel;
use solana_sdk::signature::Keypair;
use std::{fs::remove_dir_all, sync::mpsc::channel};
#[test]
fn test_cli_request_airdrop() {
@ -18,9 +17,11 @@ fn test_cli_request_airdrop() {
bob_config.command = CliCommand::Airdrop {
faucet_host: None,
faucet_port: faucet_addr.port(),
pubkey: None,
lamports: 50,
use_lamports_unit: true,
};
let keypair = Keypair::new();
bob_config.signers = vec![&keypair];
let sig_response = process_command(&bob_config);
sig_response.unwrap();
@ -28,7 +29,7 @@ fn test_cli_request_airdrop() {
let rpc_client = RpcClient::new_socket(leader_data.rpc);
let balance = rpc_client
.retry_get_balance(&bob_config.keypair.pubkey(), 1)
.retry_get_balance(&bob_config.signers[0].pubkey(), 1)
.unwrap()
.unwrap();
assert_eq!(balance, 50);

File diff suppressed because it is too large Load Diff

209
cli/tests/transfer.rs Normal file
View File

@ -0,0 +1,209 @@
use solana_clap_utils::keypair::presigner_from_pubkey_sigs;
use solana_cli::{
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
offline::{parse_sign_only_reply_string, BlockhashQuery},
};
use solana_client::rpc_client::RpcClient;
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::{
account_utils::StateMut,
fee_calculator::FeeCalculator,
nonce_state::NonceState,
pubkey::Pubkey,
signature::{keypair_from_seed, Keypair, Signer},
};
use std::{fs::remove_dir_all, sync::mpsc::channel, thread::sleep, time::Duration};
#[cfg(test)]
use solana_core::validator::new_validator_for_tests_ex;
fn check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) {
(0..5).for_each(|tries| {
let balance = client.retry_get_balance(pubkey, 1).unwrap().unwrap();
if balance == expected_balance {
return;
}
if tries == 4 {
assert_eq!(balance, expected_balance);
}
sleep(Duration::from_millis(500));
});
}
#[test]
fn test_transfer() {
let (server, leader_data, mint_keypair, ledger_path, _) = new_validator_for_tests_ex(1, 42_000);
let (sender, receiver) = channel();
run_local_faucet(mint_keypair, sender, None);
let faucet_addr = receiver.recv().unwrap();
let rpc_client = RpcClient::new_socket(leader_data.rpc);
let default_signer = Keypair::new();
let default_offline_signer = Keypair::new();
let mut config = CliConfig::default();
config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
config.signers = vec![&default_signer];
let sender_pubkey = config.signers[0].pubkey();
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 50_000).unwrap();
check_balance(50_000, &rpc_client, &sender_pubkey);
check_balance(0, &rpc_client, &recipient_pubkey);
// Plain ole transfer
config.command = CliCommand::Transfer {
lamports: 10,
to: recipient_pubkey,
from: 0,
sign_only: false,
blockhash_query: BlockhashQuery::All,
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
};
process_command(&config).unwrap();
check_balance(49_989, &rpc_client, &sender_pubkey);
check_balance(10, &rpc_client, &recipient_pubkey);
let mut offline = CliConfig::default();
offline.json_rpc_url = String::default();
offline.signers = vec![&default_offline_signer];
// Verify we cannot contact the cluster
offline.command = CliCommand::ClusterVersion;
process_command(&offline).unwrap_err();
let offline_pubkey = offline.signers[0].pubkey();
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 50).unwrap();
check_balance(50, &rpc_client, &offline_pubkey);
// Offline transfer
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
offline.command = CliCommand::Transfer {
lamports: 10,
to: recipient_pubkey,
from: 0,
sign_only: true,
blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
};
let sign_only_reply = process_command(&offline).unwrap();
let (blockhash, signers) = parse_sign_only_reply_string(&sign_only_reply);
let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap();
config.signers = vec![&offline_presigner];
config.command = CliCommand::Transfer {
lamports: 10,
to: recipient_pubkey,
from: 0,
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
};
process_command(&config).unwrap();
check_balance(39, &rpc_client, &offline_pubkey);
check_balance(20, &rpc_client, &recipient_pubkey);
// Create nonce account
let nonce_account = keypair_from_seed(&[3u8; 32]).unwrap();
let minimum_nonce_balance = rpc_client
.get_minimum_balance_for_rent_exemption(NonceState::size())
.unwrap();
config.signers = vec![&default_signer, &nonce_account];
config.command = CliCommand::CreateNonceAccount {
nonce_account: 1,
seed: None,
nonce_authority: None,
lamports: minimum_nonce_balance,
};
process_command(&config).unwrap();
check_balance(49_987 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
// Fetch nonce hash
let account = rpc_client.get_account(&nonce_account.pubkey()).unwrap();
let nonce_state: NonceState = account.state().unwrap();
let nonce_hash = match nonce_state {
NonceState::Initialized(_meta, hash) => hash,
_ => panic!("Nonce is not initialized"),
};
// Nonced transfer
config.signers = vec![&default_signer];
config.command = CliCommand::Transfer {
lamports: 10,
to: recipient_pubkey,
from: 0,
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash),
nonce_account: Some(nonce_account.pubkey()),
nonce_authority: 0,
fee_payer: 0,
};
process_command(&config).unwrap();
check_balance(49_976 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
check_balance(30, &rpc_client, &recipient_pubkey);
let account = rpc_client.get_account(&nonce_account.pubkey()).unwrap();
let nonce_state: NonceState = account.state().unwrap();
let new_nonce_hash = match nonce_state {
NonceState::Initialized(_meta, hash) => hash,
_ => panic!("Nonce is not initialized"),
};
assert_ne!(nonce_hash, new_nonce_hash);
// Assign nonce authority to offline
config.signers = vec![&default_signer];
config.command = CliCommand::AuthorizeNonceAccount {
nonce_account: nonce_account.pubkey(),
nonce_authority: 0,
new_authority: offline_pubkey,
};
process_command(&config).unwrap();
check_balance(49_975 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
// Fetch nonce hash
let account = rpc_client.get_account(&nonce_account.pubkey()).unwrap();
let nonce_state: NonceState = account.state().unwrap();
let nonce_hash = match nonce_state {
NonceState::Initialized(_meta, hash) => hash,
_ => panic!("Nonce is not initialized"),
};
// Offline, nonced transfer
offline.signers = vec![&default_offline_signer];
offline.command = CliCommand::Transfer {
lamports: 10,
to: recipient_pubkey,
from: 0,
sign_only: true,
blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()),
nonce_account: Some(nonce_account.pubkey()),
nonce_authority: 0,
fee_payer: 0,
};
let sign_only_reply = process_command(&offline).unwrap();
let (blockhash, signers) = parse_sign_only_reply_string(&sign_only_reply);
let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap();
config.signers = vec![&offline_presigner];
config.command = CliCommand::Transfer {
lamports: 10,
to: recipient_pubkey,
from: 0,
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash),
nonce_account: Some(nonce_account.pubkey()),
nonce_authority: 0,
fee_payer: 0,
};
process_command(&config).unwrap();
check_balance(28, &rpc_client, &offline_pubkey);
check_balance(40, &rpc_client, &recipient_pubkey);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
}

View File

@ -1,6 +1,6 @@
[package]
name = "solana-client"
version = "0.23.2"
version = "0.23.7"
description = "Solana Client"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -19,11 +19,12 @@ reqwest = { version = "0.10.1", default-features = false, features = ["blocking"
serde = "1.0.104"
serde_derive = "1.0.103"
serde_json = "1.0.44"
solana-net-utils = { path = "../net-utils", version = "0.23.2" }
solana-sdk = { path = "../sdk", version = "0.23.2" }
solana-net-utils = { path = "../net-utils", version = "0.23.7" }
solana-sdk = { path = "../sdk", version = "0.23.7" }
thiserror = "1.0"
[dev-dependencies]
assert_matches = "1.3.0"
jsonrpc-core = "14.0.5"
jsonrpc-http-server = "14.0.5"
solana-logger = { path = "../logger", version = "0.23.2" }
solana-logger = { path = "../logger", version = "0.23.7" }

View File

@ -1,14 +1,16 @@
use crate::rpc_request;
use solana_sdk::transaction::TransactionError;
use solana_sdk::{signature::SignerError, transaction::TransactionError};
use std::{fmt, io};
use thiserror::Error;
#[derive(Debug)]
#[derive(Error, Debug)]
pub enum ClientError {
Io(io::Error),
Reqwest(reqwest::Error),
RpcError(rpc_request::RpcError),
SerdeJson(serde_json::error::Error),
TransactionError(TransactionError),
Io(#[from] io::Error),
Reqwest(#[from] reqwest::Error),
RpcError(#[from] rpc_request::RpcError),
SerdeJson(#[from] serde_json::error::Error),
SigningError(#[from] SignerError),
TransactionError(#[from] TransactionError),
}
impl fmt::Display for ClientError {
@ -16,35 +18,3 @@ impl fmt::Display for ClientError {
write!(f, "solana client error")
}
}
impl std::error::Error for ClientError {}
impl From<io::Error> for ClientError {
fn from(err: io::Error) -> ClientError {
ClientError::Io(err)
}
}
impl From<reqwest::Error> for ClientError {
fn from(err: reqwest::Error) -> ClientError {
ClientError::Reqwest(err)
}
}
impl From<rpc_request::RpcError> for ClientError {
fn from(err: rpc_request::RpcError) -> ClientError {
ClientError::RpcError(err)
}
}
impl From<serde_json::error::Error> for ClientError {
fn from(err: serde_json::error::Error) -> ClientError {
ClientError::SerdeJson(err)
}
}
impl From<TransactionError> for ClientError {
fn from(err: TransactionError) -> ClientError {
ClientError::TransactionError(err)
}
}

View File

@ -60,13 +60,10 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
Value::Null
}
}
RpcRequest::GetBalance => {
let n = if self.url == "airdrop" { 0 } else { 50 };
serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
value: Value::Number(Number::from(n)),
})?
}
RpcRequest::GetBalance => serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
value: Value::Number(Number::from(50)),
})?,
RpcRequest::GetRecentBlockhash => serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
value: (

View File

@ -22,7 +22,8 @@ use solana_sdk::{
hash::Hash,
inflation::Inflation,
pubkey::Pubkey,
signature::{KeypairUtil, Signature},
signature::Signature,
signers::Signers,
transaction::{self, Transaction, TransactionError},
};
use std::{
@ -405,10 +406,10 @@ impl RpcClient {
})
}
pub fn send_and_confirm_transaction<T: KeypairUtil>(
pub fn send_and_confirm_transaction<T: Signers>(
&self,
transaction: &mut Transaction,
signer_keys: &[&T],
signer_keys: &T,
) -> Result<String, ClientError> {
let mut send_retries = 20;
loop {
@ -456,10 +457,10 @@ impl RpcClient {
}
}
pub fn send_and_confirm_transactions<T: KeypairUtil>(
pub fn send_and_confirm_transactions<T: Signers>(
&self,
mut transactions: Vec<Transaction>,
signer_keys: &[&T],
signer_keys: &T,
) -> Result<(), Box<dyn error::Error>> {
let mut send_retries = 5;
loop {
@ -516,24 +517,22 @@ impl RpcClient {
// Re-sign any failed transactions with a new blockhash and retry
let (blockhash, _fee_calculator) =
self.get_new_blockhash(&transactions_signatures[0].0.message().recent_blockhash)?;
transactions = transactions_signatures
.into_iter()
.map(|(mut transaction, _)| {
transaction.sign(signer_keys, blockhash);
transaction
})
.collect();
transactions = vec![];
for (mut transaction, _) in transactions_signatures.into_iter() {
transaction.try_sign(signer_keys, blockhash)?;
transactions.push(transaction);
}
}
}
pub fn resign_transaction<T: KeypairUtil>(
pub fn resign_transaction<T: Signers>(
&self,
tx: &mut Transaction,
signer_keys: &[&T],
signer_keys: &T,
) -> Result<(), ClientError> {
let (blockhash, _fee_calculator) =
self.get_new_blockhash(&tx.message().recent_blockhash)?;
tx.sign(signer_keys, blockhash);
tx.try_sign(signer_keys, blockhash)?;
Ok(())
}
@ -1129,9 +1128,7 @@ mod tests {
use serde_json::Number;
use solana_logger;
use solana_sdk::{
instruction::InstructionError,
signature::{Keypair, KeypairUtil},
system_transaction,
instruction::InstructionError, signature::Keypair, system_transaction,
transaction::TransactionError,
};
use std::{sync::mpsc::channel, thread};

View File

@ -32,6 +32,14 @@ pub struct RpcBlockCommitment<T> {
pub total_stake: u64,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct RpcReward {
pub pubkey: String,
pub lamports: i64,
}
pub type RpcRewards = Vec<RpcReward>;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcConfirmedBlock {
@ -39,6 +47,7 @@ pub struct RpcConfirmedBlock {
pub blockhash: String,
pub parent_slot: Slot,
pub transactions: Vec<RpcTransactionWithStatusMeta>,
pub rewards: RpcRewards,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]

View File

@ -17,7 +17,8 @@ use solana_sdk::{
message::Message,
packet::PACKET_DATA_SIZE,
pubkey::Pubkey,
signature::{Keypair, KeypairUtil, Signature},
signature::{Keypair, Signature, Signer},
signers::Signers,
system_instruction,
timing::duration_as_ms,
transaction::{self, Transaction},
@ -202,9 +203,9 @@ impl ThinClient {
}
/// Retry sending a signed Transaction to the server for processing
pub fn send_and_confirm_transaction(
pub fn send_and_confirm_transaction<T: Signers>(
&self,
keypairs: &[&Keypair],
keypairs: &T,
transaction: &mut Transaction,
tries: usize,
pending_confirmations: usize,
@ -351,9 +352,13 @@ impl Client for ThinClient {
}
impl SyncClient for ThinClient {
fn send_message(&self, keypairs: &[&Keypair], message: Message) -> TransportResult<Signature> {
fn send_message<T: Signers>(
&self,
keypairs: &T,
message: Message,
) -> TransportResult<Signature> {
let (blockhash, _fee_calculator) = self.get_recent_blockhash()?;
let mut transaction = Transaction::new(&keypairs, message, blockhash);
let mut transaction = Transaction::new(keypairs, message, blockhash);
let signature = self.send_and_confirm_transaction(keypairs, &mut transaction, 5, 0)?;
Ok(signature)
}
@ -561,13 +566,13 @@ impl AsyncClient for ThinClient {
.send_to(&buf[..], &self.tpu_addr())?;
Ok(transaction.signatures[0])
}
fn async_send_message(
fn async_send_message<T: Signers>(
&self,
keypairs: &[&Keypair],
keypairs: &T,
message: Message,
recent_blockhash: Hash,
) -> io::Result<Signature> {
let transaction = Transaction::new(&keypairs, message, recent_blockhash);
let transaction = Transaction::new(keypairs, message, recent_blockhash);
self.async_send_transaction(transaction)
}
fn async_send_instruction(

View File

@ -1,7 +1,7 @@
[package]
name = "solana-core"
description = "Blockchain, Rebuilt for Scale"
version = "0.23.2"
version = "0.23.7"
documentation = "https://docs.rs/solana"
homepage = "https://solana.com/"
readme = "../README.md"
@ -18,6 +18,7 @@ bincode = "1.2.1"
bs58 = "0.3.0"
byteorder = "1.3.2"
chrono = { version = "0.4.10", features = ["serde"] }
compression = "0.1.5"
core_affinity = "0.5.10"
crc = { version = "1.8.1", optional = true }
crossbeam-channel = "0.3"
@ -37,29 +38,30 @@ num-traits = "0.2"
rand = "0.6.5"
rand_chacha = "0.1.1"
rayon = "1.2.0"
regex = "1.3.4"
serde = "1.0.104"
serde_derive = "1.0.103"
serde_json = "1.0.44"
solana-budget-program = { path = "../programs/budget", version = "0.23.2" }
solana-clap-utils = { path = "../clap-utils", version = "0.23.2" }
solana-client = { path = "../client", version = "0.23.2" }
solana-faucet = { path = "../faucet", version = "0.23.2" }
solana-budget-program = { path = "../programs/budget", version = "0.23.7" }
solana-clap-utils = { path = "../clap-utils", version = "0.23.7" }
solana-client = { path = "../client", version = "0.23.7" }
solana-faucet = { path = "../faucet", version = "0.23.7" }
ed25519-dalek = "=1.0.0-pre.1"
solana-ledger = { path = "../ledger", version = "0.23.2" }
solana-logger = { path = "../logger", version = "0.23.2" }
solana-merkle-tree = { path = "../merkle-tree", version = "0.23.2" }
solana-metrics = { path = "../metrics", version = "0.23.2" }
solana-measure = { path = "../measure", version = "0.23.2" }
solana-net-utils = { path = "../net-utils", version = "0.23.2" }
solana-chacha-cuda = { path = "../chacha-cuda", version = "0.23.2" }
solana-perf = { path = "../perf", version = "0.23.2" }
solana-runtime = { path = "../runtime", version = "0.23.2" }
solana-sdk = { path = "../sdk", version = "0.23.2" }
solana-stake-program = { path = "../programs/stake", version = "0.23.2" }
solana-storage-program = { path = "../programs/storage", version = "0.23.2" }
solana-vote-program = { path = "../programs/vote", version = "0.23.2" }
solana-vote-signer = { path = "../vote-signer", version = "0.23.2" }
solana-sys-tuner = { path = "../sys-tuner", version = "0.23.2" }
solana-ledger = { path = "../ledger", version = "0.23.7" }
solana-logger = { path = "../logger", version = "0.23.7" }
solana-merkle-tree = { path = "../merkle-tree", version = "0.23.7" }
solana-metrics = { path = "../metrics", version = "0.23.7" }
solana-measure = { path = "../measure", version = "0.23.7" }
solana-net-utils = { path = "../net-utils", version = "0.23.7" }
solana-chacha-cuda = { path = "../chacha-cuda", version = "0.23.7" }
solana-perf = { path = "../perf", version = "0.23.7" }
solana-runtime = { path = "../runtime", version = "0.23.7" }
solana-sdk = { path = "../sdk", version = "0.23.7" }
solana-stake-program = { path = "../programs/stake", version = "0.23.7" }
solana-storage-program = { path = "../programs/storage", version = "0.23.7" }
solana-vote-program = { path = "../programs/vote", version = "0.23.7" }
solana-vote-signer = { path = "../vote-signer", version = "0.23.7" }
solana-sys-tuner = { path = "../sys-tuner", version = "0.23.7" }
symlink = "0.1.0"
sys-info = "0.5.8"
tempfile = "3.1.0"
@ -69,8 +71,9 @@ tokio-codec = "0.1"
tokio-fs = "0.1"
tokio-io = "0.1"
untrusted = "0.7.0"
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "0.23.2" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "0.23.7" }
reed-solomon-erasure = { package = "solana-reed-solomon-erasure", version = "4.0.1-3", features = ["simd-accel"] }
trees = "0.2.1"
[dev-dependencies]
matches = "0.1.6"

View File

@ -21,8 +21,8 @@ use solana_sdk::genesis_config::GenesisConfig;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Keypair;
use solana_sdk::signature::KeypairUtil;
use solana_sdk::signature::Signature;
use solana_sdk::signature::Signer;
use solana_sdk::system_instruction;
use solana_sdk::system_transaction;
use solana_sdk::timing::{duration_as_us, timestamp};

View File

@ -3,7 +3,7 @@ extern crate test;
use solana_ledger::entry::{next_entry_mut, Entry, EntrySlice};
use solana_sdk::hash::{hash, Hash};
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::system_transaction;
use test::Bencher;

View File

@ -9,7 +9,7 @@ use solana_ledger::shred::{
};
use solana_perf::test_tx;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
use std::sync::Arc;
use test::Bencher;

View File

@ -11,7 +11,7 @@ use solana_core::sigverify::TransactionSigVerifier;
use solana_core::sigverify_stage::SigVerifyStage;
use solana_perf::test_tx::test_tx;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::system_transaction;
use solana_sdk::timing::duration_as_ms;
use std::sync::mpsc::channel;

View File

@ -404,7 +404,7 @@ impl BankingStage {
if unprocessed_packets.is_empty() {
continue;
}
let num = unprocessed_packets
let num: usize = unprocessed_packets
.iter()
.map(|(_, unprocessed)| unprocessed.len())
.sum();
@ -1029,7 +1029,7 @@ mod tests {
use solana_runtime::bank::HashAgeKind;
use solana_sdk::{
instruction::InstructionError,
signature::{Keypair, KeypairUtil},
signature::{Keypair, Signer},
system_transaction,
transaction::TransactionError,
};

View File

@ -180,7 +180,7 @@ mod test {
use chrono::{DateTime, FixedOffset};
use serde_json::Value;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::system_transaction;
use std::collections::HashSet;
use std::path::PathBuf;

View File

@ -107,7 +107,7 @@ mod test {
use solana_ledger::create_new_tmp_ledger;
use solana_ledger::entry::{create_ticks, Entry};
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::system_transaction;
use std::path::PathBuf;
use std::sync::mpsc::channel;

View File

@ -259,7 +259,7 @@ mod test {
use solana_runtime::bank::Bank;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::mpsc::channel;

View File

@ -82,7 +82,7 @@ impl BroadcastRun for FailEntryVerificationBroadcastRun {
// Broadcast data
let all_shred_bufs: Vec<Vec<u8>> = shreds.to_vec().into_iter().map(|s| s.payload).collect();
cluster_info
.read()
.write()
.unwrap()
.broadcast_shreds(sock, all_shred_bufs, &all_seeds, stakes)?;
Ok(())

View File

@ -264,7 +264,7 @@ impl StandardBroadcastRun {
trace!("Broadcasting {:?} shreds", shred_bufs.len());
cluster_info
.read()
.write()
.unwrap()
.broadcast_shreds(sock, shred_bufs, &seeds, stakes)?;
@ -362,7 +362,7 @@ mod test {
use solana_sdk::{
clock::Slot,
genesis_config::GenesisConfig,
signature::{Keypair, KeypairUtil},
signature::{Keypair, Signer},
};
use std::sync::{Arc, RwLock};
use std::time::Duration;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -84,7 +84,7 @@ impl ClusterInfoVoteListener {
mod tests {
use crate::packet;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::transaction::Transaction;
use solana_vote_program::vote_instruction;
use solana_vote_program::vote_state::Vote;

View File

@ -1,6 +1,5 @@
use chrono::prelude::*;
use solana_ledger::bank_forks::BankForks;
use solana_metrics::datapoint_debug;
use solana_runtime::bank::Bank;
use solana_sdk::{
account::Account,
@ -37,7 +36,6 @@ impl StakeLockout {
}
}
#[derive(Default)]
pub struct Tower {
node_pubkey: Pubkey,
threshold_depth: usize,
@ -47,20 +45,40 @@ pub struct Tower {
last_timestamp: BlockTimestamp,
}
impl Default for Tower {
fn default() -> Self {
Self {
node_pubkey: Pubkey::default(),
threshold_depth: VOTE_THRESHOLD_DEPTH,
threshold_size: VOTE_THRESHOLD_SIZE,
lockouts: VoteState::default(),
last_vote: Vote::default(),
last_timestamp: BlockTimestamp::default(),
}
}
}
impl Tower {
pub fn new(node_pubkey: &Pubkey, vote_account_pubkey: &Pubkey, bank_forks: &BankForks) -> Self {
let mut tower = Self {
node_pubkey: *node_pubkey,
..Tower::default()
};
tower.initialize_lockouts_from_bank_forks(&bank_forks, vote_account_pubkey);
tower
}
pub fn new_with_key(node_pubkey: &Pubkey) -> Self {
Self {
node_pubkey: *node_pubkey,
threshold_depth: VOTE_THRESHOLD_DEPTH,
threshold_size: VOTE_THRESHOLD_SIZE,
lockouts: VoteState::default(),
last_vote: Vote::default(),
last_timestamp: BlockTimestamp::default(),
};
tower.initialize_lockouts_from_bank_forks(&bank_forks, vote_account_pubkey);
tower
}
}
#[cfg(test)]
@ -110,7 +128,7 @@ impl Tower {
vote_state.nth_recent_vote(0).map(|v| v.slot).unwrap_or(0) as i64
);
debug!("observed root {}", vote_state.root_slot.unwrap_or(0) as i64);
datapoint_debug!(
datapoint_info!(
"tower-observed",
(
"slot",
@ -229,7 +247,7 @@ impl Tower {
self.lockouts.process_vote_unchecked(&vote);
self.last_vote = vote;
datapoint_debug!(
datapoint_info!(
"tower-vote",
("latest", slot, i64),
("root", self.lockouts.root_slot.unwrap_or(0), i64)
@ -284,7 +302,6 @@ impl Tower {
assert!(ancestors.contains_key(&slot));
if !self.is_recent(slot) {
trace!("slot is not recent: {}", slot);
return true;
}
@ -456,9 +473,270 @@ impl Tower {
}
#[cfg(test)]
mod test {
pub mod test {
use super::*;
use crate::replay_stage::{ForkProgress, ReplayStage};
use solana_ledger::bank_forks::BankForks;
use solana_runtime::{
bank::Bank,
genesis_utils::{create_genesis_config, GenesisConfigInfo},
};
use solana_sdk::{
clock::Slot,
hash::Hash,
pubkey::Pubkey,
signature::{Keypair, Signer},
transaction::Transaction,
};
use solana_stake_program::stake_state;
use solana_vote_program::vote_state;
use solana_vote_program::{vote_instruction, vote_state::Vote};
use std::collections::{HashMap, VecDeque};
use std::sync::RwLock;
use std::{thread::sleep, time::Duration};
use trees::{tr, Node, Tree};
pub(crate) struct ValidatorKeypairs {
node_keypair: Keypair,
vote_keypair: Keypair,
}
impl ValidatorKeypairs {
pub(crate) fn new(node_keypair: Keypair, vote_keypair: Keypair) -> Self {
Self {
node_keypair,
vote_keypair,
}
}
}
pub(crate) struct VoteSimulator<'a> {
searchable_nodes: HashMap<u64, &'a Node<u64>>,
}
impl<'a> VoteSimulator<'a> {
pub(crate) fn new(forks: &'a Tree<u64>) -> Self {
let mut searchable_nodes = HashMap::new();
let root = forks.root();
searchable_nodes.insert(root.data, root);
Self { searchable_nodes }
}
pub(crate) fn simulate_vote(
&mut self,
vote_slot: Slot,
bank_forks: &RwLock<BankForks>,
cluster_votes: &mut HashMap<Pubkey, Vec<u64>>,
validator_keypairs: &HashMap<Pubkey, ValidatorKeypairs>,
my_keypairs: &ValidatorKeypairs,
progress: &mut HashMap<u64, ForkProgress>,
tower: &mut Tower,
) -> Vec<VoteFailures> {
let node = self
.find_node_and_update_simulation(vote_slot)
.expect("Vote to simulate must be for a slot in the tree");
let mut missing_nodes = VecDeque::new();
let mut current = node;
loop {
let current_slot = current.data;
if bank_forks.read().unwrap().get(current_slot).is_some()
|| tower.root().map(|r| current_slot < r).unwrap_or(false)
{
break;
} else {
missing_nodes.push_front(current);
}
if let Some(parent) = current.parent() {
current = parent;
} else {
break;
}
}
// Create any missing banks along the path
for missing_node in missing_nodes {
let missing_slot = missing_node.data;
let parent = missing_node.parent().unwrap().data;
let parent_bank = bank_forks
.read()
.unwrap()
.get(parent)
.expect("parent bank must exist")
.clone();
info!("parent of {} is {}", missing_slot, parent_bank.slot(),);
progress
.entry(missing_slot)
.or_insert_with(|| ForkProgress::new(parent_bank.last_blockhash()));
// Create the missing bank
let new_bank =
Bank::new_from_parent(&parent_bank, &Pubkey::default(), missing_slot);
// Simulate ingesting the cluster's votes for the parent into this bank
for (pubkey, vote) in cluster_votes.iter() {
if vote.contains(&parent_bank.slot()) {
let keypairs = validator_keypairs.get(pubkey).unwrap();
let node_pubkey = keypairs.node_keypair.pubkey();
let vote_pubkey = keypairs.vote_keypair.pubkey();
let last_blockhash = parent_bank.last_blockhash();
let votes = Vote::new(vec![parent_bank.slot()], parent_bank.hash());
info!("voting {} {}", parent_bank.slot(), parent_bank.hash());
let vote_ix = vote_instruction::vote(&vote_pubkey, &vote_pubkey, votes);
let mut vote_tx =
Transaction::new_with_payer(vec![vote_ix], Some(&node_pubkey));
vote_tx.partial_sign(&[&keypairs.node_keypair], last_blockhash);
vote_tx.partial_sign(&[&keypairs.vote_keypair], last_blockhash);
new_bank.process_transaction(&vote_tx).unwrap();
}
}
new_bank.freeze();
bank_forks.write().unwrap().insert(new_bank);
}
// Now try to simulate the vote
let my_pubkey = my_keypairs.node_keypair.pubkey();
let my_vote_pubkey = my_keypairs.vote_keypair.pubkey();
let ancestors = bank_forks.read().unwrap().ancestors();
let mut frozen_banks: Vec<_> = bank_forks
.read()
.unwrap()
.frozen_banks()
.values()
.cloned()
.collect();
ReplayStage::compute_bank_stats(
&my_pubkey,
&ancestors,
&mut frozen_banks,
tower,
progress,
);
let bank = bank_forks
.read()
.unwrap()
.get(vote_slot)
.expect("Bank must have been created before vote simulation")
.clone();
// Make sure this slot isn't locked out or failing threshold
let fork_progress = progress
.get(&vote_slot)
.expect("Slot for vote must exist in progress map");
info!("Checking vote: {}", vote_slot);
info!("lockouts: {:?}", fork_progress.fork_stats.stake_lockouts);
let mut failures = vec![];
if fork_progress.fork_stats.is_locked_out {
failures.push(VoteFailures::LockedOut(vote_slot));
}
if !fork_progress.fork_stats.vote_threshold {
failures.push(VoteFailures::FailedThreshold(vote_slot));
}
if !failures.is_empty() {
return failures;
}
let vote = tower.new_vote_from_bank(&bank, &my_vote_pubkey).0;
if let Some(new_root) = tower.record_bank_vote(vote) {
ReplayStage::handle_new_root(new_root, bank_forks, progress, &None);
}
// Mark the vote for this bank under this node's pubkey so it will be
// integrated into any future child banks
cluster_votes.entry(my_pubkey).or_default().push(vote_slot);
vec![]
}
// Find a node representing the given slot
fn find_node_and_update_simulation(&mut self, slot: u64) -> Option<&'a Node<u64>> {
let mut successful_search_node: Option<&'a Node<u64>> = None;
let mut found_node = None;
for search_node in self.searchable_nodes.values() {
if let Some((target, new_searchable_nodes)) = Self::find_node(search_node, slot) {
successful_search_node = Some(search_node);
found_node = Some(target);
for node in new_searchable_nodes {
self.searchable_nodes.insert(node.data, node);
}
break;
}
}
successful_search_node.map(|node| {
self.searchable_nodes.remove(&node.data);
});
found_node
}
fn find_node(
node: &'a Node<u64>,
slot: u64,
) -> Option<(&'a Node<u64>, Vec<&'a Node<u64>>)> {
if node.data == slot {
Some((node, node.iter().collect()))
} else {
let mut search_result: Option<(&'a Node<u64>, Vec<&'a Node<u64>>)> = None;
for child in node.iter() {
if let Some((_, ref mut new_searchable_nodes)) = search_result {
new_searchable_nodes.push(child);
continue;
}
search_result = Self::find_node(child, slot);
}
search_result
}
}
}
#[derive(PartialEq, Debug)]
pub(crate) enum VoteFailures {
LockedOut(u64),
FailedThreshold(u64),
}
// Setup BankForks with bank 0 and all the validator accounts
pub(crate) fn initialize_state(
validator_keypairs: &HashMap<Pubkey, ValidatorKeypairs>,
) -> (BankForks, HashMap<u64, ForkProgress>) {
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
voting_keypair: _,
} = create_genesis_config(1_000_000_000);
// Initialize BankForks
for keypairs in validator_keypairs.values() {
let node_pubkey = keypairs.node_keypair.pubkey();
let vote_pubkey = keypairs.vote_keypair.pubkey();
let stake_key = Pubkey::new_rand();
let vote_account = vote_state::create_account(&vote_pubkey, &node_pubkey, 0, 100);
let stake_account = stake_state::create_account(
&Pubkey::new_rand(),
&vote_pubkey,
&vote_account,
&genesis_config.rent,
100,
);
genesis_config.accounts.extend(vec![
(vote_pubkey, vote_account.clone()),
(stake_key, stake_account),
]);
}
let bank0 = Bank::new(&genesis_config);
for pubkey in validator_keypairs.keys() {
bank0.transfer(10_000, &mint_keypair, pubkey).unwrap();
}
bank0.freeze();
let mut progress = HashMap::new();
progress.insert(0, ForkProgress::new(bank0.last_blockhash()));
(BankForks::new(0, bank0), progress)
}
fn gen_stakes(stake_votes: &[(u64, &[u64])]) -> Vec<(Pubkey, (u64, Account))> {
let mut stakes = vec![];
@ -478,6 +756,194 @@ mod test {
stakes
}
fn can_progress_on_fork(
my_pubkey: &Pubkey,
tower: &mut Tower,
start_slot: u64,
num_slots: u64,
bank_forks: &RwLock<BankForks>,
cluster_votes: &mut HashMap<Pubkey, Vec<u64>>,
keypairs: &HashMap<Pubkey, ValidatorKeypairs>,
progress: &mut HashMap<u64, ForkProgress>,
) -> bool {
// Check that within some reasonable time, validator can make a new
// root on this fork
let old_root = tower.root();
let mut main_fork = tr(start_slot);
let mut tip = main_fork.root_mut();
for i in 1..num_slots {
tip.push_front(tr(start_slot + i));
tip = tip.first_mut().unwrap();
}
let mut voting_simulator = VoteSimulator::new(&main_fork);
for i in 1..num_slots {
voting_simulator.simulate_vote(
i + start_slot,
&bank_forks,
cluster_votes,
&keypairs,
keypairs.get(&my_pubkey).unwrap(),
progress,
tower,
);
if old_root != tower.root() {
return true;
}
}
false
}
#[test]
fn test_simple_votes() {
let node_keypair = Keypair::new();
let vote_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let mut keypairs = HashMap::new();
keypairs.insert(
node_pubkey,
ValidatorKeypairs::new(node_keypair, vote_keypair),
);
// Initialize BankForks
let (bank_forks, mut progress) = initialize_state(&keypairs);
let bank_forks = RwLock::new(bank_forks);
// Create the tree of banks
let forks = tr(0) / (tr(1) / (tr(2) / (tr(3) / (tr(4) / tr(5)))));
// Set the voting behavior
let mut voting_simulator = VoteSimulator::new(&forks);
let votes = vec![0, 1, 2, 3, 4, 5];
// Simulate the votes
let mut tower = Tower::new_with_key(&node_pubkey);
let mut cluster_votes = HashMap::new();
for vote in votes {
assert!(voting_simulator
.simulate_vote(
vote,
&bank_forks,
&mut cluster_votes,
&keypairs,
keypairs.get(&node_pubkey).unwrap(),
&mut progress,
&mut tower,
)
.is_empty());
}
for i in 0..5 {
assert_eq!(tower.lockouts.votes[i].slot as usize, i);
assert_eq!(tower.lockouts.votes[i].confirmation_count as usize, 6 - i);
}
}
#[test]
fn test_double_partition() {
let node_keypair = Keypair::new();
let vote_keypair = Keypair::new();
let node_pubkey = node_keypair.pubkey();
let vote_pubkey = vote_keypair.pubkey();
let mut keypairs = HashMap::new();
info!("my_pubkey: {}", node_pubkey);
keypairs.insert(
node_pubkey,
ValidatorKeypairs::new(node_keypair, vote_keypair),
);
// Create the tree of banks in a BankForks object
let forks = tr(0)
/ (tr(1)
/ (tr(2)
/ (tr(3)
/ (tr(4)
/ (tr(5)
/ (tr(6)
/ (tr(7)
/ (tr(8)
/ (tr(9)
// Minor fork 1
/ (tr(10) / (tr(11) / (tr(12) / (tr(13) / (tr(14))))))
/ (tr(43)
/ (tr(44)
// Minor fork 2
/ (tr(45) / (tr(46) / (tr(47) / (tr(48) / (tr(49) / (tr(50)))))))
/ (tr(110)))))))))))));
// Set the voting behavior
let mut voting_simulator = VoteSimulator::new(&forks);
let mut votes: Vec<Slot> = vec![];
// Vote on the first minor fork
votes.extend((0..=14).into_iter());
// Come back to the main fork
votes.extend((43..=44).into_iter());
// Vote on the second minor fork
votes.extend((45..=50).into_iter());
let mut cluster_votes: HashMap<Pubkey, Vec<Slot>> = HashMap::new();
let (bank_forks, mut progress) = initialize_state(&keypairs);
let bank_forks = RwLock::new(bank_forks);
// Simulate the votes. Should fail on trying to come back to the main fork
// at 106 exclusively due to threshold failure
let mut tower = Tower::new_with_key(&node_pubkey);
for vote in &votes {
// All these votes should be ok
assert!(voting_simulator
.simulate_vote(
*vote,
&bank_forks,
&mut cluster_votes,
&keypairs,
keypairs.get(&node_pubkey).unwrap(),
&mut progress,
&mut tower,
)
.is_empty());
}
// Try to come back to main fork
let next_unlocked_slot = 110;
assert!(voting_simulator
.simulate_vote(
next_unlocked_slot,
&bank_forks,
&mut cluster_votes,
&keypairs,
keypairs.get(&node_pubkey).unwrap(),
&mut progress,
&mut tower,
)
.is_empty());
info!("local tower: {:#?}", tower.lockouts.votes);
let vote_accounts = bank_forks
.read()
.unwrap()
.get(next_unlocked_slot)
.unwrap()
.vote_accounts();
let observed = vote_accounts.get(&vote_pubkey).unwrap();
let state = VoteState::from(&observed.1).unwrap();
info!("observed tower: {:#?}", state.votes);
assert!(can_progress_on_fork(
&node_pubkey,
&mut tower,
next_unlocked_slot,
200,
&bank_forks,
&mut cluster_votes,
&keypairs,
&mut progress
));
}
#[test]
fn test_collect_vote_lockouts_sums() {
//two accounts voting for slot 0 with 1 token staked

View File

@ -2,7 +2,7 @@ use solana_sdk::pubkey::Pubkey;
#[cfg(test)]
use solana_sdk::rpc_port;
#[cfg(test)]
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::timing::timestamp;
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use std::net::{IpAddr, SocketAddr};
@ -17,7 +17,7 @@ pub struct ContactInfo {
pub tvu: SocketAddr,
/// address to forward shreds to
pub tvu_forwards: SocketAddr,
/// address to send repairs to
/// address to send repair responses to
pub repair: SocketAddr,
/// transactions address
pub tpu: SocketAddr,
@ -29,6 +29,8 @@ pub struct ContactInfo {
pub rpc: SocketAddr,
/// websocket for JSON-RPC push notifications
pub rpc_pubsub: SocketAddr,
/// address to send repair requests to
pub serve_repair: SocketAddr,
/// latest wallclock picked
pub wallclock: u64,
/// node shred version
@ -85,6 +87,7 @@ impl Default for ContactInfo {
storage_addr: socketaddr_any!(),
rpc: socketaddr_any!(),
rpc_pubsub: socketaddr_any!(),
serve_repair: socketaddr_any!(),
wallclock: 0,
shred_version: 0,
}
@ -104,6 +107,7 @@ impl ContactInfo {
storage_addr: SocketAddr,
rpc: SocketAddr,
rpc_pubsub: SocketAddr,
serve_repair: SocketAddr,
now: u64,
) -> Self {
Self {
@ -117,6 +121,7 @@ impl ContactInfo {
storage_addr,
rpc,
rpc_pubsub,
serve_repair,
wallclock: now,
shred_version: 0,
}
@ -134,6 +139,7 @@ impl ContactInfo {
socketaddr!("127.0.0.1:1240"),
socketaddr!("127.0.0.1:1241"),
socketaddr!("127.0.0.1:1242"),
socketaddr!("127.0.0.1:1243"),
now,
)
}
@ -154,6 +160,7 @@ impl ContactInfo {
addr,
addr,
addr,
addr,
0,
)
}
@ -174,6 +181,7 @@ impl ContactInfo {
let repair = next_port(&bind_addr, 5);
let rpc_addr = SocketAddr::new(bind_addr.ip(), rpc_port::DEFAULT_RPC_PORT);
let rpc_pubsub_addr = SocketAddr::new(bind_addr.ip(), rpc_port::DEFAULT_RPC_PUBSUB_PORT);
let serve_repair = next_port(&bind_addr, 6);
Self::new(
pubkey,
gossip_addr,
@ -185,6 +193,7 @@ impl ContactInfo {
"0.0.0.0:0".parse().unwrap(),
rpc_addr,
rpc_pubsub_addr,
serve_repair,
timestamp(),
)
}
@ -209,6 +218,7 @@ impl ContactInfo {
daddr,
daddr,
daddr,
daddr,
timestamp(),
)
}
@ -267,6 +277,7 @@ mod tests {
assert!(ci.rpc_pubsub.ip().is_unspecified());
assert!(ci.tpu.ip().is_unspecified());
assert!(ci.storage_addr.ip().is_unspecified());
assert!(ci.serve_repair.ip().is_unspecified());
}
#[test]
fn test_multicast() {
@ -278,6 +289,7 @@ mod tests {
assert!(ci.rpc_pubsub.ip().is_multicast());
assert!(ci.tpu.ip().is_multicast());
assert!(ci.storage_addr.ip().is_multicast());
assert!(ci.serve_repair.ip().is_multicast());
}
#[test]
fn test_entry_point() {
@ -290,6 +302,7 @@ mod tests {
assert!(ci.rpc_pubsub.ip().is_unspecified());
assert!(ci.tpu.ip().is_unspecified());
assert!(ci.storage_addr.ip().is_unspecified());
assert!(ci.serve_repair.ip().is_unspecified());
}
#[test]
fn test_socketaddr() {
@ -302,7 +315,9 @@ mod tests {
assert_eq!(ci.rpc.port(), rpc_port::DEFAULT_RPC_PORT);
assert_eq!(ci.rpc_pubsub.port(), rpc_port::DEFAULT_RPC_PUBSUB_PORT);
assert!(ci.storage_addr.ip().is_unspecified());
assert_eq!(ci.serve_repair.port(), 16);
}
#[test]
fn replayed_data_new_with_socketaddr_with_pubkey() {
let keypair = Keypair::new();
@ -323,6 +338,9 @@ mod tests {
d1.rpc_pubsub,
socketaddr!(format!("127.0.0.1:{}", rpc_port::DEFAULT_RPC_PUBSUB_PORT))
);
assert_eq!(d1.tvu_forwards, socketaddr!("127.0.0.1:1238"));
assert_eq!(d1.repair, socketaddr!("127.0.0.1:1239"));
assert_eq!(d1.serve_repair, socketaddr!("127.0.0.1:1240"));
}
#[test]

View File

@ -3,11 +3,13 @@
//! designed to run with a simulator or over a UDP network connection with messages up to a
//! packet::PACKET_DATA_SIZE size.
use crate::crds::{Crds, VersionedCrdsValue};
use crate::crds_gossip_error::CrdsGossipError;
use crate::crds_gossip_pull::{CrdsFilter, CrdsGossipPull};
use crate::crds_gossip_push::{CrdsGossipPush, CRDS_GOSSIP_NUM_ACTIVE};
use crate::crds_value::{CrdsValue, CrdsValueLabel};
use crate::{
crds::{Crds, VersionedCrdsValue},
crds_gossip_error::CrdsGossipError,
crds_gossip_pull::{CrdsFilter, CrdsGossipPull},
crds_gossip_push::{CrdsGossipPush, CRDS_GOSSIP_NUM_ACTIVE},
crds_value::{CrdsValue, CrdsValueLabel},
};
use solana_sdk::pubkey::Pubkey;
use std::collections::{HashMap, HashSet};
@ -156,11 +158,12 @@ impl CrdsGossip {
pub fn process_pull_response(
&mut self,
from: &Pubkey,
timeouts: &HashMap<Pubkey, u64>,
response: Vec<CrdsValue>,
now: u64,
) -> usize {
self.pull
.process_pull_response(&mut self.crds, from, response, now)
.process_pull_response(&mut self.crds, from, timeouts, response, now)
}
pub fn make_timeouts_test(&self) -> HashMap<Pubkey, u64> {

View File

@ -25,6 +25,8 @@ use std::collections::HashMap;
use std::collections::VecDeque;
pub const CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS: u64 = 15000;
// The maximum age of a value received over pull responses
pub const CRDS_GOSSIP_PULL_MSG_TIMEOUT_MS: u64 = 60000;
pub const FALSE_RATE: f64 = 0.1f64;
pub const KEYS: f64 = 8f64;
@ -117,6 +119,7 @@ pub struct CrdsGossipPull {
/// hash and insert time
purged_values: VecDeque<(Hash, u64)>,
pub crds_timeout: u64,
pub msg_timeout: u64,
}
impl Default for CrdsGossipPull {
@ -125,6 +128,7 @@ impl Default for CrdsGossipPull {
purged_values: VecDeque::new(),
pull_request_time: HashMap::new(),
crds_timeout: CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS,
msg_timeout: CRDS_GOSSIP_PULL_MSG_TIMEOUT_MS,
}
}
}
@ -210,12 +214,56 @@ impl CrdsGossipPull {
&mut self,
crds: &mut Crds,
from: &Pubkey,
timeouts: &HashMap<Pubkey, u64>,
response: Vec<CrdsValue>,
now: u64,
) -> usize {
let mut failed = 0;
for r in response {
let owner = r.label().pubkey();
// Check if the crds value is older than the msg_timeout
if now
> r.wallclock()
.checked_add(self.msg_timeout)
.unwrap_or_else(|| 0)
|| now + self.msg_timeout < r.wallclock()
{
match &r.label() {
CrdsValueLabel::ContactInfo(_) => {
// Check if this ContactInfo is actually too old, it's possible that it has
// stake and so might have a longer effective timeout
let timeout = *timeouts
.get(&owner)
.unwrap_or_else(|| timeouts.get(&Pubkey::default()).unwrap());
if now > r.wallclock().checked_add(timeout).unwrap_or_else(|| 0)
|| now + timeout < r.wallclock()
{
inc_new_counter_warn!(
"cluster_info-gossip_pull_response_value_timeout",
1
);
failed += 1;
continue;
}
}
_ => {
// Before discarding this value, check if a ContactInfo for the owner
// exists in the table. If it doesn't, that implies that this value can be discarded
if crds.lookup(&CrdsValueLabel::ContactInfo(owner)).is_none() {
inc_new_counter_warn!(
"cluster_info-gossip_pull_response_value_timeout",
1
);
failed += 1;
continue;
} else {
// Silently insert this old value without bumping record timestamps
failed += crds.insert(r, now).is_err() as usize;
continue;
}
}
}
}
let old = crds.insert(r, now);
failed += old.is_err() as usize;
old.ok().map(|opt| {
@ -322,8 +370,9 @@ impl CrdsGossipPull {
mod test {
use super::*;
use crate::contact_info::ContactInfo;
use crate::crds_value::CrdsData;
use crate::crds_value::{CrdsData, Vote};
use itertools::Itertools;
use solana_perf::test_tx::test_tx;
use solana_sdk::hash::hash;
use solana_sdk::packet::PACKET_DATA_SIZE;
@ -534,8 +583,13 @@ mod test {
continue;
}
assert_eq!(rsp.len(), 1);
let failed =
node.process_pull_response(&mut node_crds, &node_pubkey, rsp.pop().unwrap(), 1);
let failed = node.process_pull_response(
&mut node_crds,
&node_pubkey,
&node.make_timeouts_def(&node_pubkey, &HashMap::new(), 0, 1),
rsp.pop().unwrap(),
1,
);
assert_eq!(failed, 0);
assert_eq!(
node_crds
@ -675,4 +729,87 @@ mod test {
.collect();
assert_eq!(masks.len(), 2u64.pow(mask_bits) as usize)
}
#[test]
fn test_process_pull_response() {
let mut node_crds = Crds::default();
let mut node = CrdsGossipPull::default();
let peer_pubkey = Pubkey::new_rand();
let peer_entry = CrdsValue::new_unsigned(CrdsData::ContactInfo(
ContactInfo::new_localhost(&peer_pubkey, 0),
));
let mut timeouts = HashMap::new();
timeouts.insert(Pubkey::default(), node.crds_timeout);
timeouts.insert(peer_pubkey, node.msg_timeout + 1);
// inserting a fresh value should be fine.
assert_eq!(
node.process_pull_response(
&mut node_crds,
&peer_pubkey,
&timeouts,
vec![peer_entry.clone()],
1,
),
0
);
let mut node_crds = Crds::default();
let unstaked_peer_entry = CrdsValue::new_unsigned(CrdsData::ContactInfo(
ContactInfo::new_localhost(&peer_pubkey, 0),
));
// check that old contact infos fail if they are too old, regardless of "timeouts"
assert_eq!(
node.process_pull_response(
&mut node_crds,
&peer_pubkey,
&timeouts,
vec![peer_entry.clone(), unstaked_peer_entry],
node.msg_timeout + 100,
),
2
);
let mut node_crds = Crds::default();
// check that old contact infos can still land as long as they have a "timeouts" entry
assert_eq!(
node.process_pull_response(
&mut node_crds,
&peer_pubkey,
&timeouts,
vec![peer_entry.clone()],
node.msg_timeout + 1,
),
0
);
// construct something that's not a contact info
let peer_vote =
CrdsValue::new_unsigned(CrdsData::Vote(0, Vote::new(&peer_pubkey, test_tx(), 0)));
// check that older CrdsValues (non-ContactInfos) infos pass even if are too old,
// but a recent contact info (inserted above) exists
assert_eq!(
node.process_pull_response(
&mut node_crds,
&peer_pubkey,
&timeouts,
vec![peer_vote.clone()],
node.msg_timeout + 1,
),
0
);
let mut node_crds = Crds::default();
// without a contact info, inserting an old value should fail
assert_eq!(
node.process_pull_response(
&mut node_crds,
&peer_pubkey,
&timeouts,
vec![peer_vote.clone()],
node.msg_timeout + 1,
),
1
);
}
}

View File

@ -8,29 +8,31 @@
//! the local nodes wallclock window they are drooped silently.
//! 2. The prune set is stored in a Bloom filter.
use crate::contact_info::ContactInfo;
use crate::crds::{Crds, VersionedCrdsValue};
use crate::crds_gossip::{get_stake, get_weight, CRDS_GOSSIP_DEFAULT_BLOOM_ITEMS};
use crate::crds_gossip_error::CrdsGossipError;
use crate::crds_value::{CrdsValue, CrdsValueLabel};
use crate::weighted_shuffle::weighted_shuffle;
use crate::{
contact_info::ContactInfo,
crds::{Crds, VersionedCrdsValue},
crds_gossip::{get_stake, get_weight, CRDS_GOSSIP_DEFAULT_BLOOM_ITEMS},
crds_gossip_error::CrdsGossipError,
crds_value::{CrdsValue, CrdsValueLabel},
weighted_shuffle::weighted_shuffle,
};
use bincode::serialized_size;
use indexmap::map::IndexMap;
use itertools::Itertools;
use rand;
use rand::seq::SliceRandom;
use rand::{thread_rng, RngCore};
use rand::{self, seq::SliceRandom, thread_rng, RngCore};
use solana_runtime::bloom::Bloom;
use solana_sdk::hash::Hash;
use solana_sdk::packet::PACKET_DATA_SIZE;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::timing::timestamp;
use std::cmp;
use std::collections::{HashMap, HashSet};
use solana_sdk::{hash::Hash, packet::PACKET_DATA_SIZE, pubkey::Pubkey, timing::timestamp};
use std::{
cmp,
collections::{HashMap, HashSet},
};
pub const CRDS_GOSSIP_NUM_ACTIVE: usize = 30;
pub const CRDS_GOSSIP_PUSH_FANOUT: usize = 6;
pub const CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS: u64 = 5000;
// With a fanout of 6, a 1000 node cluster should only take ~4 hops to converge.
// However since pushes are stake weighed, some trailing nodes
// might need more time to receive values. 30 seconds should be plenty.
pub const CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS: u64 = 30000;
pub const CRDS_GOSSIP_PRUNE_MSG_TIMEOUT_MS: u64 = 500;
pub const CRDS_GOSSIP_PRUNE_STAKE_THRESHOLD_PCT: f64 = 0.15;
@ -135,7 +137,12 @@ impl CrdsGossipPush {
value: CrdsValue,
now: u64,
) -> Result<Option<VersionedCrdsValue>, CrdsGossipError> {
if now > value.wallclock() + self.msg_timeout {
if now
> value
.wallclock()
.checked_add(self.msg_timeout)
.unwrap_or_else(|| 0)
{
return Err(CrdsGossipError::PushMessageTimeout);
}
if now + self.msg_timeout < value.wallclock() {

View File

@ -1,18 +1,23 @@
use crate::contact_info::ContactInfo;
use bincode::{serialize, serialized_size};
use solana_sdk::clock::Slot;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, Signable, Signature};
use solana_sdk::transaction::Transaction;
use std::borrow::Borrow;
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::collections::HashSet;
use std::fmt;
use solana_sdk::{
clock::Slot,
hash::Hash,
pubkey::Pubkey,
signature::{Keypair, Signable, Signature},
transaction::Transaction,
};
use std::{
borrow::{Borrow, Cow},
collections::{BTreeSet, HashSet},
fmt,
};
pub type VoteIndex = u8;
pub const MAX_VOTES: VoteIndex = 32;
pub type EpochSlotIndex = u8;
/// CrdsValue that is replicated across the cluster
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct CrdsValue {
@ -50,15 +55,51 @@ impl Signable for CrdsValue {
}
/// CrdsData that defines the different types of items CrdsValues can hold
/// * Merge Strategy - Latest wallclock is picked
#[allow(clippy::large_enum_variant)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum CrdsData {
/// * Merge Strategy - Latest wallclock is picked
ContactInfo(ContactInfo),
/// * Merge Strategy - Latest wallclock is picked
Vote(VoteIndex, Vote),
/// * Merge Strategy - Latest wallclock is picked
EpochSlots(EpochSlots),
EpochSlots(EpochSlotIndex, EpochSlots),
SnapshotHash(SnapshotHash),
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum CompressionType {
Uncompressed,
GZip,
BZip2,
}
impl Default for CompressionType {
fn default() -> Self {
Self::Uncompressed
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub struct EpochIncompleteSlots {
pub first: Slot,
pub compression: CompressionType,
pub compressed_list: Vec<u8>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct SnapshotHash {
pub from: Pubkey,
pub hashes: Vec<(Slot, Hash)>,
pub wallclock: u64,
}
impl SnapshotHash {
pub fn new(from: Pubkey, hashes: Vec<(Slot, Hash)>, wallclock: u64) -> Self {
Self {
from,
hashes,
wallclock,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
@ -67,6 +108,7 @@ pub struct EpochSlots {
pub root: Slot,
pub lowest: Slot,
pub slots: BTreeSet<Slot>,
pub stash: Vec<EpochIncompleteSlots>,
pub wallclock: u64,
}
@ -76,6 +118,7 @@ impl EpochSlots {
root: Slot,
lowest: Slot,
slots: BTreeSet<Slot>,
stash: Vec<EpochIncompleteSlots>,
wallclock: u64,
) -> Self {
Self {
@ -83,6 +126,7 @@ impl EpochSlots {
root,
lowest,
slots,
stash,
wallclock,
}
}
@ -112,6 +156,7 @@ pub enum CrdsValueLabel {
ContactInfo(Pubkey),
Vote(VoteIndex, Pubkey),
EpochSlots(Pubkey),
SnapshotHash(Pubkey),
}
impl fmt::Display for CrdsValueLabel {
@ -120,6 +165,7 @@ impl fmt::Display for CrdsValueLabel {
CrdsValueLabel::ContactInfo(_) => write!(f, "ContactInfo({})", self.pubkey()),
CrdsValueLabel::Vote(ix, _) => write!(f, "Vote({}, {})", ix, self.pubkey()),
CrdsValueLabel::EpochSlots(_) => write!(f, "EpochSlots({})", self.pubkey()),
CrdsValueLabel::SnapshotHash(_) => write!(f, "SnapshotHash({})", self.pubkey()),
}
}
}
@ -130,6 +176,7 @@ impl CrdsValueLabel {
CrdsValueLabel::ContactInfo(p) => *p,
CrdsValueLabel::Vote(_, p) => *p,
CrdsValueLabel::EpochSlots(p) => *p,
CrdsValueLabel::SnapshotHash(p) => *p,
}
}
}
@ -154,21 +201,24 @@ impl CrdsValue {
match &self.data {
CrdsData::ContactInfo(contact_info) => contact_info.wallclock,
CrdsData::Vote(_, vote) => vote.wallclock,
CrdsData::EpochSlots(vote) => vote.wallclock,
CrdsData::EpochSlots(_, vote) => vote.wallclock,
CrdsData::SnapshotHash(hash) => hash.wallclock,
}
}
pub fn pubkey(&self) -> Pubkey {
match &self.data {
CrdsData::ContactInfo(contact_info) => contact_info.id,
CrdsData::Vote(_, vote) => vote.from,
CrdsData::EpochSlots(slots) => slots.from,
CrdsData::EpochSlots(_, slots) => slots.from,
CrdsData::SnapshotHash(hash) => hash.from,
}
}
pub fn label(&self) -> CrdsValueLabel {
match &self.data {
CrdsData::ContactInfo(_) => CrdsValueLabel::ContactInfo(self.pubkey()),
CrdsData::Vote(ix, _) => CrdsValueLabel::Vote(*ix, self.pubkey()),
CrdsData::EpochSlots(_) => CrdsValueLabel::EpochSlots(self.pubkey()),
CrdsData::EpochSlots(_, _) => CrdsValueLabel::EpochSlots(self.pubkey()),
CrdsData::SnapshotHash(_) => CrdsValueLabel::SnapshotHash(self.pubkey()),
}
}
pub fn contact_info(&self) -> Option<&ContactInfo> {
@ -193,15 +243,24 @@ impl CrdsValue {
pub fn epoch_slots(&self) -> Option<&EpochSlots> {
match &self.data {
CrdsData::EpochSlots(slots) => Some(slots),
CrdsData::EpochSlots(_, slots) => Some(slots),
_ => None,
}
}
pub fn snapshot_hash(&self) -> Option<&SnapshotHash> {
match &self.data {
CrdsData::SnapshotHash(slots) => Some(slots),
_ => None,
}
}
/// Return all the possible labels for a record identified by Pubkey.
pub fn record_labels(key: &Pubkey) -> Vec<CrdsValueLabel> {
let mut labels = vec![
CrdsValueLabel::ContactInfo(*key),
CrdsValueLabel::EpochSlots(*key),
CrdsValueLabel::SnapshotHash(*key),
];
labels.extend((0..MAX_VOTES).map(|ix| CrdsValueLabel::Vote(ix, *key)));
labels
@ -246,18 +305,19 @@ mod test {
use crate::contact_info::ContactInfo;
use bincode::deserialize;
use solana_perf::test_tx::test_tx;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::timing::timestamp;
#[test]
fn test_labels() {
let mut hits = [false; 2 + MAX_VOTES as usize];
let mut hits = [false; 3 + MAX_VOTES as usize];
// this method should cover all the possible labels
for v in &CrdsValue::record_labels(&Pubkey::default()) {
match v {
CrdsValueLabel::ContactInfo(_) => hits[0] = true,
CrdsValueLabel::EpochSlots(_) => hits[1] = true,
CrdsValueLabel::Vote(ix, _) => hits[*ix as usize + 2] = true,
CrdsValueLabel::SnapshotHash(_) => hits[2] = true,
CrdsValueLabel::Vote(ix, _) => hits[*ix as usize + 3] = true,
}
}
assert!(hits.iter().all(|x| *x));
@ -277,13 +337,10 @@ mod test {
let key = v.clone().vote().unwrap().from;
assert_eq!(v.label(), CrdsValueLabel::Vote(0, key));
let v = CrdsValue::new_unsigned(CrdsData::EpochSlots(EpochSlots::new(
Pubkey::default(),
let v = CrdsValue::new_unsigned(CrdsData::EpochSlots(
0,
0,
BTreeSet::new(),
0,
)));
EpochSlots::new(Pubkey::default(), 0, 0, BTreeSet::new(), vec![], 0),
));
assert_eq!(v.wallclock(), 0);
let key = v.clone().epoch_slots().unwrap().from;
assert_eq!(v.label(), CrdsValueLabel::EpochSlots(key));
@ -304,13 +361,10 @@ mod test {
));
verify_signatures(&mut v, &keypair, &wrong_keypair);
let btreeset: BTreeSet<Slot> = vec![1, 2, 3, 6, 8].into_iter().collect();
v = CrdsValue::new_unsigned(CrdsData::EpochSlots(EpochSlots::new(
keypair.pubkey(),
v = CrdsValue::new_unsigned(CrdsData::EpochSlots(
0,
0,
btreeset,
timestamp(),
)));
EpochSlots::new(keypair.pubkey(), 0, 0, btreeset, vec![], timestamp()),
));
verify_signatures(&mut v, &keypair, &wrong_keypair);
}

View File

@ -41,7 +41,7 @@ impl GenKeys {
mod tests {
use super::*;
pub use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::KeypairUtil;
use solana_sdk::signature::Signer;
use std::collections::HashSet;
#[test]

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