Compare commits

...

515 Commits

Author SHA1 Message Date
8df4cf2895 Don't advertise the snapshot that the node was loaded from
snapshot_packager_service will remove this snapshot hash from gossip
when it starts
2020-03-02 19:16:06 -07:00
dad62e132e Remove granularity from genesis (bp #8514) (#8518)
automerge
2020-03-02 15:42:46 -08:00
0d4131ae68 Bump version to 1.0.2 2020-03-02 15:38:26 -07:00
a2539e1892 Allow stake lockup fields to be updated independently (#8568) (#8574)
automerge
2020-03-02 14:19:18 -08:00
210659e6c3 Only gossip packaged snapshots
(cherry picked from commit 42c5c59800)
2020-03-02 14:21:04 -07:00
15a0fb1fa9 --wait-for-supermajority now requires a SLOT
(cherry picked from commit 13551885c2)
2020-03-02 13:40:52 -07:00
4db31f5b48 Add ---no-untrusted-rpc flag
(cherry picked from commit d677e83ed4)
2020-03-02 11:53:15 -07:00
b38a535c63 Hack to skip cleanup_dead_slots upon snapshot load (#8562)
automerge
2020-03-02 10:40:41 -08:00
218b02aaf8 Demote gossip responder error log messages to info! 2020-03-01 10:42:32 -07:00
d6e7cbd4e8 feat: implement websocket_url as a get/set-able global parameter w/ value computation (#8553)
automerge
2020-03-01 01:09:09 -08:00
f2fda14333 Reduce max snapshot hashes to stay under MTU (bp #8541) (#8544)
automerge
2020-02-29 09:17:09 -08:00
1c576d4a68 Upgrade to Rust 1.41.1
(cherry picked from commit 7d27be2a73)
2020-02-29 09:27:11 -07:00
f6232e1b3c Fix skipping own leader slots (#8533) (#8540)
automerge
2020-02-29 01:27:46 -08:00
ad71fa3f12 rpc: GET for /snapshot.tar.bz2 now redirects to the latest snapshot (bp #8536) (#8538)
automerge
2020-02-28 23:45:15 -08:00
9c326c7c71 Ensure the validator's identity pubkey is not provided as a --trusted-validator (#8525) (#8527)
automerge
2020-02-27 22:07:07 -08:00
ac545cadaf Add versioning (#8348) (#8524)
automerge
2020-02-27 20:12:49 -08:00
082d8fff36 Use legit solana message in verify (#8513) (#8523)
automerge
2020-02-27 19:36:37 -08:00
2c3632a042 Determine vote_state ahead of time (#8303) (#8521)
automerge
2020-02-27 18:32:27 -08:00
7b23e79922 Add snapshot hash of full accounts state (#8295) (#8515)
* Add snapshot hash of full accounts state

* Use normal hashing for the accounts delta state

* Add merkle

(cherry picked from commit 947a339714)

Co-authored-by: sakridge <sakridge@gmail.com>
2020-02-27 17:36:28 -08:00
9afd14a0c6 Import Tour de SOL docs (#8516) (#8519)
automerge
2020-02-27 17:15:36 -08:00
f42aa34ab6 Reorder InstructionError to remain compatible with v0.23
(cherry picked from commit 7dac8e2dde)
2020-02-27 18:07:00 -07:00
43af91ff4b Ledger messaging cleanup (#8506) (#8508)
automerge
2020-02-27 12:38:09 -08:00
a920a0f946 Fix cluster economics figures and spelling in docs (#8502) (#8505)
automerge
2020-02-27 02:49:10 -08:00
4a8a6d0b3f Remove loop (#8496)
automerge
2020-02-27 00:38:27 -08:00
a64b8a2705 Rename snapshot.tar.bz2 to snapshot-<slot>-<hash>.tar.bz2 (bp #8482) (#8501)
automerge
2020-02-26 23:31:44 -08:00
0198f9e8af Peg snapshot version to 1.0.0 2020-02-26 22:07:50 -07:00
1582a3a927 Cargo.lock 2020-02-26 21:17:40 -07:00
e713f0e5fb Update voting simulation (#8489) 2020-02-26 20:00:20 -08:00
77031000c4 Choose more appropriate options for pubsub websocket server (#8354) (#8492)
automerge
2020-02-26 18:23:50 -08:00
635a962fba Reference the v1.0.0 installer 2020-02-26 19:20:58 -07:00
c59ec2dcff Add flag to confirm key on device (#8478) (#8490)
automerge
2020-02-26 17:31:52 -08:00
abc6c5e264 Limit leader schedule search space (#8468) (#8486)
automerge
2020-02-26 16:11:14 -08:00
87cfac12dd Validate the genesis config downloaded over RPC before accepting it (bp #8474) (#8481)
automerge
2020-02-26 15:12:06 -08:00
60b43e34b6 Ledger hardware wallet docs (#8472) (#8479)
automerge
2020-02-26 14:42:08 -08:00
9ab6222f03 Move docs from book/ to docs/ (#8469) (#8471)
automerge
2020-02-26 08:01:44 -08:00
2298dd5c07 Use runtime executor to send pubsub notifications (#8353) (#8465)
automerge
2020-02-25 21:45:18 -08:00
822d166115 live-slots now displays the rate the root slot is advancing (#8464)
automerge
2020-02-25 21:18:10 -08:00
8f71580615 Allow withdrawer to change the authorized stake key (#8456) (#8462)
automerge
2020-02-25 19:30:59 -08:00
cc3352ff06 Ledger key path rework (#8453) (#8457)
automerge
2020-02-25 18:00:53 -08:00
8f5928b7c7 Promote dangerous cond. from just warning to panic (#8439) (#8449)
automerge
2020-02-25 15:31:06 -08:00
888e9617ff 🐌🐌 Publish crates for even longer longer 2020-02-25 09:23:20 -07:00
4695c4cf7d Add --no-check-vote-account argument (#8430) (#8435)
automerge
2020-02-25 00:23:50 -08:00
bbfc56ff7f Make solana root key accessible on Ledger (#8421) (#8431)
automerge
2020-02-24 22:28:28 -08:00
4103d99018 Bump version to 1.0.1 2020-02-24 23:24:27 -07:00
c375ce1fcd CLI: collect and deduplicate signers (#8398) (#8423)
automerge
2020-02-24 17:29:34 -08:00
df813b31c5 Fix SDK deps 2020-02-24 17:30:46 -07:00
7db92e2951 Drop print- prefix from slot/accounts command
(cherry picked from commit 89baa94002)
2020-02-24 17:28:36 -07:00
6585518f78 Add genesis subcommand
(cherry picked from commit 1ef3478709)
2020-02-24 17:28:36 -07:00
6398e256e4 Move shred_version module to sdk/
(cherry picked from commit 73063544bd)
2020-02-24 17:28:36 -07:00
b64ebb45e7 validator: snapshot fetching cleanup (bp #8413) (#8417)
automerge
2020-02-24 15:22:34 -08:00
307ac66d9c Reinstate create-stale-account w/ seed test (#8401) (#8402)
automerge
2020-02-22 10:56:39 -08:00
dc02f2ea8b Add support for large transactions with Ledger Wallet (#8394) 2020-02-21 23:24:56 -07:00
b7386f9d84 Add --trusted-validator support for snapshot hash validation (#8390) 2020-02-21 18:42:24 -08:00
223f9707ca \ 2020-02-21 18:09:36 -07:00
ea5b00364f Add --enable-warmup-epochs flag 2020-02-21 16:59:43 -07:00
fb98df76b7 4x DEFAULT_MAX_LEDGER_SLOTS to give nodes 3 hours of slots to repair from (#8388)
automerge
2020-02-21 15:04:02 -08:00
4ddbf8d509 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>
2020-02-21 14:55:53 -07:00
aa80f69171 Promote some datapoints to info to fix dashboard (#8381)
automerge
2020-02-21 13:41:49 -08:00
0ace22d03f Optimize account verification (#8385) 2020-02-21 13:28:35 -08:00
0e6aca5a7e Reorganize message processor in prep for cross-program-invocation (#8338) 2020-02-21 11:30:00 -08:00
3f04226864 Update unlocks (#8363) 2020-02-21 11:23:03 -07:00
d308eed136 Bump ctrlc from 3.1.3 to 3.1.4
Bumps [ctrlc](https://github.com/Detegr/rust-ctrlc) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/commits/3.1.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-21 09:00:14 -07:00
ed1149c8e0 Update supported backport labels 2020-02-21 00:47:44 -07:00
48f58a88bc Bump version to 1.0.0 2020-02-20 23:52:19 -07:00
d238371b0c Correct missing entry handling to avoid bad warns (#8339)
* Correct missing entry handling to avoid bad warns

* Pass storage entries to AccountStorageSerialize

* Fix CI.....

* Add tests and reorder condition for cheapest first

* Remove unneeded reference
2020-02-21 15:27:55 +09:00
0b7e8d0162 Add handling for fallible signers (#8367)
automerge
2020-02-20 19:04:53 -08:00
18fd52367e If the node was loaded from a snapshot, advertise it in gossip (#8364)
automerge
2020-02-20 18:53:26 -08:00
2d665da3e1 Flip Stable and Preview enum values 2020-02-20 18:27:33 -07:00
5ef06a9d36 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
2020-02-21 10:19:45 +09:00
f4622d67e9 Submit all metrics in one HTTP POST rather than a HTTP POST per level 2020-02-20 18:12:30 -07:00
b65c9ea544 Bump serial_test_derive from 0.3.2 to 0.4.0 (#8311)
Bumps [serial_test_derive](https://github.com/palfrey/serial_test) from 0.3.2 to 0.4.0.
- [Release notes](https://github.com/palfrey/serial_test/releases)
- [Commits](https://github.com/palfrey/serial_test/compare/v0.3.2...v0.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-20 17:06:00 -07:00
cc7c6c960e Search for the validator with the highest snapshot 2020-02-20 17:04:48 -07:00
01697a9f5c Remove unnecessary arc and mutex for rpc notifications (#8351) 2020-02-21 08:03:46 +08:00
ab361a8073 Rename KeypairUtil to Signer (#8360)
automerge
2020-02-20 13:28:55 -08:00
ec5c02cb7f Book: Add instructions for verifying a paper wallet keypair (#8357) 2020-02-20 14:19:35 -07:00
e8124324ff Support transaction signing by heterogenous lists of keypairs (#8342)
automerge
2020-02-20 12:13:23 -08:00
1720fe6a46 Snapshot hash gossip changes (#8358) 2020-02-20 11:46:13 -08:00
e50bc0d34b Do not compress small incomplete slot list (#8355)
automerge
2020-02-20 09:48:39 -08:00
45774dc4aa Fix comment 2020-02-20 10:32:36 -07:00
ea8d9d1aea Bitwise compress incomplete epoch slots (#8341) 2020-02-19 20:24:09 -08:00
221866f74e Process Gossip in parallel and add an upper limit (#8328) 2020-02-19 21:31:55 -06:00
3e96d59359 Use correct static IP address 2020-02-19 18:15:18 -07:00
8c19b6268c Add Preview operating mode, rename SoftLaunch operating mode to Stable (#8331)
automerge
2020-02-19 16:48:58 -08:00
8ae26867c5 More testnet->devnet 2020-02-19 16:15:38 -07:00
19baaea0da Remove validators from genesis (#8330)
automerge
2020-02-19 14:40:07 -08:00
e3cebcf82d rename testnet.solana.com to devnet.solana.com 2020-02-19 15:33:14 -07:00
ccad5d5aaf change warnings to infos (#8322) 2020-02-19 14:25:49 -08:00
d0bcde001e New Repair Design (#8256)
* New Repair Design
2020-02-19 01:02:09 -08:00
83a8e82626 Remove dead code 2020-02-18 21:08:43 -07:00
7305a1f407 Reformatting 2020-02-18 21:08:43 -07:00
3975c7f8c9 Add --fee-burn-percentage 2020-02-18 17:43:08 -07:00
ac1d075d73 Drop packet if destination is unspecified (0.0.0.0/0) (#8321) 2020-02-18 16:14:20 -08:00
73a278dc64 Factor out creating genesis with vote accounts into a utility function (#8315)
automerge
2020-02-18 02:39:47 -08:00
a042ee609a Update README.md 2020-02-17 22:19:55 -07:00
0d5c1239c6 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-17 12:39:30 -08:00
027ec71aa9 Remove AccountInfo's (De)Serialize (#8313)
automerge
2020-02-17 03:07:36 -08:00
ef718c651e Remove needless uses (#8312)
automerge
2020-02-17 02:43:14 -08:00
fc2a0d53d9 CLI: Add optional airdrop recipient (#8291)
* CLI: Add optional airdrop recipient

* Update book usage page
2020-02-16 11:41:00 -07:00
bb47844ae6 Bump thiserror from 1.0.10 to 1.0.11 (#8288)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.10 to 1.0.11.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.10...1.0.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-15 17:59:13 -07:00
b997d3eb4e Cli: Remove units from various subcommands (#8301)
* Cli: Remove unit arg from various subcommands

* Update book usage page

* Update scripts and docs
2020-02-15 12:53:52 -07:00
9bcca268a3 Add simple gossip DoS test 2020-02-14 22:40:35 -07:00
8a2d4e2f72 Add storage rewards pools in development mode only 2020-02-14 21:12:38 -07:00
335675c51c install: support vX.Y.Z in addition to X.Y.Z (#8297)
automerge
2020-02-14 19:35:40 -08:00
1bf2285fa2 ledger-tool: Add print-accounts command 2020-02-14 19:59:48 -07:00
71f77a8e0a Remove Exchange program's use of GenericError (#8290)
automerge
2020-02-14 14:52:13 -08:00
644a7f9a44 Remove Move Loader's use of GenericError (#8289)
automerge
2020-02-14 14:49:21 -08:00
965361ff69 Remove failure's use of GenericError (#8287)
automerge
2020-02-14 14:07:53 -08:00
4593d333c7 Remove BPF Loader's use of GenericError (#8284) 2020-02-14 13:59:03 -08:00
940519ea5a Remove Native Loader's use of GenericError (#8285) 2020-02-14 13:58:48 -08:00
a0bcbf70d5 Cleanup new_result_with_negative_lamports (#8286) 2020-02-14 13:58:33 -08:00
17fb8258e5 Datapoints overwhelm the metrics queue and blow up ram usage. (#8272)
automerge
2020-02-14 11:11:55 -08:00
c350543b46 Make generate_remote_keypair more generic for potential other remote-wallets (#8274) 2020-02-14 09:38:35 -07:00
5b4ecb01ca Presigner KeypairUtil implementer (#8269)
automerge
2020-02-13 16:53:09 -08:00
28b115497f Update setup-dc-node-1.sh 2020-02-13 14:30:41 -07:00
0604029661 Bump parking_lot from 0.7.1 to 0.10.0 (#8262)
Bumps [parking_lot](https://github.com/Amanieu/parking_lot) from 0.7.1 to 0.10.0.
- [Release notes](https://github.com/Amanieu/parking_lot/releases)
- [Changelog](https://github.com/Amanieu/parking_lot/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Amanieu/parking_lot/compare/0.7.1...0.10.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-13 14:24:37 -07:00
2374cf09e2 Enable remote-wallet signing in solana-keygen (#8267)
* Add fallible methods to KeypairUtil

* Add RemoteKeypair struct and impl KeypairUtil

* Implement RemoteKeypair in keygen; also add parse_keypair_path for cleanup
2020-02-13 14:08:34 -07:00
ab475e4849 get_confirmed_block: expect() less 2020-02-13 10:56:34 -07:00
1c97b31eaf Retain signature subscriptions that haven't been notified (#8261) 2020-02-14 01:00:50 +08:00
bd257050e3 Retry to curl to codecov.io unfortunately (#8263)
automerge
2020-02-13 06:23:10 -08:00
2d362ed337 Remove references to old unused testnets (#8258)
automerge
2020-02-12 22:23:35 -08:00
cb7117beac CLI: Offline-ify remaining stake ops (#8257)
automerge
2020-02-12 22:00:28 -08:00
b358ff66e1 Plumb --enable-rpc-get-confirmed-block flag 2020-02-12 17:08:27 -07:00
6309c97697 Add CliCommand::StakeSetLockup (#8248)
automerge
2020-02-12 15:36:29 -08:00
58727463e1 Remove needless last_root for better reclaims (#8148)
* Restore last_root to fix unintended storage delete

* Remove last_root thing altogether

* Remove unneeded test...
2020-02-13 08:19:53 +09:00
741d148a0d Simplify remote wallet (#8249)
automerge
2020-02-12 14:38:51 -08:00
127553ce4b Wrap ed25519_dalek::Keypair (#8247) 2020-02-12 14:15:12 -07:00
ecb055a252 Expel ContactInfo::new() (#8245)
automerge
2020-02-12 12:58:51 -08:00
dfa6fbaa0c Bump cbindgen from 0.13.0 to 0.13.1 (#8233)
Bumps [cbindgen](https://github.com/eqrion/cbindgen) from 0.13.0 to 0.13.1.
- [Release notes](https://github.com/eqrion/cbindgen/releases)
- [Changelog](https://github.com/eqrion/cbindgen/blob/master/CHANGES)
- [Commits](https://github.com/eqrion/cbindgen/compare/v0.13.0...v0.13.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-12 12:25:39 -07:00
cf11d4c7dc Nodes with a tvu and storage port are no longer double counted (#8237)
automerge
2020-02-12 11:16:07 -08:00
d0a4686990 Avoid assigning the serve repair port to the storage port 2020-02-12 12:00:11 -07:00
2542d5dd42 Bump baseline version to 0.23.4, improve error reporting 2020-02-12 11:59:12 -07:00
1e0f2b2446 Quash 'repair listener error: Err(RecvTimeoutError(Timeout))' log spam 2020-02-12 10:35:03 -07:00
a8028fbb93 Fix accounts_db store counts in purging accounts logic (#8218)
* Show insufficient purge_zero_lamport_account logic

* Add another pass to detect non-deleted values and increment the count

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
2020-02-12 08:51:03 -08:00
ed87229cec CLI: Don't hide errors when fees are disabled (#8204)
automerge
2020-02-11 21:48:04 -08:00
c4fd81fc1c The getConfirmedBlock RPC API is now disabled by default
The --enable-rpc-get-confirmed-block flag allows validators to opt-in to
the higher disk usage and IOPS.
2020-02-11 22:24:08 -07:00
ad43babe3d ABI sanity test for running edge,beta,stable validators together 2020-02-11 21:21:31 -07:00
36c0cb052b set_read_timeout() can fail, don't expect() it not to 2020-02-11 18:57:14 -07:00
ed58bcda4c solana-install init edge when "edge" is not currently installed now works 2020-02-11 18:57:14 -07:00
268bb1b59b Fix RPC pub sub unsubscribe (#8208)
automerge
2020-02-11 17:09:40 -08:00
059764586a Rename from account userdata to data (#8224) 2020-02-11 16:30:22 -08:00
72b11081a4 Report validator rewards in getConfirmedBlock JSON RPC 2020-02-11 17:25:45 -07:00
0bbee9456f Add method to sign raw data, enabling easier device app testing (#8221)
* Add method to sign raw data, enabling easier device app testing

* Rename ugly derivation method, params
2020-02-11 17:15:38 -07:00
fcac910989 Bump jsonrpc-pubsub from 14.0.5 to 14.0.6 (#8162)
Bumps [jsonrpc-pubsub](https://github.com/paritytech/jsonrpc) from 14.0.5 to 14.0.6.
- [Release notes](https://github.com/paritytech/jsonrpc/releases)
- [Commits](https://github.com/paritytech/jsonrpc/compare/jsonrpc-pubsub-v14.0.5...ipc-14.0.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-11 15:44:21 -07:00
2e9ba149f2 Update LVM and rBPF versions (#8215) 2020-02-11 12:52:13 -08:00
d3712dd26d Factor repair from gossip (#8044) 2020-02-11 13:11:48 -07:00
60877f9ba4 Revert "Check for AVX512 at runtime to avoid invalid opcode trap (#8166)"
This reverts commit ef5fb6fa46.
2020-02-11 12:56:02 -07:00
4f2c76150f Clippy 2020-02-11 12:56:02 -07:00
137577fb86 Upgrade to rust 1.41.0 2020-02-11 12:56:02 -07:00
bf623219d2 nudge (#8214) 2020-02-11 10:50:57 -08:00
25d1f841ee Fixup sign_transaction; pass derivation_path by reference (#8194)
* Fixup sign_transaction; pass derivation_path by reference

* Pass total message length as BE u16

* Remove live integration tests (to ledger-app-solana)
2020-02-11 11:45:00 -07:00
517fe73734 Non-conflicting account duplicate marker value (#8206) 2020-02-11 10:03:28 -08:00
890919d140 Reliably track proc macro & build.rs code coverage (#8210) 2020-02-12 01:02:40 +09:00
33ea1e0edd Channel installs no longer re-download the same release. 2020-02-11 08:22:41 -07:00
7614af2a45 Verify frozen bank from snapshot by hashing (#8184) 2020-02-11 16:46:33 +09:00
1528959327 CLI: Add fee-payer parame to stake-split subcommand (#8201)
automerge
2020-02-10 23:23:54 -08:00
46b6cedff4 Fix nightly clippy warnings (#8199)
automerge
2020-02-10 22:48:50 -08:00
8d8f28c1d0 CLI: transfer fix checks pubkeys (#8198)
automerge
2020-02-10 22:34:14 -08:00
df782b93ae Add is_writable to AccountInfo (#8196) 2020-02-10 21:33:29 -08:00
124f77cdb1 Bump jsonrpc-ws-server from 14.0.5 to 14.0.6 (#8160)
Bumps [jsonrpc-ws-server](https://github.com/paritytech/jsonrpc) from 14.0.5 to 14.0.6.
- [Release notes](https://github.com/paritytech/jsonrpc/releases)
- [Commits](https://github.com/paritytech/jsonrpc/compare/jsonrpc-ws-server-v14.0.5...ipc-14.0.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 19:07:52 -07:00
fc15f74c3c CLI: Harden offline signing and tests (#8052)
* CLI: Don't sanity-check stake account when offline

* Add test helper returning vote pubkey with validator

* Delegate to the BSL. No need to force

* Be sure our offline ops are truly offline

* Specify our authorities correctly

* checks
2020-02-10 18:59:05 -07:00
1d06aa3b31 Remove repairman as its spamming cluster with unwanted repairs (#8193)
* Remove repairman as its spamming cluster with unwanted repairs

* remove obsolete test
2020-02-10 17:00:00 -08:00
0b263f8714 Fix larger than necessary allocations in streamer (#8187) 2020-02-10 11:49:07 -08:00
84b3e12e1f Minor logging improvements 2020-02-10 10:42:42 -07:00
669282ae69 Bump jsonrpc-http-server from 14.0.5 to 14.0.6 (#8161)
Bumps [jsonrpc-http-server](https://github.com/paritytech/jsonrpc) from 14.0.5 to 14.0.6.
- [Release notes](https://github.com/paritytech/jsonrpc/releases)
- [Commits](https://github.com/paritytech/jsonrpc/compare/jsonrpc-http-server-v14.0.5...ipc-14.0.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 10:30:32 -07:00
485806c488 Just define BnakSlotDelta type alias (#8186)
automerge
2020-02-10 03:11:37 -08:00
1412ee1ca6 add step "apt-get update", add package "libudev-dev" (#8180)
automerge
2020-02-08 14:45:07 -08:00
ef5fb6fa46 Check for AVX512 at runtime to avoid invalid opcode trap (#8166)
automerge
2020-02-07 15:01:45 -08:00
99432833d2 Remove reed-solomon-erasure from core/ 2020-02-07 15:37:57 -07:00
fa00803fbf Filter old CrdsValues received via Pull Responses in Gossip (#8150)
* Add CrdsValue timeout checks on Pull Responses

* Allow older values to enter Crds as long as a ContactInfo exists

* Allow staked contact infos to be inserted into crds if they haven't expired

* Try and handle oveflows

* Fix test

* Some comments

* Fix compile

* fix test deadlock

* Add a test for processing timed out values received via pull response
2020-02-07 12:38:24 -08:00
04ef977509 Remove unwanted println 2020-02-07 12:59:00 -07:00
87c6508305 CLI: Implement transfer command (#8108)
* CLI: Add transfer subcommand

* Add tests

* checks
2020-02-07 12:16:35 -07:00
ed0c1d3b52 Ledger hardware wallet integration (#8068)
* Initial remote wallet module

* Add clap derivation tooling

* Add remote-wallet path apis

* Implement remote-wallet in solana-keygen

* Implement remote-wallet in cli for read-only pubkey usage

* Linux: Use udev backend; add udev rules tool

* Ignore Ledger live test

* Cli api adjustments
2020-02-07 11:26:56 -07:00
8b5598fabd Surface shred version more in tools (#8163)
automerge
2020-02-07 08:57:54 -08:00
5b070ad014 CLI: Support offline fee payers (#8009)
* CLI: Support offline fee-payer

* Add some knobs to test genesis/validator helpers

* Add tests
2020-02-07 09:14:26 -07:00
6246405afd Bump memmap from 0.6.2 to 0.7.0 (#8157)
Bumps [memmap](https://github.com/danburkert/memmap-rs) from 0.6.2 to 0.7.0.
- [Release notes](https://github.com/danburkert/memmap-rs/releases)
- [Commits](https://github.com/danburkert/memmap-rs/compare/0.6.2...0.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-07 06:51:31 -07:00
27c8ba6afc De-replicode Tower constructors (#8153)
automerge
2020-02-06 18:24:10 -08:00
b832a03315 Add libudev-dev to docker image to build remote-wallet (#8149)
automerge
2020-02-06 13:45:05 -08:00
09686290bc Grant custodian access to all locked up accounts (#8139) 2020-02-06 14:31:27 -07:00
4aaa7b30ca Update README.md 2020-02-06 13:19:30 -07:00
fe590da3b6 Revert "Factor repair from gossip (#8044)" (#8143)
This reverts commit e61257695f.
2020-02-06 11:44:20 -08:00
a7fa92b372 feat: implementation of live-slots command (#8129) 2020-02-06 14:16:30 -05:00
a25e57c397 Ignore flaky test_exchange_local_cluster (#8146)
automerge
2020-02-06 10:44:17 -08:00
eed676113e Bump indexmap from 1.3.1 to 1.3.2 (#8144)
Bumps [indexmap](https://github.com/bluss/indexmap) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/bluss/indexmap/releases)
- [Commits](https://github.com/bluss/indexmap/compare/1.3.1...1.3.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-06 11:16:49 -07:00
b57f24f1bc Bump tokio-codec from 0.1.1 to 0.1.2 (#8126)
Bumps [tokio-codec](https://github.com/tokio-rs/tokio) from 0.1.1 to 0.1.2.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-codec-0.1.1...0.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-06 11:16:29 -07:00
0e084358b4 Fix slot_hashes documentation 2020-02-06 10:06:16 -07:00
f016c9a669 Maintenance : simplify a few patterns, remove unneeded dependencies (#8137)
* Simplify a few pattern matches

* Removing unneeded dependencies, upgrading internal version #s

 net-shaper: Removing log, semver, serde_derive
 bench-tps: Removing serde, serde_derive
 banking-bench: Removing solana
 ledger-tool: Removing bincode, serde, serde_derive
 librapay: Removing solana, language_e2e_tests
 log-analyzer: Removing log, semver, serde_derive
 exchange: Removing solana
 core: Removing crc, memmap, symlink, untrusted
 perf: Removing serde_derive
 genesis: Removing hex, serde_derive
 sdk-c: Removing sha2
 sys-tuner: Removing semver
 bench-exchange: Removing bincode, bs58, env_logger, serde, serde_derive, untrusted, ws
 btc_spv_bin: Removing serde_json
 btc_spv: Removing chrono
 bpf_loader: Removing serde
 ledger: Removing dlopen, dlopen_derive, serde_derive
 move_loader: Removing byteorder, libc, language_e2e_tests
 ownable: Removing serde, serde_derive
 client: Removing rand
 archiver-utils: Removing rand_chacha
 validator: Removing serde_json, tempfile
 param_passing_dep: Removing solana
 failure: Removing log
 vest: Removing log
 vote-signer: Removing bs58, serde
 local-cluster: Removing symlink
 keygen: Removing rpassword
 install: Removing bs58, log
 upload-perf: Removing log
 runtime: Removing serde_json
 stake: Removing rand

* Add modified Cargo.lock

* fixup! Simplify a few pattern matches

* fixup! Simplify a few pattern matches
2020-02-06 10:02:38 -07:00
59ba1df910 Bump tokio-fs from 0.1.6 to 0.1.7 (#8124)
Bumps [tokio-fs](https://github.com/tokio-rs/tokio) from 0.1.6 to 0.1.7.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Changelog](https://github.com/tokio-rs/tokio/blob/tokio-0.1.7/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-fs-0.1.6...tokio-0.1.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-05 16:27:32 -07:00
71a2c90f21 Bump tokio-io from 0.1.12 to 0.1.13 (#8125)
Bumps [tokio-io](https://github.com/tokio-rs/tokio) from 0.1.12 to 0.1.13.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Changelog](https://github.com/tokio-rs/tokio/blob/tokio-0.1.13/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-io-0.1.12...tokio-0.1.13)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-05 14:02:33 -07:00
8436457e75 Rename program_utils.rs (#8127) 2020-02-05 12:48:30 -08:00
3ac0192d40 Better surface bank hash verification failures 2020-02-05 11:39:47 -07:00
3db159f616 CLI cosmetic: make config get and verbose prints consistent (#8119)
* CLI cosmetic: make config get and verbose prints consistent

* Make print format consistent across cli
2020-02-05 11:14:44 -07:00
e21f5c784e Add next_account_info() (#8120) 2020-02-04 17:04:26 -08:00
65c24db83c Bump serde_json from 1.0.44 to 1.0.46 (#8087)
* Bump serde_json from 1.0.44 to 1.0.46

Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.44 to 1.0.46.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.44...v1.0.46)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* Update Move's serde_json to v1.0.46

Co-authored-by: Jack May <jack@solana.com>
2020-02-04 16:59:23 -08:00
ed5101b031 Generate max coding shreds when necessary (#8099)
* Generate max coding shreds when necessary

* test
2020-02-04 15:45:01 -08:00
1420628b28 Bump nix from 0.16.1 to 0.17.0 (#8112)
Bumps [nix](https://github.com/nix-rust/nix) from 0.16.1 to 0.17.0.
- [Release notes](https://github.com/nix-rust/nix/releases)
- [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/commits/v0.17.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-04 15:59:34 -07:00
15ab966ed1 Move native program entrypoint out of instruction_processor_utils (#8122) 2020-02-04 14:54:49 -08:00
b5a735878a Sysvar account_into return program error rather than option (#8121) 2020-02-04 14:54:41 -08:00
b6d09f1901 Add BPF program entrypoint return type (#8111) 2020-02-04 12:25:42 -08:00
78f6ddc5b7 Fix spelling of verification in accounts_db (#8117)
automerge
2020-02-04 11:45:47 -08:00
4e595e8e3c Facilitate printing program errors from BPF programs (#8109) 2020-02-04 09:03:45 -08:00
79249360f7 CLI: Expose sign-only reply parsing helper (#8107)
automerge
2020-02-03 18:22:36 -08:00
336d5136bf Print more program error info to user when using CLI (#8098) 2020-02-03 17:14:53 -08:00
0c8cee8c4a Refactor select_fork() to avoid clones and for clarity (#8081)
* Refactor select_fork() to avoid clones and for clarity

* Add test that fork weights are increasing
2020-02-03 16:48:24 -08:00
4c0420b884 Delete uptime command, report total credits in solana validators instead 2020-02-03 16:55:33 -07:00
0d7e093415 Broaden pattern in nits.sh to be less fragile (#8090)
* Broaden pattern in nits.sh to be less fragile

* Even more general matching
2020-02-04 08:53:01 +09:00
c835749563 Bump sys-info from 0.5.8 to 0.5.9 (#8089)
Bumps [sys-info](https://github.com/FillZpp/sys-info-rs) from 0.5.8 to 0.5.9.
- [Release notes](https://github.com/FillZpp/sys-info-rs/releases)
- [Changelog](https://github.com/FillZpp/sys-info-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/FillZpp/sys-info-rs/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-03 14:49:39 -07:00
0172d2a065 Fix consensus threshold when new root is created (#8093)
When a new root is created, the oldest slot is popped off
but when the logic checks for identical slots, it assumes
that any difference means a slot was popped off the front.
2020-02-03 13:44:34 -08:00
927f272f0e Update book release version 2020-02-03 11:35:51 -07:00
5e2891ae5d e 2020-02-03 11:34:00 -07:00
4f85481a2b Add split-stake command 2020-02-03 11:14:08 -07:00
d314e0395a Disable windows update as windows build artifacts are turned off 2020-02-01 22:25:24 -07:00
69a6d07371 Reduce rpc client pre-flight requests by setting max-age header (#8082)
automerge
2020-02-01 04:10:26 -08:00
fab8ef379f Use solana-cli config keypair in solana-keygen (#8074)
* Use solana-cli config keypair in solana-keygen

* s/infile/keypair for consistency across modules and more generality across access methods

* Move config into separate crate
2020-01-31 19:27:37 -07:00
408ef8b2cb Cleanup staking doc (#8064) 2020-01-31 19:24:51 -07:00
a2a2f1c2d2 Add new colo test cases using reduced node count (#8078)
automerge
2020-01-31 18:02:48 -08:00
dc2888c9a3 CLI: De-replicode SigningAuthority instatiation (#8076)
automerge
2020-01-31 16:30:37 -08:00
9739be9ecf CLI: Fix stake-account auth withdrawer output (#8071)
automerge
2020-01-31 14:25:05 -08:00
e61257695f Factor repair from gossip (#8044) 2020-01-31 14:23:50 -08:00
b9988b62e4 Filter repairman peers based on shred_version (#8069) 2020-01-31 14:00:19 -08:00
d6b3961530 s/mint/faucet 2020-01-31 12:14:53 -07:00
6d0be323ad Update key (#8062)
automerge
2020-01-31 11:11:22 -08:00
09256adbc3 Surface important error details 2020-01-31 12:09:41 -07:00
8e3a7da596 Rewrite new() in terms of new_with_timeout() 2020-01-31 12:09:41 -07:00
7d96510d17 Fix stake-account subcommand name 2020-01-31 12:09:41 -07:00
0fd795a676 Remove program error footgun and cleaner developer experience (#8042) 2020-01-31 10:58:07 -08:00
eff876881b Remove asteroids and pacman from QA/dev testnet availability (#8050)
automerge
2020-01-31 10:26:25 -08:00
9adf0d4ee0 Don't exit early if add. validators not found during gce.sh config 2020-01-31 08:34:10 -07:00
3bc9789e8d Remove support for 0.22.3 snapshots 2020-01-30 23:34:15 -07:00
fd207b6907 Fix stale gossip entrypoint (#8053) 2020-01-30 21:51:11 -08:00
2226c1b75c Add Rust BPF RefCell borrow helpers (#8047) 2020-01-30 20:40:27 -08:00
a0964bb2c2 Make tds slots-per-epoch configurable 2020-01-30 21:37:16 -07:00
b5383b8b54 Dial testnet down to a single node 2020-01-30 21:30:08 -07:00
39f86050a6 Bump cbindgen from 0.12.2 to 0.13.0
Bumps [cbindgen](https://github.com/eqrion/cbindgen) from 0.12.2 to 0.13.0.
- [Release notes](https://github.com/eqrion/cbindgen/releases)
- [Changelog](https://github.com/eqrion/cbindgen/blob/master/CHANGES)
- [Commits](https://github.com/eqrion/cbindgen/compare/v0.12.2...v0.13.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-30 20:58:10 -07:00
a03d441e6f Add rpc port sanity checks, fix tests 2020-01-30 20:57:58 -07:00
3900d09f6f Employ rpc_port defaults 2020-01-30 20:57:58 -07:00
e218f4e56e Clean up Validator::new() 2020-01-30 20:57:58 -07:00
81ba18eea6 Add --private-rpc flag 2020-01-30 20:57:58 -07:00
1671ece9df Book: Prod the user to verify their new paper wallet (#8048) 2020-01-30 17:20:04 -07:00
775fa0c968 Minor --expected-shred fix, clean up shred-related gossip log messages (#8041)
automerge
2020-01-30 13:22:05 -08:00
dd276138c2 Add support for idiomatic error handling to BPF instruction processors (#7968) 2020-01-30 09:47:22 -08:00
0c55b37976 Add different shred test to test_tvu_peers_and_stakes 2020-01-30 10:30:32 -07:00
966d077431 CLI: Disallow blockhash/fee-calc lookups when offline (#7981)
* CLI: Add BlockhashSpec to tighten control over --blockhash

* Use BlockhashSpec

* Add a matches-free constructor

* More descriptive naming
2020-01-30 09:21:32 -07:00
400412d76c Ignore slow archiver tests (#8032)
automerge
2020-01-30 08:17:36 -08:00
c7e77a2238 Bump indicatif from 0.13.0 to 0.14.0
Bumps [indicatif](https://github.com/mitsuhiko/indicatif) from 0.13.0 to 0.14.0.
- [Release notes](https://github.com/mitsuhiko/indicatif/releases)
- [Commits](https://github.com/mitsuhiko/indicatif/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-30 08:58:00 -07:00
64c42e28dc Add shred version filters to Crds Accessors (#8027)
* Add shred version filters to Crds Accessors

* Adopt entrypoint shred_version if one isn't provided
2020-01-30 00:15:37 -08:00
c2baf7b07d Bump thiserror from 1.0.9 to 1.0.10
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.9 to 1.0.10.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.9...1.0.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-29 23:23:24 -07:00
a52a9afa3c Bump console from 0.9.1 to 0.9.2
Bumps [console](https://github.com/mitsuhiko/console) from 0.9.1 to 0.9.2.
- [Release notes](https://github.com/mitsuhiko/console/releases)
- [Commits](https://github.com/mitsuhiko/console/compare/0.9.1...0.9.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-29 23:22:44 -07:00
669502ede7 Don't depend on user modifiable data to parse paramter buffer (#8022) 2020-01-29 21:49:42 -08:00
b19f730527 Seperate RefCells lamports and data (#8021) 2020-01-29 21:15:04 -08:00
d5ff5f4739 Update solana_rbpf v0.1.20 (#8023) 2020-01-29 21:14:49 -08:00
1c82f84595 Add leader-schedule subcommand 2020-01-29 20:08:42 -07:00
bea9cd9684 Add --expected-shred-version option 2020-01-29 20:08:42 -07:00
1bc9a9c23b Wait for supermajority by default, add --no-wait-for-supermajority flag to override 2020-01-29 20:08:42 -07:00
c4faccc77f getClusterNodes now excludes validators with a different shred version 2020-01-29 20:08:42 -07:00
e6803daf10 Remove support for stake redelegation (#7995)
* Remove support for stake redelegation

* fixup
2020-01-29 17:59:14 -08:00
effe6e3ff3 Log solana-validator args on startup to aid debugging 2020-01-29 08:27:52 -07:00
0d6c233747 Add set_lockup to stake (#7997) 2020-01-28 20:59:53 -08:00
015e696077 Solana keygen grind improvements (#8008)
automerge
2020-01-28 20:19:19 -08:00
7faab2072c Cleanup BPF use syntax (#8001) 2020-01-28 17:03:37 -08:00
83718a3b3e Cleanup runtime use syntax (#8002) 2020-01-28 17:03:20 -08:00
4a074133f7 CLI: Fix tests. sign_only requires a blockhash (#8005)
This is enforced by argument parsing and will be better enforced wholly
with #7981
2020-01-28 18:02:20 -07:00
12eff5a2f9 Cleanup SDK use syntax (#8004) 2020-01-28 16:11:22 -08:00
4197cce8c9 Tower tests (#7974)
* Add testing framework for voting
2020-01-28 16:02:28 -08:00
fed3817ed3 Update and fix transaction error documentation (#7998) 2020-01-28 15:59:50 -08:00
4ffd7693d6 Add lock to make sure slot-based locktree calls are safe (#7993) 2020-01-28 13:45:41 -08:00
1596c961d9 Rust BPF program cleanup (#7990) 2020-01-27 18:27:44 -08:00
fd7d5cbe0d Fix compute_shred_version() (#7989)
automerge
2020-01-27 17:05:31 -08:00
7058287273 Consensus fix, don't consider threshold check if.. (#7948)
* Consensus fix, don't consider threshold check if

lockouts are not increased

* Change partition tests to wait for epoch with > lockout slots

* Use atomic bool to signal partition
2020-01-27 16:49:25 -08:00
912aafcefd Reduce epoch duration from 2 weeks to 2 days 2020-01-27 10:34:55 -07:00
2f34f433b3 Specify where VM images are coming from across GCE projects (#7985)
automerge
2020-01-27 08:17:21 -08:00
1ff4dd9a9a Remove show- prefix 2020-01-26 21:00:57 -07:00
fdcaad96c7 Remove stray key 2020-01-26 14:35:33 -07:00
14a72b0fc0 CLI: --sign-only and --signer require --blockhash (#7982) 2020-01-26 10:06:21 -07:00
c13ab9f14e CLI: Consolidate offline arg declarations (#7979)
automerge
2020-01-26 00:27:24 -08:00
cff1bc6e71 s/dervied/derived/ 2020-01-25 23:22:28 -07:00
bb6c4efe9b CLI: Deterministic dummy keypair generation for SigningAuthority::Offline (#7971)
* CLI: Deterministic dummy keypair generation for SigningAuthority::Offline

* Add test
2020-01-25 22:14:07 -07:00
c324e71768 Bump cargo toml versions to 0.24.0 (#7976) 2020-01-25 11:04:27 -06:00
e2570c98ee CLI: Add authority to show-nonce-account output (#7969) 2020-01-25 07:21:23 -07:00
b5125479ec Bump perf libs to v0.18.0 for CUDA 10.2 support 2020-01-24 21:39:49 -07:00
989355e885 Add ability to hard fork at any slot (#7801)
automerge
2020-01-24 17:27:04 -08:00
a2f2c46f87 Ensure shred version is never 0 2020-01-24 17:41:20 -07:00
605623baf5 Report last tower distance and add partition testcase (#7929)
automerge
2020-01-24 16:37:19 -08:00
fdc452c536 Move testnet.solana.com and TdS to their own GCP projects 2020-01-24 16:26:54 -07:00
1b391dd36b Add account accessor functions (#7966) 2020-01-24 14:34:59 -08:00
917067741a Cleanup BPF SDK (#7965) 2020-01-24 13:41:14 -08:00
34ed93d57c Optimize account copies and use RefCell to handle duplicate accounts in BPF programs (#7958) 2020-01-24 10:54:26 -08:00
d400a64b9a Update tiny_bip39 (#7959)
automerge
2020-01-24 08:59:07 -08:00
2c7447b73e Secure sysvars under hash by freezing all strictly (#7892)
* Secure sysvars under hash by freezing all strictly

* Fix hash's non-idempotnet and add new test

* Clean up

* More cleanups
2020-01-24 16:10:32 +09:00
c0f0fa24f8 Increase --wait-for-supermajority to wait for 75% online stake 2020-01-23 22:41:46 -07:00
bda5f949bb Add create-snapshot command 2020-01-23 22:21:27 -07:00
992e985972 Add column for slot range of epoch to epoch-info (#7954)
automerge
2020-01-23 20:44:37 -08:00
afaa359b0d Reorg the book (#7952)
* Move application-oriented docs

* Reorg the book

* Fix build

* Apply review feedback

* verb-noun
2020-01-23 21:21:43 -07:00
3c17db41dc Add note to book about drone throughput limitations (#7953)
automerge
2020-01-23 18:50:25 -08:00
d62ed4f6b3 Add BlockstoreProcessorResult 2020-01-23 16:52:47 -07:00
79f3194d0c Fix cli call to stakes (#7946)
automerge
2020-01-23 14:25:40 -08:00
b045f9a50d codemod --extensions rs get_snapshot_tar_path get_snapshot_archive_path 2020-01-23 13:37:13 -07:00
ce231602dc Move snapshot archive generation out of the SnapshotPackagerService 2020-01-23 13:37:13 -07:00
6f5e0cd161 Type grooming 2020-01-23 13:37:13 -07:00
1269a79a4d Unify ledger_path arg handling with validator/ 2020-01-23 13:37:13 -07:00
1b3424ff61 Pass bank_forks by reference 2020-01-23 13:37:13 -07:00
8b8033c72b Set BankRc slot correctly when restoring a bank snapshot 2020-01-23 13:37:13 -07:00
7ca0109732 --halt-at-slot 1 now halts at slot 1 2020-01-23 13:37:13 -07:00
6b5172d002 add_snapshot now returns SlotSnapshotPaths 2020-01-23 13:37:13 -07:00
9e19a635bb Remove superfluous accounts arg 2020-01-23 13:37:13 -07:00
15193d0e1f Ensure all GCE nightly tests use dedicated instances (#7944)
automerge
2020-01-23 10:17:12 -08:00
f1c5c72e62 Fix transaction.md anchor links (#7943)
* Lowercase links

* Fix misspelled anchor link
2020-01-23 10:05:42 -07:00
25dfed207c Remove dead code (#7940)
automerge
2020-01-23 00:38:46 -08:00
006cbee88a Uninteresting cleanup 2020-01-22 21:24:20 -07:00
c95e5346a4 Boot the mut (#7926) 2020-01-22 17:54:06 -08:00
e54bf563b5 Avoid unsorted recent_blockhashes for determinism (#7918)
* Avoid unsorted recent_blockhashes for determinism

* Add a test: test_create_account_unsorted
2020-01-23 10:51:22 +09:00
8f79327190 Test account doesn't need RefCell (#7932)
automerge
2020-01-22 17:06:11 -08:00
a197ac092a New Anatomy of a Transaction (#7930)
automerge
2020-01-22 16:58:46 -08:00
1e2b55c0d7 Remove RedeemVoteCredits (#7916)
* Move redeem_vote_credits into runtime

* Move redeem_vote_credits into runtime

* Remove RedeemVoteCredits

* chugga for less indentation

* resurrect NoCreditsToRedeem

* fixup
2020-01-22 16:53:42 -08:00
964ff522be Verb-noun-ify Nonce API (#7925)
* Verb-noun-ify Nonce API

* Unify instruction naming with API naming

The more verbose nonce_account/NonceAccount was chosen for clarity
that these instructions work on a unique species of system account
2020-01-22 16:31:39 -07:00
934c32cbc6 Add mechanism to load v0.22.3 snapshots on newer Solana versions 2020-01-22 15:40:32 -07:00
9bd6be779f Reject CI on failed mergify.io backports (#7927)
automerge
2020-01-22 14:10:26 -08:00
ce70d6eedc Add redeem_vote_credits to runtime (#7910)
* Move redeem_vote_credits into runtime

* fixup

* test

* move stake manipulation to stake program

* chugga for less indentation
2020-01-22 12:21:31 -08:00
3a0d13aa77 CLI: Cleanup authority arg usage inconsistencies (#7922)
automerge
2020-01-22 11:19:07 -08:00
f9323c5273 don't put accounts in a weird location, use the defaults (#7921)
automerge
2020-01-22 10:57:37 -08:00
7587656cf6 Implement automated partition testing (#7222) 2020-01-22 13:46:50 -05:00
023074650f Allow the same account to be passed multiple times to a single instruction (#7795) 2020-01-22 09:11:56 -08:00
d854e90c23 CLI: Support offline authorities (#7905) 2020-01-22 10:10:22 -07:00
3aabeb2b81 Rename bootstrap leader (#7906)
* Rename bootstrap leader to bootstrap validator

It's a normal validator as soon as other validators enter the
leader schedule.

* cargo fmt

* Fix build

Thanks @CriesofCarrots!
2020-01-22 09:22:09 -07:00
65f5885bce sendTransaction rpc: expect transaction as base58 string (#7913) 2020-01-21 22:16:07 -07:00
7a132eabb4 Update JSON-RPC documentation (#7915)
* Streamline getBlockCommitment response

* Update json-rpc docs
2020-01-21 20:17:33 -07:00
7e1b380f01 Move vote_state current credits into epoch_credits (#7909)
* Move vote_state current credits into epoch_credits

* fixups

* fixup
2020-01-21 19:08:40 -08:00
1a2d9b8eed Bump csv from 1.1.2 to 1.1.3 (#7893)
Bumps [csv](https://github.com/BurntSushi/rust-csv) from 1.1.2 to 1.1.3.
- [Release notes](https://github.com/BurntSushi/rust-csv/releases)
- [Commits](https://github.com/BurntSushi/rust-csv/compare/1.1.2...1.1.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-21 19:49:19 -07:00
6eefa0b72d Integrate transaction chapter into programming model chapter (#7911)
automerge
2020-01-21 16:44:05 -08:00
44372db955 Move Blockstreamer doc into getting started doc 2020-01-21 17:15:29 -07:00
e24cce4aed Hoist blockstore chapter (#7908)
automerge
2020-01-21 16:01:26 -08:00
a8595c0418 Give chapters more precise titles (#7907)
automerge
2020-01-21 15:36:40 -08:00
340424e03a Use minimumLedgerSlot RPC API in block-production command 2020-01-21 14:05:26 -07:00
93036bec01 Add minimumLedgerSlot RPC API 2020-01-21 14:05:26 -07:00
663e98969d Use a different error to test rpc response (#7900)
automerge
2020-01-21 12:42:23 -08:00
37d1daf58e Revert "Generate MAX_DATA_SHREDS_PER_FEC_BLOCK coding shreds for each FEC block (#7474)" (#7898)
automerge
2020-01-21 11:48:09 -08:00
1a18f0ca55 Add rust duplicate account test program (#7897)
automerge
2020-01-21 10:59:19 -08:00
bb950ec93e Naming nits (#7896)
automerge
2020-01-21 10:38:46 -08:00
39ab3557a3 Delete "testnet participation" redirect (#7895)
automerge
2020-01-21 09:35:59 -08:00
dcdc46b97c Assume 1 or more validators 2020-01-21 10:34:58 -07:00
da3ed0dfb3 Try running testnet.solana.com with only two validators 2020-01-21 10:34:53 -07:00
e391b9fb90 Delete duplicate book content (#7894)
automerge
2020-01-21 09:17:20 -08:00
e346cdad26 Run ./book/build-cli-usage.sh 2020-01-21 08:58:29 -07:00
7e4c6ff218 solana set => solana config set 2020-01-21 08:53:44 -07:00
356f246a74 Remove get-/show- prefix from cli commands 2020-01-21 08:43:07 -07:00
80da552834 Bump rpassword from 4.0.4 to 4.0.5
Bumps [rpassword](https://github.com/conradkleinespel/rpassword) from 4.0.4 to 4.0.5.
- [Release notes](https://github.com/conradkleinespel/rpassword/releases)
- [Commits](https://github.com/conradkleinespel/rpassword/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-20 23:30:18 -07:00
2dd8ab197d Remove redundant threadpools in sigverify (#7888)
* Limit the number of thread pools sigverify creates

* Name local threadpools
2020-01-20 20:08:19 -08:00
1fe11e9ae2 chacha ignore farf (#7882) 2020-01-20 17:04:31 -08:00
21d5fe6272 Fix timestamp overflow (#7886)
* Split timestamp calculation into separate fn for math unit testing

* Add failing test

* Fix failing test; also bump stakes to near expected cluster max supply

* Don't error on timestamp of slot 0
2020-01-20 17:54:44 -07:00
52bc4a3598 nudge (#7887) 2020-01-20 15:27:36 -08:00
cccaacee36 Wait for stake distribution in automation (#7883)
automerge
2020-01-20 13:32:37 -08:00
ebf6e1c0e9 --limit-ledger-size now accepts an optional slot count value 2020-01-20 14:20:30 -07:00
5cf090c896 feat: implement RPC notification queue (#7863) 2020-01-20 16:08:29 -05:00
cc299053cc Add support for stake::split() via create_account_with_seed() (#7879)
* Add split with seed

* move to new system_program APIs

* de-replicode
2020-01-20 12:33:27 -08:00
82b75796f9 Create ledger directory if it doesn't already exist 2020-01-20 10:11:43 -07:00
a560d94a9f Bump humantime from 1.3.0 to 2.0.0
Bumps [humantime](https://github.com/tailhook/humantime) from 1.3.0 to 2.0.0.
- [Release notes](https://github.com/tailhook/humantime/releases)
- [Commits](https://github.com/tailhook/humantime/compare/v1.3.0...v2.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-20 10:08:11 -07:00
0827d52c6f Bump indexmap from 1.1.0 to 1.3.1
Bumps [indexmap](https://github.com/bluss/indexmap) from 1.1.0 to 1.3.1.
- [Release notes](https://github.com/bluss/indexmap/releases)
- [Commits](https://github.com/bluss/indexmap/compare/1.1.0...1.3.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-20 10:07:48 -07:00
a8d33c9950 Spy just for RPC to avoid premature supermajority (#7856)
* Spy just for RPC to avoid premature supermajority

* Make gossip_content_info private

Co-Authored-By: Michael Vines <mvines@gmail.com>

* Fix misindent...

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-01-20 10:50:31 +09:00
43c32ea280 Bump rpassword from 4.0.3 to 4.0.4
Bumps [rpassword](https://github.com/conradkleinespel/rpassword) from 4.0.3 to 4.0.4.
- [Release notes](https://github.com/conradkleinespel/rpassword/releases)
- [Commits](https://github.com/conradkleinespel/rpassword/compare/v.4.0.3...v4.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-18 19:32:14 -07:00
30d40e9a32 If a bad RPC node is selected try another one instead of aborting 2020-01-18 11:00:08 -07:00
e28508ad56 Abort if a snapshot download fails for any reason other than 404 2020-01-18 08:59:53 -07:00
182e4cec86 Update backport labels 2020-01-17 21:38:39 -07:00
a32de96ab1 Add show-stakes subcommand 2020-01-17 14:14:01 -07:00
0de35fdd1f CLI: Support offline and nonced stake subcommands (#7831)
* Support durable nonce for staker-authorize-*

* CLI: Factor out sign-only reply parsing to helper

* Support offline signing for staker-authorize-*
2020-01-17 10:30:56 -07:00
470d9cd752 Add system_instruction::{allocate, allocate_with_seed, assign_with_seed}, (#7847)
* cleanup test checks cargo audit

* Add system_instruction allocate

* fixup

* fixup
2020-01-17 09:29:15 -08:00
87598c7612 Consolidate tx error counters and update metrics dashboard (#7724)
automerge
2020-01-16 23:26:50 -08:00
57bf618627 Enable config program at soft launch epoch 0 (#7854)
automerge
2020-01-16 23:05:33 -08:00
c576a707b0 Increase token cap (#7855)
automerge
2020-01-16 23:02:05 -08:00
b78b1bbfa9 Improve bench-tps keypair generation (#7723)
* Improve bench-tps keypair generation

* Fix tests

* Fix move test

* cargo fmt

* Split up funding function into smaller functions

* Support restarting bench-tps without re-funding

* Change quick start logic and remove noisy log
2020-01-17 10:35:12 +08:00
e710964d05 Revamp the progress of current epoch in get-epoch-info (#7838)
* Revamp the progress of current epoch in get-epoch-info

* Incorporate suggested more concise labelling
2020-01-17 09:39:47 +09:00
2d00657756 Bump num_cpus from 1.11.1 to 1.12.0 (#7845)
Bumps [num_cpus](https://github.com/seanmonstar/num_cpus) from 1.11.1 to 1.12.0.
- [Release notes](https://github.com/seanmonstar/num_cpus/releases)
- [Changelog](https://github.com/seanmonstar/num_cpus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/num_cpus/compare/v1.11.1...v1.12.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-16 17:38:13 -07:00
0526d4ff21 Add logging surrounding failure in get_slot_entries_with_shred_info() (#7846)
* Add logging surrounding failure
2020-01-16 16:03:24 -08:00
76e20015a4 Add separate thread to check for and store duplicate slot proofs (#7834) 2020-01-16 15:27:54 -08:00
f5e797e3aa cleanup test checks cargo audit (#7849)
automerge
2020-01-16 15:08:36 -08:00
787e36a28f ignore prost is part of move (#7848) 2020-01-16 14:14:44 -08:00
8572b57834 Refactor chacha cuda to be able to test cuda crate but not in OpenCL (#7685)
* Refactor chacha cuda to be able to test cuda crate but not in OpenCL

chacha not implemeted in OpenCL

* Get off core::Error
2020-01-16 08:29:36 -08:00
ed0129f881 Don't depend on unused lazy_static 2020-01-16 08:43:13 -07:00
78836a9e22 Make run.sh not overwrite genesis if existing (#7837) 2020-01-16 14:34:36 +09:00
4c08184379 no check if no change (#7824) 2020-01-15 15:13:11 -08:00
da165d6943 Fix Rpc inconsistencies (#7826)
* Update rpc account format: remove byte arrays

* Base58-encode pubkeys in getStoragePubkeysForSlot

* Update docs
2020-01-15 15:33:53 -07:00
8ffccfbaff CLI: Plumb stake authorities throughout (#7822)
automerge
2020-01-15 13:32:06 -08:00
a6d083d69d Remove create_account bandaid now that to's signature is required (#7776)
* Remove create account bandaid now that  requires signature

* shrink scope of this PR to bandaid
2020-01-15 13:03:22 -08:00
91bae9d510 Don't use word 'securely' (#7820)
automerge
2020-01-15 11:30:11 -08:00
f0f185509f Remove tuple from programNotification (#7819)
automerge
2020-01-15 10:52:02 -08:00
5947ef7706 Remove word pair from address generator seed string (#7802)
* Remove word pair from address generator seed string
2020-01-15 13:50:37 -05:00
4f663a2a86 Add new genesis validators (#7814)
automerge
2020-01-15 09:26:49 -08:00
1d01777a13 Prefer CUDA_HOME environment variable 2020-01-15 09:03:52 -07:00
6d3b8b6d7d Remove tuples from JSON RPC responses (#7806)
* Remove RpcConfirmedBlock tuple

* Remove getRecentBlockhash tuple

* Remove getProgramAccounts tuple

* Remove tuple from get_signature_confirmation_status

* Collect Rpc response types

* Camel-case epoch schedule for rpc response

* Remove getBlockCommitment tuple

* Remove getStorageTurn tuple

* Update json-rpc docs
2020-01-15 00:25:45 -07:00
50c1c08235 Set bootstrap leader and net/ validator vote account commission to 100% 2020-01-15 00:25:26 -07:00
b16c30b4c6 Fix cluster collapse due to no proper shifted read (#7797)
* Fix cluster collapse due to no proper shifted read

* Add test for bank hash mismatch

Co-authored-by: sakridge <sakridge@gmail.com>
2020-01-15 11:45:19 +09:00
ff1ca1e0d3 Consolidate entry tick verification into one function (#7740)
* Consolidate entry tick verification into one function

* Mark bad slots as dead in blocktree processor

* more feedback

* Add bank.is_complete

* feedback
2020-01-15 09:15:26 +08:00
721c4378c1 Plumb ability to handle duplicate shreds into shred insertion functions (#7784) 2020-01-14 15:37:53 -08:00
5f4e0c7e3e Naming nits (#7798)
automerge
2020-01-14 13:38:17 -08:00
e6af4511a8 Include shred version in gossip 2020-01-14 14:32:40 -07:00
965ad778dd Improve KeypairFileNotFound error message (#7792)
automerge
2020-01-14 12:19:08 -08:00
3b78be83cf Add hash stats information to check hashes between validators (#7780)
automerge
2020-01-14 11:57:29 -08:00
564cd4e09d Book: Drop since-fixed nonce known issue (#7789)
automerge
2020-01-14 10:13:09 -08:00
699ca5fec1 Unignore advisories as affected ver. is corrected (#7730)
For details see upstream PR: https://github.com/RustSec/advisory-db/pull/221
2020-01-14 11:16:32 +09:00
f91ffbbfdf Add support in BlockStore for tracking duplicate slots (#7761)
* Add test

* Add new column family to track duplicate slots

* Fix clippy errors

* Introduce new SlotColumn for common implementation of Column trait
2020-01-13 17:21:39 -08:00
156292e408 Reduce grace ticks, and ignore grace ticks for missing leaders (#7764)
* Reduce grace ticks, and ignore grace ticks for missing leaders

* address review comments

* blockstore related renames
2020-01-14 05:25:41 +05:30
81ae44f858 Nonce: Rename instructions with VerbNoun scheme (#7775)
automerge
2020-01-13 15:34:43 -08:00
c948814eae Update getConfirmedBlock examples (#7772) 2020-01-13 15:05:27 -07:00
b5dba77056 Rename blocktree to blockstore (#7757)
automerge
2020-01-13 13:13:52 -08:00
ef06d165b4 Book: Update durable nonce proposal entry (#7694)
automerge
2020-01-13 13:12:09 -08:00
5cb23c814d Install move-loader binaries (#7768) 2020-01-13 12:53:53 -08:00
8f7ded33e0 coalesce data and coding index (#7765) 2020-01-13 12:03:19 -08:00
a17d5795fb getConfirmedBlock: add encoding optional parameter (#7756)
automerge
2020-01-12 21:34:30 -08:00
ad4d41e602 Pick an RPC node at random to avoid getting stuck on a bad RPC node 2020-01-11 12:10:11 -07:00
9754fc789e Manage durable nonce stored value in runtime (#7684)
* Bank: Return nonce pubkey/account from `check_tx_durable_nonce`

* Forward account with HashAgeKind::DurableNonce

* Add durable nonce helper for HashAgeKind

* Add nonce util for advancing stored nonce in runtime

* Advance nonce in runtime

* Store rolled back nonce account on TX InstructionError

* nonce: Add test for replayed InstErr fee theft
2020-01-10 16:57:31 -07:00
fd3c6eb320 Remove print in test (#7758)
automerge
2020-01-10 15:37:22 -08:00
b7b68ecdba Add partition testing documentation (#7739) 2020-01-10 15:32:43 -08:00
08ba27627d Direct entrypoint for execution (#7746) 2020-01-10 13:20:15 -08:00
27d2c0aaf3 Handle errors on replaying ledger properly (#7741) 2020-01-10 12:16:44 -08:00
b714a4be63 Fix call to BPF build script (#7754)
automerge
2020-01-10 10:28:55 -08:00
2356b25c58 Book: Update SPV section to reflect new account state query mechanism (#5399)
* Book: Update SPV section to reflect new account state query mechanism

* Book: SPV - Rename Bank-Merkle diagram

* Relax specificity of inclusion proof resolution

* Cosmetic: re-wrap at 80
2020-01-10 10:48:29 -07:00
05cad05505 Update validator proposal (#7752)
* Use 80-char lines

* Remove the part that was implemented in Gulf Stream
2020-01-10 10:15:49 -07:00
1e3082fbc0 Bump tiny-bip39 from 0.6.2 to 0.7.0 (#7750)
Bumps [tiny-bip39](https://github.com/maciejhirsz/tiny-bip39) from 0.6.2 to 0.7.0.
- [Release notes](https://github.com/maciejhirsz/tiny-bip39/releases)
- [Changelog](https://github.com/maciejhirsz/tiny-bip39/blob/master/CHANGELOG.md)
- [Commits](https://github.com/maciejhirsz/tiny-bip39/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-10 09:19:28 -07:00
80d2573b10 Bump cbindgen from 0.12.1 to 0.12.2 (#7749)
Bumps [cbindgen](https://github.com/eqrion/cbindgen) from 0.12.1 to 0.12.2.
- [Release notes](https://github.com/eqrion/cbindgen/releases)
- [Changelog](https://github.com/eqrion/cbindgen/blob/master/CHANGES)
- [Commits](https://github.com/eqrion/cbindgen/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-10 09:19:01 -07:00
6adcdc41f4 Bump num-traits from 0.2.10 to 0.2.11 (#7737)
Bumps [num-traits](https://github.com/rust-num/num-traits) from 0.2.10 to 0.2.11.
- [Release notes](https://github.com/rust-num/num-traits/releases)
- [Changelog](https://github.com/rust-num/num-traits/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-traits/compare/num-traits-0.2.10...num-traits-0.2.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-10 09:18:39 -07:00
2d08dddfc8 nit, cleanup uses (#7747)
automerge
2020-01-09 23:58:13 -08:00
6da8f49d8b nit, clearer error message (#7748)
automerge
2020-01-09 23:53:47 -08:00
bcd072c5e8 Clarify account creation error messages in CLI (#7719)
* Clarify account creation error messages in CLI

* feedback

* Fix rebase
2020-01-10 12:25:07 +08:00
e90a31781c Update http crate in bpf program to fix security vulnerability (#7735) 2020-01-10 10:21:20 +08:00
2e89ec9105 Don't keep generating transactions in non-sustained bench-tps mode (#7577) 2020-01-09 17:48:18 -08:00
865c42465a Cap file size for snapshot data files (#7182)
* save limit deserialize

* save

* Save

* Clean up

* rustfmt

* rustfmt

* Just comment out to please CI

* Fix ci...

* Move code

* Rustfmt

* Crean up control flow

* Add another comment

* Introduce predetermined constant limit on snapshot data files (deserialize side)

* Introduce predetermined constant limit on snapshot data files (serialize side)

* rustfmt

* Tweak message

* Revert dynamic memory limit

* Limit size of snapshot data file (de)serialization

* Fix test breakage

* Clean up

* Fix uses formatting

* Rename: deserialize_{for,from}_snapshot

* Simplify comment

* Use Slot

* Provide slot for status cache

* Align variable name with snapshot_status_cache_file_path

* Define serialize_snapshot_data_file_with_metrics

* Fix build.......

* De-marco serialize_snapshot_data_file_with_metrics

* Revert u64 => Slot
2020-01-10 09:49:36 +09:00
73c93cc345 Print bank hash and hash inputs. (#7733) 2020-01-09 16:33:10 -08:00
cf32fdf672 Bump reqwest from 0.10.0 to 0.10.1 (#7731)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.10.0 to 0.10.1.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.10.0...v0.10.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 17:10:55 -07:00
c33b54794c Propose Solana ABI management (#7524)
* Propose Solana ABI management

* Mention fuzz testing

* Address minor review comments

* Remove versioning and unit tests

* Rename

* Clean up a bit

* Pass through Grammarly

* Yet more tweaks...
2020-01-10 08:24:08 +09:00
6775e83420 Add create with seed to cli (#7713)
* Add create with seed to cli

* nonce and vote, too
2020-01-09 15:22:48 -08:00
719785a8d3 Update http crate to fix security vulnerability (#7725)
* Update http to fix security vulnerability

* Ignore RUSTSEC because they incorrectly says http 0.1.21 is vulnerable
2020-01-10 04:43:02 +09:00
287995ffdf Correctly integrate buildkite with codecov (#7718)
* Correctly integrate buildkite with codecov

* Fix shellcheck...

* Really detect Buildkite
2020-01-10 03:39:33 +09:00
0e506a53b5 Bump url from 2.1.0 to 2.1.1 (#7720)
Bumps [url](https://github.com/servo/rust-url) from 2.1.0 to 2.1.1.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-09 08:44:47 -07:00
70e1a15973 Remove vote account from genesis validators 2020-01-08 22:47:56 -07:00
09cff5e4cc Cleanup usage of feature "program" (#7712) 2020-01-08 13:49:35 -08:00
57858b8015 Bump reqwest from 0.9.24 to 0.10.0 (#7642)
* Bump reqwest from 0.9.24 to 0.10.0

Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.9.24 to 0.10.0.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* Make reqwest::blocking specific

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-01-08 13:31:43 -07:00
07855e3125 Allow override of RUST_LOG (#7705) 2020-01-08 09:19:12 -08:00
2f5f8e7afd Pass RUST_LOG through on testnet creation (#7707) 2020-01-07 21:46:28 -08:00
43897de12e Account for stake held by the current node while waiting for the supermajority to join gossip 2020-01-07 22:29:31 -07:00
4b577aa77b Bump cc from 1.0.48 to 1.0.49 (#7690)
Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.48 to 1.0.49.
- [Release notes](https://github.com/alexcrichton/cc-rs/releases)
- [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.48...1.0.49)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-07 22:13:17 -07:00
85c3d64f29 Fix rooted slot iterator (#7695)
* Enable jumping gaps caused by snapshots in rooted slot iterator
2020-01-07 22:51:28 -05:00
47dd293904 supermajority is one word 2020-01-07 15:50:59 -07:00
c4220a4853 clippy 2020-01-07 15:50:59 -07:00
48ab88a2af Add --wait-for-super-majority to facilitate asynchronous cluster restarts 2020-01-07 15:50:59 -07:00
d9cf9709d2 Bump csv from 1.1.1 to 1.1.2 (#7698)
Bumps [csv](https://github.com/BurntSushi/rust-csv) from 1.1.1 to 1.1.2.
- [Release notes](https://github.com/BurntSushi/rust-csv/releases)
- [Commits](https://github.com/BurntSushi/rust-csv/compare/1.1.1...1.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-07 14:38:02 -07:00
9720c894f1 Use commas to make a log message more readable 2020-01-06 22:31:01 -07:00
8dad3af36d Update Lockup comments (#7692) 2020-01-06 19:52:20 -08:00
e5425d4a27 Fix AppendVec test breakage... (#7693) 2020-01-07 09:21:59 +09:00
58e6d4aabb Sanitize AppendVec's file_size (#7373)
* Check append vec file size

* Don't use panic

* Clean up a bit

* Clean up

* Clean ups

* Change assertion into sanization check

* Remove...

* Clean up

* More clean up

* More clean up

* Use assert_matches
2020-01-07 08:14:56 +09:00
9ce142606c Update getBlockTime rpc docs (#7688) 2020-01-06 00:00:20 -07:00
e75a64a8a2 getBlockTime: Fix RootedSlotIterator lowest root (#7681)
* Determine lowest_nonzero_root for purged blocktrees, and clean up slot offset math

* Filter duplicate timestamp votes

* Refactor deduping code
2020-01-05 23:38:27 -07:00
bc71e1b612 Bump sha2 from 0.8.0 to 0.8.1
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.8.0 to 0.8.1.
- [Release notes](https://github.com/RustCrypto/hashes/releases)
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.8.0...sha2-v0.8.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-05 22:33:02 -07:00
580ca36a62 Cli: fund validator-info accounts with rent-exempt lamports 2020-01-04 22:59:12 -07:00
447fe48d2a Revert "Add a stand-alone gossip node on the blocksteamer instance"
This reverts commit a217920561.

This commit is causing trouble when the TdS cluster is reset and
validators running an older genesis config are still present.
Occasionally an RPC URL from an older validator will be selected,
causing a new node to fail to boot.
2020-01-04 16:42:12 -07:00
e8a6c8cd6d Don't panic if peer_addr() fails (#7678)
automerge
2020-01-04 10:00:22 -08:00
a8fd42c1df Set default vote account commission to 100% 2020-01-04 10:04:31 -07:00
e782c26908 Prune older epoch stakes 2020-01-04 09:34:27 -07:00
cd65a1e172 Run local cluster tests serially for easier debug 2020-01-04 09:34:27 -07:00
6e51c5685e Minor book fixes 2020-01-04 08:53:20 -07:00
84a37a2c0c Make validator timestamping more coincident, and increase timestamp sample range (#7673)
automerge
2020-01-03 22:38:00 -08:00
7e94cc2cc3 Move nonce into system program (#7645)
automerge
2020-01-03 16:34:58 -08:00
7002ccb866 Log root slots while processing ledger 2020-01-03 13:25:37 -07:00
4fe0b116ae Measure heap usage while processing the ledger 2020-01-03 13:25:37 -07:00
a0fb9de515 Move thread_mem_usage module into measure/ 2020-01-03 13:25:37 -07:00
5d42dcc9ec Reduce constants for ledger cleanup test (#7629) 2020-01-03 12:05:14 -08:00
96e88c90e8 Lessen test_slots_to_snapshot constants to make test faster (#7628)
Reduces test time from 6m to 45s
2020-01-03 09:58:52 -08:00
75d94240ed account_info utilities (#7666) 2020-01-03 09:14:51 -08:00
6c544708e1 Add safety docs (#7665) 2020-01-03 09:14:28 -08:00
078e7246ac Publish bpf-sdk only in Linux build 2020-01-02 23:20:59 -07:00
06cff1fb9f Publish bpf-sdk releases (#7655) 2020-01-02 20:44:15 -08:00
2e8bbed75b Revert "Remov dead code from TdS testnet manager config (#7414)"
This reverts commit 8920ac02f6.
2020-01-02 21:07:40 -07:00
a707c9410e More thiserror (#7183)
* Less solana_core::result. Module now private.

* Drop solana_core::result dependency from a few more modules

* Fix warning

* Cleanup

* Fix typo
2020-01-02 20:50:43 -07:00
a956bb08d8 Export bpf loader ser/de (#7661) 2020-01-02 18:18:56 -08:00
db52cc6749 CLI: Fix default nonce authority resolution (#7657)
automerge
2020-01-02 17:05:08 -08:00
73c6224a95 Book - Document nonceable CLI subcommands (#7656)
automerge
2020-01-02 16:30:26 -08:00
a217920561 Add a stand-alone gossip node on the blocksteamer instance
The blocksteamer instance is the TdS cluster entrypoint.  Running an
additional solana-gossip node allows other participants to join a
cluster even if the validator node on the blocksteamer instance goes down.
2020-01-02 17:20:59 -07:00
48a36f59a6 Add get-rpc-url --any option 2020-01-02 17:20:59 -07:00
965b132664 Permit --gossip-host with --entrypoint 2020-01-02 17:20:59 -07:00
63f185f9bf Delete unused type (#7653) 2020-01-02 13:15:31 -08:00
e97b0088f2 Make lockups block stake transfers via rekeying (#7651) 2020-01-01 11:03:29 -08:00
374c17a0d9 Book: Sync CLI API doc for show-block-production (#7648)
automerge
2019-12-31 09:26:45 -08:00
4b3bc587ab Add input validation for --creation-time/--lockup-date args (#7646)
automerge
2019-12-30 21:57:47 -08:00
06c63f2026 Bump cbindgen from 0.12.0 to 0.12.1 (#7637)
Bumps [cbindgen](https://github.com/eqrion/cbindgen) from 0.12.0 to 0.12.1.
- [Release notes](https://github.com/eqrion/cbindgen/releases)
- [Changelog](https://github.com/eqrion/cbindgen/blob/master/CHANGES)
- [Commits](https://github.com/eqrion/cbindgen/compare/v0.12.0...v0.12.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-30 22:16:48 -07:00
6b7d9942a7 Add authorized_voter history (#7643)
* Add authorized_voter history

* fixups

* coverage

* bigger vote state
2019-12-30 19:57:53 -08:00
760a56964f delete fixed_buf (#7644) 2019-12-30 16:45:43 -08:00
6ca575b5a3 Make sol-to-lamport const name more clear (#7641)
automerge
2019-12-30 11:28:41 -08:00
ce1d36cacb Book: Document CLI durable nonce account management (#7595)
* Book: Document CLI durable nonce account management

* Fix rent link

* review
2019-12-30 13:13:56 -05:00
87b2525e03 Limit maximum number of shreds in a slot to 32K (#7584)
* Limit maximum number of shreds in a slot to 32K

* mark dead slot replay as fatal error
2019-12-30 07:42:09 -08:00
faa77aca2e Update terminology.md 2019-12-29 21:35:06 -08:00
5d2158792c Add inflation to book, cleanup dead links, include orphaned documents (#7638)
* Add inflation as implemented proposal

* grab another orphan and add orphan-proofing
2019-12-29 18:15:32 -08:00
e1ebaa902b Add base pubkey to create_account_with_seed (#7636) 2019-12-29 16:42:24 -08:00
e0564f628e Use lamports in genesis (#7631)
* Use lamports in genesis

* readability
2019-12-28 12:49:10 -08:00
44e45aa090 Support nonced transactions in the CLI (#7624)
* Support nonced transactions in the CLI

* Update nonce.rs
2019-12-27 14:35:49 -06:00
89f5f336af Account for rent (#7626)
automerge
2019-12-24 18:01:21 -08:00
727be309b2 fix entryverification state (#7169)
automerge
2019-12-23 23:26:27 -08:00
ce2d7a2d5a Bump nix from 0.16.0 to 0.16.1 (#7623)
Bumps [nix](https://github.com/nix-rust/nix) from 0.16.0 to 0.16.1.
- [Release notes](https://github.com/nix-rust/nix/releases)
- [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 23:06:42 -07:00
fad6c7201e Remove old book location (#7621) 2019-12-23 22:56:42 -07:00
8f0e1f3349 Update gitbook-cage first 2019-12-23 18:18:30 -07:00
6f7d0c6928 Move cleanup to a script so it doesn't kill itself (#7603) 2019-12-23 14:31:57 -08:00
120c8f244c Add slot_history for slashing (#7589)
* Add slot_history for slashing

* fixup

* fixup
2019-12-23 12:23:45 -08:00
352a367570 Specify version for solana-sdk-macro to enable crate.io publishing (#7615) 2019-12-23 12:10:43 -08:00
9f65d22909 Groom log messages (#7610) 2019-12-23 10:43:07 -07:00
141131f3a6 Stabilize fn coverage by creating a clean room (#7576)
* Stabilize fn coverage by pruning all updated files

* Pruning didn't work; Switch to clean room dir

* Oh, shellcheck...

* Remove the data_dir variable

* Comment about relationale for find + while read
2019-12-23 16:32:29 +09:00
488420fdf2 Bump core_affinity from 0.5.9 to 0.5.10 (#7578)
Bumps [core_affinity](https://github.com/Elzair/core_affinity_rs) from 0.5.9 to 0.5.10.
- [Release notes](https://github.com/Elzair/core_affinity_rs/releases)
- [Commits](https://github.com/Elzair/core_affinity_rs/compare/0.5.9...0.5.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-22 22:41:47 -07:00
10e6b8f769 Fix key in genesis (#7585) 2019-12-22 22:40:35 -07:00
419da18405 show-block-production: Rename "missed" to "skipped" as not all skipped slots are missed slots (#7599) 2019-12-22 22:39:47 -07:00
7329d4bf3a Extend Stable CI job timeout to 60 minutes (#7604) 2019-12-22 20:14:07 -07:00
c8fe4043b6 Rename slot_hash => bank_hash in AcoountsDB (#7579)
* Rename slot_hash => bank_hash in AcoountsDB
2019-12-23 10:50:31 +09:00
3d133d61ca fix rent book entry (#7602) 2019-12-23 06:12:29 +05:30
d51e42c707 MISSED -> SKIPPED 2019-12-22 10:19:35 -07:00
79e39d6f0b Remove stray SOLANA_CUDA=1 2019-12-22 10:09:04 -07:00
7dec934bb3 Optimize lock_accounts mutex use (#7593)
Use the lock for the whole batch instead of per-tx
Optimize the critical section to pre-generate the keys necessary
before taking the lock.
2019-12-21 10:43:22 -08:00
83f866df01 Switch banking bench to report tps instead of total time (#7590)
Easier to compare results when modifying thread count.
2019-12-21 10:43:08 -08:00
d88d8e2dbb Fix another silly bug 2019-12-21 09:20:12 -07:00
3a40dff999 Cargo.lock 2019-12-20 21:55:35 -07:00
3f69d58498 ledger-tool: Add --all option to bounds, to display all non-empty slots (#7592) 2019-12-20 20:43:53 -07:00
ca10cf081f Update cargo.toml files from 0.22.0 to 0.23.0 (#7596) 2019-12-20 21:45:42 -05:00
672 changed files with 39219 additions and 20599 deletions

View File

@ -1,4 +1,4 @@
root: ./book/src
root: ./docs/src
structure:
readme: introduction.md

6
.gitignore vendored
View File

@ -1,6 +1,6 @@
/book/html/
/book/src/tests.ok
/book/src/.gitbook/assets/*.svg
/docs/html/
/docs/src/tests.ok
/docs/src/.gitbook/assets/*.svg
/farf/
/solana-release/
/solana-release.tar.bz2

View File

@ -19,22 +19,6 @@ pull_request_rules:
label:
add:
- automerge
- name: v0.21 backport
conditions:
- base=master
- label=v0.21
actions:
backport:
branches:
- v0.21
- name: v0.22 backport
conditions:
- base=master
- label=v0.22
actions:
backport:
branches:
- v0.22
- name: v0.23 backport
conditions:
- base=master
@ -43,3 +27,27 @@ pull_request_rules:
backport:
branches:
- v0.23
- name: v1.0 backport
conditions:
- base=master
- label=v1.0
actions:
backport:
branches:
- v1.0
- name: v1.1 backport
conditions:
- base=master
- label=v1.1
actions:
backport:
branches:
- v1.1
- name: v1.2 backport
conditions:
- base=master
- label=v1.2
actions:
backport:
branches:
- v1.2

View File

@ -45,7 +45,7 @@ $ git pull --rebase upstream master
If there are no functional changes, PRs can be very large and that's no
problem. If, however, your changes are making meaningful changes or additions,
then about 1,000 lines of changes is about the most you should ask a Solana
then about 1.0.2 lines of changes is about the most you should ask a Solana
maintainer to review.
### Should I send small PRs as I develop large, new components?
@ -224,21 +224,20 @@ Inventing new terms is allowed, but should only be done when the term is widely
used and understood. Avoid introducing new 3-letter terms, which can be
confused with 3-letter acronyms.
[Terms currently in use](book/src/terminology.md)
[Terms currently in use](docs/src/terminology.md)
## Design Proposals
Solana's architecture is described by a book generated from markdown files in
the `book/src/` directory, maintained by an *editor* (currently @garious). To
add a design proposal, you'll need to at least propose a change the content
under the [Accepted Design
Proposals](https://docs.solana.com/book/v/master/proposals) chapter. Here's
the full process:
Solana's architecture is described by docs generated from markdown files in
the `docs/src/` directory, maintained by an *editor* (currently @garious). To
add a design proposal, you'll need to include it in the
[Accepted Design Proposals](https://docs.solana.com/proposals)
section of the Solana docs. Here's the full process:
1. Propose a design by creating a PR that adds a markdown document to the
directory `book/src/` and references it from the [table of
contents](book/src/SUMMARY.md). Add any relevant *maintainers* to the PR
`docs/src/proposals` directory and references it from the [table of
contents](docs/src/SUMMARY.md). Add any relevant *maintainers* to the PR
review.
2. The PR being merged indicates your proposed change was accepted and that the
maintainers support your plan of attack.

2331
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,10 @@ members = [
"bench-streamer",
"bench-tps",
"banking-bench",
"chacha",
"chacha-cuda",
"chacha-sys",
"cli-config",
"client",
"core",
"faucet",
@ -38,6 +41,9 @@ members = [
"programs/vest",
"programs/vote",
"archiver",
"archiver-lib",
"archiver-utils",
"remote-wallet",
"runtime",
"sdk",
"sdk-c",
@ -45,7 +51,6 @@ members = [
"sys-tuner",
"upload-perf",
"net-utils",
"fixed-buf",
"vote-signer",
"cli",
"rayon-threadlimit",

View File

@ -23,12 +23,12 @@ It's possible for a centralized database to process 710,000 transactions per sec
Furthermore, and much to our surprise, it can be implemented using a mechanism that has existed in Bitcoin since day one. The Bitcoin feature is called nLocktime and it can be used to postdate transactions using block height instead of a timestamp. As a Bitcoin client, you'd use block height instead of a timestamp if you don't trust the network. Block height turns out to be an instance of what's being called a Verifiable Delay Function in cryptography circles. It's a cryptographically secure way to say time has passed. In Solana, we use a far more granular verifiable delay function, a SHA 256 hash chain, to checkpoint the ledger and coordinate consensus. With it, we implement Optimistic Concurrency Control and are now well en route towards that theoretical limit of 710,000 transactions per second.
Architecture
Documentation
===
Before you jump into the code, review the online book [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com/book/).
Before you jump into the code, review the documentation [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com).
(The _latest_ development version of the online book is also [available here](https://docs.solana.com/book/v/master/).)
(The _latest_ development version of the docs is [available here](https://docs.solana.com/v/master).)
Release Binaries
===
@ -87,7 +87,8 @@ $ rustup update
On Linux systems you may need to install libssl-dev, pkg-config, zlib1g-dev, etc. On Ubuntu:
```bash
$ sudo apt-get install libssl-dev pkg-config zlib1g-dev llvm clang
$ sudo apt-get update
$ sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang
```
Download the source code:
@ -120,16 +121,13 @@ $ cargo test
Local Testnet
---
Start your own testnet locally, instructions are in the book [Solana: Blockchain Rebuild for Scale: Getting Started](https://docs.solana.com/book/getting-started).
Start your own testnet locally, instructions are in the online docs [Solana: Blockchain Rebuild for Scale: Getting Started](https://docs.solana.com/building-from-source).
Remote Testnets
---
We maintain several testnets:
* `testnet` - public stable testnet accessible via devnet.solana.com. Runs 24/7
* `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
## Deploy process

View File

@ -138,30 +138,11 @@ There are three release channels that map to branches as follows:
### Update documentation
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.
Document the new recommended version by updating `docs/src/running-archiver.md` and `docs/src/validator-testnet.md` on the release (beta) branch to point at the `solana-install` for the upcoming release version.
#### Publish updated Book
We maintain three copies of the "book" as official documentation:
### Update software on devnet.solana.com
1) "Book" is the documentation for the latest official release. This should get manually updated whenever a new release is made. It is published here:
https://solana-labs.github.io/book/
2) "Book-edge" tracks the tip of the master branch and updates automatically.
https://solana-labs.github.io/book-edge/
3) "Book-beta" tracks the tip of the beta branch and updates automatically.
https://solana-labs.github.io/book-beta/
To manually trigger an update of the "Book", create a new job of the manual-update-book pipeline.
Set the tag of the latest release as the PUBLISH_BOOK_TAG environment variable.
```bash
PUBLISH_BOOK_TAG=v0.16.6
```
https://buildkite.com/solana-labs/manual-update-book
### Update software on testnet.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.
@ -201,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

39
archiver-lib/Cargo.toml Normal file
View File

@ -0,0 +1,39 @@
[package]
name = "solana-archiver-lib"
version = "1.0.2"
description = "Solana Archiver Library"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[dependencies]
bincode = "1.2.1"
crossbeam-channel = "0.3"
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 = "1.0.2" }
solana-storage-program = { path = "../programs/storage", version = "1.0.2" }
thiserror = "1.0"
serde = "1.0.104"
serde_json = "1.0.46"
serde_derive = "1.0.103"
solana-net-utils = { path = "../net-utils", version = "1.0.2" }
solana-chacha = { path = "../chacha", version = "1.0.2" }
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.2" }
solana-ledger = { path = "../ledger", version = "1.0.2" }
solana-logger = { path = "../logger", version = "1.0.2" }
solana-perf = { path = "../perf", version = "1.0.2" }
solana-sdk = { path = "../sdk", version = "1.0.2" }
solana-core = { path = "../core", version = "1.0.2" }
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.2" }
solana-metrics = { path = "../metrics", version = "1.0.2" }
[dev-dependencies]
hex = "0.4.0"
[lib]
name = "solana_archiver_lib"

View File

@ -1,38 +1,42 @@
use crate::{
chacha::{chacha_cbc_encrypt_ledger, CHACHA_BLOCK_SIZE},
use crate::result::ArchiverError;
use crossbeam_channel::unbounded;
use rand::{thread_rng, Rng, SeedableRng};
use rand_chacha::ChaChaRng;
use solana_archiver_utils::sample_file;
use solana_chacha::chacha::{chacha_cbc_encrypt_ledger, CHACHA_BLOCK_SIZE};
use solana_client::{
rpc_client::RpcClient, rpc_request::RpcRequest, rpc_response::RpcStorageTurn,
thin_client::ThinClient,
};
use solana_core::{
cluster_info::{ClusterInfo, Node, VALIDATOR_PORT_RANGE},
contact_info::ContactInfo,
gossip_service::GossipService,
packet::{limited_deserialize, PACKET_DATA_SIZE},
repair_service,
repair_service::{RepairService, RepairSlotRange, RepairStrategy},
result::{Error, Result},
serve_repair::ServeRepair,
shred_fetch_stage::ShredFetchStage,
sigverify_stage::{DisabledSigVerifier, SigVerifyStage},
storage_stage::NUM_STORAGE_SAMPLES,
streamer::{receiver, responder, PacketReceiver},
window_service::WindowService,
};
use crossbeam_channel::unbounded;
use ed25519_dalek;
use rand::{thread_rng, Rng, SeedableRng};
use rand_chacha::ChaChaRng;
use solana_client::{rpc_client::RpcClient, rpc_request::RpcRequest, thin_client::ThinClient};
use solana_ledger::{
blocktree::Blocktree, leader_schedule_cache::LeaderScheduleCache, shred::Shred,
blockstore::Blockstore, leader_schedule_cache::LeaderScheduleCache, shred::Shred,
};
use solana_net_utils::bind_in_range;
use solana_perf::packet::Packets;
use solana_perf::recycler::Recycler;
use solana_sdk::packet::Packet;
use solana_sdk::{
account_utils::State,
account_utils::StateMut,
client::{AsyncClient, SyncClient},
clock::{get_complete_segment_from_slot, get_segment_from_slot, Slot},
commitment_config::CommitmentConfig,
hash::{Hash, Hasher},
hash::Hash,
message::Message,
signature::{Keypair, KeypairUtil, Signature},
signature::{Keypair, Signature, Signer},
timing::timestamp,
transaction::Transaction,
transport::TransportError,
@ -42,9 +46,7 @@ use solana_storage_program::{
storage_instruction::{self, StorageAccountType},
};
use std::{
fs::File,
io::{self, BufReader, ErrorKind, Read, Seek, SeekFrom},
mem::size_of,
io::{self, ErrorKind},
net::{SocketAddr, UdpSocket},
path::{Path, PathBuf},
result,
@ -55,6 +57,8 @@ use std::{
time::Duration,
};
type Result<T> = std::result::Result<T, ArchiverError>;
static ENCRYPTED_FILENAME: &str = "ledger.enc";
#[derive(Serialize, Deserialize)]
@ -82,47 +86,12 @@ struct ArchiverMeta {
client_commitment: CommitmentConfig,
}
pub(crate) fn sample_file(in_path: &Path, sample_offsets: &[u64]) -> io::Result<Hash> {
let in_file = File::open(in_path)?;
let metadata = in_file.metadata()?;
let mut buffer_file = BufReader::new(in_file);
let mut hasher = Hasher::default();
let sample_size = size_of::<Hash>();
let sample_size64 = sample_size as u64;
let mut buf = vec![0; sample_size];
let file_len = metadata.len();
if file_len < sample_size64 {
return Err(io::Error::new(ErrorKind::Other, "file too short!"));
}
for offset in sample_offsets {
if *offset > (file_len - sample_size64) / sample_size64 {
return Err(io::Error::new(ErrorKind::Other, "offset too large"));
}
buffer_file.seek(SeekFrom::Start(*offset * sample_size64))?;
trace!("sampling @ {} ", *offset);
match buffer_file.read(&mut buf) {
Ok(size) => {
assert_eq!(size, buf.len());
hasher.hash(&buf);
}
Err(e) => {
warn!("Error sampling file");
return Err(e);
}
}
}
Ok(hasher.result())
}
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)
@ -222,30 +191,24 @@ impl Archiver {
// Note for now, this ledger will not contain any of the existing entries
// in the ledger located at ledger_path, and will only append on newly received
// entries after being passed to window_service
let blocktree = Arc::new(
Blocktree::open(ledger_path).expect("Expected to be able to open database ledger"),
let blockstore = Arc::new(
Blockstore::open(ledger_path).expect("Expected to be able to open database ledger"),
);
let gossip_service = GossipService::new(
&cluster_info,
Some(blocktree.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, _) =
match crate::gossip_service::discover_cluster(&cluster_entrypoint.gossip, 1) {
match solana_core::gossip_service::discover_cluster(&cluster_entrypoint.gossip, 1) {
Ok(nodes_and_archivers) => nodes_and_archivers,
Err(e) => {
//shutdown services before exiting
exit.store(true, Ordering::Relaxed);
gossip_service.join()?;
return Err(Error::from(e));
return Err(e.into());
}
};
let client = crate::gossip_service::get_client(&nodes);
let client = solana_core::gossip_service::get_client(&nodes);
info!("Setting up mining account...");
if let Err(e) = Self::setup_mining_account(
@ -294,7 +257,7 @@ impl Archiver {
let window_service = match Self::setup(
&mut meta,
cluster_info.clone(),
&blocktree,
&blockstore,
&exit,
&node_info,
&storage_keypair,
@ -320,7 +283,7 @@ impl Archiver {
// run archiver
Self::run(
&mut meta,
&blocktree,
&blockstore,
cluster_info,
&keypair,
&storage_keypair,
@ -344,14 +307,14 @@ impl Archiver {
fn run(
meta: &mut ArchiverMeta,
blocktree: &Arc<Blocktree>,
blockstore: &Arc<Blockstore>,
cluster_info: Arc<RwLock<ClusterInfo>>,
archiver_keypair: &Arc<Keypair>,
storage_keypair: &Arc<Keypair>,
exit: &Arc<AtomicBool>,
) {
// encrypt segment
Self::encrypt_ledger(meta, blocktree).expect("ledger encrypt not successful");
Self::encrypt_ledger(meta, blockstore).expect("ledger encrypt not successful");
let enc_file_path = meta.ledger_data_file_encrypted.clone();
// do replicate
loop {
@ -408,7 +371,7 @@ impl Archiver {
client_commitment: CommitmentConfig,
) {
let nodes = cluster_info.read().unwrap().tvu_peers();
let client = crate::gossip_service::get_client(&nodes);
let client = solana_core::gossip_service::get_client(&nodes);
if let Ok(Some(account)) =
client.get_account_with_commitment(&storage_keypair.pubkey(), client_commitment.clone())
@ -421,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,7 +406,7 @@ impl Archiver {
fn setup(
meta: &mut ArchiverMeta,
cluster_info: Arc<RwLock<ClusterInfo>>,
blocktree: &Arc<Blocktree>,
blockstore: &Arc<Blockstore>,
exit: &Arc<AtomicBool>,
node_info: &ContactInfo,
storage_keypair: &Arc<Keypair>,
@ -474,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();
@ -498,7 +461,7 @@ impl Archiver {
);
let window_service = WindowService::new(
blocktree.clone(),
blockstore.clone(),
cluster_info.clone(),
verified_receiver,
retransmit_sender,
@ -512,7 +475,7 @@ impl Archiver {
Self::wait_for_segment_download(
slot,
slots_per_segment,
&blocktree,
&blockstore,
&exit,
&node_info,
cluster_info,
@ -523,7 +486,7 @@ impl Archiver {
fn wait_for_segment_download(
start_slot: Slot,
slots_per_segment: u64,
blocktree: &Arc<Blocktree>,
blockstore: &Arc<Blockstore>,
exit: &Arc<AtomicBool>,
node_info: &ContactInfo,
cluster_info: Arc<RwLock<ClusterInfo>>,
@ -534,7 +497,7 @@ impl Archiver {
);
let mut current_slot = start_slot;
'outer: loop {
while blocktree.is_full(current_slot) {
while blockstore.is_full(current_slot) {
current_slot += 1;
info!("current slot: {}", current_slot);
if current_slot >= start_slot + slots_per_segment {
@ -553,13 +516,15 @@ impl Archiver {
let mut contact_info = node_info.clone();
contact_info.tvu = "0.0.0.0:0".parse().unwrap();
contact_info.wallclock = timestamp();
// copy over the adopted shred_version from the entrypoint
contact_info.shred_version = cluster_info.read().unwrap().my_data().shred_version;
{
let mut cluster_info_w = cluster_info.write().unwrap();
cluster_info_w.insert_self(contact_info);
}
}
fn encrypt_ledger(meta: &mut ArchiverMeta, blocktree: &Arc<Blocktree>) -> Result<()> {
fn encrypt_ledger(meta: &mut ArchiverMeta, blockstore: &Arc<Blockstore>) -> Result<()> {
meta.ledger_data_file_encrypted = meta.ledger_path.join(ENCRYPTED_FILENAME);
{
@ -567,7 +532,7 @@ impl Archiver {
ivec.copy_from_slice(&meta.signature.as_ref());
let num_encrypted_bytes = chacha_cbc_encrypt_ledger(
blocktree,
blockstore,
meta.slot,
meta.slots_per_segment,
&meta.ledger_data_file_encrypted,
@ -619,9 +584,7 @@ impl Archiver {
client_commitment.clone(),
)? == 0
{
return Err(
io::Error::new(io::ErrorKind::Other, "keypair account has no balance").into(),
);
return Err(ArchiverError::EmptyStorageAccountBalance);
}
info!("checking storage account keypair...");
@ -632,11 +595,8 @@ impl Archiver {
let blockhash =
match client.get_recent_blockhash_with_commitment(client_commitment.clone()) {
Ok((blockhash, _)) => blockhash,
Err(_) => {
return Err(Error::IO(<io::Error>::new(
io::ErrorKind::Other,
"unable to get recent blockhash, can't submit proof",
)));
Err(e) => {
return Err(ArchiverError::TransportError(e));
}
};
@ -670,7 +630,7 @@ impl Archiver {
) {
// No point if we've got no storage account...
let nodes = cluster_info.read().unwrap().tvu_peers();
let client = crate::gossip_service::get_client(&nodes);
let client = solana_core::gossip_service::get_client(&nodes);
let storage_balance = client.poll_get_balance_with_commitment(
&storage_keypair.pubkey(),
meta.client_commitment.clone(),
@ -711,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,
@ -734,10 +694,10 @@ impl Archiver {
fn get_segment_config(
cluster_info: &Arc<RwLock<ClusterInfo>>,
client_commitment: CommitmentConfig,
) -> result::Result<u64, Error> {
) -> Result<u64> {
let rpc_peers = {
let cluster_info = cluster_info.read().unwrap();
cluster_info.rpc_peers()
cluster_info.all_rpc_peers()
};
debug!("rpc peers: {:?}", rpc_peers);
if !rpc_peers.is_empty() {
@ -753,12 +713,12 @@ impl Archiver {
)
.map_err(|err| {
warn!("Error while making rpc request {:?}", err);
Error::IO(io::Error::new(ErrorKind::Other, "rpc error"))
ArchiverError::ClientError(err)
})?
.as_u64()
.unwrap())
} else {
Err(io::Error::new(io::ErrorKind::Other, "No RPC peers...".to_string()).into())
Err(ArchiverError::NoRpcPeers)
}
}
@ -768,7 +728,7 @@ impl Archiver {
slots_per_segment: u64,
previous_blockhash: &Hash,
exit: &Arc<AtomicBool>,
) -> result::Result<(Hash, u64), Error> {
) -> Result<(Hash, u64)> {
loop {
let (blockhash, turn_slot) = Self::poll_for_blockhash_and_slot(
cluster_info,
@ -788,12 +748,12 @@ impl Archiver {
slots_per_segment: u64,
previous_blockhash: &Hash,
exit: &Arc<AtomicBool>,
) -> result::Result<(Hash, u64), Error> {
) -> Result<(Hash, u64)> {
info!("waiting for the next turn...");
loop {
let rpc_peers = {
let cluster_info = cluster_info.read().unwrap();
cluster_info.rpc_peers()
cluster_info.all_rpc_peers()
};
debug!("rpc peers: {:?}", rpc_peers);
if !rpc_peers.is_empty() {
@ -809,15 +769,13 @@ impl Archiver {
)
.map_err(|err| {
warn!("Error while making rpc request {:?}", err);
Error::IO(io::Error::new(ErrorKind::Other, "rpc error"))
})?;
let (storage_blockhash, turn_slot) =
serde_json::from_value::<(String, u64)>(response).map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("Couldn't parse response: {:?}", err),
)
ArchiverError::ClientError(err)
})?;
let RpcStorageTurn {
blockhash: storage_blockhash,
slot: turn_slot,
} = serde_json::from_value::<RpcStorageTurn>(response)
.map_err(ArchiverError::JsonError)?;
let turn_blockhash = storage_blockhash.parse().map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
@ -835,7 +793,7 @@ impl Archiver {
}
}
if exit.load(Ordering::Relaxed) {
return Err(Error::IO(io::Error::new(
return Err(ArchiverError::IO(io::Error::new(
ErrorKind::Other,
"exit signalled...",
)));
@ -844,15 +802,15 @@ impl Archiver {
}
}
/// Ask an archiver to populate a given blocktree with its segment.
/// Ask an archiver to populate a given blockstore with its segment.
/// Return the slot at the start of the archiver's segment
///
/// It is recommended to use a temporary blocktree for this since the download will not verify
/// 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,
blocktree: &Arc<Blocktree>,
blockstore: &Arc<Blockstore>,
slots_per_segment: u64,
) -> Result<u64> {
// Create a client which downloads from the archiver and see that it
@ -870,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 {
@ -884,7 +842,7 @@ impl Archiver {
for _ in 0..120 {
// Strategy used by archivers
let repairs = RepairService::generate_repairs_in_range(
blocktree,
blockstore,
repair_service::MAX_REPAIR_LENGTH,
&repair_slot_range,
);
@ -893,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()
@ -930,10 +886,10 @@ impl Archiver {
.into_iter()
.filter_map(|p| Shred::new_from_serialized_shred(p.data.to_vec()).ok())
.collect();
blocktree.insert_shreds(shreds, None, false)?;
blockstore.insert_shreds(shreds, None, false)?;
}
// check if all the slots in the segment are complete
if Self::segment_complete(start_slot, slots_per_segment, blocktree) {
if Self::segment_complete(start_slot, slots_per_segment, blockstore) {
break;
}
sleep(Duration::from_millis(500));
@ -942,10 +898,8 @@ impl Archiver {
t_receiver.join().unwrap();
// check if all the slots in the segment are complete
if !Self::segment_complete(start_slot, slots_per_segment, blocktree) {
return Err(
io::Error::new(ErrorKind::Other, "Unable to download the full segment").into(),
);
if !Self::segment_complete(start_slot, slots_per_segment, blockstore) {
return Err(ArchiverError::SegmentDownloadError);
}
Ok(start_slot)
}
@ -953,10 +907,10 @@ impl Archiver {
fn segment_complete(
start_slot: Slot,
slots_per_segment: u64,
blocktree: &Arc<Blocktree>,
blockstore: &Arc<Blockstore>,
) -> bool {
for slot in start_slot..(start_slot + slots_per_segment) {
if !blocktree.is_full(slot) {
if !blockstore.is_full(slot) {
return false;
}
}
@ -988,74 +942,3 @@ impl Archiver {
panic!("Couldn't get segment slot from archiver!");
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::{create_dir_all, remove_file};
use std::io::Write;
fn tmp_file_path(name: &str) -> PathBuf {
use std::env;
let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
let keypair = Keypair::new();
let mut path = PathBuf::new();
path.push(out_dir);
path.push("tmp");
create_dir_all(&path).unwrap();
path.push(format!("{}-{}", name, keypair.pubkey()));
path
}
#[test]
fn test_sample_file() {
solana_logger::setup();
let in_path = tmp_file_path("test_sample_file_input.txt");
let num_strings = 4096;
let string = "12foobar";
{
let mut in_file = File::create(&in_path).unwrap();
for _ in 0..num_strings {
in_file.write(string.as_bytes()).unwrap();
}
}
let num_samples = (string.len() * num_strings / size_of::<Hash>()) as u64;
let samples: Vec<_> = (0..num_samples).collect();
let res = sample_file(&in_path, samples.as_slice());
let ref_hash: Hash = Hash::new(&[
173, 251, 182, 165, 10, 54, 33, 150, 133, 226, 106, 150, 99, 192, 179, 1, 230, 144,
151, 126, 18, 191, 54, 67, 249, 140, 230, 160, 56, 30, 170, 52,
]);
let res = res.unwrap();
assert_eq!(res, ref_hash);
// Sample just past the end
assert!(sample_file(&in_path, &[num_samples]).is_err());
remove_file(&in_path).unwrap();
}
#[test]
fn test_sample_file_invalid_offset() {
let in_path = tmp_file_path("test_sample_file_invalid_offset_input.txt");
{
let mut in_file = File::create(&in_path).unwrap();
for _ in 0..4096 {
in_file.write("123456foobar".as_bytes()).unwrap();
}
}
let samples = [0, 200000];
let res = sample_file(&in_path, &samples);
assert!(res.is_err());
remove_file(in_path).unwrap();
}
#[test]
fn test_sample_file_missing_file() {
let in_path = tmp_file_path("test_sample_file_that_doesnt_exist.txt");
let samples = [0, 5];
let res = sample_file(&in_path, &samples);
assert!(res.is_err());
}
}

11
archiver-lib/src/lib.rs Normal file
View File

@ -0,0 +1,11 @@
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate solana_metrics;
pub mod archiver;
mod result;

View File

@ -0,0 +1,48 @@
use serde_json;
use solana_client::client_error;
use solana_ledger::blockstore;
use solana_sdk::transport;
use std::any::Any;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ArchiverError {
#[error("IO error")]
IO(#[from] std::io::Error),
#[error("blockstore error")]
BlockstoreError(#[from] blockstore::BlockstoreError),
#[error("crossbeam error")]
CrossbeamSendError(#[from] crossbeam_channel::SendError<u64>),
#[error("send error")]
SendError(#[from] std::sync::mpsc::SendError<u64>),
#[error("join error")]
JoinError(Box<dyn Any + Send + 'static>),
#[error("transport error")]
TransportError(#[from] transport::TransportError),
#[error("client error")]
ClientError(#[from] client_error::ClientError),
#[error("Json parsing error")]
JsonError(#[from] serde_json::error::Error),
#[error("Storage account has no balance")]
EmptyStorageAccountBalance,
#[error("No RPC peers..")]
NoRpcPeers,
#[error("Couldn't download full segment")]
SegmentDownloadError,
}
impl std::convert::From<Box<dyn Any + Send + 'static>> for ArchiverError {
fn from(e: Box<dyn Any + Send + 'static>) -> ArchiverError {
ArchiverError::JoinError(e)
}
}

25
archiver-utils/Cargo.toml Normal file
View File

@ -0,0 +1,25 @@
[package]
name = "solana-archiver-utils"
version = "1.0.2"
description = "Solana Archiver Utils"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[dependencies]
log = "0.4.8"
rand = "0.6.5"
solana-chacha = { path = "../chacha", version = "1.0.2" }
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.2" }
solana-ledger = { path = "../ledger", version = "1.0.2" }
solana-logger = { path = "../logger", version = "1.0.2" }
solana-perf = { path = "../perf", version = "1.0.2" }
solana-sdk = { path = "../sdk", version = "1.0.2" }
[dev-dependencies]
hex = "0.4.0"
[lib]
name = "solana_archiver_utils"

120
archiver-utils/src/lib.rs Normal file
View File

@ -0,0 +1,120 @@
#[macro_use]
extern crate log;
use solana_sdk::hash::{Hash, Hasher};
use std::fs::File;
use std::io::{self, BufReader, ErrorKind, Read, Seek, SeekFrom};
use std::mem::size_of;
use std::path::Path;
pub fn sample_file(in_path: &Path, sample_offsets: &[u64]) -> io::Result<Hash> {
let in_file = File::open(in_path)?;
let metadata = in_file.metadata()?;
let mut buffer_file = BufReader::new(in_file);
let mut hasher = Hasher::default();
let sample_size = size_of::<Hash>();
let sample_size64 = sample_size as u64;
let mut buf = vec![0; sample_size];
let file_len = metadata.len();
if file_len < sample_size64 {
return Err(io::Error::new(ErrorKind::Other, "file too short!"));
}
for offset in sample_offsets {
if *offset > (file_len - sample_size64) / sample_size64 {
return Err(io::Error::new(ErrorKind::Other, "offset too large"));
}
buffer_file.seek(SeekFrom::Start(*offset * sample_size64))?;
trace!("sampling @ {} ", *offset);
match buffer_file.read(&mut buf) {
Ok(size) => {
assert_eq!(size, buf.len());
hasher.hash(&buf);
}
Err(e) => {
warn!("Error sampling file");
return Err(e);
}
}
}
Ok(hasher.result())
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{thread_rng, Rng};
use std::fs::{create_dir_all, remove_file};
use std::io::Write;
use std::path::PathBuf;
extern crate hex;
fn tmp_file_path(name: &str) -> PathBuf {
use std::env;
let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
let mut rand_bits = [0u8; 32];
thread_rng().fill(&mut rand_bits[..]);
let mut path = PathBuf::new();
path.push(out_dir);
path.push("tmp");
create_dir_all(&path).unwrap();
path.push(format!("{}-{:?}", name, hex::encode(rand_bits)));
println!("path: {:?}", path);
path
}
#[test]
fn test_sample_file() {
solana_logger::setup();
let in_path = tmp_file_path("test_sample_file_input.txt");
let num_strings = 4096;
let string = "12foobar";
{
let mut in_file = File::create(&in_path).unwrap();
for _ in 0..num_strings {
in_file.write(string.as_bytes()).unwrap();
}
}
let num_samples = (string.len() * num_strings / size_of::<Hash>()) as u64;
let samples: Vec<_> = (0..num_samples).collect();
let res = sample_file(&in_path, samples.as_slice());
let ref_hash: Hash = Hash::new(&[
173, 251, 182, 165, 10, 54, 33, 150, 133, 226, 106, 150, 99, 192, 179, 1, 230, 144,
151, 126, 18, 191, 54, 67, 249, 140, 230, 160, 56, 30, 170, 52,
]);
let res = res.unwrap();
assert_eq!(res, ref_hash);
// Sample just past the end
assert!(sample_file(&in_path, &[num_samples]).is_err());
remove_file(&in_path).unwrap();
}
#[test]
fn test_sample_file_invalid_offset() {
let in_path = tmp_file_path("test_sample_file_invalid_offset_input.txt");
{
let mut in_file = File::create(&in_path).unwrap();
for _ in 0..4096 {
in_file.write("123456foobar".as_bytes()).unwrap();
}
}
let samples = [0, 200000];
let res = sample_file(&in_path, &samples);
assert!(res.is_err());
remove_file(in_path).unwrap();
}
#[test]
fn test_sample_file_missing_file() {
let in_path = tmp_file_path("test_sample_file_that_doesnt_exist.txt");
let samples = [0, 5];
let res = sample_file(&in_path, &samples);
assert!(res.is_err());
}
}

View File

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

View File

@ -1,5 +1,6 @@
use clap::{crate_description, crate_name, App, Arg};
use console::style;
use solana_archiver_lib::archiver::Archiver;
use solana_clap_utils::{
input_validators::is_keypair,
keypair::{
@ -8,11 +9,10 @@ use solana_clap_utils::{
},
};
use solana_core::{
archiver::Archiver,
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.22.0"
version = "1.0.2"
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.22.0" }
solana-ledger = { path = "../ledger", version = "0.22.0" }
solana-logger = { path = "../logger", version = "0.22.0" }
solana-runtime = { path = "../runtime", version = "0.22.0" }
solana-measure = { path = "../measure", version = "0.22.0" }
solana-sdk = { path = "../sdk", version = "0.22.0" }
solana-core = { path = "../core", version = "1.0.2" }
solana-ledger = { path = "../ledger", version = "1.0.2" }
solana-logger = { path = "../logger", version = "1.0.2" }
solana-runtime = { path = "../runtime", version = "1.0.2" }
solana-measure = { path = "../measure", version = "1.0.2" }
solana-sdk = { path = "../sdk", version = "1.0.2" }
rand = "0.6.5"
crossbeam-channel = "0.3"

View File

@ -10,7 +10,7 @@ use solana_core::packet::to_packets_chunked;
use solana_core::poh_recorder::PohRecorder;
use solana_core::poh_recorder::WorkingBankEntry;
use solana_ledger::bank_forks::BankForks;
use solana_ledger::{blocktree::Blocktree, get_tmp_ledger_path};
use solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path};
use solana_measure::measure::Measure;
use solana_runtime::bank::Bank;
use solana_sdk::hash::Hash;
@ -139,11 +139,11 @@ fn main() {
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
let ledger_path = get_tmp_ledger_path!();
{
let blocktree = Arc::new(
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"),
let blockstore = Arc::new(
Blockstore::open(&ledger_path).expect("Expected to be able to open database ledger"),
);
let (exit, poh_recorder, poh_service, signal_receiver) =
create_test_recorder(&bank, &blocktree, None);
create_test_recorder(&bank, &blockstore, None);
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
let cluster_info = Arc::new(RwLock::new(cluster_info));
let banking_stage = BankingStage::new(
@ -162,8 +162,8 @@ fn main() {
// If it is dropped before poh_service, then poh_service will error when
// calling send() on the channel.
let signal_receiver = Arc::new(signal_receiver);
let mut total = 0;
let mut tx_total = 0;
let mut total_us = 0;
let mut tx_total_us = 0;
let mut txs_processed = 0;
let mut root = 1;
let collector = Pubkey::new_rand();
@ -173,6 +173,7 @@ fn main() {
chunk_len,
num_threads,
};
let mut total_sent = 0;
for _ in 0..ITERS {
let now = Instant::now();
let mut sent = 0;
@ -223,7 +224,7 @@ fn main() {
);
assert!(txs_processed < bank.transaction_count());
txs_processed = bank.transaction_count();
tx_total += duration_as_us(&now.elapsed());
tx_total_us += duration_as_us(&now.elapsed());
let mut poh_time = Measure::start("poh_time");
poh_recorder.lock().unwrap().reset(
@ -255,20 +256,21 @@ fn main() {
poh_time.as_us(),
);
} else {
tx_total += duration_as_us(&now.elapsed());
tx_total_us += duration_as_us(&now.elapsed());
}
// This signature clear may not actually clear the signatures
// in this chunk, but since we rotate between CHUNKS then
// we should clear them by the time we come around again to re-use that chunk.
bank.clear_signatures();
total += duration_as_us(&now.elapsed());
total_us += duration_as_us(&now.elapsed());
debug!(
"time: {} us checked: {} sent: {}",
duration_as_us(&now.elapsed()),
txes / CHUNKS,
sent,
);
total_sent += sent;
if bank.slot() > 0 && bank.slot() % 16 == 0 {
for tx in transactions.iter_mut() {
@ -284,11 +286,11 @@ fn main() {
}
eprintln!(
"{{'name': 'banking_bench_total', 'median': '{}'}}",
total / ITERS as u64,
(1000.0 * 1000.0 * total_sent as f64) / (total_us as f64),
);
eprintln!(
"{{'name': 'banking_bench_tx_total', 'median': '{}'}}",
tx_total / ITERS as u64,
(1000.0 * 1000.0 * total_sent as f64) / (tx_total_us as f64),
);
drop(verified_sender);
@ -300,5 +302,5 @@ fn main() {
sleep(Duration::from_secs(1));
debug!("waited for poh_service");
}
let _unused = Blocktree::destroy(&ledger_path);
let _unused = Blockstore::destroy(&ledger_path);
}

View File

@ -2,40 +2,33 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-exchange"
version = "0.22.0"
version = "1.0.2"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
publish = false
[dependencies]
bincode = "1.2.1"
bs58 = "0.3.0"
clap = "2.32.0"
env_logger = "0.7.1"
itertools = "0.8.2"
log = "0.4.8"
num-derive = "0.3"
num-traits = "0.2"
rand = "0.6.5"
rayon = "1.2.0"
serde = "1.0.104"
serde_derive = "1.0.103"
serde_json = "1.0.44"
serde_json = "1.0.46"
serde_yaml = "0.8.11"
solana-clap-utils = { path = "../clap-utils", version = "0.22.0" }
solana-core = { path = "../core", version = "0.22.0" }
solana-genesis = { path = "../genesis", version = "0.22.0" }
solana-client = { path = "../client", version = "0.22.0" }
solana-faucet = { path = "../faucet", version = "0.22.0" }
solana-exchange-program = { path = "../programs/exchange", version = "0.22.0" }
solana-logger = { path = "../logger", version = "0.22.0" }
solana-metrics = { path = "../metrics", version = "0.22.0" }
solana-net-utils = { path = "../net-utils", version = "0.22.0" }
solana-runtime = { path = "../runtime", version = "0.22.0" }
solana-sdk = { path = "../sdk", version = "0.22.0" }
untrusted = "0.7.0"
ws = "0.9.1"
solana-clap-utils = { path = "../clap-utils", version = "1.0.2" }
solana-core = { path = "../core", version = "1.0.2" }
solana-genesis = { path = "../genesis", version = "1.0.2" }
solana-client = { path = "../client", version = "1.0.2" }
solana-faucet = { path = "../faucet", version = "1.0.2" }
solana-exchange-program = { path = "../programs/exchange", version = "1.0.2" }
solana-logger = { path = "../logger", version = "1.0.2" }
solana-metrics = { path = "../metrics", version = "1.0.2" }
solana-net-utils = { path = "../net-utils", version = "1.0.2" }
solana-runtime = { path = "../runtime", version = "1.0.2" }
solana-sdk = { path = "../sdk", version = "1.0.2" }
[dev-dependencies]
solana-local-cluster = { path = "../local-cluster", version = "0.22.0" }
solana-local-cluster = { path = "../local-cluster", version = "1.0.2" }

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.22.0"
version = "1.0.2"
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.22.0" }
solana-core = { path = "../core", version = "0.22.0" }
solana-logger = { path = "../logger", version = "0.22.0" }
solana-net-utils = { path = "../net-utils", version = "0.22.0" }
solana-clap-utils = { path = "../clap-utils", version = "1.0.2" }
solana-core = { path = "../core", version = "1.0.2" }
solana-logger = { path = "../logger", version = "1.0.2" }
solana-net-utils = { path = "../net-utils", version = "1.0.2" }

View File

@ -1,6 +1,5 @@
use clap::{crate_description, crate_name, App, Arg};
use solana_core::packet::{Packet, Packets, PacketsRecycler, PACKET_DATA_SIZE};
use solana_core::result::Result;
use solana_core::streamer::{receiver, PacketReceiver};
use std::cmp::max;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
@ -8,7 +7,7 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::mpsc::channel;
use std::sync::Arc;
use std::thread::sleep;
use std::thread::{spawn, JoinHandle};
use std::thread::{spawn, JoinHandle, Result};
use std::time::Duration;
use std::time::SystemTime;

View File

@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-tps"
version = "0.22.0"
version = "1.0.2"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -12,28 +12,26 @@ bincode = "1.2.1"
clap = "2.33.0"
log = "0.4.8"
rayon = "1.2.0"
serde = "1.0.104"
serde_derive = "1.0.103"
serde_json = "1.0.44"
serde_json = "1.0.46"
serde_yaml = "0.8.11"
solana-clap-utils = { path = "../clap-utils", version = "0.22.0" }
solana-core = { path = "../core", version = "0.22.0" }
solana-genesis = { path = "../genesis", version = "0.22.0" }
solana-client = { path = "../client", version = "0.22.0" }
solana-faucet = { path = "../faucet", version = "0.22.0" }
solana-librapay = { path = "../programs/librapay", version = "0.22.0", optional = true }
solana-logger = { path = "../logger", version = "0.22.0" }
solana-metrics = { path = "../metrics", version = "0.22.0" }
solana-measure = { path = "../measure", version = "0.22.0" }
solana-net-utils = { path = "../net-utils", version = "0.22.0" }
solana-runtime = { path = "../runtime", version = "0.22.0" }
solana-sdk = { path = "../sdk", version = "0.22.0" }
solana-move-loader-program = { path = "../programs/move_loader", version = "0.22.0", optional = true }
solana-clap-utils = { path = "../clap-utils", version = "1.0.2" }
solana-core = { path = "../core", version = "1.0.2" }
solana-genesis = { path = "../genesis", version = "1.0.2" }
solana-client = { path = "../client", version = "1.0.2" }
solana-faucet = { path = "../faucet", version = "1.0.2" }
solana-librapay = { path = "../programs/librapay", version = "1.0.2", optional = true }
solana-logger = { path = "../logger", version = "1.0.2" }
solana-metrics = { path = "../metrics", version = "1.0.2" }
solana-measure = { path = "../measure", version = "1.0.2" }
solana-net-utils = { path = "../net-utils", version = "1.0.2" }
solana-runtime = { path = "../runtime", version = "1.0.2" }
solana-sdk = { path = "../sdk", version = "1.0.2" }
solana-move-loader-program = { path = "../programs/move_loader", version = "1.0.2", optional = true }
[dev-dependencies]
serial_test = "0.3.2"
serial_test_derive = "0.3.1"
solana-local-cluster = { path = "../local-cluster", version = "0.22.0" }
serial_test_derive = "0.4.0"
solana-local-cluster = { path = "../local-cluster", version = "1.0.2" }
[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,14 +15,13 @@ 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,
};
use std::{
cmp,
collections::VecDeque,
collections::{HashSet, VecDeque},
net::SocketAddr,
process::exit,
sync::{
@ -66,10 +65,9 @@ fn get_recent_blockhash<T: Client>(client: &T) -> (Hash, FeeCalculator) {
}
pub fn do_bench_tps<T>(
clients: Vec<T>,
client: Arc<T>,
config: Config,
gen_keypairs: Vec<Keypair>,
keypair0_balance: u64,
libra_args: Option<LibraKeys>,
) -> u64
where
@ -82,13 +80,9 @@ where
duration,
tx_count,
sustained,
num_lamports_per_account,
..
} = config;
let clients: Vec<_> = clients.into_iter().map(Arc::new).collect();
let client = &clients[0];
let mut source_keypair_chunks: Vec<Vec<&Keypair>> = Vec::new();
let mut dest_keypair_chunks: Vec<VecDeque<&Keypair>> = Vec::new();
assert!(gen_keypairs.len() >= 2 * tx_count);
@ -115,20 +109,17 @@ where
let maxes = Arc::new(RwLock::new(Vec::new()));
let sample_period = 1; // in seconds
info!("Sampling TPS every {} second...", sample_period);
let v_threads: Vec<_> = clients
.iter()
.map(|client| {
let exit_signal = exit_signal.clone();
let maxes = maxes.clone();
let client = client.clone();
Builder::new()
.name("solana-client-sample".to_string())
.spawn(move || {
sample_txs(&exit_signal, &maxes, sample_period, &client);
})
.unwrap()
})
.collect();
let sample_thread = {
let exit_signal = exit_signal.clone();
let maxes = maxes.clone();
let client = client.clone();
Builder::new()
.name("solana-client-sample".to_string())
.spawn(move || {
sample_txs(&exit_signal, &maxes, sample_period, &client);
})
.unwrap()
};
let shared_txs: SharedTransactions = Arc::new(RwLock::new(VecDeque::new()));
@ -174,11 +165,10 @@ where
// generate and send transactions for the specified duration
let start = Instant::now();
let keypair_chunks = source_keypair_chunks.len() as u64;
let keypair_chunks = source_keypair_chunks.len();
let mut reclaim_lamports_back_to_source_account = false;
let mut i = keypair0_balance;
let mut chunk_index = 0;
while start.elapsed() < duration {
let chunk_index = (i % keypair_chunks) as usize;
generate_txs(
&shared_txs,
&recent_blockhash,
@ -197,7 +187,9 @@ where
sleep(Duration::from_millis(1));
}
} else {
while shared_tx_active_thread_count.load(Ordering::Relaxed) > 0 {
while !shared_txs.read().unwrap().is_empty()
|| shared_tx_active_thread_count.load(Ordering::Relaxed) > 0
{
sleep(Duration::from_millis(1));
}
}
@ -206,8 +198,11 @@ where
// transaction signatures even when blockhash is reused.
dest_keypair_chunks[chunk_index].rotate_left(1);
i += 1;
if should_switch_directions(num_lamports_per_account, keypair_chunks, i) {
// Move on to next chunk
chunk_index = (chunk_index + 1) % keypair_chunks;
// Switch directions after transfering for each "chunk"
if chunk_index == 0 {
reclaim_lamports_back_to_source_account = !reclaim_lamports_back_to_source_account;
}
}
@ -215,11 +210,9 @@ where
// Stop the sampling threads so it will collect the stats
exit_signal.store(true, Ordering::Relaxed);
info!("Waiting for validator threads...");
for t in v_threads {
if let Err(err) = t.join() {
info!(" join() failed with: {:?}", err);
}
info!("Waiting for sampler threads...");
if let Err(err) = sample_thread.join() {
info!(" join() failed with: {:?}", err);
}
// join the tx send threads
@ -251,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)
);
@ -382,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)
);
@ -488,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)
@ -500,177 +493,218 @@ fn do_tx_transfers<T: Client>(
}
}
fn verify_funding_transfer<T: Client>(client: &T, tx: &Transaction, amount: u64) -> bool {
fn verify_funding_transfer<T: Client>(client: &Arc<T>, tx: &Transaction, amount: u64) -> bool {
for a in &tx.message().account_keys[1..] {
if client
.get_balance_with_commitment(a, CommitmentConfig::recent())
.unwrap_or(0)
>= amount
{
return true;
match client.get_balance_with_commitment(a, CommitmentConfig::recent()) {
Ok(balance) => return balance >= amount,
Err(err) => error!("failed to get balance {:?}", err),
}
}
false
}
trait FundingTransactions<'a> {
fn fund<T: 'static + Client + Send + Sync>(
&mut self,
client: &Arc<T>,
to_fund: &[(&'a Keypair, Vec<(Pubkey, u64)>)],
to_lamports: u64,
);
fn make(&mut self, to_fund: &[(&'a Keypair, Vec<(Pubkey, u64)>)]);
fn sign(&mut self, blockhash: Hash);
fn send<T: Client>(&self, client: &Arc<T>);
fn verify<T: 'static + Client + Send + Sync>(&mut self, client: &Arc<T>, to_lamports: u64);
}
impl<'a> FundingTransactions<'a> for Vec<(&'a Keypair, Transaction)> {
fn fund<T: 'static + Client + Send + Sync>(
&mut self,
client: &Arc<T>,
to_fund: &[(&'a Keypair, Vec<(Pubkey, u64)>)],
to_lamports: u64,
) {
self.make(to_fund);
let mut tries = 0;
while !self.is_empty() {
info!(
"{} {} each to {} accounts in {} txs",
if tries == 0 {
"transferring"
} else {
" retrying"
},
to_lamports,
self.len() * MAX_SPENDS_PER_TX as usize,
self.len(),
);
let (blockhash, _fee_calculator) = get_recent_blockhash(client.as_ref());
// re-sign retained to_fund_txes with updated blockhash
self.sign(blockhash);
self.send(&client);
// Sleep a few slots to allow transactions to process
sleep(Duration::from_secs(1));
self.verify(&client, to_lamports);
// retry anything that seems to have dropped through cracks
// again since these txs are all or nothing, they're fine to
// retry
tries += 1;
}
info!("transferred");
}
fn make(&mut self, to_fund: &[(&'a Keypair, Vec<(Pubkey, u64)>)]) {
let mut make_txs = Measure::start("make_txs");
let to_fund_txs: Vec<(&Keypair, Transaction)> = to_fund
.par_iter()
.map(|(k, t)| {
let tx = Transaction::new_unsigned_instructions(system_instruction::transfer_many(
&k.pubkey(),
&t,
));
(*k, tx)
})
.collect();
make_txs.stop();
debug!(
"make {} unsigned txs: {}us",
to_fund_txs.len(),
make_txs.as_us()
);
self.extend(to_fund_txs);
}
fn sign(&mut self, blockhash: Hash) {
let mut sign_txs = Measure::start("sign_txs");
self.par_iter_mut().for_each(|(k, tx)| {
tx.sign(&[*k], blockhash);
});
sign_txs.stop();
debug!("sign {} txs: {}us", self.len(), sign_txs.as_us());
}
fn send<T: Client>(&self, client: &Arc<T>) {
let mut send_txs = Measure::start("send_txs");
self.iter().for_each(|(_, tx)| {
client.async_send_transaction(tx.clone()).expect("transfer");
});
send_txs.stop();
debug!("send {} txs: {}us", self.len(), send_txs.as_us());
}
fn verify<T: 'static + Client + Send + Sync>(&mut self, client: &Arc<T>, to_lamports: u64) {
let starting_txs = self.len();
let verified_txs = Arc::new(AtomicUsize::new(0));
let too_many_failures = Arc::new(AtomicBool::new(false));
let loops = if starting_txs < 1000 { 3 } else { 1 };
// Only loop multiple times for small (quick) transaction batches
for _ in 0..loops {
let failed_verify = Arc::new(AtomicUsize::new(0));
let client = client.clone();
let verified_txs = &verified_txs;
let failed_verify = &failed_verify;
let too_many_failures = &too_many_failures;
let verified_set: HashSet<Pubkey> = self
.par_iter()
.filter_map(move |(k, tx)| {
if too_many_failures.load(Ordering::Relaxed) {
return None;
}
let verified = if verify_funding_transfer(&client, &tx, to_lamports) {
verified_txs.fetch_add(1, Ordering::Relaxed);
Some(k.pubkey())
} else {
failed_verify.fetch_add(1, Ordering::Relaxed);
None
};
let verified_txs = verified_txs.load(Ordering::Relaxed);
let failed_verify = failed_verify.load(Ordering::Relaxed);
let remaining_count = starting_txs.saturating_sub(verified_txs + failed_verify);
if failed_verify > 100 && failed_verify > verified_txs {
too_many_failures.store(true, Ordering::Relaxed);
warn!(
"Too many failed transfers... {} remaining, {} verified, {} failures",
remaining_count, verified_txs, failed_verify
);
}
if remaining_count % 100 == 0 {
info!(
"Verifying transfers... {} remaining, {} verified, {} failures",
remaining_count, verified_txs, failed_verify
);
}
verified
})
.collect();
self.retain(|(k, _)| !verified_set.contains(&k.pubkey()));
if self.is_empty() {
break;
}
info!("Looping verifications");
let verified_txs = verified_txs.load(Ordering::Relaxed);
let failed_verify = failed_verify.load(Ordering::Relaxed);
let remaining_count = starting_txs.saturating_sub(verified_txs + failed_verify);
info!(
"Verifying transfers... {} remaining, {} verified, {} failures",
remaining_count, verified_txs, failed_verify
);
sleep(Duration::from_millis(100));
}
}
}
/// fund the dests keys by spending all of the source keys into MAX_SPENDS_PER_TX
/// on every iteration. This allows us to replay the transfers because the source is either empty,
/// or full
pub fn fund_keys<T: Client>(
client: &T,
pub fn fund_keys<T: 'static + Client + Send + Sync>(
client: Arc<T>,
source: &Keypair,
dests: &[Keypair],
total: u64,
max_fee: u64,
mut extra: u64,
lamports_per_account: u64,
) {
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
let mut notfunded: Vec<&Keypair> = dests.iter().collect();
let lamports_per_account = (total - (extra * max_fee)) / (notfunded.len() as u64 + 1);
info!(
"funding keys {} with lamports: {:?} total: {}",
dests.len(),
client.get_balance(&source.pubkey()),
total
);
while !notfunded.is_empty() {
let mut new_funded: Vec<(&Keypair, u64)> = vec![];
let mut to_fund = vec![];
info!("creating from... {}", funded.len());
let mut build_to_fund = Measure::start("build_to_fund");
for f in &mut funded {
let max_units = cmp::min(notfunded.len() as u64, MAX_SPENDS_PER_TX);
if max_units == 0 {
break;
}
let start = notfunded.len() - max_units as usize;
let fees = if extra > 0 { max_fee } else { 0 };
let per_unit = (f.1 - lamports_per_account - fees) / max_units;
let moves: Vec<_> = notfunded[start..]
.iter()
.map(|k| (k.pubkey(), per_unit))
.collect();
notfunded[start..]
.iter()
.for_each(|k| new_funded.push((k, per_unit)));
notfunded.truncate(start);
if !moves.is_empty() {
to_fund.push((f.0, moves));
}
extra -= 1;
let mut funded: Vec<&Keypair> = vec![source];
let mut funded_funds = total;
let mut not_funded: Vec<&Keypair> = dests.iter().collect();
while !not_funded.is_empty() {
// Build to fund list and prepare funding sources for next iteration
let mut new_funded: Vec<&Keypair> = vec![];
let mut to_fund: Vec<(&Keypair, Vec<(Pubkey, u64)>)> = vec![];
let to_lamports = (funded_funds - lamports_per_account - max_fee) / MAX_SPENDS_PER_TX;
for f in funded {
let start = not_funded.len() - MAX_SPENDS_PER_TX as usize;
let dests: Vec<_> = not_funded.drain(start..).collect();
let spends: Vec<_> = dests.iter().map(|k| (k.pubkey(), to_lamports)).collect();
to_fund.push((f, spends));
new_funded.extend(dests.into_iter());
}
build_to_fund.stop();
debug!("build to_fund vec: {}us", build_to_fund.as_us());
// try to transfer a "few" at a time with recent blockhash
// assume 4MB network buffers, and 512 byte packets
const FUND_CHUNK_LEN: usize = 4 * 1024 * 1024 / 512;
to_fund.chunks(FUND_CHUNK_LEN).for_each(|chunk| {
let mut tries = 0;
let mut make_txs = Measure::start("make_txs");
// this set of transactions just initializes us for bookkeeping
#[allow(clippy::clone_double_ref)] // sigh
let mut to_fund_txs: Vec<_> = chunk
.par_iter()
.map(|(k, m)| {
let tx = Transaction::new_unsigned_instructions(
system_instruction::transfer_many(&k.pubkey(), &m),
);
(k.clone(), tx)
})
.collect();
make_txs.stop();
debug!(
"make {} unsigned txs: {}us",
to_fund_txs.len(),
make_txs.as_us()
Vec::<(&Keypair, Transaction)>::with_capacity(chunk.len()).fund(
&client,
chunk,
to_lamports,
);
let amount = chunk[0].1[0].1;
while !to_fund_txs.is_empty() {
let receivers = to_fund_txs
.iter()
.fold(0, |len, (_, tx)| len + tx.message().instructions.len());
info!(
"{} {} to {} in {} txs",
if tries == 0 {
"transferring"
} else {
" retrying"
},
amount,
receivers,
to_fund_txs.len(),
);
let (blockhash, _fee_calculator) = get_recent_blockhash(client);
// re-sign retained to_fund_txes with updated blockhash
let mut sign_txs = Measure::start("sign_txs");
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
tx.sign(&[*k], blockhash);
});
sign_txs.stop();
debug!("sign {} txs: {}us", to_fund_txs.len(), sign_txs.as_us());
let mut send_txs = Measure::start("send_txs");
to_fund_txs.iter().for_each(|(_, tx)| {
client.async_send_transaction(tx.clone()).expect("transfer");
});
send_txs.stop();
debug!("send {} txs: {}us", to_fund_txs.len(), send_txs.as_us());
let mut verify_txs = Measure::start("verify_txs");
let mut starting_txs = to_fund_txs.len();
let mut verified_txs = 0;
let mut failed_verify = 0;
// Only loop multiple times for small (quick) transaction batches
for _ in 0..(if starting_txs < 1000 { 3 } else { 1 }) {
let mut timer = Instant::now();
to_fund_txs.retain(|(_, tx)| {
if timer.elapsed() >= Duration::from_secs(5) {
if failed_verify > 0 {
debug!("total txs failed verify: {}", failed_verify);
}
info!(
"Verifying transfers... {} remaining",
starting_txs - verified_txs
);
timer = Instant::now();
}
let verified = verify_funding_transfer(client, &tx, amount);
if verified {
verified_txs += 1;
} else {
failed_verify += 1;
}
!verified
});
if to_fund_txs.is_empty() {
break;
}
debug!("Looping verifications");
info!("Verifying transfers... {} remaining", to_fund_txs.len());
sleep(Duration::from_millis(100));
}
starting_txs -= to_fund_txs.len();
verify_txs.stop();
debug!("verified {} txs: {}us", starting_txs, verify_txs.as_us());
// retry anything that seems to have dropped through cracks
// again since these txs are all or nothing, they're fine to
// retry
tries += 1;
}
info!("transferred");
});
info!("funded: {} left: {}", new_funded.len(), notfunded.len());
info!("funded: {} left: {}", new_funded.len(), not_funded.len());
funded = new_funded;
funded_funds = to_lamports;
}
}
@ -678,14 +712,14 @@ pub fn airdrop_lamports<T: Client>(
client: &T,
faucet_addr: &SocketAddr,
id: &Keypair,
tx_count: u64,
desired_balance: u64,
) -> Result<()> {
let starting_balance = client.get_balance(&id.pubkey()).unwrap_or(0);
metrics_submit_lamport_balance(starting_balance);
info!("starting balance {}", starting_balance);
if starting_balance < tx_count {
let airdrop_amount = tx_count - starting_balance;
if starting_balance < desired_balance {
let airdrop_amount = desired_balance - starting_balance;
info!(
"Airdropping {:?} lamports from {} for {}",
airdrop_amount,
@ -810,17 +844,6 @@ fn compute_and_report_stats(
);
}
// First transfer 2/3 of the lamports to the dest accounts
// then ping-pong 1/3 of the lamports back to the other account
// this leaves 1/3 lamport buffer in each account
fn should_switch_directions(num_lamports_per_account: u64, keypair_chunks: u64, i: u64) -> bool {
if i < keypair_chunks * (2 * num_lamports_per_account) / 3 {
return false;
}
i % (keypair_chunks * num_lamports_per_account / 3) == 0
}
pub fn generate_keypairs(seed_keypair: &Keypair, count: u64) -> (Vec<Keypair>, u64) {
let mut seed = [0u8; 32];
seed.copy_from_slice(&seed_keypair.to_bytes()[..32]);
@ -1004,23 +1027,25 @@ fn fund_move_keys<T: Client>(
info!("done funding keys, took {} ms", funding_time.as_ms());
}
pub fn generate_and_fund_keypairs<T: Client>(
client: &T,
pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
client: Arc<T>,
faucet_addr: Option<SocketAddr>,
funding_key: &Keypair,
keypair_count: usize,
lamports_per_account: u64,
use_move: bool,
) -> Result<(Vec<Keypair>, Option<LibraKeys>, u64)> {
) -> Result<(Vec<Keypair>, Option<LibraKeys>)> {
info!("Creating {} keypairs...", keypair_count);
let (mut keypairs, extra) = generate_keypairs(funding_key, keypair_count as u64);
info!("Get lamports...");
// Sample the first keypair, see if it has lamports, if so then resume.
// This logic is to prevent lamport loss on repeated solana-bench-tps executions
let last_keypair_balance = client
.get_balance(&keypairs[keypair_count - 1].pubkey())
.unwrap_or(0);
// Sample the first keypair, to prevent lamport loss on repeated solana-bench-tps executions
let first_key = keypairs[0].pubkey();
let first_keypair_balance = client.get_balance(&first_key).unwrap_or(0);
// Sample the last keypair, to check if funding was already completed
let last_key = keypairs[keypair_count - 1].pubkey();
let last_keypair_balance = client.get_balance(&last_key).unwrap_or(0);
#[cfg(feature = "move")]
let mut move_keypairs_ret = None;
@ -1028,31 +1053,38 @@ pub fn generate_and_fund_keypairs<T: Client>(
#[cfg(not(feature = "move"))]
let move_keypairs_ret = None;
if lamports_per_account > last_keypair_balance {
let (_blockhash, fee_calculator) = get_recent_blockhash(client);
let account_desired_balance =
lamports_per_account - last_keypair_balance + fee_calculator.max_lamports_per_signature;
let extra_fees = extra * fee_calculator.max_lamports_per_signature;
let mut total = account_desired_balance * (1 + keypairs.len() as u64) + extra_fees;
// Repeated runs will eat up keypair balances from transaction fees. In order to quickly
// start another bench-tps run without re-funding all of the keypairs, check if the
// keypairs still have at least 80% of the expected funds. That should be enough to
// pay for the transaction fees in a new run.
let enough_lamports = 8 * lamports_per_account / 10;
if first_keypair_balance < enough_lamports || last_keypair_balance < enough_lamports {
let (_blockhash, fee_calculator) = get_recent_blockhash(client.as_ref());
let max_fee = fee_calculator.max_lamports_per_signature;
let extra_fees = extra * max_fee;
let total_keypairs = keypairs.len() as u64 + 1; // Add one for funding keypair
let mut total = lamports_per_account * total_keypairs + extra_fees;
if use_move {
total *= 3;
}
info!("Previous key balance: {} max_fee: {} lamports_per_account: {} extra: {} desired_balance: {} total: {}",
last_keypair_balance, fee_calculator.max_lamports_per_signature, lamports_per_account, extra,
account_desired_balance, total
);
let funding_key_balance = client.get_balance(&funding_key.pubkey()).unwrap_or(0);
info!(
"Funding keypair balance: {} max_fee: {} lamports_per_account: {} extra: {} total: {}",
funding_key_balance, max_fee, lamports_per_account, extra, total
);
if client.get_balance(&funding_key.pubkey()).unwrap_or(0) < total {
airdrop_lamports(client, &faucet_addr.unwrap(), funding_key, total)?;
airdrop_lamports(client.as_ref(), &faucet_addr.unwrap(), funding_key, total)?;
}
#[cfg(feature = "move")]
{
if use_move {
let libra_genesis_keypair = create_genesis(&funding_key, client, 10_000_000);
let libra_mint_program_id = upload_mint_script(&funding_key, client);
let libra_pay_program_id = upload_payment_script(&funding_key, client);
let libra_genesis_keypair =
create_genesis(&funding_key, client.as_ref(), 10_000_000);
let libra_mint_program_id = upload_mint_script(&funding_key, client.as_ref());
let libra_pay_program_id = upload_payment_script(&funding_key, client.as_ref());
// Generate another set of keypairs for move accounts.
// Still fund the solana ones which will be used for fees.
@ -1060,7 +1092,7 @@ pub fn generate_and_fund_keypairs<T: Client>(
let mut rnd = GenKeys::new(seed);
let move_keypairs = rnd.gen_n_keypairs(keypair_count as u64);
fund_move_keys(
client,
client.as_ref(),
funding_key,
&move_keypairs,
total / 3,
@ -1085,15 +1117,15 @@ pub fn generate_and_fund_keypairs<T: Client>(
funding_key,
&keypairs,
total,
fee_calculator.max_lamports_per_signature,
extra,
max_fee,
lamports_per_account,
);
}
// 'generate_keypairs' generates extra keys to be able to have size-aligned funding batches for fund_keys.
keypairs.truncate(keypair_count);
Ok((keypairs, move_keypairs_ret, last_keypair_balance))
Ok((keypairs, move_keypairs_ret))
}
#[cfg(test)]
@ -1105,30 +1137,11 @@ mod tests {
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::genesis_config::create_genesis_config;
#[test]
fn test_switch_directions() {
assert_eq!(should_switch_directions(30, 1, 0), false);
assert_eq!(should_switch_directions(30, 1, 1), false);
assert_eq!(should_switch_directions(30, 1, 20), true);
assert_eq!(should_switch_directions(30, 1, 21), false);
assert_eq!(should_switch_directions(30, 1, 30), true);
assert_eq!(should_switch_directions(30, 1, 90), true);
assert_eq!(should_switch_directions(30, 1, 91), false);
assert_eq!(should_switch_directions(30, 2, 0), false);
assert_eq!(should_switch_directions(30, 2, 1), false);
assert_eq!(should_switch_directions(30, 2, 20), false);
assert_eq!(should_switch_directions(30, 2, 40), true);
assert_eq!(should_switch_directions(30, 2, 90), false);
assert_eq!(should_switch_directions(30, 2, 100), true);
assert_eq!(should_switch_directions(30, 2, 101), false);
}
#[test]
fn test_bench_tps_bank_client() {
let (genesis_config, id) = create_genesis_config(10_000);
let bank = Bank::new(&genesis_config);
let clients = vec![BankClient::new(bank)];
let client = Arc::new(BankClient::new(bank));
let mut config = Config::default();
config.id = id;
@ -1136,23 +1149,24 @@ mod tests {
config.duration = Duration::from_secs(5);
let keypair_count = config.tx_count * config.keypair_multiplier;
let (keypairs, _move_keypairs, _keypair_balance) =
generate_and_fund_keypairs(&clients[0], None, &config.id, keypair_count, 20, false)
let (keypairs, _move_keypairs) =
generate_and_fund_keypairs(client.clone(), None, &config.id, keypair_count, 20, false)
.unwrap();
do_bench_tps(clients, config, keypairs, 0, None);
do_bench_tps(client, config, keypairs, None);
}
#[test]
fn test_bench_tps_fund_keys() {
let (genesis_config, id) = create_genesis_config(10_000);
let bank = Bank::new(&genesis_config);
let client = BankClient::new(bank);
let client = Arc::new(BankClient::new(bank));
let keypair_count = 20;
let lamports = 20;
let (keypairs, _move_keypairs, _keypair_balance) =
generate_and_fund_keypairs(&client, None, &id, keypair_count, lamports, false).unwrap();
let (keypairs, _move_keypairs) =
generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports, false)
.unwrap();
for kp in &keypairs {
assert_eq!(
@ -1170,23 +1184,16 @@ mod tests {
let fee_calculator = FeeCalculator::new(11, 0);
genesis_config.fee_calculator = fee_calculator;
let bank = Bank::new(&genesis_config);
let client = BankClient::new(bank);
let client = Arc::new(BankClient::new(bank));
let keypair_count = 20;
let lamports = 20;
let (keypairs, _move_keypairs, _keypair_balance) =
generate_and_fund_keypairs(&client, None, &id, keypair_count, lamports, false).unwrap();
let (keypairs, _move_keypairs) =
generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports, false)
.unwrap();
let max_fee = client
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
.unwrap()
.1
.max_lamports_per_signature;
for kp in &keypairs {
assert_eq!(
client.get_balance(&kp.pubkey()).unwrap(),
lamports + max_fee
);
assert_eq!(client.get_balance(&kp.pubkey()).unwrap(), lamports);
}
}
}

View File

@ -1,10 +1,10 @@
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::SOL_LAMPORTS;
const NUM_LAMPORTS_PER_ACCOUNT_DEFAULT: u64 = solana_sdk::native_token::LAMPORTS_PER_SOL;
/// Holds the configuration for a single run of the benchmark
pub struct Config {

View File

@ -4,15 +4,15 @@ 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};
use std::{collections::HashMap, fs::File, io::prelude::*, path::Path, process::exit, sync::Arc};
/// Number of signatures for all transactions in ~1 week at ~100K TPS
pub const NUM_SIGNATURES_FOR_TXS: u64 = 100_000 * 60 * 60 * 24 * 7;
fn main() {
solana_logger::setup_with_filter("solana=info");
solana_logger::setup_with_default("solana=info");
solana_metrics::set_panic_hook("bench-tps");
let matches = cli::build_args(solana_clap_utils::version!()).get_matches();
@ -82,12 +82,12 @@ fn main() {
);
exit(1);
}
client
Arc::new(client)
} else {
get_client(&nodes)
Arc::new(get_client(&nodes))
};
let (keypairs, move_keypairs, keypair_balance) = if *read_from_client_file && !use_move {
let (keypairs, move_keypairs) = if *read_from_client_file && !use_move {
let path = Path::new(&client_ids_and_stake_file);
let file = File::open(path).unwrap();
@ -117,10 +117,10 @@ fn main() {
// This prevents the amount of storage needed for bench-tps accounts from creeping up
// across multiple runs.
keypairs.sort_by(|x, y| x.pubkey().to_string().cmp(&y.pubkey().to_string()));
(keypairs, None, last_balance)
(keypairs, None)
} else {
generate_and_fund_keypairs(
&client,
client.clone(),
Some(*faucet_addr),
&id,
keypair_count,
@ -133,11 +133,5 @@ fn main() {
})
};
do_bench_tps(
vec![client],
cli_config,
keypairs,
keypair_balance,
move_keypairs,
);
do_bench_tps(client, cli_config, keypairs, move_keypairs);
}

View File

@ -8,8 +8,8 @@ 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 std::sync::mpsc::channel;
use solana_sdk::signature::{Keypair, Signer};
use std::sync::{mpsc::channel, Arc};
use std::time::Duration;
fn test_bench_tps_local_cluster(config: Config) {
@ -36,10 +36,10 @@ fn test_bench_tps_local_cluster(config: Config) {
100_000_000,
);
let client = create_client(
let client = Arc::new(create_client(
(cluster.entry_point_info.rpc, cluster.entry_point_info.tpu),
VALIDATOR_PORT_RANGE,
);
));
let (addr_sender, addr_receiver) = channel();
run_local_faucet(faucet_keypair, addr_sender, None);
@ -48,8 +48,8 @@ fn test_bench_tps_local_cluster(config: Config) {
let lamports_per_account = 100;
let keypair_count = config.tx_count * config.keypair_multiplier;
let (keypairs, move_keypairs, _keypair_balance) = generate_and_fund_keypairs(
&client,
let (keypairs, move_keypairs) = generate_and_fund_keypairs(
client.clone(),
Some(faucet_addr),
&config.id,
keypair_count,
@ -58,7 +58,7 @@ fn test_bench_tps_local_cluster(config: Config) {
)
.unwrap();
let _total = do_bench_tps(vec![client], config, keypairs, 0, move_keypairs);
let _total = do_bench_tps(client, config, keypairs, move_keypairs);
#[cfg(not(debug_assertions))]
assert!(_total > 100);

View File

@ -1,18 +0,0 @@
+------------+
| Bank-Merkle|
+------------+
^ ^
/ \
+-----------------+ +-------------+
| Bank-Diff-Merkle| | Block-Merkle|
+-----------------+ +-------------+
^ ^
/ \
+------+ +--------------------------+
| Hash | | Previous Bank-Diff-Merkle|
+------+ +--------------------------+
^ ^
/ \
+---------------+ +---------------+
| Hash(Account1)| | Hash(Account2)|
+---------------+ +---------------+

View File

@ -1,6 +0,0 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"
make -j"$(nproc)" test

View File

@ -1,4 +0,0 @@
# API Reference
The following sections contain API references material you may find useful when developing applications utilizing a Solana cluster.

View File

@ -1,28 +0,0 @@
# Blockstreamer
Solana supports a node type called an _blockstreamer_. This validator variation is intended for applications that need to observe the data plane without participating in transaction validation or ledger replication.
A blockstreamer runs without a vote signer, and can optionally stream ledger entries out to a Unix domain socket as they are processed. The JSON-RPC service still functions as on any other node.
To run a blockstreamer, include the argument `no-signer` and \(optional\) `blockstream` socket location:
```bash
$ ./multinode-demo/validator-x.sh --no-signer --blockstream <SOCKET>
```
The stream will output a series of JSON objects:
* An Entry event JSON object is sent when each ledger entry is processed, with the following fields:
* `dt`, the system datetime, as RFC3339-formatted string
* `t`, the event type, always "entry"
* `s`, the slot height, as unsigned 64-bit integer
* `h`, the tick height, as unsigned 64-bit integer
* `entry`, the entry, as JSON object
* A Block event JSON object is sent when a block is complete, with the following fields:
* `dt`, the system datetime, as RFC3339-formatted string
* `t`, the event type, always "block"
* `s`, the slot height, as unsigned 64-bit integer
* `h`, the tick height, as unsigned 64-bit integer
* `l`, the slot leader id, as base-58 encoded string
* `hash`, the [blockhash](terminology.md#blockhash), as base-58 encoded string

View File

@ -1,38 +0,0 @@
# Instruction
For the purposes of building a [Transaction](../transaction.md), a more verbose instruction format is used:
* **Instruction:**
* **program\_id:** The pubkey of the on-chain program that executes the
instruction
* **accounts:** An ordered list of accounts that should be passed to
the program processing the instruction, including metadata detailing
if an account is a signer of the transaction and if it is a credit
only account.
* **data:** A byte array that is passed to the program executing the
instruction
A more compact form is actually included in a `Transaction`:
* **CompiledInstruction:**
* **program\_id\_index:** The index of the `program_id` in the
`account_keys` list
* **accounts:** An ordered list of indices into `account_keys`
specifying the accounds that should be passed to the program
processing the instruction.
* **data:** A byte array that is passed to the program executing the
instruction

View File

@ -1,994 +0,0 @@
# JSON RPC API
Solana nodes accept HTTP requests using the [JSON-RPC 2.0](https://www.jsonrpc.org/specification) specification.
To interact with a Solana node inside a JavaScript application, use the [solana-web3.js](https://github.com/solana-labs/solana-web3.js) library, which gives a convenient interface for the RPC methods.
## RPC HTTP Endpoint
**Default port:** 8899 eg. [http://localhost:8899](http://localhost:8899), [http://192.168.1.88:8899](http://192.168.1.88:8899)
## RPC PubSub WebSocket Endpoint
**Default port:** 8900 eg. ws://localhost:8900, [http://192.168.1.88:8900](http://192.168.1.88:8900)
## Methods
* [confirmTransaction](jsonrpc-api.md#confirmtransaction)
* [getAccountInfo](jsonrpc-api.md#getaccountinfo)
* [getBalance](jsonrpc-api.md#getbalance)
* [getBlockCommitment](jsonrpc-api.md#getblockcommitment)
* [getBlockTime](jsonrpc-api.md#getblocktime)
* [getClusterNodes](jsonrpc-api.md#getclusternodes)
* [getConfirmedBlock](jsonrpc-api.md#getconfirmedblock)
* [getConfirmedBlocks](jsonrpc-api.md#getconfirmedblocks)
* [getEpochInfo](jsonrpc-api.md#getepochinfo)
* [getEpochSchedule](jsonrpc-api.md#getepochschedule)
* [getGenesisHash](jsonrpc-api.md#getgenesishash)
* [getLeaderSchedule](jsonrpc-api.md#getleaderschedule)
* [getMinimumBalanceForRentExemption](jsonrpc-api.md#getminimumbalanceforrentexemption)
* [getNumBlocksSinceSignatureConfirmation](jsonrpc-api.md#getnumblockssincesignatureconfirmation)
* [getProgramAccounts](jsonrpc-api.md#getprogramaccounts)
* [getRecentBlockhash](jsonrpc-api.md#getrecentblockhash)
* [getSignatureStatus](jsonrpc-api.md#getsignaturestatus)
* [getSlot](jsonrpc-api.md#getslot)
* [getSlotLeader](jsonrpc-api.md#getslotleader)
* [getSlotsPerSegment](jsonrpc-api.md#getslotspersegment)
* [getStorageTurn](jsonrpc-api.md#getstorageturn)
* [getStorageTurnRate](jsonrpc-api.md#getstorageturnrate)
* [getTransactionCount](jsonrpc-api.md#gettransactioncount)
* [getTotalSupply](jsonrpc-api.md#gettotalsupply)
* [getVersion](jsonrpc-api.md#getversion)
* [getVoteAccounts](jsonrpc-api.md#getvoteaccounts)
* [requestAirdrop](jsonrpc-api.md#requestairdrop)
* [sendTransaction](jsonrpc-api.md#sendtransaction)
* [startSubscriptionChannel](jsonrpc-api.md#startsubscriptionchannel)
* [Subscription Websocket](jsonrpc-api.md#subscription-websocket)
* [accountSubscribe](jsonrpc-api.md#accountsubscribe)
* [accountUnsubscribe](jsonrpc-api.md#accountunsubscribe)
* [programSubscribe](jsonrpc-api.md#programsubscribe)
* [programUnsubscribe](jsonrpc-api.md#programunsubscribe)
* [signatureSubscribe](jsonrpc-api.md#signaturesubscribe)
* [signatureUnsubscribe](jsonrpc-api.md#signatureunsubscribe)
## Request Formatting
To make a JSON-RPC request, send an HTTP POST request with a `Content-Type: application/json` header. The JSON request data should contain 4 fields:
* `jsonrpc`, set to `"2.0"`
* `id`, a unique client-generated identifying integer
* `method`, a string containing the method to be invoked
* `params`, a JSON array of ordered parameter values
Example using curl:
```bash
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' 192.168.1.88:8899
```
The response output will be a JSON object with the following fields:
* `jsonrpc`, matching the request specification
* `id`, matching the request identifier
* `result`, requested data or success confirmation
Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.
## Definitions
* Hash: A SHA-256 hash of a chunk of data.
* Pubkey: The public key of a Ed25519 key-pair.
* Signature: An Ed25519 signature of a chunk of data.
* Transaction: A Solana instruction signed by a client key-pair.
## Configuring State Commitment
Solana nodes choose which bank state to query based on a commitment requirement
set by the client. Clients may specify either:
* `{"commitment":"max"}` - the node will query the most recent bank having reached `MAX_LOCKOUT_HISTORY` confirmations
* `{"commitment":"recent"}` - the node will query its most recent bank state
The commitment parameter should be included as the last element in the `params` array:
```bash
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri",{"commitment":"max"}]}' 192.168.1.88:8899
```
#### Default:
If commitment configuration is not provided, the node will default to `"commitment":"max"`
Only methods that query bank state accept the commitment parameter. They are indicated in the API Reference below.
#### RpcResponse Structure
Many methods that take a commitment parameter return an RpcResponse JSON object comprised of two parts:
* `context` : An RpcResponseContext JSON structure including a `slot` field at which the operation was evaluated.
* `value` : The value returned by the operation itself.
## JSON RPC API Reference
### confirmTransaction
Returns a transaction receipt
#### Parameters:
* `string` - Signature of Transaction to confirm, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `RpcResponse<boolean>` - RpcResponse JSON object with `value` field set to Transaction status, boolean true if Transaction is confirmed
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"confirmTransaction", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":true},"id":1}
```
### getAccountInfo
Returns all information associated with the account of provided Pubkey
#### Parameters:
* `string` - Pubkey of account to query, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
The result value will be an RpcResponse JSON object containing an AccountInfo JSON object.
* `RpcResponse<AccountInfo>`, RpcResponse JSON object with `value` field set to AccountInfo, a JSON object containing:
* `lamports`, number of lamports assigned to this account, as a u64
* `owner`, array of 32 bytes representing the program this account has been assigned to
* `data`, array of bytes representing any data associated with the account
* `executable`, boolean indicating if the account contains a program \(and is strictly read-only\)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0.22.0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]}},"id":1}
```
### getBalance
Returns the balance of the account of provided Pubkey
#### Parameters:
* `string` - Pubkey of account to query, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `RpcResponse<u64>` - RpcResponse JSON object with `value` field set to quantity
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":0},"id":1}
```
### getBlockCommitment
Returns commitment for particular block
#### Parameters:
* `u64` - block, identified by Slot
#### Results:
The result field will be an array with two fields:
* Commitment
* `null` - Unknown block
* `object` - BlockCommitment
* `array` - commitment, array of u64 integers logging the amount of cluster stake in lamports that has voted on the block at each depth from 0 to `MAX_LOCKOUT_HISTORY`
* 'integer' - total active stake, in lamports, of the current epoch
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getBlockCommitment","params":[5]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[{"commitment":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,32]},42],"id":1}
```
### getBlockTime
Returns the estimated production time of a block. Validators report their UTC
time to the ledger on a regular interval. A block's time is calculated as an
offset from the median value of the most recent validator time report.
#### Parameters:
* `u64` - block, identified by Slot
#### Results:
* `null` - block has not yet been produced
* `i64` - estimated production time, as Unix timestamp (seconds since the Unix epoch)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getBlockTime","params":[5]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":1574721591,"id":1}
```
### getClusterNodes
Returns information about all the nodes participating in the cluster
#### Parameters:
None
#### Results:
The result field will be an array of JSON objects, each with the following sub fields:
* `pubkey` - Node public key, as base-58 encoded string
* `gossip` - Gossip network address for the node
* `tpu` - TPU network address for the node
* `rpc` - JSON RPC network address for the node, or `null` if the JSON RPC service is not enabled
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getClusterNodes"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[{"gossip":"10.239.6.48:8001","pubkey":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"}],"id":1}
```
### getConfirmedBlock
Returns identity and transaction information about a confirmed block in the ledger
#### Parameters:
* `integer` - slot, as u64 integer
#### Results:
The result field will be an object with the following fields:
* `blockhash` - the blockhash of this block
* `previousBlockhash` - the blockhash of this block's parent
* `parentSlot` - the slot index of this block's parent
* `transactions` - an array of tuples containing:
* [Transaction](transaction-api.md) object, in JSON format
* Transaction status object, containing:
* `status` - Transaction status:
* `"Ok": null` - Transaction was successful
* `"Err": <ERR>` - Transaction failed with TransactionError [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
* `fee` - fee this transaction was charged, as u64 integer
* `preBalances` - array of u64 account balances from before the transaction was processed
* `postBalances` - array of u64 account balances after the transaction was processed
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430]}' localhost:8899
// Result
{"jsonrpc":"2.0","result":{"blockhash":[165,245,120,183,32,205,89,222,249,114,229,49,250,231,149,122,156,232,181,83,238,194,157,153,7,213,180,54,177,6,25,101],"parentSlot":429,"previousBlockhash":[21,108,181,90,139,241,212,203,45,78,232,29,161,31,159,188,110,82,81,11,250,74,47,140,188,28,23,96,251,164,208,166],"transactions":[[{"message":{"accountKeys":[[5],[219,181,202,40,52,148,34,136,186,59,137,160,250,225,234,17,244,160,88,116,24,176,30,227,68,11,199,38,141,68,131,228],[233,48,179,56,91,40,254,206,53,48,196,176,119,248,158,109,121,77,11,69,108,160,128,27,228,122,146,249,53,184,68,87],[6,167,213,23,25,47,10,175,198,242,101,227,251,119,204,122,218,130,197,41,208,190,59,19,110,45,0,85,32,0,0,0],[6,167,213,23,24,199,116,201,40,86,99,152,105,29,94,182,139,94,184,163,155,75,109,92,115,85,91,33,0,0,0,0],[7,97,72,29,53,116,116,187,124,77,118,36,235,211,189,179,216,53,94,115,209,16,67,252,13,163,83,128,0,0,0,0]],"header":{"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":3,"numRequiredSignatures":2},"instructions":[[1],{"accounts":[[3],1,2,3],"data":[[52],2,0,0,0,1,0,0,0,0,0,0,0,173,1,0,0,0,0,0,0,86,55,9,248,142,238,135,114,103,83,247,124,67,68,163,233,55,41,59,129,64,50,110,221,234,234,27,213,205,193,219,50],"program_id_index":4}],"recentBlockhash":[21,108,181,90,139,241,212,203,45,78,232,29,161,31,159,188,110,82,81,11,250,74,47,140,188,28,23,96,251,164,208,166]},"signatures":[[2],[119,9,95,108,35,95,7,1,69,101,65,45,5,204,61,114,172,88,123,238,32,201,135,229,57,50,13,21,106,216,129,183,238,43,37,101,148,81,56,232,88,136,80,65,46,189,39,106,94,13,238,54,186,48,118,186,0,62,121,122,172,171,66,5],[78,40,77,250,10,93,6,157,48,173,100,40,251,9,7,218,7,184,43,169,76,240,254,34,235,48,41,175,119,126,75,107,106,248,45,161,119,48,174,213,57,69,111,225,245,60,148,73,124,82,53,6,203,126,120,180,111,169,89,64,29,23,237,13]]},{"fee":100000,"status":{"Ok":null},"preBalances":[499998337500,15298080,1,1,1],"postBalances":[499998237500,15298080,1,1,1]}]]},"id":1}
```
### getConfirmedBlocks
Returns a list of confirmed blocks
#### Parameters:
* `integer` - start_slot, as u64 integer
* `integer` - (optional) end_slot, as u64 integer
#### Results:
The result field will be an array of u64 integers listing confirmed blocks
between start_slot and either end_slot, if provided, or latest confirmed block,
inclusive.
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlocks","params":[5, 10]}' localhost:8899
// Result
{"jsonrpc":"2.0","result":[5,6,7,8,9,10],"id":1}
```
### getEpochInfo
Returns information about the current epoch
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
The result field will be an object with the following fields:
* `epoch`, the current epoch
* `slotIndex`, the current slot relative to the start of the current epoch
* `slotsInEpoch`, the number of slots in this epoch
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"epoch":3,"slotIndex":126,"slotsInEpoch":256},"id":1}
```
### getEpochSchedule
Returns epoch schedule information from this cluster's genesis config
#### Parameters:
None
#### Results:
The result field will be an object with the following fields:
* `slots_per_epoch`, the maximum number of slots in each epoch
* `leader_schedule_slot_offset`, the number of slots before beginning of an epoch to calculate a leader schedule for that epoch
* `warmup`, whether epochs start short and grow
* `first_normal_epoch`, first normal-length epoch, log2(slots_per_epoch) - log2(MINIMUM_SLOTS_PER_EPOCH)
* `first_normal_slot`, MINIMUM_SLOTS_PER_EPOCH * (2.pow(first_normal_epoch) - 1)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochSchedule"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"first_normal_epoch":8,"first_normal_slot":8160,"leader_schedule_slot_offset":8192,"slots_per_epoch":8192,"warmup":true},"id":1}
```
### getGenesisHash
Returns the genesis hash
#### Parameters:
None
#### Results:
* `string` - a Hash as base-58 encoded string
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getGenesisHash"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
```
### getLeaderSchedule
Returns the leader schedule for an epoch
#### Parameters:
* `slot` - (optional) Fetch the leader schedule for the epoch that corresponds to the provided slot. If unspecified, the leader schedule for the current epoch is fetched
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
The result field will be a dictionary of leader public keys \(as base-58 encoded
strings\) and their corresponding leader slot indices as values (indices are to
the first slot in the requested epoch)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLeaderSchedule"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63]},"id":1}
```
### getMinimumBalanceForRentExemption
Returns minimum balance required to make account rent exempt.
#### Parameters:
* `u64` - account data length
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `u64` - minimum lamports required in account
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getMinimumBalanceForRentExemption", "params":[50]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":500,"id":1}
```
### getNumBlocksSinceSignatureConfirmation
Returns the current number of blocks since signature has been confirmed.
#### Parameters:
* `string` - Signature of Transaction to confirm, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `u64` - count
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getNumBlocksSinceSignatureConfirmation", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":8,"id":1}
```
### getProgramAccounts
Returns all accounts owned by the provided program Pubkey
#### Parameters:
* `string` - Pubkey of program, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
The result field will be an array of arrays. Each sub array will contain:
* `string` - the account Pubkey as base-58 encoded string and a JSON object, with the following sub fields:
* `lamports`, number of lamports assigned to this account, as a u64
* `owner`, array of 32 bytes representing the program this account has been assigned to
* `data`, array of bytes representing any data associated with the account
* `executable`, boolean indicating if the account contains a program \(and is strictly read-only\)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getProgramAccounts", "params":["8nQwAgzN2yyUzrukXsCa3JELBYqDQrqJ3UyHiWazWxHR"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[["BqGKYtAKu69ZdWEBtZHh4xgJY1BYa2YBiBReQE3pe383", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":1,"data":[]], ["4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":10,"data":[]]]},"id":1}
```
### getRecentBlockhash
Returns a recent block hash from the ledger, and a fee schedule that can be used to compute the cost of submitting a transaction using it.
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
An RpcResponse containing an array consisting of a string blockhash and FeeCalculator JSON object.
* `RpcResponse<array>` - RpcResponse JSON object with `value` field set to an array including:
* `string` - a Hash as base-58 encoded string
* `FeeCalculator object` - the fee schedule for this block hash
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC",{"lamportsPerSignature": 0}]},"id":1}
```
### getSignatureStatus
Returns the status of a given signature. This method is similar to [confirmTransaction](jsonrpc-api.md#confirmtransaction) but provides more resolution for error events.
#### Parameters:
* `string` - Signature of Transaction to confirm, as base-58 encoded string
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `null` - Unknown transaction
* `object` - Transaction status:
* `"Ok": null` - Transaction was successful
* `"Err": <ERR>` - Transaction failed with TransactionError [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"SignatureNotFound","id":1}
```
### getSlot
Returns the current slot the node is processing
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `u64` - Current slot
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlot"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"1234","id":1}
```
### getSlotLeader
Returns the current slot leader
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `string` - Node Id as base-58 encoded string
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotLeader"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"ENvAW7JScgYq6o4zKZwewtkzzJgDzuJAFxYasvmEQdpS","id":1}
```
### getSlotsPerSegment
Returns the current storage segment size in terms of slots
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `u64` - Number of slots in a storage segment
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotsPerSegment"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"1024","id":1}
```
### getStorageTurn
Returns the current storage turn's blockhash and slot
#### Parameters:
None
#### Results:
An array consisting of
* `string` - a Hash as base-58 encoded string indicating the blockhash of the turn slot
* `u64` - the current storage turn slot
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurn"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC", "2048"],"id":1}
```
### getStorageTurnRate
Returns the current storage turn rate in terms of slots per turn
#### Parameters:
None
#### Results:
* `u64` - Number of slots in storage turn
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurnRate"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"1024","id":1}
```
### getTransactionCount
Returns the current Transaction count from the ledger
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `u64` - count
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":268,"id":1}
```
### getTotalSupply
Returns the current total supply in Lamports
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
* `u64` - Total supply
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTotalSupply"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":10126,"id":1}
```
### getVersion
Returns the current solana versions running on the node
#### Parameters:
None
#### Results:
The result field will be a JSON object with the following sub fields:
* `solana-core`, software version of solana-core
#### Example:
```bash
// 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.17.2"},"id":1}
```
### getVoteAccounts
Returns the account info and associated stake for all the voting accounts in the current bank.
#### Parameters:
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
#### Results:
The result field will be a JSON object of `current` and `delinquent` accounts, each containing an array of JSON objects with the following sub fields:
* `votePubkey` - Vote account public key, as base-58 encoded string
* `nodePubkey` - Node public key, as base-58 encoded string
* `activatedStake` - the stake, in lamports, delegated to this vote account and active in this epoch
* `epochVoteAccount` - bool, whether the vote account is staked for this epoch
* `commission`, percentage (0-100) of rewards payout owed to the vote account
* `lastVote` - Most recent slot voted on by this vote account
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVoteAccounts"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"current":[{"commission":0,"epochVoteAccount":true,"nodePubkey":"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD","lastVote":147,"activatedStake":42,"votePubkey":"3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"}],"delinquent":[{"commission":127,"epochVoteAccount":false,"nodePubkey":"6ZPxeQaDo4bkZLRsdNrCzchNQr5LN9QMc9sipXv9Kw8f","lastVote":0,"activatedStake":0,"votePubkey":"CmgCk4aMS7KW1SHX3s9K5tBJ6Yng2LBaC8MFov4wx9sm"}]},"id":1}
```
### requestAirdrop
Requests an airdrop of lamports to a Pubkey
#### Parameters:
* `string` - Pubkey of account to receive lamports, as base-58 encoded string
* `integer` - lamports, as a u64
* `object` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) (used for retrieving blockhash and verifying airdrop success)
#### Results:
* `string` - Transaction Signature of airdrop, as base-58 encoded string
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"requestAirdrop", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri", 50]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW","id":1}
```
### sendTransaction
Creates new transaction
#### Parameters:
* `array` - array of octets containing a fully-signed Transaction
#### Results:
* `string` - Transaction Signature, as base-58 encoded string
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"sendTransaction", "params":[[61, 98, 55, 49, 15, 187, 41, 215, 176, 49, 234, 229, 228, 77, 129, 221, 239, 88, 145, 227, 81, 158, 223, 123, 14, 229, 235, 247, 191, 115, 199, 71, 121, 17, 32, 67, 63, 209, 239, 160, 161, 2, 94, 105, 48, 159, 235, 235, 93, 98, 172, 97, 63, 197, 160, 164, 192, 20, 92, 111, 57, 145, 251, 6, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 13, 39, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 11, 12, 106, 49, 74, 226, 201, 16, 161, 192, 28, 84, 124, 97, 190, 201, 171, 186, 6, 18, 70, 142, 89, 185, 176, 154, 115, 61, 26, 163, 77, 1, 88, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b","id":1}
```
### Subscription Websocket
After connect to the RPC PubSub websocket at `ws://<ADDRESS>/`:
* Submit subscription requests to the websocket using the methods below
* Multiple subscriptions may be active at once
* All subscriptions take an optional `confirmations` parameter, which defines
how many confirmed blocks the node should wait before sending a notification.
The greater the number, the more likely the notification is to represent
consensus across the cluster, and the less likely it is to be affected by
forking or rollbacks. If unspecified, the default value is 0; the node will
send a notification as soon as it witnesses the event. The maximum
`confirmations` wait length is the cluster's `MAX_LOCKOUT_HISTORY`, which
represents the economic finality of the chain.
### accountSubscribe
Subscribe to an account to receive notifications when the lamports or data for a given account public key changes
#### Parameters:
* `string` - account Pubkey, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` \(greater integers rounded down\)
#### Results:
* `integer` - Subscription id \(needed to unsubscribe\)
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
#### Notification Format:
```bash
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0.22.0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
```
### accountUnsubscribe
Unsubscribe from account change notifications
#### Parameters:
* `integer` - id of account Subscription to cancel
#### Results:
* `bool` - unsubscribe success message
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"accountUnsubscribe", "params":[0]}
// Result
{"jsonrpc": "2.0","result": true,"id": 1}
```
### programSubscribe
Subscribe to a program to receive notifications when the lamports or data for a given account owned by the program changes
#### Parameters:
* `string` - program\_id Pubkey, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` \(greater integers rounded down\)
#### Results:
* `integer` - Subscription id \(needed to unsubscribe\)
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV"]}
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
#### Notification Format:
* `string` - account Pubkey, as base-58 encoded string
* `object` - account info JSON object \(see [getAccountInfo](jsonrpc-api.md#getaccountinfo) for field details\)
```bash
{"jsonrpc":"2.0","method":"programNotification","params":{{"result":["8Rshv2oMkPu5E4opXTRyuyBeZBqQ4S477VG26wUTFxUM",{"executable":false,"lamports":1,"owner":[129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"data":[1,1,1,0,0,0,0,0,0,0.22.0,0,0,0,0,0,0,50,48,49,56,45,49,50,45,50,52,84,50,51,58,53,57,58,48,48,90,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,55,89,0,0,0,0,50,0,0,0,0,0,0,0,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,45,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}],"subscription":0}}
```
### programUnsubscribe
Unsubscribe from program-owned account change notifications
#### Parameters:
* `integer` - id of account Subscription to cancel
#### Results:
* `bool` - unsubscribe success message
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"programUnsubscribe", "params":[0]}
// Result
{"jsonrpc": "2.0","result": true,"id": 1}
```
### signatureSubscribe
Subscribe to a transaction signature to receive notification when the transaction is confirmed On `signatureNotification`, the subscription is automatically cancelled
#### Parameters:
* `string` - Transaction Signature, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` \(greater integers rounded down\)
#### Results:
* `integer` - subscription id \(needed to unsubscribe\)
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
#### Notification Format:
```bash
{"jsonrpc": "2.0","method": "signatureNotification", "params": {"result": "Confirmed","subscription":0}}
```
### signatureUnsubscribe
Unsubscribe from signature confirmation notification
#### Parameters:
* `integer` - subscription id to cancel
#### Results:
* `bool` - unsubscribe success message
#### Example:
```bash
// Request
{"jsonrpc":"2.0", "id":1, "method":"signatureUnsubscribe", "params":[0]}
// Result
{"jsonrpc": "2.0","result": true,"id": 1}
```

View File

@ -1,62 +0,0 @@
# Transaction
## Components of a `Transaction`
* **Transaction:**
* **message:** Defines the transaction
* **header:** Details the account types of and signatures required by
the transaction
* **num\_required\_signatures:** The total number of signatures
required to make the transaction valid.
* **num\_credit\_only\_signed\_accounts:** The last
`num_readonly_signed_accounts` signatures refer to signing
credit only accounts. Credit only accounts can be used concurrently
by multiple parallel transactions, but their balance may only be
increased, and their account data is read-only.
* **num\_credit\_only\_unsigned\_accounts:** The last
`num_readonly_unsigned_accounts` public keys in `account_keys` refer
to non-signing credit only accounts
* **account\_keys:** List of public keys used by the transaction, including
by the instructions and for signatures. The first
`num_required_signatures` public keys must sign the transaction.
* **recent\_blockhash:** The ID of a recent ledger entry. Validators will
reject transactions with a `recent_blockhash` that is too old.
* **instructions:** A list of [instructions](https://github.com/solana-labs/solana/tree/aacead62c0eb052068172eba6b53fc85874d6d54/book/src/instruction.md) that are
run sequentially and committed in one atomic transaction if all
succeed.
* **signatures:** A list of signatures applied to the transaction. The
list is always of length `num_required_signatures`, and the signature
at index `i` corresponds to the public key at index `i` in `account_keys`.
The list is initialized with empty signatures \(i.e. zeros\), and
populated as signatures are added.
## Transaction Signing
A `Transaction` is signed by using an ed25519 keypair to sign the serialization of the `message`. The resulting signature is placed at the index of `signatures` matching the index of the keypair's public key in `account_keys`.
## Transaction Serialization
`Transaction`s \(and their `message`s\) are serialized and deserialized using the [bincode](https://crates.io/crates/bincode) crate with a non-standard vector serialization that uses only one byte for the length if it can be encoded in 7 bits, 2 bytes if it fits in 14 bits, or 3 bytes if it requires 15 or 16 bits. The vector serialization is defined by Solana's [short-vec](https://github.com/solana-labs/solana/blob/master/sdk/src/short_vec.rs).

View File

@ -1,7 +0,0 @@
# Testnet Participation
Participate in our testnet:
* [Running a Validator](../running-validator/)
* [Running an Archiver](../running-archiver.md)

View File

@ -1,90 +0,0 @@
# Blocktree
After a block reaches finality, all blocks from that one on down to the genesis block form a linear chain with the familiar name blockchain. Until that point, however, the validator must maintain all potentially valid chains, called _forks_. The process by which forks naturally form as a result of leader rotation is described in [fork generation](../cluster/fork-generation.md). The _blocktree_ data structure described here is how a validator copes with those forks until blocks are finalized.
The blocktree allows a validator to record every shred it observes on the network, in any order, as long as the shred is signed by the expected leader for a given slot.
Shreds are moved to a fork-able key space the tuple of `leader slot` + `shred index` \(within the slot\). This permits the skip-list structure of the Solana protocol to be stored in its entirety, without a-priori choosing which fork to follow, which Entries to persist or when to persist them.
Repair requests for recent shreds are served out of RAM or recent files and out of deeper storage for less recent shreds, as implemented by the store backing Blocktree.
## Functionalities of Blocktree
1. Persistence: the Blocktree lives in the front of the nodes verification
pipeline, right behind network receive and signature verification. If the
shred received is consistent with the leader schedule \(i.e. was signed by the
leader for the indicated slot\), it is immediately stored.
2. Repair: repair is the same as window repair above, but able to serve any
shred that's been received. Blocktree stores shreds with signatures,
preserving the chain of origination.
3. Forks: Blocktree supports random access of shreds, so can support a
validator's need to rollback and replay from a Bank checkpoint.
4. Restart: with proper pruning/culling, the Blocktree can be replayed by
ordered enumeration of entries from slot 0. The logic of the replay stage
\(i.e. dealing with forks\) will have to be used for the most recent entries in
the Blocktree.
## Blocktree Design
1. Entries in the Blocktree are stored as key-value pairs, where the key is the concatenated slot index and shred index for an entry, and the value is the entry data. Note shred indexes are zero-based for each slot \(i.e. they're slot-relative\).
2. The Blocktree maintains metadata for each slot, in the `SlotMeta` struct containing:
* `slot_index` - The index of this slot
* `num_blocks` - The number of blocks in the slot \(used for chaining to a previous slot\)
* `consumed` - The highest shred index `n`, such that for all `m < n`, there exists a shred in this slot with shred index equal to `n` \(i.e. the highest consecutive shred index\).
* `received` - The highest received shred index for the slot
* `next_slots` - A list of future slots this slot could chain to. Used when rebuilding
the ledger to find possible fork points.
* `last_index` - The index of the shred that is flagged as the last shred for this slot. This flag on a shred will be set by the leader for a slot when they are transmitting the last shred for a slot.
* `is_rooted` - True iff every block from 0...slot forms a full sequence without any holes. We can derive is\_rooted for each slot with the following rules. Let slot\(n\) be the slot with index `n`, and slot\(n\).is\_full\(\) is true if the slot with index `n` has all the ticks expected for that slot. Let is\_rooted\(n\) be the statement that "the slot\(n\).is\_rooted is true". Then:
is\_rooted\(0\) is\_rooted\(n+1\) iff \(is\_rooted\(n\) and slot\(n\).is\_full\(\)
3. Chaining - When a shred for a new slot `x` arrives, we check the number of blocks \(`num_blocks`\) for that new slot \(this information is encoded in the shred\). We then know that this new slot chains to slot `x - num_blocks`.
4. Subscriptions - The Blocktree records a set of slots that have been "subscribed" to. This means entries that chain to these slots will be sent on the Blocktree channel for consumption by the ReplayStage. See the `Blocktree APIs` for details.
5. Update notifications - The Blocktree notifies listeners when slot\(n\).is\_rooted is flipped from false to true for any `n`.
## Blocktree APIs
The Blocktree offers a subscription based API that ReplayStage uses to ask for entries it's interested in. The entries will be sent on a channel exposed by the Blocktree. These subscription API's are as follows: 1. `fn get_slots_since(slot_indexes: &[u64]) -> Vec<SlotMeta>`: Returns new slots connecting to any element of the list `slot_indexes`.
1. `fn get_slot_entries(slot_index: u64, entry_start_index: usize, max_entries: Option<u64>) -> Vec<Entry>`: Returns the entry vector for the slot starting with `entry_start_index`, capping the result at `max` if `max_entries == Some(max)`, otherwise, no upper limit on the length of the return vector is imposed.
Note: Cumulatively, this means that the replay stage will now have to know when a slot is finished, and subscribe to the next slot it's interested in to get the next set of entries. Previously, the burden of chaining slots fell on the Blocktree.
## Interfacing with Bank
The bank exposes to replay stage:
1. `prev_hash`: which PoH chain it's working on as indicated by the hash of the last
entry it processed
2. `tick_height`: the ticks in the PoH chain currently being verified by this
bank
3. `votes`: a stack of records that contain: 1. `prev_hashes`: what anything after this vote must chain to in PoH 2. `tick_height`: the tick height at which this vote was cast 3. `lockout period`: how long a chain must be observed to be in the ledger to
be able to be chained below this vote
Replay stage uses Blocktree APIs to find the longest chain of entries it can hang off a previous vote. If that chain of entries does not hang off the latest vote, the replay stage rolls back the bank to that vote and replays the chain from there.
## Pruning Blocktree
Once Blocktree entries are old enough, representing all the possible forks becomes less useful, perhaps even problematic for replay upon restart. Once a validator's votes have reached max lockout, however, any Blocktree contents that are not on the PoH chain for that vote for can be pruned, expunged.
Archiver nodes will be responsible for storing really old ledger contents, and validators need only persist their bank periodically.

View File

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

View File

@ -1,12 +0,0 @@
# Replication-validation Transaction Fees
**Subject to change.**
As previously mentioned, validator-clients will also be responsible for validating PoReps submitted into the PoH stream by archiver-clients. In this case, validators are providing compute \(CPU/GPU\) and light storage resources to confirm that these replication proofs could only be generated by a client that is storing the referenced PoH leger block.
While replication-clients are incentivized and rewarded through protocol-based rewards schedule \(see [Replication-client Economics](../ed_replication_client_economics/)\), validator-clients will be incentivized to include and validate PoReps in PoH through collection of transaction fees associated with the submitted PoReps and distribution of protocol rewards proportional to the validated PoReps. As will be described in detail in the Section 3.1, replication-client rewards are protocol-based and designed to reward based on a global data redundancy factor. I.e. the protocol will incentivize replication-client participation through rewards based on a target ledger redundancy \(e.g. 10x data redundancy\).
The validation of PoReps by validation-clients is computationally more expensive than state-validation \(detail in the [Economic Sustainability](../ed_economic_sustainability.md) chapter\), thus the transaction fees are expected to be proportionally higher.
There are various attack vectors available for colluding validation and replication clients, also described in detail below in [Economic Sustainability](https://github.com/solana-labs/solana/tree/aacead62c0eb052068172eba6b53fc85874d6d54/book/src/ed_economic_sustainability/README.md). To protect against various collusion attack vectors, for a given epoch, validator rewards are distributed across participating validation-clients in proportion to the number of validated PoReps in the epoch less the number of PoReps that mismatch the archivers challenge. The PoRep challenge game is described in [Ledger Replication](https://github.com/solana-labs/solana/blob/master/book/src/ledger-replication.md#the-porep-game). This design rewards validators proportional to the number of PoReps they process and validate, while providing negative pressure for validation-clients to submit lazy or malicious invalid votes on submitted PoReps \(note that it is computationally prohibitive to determine whether a validator-client has marked a valid PoRep as invalid\).

View File

@ -1,49 +0,0 @@
# Repair Service
## Repair Service
The RepairService is in charge of retrieving missing shreds that failed to be delivered by primary communication protocols like Avalanche. It is in charge of managing the protocols described below in the `Repair Protocols` section below.
## Challenges:
1\) Validators can fail to receive particular shreds due to network failures
2\) Consider a scenario where blocktree contains the set of slots {1, 3, 5}. Then Blocktree receives shreds for some slot 7, where for each of the shreds b, b.parent == 6, so then the parent-child relation 6 -&gt; 7 is stored in blocktree. However, there is no way to chain these slots to any of the existing banks in Blocktree, and thus the `Shred Repair` protocol will not repair these slots. If these slots happen to be part of the main chain, this will halt replay progress on this node.
3\) Validators that find themselves behind the cluster by an entire epoch struggle/fail to catch up because they do not have a leader schedule for future epochs. If nodes were to blindly accept repair shreds in these future epochs, this exposes nodes to spam.
## Repair Protocols
The repair protocol makes best attempts to progress the forking structure of Blocktree.
The different protocol strategies to address the above challenges:
1. Shred Repair \(Addresses Challenge \#1\): This is the most basic repair protocol, with the purpose of detecting and filling "holes" in the ledger. Blocktree tracks the latest root slot. RepairService will then periodically iterate every fork in blocktree starting from the root slot, sending repair requests to validators for any missing shreds. It will send at most some `N` repair reqeusts per iteration.
Note: Validators will only accept shreds within the current verifiable epoch \(epoch the validator has a leader schedule for\).
2. Preemptive Slot Repair \(Addresses Challenge \#2\): The goal of this protocol is to discover the chaining relationship of "orphan" slots that do not currently chain to any known fork.
* Blocktree will track the set of "orphan" slots in a separate column family.
* RepairService will periodically make `RequestOrphan` requests for each of the orphans in blocktree.
`RequestOrphan(orphan)` request - `orphan` is the orphan slot that the requestor wants to know the parents of `RequestOrphan(orphan)` response - The highest shreds for each of the first `N` parents of the requested `orphan`
On receiving the responses `p`, where `p` is some shred in a parent slot, validators will:
* Insert an empty `SlotMeta` in blocktree for `p.slot` if it doesn't already exist.
* If `p.slot` does exist, update the parent of `p` based on `parents`
Note: that once these empty slots are added to blocktree, the `Shred Repair` protocol should attempt to fill those slots.
Note: Validators will only accept responses containing shreds within the current verifiable epoch \(epoch the validator has a leader schedule for\).
3. Repairmen \(Addresses Challenge \#3\): This part of the repair protocol is the primary mechanism by which new nodes joining the cluster catch up after loading a snapshot. This protocol works in a "forward" fashion, so validators can verify every shred that they receive against a known leader schedule.
Each validator advertises in gossip:
* Current root
* The set of all completed slots in the confirmed epochs \(an epoch that was calculated based on a bank &lt;= current root\) past the current root
Observers of this gossip message with higher epochs \(repairmen\) send shreds to catch the lagging node up with the rest of the cluster. The repairmen are responsible for sending the slots within the epochs that are confrimed by the advertised `root` in gossip. The repairmen divide the responsibility of sending each of the missing slots in these epochs based on a random seed \(simple shred.index iteration by N, seeded with the repairman's node\_pubkey\). Ideally, each repairman in an N node cluster \(N nodes whose epochs are higher than that of the repairee\) sends 1/N of the missing shreds. Both data and coding shreds for missing slots are sent. Repairmen do not send shreds again to the same validator until they see the message in gossip updated, at which point they perform another iteration of this protocol.
Gossip messages are updated every time a validator receives a complete slot within the epoch. Completed slots are detected by blocktree and sent over a channel to RepairService. It is important to note that we know that by the time a slot X is complete, the epoch schedule must exist for the epoch that contains slot X because WindowService will reject shreds for unconfirmed epochs. When a newly completed slot is detected, we also update the current root if it has changed since the last update. The root is made available to RepairService through Blocktree, which holds the latest root.

View File

@ -1,109 +0,0 @@
# Simple Payment and State Verification
It is often useful to allow low resourced clients to participate in a Solana cluster. Be this participation economic or contract execution, verification that a client's activity has been accepted by the network is typically expensive. This proposal lays out a mechanism for such clients to confirm that their actions have been committed to the ledger state with minimal resource expenditure and third-party trust.
## A Naive Approach
Validators store the signatures of recently confirmed transactions for a short period of time to ensure that they are not processed more than once. Validators provide a JSON RPC endpoint, which clients can use to query the cluster if a transaction has been recently processed. Validators also provide a PubSub notification, whereby a client registers to be notified when a given signature is observed by the validator. While these two mechanisms allow a client to verify a payment, they are not a proof and rely on completely trusting a validator.
We will describe a way to minimize this trust using Merkle Proofs to anchor the validator's response in the ledger, allowing the client to confirm on their own that a sufficient number of their preferred validators have confirmed a transaction. Requiring multiple validator attestations further reduces trust in the validator, as it increases both the technical and economic difficulty of compromising several other network participants.
## Light Clients
A 'light client' is a cluster participant that does not itself run a validator. This light client would provide a level of security greater than trusting a remote validator, without requiring the light client to spend a lot of resources verifying the ledger.
Rather than providing transaction signatures directly to a light client, the validator instead generates a Merkle Proof from the transaction of interest to the root of a Merkle Tree of all transactions in the including block. This Merkle Root is stored in a ledger entry which is voted on by validators, providing it consensus legitimacy. The additional level of security for a light client depends on an initial canonical set of validators the light client considers to be the stakeholders of the cluster. As that set is changed, the client can update its internal set of known validators with [receipts](simple-payment-and-state-verification.md#receipts). This may become challenging with a large number of delegated stakes.
Validators themselves may want to use light client APIs for performance reasons. For example, during the initial launch of a validator, the validator may use a cluster provided checkpoint of the state and verify it with a receipt.
## Receipts
A receipt is a minimal proof that; a transaction has been included in a block, that the block has been voted on by the client's preferred set of validators and that the votes have reached the desired confirmation depth.
The receipts for both state and payments start with a Merkle Path from the value into a Bank-Merkle that has been voted on and included in the ledger. A chain of PoH Entries containing subsequent validator votes, deriving from the Bank-Merkle, is the confirmation proof.
Clients can examine this ledger data and compute the finality using Solana's fork selection rules.
### Payment Merkle Path
A payment receipt is a data structure that contains a Merkle Path from a transaction to the required set of validator votes.
An Entry-Merkle is a Merkle Root including all transactions in the entry, sorted by signature.
![Block Merkle Diagram](../.gitbook/assets/spv-block-merkle.svg)
A Block-Merkle is a Merkle root of all the Entry-Merkles sequenced in the block. Transaction status is necessary for the receipt because the state receipt is constructed for the block. Two transactions over the same state can appear in the block, and therefore, there is no way to infer from just the state whether a transaction that is committed to the ledger has succeeded or failed in modifying the intended state. It may not be necessary to encode the full status code, but a single status bit to indicate the transaction's success.
### State Merkle Path
A state receipt provides a confirmation that a specific state is committed at the end of the block. Inter-block state transitions do not generate a receipt.
For example:
* A sends 5 Lamports to B
* B spends 5 Lamports
* C sends 5 Lamports to A
At the end of the block, A and B are in the exact same starting state, and any state receipt would point to the same value for A or B.
The Bank-Merkle is computed from the Merkle Tree of the new state changes, along with the Previous Bank-Merkle, and the Block-Merkle.
![Bank Merkle Diagram](../.gitbook/assets/spv-bank-merkle.svg)
A state receipt contains only the state changes occurring in the block. A direct Merkle Path to the current Bank-Merkle guarantees the state value at that bank hash, but it cannot be used to generate a “current” receipt to the latest state if the state modification occurred in some previous block. There is no guarantee that the path provided by the validator is the latest one available out of all the previous Bank-Merkles.
Clients that want to query the chain for a receipt of the "latest" state would need to create a transaction that would update the Merkle Path for that account, such as a credit of 0 Lamports.
### Validator Votes
Leaders should coalesce the validator votes by stake weight into a single entry. This will reduce the number of entries necessary to create a receipt.
### Chain of Entries
A receipt has a PoH link from the payment or state Merkle Path root to a list of consecutive validation votes.
It contains the following:
* State -&gt; Bank-Merkle
or
* Transaction -&gt; Entry-Merkle -&gt; Block-Merkle -&gt; Bank-Merkle
And a vector of PoH entries:
* Validator vote entries
* Ticks
* Light entries
```text
/// This Entry definition skips over the transactions and only contains the
/// hash of the transactions used to modify PoH.
LightEntry {
/// The number of hashes since the previous Entry ID.
pub num_hashes: u64,
/// The SHA-256 hash `num_hashes` after the previous Entry ID.
hash: Hash,
/// The Merkle Root of the transactions encoded into the Entry.
entry_hash: Hash,
}
```
The light entries are reconstructed from Entries and simply show the entry Merkle Root that was mixed in to the PoH hash, instead of the full transaction set.
Clients do not need the starting vote state. The [fork selection](https://github.com/solana-labs/solana/tree/aacead62c0eb052068172eba6b53fc85874d6d54/book/src/book/src/fork-selection.md) algorithm is defined such that only votes that appear after the transaction provide finality for the transaction, and finality is independent of the starting state.
### Verification
A light client that is aware of the supermajority set validators can verify a receipt by following the Merkle Path to the PoH chain. The Bank-Merkle is the Merkle Root and will appear in votes included in an Entry. The light client can simulate [fork selection](https://github.com/solana-labs/solana/tree/aacead62c0eb052068172eba6b53fc85874d6d54/book/src/book/src/fork-selection.md) for the consecutive votes and verify that the receipt is confirmed at the desired lockout threshold.
### Synthetic State
Synthetic state should be computed into the Bank-Merkle along with the bank generated state.
For example:
* Epoch validator accounts and their stakes and weights.
* Computed fee rates
These values should have an entry in the Bank-Merkle. They should live under known accounts, and therefore have an exact address in the Merkle Path.

View File

@ -1,35 +0,0 @@
# Validator
## History
When we first started Solana, the goal was to de-risk our TPS claims. We knew that between optimistic concurrency control and sufficiently long leader slots, that PoS consensus was not the biggest risk to TPS. It was GPU-based signature verification, software pipelining and concurrent banking. Thus, the TPU was born. After topping 100k TPS, we split the team into one group working toward 710k TPS and another to flesh out the validator pipeline. Hence, the TVU was born. The current architecture is a consequence of incremental development with that ordering and project priorities. It is not a reflection of what we ever believed was the most technically elegant cross-section of those technologies. In the context of leader rotation, the strong distinction between leading and validating is blurred.
## Difference between validating and leading
The fundamental difference between the pipelines is when the PoH is present. In a leader, we process transactions, removing bad ones, and then tag the result with a PoH hash. In the validator, we verify that hash, peel it off, and process the transactions in exactly the same way. The only difference is that if a validator sees a bad transaction, it can't simply remove it like the leader does, because that would cause the PoH hash to change. Instead, it rejects the whole block. The other difference between the pipelines is what happens _after_ banking. The leader broadcasts entries to downstream validators whereas the validator will have already done that in RetransmitStage, which is a confirmation time optimization. The validation pipeline, on the other hand, has one last step. Any time it finishes processing a block, it needs to weigh any forks it's observing, possibly cast a vote, and if so, reset its PoH hash to the block hash it just voted on.
## Proposed Design
We unwrap the many abstraction layers and build a single pipeline that can toggle leader mode on whenever the validator's ID shows up in the leader schedule.
![Validator block diagram](../.gitbook/assets/validator-proposal.svg)
## Notable changes
* No threads are shut down to switch out of leader mode. Instead, FetchStage
should forward transactions to the next leader.
* Hoist FetchStage and BroadcastStage out of TPU
* Blocktree renamed to Blockstore
* BankForks renamed to Banktree
* TPU moves to new socket-free crate called solana-tpu.
* TPU's BankingStage absorbs ReplayStage
* TVU goes away
* New RepairStage absorbs Shred Fetch Stage and repair requests
* JSON RPC Service is optional - used for debugging. It should instead be part
of a separate `solana-blockstreamer` executable.
* New MulticastStage absorbs retransmit part of RetransmitStage
* MulticastStage downstream of Blockstore

View File

@ -1,17 +0,0 @@
# Anatomy of a Transaction
Transactions encode lists of instructions that are executed sequentially, and only committed if all the instructions complete successfully. All account updates are reverted upon the failure of a transaction. Each transaction details the accounts used, including which must sign and which are read only, a recent blockhash, the instructions, and any signatures.
## Accounts and Signatures
Each transaction explicitly lists all account public keys referenced by the transaction's instructions. A subset of those public keys are each accompanied by a transaction signature. Those signatures signal on-chain programs that the account holder has authorized the transaction. Typically, the program uses the authorization to permit debiting the account or modifying its data.
The transaction also marks some accounts as _read-only accounts_. The runtime permits read-only accounts to be read concurrently. If a program attempts to modify a read-only account, the transaction is rejected by the runtime.
## Recent Blockhash
A Transaction includes a recent blockhash to prevent duplication and to give transactions lifetimes. Any transaction that is completely identical to a previous one is rejected, so adding a newer blockhash allows multiple transactions to repeat the exact same action. Transactions also have lifetimes that are defined by the blockhash, as any transaction whose blockhash is too old will be rejected.
## Instructions
Each instruction specifies a single program account \(which must be marked executable\), a subset of the transaction's accounts that should be passed to the program, and a data byte array instruction that is passed to the program. The program interprets the data array and operates on the accounts specified by the instructions. The program can return successfully, or with an error code. An error return causes the entire transaction to fail immediately.

24
chacha-cuda/Cargo.toml Normal file
View File

@ -0,0 +1,24 @@
[package]
name = "solana-chacha-cuda"
version = "1.0.2"
description = "Solana Chacha Cuda APIs"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[dependencies]
log = "0.4.8"
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.2" }
solana-chacha = { path = "../chacha", version = "1.0.2" }
solana-ledger = { path = "../ledger", version = "1.0.2" }
solana-logger = { path = "../logger", version = "1.0.2" }
solana-perf = { path = "../perf", version = "1.0.2" }
solana-sdk = { path = "../sdk", version = "1.0.2" }
[dev-dependencies]
hex-literal = "0.2.1"
[lib]
name = "solana_chacha_cuda"

View File

@ -1,7 +1,7 @@
// Module used by validators to approve storage mining proofs in parallel using the GPU
use crate::chacha::{CHACHA_BLOCK_SIZE, CHACHA_KEY_SIZE};
use solana_ledger::blocktree::Blocktree;
use solana_chacha::chacha::{CHACHA_BLOCK_SIZE, CHACHA_KEY_SIZE};
use solana_ledger::blockstore::Blockstore;
use solana_perf::perf_libs;
use solana_sdk::hash::Hash;
use std::io;
@ -13,7 +13,7 @@ use std::sync::Arc;
// Then sample each block at the offsets provided by samples argument with sha256
// and return the vec of sha states
pub fn chacha_cbc_encrypt_file_many_keys(
blocktree: &Arc<Blocktree>,
blockstore: &Arc<Blockstore>,
segment: u64,
slots_per_segment: u64,
ivecs: &mut [u8],
@ -46,7 +46,7 @@ pub fn chacha_cbc_encrypt_file_many_keys(
(api.chacha_init_sha_state)(int_sha_states.as_mut_ptr(), num_keys as u32);
}
loop {
match blocktree.get_data_shreds(current_slot, start_index, std::u64::MAX, &mut buffer) {
match blockstore.get_data_shreds(current_slot, start_index, std::u64::MAX, &mut buffer) {
Ok((last_index, mut size)) => {
debug!(
"chacha_cuda: encrypting segment: {} num_shreds: {} data_len: {}",
@ -113,12 +113,12 @@ pub fn chacha_cbc_encrypt_file_many_keys(
#[cfg(test)]
mod tests {
use super::*;
use crate::archiver::sample_file;
use crate::chacha::chacha_cbc_encrypt_ledger;
use solana_archiver_utils::sample_file;
use solana_chacha::chacha::chacha_cbc_encrypt_ledger;
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;
@ -134,9 +134,9 @@ mod tests {
let entries = create_ticks(slots_per_segment, 0, Hash::default());
let ledger_path = get_tmp_ledger_path!();
let ticks_per_slot = 16;
let blocktree = Arc::new(Blocktree::open(&ledger_path).unwrap());
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
blocktree
blockstore
.write_entries(
0,
0,
@ -160,7 +160,7 @@ mod tests {
let mut cpu_iv = ivecs.clone();
chacha_cbc_encrypt_ledger(
&blocktree,
&blockstore,
0,
slots_per_segment as u64,
out_path,
@ -171,7 +171,7 @@ mod tests {
let ref_hash = sample_file(&out_path, &samples).unwrap();
let hashes = chacha_cbc_encrypt_file_many_keys(
&blocktree,
&blockstore,
0,
slots_per_segment as u64,
&mut ivecs,
@ -196,8 +196,8 @@ mod tests {
let ledger_path = get_tmp_ledger_path!();
let ticks_per_slot = 90;
let entries = create_ticks(2 * ticks_per_slot, 0, Hash::default());
let blocktree = Arc::new(Blocktree::open(&ledger_path).unwrap());
blocktree
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
blockstore
.write_entries(
0,
0,
@ -224,7 +224,7 @@ mod tests {
ivec[0] = i;
ivecs.extend(ivec.clone().iter());
chacha_cbc_encrypt_ledger(
&blocktree.clone(),
&blockstore.clone(),
0,
DEFAULT_SLOTS_PER_SEGMENT,
out_path,
@ -242,7 +242,7 @@ mod tests {
}
let hashes = chacha_cbc_encrypt_file_many_keys(
&blocktree,
&blockstore,
0,
DEFAULT_SLOTS_PER_SEGMENT,
&mut ivecs,
@ -267,9 +267,9 @@ mod tests {
let mut keys = hex!("abc123");
let ledger_path = get_tmp_ledger_path!();
let samples = [0];
let blocktree = Arc::new(Blocktree::open(&ledger_path).unwrap());
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
assert!(chacha_cbc_encrypt_file_many_keys(
&blocktree,
&blockstore,
0,
DEFAULT_SLOTS_PER_SEGMENT,
&mut keys,

8
chacha-cuda/src/lib.rs Normal file
View File

@ -0,0 +1,8 @@
#[macro_use]
extern crate log;
#[cfg(test)]
#[macro_use]
extern crate hex_literal;
pub mod chacha_cuda;

View File

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

1
chacha/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/farf/

25
chacha/Cargo.toml Normal file
View File

@ -0,0 +1,25 @@
[package]
name = "solana-chacha"
version = "1.0.2"
description = "Solana Chacha APIs"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[dependencies]
log = "0.4.8"
rand = "0.6.5"
rand_chacha = "0.1.1"
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.2" }
solana-ledger = { path = "../ledger", version = "1.0.2" }
solana-logger = { path = "../logger", version = "1.0.2" }
solana-perf = { path = "../perf", version = "1.0.2" }
solana-sdk = { path = "../sdk", version = "1.0.2" }
[dev-dependencies]
hex-literal = "0.2.1"
[lib]
name = "solana_chacha"

View File

@ -1,4 +1,4 @@
use solana_ledger::blocktree::Blocktree;
use solana_ledger::blockstore::Blockstore;
use solana_sdk::clock::Slot;
use std::fs::File;
use std::io;
@ -12,7 +12,7 @@ pub const CHACHA_BLOCK_SIZE: usize = 64;
pub const CHACHA_KEY_SIZE: usize = 32;
pub fn chacha_cbc_encrypt_ledger(
blocktree: &Arc<Blocktree>,
blockstore: &Arc<Blockstore>,
start_slot: Slot,
slots_per_segment: u64,
out_path: &Path,
@ -28,7 +28,7 @@ pub fn chacha_cbc_encrypt_ledger(
let mut current_slot = start_slot;
let mut start_index = 0;
loop {
match blocktree.get_data_shreds(current_slot, start_index, std::u64::MAX, &mut buffer) {
match blockstore.get_data_shreds(current_slot, start_index, std::u64::MAX, &mut buffer) {
Ok((last_index, mut size)) => {
debug!(
"chacha: encrypting slice: {} num_shreds: {} data_len: {}",
@ -74,13 +74,14 @@ pub fn chacha_cbc_encrypt_ledger(
#[cfg(test)]
mod tests {
use crate::chacha::chacha_cbc_encrypt_ledger;
use crate::gen_keys::GenKeys;
use solana_ledger::blocktree::Blocktree;
use rand::SeedableRng;
use rand_chacha::ChaChaRng;
use solana_ledger::blockstore::Blockstore;
use solana_ledger::entry::Entry;
use solana_ledger::get_tmp_ledger_path;
use solana_sdk::hash::{hash, Hash, Hasher};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::KeypairUtil;
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::system_transaction;
use std::fs::remove_file;
use std::fs::File;
@ -92,8 +93,9 @@ mod tests {
let one = hash(&zero.as_ref());
let seed = [2u8; 32];
let mut rnd = GenKeys::new(seed);
let keypair = rnd.gen_keypair();
let mut generator = ChaChaRng::from_seed(seed);
let keypair = Keypair::generate(&mut generator);
let mut id = one;
let mut num_hashes = 0;
@ -131,15 +133,16 @@ mod tests {
let ledger_path = get_tmp_ledger_path!();
let ticks_per_slot = 16;
let slots_per_segment = 32;
let blocktree = Arc::new(Blocktree::open(&ledger_path).unwrap());
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
let out_path = tmp_file_path("test_encrypt_ledger");
let seed = [2u8; 32];
let mut rnd = GenKeys::new(seed);
let keypair = rnd.gen_keypair();
let mut generator = ChaChaRng::from_seed(seed);
let keypair = Keypair::generate(&mut generator);
let entries = make_tiny_deterministic_test_entries(slots_per_segment);
blocktree
blockstore
.write_entries(
0,
0,
@ -157,8 +160,14 @@ mod tests {
"abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234
abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234"
);
chacha_cbc_encrypt_ledger(&blocktree, 0, slots_per_segment as u64, &out_path, &mut key)
.unwrap();
chacha_cbc_encrypt_ledger(
&blockstore,
0,
slots_per_segment as u64,
&out_path,
&mut key,
)
.unwrap();
let mut out_file = File::open(&out_path).unwrap();
let mut buf = vec![];
let size = out_file.read_to_end(&mut buf).unwrap();

8
chacha/src/lib.rs Normal file
View File

@ -0,0 +1,8 @@
#[macro_use]
extern crate log;
#[cfg(test)]
#[macro_use]
extern crate hex_literal;
pub mod chacha;

View File

@ -9,7 +9,7 @@
# ./affects-files.sh ^snap/ -- anything under the snap/ subdirectory
# ./affects-files.sh snap/ -- also matches foo/snap/
# Any pattern starting with the ! character will be negated:
# ./affects-files.sh !^book/ -- anything *not* under the book/ subdirectory
# ./affects-files.sh !^docs/ -- anything *not* under the docs/ subdirectory
#
set -e
cd "$(dirname "$0")"/..

View File

@ -6,7 +6,7 @@ steps:
timeout_in_minutes: 60
name: "publish docker"
- command: "ci/publish-crate.sh"
timeout_in_minutes: 120
timeout_in_minutes: 240
name: "publish crate"
branches: "!master"
- command: "ci/publish-bpf-sdk.sh"
@ -15,6 +15,6 @@ steps:
- command: "ci/publish-tarball.sh"
timeout_in_minutes: 60
name: "publish tarball"
- command: "ci/publish-book.sh"
- command: "ci/publish-docs.sh"
timeout_in_minutes: 15
name: "publish book"
name: "publish docs"

View File

@ -20,7 +20,7 @@ steps:
timeout_in_minutes: 30
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-stable.sh"
name: "stable"
timeout_in_minutes: 40
timeout_in_minutes: 60
artifact_paths: "log-*.txt"
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-move.sh"
name: "move"

View File

@ -72,10 +72,16 @@ ARGS+=(
--env CI_JOB_ID
--env CI_PULL_REQUEST
--env CI_REPO_SLUG
--env CODECOV_TOKEN
--env CRATES_IO_TOKEN
)
# 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...
# 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
echo
@ -83,8 +89,10 @@ if $INTERACTIVE; then
echo
fi
set -x
exec docker run --interactive --tty "${ARGS[@]}" "$IMAGE" bash
# shellcheck disable=SC2086
exec docker run --interactive --tty "${ARGS[@]}" $CODECOV_ENVS "$IMAGE" bash
fi
set -x
exec docker run "${ARGS[@]}" "$IMAGE" "$@"
# shellcheck disable=SC2086
exec docker run "${ARGS[@]}" $CODECOV_ENVS "$IMAGE" "$@"

View File

@ -1,4 +1,4 @@
FROM solanalabs/rust:1.40.0
FROM solanalabs/rust:1.41.1
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.1
# 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

@ -29,7 +29,7 @@ Start a local cluster and run sanity on it
-x - Add an extra validator (may be supplied multiple times)
-r - Select the RPC endpoint hosted by a node that starts as
a validator node. If unspecified the RPC endpoint hosted by
the bootstrap leader will be used.
the bootstrap validator will be used.
-c - Reuse existing node/ledger configuration from a previous sanity
run
@ -74,7 +74,7 @@ source multinode-demo/common.sh
nodes=(
"multinode-demo/faucet.sh"
"multinode-demo/bootstrap-leader.sh \
"multinode-demo/bootstrap-validator.sh \
--no-restart \
--init-complete-file init-complete-node1.log \
--dynamic-port-range 8000-8050"
@ -170,7 +170,7 @@ startNodes() {
logs+=("$(getNodeLogFile "$i" "$cmd")")
fi
# 1 == bootstrap leader, wait until it boots before starting
# 1 == bootstrap validator, wait until it boots before starting
# other validators
if [[ "$i" -eq 1 ]]; then
SECONDS=
@ -178,8 +178,8 @@ startNodes() {
(
set -x
$solana_cli --keypair config/bootstrap-leader/identity-keypair.json \
--url http://127.0.0.1:8899 get-genesis-hash
$solana_cli --keypair config/bootstrap-validator/identity-keypair.json \
--url http://127.0.0.1:8899 genesis-hash
) | tee genesis-hash.log
maybeExpectedGenesisHash="--expected-genesis-hash $(tail -n1 genesis-hash.log)"
fi
@ -277,7 +277,7 @@ rollingNodeRestart() {
}
verifyLedger() {
for ledger in bootstrap-leader validator; do
for ledger in bootstrap-validator validator; do
echo "--- $ledger ledger verification"
(
set -x
@ -331,7 +331,7 @@ while [[ $iteration -le $iterations ]]; do
rm -rf $client_keypair
) || flag_error
echo "--- RPC API: bootstrap-leader getTransactionCount ($iteration)"
echo "--- RPC API: bootstrap-validator getTransactionCount ($iteration)"
(
set -x
curl --retry 5 --retry-delay 2 --retry-connrefused \
@ -351,7 +351,7 @@ while [[ $iteration -le $iterations ]]; do
http://localhost:18899
) || flag_error
# Verify transaction count as reported by the bootstrap-leader node is advancing
# Verify transaction count as reported by the bootstrap-validator node is advancing
transactionCount=$(sed -e 's/{"jsonrpc":"2.0","result":\([0-9]*\),"id":1}/\1/' log-transactionCount.txt)
if [[ -n $lastTransactionCount ]]; then
echo "--- Transaction count check: $lastTransactionCount < $transactionCount"

View File

@ -18,19 +18,22 @@ declare prints=(
# Parts of the tree that are expected to be print free
declare print_free_tree=(
'core/src'
'faucet/src'
'metrics/src'
'net-utils/src'
'runtime/src'
'sdk/bpf/rust/rust-utils'
'sdk/src'
'programs/bpf/rust'
'programs/stake/src'
'programs/vote/src'
':core/src/**.rs'
':faucet/src/**.rs'
':ledger/src/**.rs'
':metrics/src/**.rs'
':net-utils/src/**.rs'
':runtime/src/**.rs'
':sdk/bpf/rust/rust-utils/**.rs'
':sdk/**.rs'
':programs/**.rs'
':^**bin**.rs'
':^**bench**.rs'
':^**test**.rs'
':^**/build.rs'
)
if _ git --no-pager grep -n --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then
if _ git --no-pager grep -n "${prints[@]/#/-e}" -- "${print_free_tree[@]}"; then
exit 1
fi

View File

@ -1,99 +0,0 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
me=$(basename "$0")
BOOK="book"
source ci/rust-version.sh stable
eval "$(ci/channel-info.sh)"
if [[ -n $PUBLISH_BOOK_TAG ]]; then
CURRENT_TAG="$(git describe --tags)"
COMMIT_TO_PUBLISH="$(git rev-list -n 1 "${PUBLISH_BOOK_TAG}")"
# book is manually published at a specified release tag
if [[ $PUBLISH_BOOK_TAG != "$CURRENT_TAG" ]]; then
(
cat <<EOF
steps:
- trigger: "$BUILDKITE_PIPELINE_SLUG"
async: true
build:
message: "$BUILDKITE_MESSAGE"
commit: "$COMMIT_TO_PUBLISH"
env:
PUBLISH_BOOK_TAG: "$PUBLISH_BOOK_TAG"
EOF
) | buildkite-agent pipeline upload
exit 0
fi
repo=git@github.com:solana-labs/book.git
else
# book-edge and book-beta are published automatically on the tip of the branch
case $CHANNEL in
edge)
repo=git@github.com:solana-labs/book-edge.git
;;
beta)
repo=git@github.com:solana-labs/book-beta.git
;;
*)
echo "--- publish skipped"
exit 0
;;
esac
BOOK=$CHANNEL
fi
ci/docker-run.sh "$rust_stable_docker_image" bash -exc "book/build.sh"
echo --- create book repo
(
set -x
cd book/html/
git init .
git add ./* ./.nojekyll
git config user.email maintainers@solana.com
git config user.name "$me"
git commit -m "${CI_COMMIT:-local}"
)
echo "--- publish $BOOK"
(
cd book/html/
git remote add origin $repo
git fetch origin master
if ! git diff HEAD origin/master --quiet; then
git push -f origin HEAD:master
else
echo "Content unchanged, publish skipped"
fi
)
echo --- update gitbook-cage
(
if [[ -z $CI_BRANCH ]]; then
exit 0
fi
set -x
(
. ci/rust-version.sh
ci/docker-run.sh $rust_stable_docker_image make -Cbook -B svg
)
# make a local commit for the svgs
git add -A -f book/src/.gitbook/assets/.
if ! git diff-index --quiet HEAD; then
git config user.email maintainers@solana.com
git config user.name "$me"
git commit -m "gitbook-cage update $(date -Is)"
git push -f git@github.com:solana-labs/solana-gitbook-cage.git HEAD:refs/heads/"$CI_BRANCH"
# pop off the local commit
git reset --hard HEAD~
fi
)
exit 0

View File

@ -4,7 +4,12 @@ set -e
cd "$(dirname "$0")/.."
eval "$(ci/channel-info.sh)"
echo --- Creating tarball
if [[ -n "$CI_TAG" ]]; then
CHANNEL_OR_TAG=$CI_TAG
else
CHANNEL_OR_TAG=$CHANNEL
fi
(
set -x
sdk/bpf/scripts/package.sh
@ -12,7 +17,7 @@ echo --- Creating tarball
)
echo --- AWS S3 Store
if [[ -z $CHANNEL ]]; then
if [[ -z $CHANNEL_OR_TAG ]]; then
echo Skipped
else
(
@ -24,7 +29,7 @@ else
--volume "$PWD:/solana" \
eremite/aws-cli:2018.12.18 \
/usr/bin/s3cmd --acl-public put /solana/bpf-sdk.tar.bz2 \
s3://solana-sdk/"$CHANNEL"/bpf-sdk.tar.bz2
s3://solana-sdk/"$CHANNEL_OR_TAG"/bpf-sdk.tar.bz2
)
fi

31
ci/publish-docs.sh Executable file
View File

@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
me=$(basename "$0")
echo --- update gitbook-cage
if [[ -n $CI_BRANCH ]]; then
(
set -x
(
. ci/rust-version.sh stable
ci/docker-run.sh "$rust_stable_docker_image" make -C docs -B svg
)
# make a local commit for the svgs
git add -A -f docs/src/.gitbook/assets/.
if ! git diff-index --quiet HEAD; then
git config user.email maintainers@solana.com
git config user.name "$me"
git commit -m "gitbook-cage update $(date -Is)"
git push -f git@github.com:solana-labs/solana-gitbook-cage.git HEAD:refs/heads/"$CI_BRANCH"
# pop off the local commit
git reset --hard HEAD~
fi
)
else
echo CI_BRANCH not set
fi
exit 0

View File

@ -53,7 +53,7 @@ windows)
;;
esac
echo --- Creating tarball
echo --- Creating release tarball
(
set -x
rm -rf solana-release/
@ -89,15 +89,21 @@ echo --- Creating tarball
)
# Metrics tarball is platform agnostic, only publish it from Linux
MAYBE_METRICS_TARBALL=
MAYBE_TARBALLS=
if [[ "$CI_OS_NAME" = linux ]]; then
metrics/create-metrics-tarball.sh
MAYBE_METRICS_TARBALL=solana-metrics.tar.bz2
(
set -x
sdk/bpf/scripts/package.sh
[[ -f bpf-sdk.tar.bz2 ]]
)
MAYBE_TARBALLS="bpf-sdk.tar.bz2 solana-metrics.tar.bz2"
fi
source ci/upload-ci-artifact.sh
for file in solana-release-$TARGET.tar.bz2 solana-release-$TARGET.yml solana-install-init-"$TARGET"* $MAYBE_METRICS_TARBALL; do
for file in solana-release-$TARGET.tar.bz2 solana-release-$TARGET.yml solana-install-init-"$TARGET"* $MAYBE_TARBALLS; do
upload-ci-artifact "$file"
if [[ -n $DO_NOT_PUBLISH_TAR ]]; then

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.1
fi
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
nightly_version="$RUST_NIGHTLY_VERSION"
else
nightly_version=2019-12-19
nightly_version=2020-02-27
fi

View File

@ -10,6 +10,9 @@ source ci/rust-version.sh nightly
export RUST_BACKTRACE=1
export RUSTFLAGS="-D warnings"
# Look for failed mergify.io backports
_ git show HEAD --check --oneline
_ cargo +"$rust_stable" fmt --all -- --check
# Clippy gets stuck for unknown reasons if sdk-c is included in the build, so check it separately.
@ -19,15 +22,15 @@ _ cargo +"$rust_stable" clippy --all --exclude solana-sdk-c -- --deny=warnings
_ cargo +"$rust_stable" clippy --manifest-path sdk-c/Cargo.toml -- --deny=warnings
_ cargo +"$rust_stable" audit --version
_ cargo +"$rust_stable" audit --ignore RUSTSEC-2019-0013 --ignore RUSTSEC-2018-0015 --ignore RUSTSEC-2019-0031
_ cargo +"$rust_stable" audit --ignore RUSTSEC-2020-0002
_ ci/nits.sh
_ ci/order-crates-for-publishing.py
_ book/build.sh
_ docs/build.sh
_ ci/check-ssh-keys.sh
{
cd programs/bpf
_ cargo +"$rust_stable" audit --ignore RUSTSEC-2019-0031
_ cargo +"$rust_stable" audit
for project in rust/*/ ; do
echo "+++ do_bpf_checks $project"
(

View File

@ -41,7 +41,10 @@ if [[ -z "$CODECOV_TOKEN" ]]; then
echo "^^^ +++"
echo CODECOV_TOKEN undefined, codecov.io upload skipped
else
bash <(curl -s https://codecov.io/bash) -X gcov -f target/cov/lcov.info
# We normalize CI to `1`; but codecov expects it to be `true` to detect Buildkite...
# 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

@ -13,12 +13,12 @@ annotate() {
# Run the appropriate test based on entrypoint
testName=$(basename "$0" .sh)
# Skip if only the book has been modified
# Skip if only the docs have been modified
ci/affects-files.sh \
\!^book/ \
\!^docs/ \
|| {
annotate --style info \
"Skipped $testName as only book files were modified"
"Skipped $testName as only docs/ files were modified"
exit 0
}
@ -86,7 +86,7 @@ test-stable-perf)
fi
_ cargo +"$rust_stable" build --bins ${V:+--verbose}
_ cargo +"$rust_stable" test --package solana-perf --package solana-ledger --package solana-core --lib ${V:+--verbose} -- --nocapture
_ cargo +"$rust_stable" test --package solana-chacha-cuda --package solana-perf --package solana-ledger --package solana-core --lib ${V:+--verbose} -- --nocapture
;;
test-move)
ci/affects-files.sh \
@ -111,7 +111,7 @@ test-move)
;;
test-local-cluster)
_ cargo +"$rust_stable" build --release --bins ${V:+--verbose}
_ cargo +"$rust_stable" test --release --package solana-local-cluster ${V:+--verbose} -- --nocapture
_ cargo +"$rust_stable" test --release --package solana-local-cluster ${V:+--verbose} -- --nocapture --test-threads=1
exit 0
;;
*)

View File

@ -32,20 +32,10 @@ steps:
options:
- label: "testnet"
value: "testnet"
- label: "testnet-perf"
value: "testnet-perf"
- label: "testnet-edge"
value: "testnet-edge"
- label: "testnet-edge-perf"
value: "testnet-edge-perf"
- label: "testnet-beta"
value: "testnet-beta"
- label: "testnet-beta-perf"
value: "testnet-beta-perf"
- label: "testnet-demo"
value: "testnet-demo"
- label: "tds"
value: "tds"
- select: "Operation"
key: "testnet-operation"
default: "sanity-or-restart"
@ -131,32 +121,18 @@ GCE_LOW_QUOTA_ZONES=(
)
case $TESTNET in
testnet-edge|testnet-edge-perf)
testnet-edge)
CHANNEL_OR_TAG=edge
CHANNEL_BRANCH=$EDGE_CHANNEL
;;
testnet-beta|testnet-beta-perf)
testnet-beta)
CHANNEL_OR_TAG=beta
CHANNEL_BRANCH=$BETA_CHANNEL
;;
testnet)
CHANNEL_OR_TAG=$STABLE_CHANNEL_LATEST_TAG
CHANNEL_BRANCH=$STABLE_CHANNEL
;;
testnet-perf)
CHANNEL_OR_TAG=$STABLE_CHANNEL_LATEST_TAG
CHANNEL_BRANCH=$STABLE_CHANNEL
;;
testnet-demo)
CHANNEL_OR_TAG=beta
CHANNEL_BRANCH=$BETA_CHANNEL
: "${GCE_NODE_COUNT:=150}"
: "${GCE_LOW_QUOTA_NODE_COUNT:=70}"
;;
tds)
: "${TDS_CHANNEL_OR_TAG:=edge}"
CHANNEL_OR_TAG="$TDS_CHANNEL_OR_TAG"
CHANNEL_BRANCH="$CI_BRANCH"
export CLOUDSDK_CORE_PROJECT=testnet-solana-com
;;
*)
echo "Error: Invalid TESTNET=$TESTNET"
@ -204,6 +180,7 @@ steps:
TESTNET_DB_HOST: "$TESTNET_DB_HOST"
GCE_NODE_COUNT: "$GCE_NODE_COUNT"
GCE_LOW_QUOTA_NODE_COUNT: "$GCE_LOW_QUOTA_NODE_COUNT"
RUST_LOG: "$RUST_LOG"
EOF
) | buildkite-agent pipeline upload
exit 0
@ -231,64 +208,22 @@ 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
)
;;
testnet-edge-perf)
(
set -x
REJECT_EXTRA_NODES=1 \
ci/testnet-sanity.sh edge-perf-testnet-solana-com ec2 us-west-2b
)
;;
testnet-beta)
(
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
)
;;
testnet-beta-perf)
(
set -x
REJECT_EXTRA_NODES=1 \
ci/testnet-sanity.sh beta-perf-testnet-solana-com ec2 us-west-2b
)
;;
testnet)
(
set -x
ci/testnet-sanity.sh testnet-solana-com gce -P us-west1-b
)
;;
testnet-perf)
(
set -x
REJECT_EXTRA_NODES=1 \
ci/testnet-sanity.sh perf-testnet-solana-com gce us-west1-b
#ci/testnet-sanity.sh perf-testnet-solana-com ec2 us-east-1a
)
;;
testnet-demo)
(
set -x
ok=true
if [[ -n $GCE_NODE_COUNT ]]; then
ci/testnet-sanity.sh demo-testnet-solana-com gce "${GCE_ZONES[0]}" -f || ok=false
else
echo "Error: no GCE nodes"
ok=false
fi
$ok
)
;;
tds)
(
set -x
ci/testnet-sanity.sh tds-solana-com gce "${GCE_ZONES[0]}" -f
ci/testnet-sanity.sh devnet-solana-com gce -P us-west1-b
)
;;
*)
@ -324,9 +259,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} \
@ -334,24 +269,12 @@ deploy() {
${maybeDelete:+-D}
)
;;
testnet-edge-perf)
(
set -x
RUST_LOG=solana=warn \
ci/testnet-deploy.sh -p edge-perf-testnet-solana-com -C ec2 -z us-west-2b \
-g -t "$CHANNEL_OR_TAG" -c 2 \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
)
;;
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} \
@ -359,24 +282,12 @@ deploy() {
${maybeDelete:+-D}
)
;;
testnet-beta-perf)
(
set -x
RUST_LOG=solana=warn \
ci/testnet-deploy.sh -p beta-perf-testnet-solana-com -C ec2 -z us-west-2b \
-g -t "$CHANNEL_OR_TAG" -c 2 \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
)
;;
testnet)
(
set -x
ci/testnet-deploy.sh -p testnet-solana-com -C gce -z us-west1-b \
-t "$CHANNEL_OR_TAG" -n 3 -c 0 -u -P \
-a testnet-solana-com --letsencrypt testnet.solana.com \
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 devnet.solana.com \
--limit-ledger-size \
${skipCreate:+-e} \
${skipStart:+-s} \
@ -386,132 +297,7 @@ deploy() {
(
echo "--- net.sh update"
set -x
time net/net.sh update -t "$CHANNEL_OR_TAG" --platform linux --platform osx --platform windows
)
;;
testnet-perf)
(
set -x
RUST_LOG=solana=warn \
ci/testnet-deploy.sh -p perf-testnet-solana-com -C gce -z us-west1-b \
-G "--machine-type n1-standard-16 --accelerator count=2,type=nvidia-tesla-v100" \
-t "$CHANNEL_OR_TAG" -c 2 \
-d pd-ssd \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
)
;;
testnet-demo)
(
set -x
if [[ -n $GCE_LOW_QUOTA_NODE_COUNT ]] || [[ -n $skipStart ]]; then
maybeSkipStart="skip"
fi
# shellcheck disable=SC2068
ci/testnet-deploy.sh -p demo-testnet-solana-com -C gce ${GCE_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P -u --allow-boot-failures \
--skip-remote-log-retrieval \
-a demo-testnet-solana-com \
${skipCreate:+-e} \
${maybeSkipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
if [[ -n $GCE_LOW_QUOTA_NODE_COUNT ]]; then
# shellcheck disable=SC2068
ci/testnet-deploy.sh -p demo-testnet-solana-com2 -C gce ${GCE_LOW_QUOTA_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$GCE_LOW_QUOTA_NODE_COUNT" -c 0 -P --allow-boot-failures -x \
--skip-remote-log-retrieval \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
fi
)
;;
tds)
(
set -x
# Allow cluster configuration to be overridden from env vars
if [[ -z $TDS_ZONES ]]; then
TDS_ZONES="us-west1-a,us-central1-a,europe-west4-a"
fi
GCE_CLOUD_ZONES=(); while read -r -d, ; do GCE_CLOUD_ZONES+=( "$REPLY" ); done <<< "${TDS_ZONES},"
if [[ -z $TDS_NODE_COUNT ]]; then
TDS_NODE_COUNT="3"
fi
if [[ -z $TDS_CLIENT_COUNT ]]; then
TDS_CLIENT_COUNT="1"
fi
if [[ -z $ENABLE_GPU ]]; then
maybeGpu=(-G "--machine-type n1-standard-16 --accelerator count=2,type=nvidia-tesla-v100")
elif [[ $ENABLE_GPU == skip ]]; then
maybeGpu=()
else
maybeGpu=(-G "${ENABLE_GPU}")
fi
if [[ -z $DISABLE_AIRDROPS ]]; then
DISABLE_AIRDROPS="true"
fi
if [[ $DISABLE_AIRDROPS == true ]] ; then
maybeDisableAirdrops="--no-airdrop"
else
maybeDisableAirdrops=""
fi
EXTERNAL_ACCOUNTS_FILE=/tmp/validator.yml
if [[ -z $EXTERNAL_ACCOUNTS_FILE_URL ]]; then
EXTERNAL_ACCOUNTS_FILE_URL=https://raw.githubusercontent.com/solana-labs/tour-de-sol/master/validators/all.yml
wget ${EXTERNAL_ACCOUNTS_FILE_URL} -O ${EXTERNAL_ACCOUNTS_FILE}
maybeExternalAccountsFile="--external-accounts-file ${EXTERNAL_ACCOUNTS_FILE}"
elif [[ $EXTERNAL_ACCOUNTS_FILE_URL == skip ]]; then
maybeExternalAccountsFile=""
else
wget ${EXTERNAL_ACCOUNTS_FILE_URL} -O ${EXTERNAL_ACCOUNTS_FILE}
maybeExternalAccountsFile="--external-accounts-file ${EXTERNAL_ACCOUNTS_FILE}"
fi
if [[ -z $ADDITIONAL_DISK_SIZE_GB ]]; then
maybeAdditionalDisk="--validator-additional-disk-size-gb 32000"
elif [[ $ADDITIONAL_DISK_SIZE_GB == skip ]]; then
maybeAdditionalDisk=""
else
maybeAdditionalDisk="--validator-additional-disk-size-gb ${ADDITIONAL_DISK_SIZE_GB}"
fi
# Multiple V100 GPUs are available in us-west1, us-central1 and europe-west4
# shellcheck disable=SC2068
# shellcheck disable=SC2086
ci/testnet-deploy.sh -p tds-solana-com -C gce \
"${maybeGpu[@]}" \
-d pd-ssd \
${GCE_CLOUD_ZONES[@]/#/-z } \
-t "$CHANNEL_OR_TAG" \
-n ${TDS_NODE_COUNT} \
-c ${TDS_CLIENT_COUNT} \
--idle-clients \
-P -u \
-a tds-solana-com --letsencrypt tds.solana.com \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
${maybeDisableAirdrops} \
${maybeExternalAccountsFile} \
--target-lamports-per-signature 0 \
--slots-per-epoch 4096 \
${maybeAdditionalDisk}
time net/net.sh update -t "$CHANNEL_OR_TAG" --platform linux --platform osx #--platform windows
)
;;
*)

View File

@ -60,7 +60,7 @@ trap shutdown EXIT INT
set -x
for zone in "$@"; do
echo "--- $cloudProvider config [$zone]"
timeout 5m net/"$cloudProvider".sh config $maybePublicNetwork -p "$netName" -z "$zone"
timeout 5m net/"$cloudProvider".sh config $maybePublicNetwork -n 1 -p "$netName" -z "$zone"
net/init-metrics.sh -e
echo "+++ $cloudProvider.sh info"
net/"$cloudProvider".sh info

View File

@ -1,6 +1,6 @@
[package]
name = "solana-clap-utils"
version = "0.22.0"
version = "1.0.2"
description = "Solana utilities for the clap"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -11,9 +11,9 @@ edition = "2018"
[dependencies]
clap = "2.33.0"
rpassword = "4.0"
semver = "0.9.0"
solana-sdk = { path = "../sdk", version = "0.22.0" }
tiny-bip39 = "0.6.2"
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.2" }
solana-sdk = { path = "../sdk", version = "1.0.2" }
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>>
@ -33,7 +36,10 @@ where
}
}
pub fn unix_timestamp_of(matches: &ArgMatches<'_>, name: &str) -> Option<UnixTimestamp> {
pub fn unix_timestamp_from_rfc3339_datetime(
matches: &ArgMatches<'_>,
name: &str,
) -> Option<UnixTimestamp> {
matches.value_of(name).and_then(|value| {
DateTime::parse_from_rfc3339(value)
.ok()
@ -61,6 +67,20 @@ pub fn pubkey_of(matches: &ArgMatches<'_>, name: &str) -> Option<Pubkey> {
value_of(matches, name).or_else(|| keypair_of(matches, name).map(|keypair| keypair.pubkey()))
}
pub fn pubkeys_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Pubkey>> {
matches.values_of(name).map(|values| {
values
.map(|value| {
value.parse::<Pubkey>().unwrap_or_else(|_| {
read_keypair_file(value)
.expect("read_keypair_file failed")
.pubkey()
})
})
.collect()
})
}
// Return pubkey/signature pairs for a string of the form pubkey=signature
pub fn pubkeys_sigs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<(Pubkey, Signature)>> {
matches.values_of(name).map(|values| {
@ -75,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::*;
@ -151,7 +193,7 @@ mod tests {
#[test]
fn test_keypair_of() {
let keypair = Keypair::new();
let outfile = tmp_file_path("test_gen_keypair_file.json", &keypair.pubkey());
let outfile = tmp_file_path("test_keypair_of.json", &keypair.pubkey());
let _ = write_keypair_file(&keypair, &outfile).unwrap();
let matches = app()
@ -175,7 +217,7 @@ mod tests {
#[test]
fn test_pubkey_of() {
let keypair = Keypair::new();
let outfile = tmp_file_path("test_gen_keypair_file.json", &keypair.pubkey());
let outfile = tmp_file_path("test_pubkey_of.json", &keypair.pubkey());
let _ = write_keypair_file(&keypair, &outfile).unwrap();
let matches = app()
@ -199,6 +241,26 @@ mod tests {
fs::remove_file(&outfile).unwrap();
}
#[test]
fn test_pubkeys_of() {
let keypair = Keypair::new();
let outfile = tmp_file_path("test_pubkeys_of.json", &keypair.pubkey());
let _ = write_keypair_file(&keypair, &outfile).unwrap();
let matches = app().clone().get_matches_from(vec![
"test",
"--multiple",
&keypair.pubkey().to_string(),
"--multiple",
&outfile,
]);
assert_eq!(
pubkeys_of(&matches, "multiple"),
Some(vec![keypair.pubkey(), keypair.pubkey()])
);
fs::remove_file(&outfile).unwrap();
}
#[test]
fn test_pubkeys_sigs_of() {
let key1 = Pubkey::new_rand();
@ -221,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,7 +1,11 @@
use crate::keypair::ASK_KEYWORD;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{read_keypair_file, Signature};
use crate::keypair::{parse_keypair_path, KeypairUrl, ASK_KEYWORD};
use chrono::DateTime;
use solana_sdk::{
clock::Slot,
hash::Hash,
pubkey::Pubkey,
signature::{read_keypair_file, Signature},
};
use std::str::FromStr;
// Return an error if a pubkey cannot be parsed.
@ -42,6 +46,18 @@ pub fn is_pubkey_or_keypair(string: String) -> Result<(), String> {
is_pubkey(string.clone()).or_else(|_| is_keypair(string))
}
// Return an error if string cannot be parsed as pubkey or keypair file or keypair ask keyword
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('=');
@ -78,18 +94,10 @@ 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_slot(slot: String) -> Result<(), String> {
slot.parse::<Slot>()
.map(|_| ())
.map_err(|e| format!("{:?}", e))
}
pub fn is_port(port: String) -> Result<(), String> {
@ -129,3 +137,54 @@ pub fn is_amount(amount: String) -> Result<(), String> {
))
}
}
pub fn is_rfc3339_datetime(value: String) -> Result<(), String> {
DateTime::parse_from_rfc3339(&value)
.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,27 +1,121 @@
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";
pub const ASK_SEED_PHRASE_ARG: ArgConstant<'static> = ArgConstant {
long: "ask-seed-phrase",
name: "ask_seed_phrase",
help: "Securely recover a keypair using a seed phrase and optional passphrase",
help: "Recover a keypair using a seed phrase and optional passphrase",
};
pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant {
@ -32,8 +126,8 @@ pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant {
#[derive(Debug, PartialEq)]
pub enum Source {
File,
Generated,
Path,
SeedPhrase,
}
@ -80,14 +174,14 @@ pub fn keypair_from_seed_phrase(
keypair_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase)?
} else {
let sanitized = sanitize_seed_phrase(seed_phrase);
let mnemonic = Mnemonic::from_phrase(sanitized, Language::English)?;
let mnemonic = Mnemonic::from_phrase(&sanitized, Language::English)?;
let passphrase = prompt_passphrase(&passphrase_prompt)?;
let seed = Seed::new(&mnemonic, &passphrase);
keypair_from_seed(seed.as_bytes())?
};
if confirm_pubkey {
let pubkey = Pubkey::new(keypair.public.as_ref());
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

@ -1,19 +1,16 @@
[package]
name = "solana-fixed-buf"
version = "0.22.0"
description = "A fixed-size byte array that supports bincode serde"
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-cli-config"
description = "Blockchain, Rebuilt for Scale"
version = "1.0.2"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[dependencies]
bincode = "1.2.1"
dirs = "2.0.2"
lazy_static = "1.4.0"
serde = "1.0.104"
[lib]
name = "solana_fixed_buf"
[dev-dependencies]
serde_derive = "1.0.103"
serde_yaml = "0.8.11"

View File

@ -1,8 +1,10 @@
// Wallet settings that can be configured for long-term use
use serde_derive::{Deserialize, Serialize};
use std::fs::{create_dir_all, File};
use std::io::{self, Write};
use std::path::Path;
use std::{
fs::{create_dir_all, File},
io::{self, Write},
path::Path,
};
lazy_static! {
pub static ref CONFIG_FILE: Option<String> = {
@ -16,13 +18,15 @@ lazy_static! {
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
pub struct Config {
pub url: String,
pub websocket_url: String,
pub keypair_path: String,
}
impl Config {
pub fn new(url: &str, keypair_path: &str) -> Self {
pub fn new(url: &str, websocket_url: &str, keypair_path: &str) -> Self {
Self {
url: url.to_string(),
websocket_url: websocket_url.to_string(),
keypair_path: keypair_path.to_string(),
}
}

4
cli-config/src/lib.rs Normal file
View File

@ -0,0 +1,4 @@
#[macro_use]
extern crate lazy_static;
pub mod config;

View File

@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-cli"
description = "Blockchain, Rebuilt for Scale"
version = "0.22.0"
version = "1.0.2"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -14,38 +14,39 @@ bs58 = "0.3.0"
chrono = { version = "0.4.10", features = ["serde"] }
clap = "2.33.0"
criterion-stats = "0.3.0"
ctrlc = { version = "3.1.3", features = ["termination"] }
console = "0.9.1"
ctrlc = { version = "3.1.4", features = ["termination"] }
console = "0.9.2"
dirs = "2.0.2"
lazy_static = "1.4.0"
log = "0.4.8"
indicatif = "0.13.0"
humantime = "1.3.0"
indicatif = "0.14.0"
humantime = "2.0.0"
num-traits = "0.2"
pretty-hex = "0.1.1"
reqwest = { version = "0.9.24", default-features = false, features = ["rustls-tls"] }
reqwest = { version = "0.10.1", default-features = false, features = ["blocking", "rustls-tls"] }
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.22.0" }
solana-clap-utils = { path = "../clap-utils", version = "0.22.0" }
solana-client = { path = "../client", version = "0.22.0" }
solana-config-program = { path = "../programs/config", version = "0.22.0" }
solana-faucet = { path = "../faucet", version = "0.22.0" }
solana-logger = { path = "../logger", version = "0.22.0" }
solana-net-utils = { path = "../net-utils", version = "0.22.0" }
solana-runtime = { path = "../runtime", version = "0.22.0" }
solana-sdk = { path = "../sdk", version = "0.22.0" }
solana-stake-program = { path = "../programs/stake", version = "0.22.0" }
solana-storage-program = { path = "../programs/storage", version = "0.22.0" }
solana-vote-program = { path = "../programs/vote", version = "0.22.0" }
solana-vote-signer = { path = "../vote-signer", version = "0.22.0" }
url = "2.1.0"
serde_json = "1.0.46"
solana-budget-program = { path = "../programs/budget", version = "1.0.2" }
solana-clap-utils = { path = "../clap-utils", version = "1.0.2" }
solana-cli-config = { path = "../cli-config", version = "1.0.2" }
solana-client = { path = "../client", version = "1.0.2" }
solana-config-program = { path = "../programs/config", version = "1.0.2" }
solana-faucet = { path = "../faucet", version = "1.0.2" }
solana-logger = { path = "../logger", version = "1.0.2" }
solana-net-utils = { path = "../net-utils", version = "1.0.2" }
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.2" }
solana-runtime = { path = "../runtime", version = "1.0.2" }
solana-sdk = { path = "../sdk", version = "1.0.2" }
solana-stake-program = { path = "../programs/stake", version = "1.0.2" }
solana-storage-program = { path = "../programs/storage", version = "1.0.2" }
solana-vote-program = { path = "../programs/vote", version = "1.0.2" }
solana-vote-signer = { path = "../vote-signer", version = "1.0.2" }
titlecase = "1.1.0"
url = "2.1.1"
[dev-dependencies]
solana-core = { path = "../core", version = "0.22.0" }
solana-budget-program = { path = "../programs/budget", version = "0.22.0" }
solana-core = { path = "../core", version = "1.0.2" }
solana-budget-program = { path = "../programs/budget", version = "1.0.2" }
tempfile = "3.1.0"
[[bin]]

File diff suppressed because it is too large Load Diff

View File

@ -8,20 +8,32 @@ 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_client::{rpc_client::RpcClient, rpc_request::RpcVoteAccountInfo};
use solana_clap_utils::{input_parsers::*, input_validators::*, keypair::signer_from_path};
use solana_client::{
pubsub_client::{PubsubClient, SlotInfoMessage},
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, EpochSchedule},
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::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread::sleep,
time::{Duration, Instant},
};
@ -54,8 +66,9 @@ impl ClusterQuerySubCommands for App<'_, '_> {
.about("Get the version of the cluster entrypoint"),
)
.subcommand(SubCommand::with_name("fees").about("Display current cluster fees"))
.subcommand(SubCommand::with_name("get-block-time")
.subcommand(SubCommand::with_name("block-time")
.about("Get estimated production time of a block")
.alias("get-block-time")
.arg(
Arg::with_name("slot")
.index(1)
@ -65,9 +78,11 @@ impl ClusterQuerySubCommands for App<'_, '_> {
.help("Slot number of the block to query")
)
)
.subcommand(SubCommand::with_name("leader-schedule").about("Display leader schedule"))
.subcommand(
SubCommand::with_name("get-epoch-info")
SubCommand::with_name("epoch-info")
.about("Get information about the current epoch")
.alias("get-epoch-info")
.arg(
Arg::with_name("confirmed")
.long("confirmed")
@ -78,10 +93,13 @@ impl ClusterQuerySubCommands for App<'_, '_> {
),
)
.subcommand(
SubCommand::with_name("get-genesis-hash").about("Get the genesis hash"),
SubCommand::with_name("genesis-hash")
.about("Get the genesis hash")
.alias("get-genesis-hash")
)
.subcommand(
SubCommand::with_name("get-slot").about("Get current slot")
SubCommand::with_name("slot").about("Get current slot")
.alias("get-slot")
.arg(
Arg::with_name("confirmed")
.long("confirmed")
@ -92,7 +110,8 @@ impl ClusterQuerySubCommands for App<'_, '_> {
),
)
.subcommand(
SubCommand::with_name("get-transaction-count").about("Get current transaction count")
SubCommand::with_name("transaction-count").about("Get current transaction count")
.alias("get-transaction-count")
.arg(
Arg::with_name("confirmed")
.long("confirmed")
@ -150,8 +169,13 @@ impl ClusterQuerySubCommands for App<'_, '_> {
),
)
.subcommand(
SubCommand::with_name("show-block-production")
SubCommand::with_name("live-slots")
.about("Show information about the current slot progression"),
)
.subcommand(
SubCommand::with_name("block-production")
.about("Show information about block production")
.alias("show-block-production")
.arg(
Arg::with_name("epoch")
.long("epoch")
@ -166,12 +190,33 @@ impl ClusterQuerySubCommands for App<'_, '_> {
),
)
.subcommand(
SubCommand::with_name("show-gossip")
.about("Show the current gossip network nodes"),
SubCommand::with_name("gossip")
.about("Show the current gossip network nodes")
.alias("show-gossip")
)
.subcommand(
SubCommand::with_name("show-validators")
.about("Show information about the current validators")
SubCommand::with_name("stakes")
.about("Show stake account information")
.arg(
Arg::with_name("vote_account_pubkeys")
.index(1)
.value_name("VOTE ACCOUNT PUBKEYS")
.takes_value(true)
.multiple(true)
.validator(is_pubkey_or_keypair)
.help("Only show stake accounts delegated to the provided vote accounts"),
)
.arg(
Arg::with_name("lamports")
.long("lamports")
.takes_value(false)
.help("Display balance in lamports instead of SOL"),
),
)
.subcommand(
SubCommand::with_name("validators")
.about("Show summary information about the current validators")
.alias("show-validators")
.arg(
Arg::with_name("lamports")
.long("lamports")
@ -186,11 +231,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") {
@ -212,7 +261,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,
)?],
})
}
@ -220,7 +274,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![],
})
}
@ -232,7 +286,7 @@ pub fn parse_get_epoch_info(matches: &ArgMatches<'_>) -> Result<CliCommandInfo,
};
Ok(CliCommandInfo {
command: CliCommand::GetEpochInfo { commitment_config },
require_keypair: false,
signers: vec![],
})
}
@ -244,7 +298,7 @@ pub fn parse_get_slot(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliErr
};
Ok(CliCommandInfo {
command: CliCommand::GetSlot { commitment_config },
require_keypair: false,
signers: vec![],
})
}
@ -256,7 +310,20 @@ pub fn parse_get_transaction_count(matches: &ArgMatches<'_>) -> Result<CliComman
};
Ok(CliCommandInfo {
command: CliCommand::GetTransactionCount { commitment_config },
require_keypair: false,
signers: vec![],
})
}
pub fn parse_show_stakes(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let use_lamports_unit = matches.is_present("lamports");
let vote_account_pubkeys = pubkeys_of(matches, "vote_account_pubkeys");
Ok(CliCommandInfo {
command: CliCommand::ShowStakes {
use_lamports_unit,
vote_account_pubkeys,
},
signers: vec![],
})
}
@ -265,7 +332,7 @@ pub fn parse_show_validators(matches: &ArgMatches<'_>) -> Result<CliCommandInfo,
Ok(CliCommandInfo {
command: CliCommand::ShowValidators { use_lamports_unit },
require_keypair: false,
signers: vec![],
})
}
@ -278,20 +345,6 @@ fn new_spinner_progress_bar() -> ProgressBar {
progress_bar
}
/// Aggregate epoch credit stats and return (total credits, total slots, total epochs)
pub fn aggregate_epoch_credits(
epoch_credits: &[(Epoch, u64, u64)],
epoch_schedule: &EpochSchedule,
) -> (u64, u64, u64) {
epoch_credits
.iter()
.fold((0, 0, 0), |acc, (epoch, credits, prev_credits)| {
let credits_earned = credits - prev_credits;
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(*epoch);
(acc.0 + credits_earned, acc.1 + slots_in_epoch, acc.2 + 1)
})
}
pub fn process_catchup(rpc_client: &RpcClient, node_pubkey: &Pubkey) -> ProcessResult {
let cluster_nodes = rpc_client.get_cluster_nodes()?;
@ -364,35 +417,90 @@ pub fn process_fees(rpc_client: &RpcClient) -> ProcessResult {
))
}
pub fn process_leader_schedule(rpc_client: &RpcClient) -> ProcessResult {
let epoch_info = rpc_client.get_epoch_info()?;
let first_slot_in_epoch = epoch_info.absolute_slot - epoch_info.slot_index;
let leader_schedule = rpc_client.get_leader_schedule(Some(first_slot_in_epoch))?;
if leader_schedule.is_none() {
return Err(format!(
"Unable to fetch leader schedule for slot {}",
first_slot_in_epoch
)
.into());
}
let leader_schedule = leader_schedule.unwrap();
let mut leader_per_slot_index = Vec::new();
for (pubkey, leader_slots) in leader_schedule.iter() {
for slot_index in leader_slots.iter() {
if *slot_index >= leader_per_slot_index.len() {
leader_per_slot_index.resize(*slot_index + 1, "?");
}
leader_per_slot_index[*slot_index] = pubkey;
}
}
for (slot_index, leader) in leader_per_slot_index.iter().enumerate() {
println!(
" {:<15} {:<44}",
first_slot_in_epoch + slot_index as u64,
leader
);
}
Ok("".to_string())
}
pub fn process_get_block_time(rpc_client: &RpcClient, slot: Slot) -> ProcessResult {
let timestamp = rpc_client.get_block_time(slot)?;
Ok(timestamp.to_string())
}
fn slot_to_human_time(slot: Slot) -> String {
humantime::format_duration(Duration::from_secs(
slot * clock::DEFAULT_TICKS_PER_SLOT / clock::DEFAULT_TICKS_PER_SECOND,
))
.to_string()
}
pub fn process_get_epoch_info(
rpc_client: &RpcClient,
commitment_config: &CommitmentConfig,
) -> ProcessResult {
let epoch_info = rpc_client.get_epoch_info_with_commitment(commitment_config.clone())?;
println!();
println_name_value("Current epoch:", &epoch_info.epoch.to_string());
println_name_value("Current slot:", &epoch_info.absolute_slot.to_string());
println_name_value("Slot:", &epoch_info.absolute_slot.to_string());
println_name_value("Epoch:", &epoch_info.epoch.to_string());
let start_slot = epoch_info.absolute_slot - epoch_info.slot_index;
let end_slot = start_slot + epoch_info.slots_in_epoch;
println_name_value(
"Total slots in current epoch:",
&epoch_info.slots_in_epoch.to_string(),
"Epoch Slot Range:",
&format!("[{}..{})", start_slot, end_slot),
);
println_name_value(
"Epoch Completed Percent:",
&format!(
"{:>3.3}%",
epoch_info.slot_index as f64 / epoch_info.slots_in_epoch as f64 * 100_f64
),
);
let remaining_slots_in_epoch = epoch_info.slots_in_epoch - epoch_info.slot_index;
println_name_value(
"Remaining slots in current epoch:",
&remaining_slots_in_epoch.to_string(),
);
let remaining_time_in_epoch = Duration::from_secs(
remaining_slots_in_epoch * clock::DEFAULT_TICKS_PER_SLOT / clock::DEFAULT_TICKS_PER_SECOND,
"Epoch Completed Slots:",
&format!(
"{}/{} ({} remaining)",
epoch_info.slot_index, epoch_info.slots_in_epoch, remaining_slots_in_epoch
),
);
println_name_value(
"Time remaining in current epoch:",
&humantime::format_duration(remaining_time_in_epoch).to_string(),
"Epoch Completed Time:",
&format!(
"{}/{} ({} remaining)",
slot_to_human_time(epoch_info.slot_index),
slot_to_human_time(epoch_info.slots_in_epoch),
slot_to_human_time(remaining_slots_in_epoch)
),
);
Ok("".to_string())
}
@ -416,7 +524,7 @@ pub fn parse_show_block_production(matches: &ArgMatches<'_>) -> Result<CliComman
Ok(CliCommandInfo {
command: CliCommand::ShowBlockProduction { epoch, slot_limit },
require_keypair: false,
signers: vec![],
})
}
@ -434,19 +542,39 @@ pub fn process_show_block_production(
return Err(format!("Epoch {} is in the future", epoch).into());
}
let minimum_ledger_slot = rpc_client.minimum_ledger_slot()?;
let first_slot_in_epoch = epoch_schedule.get_first_slot_in_epoch(epoch);
let end_slot = std::cmp::min(
epoch_info.absolute_slot,
epoch_schedule.get_last_slot_in_epoch(epoch),
);
let start_slot = if let Some(slot_limit) = slot_limit {
let mut start_slot = if let Some(slot_limit) = slot_limit {
std::cmp::max(end_slot.saturating_sub(slot_limit), first_slot_in_epoch)
} else {
first_slot_in_epoch
};
let start_slot_index = (start_slot - first_slot_in_epoch) as usize;
let end_slot_index = (end_slot - start_slot) as usize;
if minimum_ledger_slot > end_slot {
return Err(format!(
"Ledger data not available for slots {} to {} (minimum ledger slot is {})",
start_slot, end_slot, minimum_ledger_slot
)
.into());
}
if minimum_ledger_slot > start_slot {
println!(
"\n{}",
style(format!(
"Note: Requested start slot was {} but minimum ledger slot is {}",
start_slot, minimum_ledger_slot
))
.italic(),
);
start_slot = minimum_ledger_slot;
}
let progress_bar = new_spinner_progress_bar();
progress_bar.set_message(&format!(
@ -455,12 +583,14 @@ pub fn process_show_block_production(
));
let confirmed_blocks = rpc_client.get_confirmed_blocks(start_slot, Some(end_slot))?;
let start_slot_index = (start_slot - first_slot_in_epoch) as usize;
let end_slot_index = (end_slot - first_slot_in_epoch) as usize;
let total_slots = end_slot_index - start_slot_index + 1;
let total_blocks = confirmed_blocks.len();
assert!(total_blocks <= total_slots);
let total_slots_missed = total_slots - total_blocks;
let total_slots_skipped = total_slots - total_blocks;
let mut leader_slot_count = HashMap::new();
let mut leader_missed_slots = HashMap::new();
let mut leader_skipped_slots = HashMap::new();
progress_bar.set_message(&format!("Fetching leader schedule for epoch {}...", epoch));
let leader_schedule = rpc_client
@ -482,7 +612,7 @@ pub fn process_show_block_production(
progress_bar.set_message(&format!(
"Processing {} slots containing {} blocks and {} empty slots...",
total_slots, total_blocks, total_slots_missed
total_slots, total_blocks, total_slots_skipped
));
let mut confirmed_blocks_index = 0;
@ -491,10 +621,10 @@ pub fn process_show_block_production(
let slot = start_slot + slot_index as u64;
let slot_count = leader_slot_count.entry(leader).or_insert(0);
*slot_count += 1;
let missed_slots = leader_missed_slots.entry(leader).or_insert(0);
let skipped_slots = leader_skipped_slots.entry(leader).or_insert(0);
loop {
if !confirmed_blocks.is_empty() {
if confirmed_blocks_index < confirmed_blocks.len() {
let slot_of_next_confirmed_block = confirmed_blocks[confirmed_blocks_index];
if slot_of_next_confirmed_block < slot {
confirmed_blocks_index += 1;
@ -506,9 +636,9 @@ pub fn process_show_block_production(
break;
}
}
*missed_slots += 1;
*skipped_slots += 1;
individual_slot_status.push(
style(format!(" {:<15} {:<44} MISSED", slot, leader))
style(format!(" {:<15} {:<44} SKIPPED", slot, leader))
.red()
.to_string(),
);
@ -524,23 +654,23 @@ pub fn process_show_block_production(
"Identity Pubkey",
"Leader Slots",
"Blocks Produced",
"Missed Slots",
"Missed Block Percentage",
"Skipped Slots",
"Skipped Slot Percentage",
))
.bold()
);
let mut table = vec![];
for (leader, leader_slots) in leader_slot_count.iter() {
let missed_slots = leader_missed_slots.get(leader).unwrap();
let blocks_produced = leader_slots - missed_slots;
let skipped_slots = leader_skipped_slots.get(leader).unwrap();
let blocks_produced = leader_slots - skipped_slots;
table.push(format!(
" {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
leader,
leader_slots,
blocks_produced,
missed_slots,
*missed_slots as f64 / *leader_slots as f64 * 100.
skipped_slots,
*skipped_slots as f64 / *leader_slots as f64 * 100.
));
}
table.sort();
@ -551,8 +681,8 @@ pub fn process_show_block_production(
format!("Epoch {} total:", epoch),
total_slots,
total_blocks,
total_slots_missed,
total_slots_missed as f64 / total_slots as f64 * 100.
total_slots_skipped,
total_slots_skipped as f64 / total_slots as f64 * 100.
);
println!(
" (using data from {} slots: {} to {})",
@ -589,8 +719,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();
@ -608,11 +738,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,
)?;
@ -704,6 +836,101 @@ pub fn process_ping(
Ok("".to_string())
}
pub fn process_live_slots(url: &str) -> ProcessResult {
let exit = Arc::new(AtomicBool::new(false));
// Disable Ctrl+C handler as sometimes the PubsubClient shutdown can stall. Also it doesn't
// really matter that the shutdown is clean because the process is terminating.
/*
let exit_clone = exit.clone();
ctrlc::set_handler(move || {
exit_clone.store(true, Ordering::Relaxed);
})?;
*/
let mut current: Option<SlotInfoMessage> = None;
let mut message = "".to_string();
let slot_progress = new_spinner_progress_bar();
slot_progress.set_message("Connecting...");
let (mut client, receiver) = PubsubClient::slot_subscribe(url)?;
slot_progress.set_message("Connected.");
let spacer = "|";
slot_progress.println(spacer);
let mut last_root = std::u64::MAX;
let mut last_root_update = Instant::now();
let mut slots_per_second = std::f64::NAN;
loop {
if exit.load(Ordering::Relaxed) {
eprintln!("{}", message);
client.shutdown().unwrap();
break;
}
match receiver.recv() {
Ok(new_info) => {
if last_root == std::u64::MAX {
last_root = new_info.root;
last_root_update = Instant::now();
}
if last_root_update.elapsed().as_secs() >= 5 {
let root = new_info.root;
slots_per_second =
(root - last_root) as f64 / last_root_update.elapsed().as_secs() as f64;
last_root_update = Instant::now();
last_root = root;
}
message = if slots_per_second.is_nan() {
format!("{:?}", new_info)
} else {
format!(
"{:?} | root slot advancing at {:.2} slots/second",
new_info, slots_per_second
)
}
.to_owned();
slot_progress.set_message(&message);
if let Some(previous) = current {
let slot_delta: i64 = new_info.slot as i64 - previous.slot as i64;
let root_delta: i64 = new_info.root as i64 - previous.root as i64;
//
// if slot has advanced out of step with the root, we detect
// a mismatch and output the slot information
//
if slot_delta != root_delta {
let prev_root = format!(
"|<--- {} <- … <- {} <- {} (prev)",
previous.root, previous.parent, previous.slot
);
slot_progress.println(&prev_root);
let new_root = format!(
"| '- {} <- … <- {} <- {} (next)",
new_info.root, new_info.parent, new_info.slot
);
slot_progress.println(prev_root);
slot_progress.println(new_root);
slot_progress.println(spacer);
}
}
current = Some(new_info);
}
Err(err) => {
eprintln!("disconnected: {:?}", err);
break;
}
}
}
Ok("".to_string())
}
pub fn process_show_gossip(rpc_client: &RpcClient) -> ProcessResult {
let cluster_nodes = rpc_client.get_cluster_nodes()?;
@ -740,8 +967,47 @@ pub fn process_show_gossip(rpc_client: &RpcClient) -> ProcessResult {
))
}
pub fn process_show_stakes(
rpc_client: &RpcClient,
use_lamports_unit: bool,
vote_account_pubkeys: Option<&[Pubkey]>,
) -> ProcessResult {
use crate::stake::print_stake_state;
use solana_stake_program::stake_state::StakeState;
let progress_bar = new_spinner_progress_bar();
progress_bar.set_message("Fetching stake accounts...");
let all_stake_accounts = rpc_client.get_program_accounts(&solana_stake_program::id())?;
progress_bar.finish_and_clear();
for (stake_pubkey, stake_account) in all_stake_accounts {
if let Ok(stake_state) = stake_account.state() {
match stake_state {
StakeState::Initialized(_) => {
if vote_account_pubkeys.is_none() {
println!("\nstake pubkey: {}", stake_pubkey);
print_stake_state(stake_account.lamports, &stake_state, use_lamports_unit);
}
}
StakeState::Stake(_, stake) => {
if vote_account_pubkeys.is_none()
|| vote_account_pubkeys
.unwrap()
.contains(&stake.delegation.voter_pubkey)
{
println!("\nstake pubkey: {}", stake_pubkey);
print_stake_state(stake_account.lamports, &stake_state, use_lamports_unit);
}
}
_ => {}
}
}
}
Ok("".to_string())
}
pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool) -> ProcessResult {
let epoch_schedule = rpc_client.get_epoch_schedule()?;
let epoch_info = rpc_client.get_epoch_info()?;
let vote_accounts = rpc_client.get_vote_accounts()?;
let total_active_stake = vote_accounts
.current
@ -790,7 +1056,7 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
"Commission",
"Last Vote",
"Root Block",
"Uptime",
"Credits",
"Active Stake",
))
.bold()
@ -798,7 +1064,7 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
fn print_vote_account(
vote_account: RpcVoteAccountInfo,
epoch_schedule: &EpochSchedule,
current_epoch: Epoch,
total_active_stake: f64,
use_lamports_unit: bool,
delinquent: bool,
@ -811,17 +1077,6 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
}
}
fn uptime(epoch_credits: Vec<(Epoch, u64, u64)>, epoch_schedule: &EpochSchedule) -> String {
let (total_credits, total_slots, _) =
aggregate_epoch_credits(&epoch_credits, &epoch_schedule);
if total_slots > 0 {
let total_uptime = 100_f64 * total_credits as f64 / total_slots as f64;
format!("{:.2}%", total_uptime)
} else {
"-".into()
}
}
println!(
"{} {:<44} {:<44} {:>9}% {:>8} {:>10} {:>7} {}",
if delinquent {
@ -834,7 +1089,15 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
vote_account.commission,
non_zero_or_dash(vote_account.last_vote),
non_zero_or_dash(vote_account.root_slot),
uptime(vote_account.epoch_credits, epoch_schedule),
vote_account
.epoch_credits
.iter()
.find_map(|(epoch, credits, _)| if *epoch == current_epoch {
Some(*credits)
} else {
None
})
.unwrap_or(0),
if vote_account.activated_stake > 0 {
format!(
"{} ({:.2}%)",
@ -850,7 +1113,7 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
for vote_account in vote_accounts.current.into_iter() {
print_vote_account(
vote_account,
&epoch_schedule,
epoch_info.epoch,
total_active_stake,
use_lamports_unit,
false,
@ -859,7 +1122,7 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
for vote_account in vote_accounts.delinquent.into_iter() {
print_vote_account(
vote_account,
&epoch_schedule,
epoch_info.epoch,
total_active_stake,
use_lamports_unit,
true,
@ -873,92 +1136,99 @@ 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![],
}
);
let slot = 100;
let test_get_block_time = test_commands.clone().get_matches_from(vec![
"test",
"get-block-time",
&slot.to_string(),
]);
let test_get_block_time =
test_commands
.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![],
}
);
let test_get_epoch_info = test_commands
.clone()
.get_matches_from(vec!["test", "get-epoch-info"]);
.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![],
}
);
let test_get_genesis_hash = test_commands
.clone()
.get_matches_from(vec!["test", "get-genesis-hash"]);
.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", "get-slot"]);
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![],
}
);
let test_transaction_count = test_commands
.clone()
.get_matches_from(vec!["test", "get-transaction-count"]);
.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![],
}
);
@ -974,7 +1244,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,
@ -983,7 +1253,7 @@ mod tests {
timeout: Duration::from_secs(3),
commitment_config: CommitmentConfig::default(),
},
require_keypair: true
signers: vec![default_keypair.into()],
}
);
}

View File

@ -1,3 +1,4 @@
use crate::cli::SettingType;
use console::style;
use solana_sdk::transaction::Transaction;
@ -11,17 +12,19 @@ pub fn println_name_value(name: &str, value: &str) {
println!("{} {}", style(name).bold(), styled_value);
}
pub fn println_name_value_or(name: &str, value: &str, default_value: &str) {
if value == "" {
println!(
"{} {} {}",
style(name).bold(),
style(default_value),
style("(default)").italic()
);
} else {
println!("{} {}", style(name).bold(), style(value));
pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
let description = match setting_type {
SettingType::Explicit => "",
SettingType::Computed => "(computed)",
SettingType::SystemDefault => "(default)",
};
println!(
"{} {} {}",
style(name).bold(),
style(value),
style(description).italic(),
);
}
pub fn println_signers(tx: &Transaction) {

View File

@ -1,11 +1,8 @@
#[macro_use]
extern crate lazy_static;
pub mod cli;
pub mod cluster_query;
pub mod config;
pub mod display;
pub mod nonce;
pub mod offline;
pub mod stake;
pub mod storage;
pub mod validator_info;

View File

@ -1,146 +1,143 @@
use clap::{crate_description, crate_name, Arg, ArgGroup, ArgMatches, SubCommand};
use clap::{crate_description, crate_name, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand};
use console::style;
use solana_clap_utils::{
input_validators::is_url,
keypair::{
self, keypair_input, KeypairWithSource, ASK_SEED_PHRASE_ARG,
SKIP_SEED_PHRASE_VALIDATION_ARG,
},
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},
config::{self, Config},
cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners},
display::{println_name_value, println_name_value_or},
};
use solana_sdk::signature::read_keypair_file;
use std::error;
use solana_cli_config::config::{Config, CONFIG_FILE};
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() {
("get", Some(subcommand_matches)) => {
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()),
_ => unreachable!(),
};
println_name_value_or(&format!("* {}:", field), &value, &default_value);
} else {
println_name_value("Wallet Config:", config_file);
println_name_value_or(
"* url:",
("config", Some(matches)) => match matches.subcommand() {
("get", Some(subcommand_matches)) => {
if let Some(config_file) = matches.value_of("config_file") {
let config = Config::load(config_file).unwrap_or_default();
let (url_setting_type, json_rpc_url) =
CliConfig::compute_json_rpc_url_setting("", &config.url);
let (ws_setting_type, websocket_url) = CliConfig::compute_websocket_url_setting(
"",
&config.websocket_url,
"",
&config.url,
&CliConfig::default_json_rpc_url(),
);
println_name_value_or(
"* keypair:",
&config.keypair_path,
&CliConfig::default_keypair_path(),
let (keypair_setting_type, keypair_path) =
CliConfig::compute_keypair_path_setting("", &config.keypair_path);
if let Some(field) = subcommand_matches.value_of("specific_setting") {
let (field_name, value, setting_type) = match field {
"json_rpc_url" => ("RPC URL", json_rpc_url, url_setting_type),
"websocket_url" => ("WS URL", websocket_url, ws_setting_type),
"keypair" => ("Key Path", keypair_path, keypair_setting_type),
_ => unreachable!(),
};
println_name_value_or(&format!("{}:", field_name), &value, setting_type);
} else {
println_name_value("Config File:", config_file);
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
println_name_value_or("WS URL:", &websocket_url, ws_setting_type);
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
}
} else {
println!(
"{} Either provide the `--config` arg or ensure home directory exists to use the default config location",
style("No config file found.").bold()
);
}
} else {
println!(
"{} Either provide the `--config` arg or ensure home directory exists to use the default config location",
style("No config file found.").bold()
);
false
}
false
}
("set", Some(subcommand_matches)) => {
if let Some(config_file) = matches.value_of("config_file") {
let mut config = Config::load(config_file).unwrap_or_default();
if let Some(url) = subcommand_matches.value_of("json_rpc_url") {
config.url = url.to_string();
("set", Some(subcommand_matches)) => {
if let Some(config_file) = matches.value_of("config_file") {
let mut config = Config::load(config_file).unwrap_or_default();
if let Some(url) = subcommand_matches.value_of("json_rpc_url") {
config.url = url.to_string();
}
if let Some(url) = subcommand_matches.value_of("websocket_url") {
config.websocket_url = url.to_string();
}
if let Some(keypair) = subcommand_matches.value_of("keypair") {
config.keypair_path = keypair.to_string();
}
config.save(config_file)?;
let (url_setting_type, json_rpc_url) =
CliConfig::compute_json_rpc_url_setting("", &config.url);
let (ws_setting_type, websocket_url) = CliConfig::compute_websocket_url_setting(
"",
&config.websocket_url,
"",
&config.url,
);
let (keypair_setting_type, keypair_path) =
CliConfig::compute_keypair_path_setting("", &config.keypair_path);
println_name_value("Config File:", config_file);
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
println_name_value_or("WS URL:", &websocket_url, ws_setting_type);
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
} else {
println!(
"{} Either provide the `--config` arg or ensure home directory exists to use the default config location",
style("No config file found.").bold()
);
}
if let Some(keypair) = subcommand_matches.value_of("keypair") {
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);
} else {
println!(
"{} Either provide the `--config` arg or ensure home directory exists to use the default config location",
style("No config file found.").bold()
);
false
}
false
}
_ => unreachable!(),
},
_ => true,
};
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 {
Config::default()
};
let json_rpc_url = if let Some(url) = matches.value_of("json_rpc_url") {
url.to_string()
} else if config.url != "" {
config.url
} else {
let default = CliConfig::default();
default.json_rpc_url
};
let (_, json_rpc_url) = CliConfig::compute_json_rpc_url_setting(
matches.value_of("json_rpc_url").unwrap_or(""),
&config.url,
);
let (_, websocket_url) = CliConfig::compute_websocket_url_setting(
matches.value_of("websocket_url").unwrap_or(""),
&config.websocket_url,
matches.value_of("json_rpc_url").unwrap_or(""),
&config.url,
);
let (_, default_signer_path) = CliConfig::compute_keypair_path_setting(
matches.value_of("keypair").unwrap_or(""),
&config.keypair_path,
);
let CliCommandInfo {
command,
require_keypair,
} = parse_command(&matches)?;
let CliCommandInfo { command, signers } =
parse_command(&matches, &default_signer_path, wallet_manager.as_ref())?;
let (keypair, keypair_path) = if require_keypair {
let KeypairWithSource { keypair, source } = keypair_input(&matches, "keypair")?;
match source {
keypair::Source::File => (
keypair,
Some(matches.value_of("keypair").unwrap().to_string()),
),
keypair::Source::SeedPhrase => (keypair, None),
keypair::Source::Generated => {
let keypair_path = if config.keypair_path != "" {
config.keypair_path
} else {
let default_keypair_path = CliConfig::default_keypair_path();
if !std::path::Path::new(&default_keypair_path).exists() {
return Err(CliError::KeypairFileNotFound(
"Generate a new keypair with `solana-keygen new`".to_string(),
)
.into());
}
default_keypair_path
};
let keypair = read_keypair_file(&keypair_path).or_else(|err| {
Err(CliError::BadParameter(format!(
"{}: Unable to open keypair file: {}",
err, keypair_path
)))
})?;
(keypair, Some(keypair_path))
}
}
} else {
let default = CliConfig::default();
(default.keypair, None)
};
Ok(CliConfig {
command,
json_rpc_url,
keypair,
keypair_path,
rpc_client: None,
verbose: matches.is_present("verbose"),
})
Ok((
CliConfig {
command,
json_rpc_url,
websocket_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>> {
@ -158,7 +155,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.takes_value(true)
.global(true)
.help("Configuration file to use");
if let Some(ref config_file) = *config::CONFIG_FILE {
if let Some(ref config_file) = *CONFIG_FILE {
arg.default_value(&config_file)
} else {
arg
@ -174,6 +171,15 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.validator(is_url)
.help("JSON RPC URL for the solana cluster"),
)
.arg(
Arg::with_name("websocket_url")
.long("ws")
.value_name("URL")
.takes_value(true)
.global(true)
.validator(is_url)
.help("WebSocket URL for the solana cluster"),
)
.arg(
Arg::with_name("keypair")
.short("k")
@ -181,7 +187,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")
@ -190,15 +205,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)
@ -206,31 +212,40 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.help(SKIP_SEED_PHRASE_VALIDATION_ARG.help),
)
.subcommand(
SubCommand::with_name("get")
.about("Get cli config settings")
.arg(
Arg::with_name("specific_setting")
.index(1)
.value_name("CONFIG_FIELD")
.takes_value(true)
.possible_values(&["url", "keypair"])
.help("Return a specific config setting"),
),
)
.subcommand(
SubCommand::with_name("set")
.about("Set a cli config setting")
.group(
ArgGroup::with_name("config_settings")
.args(&["json_rpc_url", "keypair"])
.multiple(true)
.required(true),
SubCommand::with_name("config")
.about("Solana command-line tool configuration settings")
.aliases(&["get", "set"])
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name("get")
.about("Get current config settings")
.arg(
Arg::with_name("specific_setting")
.index(1)
.value_name("CONFIG_FIELD")
.takes_value(true)
.possible_values(&["json_rpc_url", "websocket_url", "keypair"])
.help("Return a specific config setting"),
),
)
.subcommand(
SubCommand::with_name("set")
.about("Set a config setting")
.group(
ArgGroup::with_name("config_settings")
.args(&["json_rpc_url", "websocket_url", "keypair"])
.multiple(true)
.required(true),
),
),
)
.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,34 +1,76 @@
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,
build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
log_instruction_custom_error, CliCommand, 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::*, offline::BLOCKHASH_ARG, ArgConstant,
};
use solana_client::rpc_client::RpcClient;
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account_utils::State,
account::Account,
account_utils::StateMut,
hash::Hash,
nonce_instruction::{authorize, create_nonce_account, nonce, withdraw, NonceError},
nonce_program,
nonce_state::NonceState,
message::Message,
nonce_state::{Meta, NonceState},
pubkey::Pubkey,
signature::{Keypair, KeypairUtil},
system_instruction::SystemError,
system_instruction::{
advance_nonce_account, authorize_nonce_account, create_address_with_seed,
create_nonce_account, create_nonce_account_with_seed, withdraw_nonce_account, NonceError,
SystemError,
},
system_program,
transaction::Transaction,
};
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq)]
pub enum CliNonceError {
InvalidAccountOwner,
InvalidAccountData,
InvalidHash,
InvalidAuthority,
InvalidState,
}
pub const NONCE_ARG: ArgConstant<'static> = ArgConstant {
name: "nonce",
long: "nonce",
help: "Provide the nonce account to use when creating a nonced \n\
transaction. Nonced transactions are useful when a transaction \n\
requires a lengthy signing process. Learn more about nonced \n\
transactions at https://docs.solana.com/offline-signing/durable-nonce",
};
pub const NONCE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
name: "nonce_authority",
long: "nonce-authority",
help: "Provide the nonce authority keypair to use when signing a nonced transaction",
};
pub trait NonceSubCommands {
fn nonce_subcommands(self) -> Self;
}
fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name("nonce_authority")
.long("nonce-authority")
pub fn nonce_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(NONCE_ARG.name)
.long(NONCE_ARG.long)
.takes_value(true)
.value_name("KEYPAIR")
.validator(is_keypair_or_ask_keyword)
.help("Specify nonce authority if different from account")
.value_name("PUBKEY")
.requires(BLOCKHASH_ARG.name)
.validator(is_pubkey)
.help(NONCE_ARG.help)
}
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 or REMOTE WALLET PATH")
.validator(is_valid_signer)
.help(NONCE_AUTHORITY_ARG.help)
}
impl NonceSubCommands for App<'_, '_> {
@ -54,6 +96,13 @@ impl NonceSubCommands for App<'_, '_> {
.validator(is_pubkey_or_keypair)
.help("Account to be granted authority of the nonce account"),
)
.arg(
Arg::with_name("seed")
.long("seed")
.value_name("SEED STRING")
.takes_value(true)
.help("Seed for address generation; if specified, the resulting account will be at a derived address of the NONCE_ACCOUNT pubkey")
)
.arg(nonce_authority_arg()),
)
.subcommand(
@ -75,19 +124,11 @@ impl NonceSubCommands for App<'_, '_> {
.takes_value(true)
.required(true)
.validator(is_amount)
.help("The amount to load the nonce account with (default unit SOL)"),
.help("The amount to load the nonce account with, in 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"),
)
.arg(
Arg::with_name("nonce_authority")
.long("nonce-authority")
Arg::with_name(NONCE_AUTHORITY_ARG.name)
.long(NONCE_AUTHORITY_ARG.long)
.takes_value(true)
.value_name("BASE58_PUBKEY")
.validator(is_pubkey_or_keypair)
@ -95,8 +136,9 @@ impl NonceSubCommands for App<'_, '_> {
),
)
.subcommand(
SubCommand::with_name("get-nonce")
SubCommand::with_name("nonce")
.about("Get the current nonce value")
.alias("get-nonce")
.arg(
Arg::with_name("nonce_account_pubkey")
.index(1)
@ -122,8 +164,9 @@ impl NonceSubCommands for App<'_, '_> {
.arg(nonce_authority_arg()),
)
.subcommand(
SubCommand::with_name("show-nonce-account")
SubCommand::with_name("nonce-account")
.about("Show the contents of a nonce account")
.alias("show-nonce-account")
.arg(
Arg::with_name("nonce_account_pubkey")
.index(1)
@ -168,54 +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()),
)
}
}
fn resolve_nonce_authority(matches: &ArgMatches<'_>) -> Keypair {
keypair_of(matches, "nonce_authority")
.unwrap_or_else(|| keypair_of(matches, "nonce_account_keypair").unwrap())
}
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 = resolve_nonce_authority(matches);
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.into(),
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();
let lamports = required_lamports_from(matches, "amount", "unit")?;
let nonce_authority =
pubkey_of(matches, "nonce_authority").unwrap_or_else(|| nonce_account.pubkey());
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 = 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,
})
}
@ -224,20 +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 = resolve_nonce_authority(matches);
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.into(),
nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
},
require_keypair: true,
signers: signer_info.signers,
})
}
@ -250,75 +320,125 @@ 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 = resolve_nonce_authority(matches);
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.into(),
nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
destination_account_pubkey,
lamports,
},
require_keypair: true,
signers: signer_info.signers,
})
}
/// Check if a nonce account is initialized with the given authority and hash
pub fn check_nonce_account(
nonce_account: &Account,
nonce_authority: &Pubkey,
nonce_hash: &Hash,
) -> Result<(), Box<CliError>> {
if nonce_account.owner != system_program::ID {
return Err(CliError::InvalidNonce(CliNonceError::InvalidAccountOwner).into());
}
let nonce_state: NonceState = nonce_account
.state()
.map_err(|_| Box::new(CliError::InvalidNonce(CliNonceError::InvalidAccountData)))?;
match nonce_state {
NonceState::Initialized(meta, hash) => {
if &hash != nonce_hash {
Err(CliError::InvalidNonce(CliNonceError::InvalidHash).into())
} else if nonce_authority != &meta.nonce_authority {
Err(CliError::InvalidNonce(CliNonceError::InvalidAuthority).into())
} else {
Ok(())
}
}
NonceState::Uninitialized => {
Err(CliError::InvalidNonce(CliNonceError::InvalidState).into())
}
}
}
pub fn process_authorize_nonce_account(
rpc_client: &RpcClient,
config: &CliConfig,
nonce_account: &Pubkey,
nonce_authority: &Keypair,
nonce_authority: SignerIndex,
new_authority: &Pubkey,
) -> ProcessResult {
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ix = authorize(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 nonce_authority = config.signers[nonce_authority];
let ix = authorize_nonce_account(nonce_account, &nonce_authority.pubkey(), new_authority);
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_authority: &Pubkey,
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 {
nonce_account_pubkey
};
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
(&nonce_account_pubkey, "nonce_account_pubkey".to_string()),
(&config.signers[0].pubkey(), "cli keypair".to_string()),
(&nonce_account_address, "nonce_account".to_string()),
)?;
if rpc_client.get_account(&nonce_account_pubkey).is_ok() {
return Err(CliError::BadParameter(format!(
"Unable to create nonce account. Nonce account already exists: {}",
nonce_account_pubkey,
))
.into());
if let Ok(nonce_account) = rpc_client.get_account(&nonce_account_address) {
let err_msg = if nonce_account.owner == system_program::id()
&& StateMut::<NonceState>::state(&nonce_account).is_ok()
{
format!("Nonce account {} already exists", nonce_account_address)
} else {
format!(
"Account {} already exists and is not a nonce account",
nonce_account_address
)
};
return Err(CliError::BadParameter(err_msg).into());
}
let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(NonceState::size())?;
@ -330,33 +450,45 @@ pub fn process_create_nonce_account(
.into());
}
let ixs = create_nonce_account(
&config.keypair.pubkey(),
&nonce_account_pubkey,
nonce_authority,
lamports,
);
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.signers[0].pubkey(), // from
&nonce_account_address, // to
&nonce_account_pubkey, // base
&seed, // seed
&nonce_authority,
lamports,
)
} else {
create_nonce_account(
&config.signers[0].pubkey(),
&nonce_account_pubkey,
&nonce_authority,
lamports,
)
};
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let mut tx = Transaction::new_signed_with_payer(
ixs,
Some(&config.keypair.pubkey()),
&[&config.keypair, nonce_account],
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, nonce_account]);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
log_instruction_custom_error::<SystemError>(result)
}
pub fn process_get_nonce(rpc_client: &RpcClient, nonce_account_pubkey: &Pubkey) -> ProcessResult {
let nonce_account = rpc_client.get_account(nonce_account_pubkey)?;
if nonce_account.owner != nonce_program::id() {
if nonce_account.owner != system_program::id() {
return Err(CliError::RpcRequestError(format!(
"{:?} is not a nonce account",
nonce_account_pubkey
@ -378,10 +510,10 @@ pub fn process_new_nonce(
rpc_client: &RpcClient,
config: &CliConfig,
nonce_account: &Pubkey,
nonce_authority: &Keypair,
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()),
)?;
@ -392,22 +524,20 @@ pub fn process_new_nonce(
.into());
}
let ix = nonce(&nonce_account, &nonce_authority.pubkey());
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)
}
@ -417,35 +547,41 @@ pub fn process_show_nonce_account(
use_lamports_unit: bool,
) -> ProcessResult {
let nonce_account = rpc_client.get_account(nonce_account_pubkey)?;
if nonce_account.owner != nonce_program::id() {
if nonce_account.owner != system_program::id() {
return Err(CliError::RpcRequestError(format!(
"{:?} is not a nonce account",
nonce_account_pubkey
))
.into());
}
let print_account = |hash: Option<Hash>| {
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,
true
)
);
match hash {
Some(hash) => println!("nonce: {}", hash),
None => println!("nonce: uninitialized"),
match data {
Some((meta, hash)) => {
println!("Nonce: {}", hash);
println!("Authority: {}", meta.nonce_authority);
}
None => {
println!("Nonce: uninitialized");
println!("Authority: uninitialized");
}
}
Ok("".to_string())
};
match nonce_account.state() {
Ok(NonceState::Uninitialized) => print_account(None),
Ok(NonceState::Initialized(_, hash)) => print_account(Some(hash)),
Ok(NonceState::Initialized(meta, hash)) => print_account(Some((meta, hash))),
Err(err) => Err(CliError::RpcRequestError(format!(
"Account data could not be deserialized to nonce state: {:?}",
err
@ -458,32 +594,29 @@ pub fn process_withdraw_from_nonce_account(
rpc_client: &RpcClient,
config: &CliConfig,
nonce_account: &Pubkey,
nonce_authority: &Keypair,
nonce_authority: SignerIndex,
destination_account_pubkey: &Pubkey,
lamports: u64,
) -> ProcessResult {
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ix = withdraw(
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)
}
@ -491,7 +624,13 @@ pub fn process_withdraw_from_nonce_account(
mod tests {
use super::*;
use crate::cli::{app, parse_command};
use solana_sdk::signature::{read_keypair_file, write_keypair};
use solana_sdk::{
account::Account,
hash::hash,
nonce_state::{Meta as NonceMeta, NonceState},
signature::{read_keypair_file, write_keypair, Keypair, Signer},
system_program,
};
use tempfile::NamedTempFile;
fn make_tmp_file() -> (String, NamedTempFile) {
@ -502,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();
@ -520,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: read_keypair_file(&keypair_file).unwrap().into(),
nonce_authority: 0,
new_authority: Pubkey::default(),
},
require_keypair: true,
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
);
@ -541,14 +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: 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()
],
}
);
@ -558,17 +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_authority: nonce_account_pubkey,
lamports: 50,
nonce_account: 1,
seed: None,
nonce_authority: None,
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()
],
}
);
@ -578,19 +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_authority: read_keypair_file(&authority_keypair_file).unwrap().pubkey(),
lamports: 50,
nonce_account: 1,
seed: None,
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()
],
}
);
@ -601,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![],
}
);
@ -615,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: nonce_account.into(),
nonce_authority: 0,
},
require_keypair: true
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
);
@ -635,30 +786,33 @@ 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: 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()
],
}
);
// Test ShowNonceAccount Subcommand
let test_show_nonce_account = test_commands.clone().get_matches_from(vec![
"test",
"show-nonce-account",
"nonce-account",
&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![],
}
);
@ -669,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: read_keypair_file(&keypair_file).unwrap().into(),
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()],
}
);
@ -690,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: read_keypair_file(&keypair_file).unwrap().into(),
nonce_authority: 0,
destination_account_pubkey: nonce_account_pubkey,
lamports: 42000000000
},
require_keypair: true
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
);
@ -712,21 +874,90 @@ 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: 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()
],
}
);
}
#[test]
fn test_check_nonce_account() {
let blockhash = Hash::default();
let nonce_pubkey = Pubkey::new_rand();
let valid = Account::new_data(
1,
&NonceState::Initialized(NonceMeta::new(&nonce_pubkey), blockhash),
&system_program::ID,
);
assert!(check_nonce_account(&valid.unwrap(), &nonce_pubkey, &blockhash).is_ok());
let invalid_owner = Account::new_data(
1,
&NonceState::Initialized(NonceMeta::new(&nonce_pubkey), blockhash),
&Pubkey::new(&[1u8; 32]),
);
assert_eq!(
check_nonce_account(&invalid_owner.unwrap(), &nonce_pubkey, &blockhash),
Err(Box::new(CliError::InvalidNonce(
CliNonceError::InvalidAccountOwner
))),
);
let invalid_data = Account::new_data(1, &"invalid", &system_program::ID);
assert_eq!(
check_nonce_account(&invalid_data.unwrap(), &nonce_pubkey, &blockhash),
Err(Box::new(CliError::InvalidNonce(
CliNonceError::InvalidAccountData
))),
);
let invalid_hash = Account::new_data(
1,
&NonceState::Initialized(NonceMeta::new(&nonce_pubkey), hash(b"invalid")),
&system_program::ID,
);
assert_eq!(
check_nonce_account(&invalid_hash.unwrap(), &nonce_pubkey, &blockhash),
Err(Box::new(CliError::InvalidNonce(CliNonceError::InvalidHash))),
);
let invalid_authority = Account::new_data(
1,
&NonceState::Initialized(NonceMeta::new(&Pubkey::new_rand()), blockhash),
&system_program::ID,
);
assert_eq!(
check_nonce_account(&invalid_authority.unwrap(), &nonce_pubkey, &blockhash),
Err(Box::new(CliError::InvalidNonce(
CliNonceError::InvalidAuthority
))),
);
let invalid_state = Account::new_data(1, &NonceState::Uninitialized, &system_program::ID);
assert_eq!(
check_nonce_account(&invalid_state.unwrap(), &nonce_pubkey, &blockhash),
Err(Box::new(CliError::InvalidNonce(
CliNonceError::InvalidState
))),
);
}
}

253
cli/src/offline.rs Normal file
View File

@ -0,0 +1,253 @@
use clap::{App, Arg, ArgMatches};
use serde_json::Value;
use solana_clap_utils::{
input_parsers::value_of,
input_validators::{is_hash, is_pubkey_sig},
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;
#[derive(Clone, Debug, PartialEq)]
pub enum BlockhashQuery {
None(Hash, FeeCalculator),
FeeCalculator(Hash),
All,
}
impl BlockhashQuery {
pub fn new(blockhash: Option<Hash>, sign_only: bool) -> Self {
match blockhash {
Some(hash) if sign_only => Self::None(hash, FeeCalculator::default()),
Some(hash) if !sign_only => Self::FeeCalculator(hash),
None if !sign_only => Self::All,
_ => panic!("Cannot resolve blockhash"),
}
}
pub fn new_from_matches(matches: &ArgMatches<'_>) -> Self {
let blockhash = value_of(matches, BLOCKHASH_ARG.name);
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
BlockhashQuery::new(blockhash, sign_only)
}
pub fn get_blockhash_fee_calculator(
&self,
rpc_client: &RpcClient,
) -> Result<(Hash, FeeCalculator), Box<dyn std::error::Error>> {
let (hash, fee_calc) = match self {
BlockhashQuery::None(hash, fee_calc) => (Some(hash), Some(fee_calc)),
BlockhashQuery::FeeCalculator(hash) => (Some(hash), None),
BlockhashQuery::All => (None, None),
};
if None == fee_calc {
let (cluster_hash, fee_calc) = rpc_client.get_recent_blockhash()?;
Ok((*hash.unwrap_or(&cluster_hash), fee_calc))
} else {
Ok((*hash.unwrap(), fee_calc.unwrap().clone()))
}
}
}
impl Default for BlockhashQuery {
fn default() -> Self {
BlockhashQuery::All
}
}
fn blockhash_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(BLOCKHASH_ARG.name)
.long(BLOCKHASH_ARG.long)
.takes_value(true)
.value_name("BLOCKHASH")
.validator(is_hash)
.help(BLOCKHASH_ARG.help)
}
fn sign_only_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(SIGN_ONLY_ARG.name)
.long(SIGN_ONLY_ARG.long)
.takes_value(false)
.requires(BLOCKHASH_ARG.name)
.help(SIGN_ONLY_ARG.help)
}
fn signer_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(SIGNER_ARG.name)
.long(SIGNER_ARG.long)
.takes_value(true)
.value_name("BASE58_PUBKEY=BASE58_SIG")
.validator(is_pubkey_sig)
.requires(BLOCKHASH_ARG.name)
.multiple(true)
.help(SIGNER_ARG.help)
}
pub trait OfflineArgs {
fn offline_args(self) -> Self;
}
impl OfflineArgs for App<'_, '_> {
fn offline_args(self) -> Self {
self.arg(blockhash_arg())
.arg(sign_only_arg())
.arg(signer_arg())
}
}
pub fn parse_sign_only_reply_string(reply: &str) -> (Hash, Vec<(Pubkey, Signature)>) {
let object: Value = serde_json::from_str(&reply).unwrap();
let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
let blockhash = blockhash_str.parse::<Hash>().unwrap();
let signer_strings = object.get("signers").unwrap().as_array().unwrap();
let signers = signer_strings
.iter()
.map(|signer_string| {
let mut signer = signer_string.as_str().unwrap().split('=');
let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
let sig = Signature::from_str(signer.next().unwrap()).unwrap();
(key, sig)
})
.collect();
(blockhash, signers)
}
#[cfg(test)]
mod tests {
use super::*;
use clap::App;
use serde_json::{self, json, Value};
use solana_client::{
rpc_request::RpcRequest,
rpc_response::{Response, RpcResponseContext},
};
use solana_sdk::{fee_calculator::FeeCalculator, hash::hash};
use std::collections::HashMap;
#[test]
fn test_blockhashspec_new_ok() {
let blockhash = hash(&[1u8]);
assert_eq!(
BlockhashQuery::new(Some(blockhash), true),
BlockhashQuery::None(blockhash, FeeCalculator::default()),
);
assert_eq!(
BlockhashQuery::new(Some(blockhash), false),
BlockhashQuery::FeeCalculator(blockhash),
);
assert_eq!(BlockhashQuery::new(None, false), BlockhashQuery::All,);
}
#[test]
#[should_panic]
fn test_blockhashspec_new_fail() {
BlockhashQuery::new(None, true);
}
#[test]
fn test_blockhashspec_new_from_matches_ok() {
let test_commands = App::new("blockhashspec_test").offline_args();
let blockhash = hash(&[1u8]);
let blockhash_string = blockhash.to_string();
let matches = test_commands.clone().get_matches_from(vec![
"blockhashspec_test",
"--blockhash",
&blockhash_string,
"--sign-only",
]);
assert_eq!(
BlockhashQuery::new_from_matches(&matches),
BlockhashQuery::None(blockhash, FeeCalculator::default()),
);
let matches = test_commands.clone().get_matches_from(vec![
"blockhashspec_test",
"--blockhash",
&blockhash_string,
]);
assert_eq!(
BlockhashQuery::new_from_matches(&matches),
BlockhashQuery::FeeCalculator(blockhash),
);
let matches = test_commands
.clone()
.get_matches_from(vec!["blockhashspec_test"]);
assert_eq!(
BlockhashQuery::new_from_matches(&matches),
BlockhashQuery::All,
);
}
#[test]
#[should_panic]
fn test_blockhashspec_new_from_matches_fail() {
let test_commands = App::new("blockhashspec_test")
.arg(blockhash_arg())
// We can really only hit this case unless the arg requirements
// are broken, so unset the requires() to recreate that condition
.arg(sign_only_arg().requires(""));
let matches = test_commands
.clone()
.get_matches_from(vec!["blockhashspec_test", "--sign-only"]);
BlockhashQuery::new_from_matches(&matches);
}
#[test]
fn test_blockhashspec_get_blockhash_fee_calc() {
let test_blockhash = hash(&[0u8]);
let rpc_blockhash = hash(&[1u8]);
let rpc_fee_calc = FeeCalculator::new(42, 42);
let get_recent_blockhash_response = json!(Response {
context: RpcResponseContext { slot: 1 },
value: json!((
Value::String(rpc_blockhash.to_string()),
serde_json::to_value(rpc_fee_calc.clone()).unwrap()
)),
});
let mut mocks = HashMap::new();
mocks.insert(
RpcRequest::GetRecentBlockhash,
get_recent_blockhash_response.clone(),
);
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert_eq!(
BlockhashQuery::All
.get_blockhash_fee_calculator(&rpc_client)
.unwrap(),
(rpc_blockhash, rpc_fee_calc.clone()),
);
let mut mocks = HashMap::new();
mocks.insert(
RpcRequest::GetRecentBlockhash,
get_recent_blockhash_response.clone(),
);
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert_eq!(
BlockhashQuery::FeeCalculator(test_blockhash)
.get_blockhash_fee_calculator(&rpc_client)
.unwrap(),
(test_blockhash, rpc_fee_calc.clone()),
);
let mut mocks = HashMap::new();
mocks.insert(
RpcRequest::GetRecentBlockhash,
get_recent_blockhash_response.clone(),
);
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert_eq!(
BlockhashQuery::None(test_blockhash, FeeCalculator::default())
.get_blockhash_fee_calculator(&rpc_client)
.unwrap(),
(test_blockhash, FeeCalculator::default()),
);
let rpc_client = RpcClient::new_mock("fails".to_string());
assert!(BlockhashQuery::All
.get_blockhash_fee_calculator(&rpc_client)
.is_err());
}
}

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::State, 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;
@ -81,8 +82,9 @@ impl StorageSubCommands for App<'_, '_> {
),
)
.subcommand(
SubCommand::with_name("show-storage-account")
SubCommand::with_name("storage-account")
.about("Show the contents of a storage account")
.alias("show-storage-account")
.arg(
Arg::with_name("storage_account_pubkey")
.index(1)
@ -98,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 {
@ -134,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,
)?],
})
}
@ -144,32 +165,46 @@ 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(),
),
)?;
if let Ok(storage_account) = rpc_client.get_account(&storage_account_pubkey) {
let err_msg = if storage_account.owner == solana_storage_program::id() {
format!("Storage account {} already exists", storage_account_pubkey)
} else {
format!(
"Account {} already exists and is not a storage account",
storage_account_pubkey
)
};
return Err(CliError::BadParameter(err_msg).into());
}
use solana_storage_program::storage_contract::STORAGE_ACCOUNT_SPACE;
let required_balance = rpc_client
.get_minimum_balance_for_rent_exemption(STORAGE_ACCOUNT_SPACE as usize)?
.max(1);
let ixs = storage_instruction::create_storage_account(
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&account_owner,
&storage_account_pubkey,
required_balance,
@ -177,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)
}
@ -203,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,
)?;
@ -237,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())
}
@ -245,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) {
@ -259,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();
@ -270,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()
],
}
);
@ -294,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()
],
}
);
@ -312,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

@ -3,27 +3,29 @@ use crate::{
display::println_name_value,
};
use bincode::deserialize;
use clap::{App, Arg, ArgMatches, SubCommand};
use reqwest::Client;
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use reqwest::blocking::Client;
use serde_derive::{Deserialize, Serialize};
use serde_json::{Map, Value};
use solana_clap_utils::{
input_parsers::pubkey_of,
input_validators::{is_pubkey, is_url},
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;
@ -151,6 +153,7 @@ impl ValidatorInfoSubCommands for App<'_, '_> {
self.subcommand(
SubCommand::with_name("validator-info")
.about("Publish/get Validator info on Solana")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name("publish")
.about("Publish Validator info on Solana")
@ -223,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,
)?],
})
}
@ -243,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![],
})
}
@ -256,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);
@ -281,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
@ -299,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...",
@ -310,12 +322,14 @@ 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(),
1,
lamports,
keys.clone(),
);
instructions.extend_from_slice(&[config_instruction::store(
@ -324,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(
@ -339,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,
)?;
@ -387,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,23 +1,24 @@
use crate::{
cli::{
build_balance_message, check_account_for_fee, check_unique_pubkeys,
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
ProcessResult,
},
cluster_query::aggregate_epoch_credits,
use crate::cli::{
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_sdk::signature::Keypair;
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account::Account, pubkey::Pubkey, signature::KeypairUtil, system_instruction::SystemError,
account::Account,
message::Message,
pubkey::Pubkey,
system_instruction::{create_address_with_seed, SystemError},
transaction::Transaction,
};
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;
@ -51,7 +52,8 @@ impl VoteSubCommands for App<'_, '_> {
.long("commission")
.value_name("NUM")
.takes_value(true)
.help("The commission taken on reward redemption (0-100), default: 0"),
.default_value("100")
.help("The commission taken on reward redemption (0-100)"),
)
.arg(
Arg::with_name("authorized_voter")
@ -68,6 +70,13 @@ impl VoteSubCommands for App<'_, '_> {
.takes_value(true)
.validator(is_pubkey_or_keypair)
.help("Public key of the authorized withdrawer (defaults to cli config pubkey)"),
)
.arg(
Arg::with_name("seed")
.long("seed")
.value_name("SEED STRING")
.takes_value(true)
.help("Seed for address generation; if specified, the resulting account will be at a derived address of the VOTE ACCOUNT pubkey")
),
)
.subcommand(
@ -146,8 +155,9 @@ impl VoteSubCommands for App<'_, '_> {
),
)
.subcommand(
SubCommand::with_name("show-vote-account")
SubCommand::with_name("vote-account")
.about("Show the contents of a vote account")
.alias("show-vote-account")
.arg(
Arg::with_name("vote_account_pubkey")
.index(1)
@ -164,82 +174,91 @@ impl VoteSubCommands for App<'_, '_> {
.help("Display balance in lamports instead of SOL"),
),
)
.subcommand(
SubCommand::with_name("uptime")
.about("Show the uptime of a validator, based on epoch voting history")
.arg(
Arg::with_name("vote_account_pubkey")
.index(1)
.value_name("VOTE ACCOUNT PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("Vote account pubkey"),
)
.arg(
Arg::with_name("span")
.long("span")
.value_name("NUM OF EPOCHS")
.takes_value(true)
.help("Number of recent epochs to examine"),
)
.arg(
Arg::with_name("aggregate")
.long("aggregate")
.help("Aggregate uptime data across span"),
),
)
}
}
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_of(&matches, "commission").unwrap_or(0);
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,
})
}
@ -253,46 +272,47 @@ pub fn parse_vote_get_account_command(
pubkey: vote_account_pubkey,
use_lamports_unit,
},
require_keypair: false,
})
}
pub fn parse_vote_uptime_command(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
let aggregate = matches.is_present("aggregate");
let span = if matches.is_present("span") {
Some(value_t_or_exit!(matches, "span", u64))
} else {
None
};
Ok(CliCommandInfo {
command: CliCommand::Uptime {
pubkey: vote_account_pubkey,
aggregate,
span,
},
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())?
} else {
vote_account_pubkey
};
check_unique_pubkeys(
(&vote_account_pubkey, "vote_account_pubkey".to_string()),
(&config.signers[0].pubkey(), "cli keypair".to_string()),
(&vote_account_address, "vote_account".to_string()),
)?;
check_unique_pubkeys(
(&vote_account_address, "vote_account".to_string()),
(&identity_pubkey, "identity_pubkey".to_string()),
)?;
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
(&vote_account_pubkey, "vote_account_pubkey".to_string()),
)?;
if let Ok(vote_account) = rpc_client.get_account(&vote_account_address) {
let err_msg = if vote_account.owner == solana_vote_program::id() {
format!("Vote account {} already exists", vote_account_address)
} else {
format!(
"Account {} already exists and is not a vote account",
vote_account_address
)
};
return Err(CliError::BadParameter(err_msg).into());
}
let required_balance = rpc_client
.get_minimum_balance_for_rent_exemption(VoteState::size_of())?
@ -301,28 +321,39 @@ pub fn process_create_vote_account(
let vote_init = VoteInit {
node_pubkey: *identity_pubkey,
authorized_voter: authorized_voter.unwrap_or(vote_account_pubkey),
authorized_withdrawer: authorized_withdrawer.unwrap_or(config.keypair.pubkey()),
authorized_withdrawer: authorized_withdrawer.unwrap_or(vote_account_pubkey),
commission,
};
let ixs = vote_instruction::create_account(
&config.keypair.pubkey(),
&vote_account_pubkey,
&vote_init,
required_balance,
);
let ixs = if let Some(seed) = seed {
vote_instruction::create_account_with_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.signers[0].pubkey(),
&vote_account_pubkey,
&vote_init,
required_balance,
)
};
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let mut tx = Transaction::new_signed_instructions(
&[&config.keypair, vote_account],
ixs,
recent_blockhash,
);
let 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, vote_account]);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
log_instruction_custom_error::<SystemError>(result)
}
@ -339,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)
}
@ -366,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()),
@ -379,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)
}
@ -428,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_voters());
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 {
@ -456,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);
@ -469,65 +494,11 @@ pub fn process_show_vote_account(
Ok("".to_string())
}
pub fn process_uptime(
rpc_client: &RpcClient,
_config: &CliConfig,
vote_account_pubkey: &Pubkey,
aggregate: bool,
span: Option<u64>,
) -> ProcessResult {
let (_vote_account, vote_state) = get_vote_account(rpc_client, vote_account_pubkey)?;
let epoch_schedule = rpc_client.get_epoch_schedule()?;
println!("validator identity: {}", vote_state.node_pubkey);
println!("authorized voter: {}", vote_state.authorized_voter);
if !vote_state.votes.is_empty() {
println!("uptime:");
let epoch_credits: Vec<(u64, u64, u64)> = if let Some(x) = span {
vote_state
.epoch_credits()
.iter()
.rev()
.take(x as usize)
.cloned()
.collect()
} else {
vote_state.epoch_credits().iter().rev().cloned().collect()
};
if aggregate {
let (total_credits, total_slots, epochs) =
aggregate_epoch_credits(&epoch_credits, &epoch_schedule);
if total_slots > 0 {
let total_uptime = 100_f64 * total_credits as f64 / total_slots as f64;
println!("{:.2}% over {} epochs", total_uptime, epochs);
} else {
println!("Insufficient voting history available");
}
} else {
for (epoch, credits, prev_credits) in epoch_credits {
let credits_earned = credits - prev_credits;
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
let uptime = credits_earned as f64 / slots_in_epoch as f64;
println!("- epoch: {} {:.2}% uptime", epoch, uptime * 100_f64,);
}
}
if let Some(x) = span {
if x > vote_state.epoch_credits().len() as u64 {
println!("(span longer than available epochs)");
}
}
}
Ok("".to_string())
}
#[cfg(test)]
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) {
@ -545,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",
@ -552,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()],
}
);
@ -578,16 +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)
],
}
);
@ -602,16 +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: 0,
commission: 100,
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(keypair)
],
}
);
@ -630,16 +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: 0
commission: 100
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(keypair)
],
}
);
@ -656,16 +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: 0
commission: 100
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(keypair)
],
}
);
@ -677,38 +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
}
);
// Test Uptime Subcommand
let pubkey = Pubkey::new_rand();
let matches = test_commands.clone().get_matches_from(vec![
"test",
"uptime",
&pubkey.to_string(),
"--span",
"4",
"--aggregate",
]);
assert_eq!(
parse_command(&matches).unwrap(),
CliCommandInfo {
command: CliCommand::Uptime {
pubkey,
aggregate: true,
span: Some(4)
},
require_keypair: false
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(read_keypair_file(&keypair_file).unwrap())
],
}
);
}

View File

@ -1,9 +1,9 @@
use serde_json::Value;
use solana_cli::cli::{process_command, CliCommand, CliConfig};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::new_validator_for_tests;
use solana_core::validator::TestValidator;
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,
@ -22,7 +22,13 @@ fn test_cli_deploy_program() {
pathbuf.push("noop");
pathbuf.set_extension("so");
let (server, leader_data, alice, ledger_path) = new_validator_for_tests();
let TestValidator {
server,
leader_data,
alice,
ledger_path,
..
} = TestValidator::run();
let (sender, receiver) = channel();
run_local_faucet(alice, sender, None);
@ -38,13 +44,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,25 +1,15 @@
use solana_cli::cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::TestValidator;
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;
#[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)
}
use std::{fs::remove_dir_all, sync::mpsc::channel, thread::sleep, time::Duration};
fn check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) {
(0..5).for_each(|tries| {
@ -36,30 +26,48 @@ fn check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) {
#[test]
fn test_nonce() {
let (server, leader_data, alice, ledger_path) = new_validator_for_tests();
let TestValidator {
server,
leader_data,
alice,
ledger_path,
..
} = TestValidator::run();
let (sender, receiver) = channel();
run_local_faucet(alice, sender, None);
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());
full_battery_tests(&rpc_client, &faucet_addr, json_rpc_url, None, false);
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();
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
}
#[test]
fn test_nonce_with_seed() {
let TestValidator {
server,
leader_data,
alice,
ledger_path,
..
} = TestValidator::run();
let (sender, receiver) = channel();
run_local_faucet(alice, sender, None);
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());
full_battery_tests(
&rpc_client,
&faucet_addr,
&mut config_payer,
&mut config_nonce,
&keypair_file,
&keypair_file,
json_rpc_url,
Some(String::from("seed")),
false,
);
server.close().unwrap();
@ -68,35 +76,21 @@ fn test_nonce() {
#[test]
fn test_nonce_with_authority() {
let (server, leader_data, alice, ledger_path) = new_validator_for_tests();
let TestValidator {
server,
leader_data,
alice,
ledger_path,
..
} = TestValidator::run();
let (sender, receiver) = channel();
run_local_faucet(alice, sender, None);
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,
&authority_keypair_file,
);
full_battery_tests(&rpc_client, &faucet_addr, json_rpc_url, None, true);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
@ -105,51 +99,92 @@ fn test_nonce_with_authority() {
fn full_battery_tests(
rpc_client: &RpcClient,
faucet_addr: &std::net::SocketAddr,
config_payer: &mut CliConfig,
config_nonce: &mut CliConfig,
nonce_keypair_file: &str,
authority_keypair_file: &str,
json_rpc_url: String,
seed: Option<String>,
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.signers[0].pubkey(),
seed,
&system_program::id(),
)
.unwrap()
} else {
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_authority: read_keypair_file(&authority_keypair_file).unwrap().pubkey(),
nonce_account: 1,
seed,
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_nonce.keypair.pubkey());
check_balance(1000, &rpc_client, &config_payer.signers[0].pubkey());
check_balance(1000, &rpc_client, &nonce_account);
// Get nonce
config_payer.command = CliCommand::GetNonce(config_nonce.keypair.pubkey());
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();
// Get nonce
config_payer.command = CliCommand::GetNonce(config_nonce.keypair.pubkey());
config_payer.command = CliCommand::GetNonce(nonce_account);
let second_nonce_string = process_command(&config_payer).unwrap();
let second_nonce = second_nonce_string.parse::<Hash>().unwrap();
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: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(),
nonce_authority: read_keypair_file(&authority_keypair_file).unwrap().into(),
nonce_account,
nonce_authority: index,
};
process_command(&config_payer).unwrap();
// Get nonce
config_payer.command = CliCommand::GetNonce(config_nonce.keypair.pubkey());
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();
@ -157,64 +192,58 @@ 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: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(),
nonce_authority: read_keypair_file(&authority_keypair_file).unwrap().into(),
nonce_account,
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(900, &rpc_client, &config_nonce.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);
// Show nonce account
config_payer.command = CliCommand::ShowNonceAccount {
nonce_account_pubkey: config_nonce.keypair.pubkey(),
nonce_account_pubkey: nonce_account,
use_lamports_unit: true,
};
process_command(&config_payer).unwrap();
// 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: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(),
nonce_authority: read_keypair_file(&authority_keypair_file).unwrap().into(),
new_authority: read_keypair_file(&new_authority_keypair_file)
.unwrap()
.pubkey(),
nonce_account,
nonce_authority: index,
new_authority: new_authority.pubkey(),
};
process_command(&config_payer).unwrap();
// Old authority fails now
config_payer.command = CliCommand::NewNonce {
nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(),
nonce_authority: read_keypair_file(&authority_keypair_file).unwrap().into(),
nonce_account,
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: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(),
nonce_authority: read_keypair_file(&new_authority_keypair_file)
.unwrap()
.into(),
nonce_account,
nonce_authority: 1,
};
process_command(&config_payer).unwrap();
// New authority can withdraw from nonce account
config_payer.command = CliCommand::WithdrawFromNonceAccount {
nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(),
nonce_authority: read_keypair_file(&new_authority_keypair_file)
.unwrap()
.into(),
nonce_account,
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(800, &rpc_client, &config_nonce.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,17 +1,21 @@
use chrono::prelude::*;
use serde_json::Value;
use solana_cli::cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig};
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},
};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::TestValidator;
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::{hash::Hash, pubkey::Pubkey, signature::KeypairUtil, signature::Signature};
use std::fs::remove_dir_all;
use std::str::FromStr;
use std::sync::mpsc::channel;
#[cfg(test)]
use solana_core::validator::new_validator_for_tests;
use std::thread::sleep;
use std::time::Duration;
use solana_sdk::{
account_utils::StateMut,
fee_calculator::FeeCalculator,
nonce_state::NonceState,
pubkey::Pubkey,
signature::{Keypair, Signer},
};
use std::{fs::remove_dir_all, sync::mpsc::channel, thread::sleep, time::Duration};
fn check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) {
(0..5).for_each(|tries| {
@ -28,7 +32,13 @@ fn check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) {
#[test]
fn test_cli_timestamp_tx() {
let (server, leader_data, alice, ledger_path) = new_validator_for_tests();
let TestValidator {
server,
leader_data,
alice,
ledger_path,
..
} = TestValidator::run();
let bob_pubkey = Pubkey::new_rand();
let (sender, receiver) = channel();
@ -36,32 +46,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();
@ -69,17 +83,13 @@ fn test_cli_timestamp_tx() {
// Make transaction (from config_payer to bob_pubkey) requiring timestamp from config_witness
let date_string = "\"2018-09-19T17:30:59Z\"";
let dt: DateTime<Utc> = serde_json::from_str(&date_string).unwrap();
config_payer.command = CliCommand::Pay {
config_payer.command = CliCommand::Pay(PayCommand {
lamports: 10,
to: bob_pubkey,
timestamp: Some(dt),
timestamp_pubkey: Some(config_witness.keypair.pubkey()),
witnesses: None,
cancelable: false,
sign_only: false,
signers: None,
blockhash: None,
};
timestamp_pubkey: Some(config_witness.signers[0].pubkey()),
..PayCommand::default()
});
let sig_response = process_command(&config_payer);
let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap();
@ -89,7 +99,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
@ -97,7 +107,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
@ -107,7 +117,13 @@ fn test_cli_timestamp_tx() {
#[test]
fn test_cli_witness_tx() {
let (server, leader_data, alice, ledger_path) = new_validator_for_tests();
let TestValidator {
server,
leader_data,
alice,
ledger_path,
..
} = TestValidator::run();
let bob_pubkey = Pubkey::new_rand();
let (sender, receiver) = channel();
@ -115,46 +131,45 @@ 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();
// Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness
config_payer.command = CliCommand::Pay {
config_payer.command = CliCommand::Pay(PayCommand {
lamports: 10,
to: bob_pubkey,
timestamp: None,
timestamp_pubkey: None,
witnesses: Some(vec![config_witness.keypair.pubkey()]),
cancelable: false,
sign_only: false,
signers: None,
blockhash: None,
};
witnesses: Some(vec![config_witness.signers[0].pubkey()]),
..PayCommand::default()
});
let sig_response = process_command(&config_payer);
let object: Value = serde_json::from_str(&sig_response.unwrap()).unwrap();
@ -164,7 +179,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
@ -172,7 +187,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
@ -182,7 +197,13 @@ fn test_cli_witness_tx() {
#[test]
fn test_cli_cancel_tx() {
let (server, leader_data, alice, ledger_path) = new_validator_for_tests();
let TestValidator {
server,
leader_data,
alice,
ledger_path,
..
} = TestValidator::run();
let bob_pubkey = Pubkey::new_rand();
let (sender, receiver) = channel();
@ -190,39 +211,39 @@ 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();
// Make transaction (from config_payer to bob_pubkey) requiring witness signature from config_witness
config_payer.command = CliCommand::Pay {
config_payer.command = CliCommand::Pay(PayCommand {
lamports: 10,
to: bob_pubkey,
timestamp: None,
timestamp_pubkey: None,
witnesses: Some(vec![config_witness.keypair.pubkey()]),
witnesses: Some(vec![config_witness.signers[0].pubkey()]),
cancelable: true,
sign_only: false,
signers: None,
blockhash: None,
};
..PayCommand::default()
});
let sig_response = process_command(&config_payer).unwrap();
let object: Value = serde_json::from_str(&sig_response).unwrap();
@ -232,7 +253,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
@ -240,7 +261,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
@ -250,7 +271,13 @@ fn test_cli_cancel_tx() {
#[test]
fn test_offline_pay_tx() {
let (server, leader_data, alice, ledger_path) = new_validator_for_tests();
let TestValidator {
server,
leader_data,
alice,
ledger_path,
..
} = TestValidator::run();
let bob_pubkey = Pubkey::new_rand();
let (sender, receiver) = channel();
@ -258,22 +285,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();
@ -281,60 +312,131 @@ 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());
config_offline.command = CliCommand::Pay {
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
config_offline.command = CliCommand::Pay(PayCommand {
lamports: 10,
to: bob_pubkey,
timestamp: None,
timestamp_pubkey: None,
witnesses: None,
cancelable: false,
blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()),
sign_only: true,
signers: None,
blockhash: None,
};
..PayCommand::default()
});
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 object: Value = serde_json::from_str(&sig_response).unwrap();
let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
let signer_strings = object.get("signers").unwrap().as_array().unwrap();
let signers: Vec<_> = signer_strings
.iter()
.map(|signer_string| {
let mut signer = signer_string.as_str().unwrap().split('=');
let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
let sig = Signature::from_str(signer.next().unwrap()).unwrap();
(key, sig)
})
.collect();
config_online.command = CliCommand::Pay {
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,
timestamp: None,
timestamp_pubkey: None,
witnesses: None,
cancelable: false,
sign_only: false,
signers: Some(signers),
blockhash: Some(blockhash_str.parse::<Hash>().unwrap()),
};
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();
remove_dir_all(ledger_path).unwrap();
}
#[test]
fn test_nonced_pay_tx() {
solana_logger::setup();
let TestValidator {
server,
leader_data,
alice,
ledger_path,
..
} = TestValidator::run();
let (sender, receiver) = channel();
run_local_faucet(alice, sender, None);
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())
.unwrap();
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config.signers[0].pubkey(),
50 + minimum_nonce_balance,
)
.unwrap();
check_balance(
50 + minimum_nonce_balance,
&rpc_client,
&config.signers[0].pubkey(),
);
// Create nonce account
let nonce_account = Keypair::new();
config.command = CliCommand::CreateNonceAccount {
nonce_account: 1,
seed: None,
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.signers[0].pubkey());
check_balance(minimum_nonce_balance, &rpc_client, &nonce_account.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"),
};
let bob_pubkey = Pubkey::new_rand();
config.signers = vec![&default_signer];
config.command = CliCommand::Pay(PayCommand {
lamports: 10,
to: bob_pubkey,
blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash),
nonce_account: Some(nonce_account.pubkey()),
..PayCommand::default()
});
process_command(&config).expect("failed to process pay command");
check_balance(40, &rpc_client, &config.signers[0].pubkey());
check_balance(10, &rpc_client, &bob_pubkey);
// Verify that nonce has been used
let account = rpc_client.get_account(&nonce_account.pubkey()).unwrap();
let nonce_state: NonceState = account.state().unwrap();
match nonce_state {
NonceState::Initialized(_meta, hash) => assert_ne!(hash, nonce_hash),
_ => assert!(false, "Nonce is not initialized"),
}
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
}

View File

@ -1,14 +1,19 @@
use solana_cli::cli::{process_command, CliCommand, CliConfig};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::new_validator_for_tests;
use solana_core::validator::TestValidator;
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() {
let (server, leader_data, alice, ledger_path) = new_validator_for_tests();
let TestValidator {
server,
leader_data,
alice,
ledger_path,
..
} = TestValidator::run();
let (sender, receiver) = channel();
run_local_faucet(alice, sender, None);
let faucet_addr = receiver.recv().unwrap();
@ -18,9 +23,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 +35,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

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

@ -0,0 +1,216 @@
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_core::validator::{TestValidator, TestValidatorOptions};
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};
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 TestValidator {
server,
leader_data,
alice: mint_keypair,
ledger_path,
..
} = TestValidator::run_with_options(TestValidatorOptions {
fees: 1,
bootstrap_validator_lamports: 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.22.0"
version = "1.0.2"
description = "Solana Client"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -13,17 +13,19 @@ bincode = "1.2.1"
bs58 = "0.3.0"
jsonrpc-core = "14.0.5"
log = "0.4.8"
rand = "0.6.5"
rayon = "1.2.0"
reqwest = { version = "0.9.24", default-features = false, features = ["rustls-tls"] }
reqwest = { version = "0.10.1", default-features = false, features = ["blocking", "rustls-tls"] }
serde = "1.0.104"
serde_derive = "1.0.103"
serde_json = "1.0.44"
solana-net-utils = { path = "../net-utils", version = "0.22.0" }
solana-sdk = { path = "../sdk", version = "0.22.0" }
serde_json = "1.0.46"
solana-net-utils = { path = "../net-utils", version = "1.0.2" }
solana-sdk = { path = "../sdk", version = "1.0.2" }
thiserror = "1.0"
tungstenite = "0.10.1"
url = "2.1.1"
[dev-dependencies]
assert_matches = "1.3.0"
jsonrpc-core = "14.0.5"
jsonrpc-http-server = "14.0.5"
solana-logger = { path = "../logger", version = "0.22.0" }
jsonrpc-http-server = "14.0.6"
solana-logger = { path = "../logger", version = "1.0.2" }

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)
}
}

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