Compare commits

...

388 Commits

Author SHA1 Message Date
0b33162138 Bump version to 1.0.25 2020-05-26 15:11:08 -07:00
a93915f1bd Wait for one slot to be produced 2020-05-26 15:06:34 -07:00
ee05266a09 Adjust v1.0 CRDS version to be compatible with v1.1 2020-05-26 15:06:34 -07:00
8cde8a54ac LedgerCleanupService no longer causes an OOM and actually purges (bp #10199) (#10220)
* LedgerCleanupService no longer causes an OOM and actually purges (#10199)

* cleanup_ledger() now services new_root_receiver while purging
* purge_slots() now fully deletes before compacting
* Add ledger pruning grafana graph

(cherry picked from commit 156387aba4)

* fixup

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-05-25 00:00:59 -07:00
65b5bddf4e v1.0: Test ledger-tool commands in run-sanity.sh (bp #10211) (#10213)
automerge
2020-05-24 09:00:21 -07:00
88199e77ab Enable disk metrics (#10009) 2020-05-22 22:55:39 -07:00
b2b10f4989 Update another non-circulating account 2020-05-22 15:10:56 -07:00
41d3fbcb60 Add another non-circulating account (#10186) (#10189)
automerge

(cherry picked from commit e2b5cd6d47)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-05-22 14:35:36 -07:00
33322d9501 Add circ/non-circ filter to getLargestAccounts (#10188) 2020-05-22 15:14:18 -06:00
ee1f218e76 Fixup deserialize_bs58_transaction, and make a few error types more targeted (#10171) (#10176)
automerge

(cherry picked from commit 12a3b1ba6a)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-05-21 17:50:54 -07:00
b3b32befd7 REST API now returns supply in SOL rather than lamports (#10170) (#10173)
automerge

(cherry picked from commit 18be7a7966)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-05-21 16:53:58 -07:00
95675f8f42 Bump version to 1.0.24 2020-05-21 13:29:16 -07:00
825c0e2b6e Revert "Add AVX2 runtime checks (#10033)" (#10167) (#10168)
This reverts commit cf8eb7700b.

(cherry picked from commit 486168b796)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-05-21 13:19:42 -07:00
4334fef955 Add v0 REST APIs for circulating and total supply (#10102) (#10159)
automerge
2020-05-20 22:14:59 -07:00
2333468350 Rename getCirculatingSuppy to getSupply in JSON API doc (#10121) (#10122)
automerge
2020-05-19 15:41:30 -07:00
86968cb311 Add SimulateTransaction RPC endpoint (bp #10106) (#10115)
automerge
2020-05-19 13:55:07 -07:00
3ce9a16e7f v1.0: Add nonce to shreds repairs, add shred data size to header (#10110)
* Add nonce to shreds/repairs

* Add data shred size to header

* Align with future epoch

Co-authored-by: Carl <carl@solana.com>
2020-05-19 12:34:26 -07:00
3746c0c6ac Update accounts whitelist (#10100) (#10103)
automerge
2020-05-18 14:54:50 -07:00
2b71bf37f9 Make repair metrics less chatty (#9094) (#10080) 2020-05-16 10:46:50 -07:00
3b526cc2de Increase the number of JSON RPC service threads (#10075)
automerge
2020-05-15 15:00:41 -07:00
198f87ffea Abort if the open fd limit cannot be increased (bp #10064) (#10073)
automerge
2020-05-15 14:47:41 -07:00
859d4db87e validator: Forge a confirmed root before halting for RPC inspection (bp #10061) (#10066)
* Forge a confirmed root before halting for RPC inspection (#10061)

(cherry picked from commit 1da1667920)

# Conflicts:
#	core/src/commitment.rs

* Update commitment.rs

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-05-15 14:36:54 -07:00
1163144914 solana-gossip spy can now specify a shred version (#10040) (#10041)
automerge
2020-05-13 21:45:16 -07:00
49f212247a Add AVX2 runtime checks (#10033) (#10034)
automerge
2020-05-13 13:34:27 -07:00
e26840cb09 Introduce type alias Ancestors (bp #9699) (#10019)
automerge
2020-05-13 04:10:18 -07:00
14bbcef722 v1.0: Advertise node version in gossip (bp #9986) (#9995)
automerge
2020-05-12 19:38:52 -07:00
5326f3ec73 Check slot cleaned up for RPC blockstore/slot queries (#9982) (#9988)
automerge
2020-05-11 16:29:03 -07:00
fca4554d3f Bump version to v1.0.23 2020-05-10 21:49:52 -07:00
15de250c2c Rpc: Add getCirculatingSupply endpoint, redux (#9953) (#9954)
automerge
2020-05-09 14:20:58 -07:00
a7b0fcc21e v1.0: Maintain sysvar balances for consistent market cap. (#9937)
* Maintain sysvar balances for consistent market cap.

* Back-port fun and gating adjustments

* Add comment

* Adjust test
2020-05-08 09:19:03 -07:00
4999fa6263 Support ad-hoc genesis args in run.sh (#9697) (#9939)
automerge
2020-05-08 08:16:36 -07:00
1f54be66c9 v1.0: Include account.owner into account hash (#9920)
automerge
2020-05-07 13:42:58 -07:00
2e1c3a8338 Correct method name 2020-05-06 11:28:52 -07:00
fe934eb7a0 Reduce spammy 'ReceiveUpdates took:' log 2020-05-06 08:48:27 -07:00
62a0c2f348 Gossip no longer pushes/pulls from nodes with a different shred version (bp #9868) (#9893)
automerge
2020-05-05 23:47:03 -07:00
cfeef3a9eb Display transaction fee in SOL (#9892) (#9897)
automerge

(cherry picked from commit e078ba1dde)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-05-05 22:44:01 -07:00
52009788ee Rpc: Filter blockstore data by cluster-confirmed root (#9873) (#9880)
automerge
2020-05-04 22:37:00 -07:00
2acf4d874d Rpc: add getLargestAccounts endpoint (#9869) (#9876)
automerge
2020-05-04 19:12:18 -07:00
f951d7d33f Avoid panic caused by converting non-positive / non-normal floating points values to duration (#9867) (#9871)
automerge
2020-05-04 14:40:58 -07:00
20fe24f348 cli: Add clap.rs default for --commitment (bp #9859) (#9860)
automerge
2020-05-02 17:58:31 -07:00
2e5cc41945 Bump version to 1.0.22 2020-05-02 11:31:15 -07:00
bc76b20e6d Fuzzer test and fixes (#9853) (#9857) 2020-05-02 10:00:51 -07:00
3f6befe012 Watchtower can now emit a notification on all non-vote transactions (#9848) 2020-05-01 17:48:18 -07:00
ee201cfb84 Put empty accounts in the accounts list on load (#9842)
Indexing into accounts array does not match account_keys otherwise.
Also enforce program accounts not at index 0
Enforce at least 1 Read-write signing fee-payer account.
2020-05-01 17:24:06 -07:00
66ec12e869 Passing -v/--verbose to solana confirm now displays the full transaction (#9530) (#9843)
automerge

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2020-05-01 13:28:57 -07:00
addfc99ff4 Add delay to keep RPC traffic down on error 2020-05-01 10:40:08 -07:00
91f0faa72d v1.0: incinerator backport (#9837) 2020-05-01 09:02:43 -07:00
2deebe4d73 v1.1 backport custom error rename (bp #9826) (#9838)
automerge
2020-05-01 01:58:15 -07:00
5bc57ea004 Nits for sanitize trait (bp #9741) (#9809)
* thiserror, docs, remove general Failure case (#9741)

automerge

(cherry picked from commit a0514eb2ae)

# Conflicts:
#	core/src/crds_value.rs
#	core/src/epoch_slots.rs
#	sdk/src/sanitize.rs

* rebase

Co-authored-by: anatoly yakovenko <anatoly@solana.com>
Co-authored-by: Michael Vines <mvines@gmail.com>
2020-04-30 14:35:18 -07:00
c63bd05458 Add commitment Root variant, and add fleshed out --commitment arg to Cli (#9806) (#9812)
automerge
2020-04-30 12:49:36 -07:00
b4933f4c74 Upgrade to Rust 1.43.0 (bp #9754) (#9807)
* Upgrade to Rust 1.43.0 (#9754)

(cherry picked from commit 230df0ec0c)

# Conflicts:
#	core/src/validator.rs
#	runtime/src/accounts_db.rs

* Update validator.rs

* Update accounts_db.rs

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-04-30 08:50:03 -07:00
e7748c603b Rpc Client: Prevent error out on get_num_blocks_since_signature_confirmation (#9792) (#9799)
automerge
2020-04-29 16:50:07 -07:00
6a5d782d6c Bump Rust-BPF version to be interoperable with latest Rust (#9783) (#9801)
automerge
2020-04-29 16:32:06 -07:00
89fad8f566 Rpc: remove unwraps (#9793) (#9796)
automerge
2020-04-29 14:44:41 -07:00
01cac89867 Fix BPF tool caching (#9781) (#9794)
automerge
2020-04-29 13:13:00 -07:00
dfb4729b02 Don't divide by zero 2020-04-29 11:03:13 -07:00
6ab5f823b3 Bump version to 1.0.21 2020-04-29 08:57:57 -07:00
bf1ceab6ed catchup now estimates the time remaining (#9782) (#9784)
automerge
2020-04-29 00:31:43 -07:00
dbaff495c8 v1.1: backport commitment max changes (#9775) (#9778)
automerge
2020-04-28 16:42:08 -07:00
f65caa66bf Don't --use-move 2020-04-28 12:47:08 -07:00
2f455e18ef Reorder steps by relative priority for when there aren't enough agents 2020-04-28 12:46:19 -07:00
7b155f384d Disable move more 2020-04-28 12:38:51 -07:00
fd405239d9 Report duration of last alarm in the All Clear message (#9766) (#9770)
automerge

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-04-28 12:38:34 -07:00
8698156e27 Update dalek (v1.0 bp) (#9765)
* Disable Move/Libra components

* Update dalek version

Co-authored-by: Trent Nelson <trent@solana.com>
2020-04-28 12:02:09 -07:00
2f0f218ad9 Use Blockstore lowest_slot to start root iterator (#9738) (#9767)
automerge
2020-04-28 11:11:10 -07:00
3cc75b4bab Set HOME correctly (#9757) (#9761)
automerge
2020-04-28 02:57:49 -07:00
0a0f8470d7 Clean up use to keep rust 1.43.0 from complaining (bp #9740) (#9748)
* Clean up `use` to keep rust 1.43.0 from complaining (#9740)

(cherry picked from commit c11abf88b7)
2020-04-27 23:21:32 -07:00
e46026f1fb Input values are not sanitized after they are deserialized, making it far too easy for Leo to earn SOL (bp #9706) (#9735)
automerge
2020-04-27 19:58:40 -07:00
fef5089d7e Fix broken doc link to anatomy of transaction (#9728) (#9729)
automerge
2020-04-27 01:27:26 -07:00
a844bd70da Update metrics dashboard 2020-04-26 09:54:19 -07:00
cbc01bd1b9 Cargo.lock 2020-04-25 23:08:21 -07:00
e096cc1101 Use vec! 2020-04-25 23:08:09 -07:00
0dc559d3cf Filter program ids v1.0 (#9723)
* Filter program ids to store

* add test
2020-04-25 22:52:39 -07:00
34f537adad Add support for log rotation, sending SIGUSR1 will cause the log file to be re-opened (#9713) (#9715)
(cherry picked from commit 50f1ec0374)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-04-24 15:57:47 -07:00
9558628537 Update to rocksdb 0.14 and set max wal size (#9668) (#9688) (#9708)
Co-authored-by: sakridge <sakridge@gmail.com>
2020-04-24 10:43:01 -07:00
940bf7081a Update solana-user-authorized_keys.sh 2020-04-23 16:32:23 -06:00
38c31c3b4e Exit cleanly on panic! so the process don't limp along in a half-dead state (#9690) (#9692)
automerge
2020-04-23 14:07:12 -07:00
253272d757 Remove stray 'v' (#9679) (#9680)
automerge
2020-04-23 00:51:25 -07:00
9b1bd8065f Bump version to 1.0.20 2020-04-22 22:17:34 -07:00
b2a5cf57ad Remove validator-info publish from net scripts 2020-04-22 18:06:46 -06:00
33c51ee75d Add getLowestNonpurgedBlock rpc; use blockstore api in getConfirmedBlocks (#9656) (#9663)
automerge
2020-04-22 15:17:51 -07:00
d8aa107fae Extend snapshot interval in multinode demo (#9657) (#9660)
automerge
2020-04-22 13:45:01 -07:00
82e02d0734 Relax setting withdraw authority during lockup (#9644) (#9645)
automerge
2020-04-21 22:35:27 -07:00
dae59bb3e1 Flag test_tvu_exit as serial to hopefully reduce CI flakiness (bp #9509) (#9636)
automerge
2020-04-21 17:16:05 -07:00
e0e7fc8e52 Wait for supermajority of cluster to have rooted a transaction to consider it finalized (#9618) (#9626)
automerge
2020-04-21 01:09:26 -07:00
9abc84c583 Move streamer test to integration test (#9050) (#9624)
automerge
2020-04-21 00:50:46 -07:00
db6540772c Check distance for timestamp 2020-04-20 11:36:17 -07:00
840ebfe625 Error for invalid shred. (#9588) (#9596)
automerge
2020-04-19 22:42:48 -07:00
953282dbf4 Budget for gossip traffic (#9550) (#9583)
automerge
2020-04-19 09:43:44 -07:00
788d1199ac Fix local-cluster test - archiver should wait for itself + 1 validator (#9577) (#9584)
automerge
2020-04-19 01:36:14 -07:00
f6a8f718a8 Make rpc_subscriptions.rs tests serial (#9556)
automerge

(cherry picked from commit b58338b066)
2020-04-17 11:40:31 -07:00
f225bf6f01 Make rpc tests serial (#9537)
(cherry picked from commit e655cba5bd)
2020-04-16 22:17:41 -07:00
b5f03a380b Only build x86_64-unknown-linux-gnu on docs.rs 2020-04-16 19:07:43 -07:00
140c75f613 Don't upload tarballs to buildkite to speed up build 2020-04-16 13:55:15 -07:00
6a59f67fdc Write wallet key to explicit file
(cherry picked from commit 93669ab1fc)
2020-04-16 13:41:14 -07:00
5943747001 Bump version to 1.0.19 2020-04-16 12:19:48 -07:00
f26f18d29d Don't unwrap on session new (#9531)
automerge
2020-04-16 10:05:41 -07:00
9b58d72f52 Rpc: Speed up getBlockTime (#9510) (#9513)
automerge
2020-04-16 00:19:50 -07:00
0c885d6a04 Default to RUST_BACKTRACE=1 for more informative validator logs
(cherry picked from commit 4ac15e68cf)
2020-04-15 22:46:24 -07:00
70b51c0a5a Pacify shellcheck 2020-04-15 17:53:23 -07:00
560660ae11 Always run shellcheck 2020-04-15 17:53:03 -07:00
5436855b67 Update build-cli-usage.sh 2020-04-15 17:49:15 -07:00
7351e5ed2c Use $rust_stable
(cherry picked from commit d567799d43)

# Conflicts:
#	docs/build-cli-usage.sh
2020-04-15 17:49:15 -07:00
7ee993fadf RPC: Add health check URI (bp #9499) (#9504)
automerge
2020-04-15 12:31:08 -07:00
9bf459e82f Fix race in multi_bind_in_range (#9493)
(cherry picked from commit ee72714c08)
2020-04-14 17:59:15 -07:00
545090ff17 limit test jobs to 16 to prevent OOM (#9500)
(cherry picked from commit 2b2b2cac1f)
2020-04-14 17:52:05 -07:00
bd8074507f Bump version to v1.0.18 2020-04-14 09:58:33 -07:00
cfc7b22c4c Use type alias 2020-04-13 21:12:44 -07:00
5f1c637508 Conditionally change max_age 2020-04-13 21:12:44 -07:00
d888e0a6d7 Use same max_age regardless of leader/not-leader 2020-04-13 21:12:44 -07:00
10e808e1bd Fail coverage faster in CI 2020-04-13 21:09:26 -07:00
d9b03ca38b Assume json_rpc_url can be upgrade to a websocket if no port is supplied
(cherry picked from commit bcfadd6085)
2020-04-13 20:32:26 -07:00
608d75b348 Unfold coverage test failures
(cherry picked from commit d4ea1ec6ad)
2020-04-13 18:08:25 -07:00
cee3cee4ef Reorder CI jobs to allow for more concurrent PRs
(cherry picked from commit ce027da236)
2020-04-13 13:00:39 -07:00
09e51323f0 Update buildkite-tests.yml
(cherry picked from commit 92a5a51632)
2020-04-13 11:01:00 -07:00
da6f702129 Bump version to 1.0.17 2020-04-11 19:32:42 -07:00
02a83e7c6e Allow lower shred count (#9410) (#9451)
automerge
2020-04-11 14:49:32 -07:00
83263e1737 Calculate account refs fix (#9448) 2020-04-11 12:56:20 -07:00
1f7ac22b60 Don't subject authorizing a new stake authority to lockup (#9434) (#9441)
automerge
2020-04-10 17:25:15 -07:00
747debae56 Cache downloads to speed up CI
(cherry picked from commit b4e00275b2)
2020-04-10 12:25:49 -07:00
00b4186469 Improve coverage.sh usability when used locally (#9054) (#9424)
automerge
2020-04-10 05:59:35 -07:00
b087dabf4f Rpc: Add getConfirmedSignaturesForAddress (#9407) (#9417)
automerge
2020-04-09 21:20:28 -07:00
e00eb0a069 Remove Trust Wallet Beta install instructions (#9396) (#9397)
automerge
2020-04-09 08:52:04 -07:00
d4e49ffd06 Rpc: Add getConfirmedTransaction (#9381) (#9392)
automerge
2020-04-09 01:00:34 -07:00
0f34a190ea Bump version to 1.0.16 2020-04-09 00:05:16 -07:00
df2fb8f5b3 Add --no-wait arg to transfer (#9388) (#9390)
automerge
2020-04-08 23:49:40 -07:00
80d2a6046b Moar vm.max_map_count (#9389)
automerge
2020-04-08 23:18:30 -07:00
24273b826f Add blockstore address-to-signature index (#9367) (#9378)
automerge
2020-04-08 13:55:53 -07:00
68d2616e35 stake-monitor: Add 1 SOL grace, to allow for a complaint system account to fund a reasonable number of transactions. (bp #9359) (#9363)
automerge
2020-04-08 11:56:01 -07:00
f506d39339 Improve ledger-tool/accounts for easier debuging (#9370) (#9371)
automerge
2020-04-08 11:31:41 -07:00
bc58c9ec7e Cache solana-perf.tgz to speed up CI (#9360)
automerge

(cherry picked from commit dc91698b3a)
2020-04-07 13:32:13 -07:00
b57a52cd85 Bump version to 1.0.15 2020-04-07 09:36:47 -07:00
8631be42ac Add support for monitoring system account balances (#9345)
automerge

(cherry picked from commit 03978ac5a5)
2020-04-06 22:57:47 -07:00
09367369ef Reinstate commitment param to support old clients (#9324) (#9329)
automerge
2020-04-06 12:21:19 -07:00
23b8c95cc4 Update getSignatureStatuses to return historical statuses (#9314) (#9321)
automerge
2020-04-06 04:24:19 -07:00
e61392b057 Rework TransactionStatus index in blockstore (#9281) (#9304) (#9312)
automerge

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2020-04-05 12:52:15 -06:00
7deba20395 Deprecate confirmTransaction, getSignatureStatus, and getSignatureConfirmation (bp #9298) (#9308)
automerge
2020-04-05 02:41:10 -07:00
274c097f84 RPC: add err field to TransactionStatus, alongside the now deprecated status field (#9296) (#9307)
automerge
2020-04-05 00:21:15 -07:00
1c7cea1af4 Add stake-monitor 2020-04-03 20:37:28 -07:00
4406496d2f Add log before opening database
(cherry picked from commit b557b3170e)
2020-04-03 15:10:57 -07:00
a15fa4840c Fix repair dos (#9056) 2020-04-03 13:20:39 -07:00
c9030660d6 Bump version to 1.0.14 2020-04-02 22:42:28 -07:00
fdeda769d0 Backport wallet doc changes to v1.0 (#9268)
* Add ledger live screenshots and reduce duplicate instructions (#9258)

automerge

* Add instructions for Trust Wallet Beta for Android (#9261)

automerge
2020-04-02 15:37:26 -06:00
53edd26578 Add instructions for Trust Wallet Beta for Android (#9261) (#9264)
automerge
2020-04-02 12:25:32 -07:00
2433cdd6d6 Set checks timeout to 20 minutes 2020-04-02 13:11:03 -06:00
77b34c278e Do not trigger tests if only docs were modified (#9240) (#9257) 2020-04-02 10:44:51 -06:00
90e993fd9a Add epoch subcommand (#9249) (#9254)
automerge
2020-04-01 22:29:18 -07:00
251054d8c9 Backport confirmations fix. (#9252)
automerge
2020-04-01 19:42:04 -07:00
d4bb7cec69 Undo breaking rpc removal of getSignatureConfirmation (#9247) 2020-04-01 18:04:11 -06:00
a3f6b04345 Add fee-payer option to docs (bp #9230) (#9236)
automerge
2020-04-01 15:11:47 -07:00
ba4a5053dd Undo getSignatureStatus breaking change, add getSignatureStatuses (#9231)
automerge
2020-04-01 12:21:56 -07:00
6b47a259c3 Add a support page for wallet docs (#9229) (#9234)
automerge
2020-04-01 11:22:50 -07:00
c9ec13cf1f Tune udp buffers and vmmap immediately (#9194) (#9216)
automerge
2020-04-01 00:52:50 -07:00
906a6ab837 Fix error with account hash list getting too big for gossip (#9197) (#9214)
automerge
2020-03-31 23:27:18 -07:00
d0e478a9f8 Fix panic (#9195) (#9208)
automerge
2020-03-31 22:03:46 -07:00
b560b64d33 Remove unecessary security exception and add a new one (bp #9200) (#9205)
* Remove unecessary exception and add a new one (#9200)

(cherry picked from commit 62e12e3af5)

# Conflicts:
#	ci/test-checks.sh

* Update test-checks.sh

Co-authored-by: sakridge <sakridge@gmail.com>
Co-authored-by: Michael Vines <mvines@gmail.com>
2020-03-31 19:50:04 -07:00
057af41716 Fix links (#9184) (#9187)
automerge
2020-03-31 09:57:23 -07:00
a44b8abd48 Bump version to v1.0.13 2020-03-30 23:05:41 -07:00
8778ecaed5 Ledger cleanup fixes (#9131) (#9175)
automerge
2020-03-30 20:42:17 -07:00
a02542ada3 Bump version to v1.0.12 2020-03-30 08:45:50 -07:00
ea17c6883f Remove chatty 'setting snapshot root:' info log 2020-03-30 08:44:24 -07:00
706306645b solana account now displays the account's rent epoch 2020-03-30 08:44:24 -07:00
da9e930788 Calculate ref counts earlier to prevent bad clean (#9153) 2020-03-29 14:42:57 -07:00
8b8e066bbe Add RPC subscription api for rooted slots (#9118) (#9126)
automerge
2020-03-27 13:09:32 -07:00
3473350b62 fix links (#9125) (#9128)
automerge
2020-03-27 10:28:23 -07:00
59d7eb5216 Fix links in docs (#9119) (#9127)
automerge
2020-03-27 09:50:28 -07:00
55ba934137 Document transaction field in getConfirmedBlock responses (#9121) (#9124)
automerge
2020-03-27 09:45:30 -07:00
4c3dcb7f7e Exclude all executable accounts from rent collection (#9116) (#9120)
automerge
2020-03-27 09:27:07 -07:00
3a879db8af Fix broken gitbook links (#9107) (#9108)
automerge
2020-03-26 20:33:20 -07:00
d2107270ea Consolidate signature-status rpcs (bp #9069) (#9105)
automerge
2020-03-26 20:30:46 -07:00
007afe22d0 Add docs for app wallets (#9098) (#9103)
automerge
2020-03-26 18:26:23 -07:00
93a1d10e15 Revert setting the default toolchain (#9093) (#9097)
automerge
2020-03-26 15:59:26 -07:00
d57a7c8f21 Restructure wallet docs to prep for app wallet content (#9088) (#9095)
automerge
2020-03-26 14:39:32 -07:00
6db39829c8 Install xargo using CI dictated cargo version if available (#9068) (#9092)
automerge
2020-03-26 14:02:09 -07:00
ec76826493 Unflake rpc subscriptions test by reducing sub count (#9078) (#9082)
automerge
2020-03-25 22:56:52 -07:00
d4ddb6265b Convert Banks (#9033)
* Store and compute needed state in EpochStakes struct
Co-authored-by: Carl <carl@solana.com>
2020-03-25 20:43:48 -07:00
7a8233d7ca Bump version to 1.0.11 2020-03-25 19:17:06 -07:00
95bc051129 Cargo update bumpalo (#9067) (#9077)
automerge
2020-03-25 18:51:38 -07:00
02bcf4f8e2 ledger-tool can now decode stake instructions (bp #9045) (#9076)
automerge
2020-03-25 18:02:31 -07:00
4b0d4e9834 Remove accounts unwrap (#9063)
automerge
2020-03-25 10:59:52 -07:00
bf4cdc091a Fix xargo to version 0.3.19 to avoid unstable feature (#9065) (#9066)
automerge

(cherry picked from commit c558db2a48)

Co-authored-by: Justin Starry <justin@solana.com>
2020-03-25 08:48:22 -07:00
5f2cf2b44d Increase vmap count in sys-tuner (#8940) (#9064)
(cherry picked from commit a70008cc5c)

Co-authored-by: sakridge <sakridge@gmail.com>
2020-03-25 08:06:36 -07:00
03aae5eb5f Ignore RUSTSEC-2020-0006 for the moment (#9057) (#9059)
automerge
2020-03-24 21:06:15 -07:00
5f1ce81fbc ledger tool now outputs transaction status information if available (#9024) (#9026)
automerge
2020-03-24 12:23:25 -07:00
fc582aa57c Refactor how pubsub subscriptions are added (#9042) (#9049)
automerge
2020-03-24 11:05:33 -07:00
13676e9614 Fix timeout for subscriptions test (#9043) (#9044)
automerge
2020-03-24 02:57:25 -07:00
7636a0521f solana-install-init: --pubkey is no longer required on platforms without a default update manifest 2020-03-23 22:40:53 -07:00
ad06354a18 Remove , 2020-03-23 22:11:56 -07:00
953cb93e44 Bump version to 1.0.10 2020-03-23 21:58:45 -07:00
38b2957a8e Remove unused default update manifest pubkeys (#9041)
automerge
2020-03-23 20:57:43 -07:00
9eb39df93f Backport: add slot to signature notification & respect confirmations param (#9036)
automerge
2020-03-23 18:32:05 -07:00
f34ce94347 Remove Ledger-specific analysis of hardware wallets (#9028) (#9030)
automerge
2020-03-23 14:48:58 -07:00
5c6411fc06 Fix link in gitbook (#9027) (#9029)
automerge
2020-03-23 14:48:41 -07:00
3ab428693a Manual v1.0 backports (#9025)
automerge
2020-03-23 13:55:03 -07:00
7ffaf2ad29 Manual v1.0 backports (#9015)
automerge
2020-03-22 22:44:55 -07:00
a5e4a1f2d8 Drop storage lock (#8997) 2020-03-21 18:55:59 -07:00
6ae99848f4 Revert "Move Install Solana doc into the Command-line Guide (#8982)" (#8992) (#8993)
This reverts commit 5fa36bbab3.

(cherry picked from commit 1aab959d4e)

Co-authored-by: Greg Fitzgerald <greg@solana.com>
2020-03-21 14:52:53 -06:00
5342b3a53f Shred fetch comment and debug message tweak (#8980) (#8990)
automerge

(cherry picked from commit 909321928c)

Co-authored-by: sakridge <sakridge@gmail.com>
2020-03-21 09:25:08 -07:00
50a3c98d21 Shred filter (#8975) (#8987)
Thread bank_forks into shred fetch and filter by slot.
2020-03-20 11:33:19 -07:00
ca2b7a2c5b Cargo.lock 2020-03-20 11:05:45 -07:00
e900623817 Update value names in docs (#8983) (#8985)
automerge
2020-03-20 09:36:55 -07:00
a46e953ebb Move Install Solana doc into the Command-line Guide (#8982) (#8984)
automerge
2020-03-20 09:24:56 -07:00
3bbc51a6ae Improve CLI usage messages (#8972) (#8977)
automerge
2020-03-19 21:52:37 -07:00
7ae2464cf3 Fix windows build by removing sys-info (#8860) (#8973)
automerge
2020-03-19 18:14:01 -07:00
58a36ce484 Bump version to 1.0.9 2020-03-19 17:10:35 -07:00
61bd9e6a28 Fix windows binary build on v1.0 (#8968)
Co-authored-by: Michael Vines <mvines@gmail.com>
2020-03-19 15:58:34 -07:00
495ab631d6 Some Cli polish (#8966) (#8970)
automerge
2020-03-19 14:11:52 -07:00
93906847f9 Cli: polish transaction progress bar (#8963) (#8967)
automerge
2020-03-19 12:54:50 -07:00
70f4c4a974 Build windows binaries on v1.0 (#8965) 2020-03-19 11:06:17 -07:00
ad6078da2d CLI: Fix create-nonce-account with seed (#8929) (#8961)
automerge
2020-03-19 10:32:35 -07:00
2a617f2d07 v1.0: Backport of #8939 (#8957)
automerge
2020-03-19 02:08:36 -07:00
6af3c6ecbc CLI: Add multi-session signing support (#8927) (#8953)
* SDK: Add `NullSigner` implementation

* SDK: Split `Transaction::verify()` to gain access to results

* CLI: Minor refactor of --sign_only result parsing

* CLI: Enable paritial signing

Signers specified by pubkey, but without a matching --signer arg
supplied fall back to a `NullSigner` when --sign-only is in effect.
This allows their pubkey to be used for TX construction as usual,
but leaves their `sign_message()` a NOP. As such, with --sign-only
in effect, signing and verification must be done separately, with
the latter's per-signature results considered

* CLI: Surface/report missing/bad signers to user

* CLI: Suppress --sign-only JSON output

* nits

* Docs for multi-session offline signing

(cherry picked from commit 98228c392e)

Co-authored-by: Trent Nelson <trent@solana.com>
2020-03-18 23:39:44 -07:00
a5938e5a21 Bump bv from 0.11.0 to 0.11.1 (bp #8952) (#8955)
automerge
2020-03-18 22:33:05 -07:00
8eaa8d8788 Add docs on wallets and generating keys (bp #8905) (#8946)
automerge
2020-03-18 19:15:34 -07:00
51940eec9b Delete broken link (#8950) (#8951)
automerge
2020-03-18 18:20:36 -07:00
d1e16b2bc4 Remove product string from device keypair URL (#8942) (#8945)
automerge
2020-03-18 14:11:41 -07:00
97051c87b4 Document account/signer requirements for vote instructions (#8941)
automerge
2020-03-18 12:20:02 -07:00
2521913654 Add counter for accounts hash verification. (#8928) (#8934)
automerge
2020-03-18 09:57:38 -07:00
803c87b4bd Add docs for --trusted-validator options (#8911) (#8933)
automerge
2020-03-18 08:37:15 -07:00
8ff9ee3a06 Cli: add spinner progress bar when waiting for transaction confirmation (#8916) (#8920)
automerge
2020-03-17 20:06:01 -07:00
25bf2c6062 Extend local-cluster CI timeout (#8921) (#8923)
automerge
2020-03-17 19:48:57 -07:00
fc80b77fc4 Increase buffer on low SOL fault to over a week (#8903) (#8904)
automerge
2020-03-17 10:32:28 -07:00
5a7707362c Sort device paths for select (#8896) (#8897)
automerge
2020-03-16 19:43:11 -07:00
0102ee3fa9 Hoist USB URL docs (#8894) (#8895)
automerge
2020-03-16 16:31:35 -07:00
d29cb19a73 Cli: enable flexible flexible signer paths for pubkey args (#8892) (#8893)
automerge
2020-03-16 16:16:58 -07:00
1cc66f0cd7 Add Accounts hash consistency halting (#8772) (#8889)
* Accounts hash consistency halting

* Add option to inject account hash faults for testing.

Enable option in local cluster test to see that node halts.
2020-03-16 14:29:44 -07:00
e2cfc513eb Add genesis token counter test to system test (#8824) (#8891)
automerge
2020-03-16 13:32:58 -07:00
8115a962e9 Cleanup CLI types (#8888) (#8890)
automerge
2020-03-16 12:28:22 -07:00
0c804e2ef2 Use types for CLI value names (#8878) (#8886)
automerge
2020-03-16 09:23:15 -07:00
cbe36a7a63 Lower error level of InvalidTickCount (bp #8880) (#8885)
automerge
2020-03-16 08:53:33 -07:00
174825fecf Fix faucet command in run.sh (#8883) (#8884)
automerge
2020-03-16 05:38:31 -07:00
c7e80bebdc Bump version to 1.0.8 2020-03-15 21:50:06 -07:00
57abc370fa Quietly re-introduce legacy --voting-keypair/--identity-keypair args for v1.0.6 compatibility
(cherry picked from commit 49706172f3)
2020-03-15 20:21:23 -07:00
42ab421a87 Rename leader to validator, drop _keypair/-keypair suffix (#8876) (#8877)
automerge
2020-03-15 14:14:32 -07:00
9ab27291f5 Validators now run a full gossip node while looking for a snapshot (#8872)
automerge
2020-03-15 10:29:13 -07:00
27e4e9cb8d Cli: Add resolve-signer subcommand (#8859) (#8870)
automerge
2020-03-14 22:14:24 -07:00
b0cf65dfc8 Refactor system tests dir structure (#8865) (#8869)
automerge
2020-03-14 19:38:19 -07:00
bfc97c682c Apply s/faucet-keypair/faucet renaming to net scripts (#8868)
automerge
2020-03-14 18:14:49 -07:00
231c9b25d1 Rework validator vote account defaults to half voting fees (#8858)
automerge
2020-03-13 21:47:32 -07:00
f536d805ed Rework cluster metrics dashboard to support the modern clusters
(cherry picked from commit 5f5824d78d)

# Conflicts:
#	system-test/automation_utils.sh
2020-03-13 20:18:06 -07:00
cb6e7426a4 Drop :8899 port from http://devnet.solana.com references
(cherry picked from commit 9e0a26628b)
2020-03-13 20:14:02 -07:00
71f391ae04 Enable any signer in various cli subcommands (#8844) (#8856)
automerge
2020-03-13 18:11:05 -07:00
12bf34f059 Remove holding Poh lock (#8838) (#8850)
automerge
2020-03-13 16:56:22 -07:00
a3a14d6b5b Docs: Use correct flag in keypair verification instructions (#8677)
automerge

(cherry picked from commit 542691c4e4)
2020-03-13 16:00:33 -07:00
5e22db017a Surface the missing pubkey
(cherry picked from commit ce88602ced)
2020-03-13 16:00:01 -07:00
2cd09957b4 Cli: add subcommand to withdraw from vote account (#8550) (#8840)
automerge
2020-03-13 15:31:47 -07:00
d1ec6c0b8b Upgrade to Rust 1.42 (#8836) (#8839)
automerge
2020-03-13 14:29:18 -07:00
7722723400 Bump version to 1.0.7 2020-03-13 08:10:09 -07:00
4a42cfc42a Cli error cleanup 1.0 (#8834)
* Don't use move semantics if not needed (#8793)

* SDK: Deboilerplate `TransportError` with thiserror

* Enable interchange between `TransportError` and `ClientError`

* SDK: Retval consistency between `Client` and `AsyncClient` traits

* Client: Introduce/use `Result` type

* Client: Remove unused `RpcResponseIn` type

* Client: Rename `RpcResponse` to more appropriate `RpcResult`

* Client: Death to `io::Result` return types

* Client: Struct-ify `ClientError`

* Client: Add optional `command` parameter to `ClientError`

* RpcClient: Stop abusing `io::Error` (low-fruit)

* ClientError: Use `thiserror`'s `Display` impl

* Extend `RpcError`'s utility

* RpcClient: Stop abusing `io::Error` (the rest)

* CLI: Shim `main()` so we can `Display` format errors

* claputils: format input validator errors with `Display`

They are intended to be displayed to users

* SDK: `thiserror` for hash and sig parse erros

* Keygen: Shim main to format errors with `Display`

* SDK: `thiserror` for `InstructionError`

* CLI: `thiserror` for `CliError`

* CLI: Format user messages with `Display`

* Client: Tweak `Display` for `ClientError`

* RpcClient: Improve messaging when TX cannot be confirmed

* fu death io res retval

* CLI/Keygen - fix shell return value on error

* Tweak `InstructionError` `Display` messages as per review

* Cleanup hackjob return code fix

* Embrace that which you hate most

* Too much...

Co-authored-by: Jack May <jack@solana.com>
2020-03-13 07:42:25 -06:00
976d744b0d Enable conservative out-of-bound snapshot cleaning (#8811) (#8832)
automerge
2020-03-12 23:40:17 -07:00
62de02c8d4 Avoid early clean and bad snapshot by ref-counting (#8724) (#8831)
automerge
2020-03-12 23:09:45 -07:00
2741a5ce3b Move history out of intro (bp #8825) (#8826)
automerge
2020-03-12 18:21:56 -07:00
0c818acd90 Move intro out of README (#8735) (#8827)
automerge

(cherry picked from commit c0fd017906)

Co-authored-by: Greg Fitzgerald <greg@solana.com>
2020-03-12 18:49:51 -06:00
4d04915302 Update source markdown in CI 2020-03-12 14:56:00 -07:00
7576411c59 Don't tell users to install unreleased software versions 2020-03-12 14:55:46 -07:00
ab2bed6e8f Add all of docs/src 2020-03-12 14:45:48 -07:00
f66a5ba579 Update keys (#8821) (#8822)
automerge
2020-03-12 14:15:32 -07:00
9253d786f8 Revert over-zealous versioning text (#8819)
automerge
2020-03-12 09:49:26 -07:00
d1be1ee49e Fix malformed doc link (#8817) (#8818)
automerge
2020-03-12 09:46:26 -07:00
65833eacd8 Update keys (#8814) (#8816)
automerge
2020-03-12 09:39:31 -07:00
921994e588 Restrict which nodes can run stable and coverage (#8807)
automerge
2020-03-11 19:18:10 -07:00
91dfd962e5 Update keys (#8800) (#8806)
automerge
2020-03-11 19:04:46 -07:00
18067dcb55 CLI: Plumb nonce-stored fees (#8750)
automerge

(cherry picked from commit 0422af2aae)
2020-03-11 13:09:40 -07:00
7501e1b0f0 Update keys (#8791)
(cherry picked from commit a0d0d4c0e9)
2020-03-11 13:08:48 -07:00
1bd2c1de20 Notify when validator balance goes below 1 SOL (#8792)
automerge
2020-03-11 11:47:08 -07:00
a9ef20d43f Update keys (#8783) (#8784)
automerge
2020-03-10 21:39:34 -07:00
00a9d66fed Improve install messaging (#8477) (#8785)
automerge
2020-03-10 20:30:39 -07:00
7508ffe703 Add checkmark (#8781) (#8782)
automerge
2020-03-10 18:32:53 -07:00
6c2368bfc9 Permit fee-payer/split-stake accounts to be the same when using --seed
(cherry picked from commit 775ce3a03f)
2020-03-10 16:16:26 -07:00
6bcb797260 Revert to a computed websocket_url value when json_rpc_url is changed
(cherry picked from commit f655372b08)
2020-03-10 16:12:23 -07:00
329945e56f Print approved msg after Ledger interaction (#8771) (#8775)
automerge
2020-03-10 15:06:43 -07:00
1f80346d97 watchtower: Add --monitor-active-stake flag (bp #8765) (#8769)
automerge
2020-03-10 13:27:19 -07:00
38e661d7ba CLI Nonce account access dereplicode (#8743) (#8767)
automerge
2020-03-10 13:15:18 -07:00
592c4efd17 Configure the cluster right after installing it (#8761) (#8762)
automerge
2020-03-10 10:14:55 -07:00
1629745bd3 Move docs to imperative mood (bp #8643) (#8763)
automerge
2020-03-10 09:50:59 -07:00
02e078c22e Fix Gitbook's markdown rendering (#8759) (#8760)
automerge
2020-03-10 08:09:51 -07:00
ea9e7c710a Bump version to 1.0.6 2020-03-09 22:38:57 -07:00
ee5aa0c1a2 Support monitoring multiple validators 2020-03-09 21:07:32 -07:00
HM
55dee2901e watchtower: flag to suppress duplicate notifications (#8549)
* watchtower: send error message as notification

* watchtower: send all clear notification when ok again

* watchtower: add twilio sms notifications

* watchtower: flag to suppress duplicate notifications

* remove trailing space character

* changes as per suggestion on PR

* all changes together

* cargo fmt
2020-03-09 21:07:32 -07:00
2b0824d18b watchtower now uses cli-config/
(cherry picked from commit 74e7da214a)
2020-03-09 20:40:18 -07:00
b0709ea0ac Move cli-config default out of cli/ into cli-config/
(cherry picked from commit 756ba07b16)
2020-03-09 20:40:18 -07:00
81b5499f7a Rename 'url' to 'json_rpc_url'
(cherry picked from commit 5c236fd06c)
2020-03-09 20:40:18 -07:00
c96ce99705 Wait for 80% of the active stake instead of 75% 2020-03-09 20:31:44 -07:00
8dcd2d11e1 Docs: Fix missing CLI usage.md (#8745) (#8749)
automerge
2020-03-09 20:05:24 -07:00
777aae9059 Remove --derivation-path option (#8741) (#8747)
automerge
2020-03-09 19:17:14 -07:00
4dd1340236 Limit waiting-message to single- or last-chunk apdus (#8730) (#8733)
automerge
2020-03-09 15:44:17 -07:00
889b06e1d4 Allow passing of program_id to programs (bp #8639) (#8670)
automerge
2020-03-09 12:36:34 -07:00
f511296ee8 Fix account tests (#8615) (#8729)
automerge
2020-03-09 11:19:14 -07:00
c19eb717b4 Update rust-bpf to include matching cargo (#8598) (#8727)
automerge
2020-03-09 10:42:19 -07:00
dd54369e1b Cli: Fix create-with-seed (#8706) (#8723)
automerge
2020-03-09 00:16:29 -07:00
bb563b4835 Permit --no-untrusted-rpc without any --trusted-validators 2020-03-08 22:34:53 -07:00
d061fadede Add purge function to ledger-tool (#8719) (#8720)
(cherry picked from commit de34187db0)

Co-authored-by: sakridge <sakridge@gmail.com>
2020-03-08 21:38:38 -07:00
577cd2bd3a Remove unnecessary snapshot hash verification (#8711) (#8714)
(cherry picked from commit f992ee3140)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-03-07 14:41:45 -07:00
9d1c8657e2 Groom ledger-tool bounds output (#8710) (#8715)
(cherry picked from commit acb23e8ef0)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-03-07 14:15:50 -07:00
67216ac7f5 Split staker infos (#8683)
automerge
2020-03-06 23:44:06 -08:00
e63b24c39f Remove ask-seed-phrase arg from validator, archiver (#8697) (#8713)
automerge
2020-03-06 23:34:20 -08:00
d963f7afb4 Set withdrawer keys (#8707)
automerge
2020-03-06 21:21:00 -08:00
a07bf4870a Fix Ledger docs (#8705) (#8708)
automerge
2020-03-06 21:03:15 -08:00
8422d4b3fb Disable setLogFilter RPC API by default (#8693) (#8699)
automerge
2020-03-06 18:16:04 -08:00
ff4731cce2 RPC: Add getFeeCalculatorForBlockhash method call (#8687) (#8698)
automerge
2020-03-06 17:25:33 -08:00
659aaafff6 Ledger: return specific error if ledger-app-solana is not running (#8684) (#8695)
automerge
2020-03-06 16:28:53 -08:00
175651c497 Add shred version support to net/ (#8689) (#8694)
automerge
2020-03-06 15:41:16 -08:00
085e773f27 Properly escape current version (#8686) (#8688)
(cherry picked from commit a78a339407)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-03-06 14:37:41 -07:00
5d3140c040 Cargo.lock 2020-03-06 14:25:54 -07:00
d8d7238920 Bump version to 1.0.5 2020-03-06 14:14:53 -07:00
418c3cd4cf Delete Archiver installation docs (#8665) (#8669)
automerge
2020-03-06 12:49:52 -08:00
9f532cb50f Publish initial snapshot hash in gossip on validator startup (#8678) 2020-03-05 23:01:35 -07:00
c35f4927cd Bump version to 1.0.4 2020-03-05 16:10:24 -07:00
2d0f4b5c8c Bump version to 1.0.3 2020-03-05 13:05:12 -07:00
fe59ee61e6 Add find_incomplete_slots (#8654) (#8671)
automerge
2020-03-05 11:52:20 -08:00
bda2acb06d SDK: Allow RecentBlockhashes to hold the entire BlockhashQueue (#8632) (#8672)
automerge

(cherry picked from commit 9d667db634)

Co-authored-by: Grimes <39311140+solana-grimes@users.noreply.github.com>
2020-03-05 12:09:13 -07:00
ac3fe1da02 Fix docs build (#8663) (#8667)
automerge
2020-03-05 10:25:43 -08:00
57813041d2 Remove solana-archiver from release artifacts 2020-03-05 11:02:18 -07:00
7e446da82c Choose a cluster before checking balances (#8666) (#8668)
automerge
2020-03-05 10:00:53 -08:00
d94f5c94a3 Nonce fees 1.0 (#8664)
automerge
2020-03-05 09:41:45 -08:00
4d9aee4794 Always and fully normalize stored 0-lamport accts. (#8657) (#8661)
(cherry picked from commit f146c92e88)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-03-05 09:49:26 -07:00
f32c152bce genesis: Add support for multiple bootstrap validators (#8656) (#8658)
automerge
2020-03-05 00:53:40 -08:00
5caf9110e0 Add --bind-address and --rpc-bind-address validator arguments (#8628) (#8655)
automerge
2020-03-04 22:39:38 -08:00
d1c80143ea Add NextSlotsIterator (#8652) (#8653)
automerge
2020-03-04 21:59:04 -08:00
bb132df121 Add orphan iterator (#8636) (#8651)
automerge
2020-03-04 20:02:46 -08:00
b3af1c7e57 Connect partition flag to validators (#8622) (#8646)
automerge
2020-03-04 18:16:54 -08:00
14cba53338 Install Solana before using it (#8638) (#8644)
automerge
2020-03-04 16:08:25 -08:00
dc8abbe9e3 solana catchup now detects when you try to catchup to yourself (#8635) (#8641)
automerge
2020-03-04 16:00:20 -08:00
dd06001ed8 compute_bank_stats needs to return newly computed ForkStats (#8608) (#8634)
* Fix broken confirmation, add test

(cherry picked from commit f23dc11a86)

Co-authored-by: carllin <wumu727@gmail.com>
2020-03-04 14:28:17 -08:00
298b7de2e2 catchup now supports an optional RPC URL argument for validators with private RPC (#8629) (#8633)
automerge
2020-03-04 12:48:18 -08:00
74cbc6953f Expose executable and rent_epoch in AccountInfo (#8619) (#8631)
automerge
2020-03-04 12:09:14 -08:00
27e5203078 Fix sendTransaction doc (#8625) (#8626)
automerge
2020-03-04 08:53:30 -08:00
73787e162c Keep GenesisConfig binary compatible with v0.23 (#8617) (#8620)
automerge
2020-03-04 02:02:28 -08:00
74ae40be41 Check transaction signatures in entry verify (#8596) (#8614)
automerge
2020-03-03 22:10:35 -08:00
8dd58e9cea Remove accounts hack and correctly restore accounts store counts (#8569) (#8613)
automerge
2020-03-03 22:05:50 -08:00
7d86179c60 Split signature throughput tracking out of FeeCalculator (#8447) (#8610)
automerge
2020-03-03 20:36:52 -08:00
8115cf1360 Docs: Update CLI offline cmds (#8548) (#8558)
automerge
2020-03-03 20:26:28 -08:00
3e5d45053d Cargo.lock 2020-03-03 21:14:00 -07:00
061319f35a Add commitment flag to vote-account and validators commands (#8597) (#8606)
automerge
2020-03-03 17:47:14 -08:00
dc75837500 Use fs::rename which is much faster than move_items (#8579) (#8595)
automerge
2020-03-03 11:23:11 -08:00
dfe26f5275 Add Ledger wallet installation instructions (#8581) (#8592)
automerge
2020-03-03 08:29:12 -08:00
f4385f7ad2 Do periodic inbound cleaning for rooted slots (#8436) (#8583)
automerge
2020-03-02 23:04:57 -08:00
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
544 changed files with 34997 additions and 19083 deletions

View File

@ -7,9 +7,6 @@
"GITHUB_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:Vq2dkGTOzfEpRht0BAGHFp/hDogMvXJe:tFXHg1epVt2mq9hkuc5sRHe+KAnVREi/p8S+IZu67XRyzdiA/nGak1k860FXYuuzuaE0QWekaEc=]",
"INFLUX_DATABASE": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:5KI9WBkXx3R/W4m256mU5MJOE7N8aAT9:Cb8QFELZ9I60t5zhJ9h55Kcs]",
"INFLUX_PASSWORD": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:hQRMpLCrav+OYkNphkeM4hagdVoZv5Iw:AUO76rr6+gF1OLJA8ZLSG8wHKXgYCPNk6gRCV8rBhZBJ4KwDaxpvOhMl7bxxXG6jol7v4aRa/Lk=]",
"INFLUX_USERNAME": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R7BNmQjfeqoGDAFTJu9bYTGHol2NgnYN:Q2tOT/EBcFvhFk+DKLKmVU7tLCpVC3Ui]",
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:Egc2dMrHDU0NcZ71LwGv/V66shUhwYUE:04VoIb8CKy7KYhQ5W4cEW9SDKZltxWBL5Hob106lMBbUOD/yUvKYcG3Ep8JfTMwO3K8zowW5HpU/IdGoilX0XWLiJJ6t+p05WWK0TA16nOEtwrEG+UK8wm3sN+xCO20i4jDhpNpgg3FYFHT5rKTHW8+zaBTNUX/SFxkN67Lm+92IM28CXYE43SU1WV6H99hGFFVpTK5JVM3JuYU1ex/dHRE+xCzTr4MYUB/F+nGoNFW8HUDV/y0e1jxT9to3x0SmnytEEuk+5RUzFuEt9cKNFeNml3fOCi4qL+sfj/Y5pjH9xDiUxsvH/8NL35jbLP244aFHgWcp]",
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_apple_darwin": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:NeOxSoWCvXB9AL4H6OK26l/7bmsKd/oz:Ijfoxtvk2CHlN1ZXHup3Gg/914kbbAkEGWJfvozA8UIe+aUzUObMyTrKkVOeNAH8Q8YH9tNzk7RRnrTcpnzeCCBLlWcVEeruMxHox3mPRzmSeDLxtbzCl9VePlRO3T7jg90K5hW+ZAkd5J/WJNzpAcmr93ts/of3MbvGHSujId/efCTzJEcP6JInnBb8Vrj7TlgKbzUlnqpq1+NjYPSXN3maKa9pKeo2JWxZlGBMoy6QWUUY5GbYEylw9smwh1LJcHZjlaZNMuOl4gNKtaSr38IXQkAXaRUJDPAmPras00YObKzXU8RkTrP4EoP/jx5LPR7f]",
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_pc_windows_msvc": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:7t+56twjW+jR7fpFNNeRFLPd7E4lbmyN:JuviDpkQrfVcNUGRGsa2e/UhvH6tTYyk1s4cHHE5xZH1NByL7Kpqx36VG/+o1AUGEeSQdsBnKgzYdMoFYbO8o50DoRPc86QIEVXCupD6J9avxLFtQgOWgJp+/mCdUVXlqXiFs/vQgS/L4psrcKdF6WHd77BeUr6ll8DjH+9m5FC9Rcai2pXno6VbPpunHQ0oUdYzhFR64+LiRacBaefQ9igZ+nSEWDLqbaZSyfm9viWkijoVFTq8gAgdXXEh7g0QdxVE5T6bPristJhT6jWBhWunPUCDNFFErWIsbRGctepl4pbCWqh2hNTw9btSgVfeY6uGCOsdy9E=]"
"INFLUX_USERNAME": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R7BNmQjfeqoGDAFTJu9bYTGHol2NgnYN:Q2tOT/EBcFvhFk+DKLKmVU7tLCpVC3Ui]"
}
}

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

@ -1,5 +1,6 @@
os:
- osx
- windows
language: rust
rust:

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 1000 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.

6129
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -48,9 +48,12 @@ members = [
"sdk",
"sdk-c",
"scripts",
"stake-monitor",
"sys-tuner",
"transaction-status",
"upload-perf",
"net-utils",
"version",
"vote-signer",
"cli",
"rayon-threadlimit",

View File

@ -9,26 +9,12 @@ Blockchain Rebuilt for Scale
Solana&trade; is a new blockchain architecture built from the ground up for scale. The architecture supports
up to 710 thousand transactions per second on a gigabit network.
Disclaimer
Documentation
===
All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment.
Before you jump into the code, review the documentation [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com).
Introduction
===
It's possible for a centralized database to process 710,000 transactions per second on a standard gigabit network if the transactions are, on average, no more than 176 bytes. A centralized database can also replicate itself and maintain high availability without significantly compromising that transaction rate using the distributed system technique known as Optimistic Concurrency Control [\[H.T.Kung, J.T.Robinson (1981)\]](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.65.4735). At Solana, we're demonstrating that these same theoretical limits apply just as well to blockchain on an adversarial network. The key ingredient? Finding a way to share time when nodes can't trust one-another. Once nodes can trust time, suddenly ~40 years of distributed systems research becomes applicable to blockchain!
> Perhaps the most striking difference between algorithms obtained by our method and ones based upon timeout is that using timeout produces a traditional distributed algorithm in which the processes operate asynchronously, while our method produces a globally synchronous one in which every process does the same thing at (approximately) the same time. Our method seems to contradict the whole purpose of distributed processing, which is to permit different processes to operate independently and perform different functions. However, if a distributed system is really a single system, then the processes must be synchronized in some way. Conceptually, the easiest way to synchronize processes is to get them all to do the same thing at the same time. Therefore, our method is used to implement a kernel that performs the necessary synchronization--for example, making sure that two different processes do not try to modify a file at the same time. Processes might spend only a small fraction of their time executing the synchronizing kernel; the rest of the time, they can operate independently--e.g., accessing different files. This is an approach we have advocated even when fault-tolerance is not required. The method's basic simplicity makes it easier to understand the precise properties of a system, which is crucial if one is to know just how fault-tolerant the system is. [\[L.Lamport (1984)\]](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.71.1078)
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
===
Before you jump into the code, review the online book [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com/book/).
(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
===
@ -121,7 +107,7 @@ $ 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/building-from-source).
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
---
@ -238,3 +224,8 @@ problem is solved by this code?" On the other hand, if a test does fail and you
better way to solve the same problem, a Pull Request with your solution would most certainly be
welcome! Likewise, if rewriting a test can better communicate what code it's protecting, please
send us that patch!
Disclaimer
===
All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment.

View File

@ -138,7 +138,7 @@ 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.
### Update software on devnet.solana.com

View File

@ -1,6 +1,6 @@
[package]
name = "solana-archiver-lib"
version = "1.0.0"
version = "1.0.25"
description = "Solana Archiver Library"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -11,29 +11,32 @@ edition = "2018"
[dependencies]
bincode = "1.2.1"
crossbeam-channel = "0.3"
ed25519-dalek = "=1.0.0-pre.1"
ed25519-dalek = "=1.0.0-pre.3"
log = "0.4.8"
rand = "0.6.5"
rand_chacha = "0.1.1"
solana-client = { path = "../client", version = "1.0.0" }
solana-storage-program = { path = "../programs/storage", version = "1.0.0" }
rand = "0.7.0"
rand_chacha = "0.2.2"
solana-client = { path = "../client", version = "1.0.25" }
solana-storage-program = { path = "../programs/storage", version = "1.0.25" }
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.0" }
solana-chacha = { path = "../chacha", version = "1.0.0" }
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.0" }
solana-ledger = { path = "../ledger", version = "1.0.0" }
solana-logger = { path = "../logger", version = "1.0.0" }
solana-perf = { path = "../perf", version = "1.0.0" }
solana-sdk = { path = "../sdk", version = "1.0.0" }
solana-core = { path = "../core", version = "1.0.0" }
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.0" }
solana-metrics = { path = "../metrics", version = "1.0.0" }
solana-net-utils = { path = "../net-utils", version = "1.0.25" }
solana-chacha = { path = "../chacha", version = "1.0.25" }
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.25" }
solana-ledger = { path = "../ledger", version = "1.0.25" }
solana-logger = { path = "../logger", version = "1.0.25" }
solana-perf = { path = "../perf", version = "1.0.25" }
solana-sdk = { path = "../sdk", version = "1.0.25" }
solana-core = { path = "../core", version = "1.0.25" }
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.25" }
solana-metrics = { path = "../metrics", version = "1.0.25" }
[dev-dependencies]
hex = "0.4.0"
[lib]
name = "solana_archiver_lib"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -1,7 +1,7 @@
use crate::result::ArchiverError;
use crossbeam_channel::unbounded;
use rand::{thread_rng, Rng, SeedableRng};
use rand_chacha::ChaChaRng;
use rand::{thread_rng, Rng};
use rand_chacha::{rand_core::SeedableRng, ChaChaRng};
use solana_archiver_utils::sample_file;
use solana_chacha::chacha::{chacha_cbc_encrypt_ledger, CHACHA_BLOCK_SIZE};
use solana_client::{
@ -13,8 +13,7 @@ use solana_core::{
contact_info::ContactInfo,
gossip_service::GossipService,
packet::{limited_deserialize, PACKET_DATA_SIZE},
repair_service,
repair_service::{RepairService, RepairSlotRange, RepairStrategy},
repair_service::{self, RepairService, RepairSlotRange, RepairStats, RepairStrategy},
serve_repair::ServeRepair,
shred_fetch_stage::ShredFetchStage,
sigverify_stage::{DisabledSigVerifier, SigVerifyStage},
@ -47,7 +46,7 @@ use solana_storage_program::{
};
use std::{
io::{self, ErrorKind},
net::{SocketAddr, UdpSocket},
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
path::{Path, PathBuf},
result,
sync::atomic::{AtomicBool, Ordering},
@ -199,7 +198,7 @@ impl Archiver {
info!("Connecting to the cluster via {:?}", cluster_entrypoint);
let (nodes, _) =
match solana_core::gossip_service::discover_cluster(&cluster_entrypoint.gossip, 1) {
match solana_core::gossip_service::discover_cluster(&cluster_entrypoint.gossip, 2) {
Ok(nodes_and_archivers) => nodes_and_archivers,
Err(e) => {
//shutdown services before exiting
@ -211,12 +210,9 @@ impl Archiver {
let client = solana_core::gossip_service::get_client(&nodes);
info!("Setting up mining account...");
if let Err(e) = Self::setup_mining_account(
&client,
&keypair,
&storage_keypair,
client_commitment.clone(),
) {
if let Err(e) =
Self::setup_mining_account(&client, &keypair, &storage_keypair, client_commitment)
{
//shutdown services before exiting
exit.store(true, Ordering::Relaxed);
gossip_service.join()?;
@ -238,6 +234,7 @@ impl Archiver {
shred_forward_sockets,
repair_socket.clone(),
&shred_fetch_sender,
None,
&exit,
);
let (slot_sender, slot_receiver) = channel();
@ -358,7 +355,7 @@ impl Archiver {
&cluster_info,
archiver_keypair,
storage_keypair,
meta.client_commitment.clone(),
meta.client_commitment,
);
}
exit.store(true, Ordering::Relaxed);
@ -374,7 +371,7 @@ impl Archiver {
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())
client.get_account_with_commitment(&storage_keypair.pubkey(), client_commitment)
{
if let Ok(StorageContract::ArchiverStorage { validations, .. }) = account.state() {
if !validations.is_empty() {
@ -382,8 +379,7 @@ impl Archiver {
&archiver_keypair.pubkey(),
&storage_keypair.pubkey(),
);
let message =
Message::new_with_payer(vec![ix], Some(&archiver_keypair.pubkey()));
let message = Message::new_with_payer(&[ix], Some(&archiver_keypair.pubkey()));
if let Err(e) = client.send_message(&[archiver_keypair.as_ref()], message) {
error!("unable to redeem reward, tx failed: {:?}", e);
} else {
@ -415,7 +411,7 @@ impl Archiver {
slot_sender: Sender<u64>,
) -> Result<WindowService> {
let slots_per_segment =
match Self::get_segment_config(&cluster_info, meta.client_commitment.clone()) {
match Self::get_segment_config(&cluster_info, meta.client_commitment) {
Ok(slots_per_segment) => slots_per_segment,
Err(e) => {
error!("unable to get segment size configuration, exiting...");
@ -581,7 +577,7 @@ impl Archiver {
&keypair.pubkey(),
&Duration::from_millis(100),
&Duration::from_secs(5),
client_commitment.clone(),
client_commitment,
)? == 0
{
return Err(ArchiverError::EmptyStorageAccountBalance);
@ -589,16 +585,15 @@ impl Archiver {
info!("checking storage account keypair...");
// check if the storage account exists
let balance = client
.poll_get_balance_with_commitment(&storage_keypair.pubkey(), client_commitment.clone());
let balance =
client.poll_get_balance_with_commitment(&storage_keypair.pubkey(), client_commitment);
if balance.is_err() || balance.unwrap() == 0 {
let blockhash =
match client.get_recent_blockhash_with_commitment(client_commitment.clone()) {
Ok((blockhash, _)) => blockhash,
Err(e) => {
return Err(ArchiverError::TransportError(e));
}
};
let blockhash = match client.get_recent_blockhash_with_commitment(client_commitment) {
Ok((blockhash, _)) => blockhash,
Err(e) => {
return Err(ArchiverError::TransportError(e));
}
};
let ix = storage_instruction::create_storage_account(
&keypair.pubkey(),
@ -617,6 +612,7 @@ impl Archiver {
ErrorKind::Other,
"setup_mining_account: signature not found",
),
TransportError::Custom(e) => io::Error::new(ErrorKind::Other, e),
})?;
}
Ok(())
@ -631,32 +627,27 @@ impl Archiver {
// No point if we've got no storage account...
let nodes = cluster_info.read().unwrap().tvu_peers();
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(),
);
let storage_balance = client
.poll_get_balance_with_commitment(&storage_keypair.pubkey(), meta.client_commitment);
if storage_balance.is_err() || storage_balance.unwrap() == 0 {
error!("Unable to submit mining proof, no storage account");
return;
}
// ...or no lamports for fees
let balance = client.poll_get_balance_with_commitment(
&archiver_keypair.pubkey(),
meta.client_commitment.clone(),
);
let balance = client
.poll_get_balance_with_commitment(&archiver_keypair.pubkey(), meta.client_commitment);
if balance.is_err() || balance.unwrap() == 0 {
error!("Unable to submit mining proof, insufficient Archiver Account balance");
return;
}
let blockhash =
match client.get_recent_blockhash_with_commitment(meta.client_commitment.clone()) {
Ok((blockhash, _)) => blockhash,
Err(_) => {
error!("unable to get recent blockhash, can't submit proof");
return;
}
};
let blockhash = match client.get_recent_blockhash_with_commitment(meta.client_commitment) {
Ok((blockhash, _)) => blockhash,
Err(_) => {
error!("unable to get recent blockhash, can't submit proof");
return;
}
};
let instruction = storage_instruction::mining_proof(
&storage_keypair.pubkey(),
meta.sha_state,
@ -664,7 +655,7 @@ impl Archiver {
Signature::new(&meta.signature.as_ref()),
meta.blockhash,
);
let message = Message::new_with_payer(vec![instruction], Some(&archiver_keypair.pubkey()));
let message = Message::new_with_payer(&[instruction], Some(&archiver_keypair.pubkey()));
let mut transaction = Transaction::new(
&[archiver_keypair.as_ref(), storage_keypair.as_ref()],
message,
@ -813,14 +804,15 @@ impl Archiver {
blockstore: &Arc<Blockstore>,
slots_per_segment: u64,
) -> Result<u64> {
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
// Create a client which downloads from the archiver and see that it
// can respond with shreds.
let start_slot = Self::get_archiver_segment_slot(archiver_info.storage_addr);
let start_slot = Self::get_archiver_segment_slot(ip_addr, archiver_info.storage_addr);
info!("Archiver download: start at {}", start_slot);
let exit = Arc::new(AtomicBool::new(false));
let (s_reader, r_reader) = channel();
let repair_socket = Arc::new(bind_in_range(VALIDATOR_PORT_RANGE).unwrap().1);
let repair_socket = Arc::new(bind_in_range(ip_addr, VALIDATOR_PORT_RANGE).unwrap().1);
let t_receiver = receiver(
repair_socket.clone(),
&exit,
@ -846,13 +838,14 @@ impl Archiver {
repair_service::MAX_REPAIR_LENGTH,
&repair_slot_range,
);
let mut repair_stats = RepairStats::default();
//iter over the repairs and send them
if let Ok(repairs) = repairs {
let reqs: Vec<_> = repairs
.into_iter()
.filter_map(|repair_request| {
serve_repair
.map_repair_request(&repair_request)
.map_repair_request(&repair_request, &mut repair_stats, Some(0))
.map(|result| ((archiver_info.gossip, result), repair_request))
.ok()
})
@ -917,8 +910,8 @@ impl Archiver {
true
}
fn get_archiver_segment_slot(to: SocketAddr) -> u64 {
let (_port, socket) = bind_in_range(VALIDATOR_PORT_RANGE).unwrap();
fn get_archiver_segment_slot(bind_ip_addr: IpAddr, to: SocketAddr) -> u64 {
let (_port, socket) = bind_in_range(bind_ip_addr, VALIDATOR_PORT_RANGE).unwrap();
socket
.set_read_timeout(Some(Duration::from_secs(5)))
.unwrap();

View File

@ -1,4 +1,3 @@
use serde_json;
use solana_client::client_error;
use solana_ledger::blockstore;
use solana_sdk::transport;

View File

@ -1,6 +1,6 @@
[package]
name = "solana-archiver-utils"
version = "1.0.0"
version = "1.0.25"
description = "Solana Archiver Utils"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -10,16 +10,19 @@ edition = "2018"
[dependencies]
log = "0.4.8"
rand = "0.6.5"
solana-chacha = { path = "../chacha", version = "1.0.0" }
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.0" }
solana-ledger = { path = "../ledger", version = "1.0.0" }
solana-logger = { path = "../logger", version = "1.0.0" }
solana-perf = { path = "../perf", version = "1.0.0" }
solana-sdk = { path = "../sdk", version = "1.0.0" }
rand = "0.7.0"
solana-chacha = { path = "../chacha", version = "1.0.25" }
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.25" }
solana-ledger = { path = "../ledger", version = "1.0.25" }
solana-logger = { path = "../logger", version = "1.0.25" }
solana-perf = { path = "../perf", version = "1.0.25" }
solana-sdk = { path = "../sdk", version = "1.0.25" }
[dev-dependencies]
hex = "0.4.0"
[lib]
name = "solana_archiver_utils"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-archiver"
version = "1.0.0"
version = "1.0.25"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -10,11 +10,14 @@ homepage = "https://solana.com/"
[dependencies]
clap = "2.33.0"
console = "0.9.2"
solana-clap-utils = { path = "../clap-utils", version = "1.0.0" }
solana-core = { path = "../core", version = "1.0.0" }
solana-logger = { path = "../logger", version = "1.0.0" }
solana-metrics = { path = "../metrics", version = "1.0.0" }
solana-archiver-lib = { path = "../archiver-lib", version = "1.0.0" }
solana-net-utils = { path = "../net-utils", version = "1.0.0" }
solana-sdk = { path = "../sdk", version = "1.0.0" }
solana-clap-utils = { path = "../clap-utils", version = "1.0.25" }
solana-core = { path = "../core", version = "1.0.25" }
solana-logger = { path = "../logger", version = "1.0.25" }
solana-metrics = { path = "../metrics", version = "1.0.25" }
solana-archiver-lib = { path = "../archiver-lib", version = "1.0.25" }
solana-net-utils = { path = "../net-utils", version = "1.0.25" }
solana-sdk = { path = "../sdk", version = "1.0.25" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -2,18 +2,22 @@ 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::{
self, keypair_input, KeypairWithSource, ASK_SEED_PHRASE_ARG,
SKIP_SEED_PHRASE_VALIDATION_ARG,
},
input_parsers::keypair_of, input_validators::is_keypair_or_ask_keyword,
keypair::SKIP_SEED_PHRASE_VALIDATION_ARG,
};
use solana_core::{
cluster_info::{Node, VALIDATOR_PORT_RANGE},
contact_info::ContactInfo,
};
use solana_sdk::{commitment_config::CommitmentConfig, signature::Signer};
use std::{net::SocketAddr, path::PathBuf, process::exit, sync::Arc};
use solana_sdk::{
commitment_config::CommitmentConfig,
signature::{Keypair, Signer},
};
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf,
sync::Arc,
};
fn main() {
solana_logger::setup();
@ -24,10 +28,10 @@ fn main() {
.arg(
Arg::with_name("identity_keypair")
.short("i")
.long("identity-keypair")
.long("identity")
.value_name("PATH")
.takes_value(true)
.validator(is_keypair)
.validator(is_keypair_or_ask_keyword)
.help("File containing an identity (keypair)"),
)
.arg(
@ -55,48 +59,27 @@ fn main() {
.long("storage-keypair")
.value_name("PATH")
.takes_value(true)
.validator(is_keypair)
.validator(is_keypair_or_ask_keyword)
.help("File containing the storage account keypair"),
)
.arg(
Arg::with_name(ASK_SEED_PHRASE_ARG.name)
.long(ASK_SEED_PHRASE_ARG.long)
.value_name("KEYPAIR NAME")
.multiple(true)
.takes_value(true)
.possible_values(&["identity-keypair", "storage-keypair"])
.help(ASK_SEED_PHRASE_ARG.help),
)
.arg(
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
.requires(ASK_SEED_PHRASE_ARG.name)
.help(SKIP_SEED_PHRASE_VALIDATION_ARG.help),
)
.get_matches();
let ledger_path = PathBuf::from(matches.value_of("ledger").unwrap());
let identity_keypair = keypair_input(&matches, "identity_keypair")
.unwrap_or_else(|err| {
eprintln!("Identity keypair input failed: {}", err);
exit(1);
})
.keypair;
let KeypairWithSource {
keypair: storage_keypair,
source: storage_keypair_source,
} = keypair_input(&matches, "storage_keypair").unwrap_or_else(|err| {
eprintln!("Storage keypair input failed: {}", err);
exit(1);
});
if storage_keypair_source == keypair::Source::Generated {
let identity_keypair = keypair_of(&matches, "identity_keypair").unwrap_or_else(Keypair::new);
let storage_keypair = keypair_of(&matches, "storage_keypair").unwrap_or_else(|| {
clap::Error::with_description(
"The `storage-keypair` argument was not found",
clap::ErrorKind::ArgumentNotFound,
)
.exit();
}
});
let entrypoint_addr = matches
.value_of("entrypoint")
@ -116,6 +99,7 @@ fn main() {
&identity_keypair.pubkey(),
&gossip_addr,
VALIDATOR_PORT_RANGE,
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
);
println!(

View File

@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-banking-bench"
version = "1.0.0"
version = "1.0.25"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -10,11 +10,14 @@ homepage = "https://solana.com/"
[dependencies]
log = "0.4.6"
rayon = "1.2.0"
solana-core = { path = "../core", version = "1.0.0" }
solana-ledger = { path = "../ledger", version = "1.0.0" }
solana-logger = { path = "../logger", version = "1.0.0" }
solana-runtime = { path = "../runtime", version = "1.0.0" }
solana-measure = { path = "../measure", version = "1.0.0" }
solana-sdk = { path = "../sdk", version = "1.0.0" }
rand = "0.6.5"
solana-core = { path = "../core", version = "1.0.25" }
solana-ledger = { path = "../ledger", version = "1.0.25" }
solana-logger = { path = "../logger", version = "1.0.25" }
solana-runtime = { path = "../runtime", version = "1.0.25" }
solana-measure = { path = "../measure", version = "1.0.25" }
solana-sdk = { path = "../sdk", version = "1.0.25" }
rand = "0.7.0"
crossbeam-channel = "0.3"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -246,7 +246,7 @@ fn main() {
poh_recorder.lock().unwrap().set_bank(&bank);
assert!(poh_recorder.lock().unwrap().bank().is_some());
if bank.slot() > 32 {
bank_forks.set_root(root, &None);
bank_forks.set_root(root, &None, None);
root += 1;
}
debug!(

View File

@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-exchange"
version = "1.0.0"
version = "1.0.25"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -14,21 +14,24 @@ itertools = "0.8.2"
log = "0.4.8"
num-derive = "0.3"
num-traits = "0.2"
rand = "0.6.5"
rand = "0.7.0"
rayon = "1.2.0"
serde_json = "1.0.46"
serde_yaml = "0.8.11"
solana-clap-utils = { path = "../clap-utils", version = "1.0.0" }
solana-core = { path = "../core", version = "1.0.0" }
solana-genesis = { path = "../genesis", version = "1.0.0" }
solana-client = { path = "../client", version = "1.0.0" }
solana-faucet = { path = "../faucet", version = "1.0.0" }
solana-exchange-program = { path = "../programs/exchange", version = "1.0.0" }
solana-logger = { path = "../logger", version = "1.0.0" }
solana-metrics = { path = "../metrics", version = "1.0.0" }
solana-net-utils = { path = "../net-utils", version = "1.0.0" }
solana-runtime = { path = "../runtime", version = "1.0.0" }
solana-sdk = { path = "../sdk", version = "1.0.0" }
solana-clap-utils = { path = "../clap-utils", version = "1.0.25" }
solana-core = { path = "../core", version = "1.0.25" }
solana-genesis = { path = "../genesis", version = "1.0.25" }
solana-client = { path = "../client", version = "1.0.25" }
solana-faucet = { path = "../faucet", version = "1.0.25" }
solana-exchange-program = { path = "../programs/exchange", version = "1.0.25" }
solana-logger = { path = "../logger", version = "1.0.25" }
solana-metrics = { path = "../metrics", version = "1.0.25" }
solana-net-utils = { path = "../net-utils", version = "1.0.25" }
solana-runtime = { path = "../runtime", version = "1.0.25" }
solana-sdk = { path = "../sdk", version = "1.0.25" }
[dev-dependencies]
solana-local-cluster = { path = "../local-cluster", version = "1.0.0" }
solana-local-cluster = { path = "../local-cluster", version = "1.0.25" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -2,14 +2,17 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-streamer"
version = "1.0.0"
version = "1.0.25"
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 = "1.0.0" }
solana-core = { path = "../core", version = "1.0.0" }
solana-logger = { path = "../logger", version = "1.0.0" }
solana-net-utils = { path = "../net-utils", version = "1.0.0" }
solana-clap-utils = { path = "../clap-utils", version = "1.0.25" }
solana-core = { path = "../core", version = "1.0.25" }
solana-logger = { path = "../logger", version = "1.0.25" }
solana-net-utils = { path = "../net-utils", version = "1.0.25" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -67,7 +67,8 @@ fn main() -> Result<()> {
}
let mut port = 0;
let mut addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0);
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
let mut addr = SocketAddr::new(ip_addr, 0);
let exit = Arc::new(AtomicBool::new(false));
@ -75,7 +76,7 @@ fn main() -> Result<()> {
let mut read_threads = Vec::new();
let recycler = PacketsRecycler::default();
for _ in 0..num_sockets {
let read = solana_net_utils::bind_to(port, false).unwrap();
let read = solana_net_utils::bind_to(ip_addr, port, false).unwrap();
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
addr = read.local_addr().unwrap();

View File

@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-tps"
version = "1.0.0"
version = "1.0.25"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -14,24 +14,27 @@ log = "0.4.8"
rayon = "1.2.0"
serde_json = "1.0.46"
serde_yaml = "0.8.11"
solana-clap-utils = { path = "../clap-utils", version = "1.0.0" }
solana-core = { path = "../core", version = "1.0.0" }
solana-genesis = { path = "../genesis", version = "1.0.0" }
solana-client = { path = "../client", version = "1.0.0" }
solana-faucet = { path = "../faucet", version = "1.0.0" }
solana-librapay = { path = "../programs/librapay", version = "1.0.0", optional = true }
solana-logger = { path = "../logger", version = "1.0.0" }
solana-metrics = { path = "../metrics", version = "1.0.0" }
solana-measure = { path = "../measure", version = "1.0.0" }
solana-net-utils = { path = "../net-utils", version = "1.0.0" }
solana-runtime = { path = "../runtime", version = "1.0.0" }
solana-sdk = { path = "../sdk", version = "1.0.0" }
solana-move-loader-program = { path = "../programs/move_loader", version = "1.0.0", optional = true }
solana-clap-utils = { path = "../clap-utils", version = "1.0.25" }
solana-core = { path = "../core", version = "1.0.25" }
solana-genesis = { path = "../genesis", version = "1.0.25" }
solana-client = { path = "../client", version = "1.0.25" }
solana-faucet = { path = "../faucet", version = "1.0.25" }
#solana-librapay = { path = "../programs/librapay", version = "1.0.20", optional = true }
solana-logger = { path = "../logger", version = "1.0.25" }
solana-metrics = { path = "../metrics", version = "1.0.25" }
solana-measure = { path = "../measure", version = "1.0.25" }
solana-net-utils = { path = "../net-utils", version = "1.0.25" }
solana-runtime = { path = "../runtime", version = "1.0.25" }
solana-sdk = { path = "../sdk", version = "1.0.25" }
#solana-move-loader-program = { path = "../programs/move_loader", version = "1.0.20", optional = true }
[dev-dependencies]
serial_test = "0.3.2"
serial_test_derive = "0.4.0"
solana-local-cluster = { path = "../local-cluster", version = "1.0.0" }
solana-local-cluster = { path = "../local-cluster", version = "1.0.25" }
[features]
move = ["solana-librapay", "solana-move-loader-program"]
#[features]
#move = ["solana-librapay", "solana-move-loader-program"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -1059,8 +1059,8 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
// 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 fee_rate_governor = client.get_fee_rate_governor().unwrap();
let max_fee = fee_rate_governor.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;
@ -1134,7 +1134,7 @@ mod tests {
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::client::SyncClient;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::fee_calculator::FeeRateGovernor;
use solana_sdk::genesis_config::create_genesis_config;
#[test]
@ -1181,8 +1181,8 @@ mod tests {
#[test]
fn test_bench_tps_fund_keys_with_fees() {
let (mut genesis_config, id) = create_genesis_config(10_000);
let fee_calculator = FeeCalculator::new(11, 0);
genesis_config.fee_calculator = fee_calculator;
let fee_rate_governor = FeeRateGovernor::new(11, 0);
genesis_config.fee_rate_governor = fee_rate_governor;
let bank = Bank::new(&genesis_config);
let client = Arc::new(BankClient::new(bank));
let keypair_count = 20;

View File

@ -1,6 +1,6 @@
use clap::{crate_description, crate_name, App, Arg, ArgMatches};
use solana_faucet::faucet::FAUCET_PORT;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::fee_calculator::FeeRateGovernor;
use solana_sdk::signature::{read_keypair_file, Keypair};
use std::{net::SocketAddr, process::exit, time::Duration};
@ -43,7 +43,7 @@ impl Default for Config {
client_ids_and_stake_file: String::new(),
write_to_client_file: false,
read_from_client_file: false,
target_lamports_per_signature: FeeCalculator::default().target_lamports_per_signature,
target_lamports_per_signature: FeeRateGovernor::default().target_lamports_per_signature,
multi_client: true,
use_move: false,
num_lamports_per_account: NUM_LAMPORTS_PER_ACCOUNT_DEFAULT,

View File

@ -3,7 +3,7 @@ use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs, generate
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::fee_calculator::FeeRateGovernor;
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::system_program;
use std::{collections::HashMap, fs::File, io::prelude::*, path::Path, process::exit, sync::Arc};
@ -41,7 +41,7 @@ fn main() {
let (keypairs, _) = generate_keypairs(&id, keypair_count as u64);
let num_accounts = keypairs.len() as u64;
let max_fee =
FeeCalculator::new(*target_lamports_per_signature, 0).max_lamports_per_signature;
FeeRateGovernor::new(*target_lamports_per_signature, 0).max_lamports_per_signature;
let num_lamports_per_account = (num_accounts - 1 + NUM_SIGNATURES_FOR_TXS * max_fee)
/ num_accounts
+ num_lamports_per_account;

View File

@ -1,5 +0,0 @@
# Using Solana from the Command-line
This chapter describes the command-line tools for interacting with Solana. One
could use these tools to send payments, stake validators, and check account
balances.

View File

@ -1,11 +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](../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,37 +0,0 @@
# Introduction
## What is Solana?
Solana is an open source project implementing a new, high-performance, permissionless blockchain. Solana is also the name of a company headquartered in San Francisco that maintains the open source project.
## About this Book
This book describes the Solana open source project, a blockchain built from the ground up for scale. The book covers why Solana is useful, how to use it, how it works, and why it will continue to work long after the company Solana closes its doors. The goal of the Solana architecture is to demonstrate there exists a set of software algorithms that when used in combination to implement a blockchain, removes software as a performance bottleneck, allowing transaction throughput to scale proportionally with network bandwidth. The architecture goes on to satisfy all three desirable properties of a proper blockchain: it is scalable, secure and decentralized.
The architecture describes a theoretical upper bound of 710 thousand transactions per second \(tps\) on a standard gigabit network and 28.4 million tps on 40 gigabit. Furthermore, the architecture supports safe, concurrent execution of programs authored in general purpose programming languages such as C or Rust.
## Disclaimer
All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore, nothing in this project constitutes a solicitation for investment.
## History of the Solana Codebase
In November of 2017, Anatoly Yakovenko published a whitepaper describing Proof of History, a technique for keeping time between computers that do not trust one another. From Anatoly's previous experience designing distributed systems at Qualcomm, Mesosphere and Dropbox, he knew that a reliable clock makes network synchronization very simple. When synchronization is simple the resulting network can be blazing fast, bound only by network bandwidth.
Anatoly watched as blockchain systems without clocks, such as Bitcoin and Ethereum, struggled to scale beyond 15 transactions per second worldwide when centralized payment systems such as Visa required peaks of 65,000 tps. Without a clock, it was clear they'd never graduate to being the global payment system or global supercomputer most had dreamed them to be. When Anatoly solved the problem of getting computers that dont trust each other to agree on time, he knew he had the key to bring 40 years of distributed systems research to the world of blockchain. The resulting cluster wouldn't be just 10 times faster, or a 100 times, or a 1,000 times, but 10,000 times faster, right out of the gate!
Anatoly's implementation began in a private codebase and was implemented in the C programming language. Greg Fitzgerald, who had previously worked with Anatoly at semiconductor giant Qualcomm Incorporated, encouraged him to reimplement the project in the Rust programming language. Greg had worked on the LLVM compiler infrastructure, which underlies both the Clang C/C++ compiler as well as the Rust compiler. Greg claimed that the language's safety guarantees would improve software productivity and that its lack of a garbage collector would allow programs to perform as well as those written in C. Anatoly gave it a shot and just two weeks later, had migrated his entire codebase to Rust. Sold. With plans to weave all the world's transactions together on a single, scalable blockchain, Anatoly called the project Loom.
On February 13th of 2018, Greg began prototyping the first open source implementation of Anatoly's whitepaper. The project was published to GitHub under the name Silk in the loomprotocol organization. On February 28th, Greg made his first release, demonstrating 10 thousand signed transactions could be verified and processed in just over half a second. Shortly after, another former Qualcomm cohort, Stephen Akridge, demonstrated throughput could be massively improved by offloading signature verification to graphics processors. Anatoly recruited Greg, Stephen and three others to co-found a company, then called Loom.
Around the same time, Ethereum-based project Loom Network sprung up and many people were confused about whether they were the same project. The Loom team decided it would rebrand. They chose the name Solana, a nod to a small beach town North of San Diego called Solana Beach, where Anatoly, Greg and Stephen lived and surfed for three years when they worked for Qualcomm. On March 28th, the team created the Solana Labs GitHub organization and renamed Greg's prototype Silk to Solana.
In June of 2018, the team scaled up the technology to run on cloud-based networks and on July 19th, published a 50-node, permissioned, public testnet consistently supporting bursts of 250,000 transactions per second. In a later release in December, called v0.10 Pillbox, the team published a permissioned testnet running 150 nodes on a gigabit network and demonstrated soak tests processing an _average_ of 200 thousand transactions per second with bursts over 500 thousand. The project was also extended to support on-chain programs written in the C programming language and run concurrently in a safe execution environment called BPF.
## What is a Solana Cluster?
A cluster is a set of computers that work together and can be viewed from the outside as a single system. A Solana cluster is a set of independently owned computers working together \(and sometimes against each other\) to verify the output of untrusted, user-submitted programs. A Solana cluster can be utilized any time a user wants to preserve an immutable record of events in time or programmatic interpretations of those events. One use is to track which of the computers did meaningful work to keep the cluster running. Another use might be to track the possession of real-world assets. In each case, the cluster produces a record of events called the ledger. It will be preserved for the lifetime of the cluster. As long as someone somewhere in the world maintains a copy of the ledger, the output of its programs \(which may contain a record of who possesses what\) will forever be reproducible, independent of the organization that launched it.
## What are SOLs?
A SOL is the name of Solana's native token, which can be passed to nodes in a Solana cluster in exchange for running an on-chain program or validating its output. The system may perform micropayments of fractional SOLs, which are called _lamports_. They are named in honor of Solana's biggest technical influence, [Leslie Lamport](https://en.wikipedia.org/wiki/Leslie_Lamport). A lamport has a value of 0.000000001 SOL.

View File

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

View File

@ -1,156 +0,0 @@
# Running an Archiver
This document describes how to setup an archiver in the testnet
Please note some of the information and instructions described here may change in future releases.
## Overview
Archivers are specialized light clients. They download a part of the ledger \(a.k.a Segment\) and store it. They earn rewards for storing segments.
The testnet features a validator running at devnet.solana.com, which serves as the entrypoint to the cluster for your archiver node.
Additionally there is a blockexplorer available at [http://devnet.solana.com/](http://devnet.solana.com/).
The testnet is configured to reset the ledger daily, or sooner should the hourly automated cluster sanity test fail.
## Machine Requirements
Archivers don't need specialized hardware. Anything with more than 128GB of disk space will be able to participate in the cluster as an archiver node.
Currently the disk space requirements are very low but we expect them to change in the future.
Prebuilt binaries are available for Linux x86\_64 \(Ubuntu 18.04 recommended\), macOS, and Windows.
### Confirm The Testnet Is Reachable
Before starting an archiver node, sanity check that the cluster is accessible to your machine by running some simple commands. If any of the commands fail, please retry 5-10 minutes later to confirm the testnet is not just restarting itself before debugging further.
Fetch the current transaction count over JSON RPC:
```bash
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://devnet.solana.com:8899
```
Inspect the blockexplorer at [http://devnet.solana.com/](http://devnet.solana.com/) for activity.
View the [metrics dashboard](https://metrics.solana.com:3000/d/testnet-beta/testnet-monitor-beta?var-testnet=testnet) for more detail on cluster activity.
## Archiver Setup
#### Obtaining The Software
#### Bootstrap with `solana-install`
The `solana-install` tool can be used to easily install and upgrade the cluster software.
#### Linux and mac OS
```bash
curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh -s
```
Alternatively build the `solana-install` program from source and run the following command to obtain the same result:
```bash
solana-install init
```
#### Windows
Download and install **solana-install-init** from [https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest)
After a successful install, `solana-install update` may be used to easily update the software to a newer version at any time.
#### Download Prebuilt Binaries
If you would rather not use `solana-install` to manage the install, you can manually download and install the binaries.
#### Linux
Download the binaries by navigating to [https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest), download **solana-release-x86\_64-unknown-linux-gnu.tar.bz2**, then extract the archive:
```bash
tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
cd solana-release/
export PATH=$PWD/bin:$PATH
```
#### mac OS
Download the binaries by navigating to [https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest), download **solana-release-x86\_64-apple-darwin.tar.bz2**, then extract the archive:
```bash
tar jxf solana-release-x86_64-apple-darwin.tar.bz2
cd solana-release/
export PATH=$PWD/bin:$PATH
```
#### Windows
Download the binaries by navigating to [https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest), download **solana-release-x86\_64-pc-windows-msvc.tar.bz2**, then extract it into a folder. It is a good idea to add this extracted folder to your windows PATH.
## Starting The Archiver
Try running following command to join the gossip network and view all the other nodes in the cluster:
```bash
solana-gossip spy --entrypoint devnet.solana.com:8001
# Press ^C to exit
```
Now configure the keypairs for your archiver by running:
Navigate to the solana install location and open a cmd prompt
```bash
solana-keygen new -o archiver-keypair.json
solana-keygen new -o storage-keypair.json
```
Use solana-keygen to show the public keys for each of the keypairs, they will be needed in the next step:
* Windows
```bash
# The archiver's identity
solana-keygen pubkey archiver-keypair.json
solana-keygen pubkey storage-keypair.json
```
* Linux and mac OS
\`\`\`bash
export ARCHIVER\_IDENTITY=$\(solana-keygen pubkey archiver-keypair.json\)
export STORAGE\_IDENTITY=$\(solana-keygen pubkey storage-keypair.json\)
```text
Then set up the storage accounts for your archiver by running:
```bash
solana --keypair archiver-keypair.json airdrop .0001
solana --keypair archiver-keypair.json create-archiver-storage-account $ARCHIVER_IDENTITY $STORAGE_IDENTITY
```
Note: Every time the testnet restarts, run the steps to setup the archiver accounts again.
To start the archiver:
```bash
solana-archiver --entrypoint devnet.solana.com:8001 --identity-keypair archiver-keypair.json --storage-keypair storage-keypair.json --ledger archiver-ledger
```
## Verify Archiver Setup
From another console, confirm the IP address and **identity pubkey** of your archiver is visible in the gossip network by running:
```bash
solana-gossip spy --entrypoint devnet.solana.com:8001
```
Provide the **storage account pubkey** to the `solana storage-account` command to view the recent mining activity from your archiver:
```bash
solana --keypair storage-keypair.json storage-account $STORAGE_IDENTITY
```

View File

@ -1,39 +0,0 @@
# Choosing a Testnet
Solana maintains several testnets, each featuring a Solana-owned validator
that serves as an entrypoint to the cluster.
Current testnet entrypoints:
* Stable: devnet.solana.com
Application developers should target the Stable testnet. Key differences
between the Stable testnet and what will be mainnet:
* Stable testnet tokens are not real
* Stable testnet includes a token faucet for application testing
* Stable testnet may be subject to ledger resets
* Stable testnet typically runs a newer software version than mainnet
* Stable testnet may be maintained by different validators than mainnet
The Beta testnet is used to showcase and stabilize new features before they
are tagged for release. Application developers are free to target the Beta
testnet, but should expect instability and periodic ledger resets. Regarding
stability, all that can be said is that CI automation was successful.
### Get Testnet Version
You can submit a JSON-RPC request to see the specific software version of the
cluster. Use this to specify [the software version to install](validator-software.md).
```bash
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' devnet.solana.com:8899
```
Example result:
`{"jsonrpc":"2.0","result":{"solana-core":"0.21.0"},"id":1}`
## Using a Different Testnet
This guide is written in the context of devnet.solana.com, our most stable
cluster. To participate in another testnet, modify the commands in the following
pages, replacing `devnet.solana.com` with your desired testnet.

View File

@ -1,6 +1,6 @@
[package]
name = "solana-chacha-cuda"
version = "1.0.0"
version = "1.0.25"
description = "Solana Chacha Cuda APIs"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -10,15 +10,18 @@ edition = "2018"
[dependencies]
log = "0.4.8"
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.0" }
solana-chacha = { path = "../chacha", version = "1.0.0" }
solana-ledger = { path = "../ledger", version = "1.0.0" }
solana-logger = { path = "../logger", version = "1.0.0" }
solana-perf = { path = "../perf", version = "1.0.0" }
solana-sdk = { path = "../sdk", version = "1.0.0" }
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.25" }
solana-chacha = { path = "../chacha", version = "1.0.25" }
solana-ledger = { path = "../ledger", version = "1.0.25" }
solana-logger = { path = "../logger", version = "1.0.25" }
solana-perf = { path = "../perf", version = "1.0.25" }
solana-sdk = { path = "../sdk", version = "1.0.25" }
[dev-dependencies]
hex-literal = "0.2.1"
[lib]
name = "solana_chacha_cuda"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -1,6 +1,6 @@
[package]
name = "solana-chacha-sys"
version = "1.0.0"
version = "1.0.25"
description = "Solana chacha-sys"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -10,3 +10,6 @@ edition = "2018"
[build-dependencies]
cc = "1.0.49"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -1,6 +1,6 @@
[package]
name = "solana-chacha"
version = "1.0.0"
version = "1.0.25"
description = "Solana Chacha APIs"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -10,16 +10,19 @@ 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.0" }
solana-ledger = { path = "../ledger", version = "1.0.0" }
solana-logger = { path = "../logger", version = "1.0.0" }
solana-perf = { path = "../perf", version = "1.0.0" }
solana-sdk = { path = "../sdk", version = "1.0.0" }
rand = "0.7.0"
rand_chacha = "0.2.2"
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.25" }
solana-ledger = { path = "../ledger", version = "1.0.25" }
solana-logger = { path = "../logger", version = "1.0.25" }
solana-perf = { path = "../perf", version = "1.0.25" }
solana-sdk = { path = "../sdk", version = "1.0.25" }
[dev-dependencies]
hex-literal = "0.2.1"
[lib]
name = "solana_chacha"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

8
ci/_
View File

@ -5,7 +5,13 @@
# |source| me
#
base_dir=$(realpath --strip "$(dirname "$0")/..")
_() {
echo "--- $*"
if [[ $(pwd) = $base_dir ]]; then
echo "--- $*"
else
echo "--- $* (wd: $(pwd))"
fi
"$@"
}

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

@ -2,19 +2,23 @@
# Build steps that run after the primary pipeline on pushes and tags.
# Pull requests to not run these steps.
steps:
- command: "ci/publish-tarball.sh"
timeout_in_minutes: 60
name: "publish tarball"
- command: "ci/publish-docs.sh"
timeout_in_minutes: 15
name: "publish docs"
- command: "ci/publish-bpf-sdk.sh"
timeout_in_minutes: 5
name: "publish bpf sdk"
- wait
- command: "sdk/docker-solana/build.sh"
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"
timeout_in_minutes: 5
name: "publish bpf sdk"
- command: "ci/publish-tarball.sh"
timeout_in_minutes: 60
name: "publish tarball"
- command: "ci/publish-book.sh"
timeout_in_minutes: 15
name: "publish book"
# - command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-move.sh"
# name: "move"
# timeout_in_minutes: 20

26
ci/buildkite-tests.yml Normal file
View File

@ -0,0 +1,26 @@
# These steps are conditionally triggered by ci/buildkite.yml when files
# other than those in docs/ are modified
steps:
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
name: "coverage"
timeout_in_minutes: 30
- wait
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-stable.sh"
name: "stable"
timeout_in_minutes: 60
artifact_paths: "log-*.txt"
- wait
- command: "ci/test-stable-perf.sh"
name: "stable-perf"
timeout_in_minutes: 40
artifact_paths: "log-*.txt"
agents:
- "queue=cuda"
- command: "ci/test-bench.sh"
name: "bench"
timeout_in_minutes: 30
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-local-cluster.sh"
name: "local-cluster"
timeout_in_minutes: 45
artifact_paths: "log-*.txt"

View File

@ -1,38 +1,25 @@
# Build steps that run on pushes and pull requests.
# If files other than those in docs/ were modified, this will be followed up by
# ci/buildkite-tests.yml
#
# Release tags use buildkite-release.yml instead
steps:
- command: "ci/shellcheck.sh"
name: "shellcheck"
timeout_in_minutes: 5
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-checks.sh"
name: "checks"
timeout_in_minutes: 20
- command: "ci/shellcheck.sh"
name: "shellcheck"
timeout_in_minutes: 5
- wait
- command: "ci/test-stable-perf.sh"
name: "stable-perf"
timeout_in_minutes: 40
artifact_paths: "log-*.txt"
agents:
- "queue=cuda"
- command: "ci/test-bench.sh"
name: "bench"
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: 60
artifact_paths: "log-*.txt"
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-move.sh"
name: "move"
timeout_in_minutes: 20
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-local-cluster.sh"
name: "local-cluster"
timeout_in_minutes: 30
artifact_paths: "log-*.txt"
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
name: "coverage"
timeout_in_minutes: 30
- command: "ci/maybe-trigger-tests.sh"
name: "maybe-trigger-tests"
timeout_in_minutes: 2
- wait
- trigger: "solana-secondary"
branches: "!pull/*"
async: true

View File

@ -49,7 +49,7 @@ else
# ~/.cargo
ARGS+=(--volume "$PWD:/home")
fi
ARGS+=(--env "CARGO_HOME=/home/.cargo")
ARGS+=(--env "HOME=/home" --env "CARGO_HOME=/home/.cargo")
# kcov tries to set the personality of the binary which docker
# doesn't allow by default.
@ -67,6 +67,7 @@ ARGS+=(
--env BUILDKITE_JOB_ID
--env CI
--env CI_BRANCH
--env CI_TAG
--env CI_BUILD_ID
--env CI_COMMIT
--env CI_JOB_ID

View File

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

View File

@ -15,6 +15,8 @@ To update the pinned version:
1. Run `ci/docker-rust-nightly/build.sh` to rebuild the nightly image locally,
or potentially `ci/docker-rust-nightly/build.sh YYYY-MM-DD` if there's a
specific YYYY-MM-DD that is desired (default is today's build).
Check https://rust-lang.github.io/rustup-components-history/ for build
status
1. Update `ci/rust-version.sh` to reflect the new nightly `YYY-MM-DD`
1. Run `SOLANA_DOCKER_RUN_NOSETUID=1 ci/docker-run.sh --nopull solanalabs/rust-nightly:YYYY-MM-DD ci/test-coverage.sh`
to confirm the new nightly image builds. Fix any issues as needed

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.41.0
FROM rust:1.43.0
# Add Google Protocol Buffers for Libra's metrics library.
ENV PROTOC_VERSION 3.8.0

View File

@ -178,7 +178,7 @@ startNodes() {
(
set -x
$solana_cli --keypair config/bootstrap-validator/identity-keypair.json \
$solana_cli --keypair config/bootstrap-validator/identity.json \
--url http://127.0.0.1:8899 genesis-hash
) | tee genesis-hash.log
maybeExpectedGenesisHash="--expected-genesis-hash $(tail -n1 genesis-hash.log)"

21
ci/maybe-trigger-tests.sh Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
annotate() {
${BUILDKITE:-false} && {
buildkite-agent annotate "$@"
}
}
# Skip if only the docs have been modified
ci/affects-files.sh \
\!^docs/ \
|| {
annotate --style info \
"Skipping all further tests as only docs/ files were modified"
exit 0
}
annotate --style info "Triggering tests"
buildkite-agent pipeline upload ci/buildkite-tests.yml

View File

@ -11,10 +11,10 @@ if [[ -n $CI_BRANCH ]]; then
set -x
(
. ci/rust-version.sh stable
ci/docker-run.sh "$rust_stable_docker_image" make -Cbook -B svg
ci/docker-run.sh "$rust_stable_docker_image" make -C docs
)
# make a local commit for the svgs
git add -A -f book/src/.gitbook/assets/.
# make a local commit for the svgs and generated/updated markdown
git add -f docs/src
if ! git diff-index --quiet HEAD; then
git config user.email maintainers@solana.com
git config user.name "$me"

View File

@ -45,7 +45,7 @@ linux)
TARGET=x86_64-unknown-linux-gnu
;;
windows)
TARGET=x86_64-pc-windows-msvc
TARGET=x86_64-pc-windows-gnu
;;
*)
echo CI_OS_NAME unset
@ -71,16 +71,7 @@ echo --- Creating release tarball
export CHANNEL
source ci/rust-version.sh stable
scripts/cargo-install-all.sh +"$rust_stable" --use-move solana-release
# Reduce the Windows archive size until
# https://github.com/appveyor/ci/issues/2997 is fixed
if [[ -n $APPVEYOR ]]; then
rm -f \
solana-release/bin/solana-validator.exe \
solana-release/bin/solana-bench-exchange.exe \
fi
scripts/cargo-install-all.sh +"$rust_stable" solana-release
tar cvf solana-release-$TARGET.tar solana-release
bzip2 solana-release-$TARGET.tar
@ -104,9 +95,8 @@ fi
source ci/upload-ci-artifact.sh
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
upload-ci-artifact "$file"
echo "Skipped $file due to DO_NOT_PUBLISH_TAR"
continue
fi

View File

@ -2,8 +2,10 @@
set -e
cd "$(dirname "$0")/.."
# shellcheck source=multinode-demo/common.sh
source multinode-demo/common.sh
rm -f config/run/init-completed
rm -rf config/run/init-completed config/ledger config/snapshot-ledger
timeout 15 ./run.sh &
pid=$!
@ -17,6 +19,14 @@ while [[ ! -f config/run/init-completed ]]; do
fi
done
while [[ $($solana_cli slot --commitment recent) -eq 0 ]]; do
sleep 1
done
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"validatorExit"}' http://localhost:8899
wait $pid
$solana_ledger_tool create-snapshot --ledger config/ledger 1 config/snapshot-ledger
cp config/ledger/genesis.tar.bz2 config/snapshot-ledger
tar -C config/snapshot-ledger -xvf config/snapshot-ledger/genesis.tar.bz2
$solana_ledger_tool verify --ledger config/snapshot-ledger

View File

@ -1,28 +1,30 @@
#
# This file maintains the rust versions for use by CI.
#
# Build with stable rust, updating the stable toolchain if necessary:
# $ source ci/rust-version.sh stable
# $ cargo +"$rust_stable" build
#
# Build with nightly rust, updating the nightly toolchain if necessary:
# $ source ci/rust-version.sh nightly
# $ cargo +"$rust_nightly" build
#
# Obtain the environment variables without any automatic toolchain updating:
# $ source ci/rust-version.sh
#
# Obtain the environment variables updating both stable and nightly, only stable, or
# only nightly:
# $ source ci/rust-version.sh all
# $ source ci/rust-version.sh stable
# $ source ci/rust-version.sh nightly
# Then to build with either stable or nightly:
# $ cargo +"$rust_stable" build
# $ cargo +"$rust_nightly" build
#
if [[ -n $RUST_STABLE_VERSION ]]; then
stable_version="$RUST_STABLE_VERSION"
else
stable_version=1.41.0
stable_version=1.43.0
fi
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
nightly_version="$RUST_NIGHTLY_VERSION"
else
nightly_version=2020-02-06
nightly_version=2020-04-23
fi
@ -51,6 +53,10 @@ export rust_nightly_docker_image=solanalabs/rust-nightly:"$nightly_version"
nightly)
rustup_install "$rust_nightly"
;;
all)
rustup_install "$rust_stable"
rustup_install "$rust_nightly"
;;
*)
echo "Note: ignoring unknown argument: $1"
;;

View File

@ -25,7 +25,7 @@ source ci/_
source ci/upload-ci-artifact.sh
eval "$(ci/channel-info.sh)"
source ci/rust-version.sh nightly
source ci/rust-version.sh all
set -o pipefail
export RUST_BACKTRACE=1

View File

@ -22,10 +22,10 @@ _ 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-2020-0002
_ cargo +"$rust_stable" audit --ignore RUSTSEC-2020-0002 --ignore RUSTSEC-2020-0008
_ ci/nits.sh
_ ci/order-crates-for-publishing.py
_ book/build.sh
_ docs/build.sh
_ ci/check-ssh-keys.sh
{

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
}
@ -38,11 +38,16 @@ test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
# Clear the BPF sysroot files, they are not automatically rebuilt
rm -rf target/xargo # Issue #3105
# Limit compiler jobs to reduce memory usage
# on machines with 1gb/thread of memory
NPROC=$(nproc)
NPROC=$((NPROC>16 ? 16 : NPROC))
echo "Executing $testName"
case $testName in
test-stable)
_ cargo +"$rust_stable" test --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
_ cargo +"$rust_stable" test --manifest-path bench-tps/Cargo.toml --features=move ${V:+--verbose} test_bench_tps_local_cluster_move -- --nocapture
_ cargo +"$rust_stable" test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
#_ cargo +"$rust_stable" test --manifest-path bench-tps/Cargo.toml --features=move ${V:+--verbose} test_bench_tps_local_cluster_move -- --nocapture
;;
test-stable-perf)
ci/affects-files.sh \

View File

@ -1,6 +1,6 @@
[package]
name = "solana-clap-utils"
version = "1.0.0"
version = "1.0.25"
description = "Solana utilities for the clap"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -11,11 +11,15 @@ edition = "2018"
[dependencies]
clap = "2.33.0"
rpassword = "4.0"
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.0" }
solana-sdk = { path = "../sdk", version = "1.0.0" }
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.25" }
solana-sdk = { path = "../sdk", version = "1.0.25" }
thiserror = "1.0.11"
tiny-bip39 = "0.7.0"
url = "2.1.0"
chrono = "0.4"
[lib]
name = "solana_clap_utils"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -0,0 +1,18 @@
use crate::ArgConstant;
use clap::Arg;
pub const COMMITMENT_ARG: ArgConstant<'static> = ArgConstant {
name: "commitment",
long: "commitment",
help: "Return information at the selected commitment level",
};
pub fn commitment_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(COMMITMENT_ARG.name)
.long(COMMITMENT_ARG.long)
.takes_value(true)
.possible_values(&["recent", "root", "max"])
.default_value("recent")
.value_name("COMMITMENT_LEVEL")
.help(COMMITMENT_ARG.help)
}

View File

@ -1,16 +1,18 @@
use crate::keypair::{
keypair_from_seed_phrase, keypair_util_from_path, ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG,
keypair_from_seed_phrase, pubkey_from_path, resolve_signer_from_path, signer_from_path,
ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG,
};
use chrono::DateTime;
use clap::ArgMatches;
use solana_remote_wallet::remote_wallet::DerivationPath;
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
clock::UnixTimestamp,
commitment_config::CommitmentConfig,
native_token::sol_to_lamports,
pubkey::Pubkey,
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>>
@ -96,28 +98,77 @@ pub fn pubkeys_sigs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<(Pubk
}
// Return a signer from matches at `name`
#[allow(clippy::type_complexity)]
pub fn signer_of(
name: &str,
matches: &ArgMatches<'_>,
) -> Result<Option<Box<dyn Signer>>, Box<dyn std::error::Error>> {
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) {
keypair_util_from_path(matches, location, name).map(Some)
let signer = signer_from_path(matches, location, name, wallet_manager)?;
let signer_pubkey = signer.pubkey();
Ok((Some(signer), Some(signer_pubkey)))
} else {
Ok((None, None))
}
}
pub fn pubkey_of_signer(
matches: &ArgMatches<'_>,
name: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<Option<Pubkey>, Box<dyn std::error::Error>> {
if let Some(location) = matches.value_of(name) {
Ok(Some(pubkey_from_path(
matches,
location,
name,
wallet_manager,
)?))
} else {
Ok(None)
}
}
pub fn pubkeys_of_multiple_signers(
matches: &ArgMatches<'_>,
name: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<Option<Vec<Pubkey>>, Box<dyn std::error::Error>> {
if let Some(pubkey_matches) = matches.values_of(name) {
let mut pubkeys: Vec<Pubkey> = vec![];
for signer in pubkey_matches {
pubkeys.push(pubkey_from_path(matches, signer, name, wallet_manager)?);
}
Ok(Some(pubkeys))
} else {
Ok(None)
}
}
pub fn resolve_signer(
matches: &ArgMatches<'_>,
name: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<Option<String>, Box<dyn std::error::Error>> {
Ok(resolve_signer_from_path(
matches,
matches.value_of(name).unwrap(),
name,
wallet_manager,
)?)
}
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().unwrap().parse::<u16>().unwrap();
let change = parts.next().map(|change| change.parse::<u16>().unwrap());
DerivationPath { account, change }
pub fn commitment_of(matches: &ArgMatches<'_>, name: &str) -> Option<CommitmentConfig> {
matches.value_of(name).map(|value| match value {
"max" => CommitmentConfig::max(),
"recent" => CommitmentConfig::recent(),
"root" => CommitmentConfig::root(),
_ => CommitmentConfig::default(),
})
}
@ -295,40 +346,4 @@ mod tests {
.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", "2/3"]);
assert_eq!(
derivation_of(&matches, "single"),
Some(DerivationPath {
account: 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: 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: 2,
change: Some(3)
})
);
}
}

View File

@ -1,7 +1,7 @@
use crate::keypair::{parse_keypair_path, KeypairUrl, ASK_KEYWORD};
use chrono::DateTime;
use solana_remote_wallet::remote_keypair::generate_remote_keypair;
use solana_sdk::{
clock::Slot,
hash::Hash,
pubkey::Pubkey,
signature::{read_keypair_file, Signature},
@ -12,7 +12,7 @@ use std::str::FromStr;
pub fn is_pubkey(string: String) -> Result<(), String> {
match string.parse::<Pubkey>() {
Ok(_) => Ok(()),
Err(err) => Err(format!("{:?}", err)),
Err(err) => Err(format!("{}", err)),
}
}
@ -20,7 +20,7 @@ pub fn is_pubkey(string: String) -> Result<(), String> {
pub fn is_hash(string: String) -> Result<(), String> {
match string.parse::<Hash>() {
Ok(_) => Ok(()),
Err(err) => Err(format!("{:?}", err)),
Err(err) => Err(format!("{}", err)),
}
}
@ -28,7 +28,7 @@ pub fn is_hash(string: String) -> Result<(), String> {
pub fn is_keypair(string: String) -> Result<(), String> {
read_keypair_file(&string)
.map(|_| ())
.map_err(|err| format!("{:?}", err))
.map_err(|err| format!("{}", err))
}
// Return an error if a keypair file cannot be parsed
@ -38,7 +38,7 @@ pub fn is_keypair_or_ask_keyword(string: String) -> Result<(), String> {
}
read_keypair_file(&string)
.map(|_| ())
.map_err(|err| format!("{:?}", err))
.map_err(|err| format!("{}", err))
}
// Return an error if string cannot be parsed as pubkey string or keypair file location
@ -46,21 +46,27 @@ 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> {
// Return an error if string cannot be parsed as a pubkey string, or a valid Signer that can
// produce a pubkey()
pub fn is_valid_pubkey(string: String) -> Result<(), String> {
match parse_keypair_path(&string) {
KeypairUrl::Usb(path) => generate_remote_keypair(path, None)
.map(|_| ())
.map_err(|err| format!("{:?}", err)),
KeypairUrl::Filepath(path) => is_keypair(path),
_ => Ok(()),
}
}
// Return an error if string cannot be parsed as a valid Signer. This is an alias of
// `is_valid_pubkey`, and does accept pubkey strings, even though a Pubkey is not by itself
// sufficient to sign a transaction.
//
// In the current offline-signing implementation, a pubkey is the valid input for a signer field
// when paired with an offline `--signer` argument to provide a Presigner (pubkey + signature).
// Clap validators can't check multiple fields at once, so the verification that a `--signer` is
// also provided and correct happens in parsing, not in validation.
pub fn is_valid_signer(string: String) -> Result<(), String> {
is_valid_pubkey(string)
}
// Return an error if string cannot be parsed as pubkey=signature string
pub fn is_pubkey_sig(string: String) -> Result<(), String> {
let mut signer = string.split('=');
@ -76,10 +82,10 @@ pub fn is_pubkey_sig(string: String) -> Result<(), String> {
.ok_or_else(|| "Malformed signer string".to_string())?,
) {
Ok(_) => Ok(()),
Err(err) => Err(format!("{:?}", err)),
Err(err) => Err(format!("{}", err)),
}
}
Err(err) => Err(format!("{:?}", err)),
Err(err) => Err(format!("{}", err)),
}
}
@ -93,14 +99,20 @@ pub fn is_url(string: String) -> Result<(), String> {
Err("no host provided".to_string())
}
}
Err(err) => Err(format!("{:?}", err)),
Err(err) => Err(format!("{}", err)),
}
}
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> {
port.parse::<u16>()
.map(|_| ())
.map_err(|e| format!("{:?}", e))
.map_err(|e| format!("{}", e))
}
pub fn is_valid_percentage(percentage: String) -> Result<(), String> {
@ -108,7 +120,7 @@ pub fn is_valid_percentage(percentage: String) -> Result<(), String> {
.parse::<u8>()
.map_err(|e| {
format!(
"Unable to parse input percentage, provided: {}, err: {:?}",
"Unable to parse input percentage, provided: {}, err: {}",
percentage, e
)
})
@ -138,7 +150,7 @@ 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))
.map_err(|e| format!("{}", e))
}
pub fn is_derivation(value: String) -> Result<(), String> {
@ -146,18 +158,18 @@ pub fn is_derivation(value: String) -> Result<(), String> {
let mut parts = value.split('/');
let account = parts.next().unwrap();
account
.parse::<u16>()
.parse::<u32>()
.map_err(|e| {
format!(
"Unable to parse derivation, provided: {}, err: {:?}",
"Unable to parse derivation, provided: {}, err: {}",
account, e
)
})
.and_then(|_| {
if let Some(change) = parts.next() {
change.parse::<u16>().map_err(|e| {
change.parse::<u32>().map_err(|e| {
format!(
"Unable to parse derivation, provided: {}, err: {:?}",
"Unable to parse derivation, provided: {}, err: {}",
change, e
)
})
@ -176,11 +188,12 @@ mod tests {
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("65537".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/65537".to_string()).is_err());
assert!(is_derivation("0/4294967296".to_string()).is_err());
}
}

View File

@ -1,17 +1,20 @@
use crate::{
input_parsers::{derivation_of, pubkeys_sigs_of},
offline::SIGNER_ARG,
input_parsers::pubkeys_sigs_of,
offline::{SIGNER_ARG, SIGN_ONLY_ARG},
ArgConstant,
};
use bip39::{Language, Mnemonic, Seed};
use clap::{values_t, ArgMatches, Error, ErrorKind};
use clap::ArgMatches;
use rpassword::prompt_password_stderr;
use solana_remote_wallet::remote_keypair::generate_remote_keypair;
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,
read_keypair_file, Keypair, Presigner, Signature, Signer,
read_keypair_file, Keypair, NullSigner, Presigner, Signature, Signer,
},
};
use std::{
@ -19,6 +22,7 @@ use std::{
io::{stdin, stdout, Write},
process::exit,
str::FromStr,
sync::Arc,
};
pub enum KeypairUrl {
@ -35,7 +39,7 @@ pub fn parse_keypair_path(path: &str) -> KeypairUrl {
} else if path == ASK_KEYWORD {
KeypairUrl::Ask
} else if path.starts_with("usb://") {
KeypairUrl::Usb(path.split_at(6).1.to_string())
KeypairUrl::Usb(path.to_string())
} else if let Ok(pubkey) = Pubkey::from_str(path) {
KeypairUrl::Pubkey(pubkey)
} else {
@ -56,10 +60,11 @@ pub fn presigner_from_pubkey_sigs(
})
}
pub fn keypair_util_from_path(
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 => {
@ -70,25 +75,42 @@ pub fn keypair_util_from_path(
false,
)?))
}
KeypairUrl::Filepath(path) => Ok(Box::new(read_keypair_file(&path)?)),
KeypairUrl::Filepath(path) => match read_keypair_file(&path) {
Err(e) => Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("could not find keypair file: {} error: {}", path, e),
)
.into()),
Ok(file) => Ok(Box::new(file)),
},
KeypairUrl::Stdin => {
let mut stdin = std::io::stdin();
Ok(Box::new(read_keypair(&mut stdin)?))
}
KeypairUrl::Usb(path) => Ok(Box::new(generate_remote_keypair(
path,
derivation_of(matches, "derivation_path"),
)?)),
KeypairUrl::Usb(path) => {
if let Some(wallet_manager) = wallet_manager {
Ok(Box::new(generate_remote_keypair(
path,
wallet_manager,
matches.is_present("confirm_key"),
keypair_name,
)?))
} 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 if matches.is_present(SIGN_ONLY_ARG.name) {
Ok(Box::new(NullSigner::new(&pubkey)))
} else {
Err(Error::with_description(
"Missing signature for supplied pubkey",
ErrorKind::MissingRequiredArgument,
Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("missing signature for supplied pubkey: {}", pubkey),
)
.into())
}
@ -96,39 +118,72 @@ pub fn keypair_util_from_path(
}
}
pub fn pubkey_from_path(
matches: &ArgMatches,
path: &str,
keypair_name: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<Pubkey, Box<dyn error::Error>> {
match parse_keypair_path(path) {
KeypairUrl::Pubkey(pubkey) => Ok(pubkey),
_ => Ok(signer_from_path(matches, path, keypair_name, wallet_manager)?.pubkey()),
}
}
pub fn resolve_signer_from_path(
matches: &ArgMatches,
path: &str,
keypair_name: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<Option<String>, Box<dyn error::Error>> {
match parse_keypair_path(path) {
KeypairUrl::Ask => {
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
// This method validates the seed phrase, but returns `None` because there is no path
// on disk or to a device
keypair_from_seed_phrase(keypair_name, skip_validation, false).map(|_| None)
}
KeypairUrl::Filepath(path) => match read_keypair_file(&path) {
Err(e) => Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("could not find keypair file: {} error: {}", path, e),
)
.into()),
Ok(_) => Ok(Some(path.to_string())),
},
KeypairUrl::Stdin => {
let mut stdin = std::io::stdin();
// This method validates the keypair from stdin, but returns `None` because there is no
// path on disk or to a device
read_keypair(&mut stdin).map(|_| None)
}
KeypairUrl::Usb(path) => {
if let Some(wallet_manager) = wallet_manager {
let path = generate_remote_keypair(
path,
wallet_manager,
matches.is_present("confirm_key"),
keypair_name,
)
.map(|keypair| keypair.path)?;
Ok(Some(path))
} else {
Err(RemoteWalletError::NoDeviceFound.into())
}
}
_ => Ok(Some(path.to_string())),
}
}
// 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: "Recover a keypair using a seed phrase and optional passphrase",
};
pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant {
long: "skip-seed-phrase-validation",
name: "skip_seed_phrase_validation",
help: "Skip validation of seed phrases. Use this if your phrase does not use the BIP39 official English word list",
};
#[derive(Debug, PartialEq)]
pub enum Source {
Generated,
Path,
SeedPhrase,
}
pub struct KeypairWithSource {
pub keypair: Keypair,
pub source: Source,
}
impl KeypairWithSource {
fn new(keypair: Keypair, source: Source) -> Self {
Self { keypair, source }
}
}
/// Prompts user for a passphrase and then asks for confirmirmation to check for mistakes
pub fn prompt_passphrase(prompt: &str) -> Result<String, Box<dyn error::Error>> {
let passphrase = prompt_password_stderr(&prompt)?;
@ -182,47 +237,6 @@ pub fn keypair_from_seed_phrase(
Ok(keypair)
}
/// Checks CLI arguments to determine whether a keypair should be:
/// - inputted securely via stdin,
/// - read in from a file,
/// - or newly generated
pub fn keypair_input(
matches: &clap::ArgMatches,
keypair_name: &str,
) -> Result<KeypairWithSource, Box<dyn error::Error>> {
let ask_seed_phrase_matches =
values_t!(matches.values_of(ASK_SEED_PHRASE_ARG.name), String).unwrap_or_default();
let keypair_match_name = keypair_name.replace('-', "_");
if ask_seed_phrase_matches
.iter()
.any(|s| s.as_str() == keypair_name)
{
if matches.value_of(keypair_match_name).is_some() {
clap::Error::with_description(
&format!(
"`--{} {}` cannot be used with `{} <PATH>`",
ASK_SEED_PHRASE_ARG.long, keypair_name, keypair_name
),
clap::ErrorKind::ArgumentConflict,
)
.exit();
}
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
keypair_from_seed_phrase(keypair_name, skip_validation, true)
.map(|keypair| KeypairWithSource::new(keypair, Source::SeedPhrase))
} else if let Some(keypair_file) = matches.value_of(keypair_match_name) {
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))
}
}
fn sanitize_seed_phrase(seed_phrase: &str) -> String {
seed_phrase
.split_whitespace()
@ -233,14 +247,6 @@ fn sanitize_seed_phrase(seed_phrase: &str) -> String {
#[cfg(test)]
mod tests {
use super::*;
use clap::ArgMatches;
#[test]
fn test_keypair_input() {
let arg_matches = ArgMatches::default();
let KeypairWithSource { source, .. } = keypair_input(&arg_matches, "").unwrap();
assert_eq!(source, Source::Generated);
}
#[test]
fn test_sanitize_seed_phrase() {

View File

@ -1,3 +1,5 @@
use thiserror::Error;
#[macro_export]
macro_rules! version {
() => {
@ -23,6 +25,24 @@ pub struct ArgConstant<'a> {
pub help: &'a str,
}
/// Error type for forwarding Errors out of `main()` of a `clap` app
/// and still using the `Display` formatter
#[derive(Error)]
#[error("{0}")]
pub struct DisplayError(Box<dyn std::error::Error>);
impl DisplayError {
pub fn new_as_boxed(inner: Box<dyn std::error::Error>) -> Box<Self> {
DisplayError(inner).into()
}
}
impl std::fmt::Debug for DisplayError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "{}", self.0)
}
}
pub mod commitment;
pub mod input_parsers;
pub mod input_validators;
pub mod keypair;

View File

@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-cli-config"
description = "Blockchain, Rebuilt for Scale"
version = "1.0.0"
version = "1.0.25"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -14,3 +14,7 @@ lazy_static = "1.4.0"
serde = "1.0.104"
serde_derive = "1.0.103"
serde_yaml = "0.8.11"
url = "2.1.1"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -1,10 +1,7 @@
// Wallet settings that can be configured for long-term use
use serde_derive::{Deserialize, Serialize};
use std::{
fs::{create_dir_all, File},
io::{self, Write},
path::Path,
};
use std::io;
use url::Url;
lazy_static! {
pub static ref CONFIG_FILE: Option<String> = {
@ -15,37 +12,86 @@ lazy_static! {
};
}
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct Config {
pub url: String,
pub json_rpc_url: String,
pub websocket_url: String,
pub keypair_path: String,
}
impl Config {
pub fn new(url: &str, keypair_path: &str) -> Self {
impl Default for Config {
fn default() -> Self {
let keypair_path = {
let mut keypair_path = dirs::home_dir().expect("home directory");
keypair_path.extend(&[".config", "solana", "id.json"]);
keypair_path.to_str().unwrap().to_string()
};
let json_rpc_url = "http://127.0.0.1:8899".to_string();
// Empty websocket_url string indicates the client should
// `Config::compute_websocket_url(&json_rpc_url)`
let websocket_url = "".to_string();
Self {
url: url.to_string(),
keypair_path: keypair_path.to_string(),
json_rpc_url,
websocket_url,
keypair_path,
}
}
}
impl Config {
pub fn load(config_file: &str) -> Result<Self, io::Error> {
let file = File::open(config_file.to_string())?;
let config = serde_yaml::from_reader(file)
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
Ok(config)
crate::load_config_file(config_file)
}
pub fn save(&self, config_file: &str) -> Result<(), io::Error> {
let serialized = serde_yaml::to_string(self)
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
crate::save_config_file(self, config_file)
}
if let Some(outdir) = Path::new(&config_file).parent() {
create_dir_all(outdir)?;
pub fn compute_websocket_url(json_rpc_url: &str) -> String {
let json_rpc_url: Option<Url> = json_rpc_url.parse().ok();
if json_rpc_url.is_none() {
return "".to_string();
}
let mut file = File::create(config_file)?;
file.write_all(&serialized.into_bytes())?;
Ok(())
let json_rpc_url = json_rpc_url.unwrap();
let is_secure = json_rpc_url.scheme().to_ascii_lowercase() == "https";
let mut ws_url = json_rpc_url.clone();
ws_url
.set_scheme(if is_secure { "wss" } else { "ws" })
.expect("unable to set scheme");
if let Some(port) = json_rpc_url.port() {
ws_url.set_port(Some(port + 1)).expect("unable to set port");
}
ws_url.to_string()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn compute_websocket_url() {
assert_eq!(
Config::compute_websocket_url(&"http://devnet.solana.com"),
"ws://devnet.solana.com/".to_string()
);
assert_eq!(
Config::compute_websocket_url(&"https://devnet.solana.com"),
"wss://devnet.solana.com/".to_string()
);
assert_eq!(
Config::compute_websocket_url(&"http://example.com:8899"),
"ws://example.com:8900/".to_string()
);
assert_eq!(
Config::compute_websocket_url(&"https://example.com:1234"),
"wss://example.com:1235/".to_string()
);
assert_eq!(Config::compute_websocket_url(&"garbage"), String::new());
}
}

View File

@ -1,4 +1,39 @@
#[macro_use]
extern crate lazy_static;
pub mod config;
mod config;
pub use config::{Config, CONFIG_FILE};
use std::{
fs::{create_dir_all, File},
io::{self, Write},
path::Path,
};
pub fn load_config_file<T, P>(config_file: P) -> Result<T, io::Error>
where
T: serde::de::DeserializeOwned,
P: AsRef<Path>,
{
let file = File::open(config_file)?;
let config = serde_yaml::from_reader(file)
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
Ok(config)
}
pub fn save_config_file<T, P>(config: &T, config_file: P) -> Result<(), io::Error>
where
T: serde::ser::Serialize,
P: AsRef<Path>,
{
let serialized = serde_yaml::to_string(config)
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
if let Some(outdir) = config_file.as_ref().parent() {
create_dir_all(outdir)?;
}
let mut file = File::create(config_file)?;
file.write_all(&serialized.into_bytes())?;
Ok(())
}

View File

@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-cli"
description = "Blockchain, Rebuilt for Scale"
version = "1.0.0"
version = "1.0.25"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -26,29 +26,34 @@ reqwest = { version = "0.10.1", default-features = false, features = ["blocking"
serde = "1.0.104"
serde_derive = "1.0.103"
serde_json = "1.0.46"
solana-budget-program = { path = "../programs/budget", version = "1.0.0" }
solana-clap-utils = { path = "../clap-utils", version = "1.0.0" }
solana-cli-config = { path = "../cli-config", version = "1.0.0" }
solana-client = { path = "../client", version = "1.0.0" }
solana-config-program = { path = "../programs/config", version = "1.0.0" }
solana-faucet = { path = "../faucet", version = "1.0.0" }
solana-logger = { path = "../logger", version = "1.0.0" }
solana-net-utils = { path = "../net-utils", version = "1.0.0" }
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.0" }
solana-runtime = { path = "../runtime", version = "1.0.0" }
solana-sdk = { path = "../sdk", version = "1.0.0" }
solana-stake-program = { path = "../programs/stake", version = "1.0.0" }
solana-storage-program = { path = "../programs/storage", version = "1.0.0" }
solana-vote-program = { path = "../programs/vote", version = "1.0.0" }
solana-vote-signer = { path = "../vote-signer", version = "1.0.0" }
solana-budget-program = { path = "../programs/budget", version = "1.0.25" }
solana-clap-utils = { path = "../clap-utils", version = "1.0.25" }
solana-cli-config = { path = "../cli-config", version = "1.0.25" }
solana-client = { path = "../client", version = "1.0.25" }
solana-config-program = { path = "../programs/config", version = "1.0.25" }
solana-faucet = { path = "../faucet", version = "1.0.25" }
solana-logger = { path = "../logger", version = "1.0.25" }
solana-net-utils = { path = "../net-utils", version = "1.0.25" }
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.25" }
solana-runtime = { path = "../runtime", version = "1.0.25" }
solana-sdk = { path = "../sdk", version = "1.0.25" }
solana-stake-program = { path = "../programs/stake", version = "1.0.25" }
solana-storage-program = { path = "../programs/storage", version = "1.0.25" }
solana-transaction-status = { path = "../transaction-status", version = "1.0.25" }
solana-vote-program = { path = "../programs/vote", version = "1.0.25" }
solana-vote-signer = { path = "../vote-signer", version = "1.0.25" }
titlecase = "1.1.0"
thiserror = "1.0.11"
url = "2.1.1"
[dev-dependencies]
solana-core = { path = "../core", version = "1.0.0" }
solana-budget-program = { path = "../programs/budget", version = "1.0.0" }
solana-core = { path = "../core", version = "1.0.25" }
solana-budget-program = { path = "../programs/budget", version = "1.0.25" }
tempfile = "3.1.0"
[[bin]]
name = "solana"
path = "src/main.rs"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

File diff suppressed because it is too large Load Diff

View File

@ -8,12 +8,18 @@ use crate::{
use clap::{value_t, value_t_or_exit, App, Arg, ArgMatches, SubCommand};
use console::{style, Emoji};
use indicatif::{ProgressBar, ProgressStyle};
use solana_clap_utils::{input_parsers::*, input_validators::*};
use solana_clap_utils::{
commitment::{commitment_arg, COMMITMENT_ARG},
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},
@ -54,10 +60,18 @@ impl ClusterQuerySubCommands for App<'_, '_> {
Arg::with_name("node_pubkey")
.index(1)
.takes_value(true)
.value_name("PUBKEY")
.validator(is_pubkey_or_keypair)
.value_name("VALIDATOR_PUBKEY")
.validator(is_valid_pubkey)
.required(true)
.help("Identity pubkey of the validator"),
)
.arg(
Arg::with_name("node_json_rpc_url")
.index(2)
.value_name("URL")
.takes_value(true)
.validator(is_url)
.help("JSON RPC URL for validator, which is useful for validators with a private RPC service")
),
)
.subcommand(
@ -82,14 +96,7 @@ impl ClusterQuerySubCommands for App<'_, '_> {
SubCommand::with_name("epoch-info")
.about("Get information about the current epoch")
.alias("get-epoch-info")
.arg(
Arg::with_name("confirmed")
.long("confirmed")
.takes_value(false)
.help(
"Return information at maximum-lockout commitment level",
),
),
.arg(commitment_arg()),
)
.subcommand(
SubCommand::with_name("genesis-hash")
@ -99,26 +106,16 @@ impl ClusterQuerySubCommands for App<'_, '_> {
.subcommand(
SubCommand::with_name("slot").about("Get current slot")
.alias("get-slot")
.arg(
Arg::with_name("confirmed")
.long("confirmed")
.takes_value(false)
.help(
"Return slot at maximum-lockout commitment level",
),
),
.arg(commitment_arg()),
)
.subcommand(
SubCommand::with_name("epoch").about("Get current epoch")
.arg(commitment_arg()),
)
.subcommand(
SubCommand::with_name("transaction-count").about("Get current transaction count")
.alias("get-transaction-count")
.arg(
Arg::with_name("confirmed")
.long("confirmed")
.takes_value(false)
.help(
"Return count at maximum-lockout commitment level",
),
),
.arg(commitment_arg()),
)
.subcommand(
SubCommand::with_name("ping")
@ -158,27 +155,11 @@ impl ClusterQuerySubCommands for App<'_, '_> {
.default_value("15")
.help("Wait up to timeout seconds for transaction confirmation"),
)
.arg(
Arg::with_name("confirmed")
.long("confirmed")
.takes_value(false)
.help(
"Wait until the transaction is confirmed at maximum-lockout commitment level",
),
),
.arg(commitment_arg()),
)
.subcommand(
SubCommand::with_name("live-slots")
.about("Show information about the current slot progression")
.arg(
Arg::with_name("websocket_url")
.short("w")
.long("ws")
.value_name("URL")
.takes_value(true)
.default_value("ws://127.0.0.1:8900")
.help("WebSocket URL for PubSub RPC connection"),
),
.about("Show information about the current slot progression"),
)
.subcommand(
SubCommand::with_name("block-production")
@ -208,10 +189,10 @@ impl ClusterQuerySubCommands for App<'_, '_> {
.arg(
Arg::with_name("vote_account_pubkeys")
.index(1)
.value_name("VOTE ACCOUNT PUBKEYS")
.value_name("VOTE_ACCOUNT_PUBKEYS")
.takes_value(true)
.multiple(true)
.validator(is_pubkey_or_keypair)
.validator(is_valid_pubkey)
.help("Only show stake accounts delegated to the provided vote accounts"),
)
.arg(
@ -230,20 +211,32 @@ impl ClusterQuerySubCommands for App<'_, '_> {
.long("lamports")
.takes_value(false)
.help("Display balance in lamports instead of SOL"),
),
)
.arg(commitment_arg()),
)
}
}
pub fn parse_catchup(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let node_pubkey = pubkey_of(matches, "node_pubkey").unwrap();
pub fn parse_catchup(
matches: &ArgMatches<'_>,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let node_pubkey = pubkey_of_signer(matches, "node_pubkey", wallet_manager)?.unwrap();
let node_json_rpc_url = value_t!(matches, "node_json_rpc_url", String).ok();
Ok(CliCommandInfo {
command: CliCommand::Catchup { node_pubkey },
require_keypair: false,
command: CliCommand::Catchup {
node_pubkey,
node_json_rpc_url,
},
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") {
@ -252,11 +245,7 @@ pub fn parse_cluster_ping(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, Cl
None
};
let timeout = Duration::from_secs(value_t_or_exit!(matches, "timeout", u64));
let commitment_config = if matches.is_present("confirmed") {
CommitmentConfig::default()
} else {
CommitmentConfig::recent()
};
let commitment_config = commitment_of(matches, COMMITMENT_ARG.long).unwrap();
Ok(CliCommandInfo {
command: CliCommand::Ping {
lamports,
@ -265,15 +254,12 @@ pub fn parse_cluster_ping(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, Cl
timeout,
commitment_config,
},
require_keypair: true,
})
}
pub fn parse_live_slots(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let url: String = value_t_or_exit!(matches, "websocket_url", String);
Ok(CliCommandInfo {
command: CliCommand::LiveSlots { url },
require_keypair: false,
signers: vec![signer_from_path(
matches,
default_signer_path,
"keypair",
wallet_manager,
)?],
})
}
@ -281,65 +267,69 @@ 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![],
})
}
pub fn parse_get_epoch_info(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let commitment_config = if matches.is_present("confirmed") {
CommitmentConfig::default()
} else {
CommitmentConfig::recent()
};
let commitment_config = commitment_of(matches, COMMITMENT_ARG.long).unwrap();
Ok(CliCommandInfo {
command: CliCommand::GetEpochInfo { commitment_config },
require_keypair: false,
signers: vec![],
})
}
pub fn parse_get_slot(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let commitment_config = if matches.is_present("confirmed") {
CommitmentConfig::default()
} else {
CommitmentConfig::recent()
};
let commitment_config = commitment_of(matches, COMMITMENT_ARG.long).unwrap();
Ok(CliCommandInfo {
command: CliCommand::GetSlot { commitment_config },
require_keypair: false,
signers: vec![],
})
}
pub fn parse_get_epoch(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let commitment_config = commitment_of(matches, COMMITMENT_ARG.long).unwrap();
Ok(CliCommandInfo {
command: CliCommand::GetEpoch { commitment_config },
signers: vec![],
})
}
pub fn parse_get_transaction_count(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let commitment_config = if matches.is_present("confirmed") {
CommitmentConfig::default()
} else {
CommitmentConfig::recent()
};
let commitment_config = commitment_of(matches, COMMITMENT_ARG.long).unwrap();
Ok(CliCommandInfo {
command: CliCommand::GetTransactionCount { commitment_config },
require_keypair: false,
signers: vec![],
})
}
pub fn parse_show_stakes(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
pub fn parse_show_stakes(
matches: &ArgMatches<'_>,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let use_lamports_unit = matches.is_present("lamports");
let vote_account_pubkeys = pubkeys_of(matches, "vote_account_pubkeys");
let vote_account_pubkeys =
pubkeys_of_multiple_signers(matches, "vote_account_pubkeys", wallet_manager)?;
Ok(CliCommandInfo {
command: CliCommand::ShowStakes {
use_lamports_unit,
vote_account_pubkeys,
},
require_keypair: false,
signers: vec![],
})
}
pub fn parse_show_validators(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let use_lamports_unit = matches.is_present("lamports");
let commitment_config = commitment_of(matches, COMMITMENT_ARG.long).unwrap();
Ok(CliCommandInfo {
command: CliCommand::ShowValidators { use_lamports_unit },
require_keypair: false,
command: CliCommand::ShowValidators {
use_lamports_unit,
commitment_config,
},
signers: vec![],
})
}
@ -352,20 +342,42 @@ fn new_spinner_progress_bar() -> ProgressBar {
progress_bar
}
pub fn process_catchup(rpc_client: &RpcClient, node_pubkey: &Pubkey) -> ProcessResult {
pub fn process_catchup(
rpc_client: &RpcClient,
node_pubkey: &Pubkey,
node_json_rpc_url: &Option<String>,
) -> ProcessResult {
let cluster_nodes = rpc_client.get_cluster_nodes()?;
let rpc_addr = cluster_nodes
.iter()
.find(|contact_info| contact_info.pubkey == node_pubkey.to_string())
.ok_or_else(|| format!("Contact information not found for {}", node_pubkey))?
.rpc
.ok_or_else(|| format!("RPC service not found for {}", node_pubkey))?;
let node_client = if let Some(node_json_rpc_url) = node_json_rpc_url {
RpcClient::new(node_json_rpc_url.to_string())
} else {
RpcClient::new_socket(
cluster_nodes
.iter()
.find(|contact_info| contact_info.pubkey == node_pubkey.to_string())
.ok_or_else(|| format!("Contact information not found for {}", node_pubkey))?
.rpc
.ok_or_else(|| format!("RPC service not found for {}", node_pubkey))?,
)
};
let reported_node_pubkey = node_client.get_identity()?;
if reported_node_pubkey != *node_pubkey {
return Err(format!(
"The identity reported by node RPC URL does not match. Expected: {:?}. Reported: {:?}",
node_pubkey, reported_node_pubkey
)
.into());
}
if rpc_client.get_identity()? == *node_pubkey {
return Err("Both RPC URLs reference the same node, unable to monitor for catchup. Try a different --url".into());
}
let progress_bar = new_spinner_progress_bar();
progress_bar.set_message("Connecting...");
let node_client = RpcClient::new_socket(rpc_addr);
let mut previous_rpc_slot = std::u64::MAX;
let mut previous_slot_distance = 0;
let sleep_interval = 5;
@ -381,25 +393,35 @@ pub fn process_catchup(rpc_client: &RpcClient, node_pubkey: &Pubkey) -> ProcessR
}
let slot_distance = rpc_slot as i64 - node_slot as i64;
let slots_per_second =
(previous_slot_distance - slot_distance) as f64 / f64::from(sleep_interval);
let time_remaining = (slot_distance as f64 / slots_per_second).round();
let time_remaining = if !time_remaining.is_normal() || time_remaining <= 0.0 {
"".to_string()
} else {
format!(
". Time remaining: {}",
humantime::format_duration(Duration::from_secs_f64(time_remaining))
)
};
progress_bar.set_message(&format!(
"Validator is {} slots away (us:{} them:{}){}",
"{} slots behind (us:{} them:{}){}",
slot_distance,
node_slot,
rpc_slot,
if previous_rpc_slot == std::u64::MAX {
"".to_string()
} else {
let slots_per_second =
(previous_slot_distance - slot_distance) as f64 / f64::from(sleep_interval);
format!(
" and {} at {:.1} slots/second",
", {} at {:.1} slots/second{}",
if slots_per_second < 0.0 {
"falling behind"
} else {
"gaining"
},
slots_per_second,
time_remaining
)
}
));
@ -473,7 +495,7 @@ fn slot_to_human_time(slot: Slot) -> String {
pub fn process_get_epoch_info(
rpc_client: &RpcClient,
commitment_config: &CommitmentConfig,
commitment_config: CommitmentConfig,
) -> ProcessResult {
let epoch_info = rpc_client.get_epoch_info_with_commitment(commitment_config.clone())?;
println!();
@ -519,19 +541,27 @@ pub fn process_get_genesis_hash(rpc_client: &RpcClient) -> ProcessResult {
pub fn process_get_slot(
rpc_client: &RpcClient,
commitment_config: &CommitmentConfig,
commitment_config: CommitmentConfig,
) -> ProcessResult {
let slot = rpc_client.get_slot_with_commitment(commitment_config.clone())?;
Ok(slot.to_string())
}
pub fn process_get_epoch(
rpc_client: &RpcClient,
commitment_config: CommitmentConfig,
) -> ProcessResult {
let epoch_info = rpc_client.get_epoch_info_with_commitment(commitment_config.clone())?;
Ok(epoch_info.epoch.to_string())
}
pub fn parse_show_block_production(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let epoch = value_t!(matches, "epoch", Epoch).ok();
let slot_limit = value_t!(matches, "slot_limit", u64).ok();
Ok(CliCommandInfo {
command: CliCommand::ShowBlockProduction { epoch, slot_limit },
require_keypair: false,
signers: vec![],
})
}
@ -708,7 +738,7 @@ pub fn process_show_block_production(
pub fn process_get_transaction_count(
rpc_client: &RpcClient,
commitment_config: &CommitmentConfig,
commitment_config: CommitmentConfig,
) -> ProcessResult {
let transaction_count =
rpc_client.get_transaction_count_with_commitment(commitment_config.clone())?;
@ -722,11 +752,11 @@ pub fn process_ping(
interval: &Duration,
count: &Option<u64>,
timeout: &Duration,
commitment_config: &CommitmentConfig,
commitment_config: CommitmentConfig,
) -> ProcessResult {
let to = Keypair::new().pubkey();
println_name_value("Source Account:", &config.keypair.pubkey().to_string());
println_name_value("Source Account:", &config.signers[0].pubkey().to_string());
println_name_value("Destination Account:", &to.to_string());
println!();
@ -745,13 +775,13 @@ pub fn process_ping(
let (recent_blockhash, fee_calculator) = rpc_client.get_new_blockhash(&last_blockhash)?;
last_blockhash = recent_blockhash;
let ix = system_instruction::transfer(&config.keypair.pubkey(), &to, lamports);
let message = Message::new(vec![ix]);
let ix = system_instruction::transfer(&config.signers[0].pubkey(), &to, lamports);
let message = Message::new(&[ix]);
let mut transaction = Transaction::new_unsigned(message);
transaction.try_sign(&[config.keypair.as_ref()], recent_blockhash)?;
transaction.try_sign(&config.signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&fee_calculator,
&transaction.message,
)?;
@ -845,11 +875,15 @@ pub fn process_ping(
pub fn process_live_slots(url: &str) -> ProcessResult {
let exit = Arc::new(AtomicBool::new(false));
let exit_clone = exit.clone();
// 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();
@ -859,6 +893,12 @@ pub fn process_live_slots(url: &str) -> ProcessResult {
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);
@ -868,7 +908,27 @@ pub fn process_live_slots(url: &str) -> ProcessResult {
match receiver.recv() {
Ok(new_info) => {
message = format!("{:?}", new_info).to_owned();
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 {
@ -881,17 +941,25 @@ pub fn process_live_slots(url: &str) -> ProcessResult {
//
if slot_delta != root_delta {
let prev_root = format!(
"|<- {} <- … <- {} <- {}",
"|<--- {} <- … <- {} <- {} (prev)",
previous.root, previous.parent, previous.slot
)
.to_owned();
);
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);
eprintln!("disconnected: {}", err);
break;
}
}
@ -975,9 +1043,13 @@ pub fn process_show_stakes(
Ok("".to_string())
}
pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool) -> ProcessResult {
let epoch_info = rpc_client.get_epoch_info()?;
let vote_accounts = rpc_client.get_vote_accounts()?;
pub fn process_show_validators(
rpc_client: &RpcClient,
use_lamports_unit: bool,
commitment_config: CommitmentConfig,
) -> ProcessResult {
let epoch_info = rpc_client.get_epoch_info_with_commitment(commitment_config)?;
let vote_accounts = rpc_client.get_vote_accounts_with_commitment(commitment_config)?;
let total_active_stake = vote_accounts
.current
.iter()
@ -1105,28 +1177,38 @@ pub fn process_show_validators(rpc_client: &RpcClient, use_lamports_unit: bool)
mod tests {
use super::*;
use crate::cli::{app, parse_command};
use solana_sdk::signature::{write_keypair, Keypair};
use tempfile::NamedTempFile;
fn make_tmp_file() -> (String, NamedTempFile) {
let tmp_file = NamedTempFile::new().unwrap();
(String::from(tmp_file.path().to_str().unwrap()), tmp_file)
}
#[test]
fn test_parse_command() {
let test_commands = app("test", "desc", "version");
let default_keypair = Keypair::new();
let (default_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
let test_cluster_version = test_commands
.clone()
.get_matches_from(vec!["test", "cluster-version"]);
assert_eq!(
parse_command(&test_cluster_version).unwrap(),
parse_command(&test_cluster_version, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::ClusterVersion,
require_keypair: false
signers: vec![],
}
);
let test_fees = test_commands.clone().get_matches_from(vec!["test", "fees"]);
assert_eq!(
parse_command(&test_fees).unwrap(),
parse_command(&test_fees, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::Fees,
require_keypair: false
signers: vec![],
}
);
@ -1136,10 +1218,10 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "block-time", &slot.to_string()]);
assert_eq!(
parse_command(&test_get_block_time).unwrap(),
parse_command(&test_get_block_time, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::GetBlockTime { slot },
require_keypair: false
signers: vec![],
}
);
@ -1147,12 +1229,12 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "epoch-info"]);
assert_eq!(
parse_command(&test_get_epoch_info).unwrap(),
parse_command(&test_get_epoch_info, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::GetEpochInfo {
commitment_config: CommitmentConfig::recent(),
},
require_keypair: false
signers: vec![],
}
);
@ -1160,21 +1242,34 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "genesis-hash"]);
assert_eq!(
parse_command(&test_get_genesis_hash).unwrap(),
parse_command(&test_get_genesis_hash, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::GetGenesisHash,
require_keypair: false
signers: vec![],
}
);
let test_get_slot = test_commands.clone().get_matches_from(vec!["test", "slot"]);
assert_eq!(
parse_command(&test_get_slot).unwrap(),
parse_command(&test_get_slot, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::GetSlot {
commitment_config: CommitmentConfig::recent(),
},
require_keypair: false
signers: vec![],
}
);
let test_get_epoch = test_commands
.clone()
.get_matches_from(vec!["test", "epoch"]);
assert_eq!(
parse_command(&test_get_epoch, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::GetEpoch {
commitment_config: CommitmentConfig::recent(),
},
signers: vec![],
}
);
@ -1182,12 +1277,12 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "transaction-count"]);
assert_eq!(
parse_command(&test_transaction_count).unwrap(),
parse_command(&test_transaction_count, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::GetTransactionCount {
commitment_config: CommitmentConfig::recent(),
},
require_keypair: false
signers: vec![],
}
);
@ -1200,19 +1295,20 @@ mod tests {
"2",
"-t",
"3",
"--confirmed",
"--commitment",
"max",
]);
assert_eq!(
parse_command(&test_ping).unwrap(),
parse_command(&test_ping, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::Ping {
lamports: 1,
interval: Duration::from_secs(1),
count: Some(2),
timeout: Duration::from_secs(3),
commitment_config: CommitmentConfig::default(),
commitment_config: CommitmentConfig::max(),
},
require_keypair: true
signers: vec![default_keypair.into()],
}
);
}

View File

@ -1,5 +1,11 @@
use crate::cli::SettingType;
use console::style;
use solana_sdk::transaction::Transaction;
use solana_sdk::{
hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize,
transaction::Transaction,
};
use solana_transaction_status::RpcTransactionStatusMeta;
use std::io;
// Pretty print a "name value"
pub fn println_name_value(name: &str, value: &str) {
@ -11,26 +17,177 @@ 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) {
pub fn println_signers(
blockhash: &Hash,
signers: &[String],
absent: &[String],
bad_sig: &[String],
) {
println!();
println!("Blockhash: {}", tx.message.recent_blockhash);
println!("Signers (Pubkey=Signature):");
tx.signatures
.iter()
.zip(tx.message.account_keys.clone())
.for_each(|(signature, pubkey)| println!(" {:?}={:?}", pubkey, signature));
println!("Blockhash: {}", blockhash);
if !signers.is_empty() {
println!("Signers (Pubkey=Signature):");
signers.iter().for_each(|signer| println!(" {}", signer))
}
if !absent.is_empty() {
println!("Absent Signers (Pubkey):");
absent.iter().for_each(|pubkey| println!(" {}", pubkey))
}
if !bad_sig.is_empty() {
println!("Bad Signatures (Pubkey):");
bad_sig.iter().for_each(|pubkey| println!(" {}", pubkey))
}
println!();
}
pub fn write_transaction<W: io::Write>(
w: &mut W,
transaction: &Transaction,
transaction_status: &Option<RpcTransactionStatusMeta>,
prefix: &str,
) -> io::Result<()> {
let message = &transaction.message;
writeln!(
w,
"{}Recent Blockhash: {:?}",
prefix, message.recent_blockhash
)?;
for (signature_index, signature) in transaction.signatures.iter().enumerate() {
writeln!(
w,
"{}Signature {}: {:?}",
prefix, signature_index, signature
)?;
}
writeln!(w, "{}{:?}", prefix, message.header)?;
for (account_index, account) in message.account_keys.iter().enumerate() {
writeln!(w, "{}Account {}: {:?}", prefix, account_index, account)?;
}
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
let program_pubkey = message.account_keys[instruction.program_id_index as usize];
writeln!(w, "{}Instruction {}", prefix, instruction_index)?;
writeln!(
w,
"{} Program: {} ({})",
prefix, program_pubkey, instruction.program_id_index
)?;
for (account_index, account) in instruction.accounts.iter().enumerate() {
let account_pubkey = message.account_keys[*account as usize];
writeln!(
w,
"{} Account {}: {} ({})",
prefix, account_index, account_pubkey, account
)?;
}
let mut raw = true;
if program_pubkey == solana_vote_program::id() {
if let Ok(vote_instruction) = limited_deserialize::<
solana_vote_program::vote_instruction::VoteInstruction,
>(&instruction.data)
{
writeln!(w, "{} {:?}", prefix, vote_instruction)?;
raw = false;
}
} else if program_pubkey == solana_stake_program::id() {
if let Ok(stake_instruction) = limited_deserialize::<
solana_stake_program::stake_instruction::StakeInstruction,
>(&instruction.data)
{
writeln!(w, "{} {:?}", prefix, stake_instruction)?;
raw = false;
}
} else if program_pubkey == solana_sdk::system_program::id() {
if let Ok(system_instruction) = limited_deserialize::<
solana_sdk::system_instruction::SystemInstruction,
>(&instruction.data)
{
writeln!(w, "{} {:?}", prefix, system_instruction)?;
raw = false;
}
}
if raw {
writeln!(w, "{} Data: {:?}", prefix, instruction.data)?;
}
}
if let Some(transaction_status) = transaction_status {
writeln!(
w,
"{}Status: {}",
prefix,
match &transaction_status.status {
Ok(_) => "Ok".into(),
Err(err) => err.to_string(),
}
)?;
writeln!(
w,
"{} Fee: {} SOL",
prefix,
lamports_to_sol(transaction_status.fee)
)?;
assert_eq!(
transaction_status.pre_balances.len(),
transaction_status.post_balances.len()
);
for (i, (pre, post)) in transaction_status
.pre_balances
.iter()
.zip(transaction_status.post_balances.iter())
.enumerate()
{
if pre == post {
writeln!(
w,
"{} Account {} balance: {} SOL",
prefix,
i,
lamports_to_sol(*pre)
)?;
} else {
writeln!(
w,
"{} Account {} balance: {} SOL -> {} SOL",
prefix,
i,
lamports_to_sol(*pre),
lamports_to_sol(*post)
)?;
}
}
} else {
writeln!(w, "{}Status: Unavailable", prefix)?;
}
Ok(())
}
pub fn println_transaction(
transaction: &Transaction,
transaction_status: &Option<RpcTransactionStatusMeta>,
prefix: &str,
) {
let mut w = Vec::new();
if write_transaction(&mut w, transaction, transaction_status, prefix).is_ok() {
if let Ok(s) = String::from_utf8(w) {
print!("{}", s);
}
}
}

View File

@ -2,17 +2,16 @@ use clap::{crate_description, crate_name, AppSettings, Arg, ArgGroup, ArgMatches
use console::style;
use solana_clap_utils::{
input_parsers::derivation_of,
input_validators::{is_derivation, is_url},
keypair::{keypair_util_from_path, SKIP_SEED_PHRASE_VALIDATION_ARG},
input_validators::is_url, keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, offline::SIGN_ONLY_ARG,
DisplayError,
};
use solana_cli::{
cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliError},
cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners},
display::{println_name_value, println_name_value_or},
};
use solana_cli_config::config::{Config, CONFIG_FILE};
use std::error;
use solana_cli_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() {
@ -20,29 +19,31 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
("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.json_rpc_url);
let (ws_setting_type, websocket_url) = CliConfig::compute_websocket_url_setting(
"",
&config.websocket_url,
"",
&config.json_rpc_url,
);
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, default_value) = match field {
"url" => ("RPC URL", config.url, CliConfig::default_json_rpc_url()),
"keypair" => (
"Key Path",
config.keypair_path,
CliConfig::default_keypair_path(),
),
let (field_name, value, setting_type) = match field {
"json_rpc_url" => ("RPC URL", json_rpc_url, url_setting_type),
"websocket_url" => ("WebSocket URL", websocket_url, ws_setting_type),
"keypair" => ("Key Path", keypair_path, keypair_setting_type),
_ => unreachable!(),
};
println_name_value_or(&format!("{}:", field_name), &value, &default_value);
println_name_value_or(&format!("{}:", field_name), &value, setting_type);
} else {
println_name_value("Config File:", config_file);
println_name_value_or(
"RPC URL:",
&config.url,
&CliConfig::default_json_rpc_url(),
);
println_name_value_or(
"Keypair Path:",
&config.keypair_path,
&CliConfig::default_keypair_path(),
);
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
println_name_value_or("WebSocket URL:", &websocket_url, ws_setting_type);
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
}
} else {
println!(
@ -56,15 +57,34 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
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();
config.json_rpc_url = url.to_string();
// Revert to a computed `websocket_url` value when `json_rpc_url` is
// changed
config.websocket_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.json_rpc_url);
let (ws_setting_type, websocket_url) = CliConfig::compute_websocket_url_setting(
"",
&config.websocket_url,
"",
&config.json_rpc_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("RPC URL:", &config.url);
println_name_value("Keypair Path:", &config.keypair_path);
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
println_name_value_or("WebSocket 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",
@ -80,59 +100,45 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
Ok(parse_args)
}
pub fn parse_args(matches: &ArgMatches<'_>) -> Result<CliConfig, Box<dyn error::Error>> {
pub fn parse_args<'a>(
matches: &ArgMatches<'_>,
wallet_manager: Option<Arc<RemoteWalletManager>>,
) -> Result<(CliConfig<'a>, CliSigners), Box<dyn error::Error>> {
let config = if let Some(config_file) = matches.value_of("config_file") {
Config::load(config_file).unwrap_or_default()
} else {
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.json_rpc_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.json_rpc_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 path = if matches.is_present("keypair") {
matches.value_of("keypair").unwrap().to_string()
} else if config.keypair_path != "" {
config.keypair_path
} else {
let default_keypair_path = CliConfig::default_keypair_path();
if !std::path::Path::new(&default_keypair_path).exists() {
return Err(CliError::KeypairFileNotFound(format!(
"Generate a new keypair at {} with `solana-keygen new`",
default_keypair_path
))
.into());
}
default_keypair_path
};
let keypair = keypair_util_from_path(matches, &path, "keypair")?;
(keypair, Some(path))
} else {
let default = CliConfig::default();
(default.keypair, None)
};
Ok(CliConfig {
command,
json_rpc_url,
keypair,
keypair_path,
derivation_path: derivation_of(matches, "derivation_path"),
rpc_client: None,
verbose: matches.is_present("verbose"),
})
Ok((
CliConfig {
command,
json_rpc_url,
websocket_url,
signers: vec![],
keypair_path: default_signer_path,
rpc_client: None,
verbose: matches.is_present("verbose"),
},
signers,
))
}
fn main() -> Result<(), Box<dyn error::Error>> {
@ -146,7 +152,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
let arg = Arg::with_name("config_file")
.short("C")
.long("config")
.value_name("PATH")
.value_name("FILEPATH")
.takes_value(true)
.global(true)
.help("Configuration file to use");
@ -166,30 +172,30 @@ 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")
.long("keypair")
.value_name("PATH")
.value_name("KEYPAIR")
.global(true)
.takes_value(true)
.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'")
.help("Filepath or URL to a keypair"),
)
.arg(
Arg::with_name("verbose")
.long("verbose")
.short("v")
.global(true)
.help("Show extra information header"),
.help("Show additional information"),
)
.arg(
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
@ -210,7 +216,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.index(1)
.value_name("CONFIG_FIELD")
.takes_value(true)
.possible_values(&["url", "keypair"])
.possible_values(&["json_rpc_url", "websocket_url", "keypair"])
.help("Return a specific config setting"),
),
)
@ -219,7 +225,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
.about("Set a config setting")
.group(
ArgGroup::with_name("config_settings")
.args(&["json_rpc_url", "keypair"])
.args(&["json_rpc_url", "websocket_url", "keypair"])
.multiple(true)
.required(true),
),
@ -227,10 +233,23 @@ fn main() -> Result<(), Box<dyn error::Error>> {
)
.get_matches();
do_main(&matches).map_err(|err| DisplayError::new_as_boxed(err).into())
}
fn do_main(matches: &ArgMatches<'_>) -> Result<(), Box<dyn error::Error>> {
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);
}
let (_, submatches) = matches.subcommand();
let sign_only = submatches
.map(|m| m.is_present(SIGN_ONLY_ARG.name))
.unwrap_or(false);
if !sign_only {
println!("{}", result);
}
};
Ok(())
}

File diff suppressed because it is too large Load Diff

View File

@ -1,253 +0,0 @@
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());
}
}

View File

@ -0,0 +1,394 @@
use super::*;
#[derive(Debug, PartialEq)]
pub enum Source {
Cluster,
NonceAccount(Pubkey),
}
impl Source {
pub fn get_blockhash_and_fee_calculator(
&self,
rpc_client: &RpcClient,
) -> Result<(Hash, FeeCalculator), Box<dyn std::error::Error>> {
match self {
Self::Cluster => {
let res = rpc_client.get_recent_blockhash()?;
Ok(res)
}
Self::NonceAccount(ref pubkey) => {
let data = nonce::get_account(rpc_client, pubkey)
.and_then(|ref a| nonce::data_from_account(a))?;
Ok((data.blockhash, data.fee_calculator))
}
}
}
pub fn get_fee_calculator(
&self,
rpc_client: &RpcClient,
blockhash: &Hash,
) -> Result<Option<FeeCalculator>, Box<dyn std::error::Error>> {
match self {
Self::Cluster => {
let res = rpc_client.get_fee_calculator_for_blockhash(blockhash)?;
Ok(res)
}
Self::NonceAccount(ref pubkey) => {
let res = nonce::get_account(rpc_client, pubkey)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|d| {
if d.blockhash == *blockhash {
Ok(Some(d.fee_calculator))
} else {
Ok(None)
}
})?;
Ok(res)
}
}
}
}
#[derive(Debug, PartialEq)]
pub enum BlockhashQuery {
None(Hash),
FeeCalculator(Source, Hash),
All(Source),
}
impl BlockhashQuery {
pub fn new(blockhash: Option<Hash>, sign_only: bool, nonce_account: Option<Pubkey>) -> Self {
let source = nonce_account
.map(Source::NonceAccount)
.unwrap_or(Source::Cluster);
match blockhash {
Some(hash) if sign_only => Self::None(hash),
Some(hash) if !sign_only => Self::FeeCalculator(source, hash),
None if !sign_only => Self::All(source),
_ => 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);
let nonce_account = pubkey_of(matches, nonce::NONCE_ARG.name);
BlockhashQuery::new(blockhash, sign_only, nonce_account)
}
pub fn get_blockhash_and_fee_calculator(
&self,
rpc_client: &RpcClient,
) -> Result<(Hash, FeeCalculator), Box<dyn std::error::Error>> {
match self {
BlockhashQuery::None(hash) => Ok((*hash, FeeCalculator::default())),
BlockhashQuery::FeeCalculator(source, hash) => {
let fee_calculator = source
.get_fee_calculator(rpc_client, hash)?
.ok_or(format!("Hash has expired {:?}", hash))?;
Ok((*hash, fee_calculator))
}
BlockhashQuery::All(source) => source.get_blockhash_and_fee_calculator(rpc_client),
}
}
}
impl Default for BlockhashQuery {
fn default() -> Self {
BlockhashQuery::All(Source::Cluster)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{nonce::nonce_arg, offline::blockhash_query::BlockhashQuery};
use clap::App;
use serde_json::{self, json, Value};
use solana_client::{
rpc_request::RpcRequest,
rpc_response::{Response, RpcAccount, RpcFeeCalculator, RpcResponseContext},
};
use solana_sdk::{
account::Account, fee_calculator::FeeCalculator, hash::hash, nonce, system_program,
};
use std::collections::HashMap;
#[test]
fn test_blockhash_query_new_ok() {
let blockhash = hash(&[1u8]);
let nonce_pubkey = Pubkey::new(&[1u8; 32]);
assert_eq!(
BlockhashQuery::new(Some(blockhash), true, None),
BlockhashQuery::None(blockhash),
);
assert_eq!(
BlockhashQuery::new(Some(blockhash), false, None),
BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
);
assert_eq!(
BlockhashQuery::new(None, false, None),
BlockhashQuery::All(blockhash_query::Source::Cluster)
);
assert_eq!(
BlockhashQuery::new(Some(blockhash), true, Some(nonce_pubkey)),
BlockhashQuery::None(blockhash),
);
assert_eq!(
BlockhashQuery::new(Some(blockhash), false, Some(nonce_pubkey)),
BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_pubkey),
blockhash
),
);
assert_eq!(
BlockhashQuery::new(None, false, Some(nonce_pubkey)),
BlockhashQuery::All(blockhash_query::Source::NonceAccount(nonce_pubkey)),
);
}
#[test]
#[should_panic]
fn test_blockhash_query_new_no_nonce_fail() {
BlockhashQuery::new(None, true, None);
}
#[test]
#[should_panic]
fn test_blockhash_query_new_nonce_fail() {
let nonce_pubkey = Pubkey::new(&[1u8; 32]);
BlockhashQuery::new(None, true, Some(nonce_pubkey));
}
#[test]
fn test_blockhash_query_new_from_matches_ok() {
let test_commands = App::new("blockhash_query_test")
.arg(nonce_arg())
.offline_args();
let blockhash = hash(&[1u8]);
let blockhash_string = blockhash.to_string();
let matches = test_commands.clone().get_matches_from(vec![
"blockhash_query_test",
"--blockhash",
&blockhash_string,
"--sign-only",
]);
assert_eq!(
BlockhashQuery::new_from_matches(&matches),
BlockhashQuery::None(blockhash),
);
let matches = test_commands.clone().get_matches_from(vec![
"blockhash_query_test",
"--blockhash",
&blockhash_string,
]);
assert_eq!(
BlockhashQuery::new_from_matches(&matches),
BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
);
let matches = test_commands
.clone()
.get_matches_from(vec!["blockhash_query_test"]);
assert_eq!(
BlockhashQuery::new_from_matches(&matches),
BlockhashQuery::All(blockhash_query::Source::Cluster),
);
let nonce_pubkey = Pubkey::new(&[1u8; 32]);
let nonce_string = nonce_pubkey.to_string();
let matches = test_commands.clone().get_matches_from(vec![
"blockhash_query_test",
"--blockhash",
&blockhash_string,
"--sign-only",
"--nonce",
&nonce_string,
]);
assert_eq!(
BlockhashQuery::new_from_matches(&matches),
BlockhashQuery::None(blockhash),
);
let matches = test_commands.clone().get_matches_from(vec![
"blockhash_query_test",
"--blockhash",
&blockhash_string,
"--nonce",
&nonce_string,
]);
assert_eq!(
BlockhashQuery::new_from_matches(&matches),
BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_pubkey),
blockhash
),
);
}
#[test]
#[should_panic]
fn test_blockhash_query_new_from_matches_without_nonce_fail() {
let test_commands = App::new("blockhash_query_test")
.arg(blockhash_arg())
// We can really only hit this case if 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!["blockhash_query_test", "--sign-only"]);
BlockhashQuery::new_from_matches(&matches);
}
#[test]
#[should_panic]
fn test_blockhash_query_new_from_matches_with_nonce_fail() {
let test_commands = App::new("blockhash_query_test")
.arg(blockhash_arg())
// We can really only hit this case if the arg requirements
// are broken, so unset the requires() to recreate that condition
.arg(sign_only_arg().requires(""));
let nonce_pubkey = Pubkey::new(&[1u8; 32]);
let nonce_string = nonce_pubkey.to_string();
let matches = test_commands.clone().get_matches_from(vec![
"blockhash_query_test",
"--sign-only",
"--nonce",
&nonce_string,
]);
BlockhashQuery::new_from_matches(&matches);
}
#[test]
fn test_blockhash_query_get_blockhash_fee_calc() {
let test_blockhash = hash(&[0u8]);
let rpc_blockhash = hash(&[1u8]);
let rpc_fee_calc = FeeCalculator::new(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 get_fee_calculator_for_blockhash_response = json!(Response {
context: RpcResponseContext { slot: 1 },
value: json!(RpcFeeCalculator {
fee_calculator: 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::default()
.get_blockhash_and_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(),
);
mocks.insert(
RpcRequest::GetFeeCalculatorForBlockhash,
get_fee_calculator_for_blockhash_response.clone(),
);
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert_eq!(
BlockhashQuery::FeeCalculator(Source::Cluster, test_blockhash)
.get_blockhash_and_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)
.get_blockhash_and_fee_calculator(&rpc_client)
.unwrap(),
(test_blockhash, FeeCalculator::default()),
);
let rpc_client = RpcClient::new_mock("fails".to_string());
assert!(BlockhashQuery::default()
.get_blockhash_and_fee_calculator(&rpc_client)
.is_err());
let nonce_blockhash = Hash::new(&[2u8; 32]);
let nonce_fee_calc = FeeCalculator::new(4242);
let data = nonce::state::Data {
authority: Pubkey::new(&[3u8; 32]),
blockhash: nonce_blockhash,
fee_calculator: nonce_fee_calc.clone(),
};
let nonce_account = Account::new_data_with_space(
42,
&nonce::state::Versions::new_current(nonce::State::Initialized(data)),
nonce::State::size(),
&system_program::id(),
)
.unwrap();
let nonce_pubkey = Pubkey::new(&[4u8; 32]);
let rpc_nonce_account = RpcAccount::encode(nonce_account);
let get_account_response = json!(Response {
context: RpcResponseContext { slot: 1 },
value: json!(Some(rpc_nonce_account.clone())),
});
let mut mocks = HashMap::new();
mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone());
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert_eq!(
BlockhashQuery::All(Source::NonceAccount(nonce_pubkey))
.get_blockhash_and_fee_calculator(&rpc_client)
.unwrap(),
(nonce_blockhash, nonce_fee_calc.clone()),
);
let mut mocks = HashMap::new();
mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone());
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert_eq!(
BlockhashQuery::FeeCalculator(Source::NonceAccount(nonce_pubkey), nonce_blockhash)
.get_blockhash_and_fee_calculator(&rpc_client)
.unwrap(),
(nonce_blockhash, nonce_fee_calc.clone()),
);
let mut mocks = HashMap::new();
mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone());
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert!(
BlockhashQuery::FeeCalculator(Source::NonceAccount(nonce_pubkey), test_blockhash)
.get_blockhash_and_fee_calculator(&rpc_client)
.is_err()
);
let mut mocks = HashMap::new();
mocks.insert(RpcRequest::GetAccountInfo, get_account_response.clone());
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
assert_eq!(
BlockhashQuery::None(nonce_blockhash)
.get_blockhash_and_fee_calculator(&rpc_client)
.unwrap(),
(nonce_blockhash, FeeCalculator::default()),
);
let rpc_client = RpcClient::new_mock("fails".to_string());
assert!(BlockhashQuery::All(Source::NonceAccount(nonce_pubkey))
.get_blockhash_and_fee_calculator(&rpc_client)
.is_err());
}
}

114
cli/src/offline/mod.rs Normal file
View File

@ -0,0 +1,114 @@
pub mod blockhash_query;
use crate::nonce;
use clap::{App, Arg, ArgMatches};
use serde_json::Value;
use solana_clap_utils::{
input_parsers::{pubkey_of, value_of},
input_validators::{is_hash, is_pubkey_sig},
keypair::presigner_from_pubkey_sigs,
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::{Presigner, Signature},
};
use std::str::FromStr;
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("PUBKEY=SIGNATURE")
.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 struct SignOnly {
pub blockhash: Hash,
pub present_signers: Vec<(Pubkey, Signature)>,
pub absent_signers: Vec<Pubkey>,
pub bad_signers: Vec<Pubkey>,
}
impl SignOnly {
pub fn has_all_signers(&self) -> bool {
self.absent_signers.is_empty() && self.bad_signers.is_empty()
}
pub fn presigner_of(&self, pubkey: &Pubkey) -> Option<Presigner> {
presigner_from_pubkey_sigs(pubkey, &self.present_signers)
}
}
pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
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 present_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();
let signer_strings = object.get("absent").unwrap().as_array().unwrap();
let absent_signers = signer_strings
.iter()
.map(|val| {
let s = val.as_str().unwrap();
Pubkey::from_str(s).unwrap()
})
.collect();
let signer_strings = object.get("badSig").unwrap().as_array().unwrap();
let bad_signers = signer_strings
.iter()
.map(|val| {
let s = val.as_str().unwrap();
Pubkey::from_str(s).unwrap()
})
.collect();
SignOnly {
blockhash,
present_signers,
absent_signers,
bad_signers,
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,18 @@
use crate::cli::{
check_account_for_fee, check_unique_pubkeys, log_instruction_custom_error, CliCommand,
CliCommandInfo, CliConfig, CliError, ProcessResult,
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::*, keypair::signer_from_path};
use solana_client::rpc_client::RpcClient;
use solana_sdk::signature::Keypair;
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account_utils::StateMut, message::Message, pubkey::Pubkey, signature::Signer,
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;
@ -24,18 +26,18 @@ impl StorageSubCommands for App<'_, '_> {
.arg(
Arg::with_name("storage_account_owner")
.index(1)
.value_name("STORAGE ACCOUNT OWNER PUBKEY")
.value_name("AUTHORITY_PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair),
.validator(is_valid_pubkey),
)
.arg(
Arg::with_name("storage_account")
.index(2)
.value_name("STORAGE ACCOUNT")
.value_name("ACCOUNT_KEYPAIR")
.takes_value(true)
.required(true)
.validator(is_keypair_or_ask_keyword),
.validator(is_valid_signer),
),
)
.subcommand(
@ -44,18 +46,18 @@ impl StorageSubCommands for App<'_, '_> {
.arg(
Arg::with_name("storage_account_owner")
.index(1)
.value_name("STORAGE ACCOUNT OWNER PUBKEY")
.value_name("AUTHORITY_PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair),
.validator(is_valid_pubkey),
)
.arg(
Arg::with_name("storage_account")
.index(2)
.value_name("STORAGE ACCOUNT")
.value_name("ACCOUNT_KEYPAIR")
.takes_value(true)
.required(true)
.validator(is_keypair_or_ask_keyword),
.validator(is_valid_signer),
),
)
.subcommand(
@ -64,19 +66,19 @@ impl StorageSubCommands for App<'_, '_> {
.arg(
Arg::with_name("node_account_pubkey")
.index(1)
.value_name("NODE PUBKEY")
.value_name("NODE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.validator(is_valid_pubkey)
.help("The node account to credit the rewards to"),
)
.arg(
Arg::with_name("storage_account_pubkey")
.index(2)
.value_name("STORAGE ACCOUNT PUBKEY")
.value_name("STORAGE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.validator(is_valid_pubkey)
.help("Storage account address to redeem credits for"),
),
)
@ -87,11 +89,11 @@ impl StorageSubCommands for App<'_, '_> {
.arg(
Arg::with_name("storage_account_pubkey")
.index(1)
.value_name("STORAGE ACCOUNT PUBKEY")
.value_name("STORAGE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("Storage account pubkey"),
.validator(is_valid_pubkey)
.help("Storage account address"),
),
)
}
@ -99,66 +101,106 @@ 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();
let account_owner =
pubkey_of_signer(matches, "storage_account_owner", wallet_manager)?.unwrap();
let (storage_account, storage_account_pubkey) =
signer_of(matches, "storage_account", wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
vec![payer_provided, storage_account],
matches,
default_signer_path,
wallet_manager,
)?;
Ok(CliCommandInfo {
command: CliCommand::CreateStorageAccount {
account_owner,
storage_account: storage_account.into(),
storage_account: signer_info.index_of(storage_account_pubkey).unwrap(),
account_type: StorageAccountType::Archiver,
},
require_keypair: true,
signers: signer_info.signers,
})
}
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();
let account_owner =
pubkey_of_signer(matches, "storage_account_owner", wallet_manager)?.unwrap();
let (storage_account, storage_account_pubkey) =
signer_of(matches, "storage_account", wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
vec![payer_provided, storage_account],
matches,
default_signer_path,
wallet_manager,
)?;
Ok(CliCommandInfo {
command: CliCommand::CreateStorageAccount {
account_owner,
storage_account: storage_account.into(),
storage_account: signer_info.index_of(storage_account_pubkey).unwrap(),
account_type: StorageAccountType::Validator,
},
require_keypair: true,
signers: signer_info.signers,
})
}
pub fn parse_storage_claim_reward(matches: &ArgMatches<'_>) -> 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();
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_signer(matches, "node_account_pubkey", wallet_manager)?.unwrap();
let storage_account_pubkey =
pubkey_of_signer(matches, "storage_account_pubkey", wallet_manager)?.unwrap();
Ok(CliCommandInfo {
command: CliCommand::ClaimStorageReward {
node_account_pubkey,
storage_account_pubkey,
},
require_keypair: true,
signers: vec![signer_from_path(
matches,
default_signer_path,
"keypair",
wallet_manager,
)?],
})
}
pub fn parse_storage_get_account_command(
matches: &ArgMatches<'_>,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
let storage_account_pubkey =
pubkey_of_signer(matches, "storage_account_pubkey", wallet_manager)?.unwrap();
Ok(CliCommandInfo {
command: CliCommand::ShowStorageAccount(storage_account_pubkey),
require_keypair: false,
signers: vec![],
})
}
pub fn process_create_storage_account(
rpc_client: &RpcClient,
config: &CliConfig,
storage_account: SignerIndex,
account_owner: &Pubkey,
storage_account: &Keypair,
account_type: StorageAccountType,
) -> ProcessResult {
let storage_account = config.signers[storage_account];
let storage_account_pubkey = storage_account.pubkey();
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
(&config.signers[0].pubkey(), "cli keypair".to_string()),
(
&storage_account_pubkey,
"storage_account_pubkey".to_string(),
@ -183,7 +225,7 @@ pub fn process_create_storage_account(
.max(1);
let ixs = storage_instruction::create_storage_account(
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&account_owner,
&storage_account_pubkey,
required_balance,
@ -191,20 +233,16 @@ pub fn process_create_storage_account(
);
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let message = Message::new(ixs);
let message = Message::new(&ixs);
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(
&[config.keypair.as_ref(), storage_account],
recent_blockhash,
)?;
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.as_ref(), storage_account]);
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
log_instruction_custom_error::<SystemError>(result)
}
@ -218,18 +256,18 @@ pub fn process_claim_storage_reward(
let instruction =
storage_instruction::claim_reward(node_account_pubkey, storage_account_pubkey);
let signers = [config.keypair.as_ref()];
let message = Message::new_with_payer(vec![instruction], Some(&signers[0].pubkey()));
let signers = [config.signers[0]];
let message = Message::new_with_payer(&[instruction], Some(&signers[0].pubkey()));
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,
)?;
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &signers)?;
Ok(signature_str)
let signature = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &signers)?;
Ok(signature.to_string())
}
pub fn process_show_storage_account(
@ -249,7 +287,7 @@ pub fn process_show_storage_account(
use solana_storage_program::storage_contract::StorageContract;
let storage_contract: StorageContract = account.state().map_err(|err| {
CliError::RpcRequestError(format!("Unable to deserialize storage account: {:?}", err))
CliError::RpcRequestError(format!("Unable to deserialize storage account: {}", err))
})?;
println!("{:#?}", storage_contract);
println!("Account Lamports: {}", account.lamports);
@ -260,7 +298,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) {
@ -274,6 +312,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();
@ -285,14 +327,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()
],
}
);
@ -309,14 +359,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()
],
}
);
@ -327,13 +385,13 @@ mod tests {
&storage_account_string,
]);
assert_eq!(
parse_command(&test_claim_storage_reward).unwrap(),
parse_command(&test_claim_storage_reward, &default_keypair_file, None).unwrap(),
CliCommandInfo {
command: CliCommand::ClaimStorageReward {
node_account_pubkey: pubkey,
storage_account_pubkey,
},
require_keypair: true
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
);
}

View File

@ -11,9 +11,11 @@ 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,
@ -22,7 +24,7 @@ use solana_sdk::{
signature::{Keypair, Signer},
transaction::Transaction,
};
use std::error;
use std::{error, sync::Arc};
use titlecase::titlecase;
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
@ -224,17 +226,26 @@ impl ValidatorInfoSubCommands for App<'_, '_> {
}
}
pub fn parse_validator_info_command(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
pub fn parse_validator_info_command(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let info_pubkey = pubkey_of(matches, "info_pubkey");
// Prepare validator info
let validator_info = parse_args(&matches);
let validator_info = parse_args(matches);
Ok(CliCommandInfo {
command: CliCommand::SetValidatorInfo {
validator_info,
force_keybase: matches.is_present("force"),
info_pubkey,
},
require_keypair: true,
signers: vec![signer_from_path(
matches,
default_signer_path,
"keypair",
wallet_manager,
)?],
})
}
@ -244,7 +255,7 @@ pub fn parse_get_validator_info_command(
let info_pubkey = pubkey_of(matches, "info_pubkey");
Ok(CliCommandInfo {
command: CliCommand::GetValidatorInfo(info_pubkey),
require_keypair: false,
signers: vec![],
})
}
@ -257,13 +268,13 @@ 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);
} else {
result.map_err(|err| {
CliError::BadParameter(format!("Invalid validator keybase username: {:?}", err))
CliError::BadParameter(format!("Invalid validator keybase username: {}", err))
})?;
}
}
@ -282,7 +293,7 @@ pub fn process_set_validator_info(
})
.find(|(pubkey, account)| {
let (validator_pubkey, _) = parse_validator_info(&pubkey, &account).unwrap();
validator_pubkey == config.keypair.pubkey()
validator_pubkey == config.signers[0].pubkey()
});
// Create validator-info keypair to use if info_pubkey not provided or does not exist
@ -300,7 +311,7 @@ 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 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!(
@ -311,12 +322,12 @@ pub fn process_set_validator_info(
}
println!(
"Publishing info for Validator {:?}",
config.keypair.pubkey()
config.signers[0].pubkey()
);
let lamports = rpc_client
.get_minimum_balance_for_rent_exemption(ValidatorInfo::max_space() as usize)?;
let mut instructions = config_instruction::create_account::<ValidatorInfo>(
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&info_keypair.pubkey(),
lamports,
keys.clone(),
@ -327,13 +338,13 @@ pub fn process_set_validator_info(
keys,
&validator_info,
)]);
let signers = vec![config.keypair.as_ref(), &info_keypair];
let message = Message::new(instructions);
let signers = vec![config.signers[0], &info_keypair];
let message = Message::new(&instructions);
(message, signers)
} else {
println!(
"Updating Validator {:?} info at: {:?}",
config.keypair.pubkey(),
config.signers[0].pubkey(),
info_pubkey
);
let instructions = vec![config_instruction::store(
@ -342,8 +353,8 @@ 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.as_ref()];
let message = Message::new_with_payer(&instructions, Some(&config.signers[0].pubkey()));
let signers = vec![config.signers[0]];
(message, signers)
};
@ -353,11 +364,11 @@ pub fn process_set_validator_info(
tx.try_sign(&signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&fee_calculator,
&tx.message,
)?;
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &signers)?;
let signature_str = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &signers)?;
println!("Success! Validator info published at: {:?}", info_pubkey);
println!("{}", signature_str);

View File

@ -1,23 +1,29 @@
use crate::cli::{
build_balance_message, check_account_for_fee, check_unique_pubkeys,
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, CliSignerInfo,
ProcessResult, SignerIndex,
};
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
use solana_clap_utils::{input_parsers::*, input_validators::*};
use solana_clap_utils::{
commitment::{commitment_arg, COMMITMENT_ARG},
input_parsers::*,
input_validators::*,
};
use solana_client::rpc_client::RpcClient;
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account::Account,
commitment_config::CommitmentConfig,
message::Message,
pubkey::Pubkey,
signature::Keypair,
signature::Signer,
system_instruction::{create_address_with_seed, SystemError},
transaction::Transaction,
};
use solana_vote_program::{
vote_instruction::{self, VoteError},
vote_instruction::{self, withdraw, VoteError},
vote_state::{VoteAuthorize, VoteInit, VoteState},
};
use std::sync::Arc;
pub trait VoteSubCommands {
fn vote_subcommands(self) -> Self;
@ -31,25 +37,25 @@ impl VoteSubCommands for App<'_, '_> {
.arg(
Arg::with_name("vote_account")
.index(1)
.value_name("VOTE ACCOUNT KEYPAIR")
.value_name("ACCOUNT_KEYPAIR")
.takes_value(true)
.required(true)
.validator(is_keypair_or_ask_keyword)
.help("Vote account keypair to fund"),
.validator(is_valid_signer)
.help("Vote account keypair to create"),
)
.arg(
Arg::with_name("identity_pubkey")
Arg::with_name("identity_account")
.index(2)
.value_name("VALIDATOR IDENTITY PUBKEY")
.value_name("IDENTITY_KEYPAIR")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("Validator that will vote with this account"),
.validator(is_valid_signer)
.help("Keypair of validator that will vote with this account"),
)
.arg(
Arg::with_name("commission")
.long("commission")
.value_name("NUM")
.value_name("PERCENTAGE")
.takes_value(true)
.default_value("100")
.help("The commission taken on reward redemption (0-100)"),
@ -57,78 +63,47 @@ impl VoteSubCommands for App<'_, '_> {
.arg(
Arg::with_name("authorized_voter")
.long("authorized-voter")
.value_name("PUBKEY")
.value_name("VOTER_PUBKEY")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.help("Public key of the authorized voter (defaults to vote account)"),
.validator(is_valid_pubkey)
.help("Public key of the authorized voter [default: validator identity pubkey]"),
)
.arg(
Arg::with_name("authorized_withdrawer")
.long("authorized-withdrawer")
.value_name("PUBKEY")
.value_name("WITHDRAWER_PUBKEY")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.help("Public key of the authorized withdrawer (defaults to cli config pubkey)"),
.validator(is_valid_pubkey)
.help("Public key of the authorized withdrawer [default: validator identity pubkey]"),
)
.arg(
Arg::with_name("seed")
.long("seed")
.value_name("SEED STRING")
.value_name("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(
SubCommand::with_name("vote-update-validator")
.about("Update the vote account's validator identity")
.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 to update"),
)
.arg(
Arg::with_name("new_identity_pubkey")
.index(2)
.value_name("NEW VALIDATOR IDENTITY PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("New validator that will vote with this account"),
)
.arg(
Arg::with_name("authorized_voter")
.index(3)
.value_name("AUTHORIZED VOTER KEYPAIR")
.takes_value(true)
.required(true)
.validator(is_keypair)
.help("Authorized voter keypair"),
)
)
.subcommand(
SubCommand::with_name("vote-authorize-voter")
.about("Authorize a new vote signing keypair for the given vote account")
.arg(
Arg::with_name("vote_account_pubkey")
.index(1)
.value_name("VOTE ACCOUNT PUBKEY")
.value_name("VOTE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.validator(is_valid_pubkey)
.help("Vote account in which to set the authorized voter"),
)
.arg(
Arg::with_name("new_authorized_pubkey")
.index(2)
.value_name("NEW VOTER PUBKEY")
.value_name("AUTHORIZED_PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("New vote signer to authorize"),
.validator(is_valid_pubkey)
.help("New authorized vote signer"),
),
)
.subcommand(
@ -137,22 +112,53 @@ impl VoteSubCommands for App<'_, '_> {
.arg(
Arg::with_name("vote_account_pubkey")
.index(1)
.value_name("VOTE ACCOUNT PUBKEY")
.value_name("VOTE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.validator(is_valid_pubkey)
.help("Vote account in which to set the authorized withdrawer"),
)
.arg(
Arg::with_name("new_authorized_pubkey")
.index(2)
.value_name("NEW WITHDRAWER PUBKEY")
.value_name("AUTHORIZED_PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("New withdrawer to authorize"),
.validator(is_valid_pubkey)
.help("New authorized withdrawer"),
),
)
.subcommand(
SubCommand::with_name("vote-update-validator")
.about("Update the vote account's validator identity")
.arg(
Arg::with_name("vote_account_pubkey")
.index(1)
.value_name("VOTE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
.help("Vote account to update"),
)
.arg(
Arg::with_name("new_identity_account")
.index(2)
.value_name("IDENTITY_KEYPAIR")
.takes_value(true)
.required(true)
.validator(is_valid_signer)
.help("Keypair of new validator that will vote with this account"),
)
.arg(
Arg::with_name("authorized_withdrawer")
.index(3)
.value_name("AUTHORIZED_KEYPAIR")
.takes_value(true)
.required(true)
.validator(is_valid_signer)
.help("Authorized withdrawer keypair"),
)
)
.subcommand(
SubCommand::with_name("vote-account")
.about("Show the contents of a vote account")
@ -160,10 +166,10 @@ impl VoteSubCommands for App<'_, '_> {
.arg(
Arg::with_name("vote_account_pubkey")
.index(1)
.value_name("VOTE ACCOUNT PUBKEY")
.value_name("VOTE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.validator(is_valid_pubkey)
.help("Vote account pubkey"),
)
.arg(
@ -171,38 +177,102 @@ impl VoteSubCommands for App<'_, '_> {
.long("lamports")
.takes_value(false)
.help("Display balance in lamports instead of SOL"),
),
)
.arg(commitment_arg()),
)
.subcommand(
SubCommand::with_name("withdraw-from-vote-account")
.about("Withdraw lamports from a vote account into a specified account")
.arg(
Arg::with_name("vote_account_pubkey")
.index(1)
.value_name("VOTE_ACCOUNT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
.help("Vote account from which to withdraw"),
)
.arg(
Arg::with_name("destination_account_pubkey")
.index(2)
.value_name("RECIPIENT_ADDRESS")
.takes_value(true)
.required(true)
.validator(is_valid_pubkey)
.help("The recipient of withdrawn SOL"),
)
.arg(
Arg::with_name("amount")
.index(3)
.value_name("AMOUNT")
.takes_value(true)
.required(true)
.validator(is_amount)
.help("The amount to withdraw, in SOL"),
)
.arg(
Arg::with_name("authorized_withdrawer")
.long("authorized-withdrawer")
.value_name("AUTHORIZED_KEYPAIR")
.takes_value(true)
.validator(is_valid_signer)
.help("Authorized withdrawer [default: cli config keypair]"),
)
)
}
}
pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let vote_account = keypair_of(matches, "vote_account").unwrap();
pub fn parse_create_vote_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 (identity_account, identity_pubkey) =
signer_of(matches, "identity_account", wallet_manager)?;
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 authorized_voter = pubkey_of_signer(matches, "authorized_voter", wallet_manager)?;
let authorized_withdrawer = pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
vec![payer_provided, vote_account, identity_account],
matches,
default_signer_path,
wallet_manager,
)?;
Ok(CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: vote_account.into(),
seed,
node_pubkey: identity_pubkey,
identity_account: signer_info.index_of(identity_pubkey).unwrap(),
authorized_voter,
authorized_withdrawer,
commission,
},
require_keypair: true,
signers: signer_info.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 vote_account_pubkey =
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
let new_authorized_pubkey =
pubkey_of_signer(matches, "new_authorized_pubkey", wallet_manager)?.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 {
@ -210,49 +280,98 @@ pub fn parse_vote_authorize(
new_authorized_pubkey,
vote_authorize,
},
require_keypair: true,
signers,
})
}
pub fn parse_vote_update_validator(matches: &ArgMatches<'_>) -> 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();
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_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
let (new_identity_account, new_identity_pubkey) =
signer_of(matches, "new_identity_account", wallet_manager)?;
let (authorized_withdrawer, _) = signer_of(matches, "authorized_withdrawer", wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
vec![payer_provided, authorized_withdrawer, new_identity_account],
matches,
default_signer_path,
wallet_manager,
)?;
Ok(CliCommandInfo {
command: CliCommand::VoteUpdateValidator {
vote_account_pubkey,
new_identity_pubkey,
authorized_voter: authorized_voter.into(),
new_identity_account: signer_info.index_of(new_identity_pubkey).unwrap(),
},
require_keypair: true,
signers: signer_info.signers,
})
}
pub fn parse_vote_get_account_command(
matches: &ArgMatches<'_>,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
let vote_account_pubkey =
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
let use_lamports_unit = matches.is_present("lamports");
let commitment_config = commitment_of(matches, COMMITMENT_ARG.long).unwrap();
Ok(CliCommandInfo {
command: CliCommand::ShowVoteAccount {
pubkey: vote_account_pubkey,
use_lamports_unit,
commitment_config,
},
require_keypair: false,
signers: vec![],
})
}
pub fn parse_withdraw_from_vote_account(
matches: &ArgMatches<'_>,
default_signer_path: &str,
wallet_manager: Option<&Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let vote_account_pubkey =
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
let destination_account_pubkey =
pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
let lamports = lamports_of_sol(matches, "amount").unwrap();
let (withdraw_authority, withdraw_authority_pubkey) =
signer_of(matches, "authorized_withdrawer", wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
vec![payer_provided, withdraw_authority],
matches,
default_signer_path,
wallet_manager,
)?;
Ok(CliCommandInfo {
command: CliCommand::WithdrawFromVoteAccount {
vote_account_pubkey,
destination_account_pubkey,
withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
lamports,
},
signers: signer_info.signers,
})
}
pub fn process_create_vote_account(
rpc_client: &RpcClient,
config: &CliConfig,
vote_account: &Keypair,
seed: &Option<String>,
identity_pubkey: &Pubkey,
identity_account: SignerIndex,
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())?
@ -260,10 +379,12 @@ pub fn process_create_vote_account(
vote_account_pubkey
};
check_unique_pubkeys(
(&config.keypair.pubkey(), "cli keypair".to_string()),
(&config.signers[0].pubkey(), "cli keypair".to_string()),
(&vote_account_address, "vote_account".to_string()),
)?;
let identity_account = config.signers[identity_account];
let identity_pubkey = identity_account.pubkey();
check_unique_pubkeys(
(&vote_account_address, "vote_account".to_string()),
(&identity_pubkey, "identity_pubkey".to_string()),
@ -286,24 +407,24 @@ pub fn process_create_vote_account(
.max(1);
let vote_init = VoteInit {
node_pubkey: *identity_pubkey,
authorized_voter: authorized_voter.unwrap_or(vote_account_pubkey),
authorized_withdrawer: authorized_withdrawer.unwrap_or(vote_account_pubkey),
node_pubkey: identity_pubkey,
authorized_voter: authorized_voter.unwrap_or(identity_pubkey),
authorized_withdrawer: authorized_withdrawer.unwrap_or(identity_pubkey),
commission,
};
let ixs = if let Some(seed) = seed {
vote_instruction::create_account_with_seed(
&config.keypair.pubkey(), // from
&vote_account_address, // to
&vote_account_pubkey, // base
seed, // seed
&config.signers[0].pubkey(), // from
&vote_account_address, // to
&vote_account_pubkey, // base
seed, // seed
&vote_init,
required_balance,
)
} else {
vote_instruction::create_account(
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&vote_account_pubkey,
&vote_init,
required_balance,
@ -311,22 +432,16 @@ pub fn process_create_vote_account(
};
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let signers = if vote_account_pubkey != config.keypair.pubkey() {
vec![config.keypair.as_ref(), vote_account] // both must sign if `from` and `to` differ
} else {
vec![config.keypair.as_ref()] // when stake_account == config.keypair and there's a seed, we only need one signature
};
let message = Message::new(ixs);
let message = Message::new(&ixs);
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&signers, recent_blockhash)?;
tx.try_sign(&config.signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
&fee_calculator,
&tx.message,
)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &signers);
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
log_instruction_custom_error::<SystemError>(result)
}
@ -343,22 +458,23 @@ 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 message = Message::new_with_payer(ixs, Some(&config.keypair.pubkey()));
let message = Message::new_with_payer(&ixs, Some(&config.signers[0].pubkey()));
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(&[config.keypair.as_ref()], recent_blockhash)?;
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.as_ref()]);
let result =
rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0]]);
log_instruction_custom_error::<VoteError>(result)
}
@ -366,41 +482,46 @@ pub fn process_vote_update_validator(
rpc_client: &RpcClient,
config: &CliConfig,
vote_account_pubkey: &Pubkey,
new_identity_pubkey: &Pubkey,
authorized_voter: &Keypair,
new_identity_account: SignerIndex,
) -> ProcessResult {
let authorized_withdrawer = config.signers[1];
let new_identity_account = config.signers[new_identity_account];
let new_identity_pubkey = new_identity_account.pubkey();
check_unique_pubkeys(
(vote_account_pubkey, "vote_account_pubkey".to_string()),
(new_identity_pubkey, "new_identity_pubkey".to_string()),
(&new_identity_pubkey, "new_identity_account".to_string()),
)?;
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![vote_instruction::update_node(
vote_account_pubkey,
&authorized_voter.pubkey(),
new_identity_pubkey,
&authorized_withdrawer.pubkey(),
&new_identity_pubkey,
)];
let message = Message::new_with_payer(ixs, Some(&config.keypair.pubkey()));
let message = Message::new_with_payer(&ixs, Some(&config.signers[0].pubkey()));
let mut tx = Transaction::new_unsigned(message);
tx.try_sign(
&[config.keypair.as_ref(), authorized_voter],
recent_blockhash,
)?;
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.as_ref()]);
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
log_instruction_custom_error::<VoteError>(result)
}
fn get_vote_account(
rpc_client: &RpcClient,
vote_account_pubkey: &Pubkey,
commitment_config: CommitmentConfig,
) -> Result<(Account, VoteState), Box<dyn std::error::Error>> {
let vote_account = rpc_client.get_account(vote_account_pubkey)?;
let vote_account = rpc_client
.get_account_with_commitment(vote_account_pubkey, commitment_config)?
.value
.ok_or_else(|| {
CliError::RpcRequestError(format!("{:?} account does not exist", vote_account_pubkey))
})?;
if vote_account.owner != solana_vote_program::id() {
return Err(CliError::RpcRequestError(format!(
@ -423,8 +544,10 @@ pub fn process_show_vote_account(
_config: &CliConfig,
vote_account_pubkey: &Pubkey,
use_lamports_unit: bool,
commitment_config: CommitmentConfig,
) -> ProcessResult {
let (vote_account, vote_state) = get_vote_account(rpc_client, vote_account_pubkey)?;
let (vote_account, vote_state) =
get_vote_account(rpc_client, vote_account_pubkey, commitment_config)?;
let epoch_schedule = rpc_client.get_epoch_schedule()?;
@ -433,7 +556,7 @@ pub fn process_show_vote_account(
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!("Authorized Voter: {:?}", vote_state.authorized_voters());
println!(
"Authorized Withdrawer: {}",
vote_state.authorized_withdrawer
@ -470,11 +593,43 @@ pub fn process_show_vote_account(
Ok("".to_string())
}
pub fn process_withdraw_from_vote_account(
rpc_client: &RpcClient,
config: &CliConfig,
vote_account_pubkey: &Pubkey,
withdraw_authority: SignerIndex,
lamports: u64,
destination_account_pubkey: &Pubkey,
) -> ProcessResult {
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let withdraw_authority = config.signers[withdraw_authority];
let ix = withdraw(
vote_account_pubkey,
&withdraw_authority.pubkey(),
lamports,
destination_account_pubkey,
);
let message = Message::new_with_payer(&[ix], Some(&config.signers[0].pubkey()));
let mut transaction = Transaction::new_unsigned(message);
transaction.try_sign(&config.signers, recent_blockhash)?;
check_account_for_fee(
rpc_client,
&config.signers[0].pubkey(),
&fee_calculator,
&transaction.message,
)?;
let result =
rpc_client.send_and_confirm_transaction_with_spinner(&mut transaction, &config.signers);
log_instruction_custom_error::<VoteError>(result)
}
#[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) {
@ -492,6 +647,10 @@ mod tests {
let pubkey2 = keypair2.pubkey();
let pubkey2_string = pubkey2.to_string();
let default_keypair = Keypair::new();
let (default_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
"test",
"vote-authorize-voter",
@ -499,14 +658,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()],
}
);
@ -514,28 +673,32 @@ mod tests {
let keypair = Keypair::new();
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
// Test CreateVoteAccount SubCommand
let node_pubkey = Pubkey::new_rand();
let node_pubkey_string = format!("{}", node_pubkey);
let (identity_keypair_file, mut tmp_file) = make_tmp_file();
let identity_keypair = Keypair::new();
write_keypair(&identity_keypair, tmp_file.as_file_mut()).unwrap();
let test_create_vote_account = test_commands.clone().get_matches_from(vec![
"test",
"create-vote-account",
&keypair_file,
&node_pubkey_string,
&identity_keypair_file,
"--commission",
"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,
identity_account: 2,
authorized_voter: None,
authorized_withdrawer: None,
commission: 10,
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(keypair),
read_keypair_file(&identity_keypair_file).unwrap().into(),
],
}
);
@ -547,20 +710,23 @@ mod tests {
"test",
"create-vote-account",
&keypair_file,
&node_pubkey_string,
&identity_keypair_file,
]);
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,
identity_account: 2,
authorized_voter: None,
authorized_withdrawer: None,
commission: 100,
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(keypair),
read_keypair_file(&identity_keypair_file).unwrap().into(),
],
}
);
@ -574,22 +740,25 @@ mod tests {
"test",
"create-vote-account",
&keypair_file,
&node_pubkey_string,
&identity_keypair_file,
"--authorized-voter",
&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,
identity_account: 2,
authorized_voter: Some(authed),
authorized_withdrawer: None,
commission: 100
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(keypair),
read_keypair_file(&identity_keypair_file).unwrap().into(),
],
}
);
@ -601,22 +770,25 @@ mod tests {
"test",
"create-vote-account",
&keypair_file,
&node_pubkey_string,
&identity_keypair_file,
"--authorized-withdrawer",
&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,
identity_account: 2,
authorized_voter: None,
authorized_withdrawer: Some(authed),
commission: 100
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(keypair),
read_keypair_file(&identity_keypair_file).unwrap().into(),
],
}
);
@ -624,20 +796,81 @@ mod tests {
"test",
"vote-update-validator",
&pubkey_string,
&pubkey2_string,
&identity_keypair_file,
&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(),
new_identity_account: 2,
},
require_keypair: true
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
Box::new(read_keypair_file(&keypair_file).unwrap()),
read_keypair_file(&identity_keypair_file).unwrap().into(),
],
}
);
// Test WithdrawFromVoteAccount subcommand
let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
"test",
"withdraw-from-vote-account",
&keypair_file,
&pubkey_string,
"42",
]);
assert_eq!(
parse_command(
&test_withdraw_from_vote_account,
&default_keypair_file,
None
)
.unwrap(),
CliCommandInfo {
command: CliCommand::WithdrawFromVoteAccount {
vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
destination_account_pubkey: pubkey,
withdraw_authority: 0,
lamports: 42_000_000_000
},
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
}
);
// Test WithdrawFromVoteAccount subcommand with authority
let withdraw_authority = Keypair::new();
let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
"test",
"withdraw-from-vote-account",
&keypair_file,
&pubkey_string,
"42",
"--authorized-withdrawer",
&withdraw_authority_file,
]);
assert_eq!(
parse_command(
&test_withdraw_from_vote_account,
&default_keypair_file,
None
)
.unwrap(),
CliCommandInfo {
command: CliCommand::WithdrawFromVoteAccount {
vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
destination_account_pubkey: pubkey,
withdraw_authority: 1,
lamports: 42_000_000_000
},
signers: vec![
read_keypair_file(&default_keypair_file).unwrap().into(),
read_keypair_file(&withdraw_authority_file).unwrap().into()
],
}
);
}

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,6 +44,7 @@ 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,
@ -45,6 +52,7 @@ fn test_cli_deploy_program() {
pubkey: None,
lamports: minimum_balance_for_rent_exemption + 1, // min balance for rent exemption + leftover for tx processing
};
config.signers = vec![&keypair];
process_command(&config).unwrap();
config.command = CliCommand::Deploy(pathbuf.to_str().unwrap().to_string());

View File

@ -1,28 +1,22 @@
use solana_cli::cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig};
use solana_cli::{
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
nonce,
offline::{
blockhash_query::{self, BlockhashQuery},
parse_sign_only_reply_string,
},
};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::{TestValidator, TestValidatorOptions};
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::{
hash::Hash,
pubkey::Pubkey,
signature::{keypair_from_seed, read_keypair_file, write_keypair, Keypair, Signer},
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::rc::Rc;
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| {
@ -39,34 +33,21 @@ 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());
let keypair = keypair_from_seed(&[0u8; 32]).unwrap();
let (keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
let mut config_nonce = CliConfig::default();
config_nonce.keypair = keypair.into();
config_nonce.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,
None,
None,
);
full_battery_tests(&rpc_client, &faucet_addr, json_rpc_url, None, false);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
@ -74,33 +55,26 @@ fn test_nonce() {
#[test]
fn test_nonce_with_seed() {
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 mut config_payer = CliConfig::default();
config_payer.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
let keypair = keypair_from_seed(&[0u8; 32]).unwrap();
let (keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
let mut config_nonce = CliConfig::default();
config_nonce.keypair = keypair.into();
config_nonce.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
let json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
full_battery_tests(
&rpc_client,
&faucet_addr,
&mut config_payer,
&mut config_nonce,
&keypair_file,
json_rpc_url,
Some(String::from("seed")),
None,
false,
);
server.close().unwrap();
@ -109,84 +83,85 @@ fn test_nonce_with_seed() {
#[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 nonce_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
let (nonce_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&nonce_keypair, tmp_file.as_file_mut()).unwrap();
let mut config_nonce = CliConfig::default();
config_nonce.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
let nonce_authority = Keypair::new();
let (authority_keypair_file, mut tmp_file2) = make_tmp_file();
write_keypair(&nonce_authority, tmp_file2.as_file_mut()).unwrap();
full_battery_tests(
&rpc_client,
&faucet_addr,
&mut config_payer,
&mut config_nonce,
&nonce_keypair_file,
None,
Some(&authority_keypair_file),
);
full_battery_tests(&rpc_client, &faucet_addr, json_rpc_url, None, true);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
}
fn read_keypair_from_option(keypair_file: &Option<&str>) -> Option<Box<dyn Signer>> {
keypair_file.map(|akf| read_keypair_file(&akf).unwrap().into())
}
fn full_battery_tests(
rpc_client: &RpcClient,
faucet_addr: &std::net::SocketAddr,
config_payer: &mut CliConfig,
config_nonce: &mut CliConfig,
nonce_keypair_file: &str,
json_rpc_url: String,
seed: Option<String>,
authority_keypair_file: Option<&str>,
use_nonce_authority: bool,
) {
let mut config_payer = CliConfig::default();
config_payer.json_rpc_url = json_rpc_url.clone();
let payer = Keypair::new();
config_payer.signers = vec![&payer];
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config_payer.keypair.pubkey(),
&config_payer.signers[0].pubkey(),
2000,
)
.unwrap();
check_balance(2000, &rpc_client, &config_payer.keypair.pubkey());
check_balance(2000, &rpc_client, &config_payer.signers[0].pubkey());
let mut config_nonce = CliConfig::default();
config_nonce.json_rpc_url = json_rpc_url;
let nonce_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
config_nonce.signers = vec![&nonce_keypair];
let nonce_account = if let Some(seed) = seed.as_ref() {
create_address_with_seed(&config_nonce.keypair.pubkey(), seed, &system_program::id())
.unwrap()
create_address_with_seed(
&config_nonce.signers[0].pubkey(),
seed,
&system_program::id(),
)
.unwrap()
} else {
read_keypair_file(&nonce_keypair_file).unwrap().pubkey()
nonce_keypair.pubkey()
};
let nonce_authority = Keypair::new();
let optional_authority = if use_nonce_authority {
Some(nonce_authority.pubkey())
} else {
None
};
// Create nonce account
config_payer.signers.push(&nonce_keypair);
config_payer.command = CliCommand::CreateNonceAccount {
nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()),
nonce_account: 1,
seed,
nonce_authority: read_keypair_from_option(&authority_keypair_file).map(|k| k.pubkey()),
nonce_authority: optional_authority,
lamports: 1000,
};
process_command(&config_payer).unwrap();
check_balance(1000, &rpc_client, &config_payer.keypair.pubkey());
check_balance(1000, &rpc_client, &config_payer.signers[0].pubkey());
check_balance(1000, &rpc_client, &nonce_account);
// Get nonce
config_payer.signers.pop();
config_payer.command = CliCommand::GetNonce(nonce_account);
let first_nonce_string = process_command(&config_payer).unwrap();
let first_nonce = first_nonce_string.parse::<Hash>().unwrap();
@ -198,14 +173,24 @@ fn full_battery_tests(
assert_eq!(first_nonce, second_nonce);
let mut authorized_signers: Vec<&dyn Signer> = vec![&payer];
let index = if use_nonce_authority {
authorized_signers.push(&nonce_authority);
1
} else {
0
};
// New nonce
config_payer.signers = authorized_signers.clone();
config_payer.command = CliCommand::NewNonce {
nonce_account,
nonce_authority: read_keypair_from_option(&authority_keypair_file),
nonce_authority: index,
};
process_command(&config_payer).unwrap();
// Get nonce
config_payer.signers = vec![&payer];
config_payer.command = CliCommand::GetNonce(nonce_account);
let third_nonce_string = process_command(&config_payer).unwrap();
let third_nonce = third_nonce_string.parse::<Hash>().unwrap();
@ -214,14 +199,15 @@ fn full_battery_tests(
// Withdraw from nonce account
let payee_pubkey = Pubkey::new_rand();
config_payer.signers = authorized_signers;
config_payer.command = CliCommand::WithdrawFromNonceAccount {
nonce_account,
nonce_authority: read_keypair_from_option(&authority_keypair_file),
nonce_authority: index,
destination_account_pubkey: payee_pubkey,
lamports: 100,
};
process_command(&config_payer).unwrap();
check_balance(1000, &rpc_client, &config_payer.keypair.pubkey());
check_balance(1000, &rpc_client, &config_payer.signers[0].pubkey());
check_balance(900, &rpc_client, &nonce_account);
check_balance(100, &rpc_client, &payee_pubkey);
@ -234,48 +220,160 @@ fn full_battery_tests(
// Set new authority
let new_authority = Keypair::new();
let (new_authority_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&new_authority, tmp_file.as_file_mut()).unwrap();
config_payer.command = CliCommand::AuthorizeNonceAccount {
nonce_account,
nonce_authority: read_keypair_from_option(&authority_keypair_file),
new_authority: read_keypair_file(&new_authority_keypair_file)
.unwrap()
.pubkey(),
nonce_authority: index,
new_authority: new_authority.pubkey(),
};
process_command(&config_payer).unwrap();
// Old authority fails now
config_payer.command = CliCommand::NewNonce {
nonce_account,
nonce_authority: read_keypair_from_option(&authority_keypair_file),
nonce_authority: index,
};
process_command(&config_payer).unwrap_err();
// New authority can advance nonce
config_payer.signers = vec![&payer, &new_authority];
config_payer.command = CliCommand::NewNonce {
nonce_account,
nonce_authority: Some(
read_keypair_file(&new_authority_keypair_file)
.unwrap()
.into(),
),
nonce_authority: 1,
};
process_command(&config_payer).unwrap();
// New authority can withdraw from nonce account
config_payer.command = CliCommand::WithdrawFromNonceAccount {
nonce_account,
nonce_authority: Some(
read_keypair_file(&new_authority_keypair_file)
.unwrap()
.into(),
),
nonce_authority: 1,
destination_account_pubkey: payee_pubkey,
lamports: 100,
};
process_command(&config_payer).unwrap();
check_balance(1000, &rpc_client, &config_payer.keypair.pubkey());
check_balance(1000, &rpc_client, &config_payer.signers[0].pubkey());
check_balance(800, &rpc_client, &nonce_account);
check_balance(200, &rpc_client, &payee_pubkey);
}
#[test]
fn test_create_account_with_seed() {
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 offline_nonce_authority_signer = keypair_from_seed(&[1u8; 32]).unwrap();
let online_nonce_creator_signer = keypair_from_seed(&[2u8; 32]).unwrap();
let to_address = Pubkey::new(&[3u8; 32]);
// Setup accounts
let rpc_client = RpcClient::new_socket(leader_data.rpc);
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&offline_nonce_authority_signer.pubkey(),
42,
)
.unwrap();
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&online_nonce_creator_signer.pubkey(),
4242,
)
.unwrap();
check_balance(42, &rpc_client, &offline_nonce_authority_signer.pubkey());
check_balance(4242, &rpc_client, &online_nonce_creator_signer.pubkey());
check_balance(0, &rpc_client, &to_address);
// Create nonce account
let creator_pubkey = online_nonce_creator_signer.pubkey();
let authority_pubkey = offline_nonce_authority_signer.pubkey();
let seed = authority_pubkey.to_string()[0..32].to_string();
let nonce_address =
create_address_with_seed(&creator_pubkey, &seed, &system_program::id()).unwrap();
check_balance(0, &rpc_client, &nonce_address);
let mut creator_config = CliConfig::default();
creator_config.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
creator_config.signers = vec![&online_nonce_creator_signer];
creator_config.command = CliCommand::CreateNonceAccount {
nonce_account: 0,
seed: Some(seed),
nonce_authority: Some(authority_pubkey),
lamports: 241,
};
process_command(&creator_config).unwrap();
check_balance(241, &rpc_client, &nonce_address);
check_balance(42, &rpc_client, &offline_nonce_authority_signer.pubkey());
check_balance(4000, &rpc_client, &online_nonce_creator_signer.pubkey());
check_balance(0, &rpc_client, &to_address);
// Fetch nonce hash
let nonce_hash = nonce::get_account(&rpc_client, &nonce_address)
.and_then(|ref a| nonce::data_from_account(a))
.unwrap()
.blockhash;
// Test by creating transfer TX with nonce, fully offline
let mut authority_config = CliConfig::default();
authority_config.json_rpc_url = String::default();
authority_config.signers = vec![&offline_nonce_authority_signer];
// Verify we cannot contact the cluster
authority_config.command = CliCommand::ClusterVersion;
process_command(&authority_config).unwrap_err();
authority_config.command = CliCommand::Transfer {
lamports: 10,
to: to_address,
from: 0,
sign_only: true,
no_wait: false,
blockhash_query: BlockhashQuery::None(nonce_hash),
nonce_account: Some(nonce_address),
nonce_authority: 0,
fee_payer: 0,
};
let sign_only_reply = process_command(&authority_config).unwrap();
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
let authority_presigner = sign_only.presigner_of(&authority_pubkey).unwrap();
assert_eq!(sign_only.blockhash, nonce_hash);
// And submit it
let mut submit_config = CliConfig::default();
submit_config.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
submit_config.signers = vec![&authority_presigner];
submit_config.command = CliCommand::Transfer {
lamports: 10,
to: to_address,
from: 0,
sign_only: false,
no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_address),
sign_only.blockhash,
),
nonce_account: Some(nonce_address),
nonce_authority: 0,
fee_payer: 0,
};
process_command(&submit_config).unwrap();
check_balance(241, &rpc_client, &nonce_address);
check_balance(31, &rpc_client, &offline_nonce_authority_signer.pubkey());
check_balance(4000, &rpc_client, &online_nonce_creator_signer.pubkey());
check_balance(10, &rpc_client, &to_address);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
}

View File

@ -1,33 +1,22 @@
use chrono::prelude::*;
use serde_json::Value;
use solana_clap_utils::keypair::presigner_from_pubkey_sigs;
use solana_cli::{
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig, PayCommand},
offline::{parse_sign_only_reply_string, BlockhashQuery},
nonce,
offline::{
blockhash_query::{self, BlockhashQuery},
parse_sign_only_reply_string,
},
};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::TestValidator;
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::{
account_utils::StateMut,
fee_calculator::FeeCalculator,
nonce_state::NonceState,
nonce::State as NonceState,
pubkey::Pubkey,
signature::{read_keypair_file, write_keypair, Keypair, Signer},
signature::{Keypair, Signer},
};
use std::fs::remove_dir_all;
use std::sync::mpsc::channel;
#[cfg(test)]
use solana_core::validator::new_validator_for_tests;
use std::rc::Rc;
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| {
@ -44,7 +33,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();
@ -52,32 +47,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();
@ -89,7 +88,7 @@ fn test_cli_timestamp_tx() {
lamports: 10,
to: bob_pubkey,
timestamp: Some(dt),
timestamp_pubkey: Some(config_witness.keypair.pubkey()),
timestamp_pubkey: Some(config_witness.signers[0].pubkey()),
..PayCommand::default()
});
let sig_response = process_command(&config_payer);
@ -101,7 +100,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
@ -109,7 +108,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
@ -119,7 +118,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();
@ -127,30 +132,34 @@ fn test_cli_witness_tx() {
let faucet_addr = receiver.recv().unwrap();
let rpc_client = RpcClient::new_socket(leader_data.rpc);
let default_signer0 = Keypair::new();
let default_signer1 = Keypair::new();
let mut config_payer = CliConfig::default();
config_payer.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
config_payer.signers = vec![&default_signer0];
let mut config_witness = CliConfig::default();
config_witness.json_rpc_url = config_payer.json_rpc_url.clone();
config_witness.signers = vec![&default_signer1];
assert_ne!(
config_payer.keypair.pubkey(),
config_witness.keypair.pubkey()
config_payer.signers[0].pubkey(),
config_witness.signers[0].pubkey()
);
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config_payer.keypair.pubkey(),
&config_payer.signers[0].pubkey(),
50,
)
.unwrap();
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config_witness.keypair.pubkey(),
&config_witness.signers[0].pubkey(),
1,
)
.unwrap();
@ -159,7 +168,7 @@ fn test_cli_witness_tx() {
config_payer.command = CliCommand::Pay(PayCommand {
lamports: 10,
to: bob_pubkey,
witnesses: Some(vec![config_witness.keypair.pubkey()]),
witnesses: Some(vec![config_witness.signers[0].pubkey()]),
..PayCommand::default()
});
let sig_response = process_command(&config_payer);
@ -171,7 +180,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
@ -179,7 +188,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
@ -189,7 +198,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();
@ -197,23 +212,27 @@ fn test_cli_cancel_tx() {
let faucet_addr = receiver.recv().unwrap();
let rpc_client = RpcClient::new_socket(leader_data.rpc);
let default_signer0 = Keypair::new();
let default_signer1 = Keypair::new();
let mut config_payer = CliConfig::default();
config_payer.json_rpc_url =
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
config_payer.signers = vec![&default_signer0];
let mut config_witness = CliConfig::default();
config_witness.json_rpc_url = config_payer.json_rpc_url.clone();
config_witness.signers = vec![&default_signer1];
assert_ne!(
config_payer.keypair.pubkey(),
config_witness.keypair.pubkey()
config_payer.signers[0].pubkey(),
config_witness.signers[0].pubkey()
);
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config_payer.keypair.pubkey(),
&config_payer.signers[0].pubkey(),
50,
)
.unwrap();
@ -222,7 +241,7 @@ fn test_cli_cancel_tx() {
config_payer.command = CliCommand::Pay(PayCommand {
lamports: 10,
to: bob_pubkey,
witnesses: Some(vec![config_witness.keypair.pubkey()]),
witnesses: Some(vec![config_witness.signers[0].pubkey()]),
cancelable: true,
..PayCommand::default()
});
@ -235,7 +254,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
@ -243,7 +262,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
@ -253,7 +272,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();
@ -261,22 +286,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();
@ -284,41 +313,43 @@ fn test_offline_pay_tx() {
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config_online.keypair.pubkey(),
&config_online.signers[0].pubkey(),
50,
)
.unwrap();
check_balance(50, &rpc_client, &config_offline.keypair.pubkey());
check_balance(50, &rpc_client, &config_online.keypair.pubkey());
check_balance(50, &rpc_client, &config_offline.signers[0].pubkey());
check_balance(50, &rpc_client, &config_online.signers[0].pubkey());
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
config_offline.command = CliCommand::Pay(PayCommand {
lamports: 10,
to: bob_pubkey,
blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()),
blockhash_query: BlockhashQuery::None(blockhash),
sign_only: true,
..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 (blockhash, signers) = parse_sign_only_reply_string(&sig_response);
let offline_presigner =
presigner_from_pubkey_sigs(&config_offline.keypair.pubkey(), &signers).unwrap();
let online_pubkey = config_online.keypair.pubkey();
config_online.keypair = offline_presigner.into();
let sign_only = parse_sign_only_reply_string(&sig_response);
assert!(sign_only.has_all_signers());
let offline_presigner = sign_only
.presigner_of(&config_offline.signers[0].pubkey())
.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,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash),
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
..PayCommand::default()
});
process_command(&config_online).unwrap();
check_balance(40, &rpc_client, &config_offline.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);
@ -330,15 +361,23 @@ fn test_offline_pay_tx() {
fn test_nonced_pay_tx() {
solana_logger::setup();
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 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())
@ -347,59 +386,59 @@ fn test_nonced_pay_tx() {
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
50 + minimum_nonce_balance,
)
.unwrap();
check_balance(
50 + minimum_nonce_balance,
&rpc_client,
&config.keypair.pubkey(),
&config.signers[0].pubkey(),
);
// Create nonce account
let nonce_account = Keypair::new();
let (nonce_keypair_file, mut tmp_file) = make_tmp_file();
write_keypair(&nonce_account, tmp_file.as_file_mut()).unwrap();
config.command = CliCommand::CreateNonceAccount {
nonce_account: Rc::new(read_keypair_file(&nonce_keypair_file).unwrap().into()),
nonce_account: 1,
seed: None,
nonce_authority: Some(config.keypair.pubkey()),
nonce_authority: Some(config.signers[0].pubkey()),
lamports: minimum_nonce_balance,
};
config.signers.push(&nonce_account);
process_command(&config).unwrap();
check_balance(50, &rpc_client, &config.keypair.pubkey());
check_balance(50, &rpc_client, &config.signers[0].pubkey());
check_balance(minimum_nonce_balance, &rpc_client, &nonce_account.pubkey());
// Fetch nonce hash
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 nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
.and_then(|ref a| nonce::data_from_account(a))
.unwrap()
.blockhash;
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),
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
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.keypair.pubkey());
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"),
}
let nonce_hash2 = nonce::get_account(&rpc_client, &nonce_account.pubkey())
.and_then(|ref a| nonce::data_from_account(a))
.unwrap()
.blockhash;
assert_ne!(nonce_hash, nonce_hash2);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();

View File

@ -1,13 +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 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();
@ -20,6 +26,8 @@ fn test_cli_request_airdrop() {
pubkey: None,
lamports: 50,
};
let keypair = Keypair::new();
bob_config.signers = vec![&keypair];
let sig_response = process_command(&bob_config);
sig_response.unwrap();
@ -27,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

View File

@ -1,31 +1,20 @@
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},
nonce,
offline::{
blockhash_query::{self, BlockhashQuery},
parse_sign_only_reply_string,
},
};
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,
nonce::State as NonceState,
pubkey::Pubkey,
signature::{keypair_from_seed, read_keypair_file, write_keypair, Signer},
signature::{keypair_from_seed, Keypair, NullSigner, Signer},
};
use std::fs::remove_dir_all;
use std::sync::mpsc::channel;
#[cfg(test)]
use solana_core::validator::new_validator_for_tests_ex;
use std::rc::Rc;
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| {
@ -42,7 +31,16 @@ fn check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) {
#[test]
fn test_transfer() {
let (server, leader_data, mint_keypair, ledger_path, _) = new_validator_for_tests_ex(1, 42_000);
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);
@ -50,13 +48,15 @@ fn test_transfer() {
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.keypair.pubkey();
let sender_pubkey = config.signers[0].pubkey();
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
println!("sender: {:?}", sender_pubkey);
println!("recipient: {:?}", recipient_pubkey);
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 50_000).unwrap();
check_balance(50_000, &rpc_client, &sender_pubkey);
@ -66,12 +66,13 @@ fn test_transfer() {
config.command = CliCommand::Transfer {
lamports: 10,
to: recipient_pubkey,
from: None,
from: 0,
sign_only: false,
blockhash_query: BlockhashQuery::All,
no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: None,
fee_payer: None,
nonce_authority: 0,
fee_payer: 0,
};
process_command(&config).unwrap();
check_balance(49_989, &rpc_client, &sender_pubkey);
@ -79,12 +80,12 @@ fn test_transfer() {
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.keypair.pubkey();
println!("offline: {:?}", offline_pubkey);
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);
@ -93,25 +94,29 @@ fn test_transfer() {
offline.command = CliCommand::Transfer {
lamports: 10,
to: recipient_pubkey,
from: None,
from: 0,
sign_only: true,
blockhash_query: BlockhashQuery::None(blockhash, FeeCalculator::default()),
no_wait: false,
blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None,
nonce_authority: None,
fee_payer: 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();
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
assert!(sign_only.has_all_signers());
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
config.signers = vec![&offline_presigner];
config.command = CliCommand::Transfer {
lamports: 10,
to: recipient_pubkey,
from: Some(offline_presigner.clone().into()),
from: 0,
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash),
no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
nonce_account: None,
nonce_authority: None,
fee_payer: Some(offline_presigner.clone().into()),
nonce_authority: 0,
fee_payer: 0,
};
process_command(&config).unwrap();
check_balance(39, &rpc_client, &offline_pubkey);
@ -119,13 +124,12 @@ fn test_transfer() {
// Create nonce account
let nonce_account = keypair_from_seed(&[3u8; 32]).unwrap();
let (nonce_account_file, mut tmp_file) = make_tmp_file();
write_keypair(&nonce_account, tmp_file.as_file_mut()).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: Rc::new(read_keypair_file(&nonce_account_file).unwrap().into()),
nonce_account: 1,
seed: None,
nonce_authority: None,
lamports: minimum_nonce_balance,
@ -134,75 +138,83 @@ fn test_transfer() {
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"),
};
let nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
.and_then(|ref a| nonce::data_from_account(a))
.unwrap()
.blockhash;
// Nonced transfer
config.signers = vec![&default_signer];
config.command = CliCommand::Transfer {
lamports: 10,
to: recipient_pubkey,
from: None,
from: 0,
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(nonce_hash),
no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
nonce_hash,
),
nonce_account: Some(nonce_account.pubkey()),
nonce_authority: None,
fee_payer: None,
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"),
};
let new_nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
.and_then(|ref a| nonce::data_from_account(a))
.unwrap()
.blockhash;
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: None,
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"),
};
let nonce_hash = nonce::get_account(&rpc_client, &nonce_account.pubkey())
.and_then(|ref a| nonce::data_from_account(a))
.unwrap()
.blockhash;
// Offline, nonced transfer
offline.signers = vec![&default_offline_signer];
offline.command = CliCommand::Transfer {
lamports: 10,
to: recipient_pubkey,
from: None,
from: 0,
sign_only: true,
blockhash_query: BlockhashQuery::None(nonce_hash, FeeCalculator::default()),
no_wait: false,
blockhash_query: BlockhashQuery::None(nonce_hash),
nonce_account: Some(nonce_account.pubkey()),
nonce_authority: None,
fee_payer: 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();
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
assert!(sign_only.has_all_signers());
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
config.signers = vec![&offline_presigner];
config.command = CliCommand::Transfer {
lamports: 10,
to: recipient_pubkey,
from: Some(offline_presigner.clone().into()),
from: 0,
sign_only: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash),
no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
sign_only.blockhash,
),
nonce_account: Some(nonce_account.pubkey()),
nonce_authority: Some(offline_presigner.clone().into()),
fee_payer: Some(offline_presigner.clone().into()),
nonce_authority: 0,
fee_payer: 0,
};
process_command(&config).unwrap();
check_balance(28, &rpc_client, &offline_pubkey);
@ -211,3 +223,117 @@ fn test_transfer() {
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
}
#[test]
fn test_transfer_multisession_signing() {
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 to_pubkey = Pubkey::new(&[1u8; 32]);
let offline_from_signer = keypair_from_seed(&[2u8; 32]).unwrap();
let offline_fee_payer_signer = keypair_from_seed(&[3u8; 32]).unwrap();
let from_null_signer = NullSigner::new(&offline_from_signer.pubkey());
// Setup accounts
let rpc_client = RpcClient::new_socket(leader_data.rpc);
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_from_signer.pubkey(), 43)
.unwrap();
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&offline_fee_payer_signer.pubkey(),
3,
)
.unwrap();
check_balance(43, &rpc_client, &offline_from_signer.pubkey());
check_balance(3, &rpc_client, &offline_fee_payer_signer.pubkey());
check_balance(0, &rpc_client, &to_pubkey);
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
// Offline fee-payer signs first
let mut fee_payer_config = CliConfig::default();
fee_payer_config.json_rpc_url = String::default();
fee_payer_config.signers = vec![&offline_fee_payer_signer, &from_null_signer];
// Verify we cannot contact the cluster
fee_payer_config.command = CliCommand::ClusterVersion;
process_command(&fee_payer_config).unwrap_err();
fee_payer_config.command = CliCommand::Transfer {
lamports: 42,
to: to_pubkey,
from: 1,
sign_only: true,
no_wait: false,
blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
};
let sign_only_reply = process_command(&fee_payer_config).unwrap();
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
assert!(!sign_only.has_all_signers());
let fee_payer_presigner = sign_only
.presigner_of(&offline_fee_payer_signer.pubkey())
.unwrap();
// Now the offline fund source
let mut from_config = CliConfig::default();
from_config.json_rpc_url = String::default();
from_config.signers = vec![&fee_payer_presigner, &offline_from_signer];
// Verify we cannot contact the cluster
from_config.command = CliCommand::ClusterVersion;
process_command(&from_config).unwrap_err();
from_config.command = CliCommand::Transfer {
lamports: 42,
to: to_pubkey,
from: 1,
sign_only: true,
no_wait: false,
blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
};
let sign_only_reply = process_command(&from_config).unwrap();
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
assert!(sign_only.has_all_signers());
let from_presigner = sign_only
.presigner_of(&offline_from_signer.pubkey())
.unwrap();
// Finally submit to the cluster
let mut config = CliConfig::default();
config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
config.signers = vec![&fee_payer_presigner, &from_presigner];
config.command = CliCommand::Transfer {
lamports: 42,
to: to_pubkey,
from: 1,
sign_only: false,
no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
};
process_command(&config).unwrap();
check_balance(1, &rpc_client, &offline_from_signer.pubkey());
check_balance(1, &rpc_client, &offline_fee_payer_signer.pubkey());
check_balance(42, &rpc_client, &to_pubkey);
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
}

118
cli/tests/vote.rs Normal file
View File

@ -0,0 +1,118 @@
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::{
account_utils::StateMut,
pubkey::Pubkey,
signature::{Keypair, Signer},
};
use solana_vote_program::vote_state::{VoteAuthorize, VoteState, VoteStateVersions};
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_vote_authorize_and_withdraw() {
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];
request_and_confirm_airdrop(
&rpc_client,
&faucet_addr,
&config.signers[0].pubkey(),
100_000,
)
.unwrap();
// Create vote account
let vote_account_keypair = Keypair::new();
let vote_account_pubkey = vote_account_keypair.pubkey();
config.signers = vec![&default_signer, &vote_account_keypair];
config.command = CliCommand::CreateVoteAccount {
seed: None,
identity_account: 0,
authorized_voter: None,
authorized_withdrawer: Some(config.signers[0].pubkey()),
commission: 0,
};
process_command(&config).unwrap();
let vote_account = rpc_client
.get_account(&vote_account_keypair.pubkey())
.unwrap();
let vote_state: VoteStateVersions = vote_account.state().unwrap();
let authorized_withdrawer = vote_state.convert_to_current().authorized_withdrawer;
assert_eq!(authorized_withdrawer, config.signers[0].pubkey());
let expected_balance = rpc_client
.get_minimum_balance_for_rent_exemption(VoteState::size_of())
.unwrap()
.max(1);
check_balance(expected_balance, &rpc_client, &vote_account_pubkey);
// Authorize vote account withdrawal to another signer
let withdraw_authority = Keypair::new();
config.signers = vec![&default_signer];
config.command = CliCommand::VoteAuthorize {
vote_account_pubkey,
new_authorized_pubkey: withdraw_authority.pubkey(),
vote_authorize: VoteAuthorize::Withdrawer,
};
process_command(&config).unwrap();
let vote_account = rpc_client
.get_account(&vote_account_keypair.pubkey())
.unwrap();
let vote_state: VoteStateVersions = vote_account.state().unwrap();
let authorized_withdrawer = vote_state.convert_to_current().authorized_withdrawer;
assert_eq!(authorized_withdrawer, withdraw_authority.pubkey());
// Withdraw from vote account
let destination_account = Pubkey::new_rand(); // Send withdrawal to new account to make balance check easy
config.signers = vec![&default_signer, &withdraw_authority];
config.command = CliCommand::WithdrawFromVoteAccount {
vote_account_pubkey,
withdraw_authority: 1,
lamports: 100,
destination_account_pubkey: destination_account,
};
process_command(&config).unwrap();
check_balance(expected_balance - 100, &rpc_client, &vote_account_pubkey);
check_balance(100, &rpc_client, &destination_account);
// Re-assign validator identity
let new_identity_keypair = Keypair::new();
config.signers.push(&new_identity_keypair);
config.command = CliCommand::VoteUpdateValidator {
vote_account_pubkey,
new_identity_account: 2,
};
process_command(&config).unwrap();
server.close().unwrap();
remove_dir_all(ledger_path).unwrap();
}

View File

@ -1,6 +1,6 @@
[package]
name = "solana-client"
version = "1.0.0"
version = "1.0.25"
description = "Solana Client"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -11,6 +11,7 @@ edition = "2018"
[dependencies]
bincode = "1.2.1"
bs58 = "0.3.0"
indicatif = "0.14.0"
jsonrpc-core = "14.0.5"
log = "0.4.8"
rayon = "1.2.0"
@ -18,8 +19,10 @@ reqwest = { version = "0.10.1", default-features = false, features = ["blocking"
serde = "1.0.104"
serde_derive = "1.0.103"
serde_json = "1.0.46"
solana-net-utils = { path = "../net-utils", version = "1.0.0" }
solana-sdk = { path = "../sdk", version = "1.0.0" }
solana-net-utils = { path = "../net-utils", version = "1.0.25" }
solana-sdk = { path = "../sdk", version = "1.0.25" }
solana-transaction-status = { path = "../transaction-status", version = "1.0.25" }
solana-vote-program = { path = "../programs/vote", version = "1.0.25" }
thiserror = "1.0"
tungstenite = "0.10.1"
url = "2.1.1"
@ -28,4 +31,7 @@ url = "2.1.1"
assert_matches = "1.3.0"
jsonrpc-core = "14.0.5"
jsonrpc-http-server = "14.0.6"
solana-logger = { path = "../logger", version = "1.0.0" }
solana-logger = { path = "../logger", version = "1.0.25" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -1,20 +1,161 @@
use crate::rpc_request;
use solana_sdk::{signature::SignerError, transaction::TransactionError};
use std::{fmt, io};
use solana_sdk::{
signature::SignerError, transaction::TransactionError, transport::TransportError,
};
use std::io;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ClientError {
pub enum ClientErrorKind {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Reqwest(#[from] reqwest::Error),
#[error(transparent)]
RpcError(#[from] rpc_request::RpcError),
#[error(transparent)]
SerdeJson(#[from] serde_json::error::Error),
#[error(transparent)]
SigningError(#[from] SignerError),
#[error(transparent)]
TransactionError(#[from] TransactionError),
#[error("Custom: {0}")]
Custom(String),
}
impl fmt::Display for ClientError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "solana client error")
impl From<TransportError> for ClientErrorKind {
fn from(err: TransportError) -> Self {
match err {
TransportError::IoError(err) => Self::Io(err),
TransportError::TransactionError(err) => Self::TransactionError(err),
TransportError::Custom(err) => Self::Custom(err),
}
}
}
impl Into<TransportError> for ClientErrorKind {
fn into(self) -> TransportError {
match self {
Self::Io(err) => TransportError::IoError(err),
Self::TransactionError(err) => TransportError::TransactionError(err),
Self::Reqwest(err) => TransportError::Custom(format!("{:?}", err)),
Self::RpcError(err) => TransportError::Custom(format!("{:?}", err)),
Self::SerdeJson(err) => TransportError::Custom(format!("{:?}", err)),
Self::SigningError(err) => TransportError::Custom(format!("{:?}", err)),
Self::Custom(err) => TransportError::Custom(format!("{:?}", err)),
}
}
}
#[derive(Error, Debug)]
#[error("{kind}")]
pub struct ClientError {
command: Option<&'static str>,
#[source]
#[error(transparent)]
kind: ClientErrorKind,
}
impl ClientError {
pub fn new_with_command(kind: ClientErrorKind, command: &'static str) -> Self {
Self {
command: Some(command),
kind,
}
}
pub fn into_with_command(self, command: &'static str) -> Self {
Self {
command: Some(command),
..self
}
}
pub fn command(&self) -> Option<&'static str> {
self.command
}
pub fn kind(&self) -> &ClientErrorKind {
&self.kind
}
}
impl From<ClientErrorKind> for ClientError {
fn from(kind: ClientErrorKind) -> Self {
Self {
command: None,
kind,
}
}
}
impl From<TransportError> for ClientError {
fn from(err: TransportError) -> Self {
Self {
command: None,
kind: err.into(),
}
}
}
impl Into<TransportError> for ClientError {
fn into(self) -> TransportError {
self.kind.into()
}
}
impl From<std::io::Error> for ClientError {
fn from(err: std::io::Error) -> Self {
Self {
command: None,
kind: err.into(),
}
}
}
impl From<reqwest::Error> for ClientError {
fn from(err: reqwest::Error) -> Self {
Self {
command: None,
kind: err.into(),
}
}
}
impl From<rpc_request::RpcError> for ClientError {
fn from(err: rpc_request::RpcError) -> Self {
Self {
command: None,
kind: err.into(),
}
}
}
impl From<serde_json::error::Error> for ClientError {
fn from(err: serde_json::error::Error) -> Self {
Self {
command: None,
kind: err.into(),
}
}
}
impl From<SignerError> for ClientError {
fn from(err: SignerError) -> Self {
Self {
command: None,
kind: err.into(),
}
}
}
impl From<TransactionError> for ClientError {
fn from(err: TransactionError) -> Self {
Self {
command: None,
kind: err.into(),
}
}
}
pub type Result<T> = std::result::Result<T, ClientError>;

View File

@ -1,4 +1,4 @@
use crate::{client_error::ClientError, rpc_request::RpcRequest};
use crate::{client_error::Result, rpc_request::RpcRequest};
pub(crate) trait GenericRpcClientRequest {
fn send(
@ -6,5 +6,5 @@ pub(crate) trait GenericRpcClientRequest {
request: &RpcRequest,
params: serde_json::Value,
retries: usize,
) -> Result<serde_json::Value, ClientError>;
) -> Result<serde_json::Value>;
}

View File

@ -8,6 +8,7 @@ pub mod perf_utils;
pub mod pubsub_client;
pub mod rpc_client;
pub mod rpc_client_request;
pub mod rpc_config;
pub mod rpc_request;
pub mod rpc_response;
pub mod thin_client;

View File

@ -1,15 +1,16 @@
use crate::{
client_error::ClientError,
client_error::Result,
generic_rpc_client_request::GenericRpcClientRequest,
rpc_request::RpcRequest,
rpc_response::{Response, RpcResponseContext},
};
use serde_json::{Number, Value};
use solana_sdk::{
fee_calculator::FeeCalculator,
fee_calculator::{FeeCalculator, FeeRateGovernor},
instruction::InstructionError,
transaction::{self, TransactionError},
};
use solana_transaction_status::TransactionStatus;
use std::{collections::HashMap, sync::RwLock};
pub const PUBKEY: &str = "7RoSF9fUmdphVCpabEoefH81WwrW7orsWonXWqTXkKV8";
@ -39,9 +40,9 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
fn send(
&self,
request: &RpcRequest,
params: serde_json::Value,
_params: serde_json::Value,
_retries: usize,
) -> Result<serde_json::Value, ClientError> {
) -> Result<serde_json::Value> {
if let Some(value) = self.mocks.write().unwrap().remove(request) {
return Ok(value);
}
@ -49,17 +50,6 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
return Ok(Value::Null);
}
let val = match request {
RpcRequest::ConfirmTransaction => {
if let Some(params_array) = params.as_array() {
if let Value::String(param_string) = &params_array[0] {
Value::Bool(param_string == SIGNATURE)
} else {
Value::Null
}
} else {
Value::Null
}
}
RpcRequest::GetBalance => serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
value: Value::Number(Number::from(50)),
@ -71,20 +61,47 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
serde_json::to_value(FeeCalculator::default()).unwrap(),
),
})?,
RpcRequest::GetSignatureStatus => {
let response: Option<transaction::Result<()>> = if self.url == "account_in_use" {
Some(Err(TransactionError::AccountInUse))
RpcRequest::GetFeeCalculatorForBlockhash => {
let value = if self.url == "blockhash_expired" {
Value::Null
} else {
serde_json::to_value(Some(FeeCalculator::default())).unwrap()
};
serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
value,
})?
}
RpcRequest::GetFeeRateGovernor => serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
value: serde_json::to_value(FeeRateGovernor::default()).unwrap(),
})?,
RpcRequest::GetSignatureStatuses => {
let status: transaction::Result<()> = if self.url == "account_in_use" {
Err(TransactionError::AccountInUse)
} else if self.url == "instruction_error" {
Some(Err(TransactionError::InstructionError(
Err(TransactionError::InstructionError(
0,
InstructionError::UninitializedAccount,
)))
} else if self.url == "sig_not_found" {
))
} else {
Ok(())
};
let status = if self.url == "sig_not_found" {
None
} else {
Some(Ok(()))
let err = status.clone().err();
Some(TransactionStatus {
status,
slot: 1,
confirmations: None,
err,
})
};
serde_json::to_value(response).unwrap()
serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
value: vec![status],
})?
}
RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)),
RpcRequest::GetSlot => Value::Number(Number::from(0)),

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
use crate::{
client_error::ClientError,
client_error::Result,
generic_rpc_client_request::GenericRpcClientRequest,
rpc_request::{RpcError, RpcRequest},
};
@ -34,7 +34,7 @@ impl GenericRpcClientRequest for RpcClientRequest {
request: &RpcRequest,
params: serde_json::Value,
mut retries: usize,
) -> Result<serde_json::Value, ClientError> {
) -> Result<serde_json::Value> {
// Concurrent requests are not supported so reuse the same request id for all requests
let request_id = 1;

31
client/src/rpc_config.rs Normal file
View File

@ -0,0 +1,31 @@
use solana_sdk::commitment_config::CommitmentConfig;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcSignatureStatusConfig {
pub search_transaction_history: Option<bool>,
// DEPRECATED
#[serde(flatten)]
pub commitment: Option<CommitmentConfig>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcSimulateTransactionConfig {
pub sig_verify: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum RpcLargestAccountsFilter {
Circulating,
NonCirculating,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcLargestAccountsConfig {
#[serde(flatten)]
pub commitment: Option<CommitmentConfig>,
pub filter: Option<RpcLargestAccountsFilter>,
}

View File

@ -1,9 +1,8 @@
use serde_json::{json, Value};
use std::{error, fmt};
use thiserror::Error;
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum RpcRequest {
ConfirmTransaction,
DeregisterNode,
ValidatorExit,
GetAccountInfo,
@ -12,15 +11,20 @@ pub enum RpcRequest {
GetClusterNodes,
GetConfirmedBlock,
GetConfirmedBlocks,
GetConfirmedSignaturesForAddress,
GetConfirmedTransaction,
GetEpochInfo,
GetEpochSchedule,
GetGenesisHash,
GetIdentity,
GetInflation,
GetLargestAccounts,
GetLeaderSchedule,
GetNumBlocksSinceSignatureConfirmation,
GetProgramAccounts,
GetRecentBlockhash,
GetSignatureStatus,
GetFeeCalculatorForBlockhash,
GetFeeRateGovernor,
GetSignatureStatuses,
GetSlot,
GetSlotLeader,
GetStorageTurn,
@ -33,6 +37,7 @@ pub enum RpcRequest {
RegisterNode,
RequestAirdrop,
SendTransaction,
SimulateTransaction,
SignVote,
GetMinimumBalanceForRentExemption,
MinimumLedgerSlot,
@ -42,7 +47,6 @@ impl RpcRequest {
pub(crate) fn build_request_json(&self, id: u64, params: Value) -> Value {
let jsonrpc = "2.0";
let method = match self {
RpcRequest::ConfirmTransaction => "confirmTransaction",
RpcRequest::DeregisterNode => "deregisterNode",
RpcRequest::ValidatorExit => "validatorExit",
RpcRequest::GetAccountInfo => "getAccountInfo",
@ -51,17 +55,20 @@ impl RpcRequest {
RpcRequest::GetClusterNodes => "getClusterNodes",
RpcRequest::GetConfirmedBlock => "getConfirmedBlock",
RpcRequest::GetConfirmedBlocks => "getConfirmedBlocks",
RpcRequest::GetConfirmedSignaturesForAddress => "getConfirmedSignaturesForAddress",
RpcRequest::GetConfirmedTransaction => "getConfirmedTransaction",
RpcRequest::GetEpochInfo => "getEpochInfo",
RpcRequest::GetEpochSchedule => "getEpochSchedule",
RpcRequest::GetGenesisHash => "getGenesisHash",
RpcRequest::GetIdentity => "getIdentity",
RpcRequest::GetInflation => "getInflation",
RpcRequest::GetLargestAccounts => "getLargestAccounts",
RpcRequest::GetLeaderSchedule => "getLeaderSchedule",
RpcRequest::GetNumBlocksSinceSignatureConfirmation => {
"getNumBlocksSinceSignatureConfirmation"
}
RpcRequest::GetProgramAccounts => "getProgramAccounts",
RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
RpcRequest::GetSignatureStatus => "getSignatureStatus",
RpcRequest::GetFeeCalculatorForBlockhash => "getFeeCalculatorForBlockhash",
RpcRequest::GetFeeRateGovernor => "getFeeRateGovernor",
RpcRequest::GetSignatureStatuses => "getSignatureStatuses",
RpcRequest::GetSlot => "getSlot",
RpcRequest::GetSlotLeader => "getSlotLeader",
RpcRequest::GetStorageTurn => "getStorageTurn",
@ -74,6 +81,7 @@ impl RpcRequest {
RpcRequest::RegisterNode => "registerNode",
RpcRequest::RequestAirdrop => "requestAirdrop",
RpcRequest::SendTransaction => "sendTransaction",
RpcRequest::SimulateTransaction => "simulateTransaction",
RpcRequest::SignVote => "signVote",
RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption",
RpcRequest::MinimumLedgerSlot => "minimumLedgerSlot",
@ -87,26 +95,16 @@ impl RpcRequest {
}
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Error)]
pub enum RpcError {
#[error("rpc request error: {0}")]
RpcRequestError(String),
}
impl fmt::Display for RpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid")
}
}
impl error::Error for RpcError {
fn description(&self) -> &str {
"invalid"
}
fn cause(&self) -> Option<&dyn error::Error> {
// Generic error, underlying cause isn't tracked.
None
}
#[error("parse error: expected {0}")]
ParseError(String), /* "expected" */
// Anything in a `ForUser` needs to die. The caller should be
// deciding what to tell their user
#[error("{0}")]
ForUser(String), /* "direct-to-user message" */
}
#[cfg(test)]
@ -123,7 +121,7 @@ mod tests {
assert_eq!(request["params"], json!([addr]));
let test_request = RpcRequest::GetBalance;
let request = test_request.build_request_json(1, json!([addr]));
let request = test_request.build_request_json(1, json!([addr.clone()]));
assert_eq!(request["method"], "getBalance");
let test_request = RpcRequest::GetEpochInfo;
@ -138,6 +136,14 @@ mod tests {
let request = test_request.build_request_json(1, Value::Null);
assert_eq!(request["method"], "getRecentBlockhash");
let test_request = RpcRequest::GetFeeCalculatorForBlockhash;
let request = test_request.build_request_json(1, json!([addr.clone()]));
assert_eq!(request["method"], "getFeeCalculatorForBlockhash");
let test_request = RpcRequest::GetFeeRateGovernor;
let request = test_request.build_request_json(1, Value::Null);
assert_eq!(request["method"], "getFeeRateGovernor");
let test_request = RpcRequest::GetSlot;
let request = test_request.build_request_json(1, Value::Null);
assert_eq!(request["method"], "getSlot");

View File

@ -1,18 +1,14 @@
use crate::rpc_request::RpcError;
use bincode::serialize;
use jsonrpc_core::Result as JsonResult;
use crate::{client_error, rpc_request::RpcError};
use solana_sdk::{
account::Account,
clock::{Epoch, Slot},
fee_calculator::FeeCalculator,
message::MessageHeader,
fee_calculator::{FeeCalculator, FeeRateGovernor},
pubkey::Pubkey,
transaction::{Result, Transaction},
transaction::{Result, TransactionError},
};
use std::{collections::HashMap, io, net::SocketAddr, str::FromStr};
use std::{collections::HashMap, net::SocketAddr, str::FromStr};
pub type RpcResponseIn<T> = JsonResult<Response<T>>;
pub type RpcResponse<T> = io::Result<Response<T>>;
pub type RpcResult<T> = client_error::Result<Response<T>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RpcResponseContext {
@ -32,119 +28,6 @@ pub struct RpcBlockCommitment<T> {
pub total_stake: u64,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct RpcReward {
pub pubkey: String,
pub lamports: i64,
}
pub type RpcRewards = Vec<RpcReward>;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcConfirmedBlock {
pub previous_blockhash: String,
pub blockhash: String,
pub parent_slot: Slot,
pub transactions: Vec<RpcTransactionWithStatusMeta>,
pub rewards: RpcRewards,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransactionWithStatusMeta {
pub transaction: RpcEncodedTransaction,
pub meta: Option<RpcTransactionStatus>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum RpcTransactionEncoding {
Binary,
Json,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", untagged)]
pub enum RpcEncodedTransaction {
Binary(String),
Json(RpcTransaction),
}
impl RpcEncodedTransaction {
pub fn encode(transaction: Transaction, encoding: RpcTransactionEncoding) -> Self {
if encoding == RpcTransactionEncoding::Json {
RpcEncodedTransaction::Json(RpcTransaction {
signatures: transaction
.signatures
.iter()
.map(|sig| sig.to_string())
.collect(),
message: RpcMessage {
header: transaction.message.header,
account_keys: transaction
.message
.account_keys
.iter()
.map(|pubkey| pubkey.to_string())
.collect(),
recent_blockhash: transaction.message.recent_blockhash.to_string(),
instructions: transaction
.message
.instructions
.iter()
.map(|instruction| RpcCompiledInstruction {
program_id_index: instruction.program_id_index,
accounts: instruction.accounts.clone(),
data: bs58::encode(instruction.data.clone()).into_string(),
})
.collect(),
},
})
} else {
RpcEncodedTransaction::Binary(
bs58::encode(serialize(&transaction).unwrap()).into_string(),
)
}
}
}
/// A duplicate representation of a Transaction for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransaction {
pub signatures: Vec<String>,
pub message: RpcMessage,
}
/// A duplicate representation of a Message for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcMessage {
pub header: MessageHeader,
pub account_keys: Vec<String>,
pub recent_blockhash: String,
pub instructions: Vec<RpcCompiledInstruction>,
}
/// A duplicate representation of a Message for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcCompiledInstruction {
pub program_id_index: u8,
pub accounts: Vec<u8>,
pub data: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransactionStatus {
pub status: Result<()>,
pub fee: u64,
pub pre_balances: Vec<u64>,
pub post_balances: Vec<u64>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcBlockhashFeeCalculator {
@ -152,6 +35,18 @@ pub struct RpcBlockhashFeeCalculator {
pub fee_calculator: FeeCalculator,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcFeeCalculator {
pub fee_calculator: FeeCalculator,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcFeeRateGovernor {
pub fee_rate_governor: FeeRateGovernor,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcKeyedAccount {
@ -159,6 +54,12 @@ pub struct RpcKeyedAccount {
pub account: RpcAccount,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcSignatureResult {
pub err: Option<TransactionError>,
}
/// A duplicate representation of a Message for pretty JSON serialization
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
@ -207,6 +108,8 @@ pub struct RpcContactInfo {
pub tpu: Option<SocketAddr>,
/// JSON RPC port
pub rpc: Option<SocketAddr>,
/// Software version
pub version: Option<String>,
}
/// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot
@ -235,6 +138,13 @@ pub struct RpcVersionInfo {
pub solana_core: String,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct RpcIdentity {
/// The current node identity pubkey
pub identity: String,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcVoteAccountStatus {
@ -284,3 +194,19 @@ pub struct RpcStorageTurn {
pub blockhash: String,
pub slot: Slot,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RpcAccountBalance {
pub address: String,
pub lamports: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcSupply {
pub total: u64,
pub circulating: u64,
pub non_circulating: u64,
pub non_circulating_accounts: Vec<String>,
}

View File

@ -11,7 +11,7 @@ use solana_sdk::{
client::{AsyncClient, Client, SyncClient},
clock::MAX_PROCESSING_AGE,
commitment_config::CommitmentConfig,
fee_calculator::FeeCalculator,
fee_calculator::{FeeCalculator, FeeRateGovernor},
hash::Hash,
instruction::Instruction,
message::Message,
@ -26,7 +26,7 @@ use solana_sdk::{
};
use std::{
io,
net::{SocketAddr, UdpSocket},
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
RwLock,
@ -188,7 +188,7 @@ impl ThinClient {
transaction: &mut Transaction,
tries: usize,
min_confirmed_blocks: usize,
) -> io::Result<Signature> {
) -> TransportResult<Signature> {
self.send_and_confirm_transaction(&[keypair], transaction, tries, min_confirmed_blocks)
}
@ -198,7 +198,7 @@ impl ThinClient {
keypair: &Keypair,
transaction: &mut Transaction,
tries: usize,
) -> io::Result<Signature> {
) -> TransportResult<Signature> {
self.send_and_confirm_transaction(&[keypair], transaction, tries, 0)
}
@ -209,7 +209,7 @@ impl ThinClient {
transaction: &mut Transaction,
tries: usize,
pending_confirmations: usize,
) -> io::Result<Signature> {
) -> TransportResult<Signature> {
for x in 0..tries {
let now = Instant::now();
let mut buf = vec![0; serialized_size(&transaction).unwrap() as usize];
@ -243,13 +243,14 @@ impl ThinClient {
}
}
info!("{} tries failed transfer to {}", x, self.tpu_addr());
let (blockhash, _fee_calculator) = self.rpc_client().get_recent_blockhash()?;
let (blockhash, _fee_calculator) = self.get_recent_blockhash()?;
transaction.sign(keypairs, blockhash);
}
Err(io::Error::new(
io::ErrorKind::Other,
format!("retry_transfer failed in {} retries", tries),
))
)
.into())
}
pub fn poll_balance_with_timeout_and_commitment(
@ -258,13 +259,15 @@ impl ThinClient {
polling_frequency: &Duration,
timeout: &Duration,
commitment_config: CommitmentConfig,
) -> io::Result<u64> {
self.rpc_client().poll_balance_with_timeout_and_commitment(
pubkey,
polling_frequency,
timeout,
commitment_config,
)
) -> TransportResult<u64> {
self.rpc_client()
.poll_balance_with_timeout_and_commitment(
pubkey,
polling_frequency,
timeout,
commitment_config,
)
.map_err(|e| e.into())
}
pub fn poll_balance_with_timeout(
@ -272,8 +275,8 @@ impl ThinClient {
pubkey: &Pubkey,
polling_frequency: &Duration,
timeout: &Duration,
) -> io::Result<u64> {
self.rpc_client().poll_balance_with_timeout_and_commitment(
) -> TransportResult<u64> {
self.poll_balance_with_timeout_and_commitment(
pubkey,
polling_frequency,
timeout,
@ -281,18 +284,18 @@ impl ThinClient {
)
}
pub fn poll_get_balance(&self, pubkey: &Pubkey) -> io::Result<u64> {
self.rpc_client()
.poll_get_balance_with_commitment(pubkey, CommitmentConfig::default())
pub fn poll_get_balance(&self, pubkey: &Pubkey) -> TransportResult<u64> {
self.poll_get_balance_with_commitment(pubkey, CommitmentConfig::default())
}
pub fn poll_get_balance_with_commitment(
&self,
pubkey: &Pubkey,
commitment_config: CommitmentConfig,
) -> io::Result<u64> {
) -> TransportResult<u64> {
self.rpc_client()
.poll_get_balance_with_commitment(pubkey, commitment_config)
.map_err(|e| e.into())
}
pub fn wait_for_balance(&self, pubkey: &Pubkey, expected_balance: Option<u64>) -> Option<u64> {
@ -321,9 +324,9 @@ impl ThinClient {
signature: &Signature,
commitment_config: CommitmentConfig,
) -> TransportResult<()> {
Ok(self
.rpc_client()
.poll_for_signature_with_commitment(signature, commitment_config)?)
self.rpc_client()
.poll_for_signature_with_commitment(signature, commitment_config)
.map_err(|e| e.into())
}
/// Check a signature in the bank. This method blocks
@ -332,16 +335,17 @@ impl ThinClient {
self.rpc_client().check_signature(signature)
}
pub fn validator_exit(&self) -> io::Result<bool> {
self.rpc_client().validator_exit()
pub fn validator_exit(&self) -> TransportResult<bool> {
self.rpc_client().validator_exit().map_err(|e| e.into())
}
pub fn get_num_blocks_since_signature_confirmation(
&mut self,
sig: &Signature,
) -> io::Result<usize> {
) -> TransportResult<usize> {
self.rpc_client()
.get_num_blocks_since_signature_confirmation(sig)
.map_err(|e| e.into())
}
}
@ -368,7 +372,7 @@ impl SyncClient for ThinClient {
keypair: &Keypair,
instruction: Instruction,
) -> TransportResult<Signature> {
let message = Message::new(vec![instruction]);
let message = Message::new(&[instruction]);
self.send_message(&[keypair], message)
}
@ -400,14 +404,14 @@ impl SyncClient for ThinClient {
pubkey: &Pubkey,
commitment_config: CommitmentConfig,
) -> TransportResult<Option<Account>> {
Ok(self
.rpc_client()
.get_account_with_commitment(pubkey, commitment_config)?
.value)
self.rpc_client()
.get_account_with_commitment(pubkey, commitment_config)
.map_err(|e| e.into())
.map(|r| r.value)
}
fn get_balance(&self, pubkey: &Pubkey) -> TransportResult<u64> {
Ok(self.rpc_client().get_balance(pubkey)?)
self.rpc_client().get_balance(pubkey).map_err(|e| e.into())
}
fn get_balance_with_commitment(
@ -415,10 +419,10 @@ impl SyncClient for ThinClient {
pubkey: &Pubkey,
commitment_config: CommitmentConfig,
) -> TransportResult<u64> {
let balance = self
.rpc_client()
.get_balance_with_commitment(pubkey, commitment_config)?;
Ok(balance.value)
self.rpc_client()
.get_balance_with_commitment(pubkey, commitment_config)
.map_err(|e| e.into())
.map(|r| r.value)
}
fn get_recent_blockhash(&self) -> TransportResult<(Hash, FeeCalculator)> {
@ -445,13 +449,29 @@ impl SyncClient for ThinClient {
}
}
fn get_fee_calculator_for_blockhash(
&self,
blockhash: &Hash,
) -> TransportResult<Option<FeeCalculator>> {
self.rpc_client()
.get_fee_calculator_for_blockhash(blockhash)
.map_err(|e| e.into())
}
fn get_fee_rate_governor(&self) -> TransportResult<FeeRateGovernor> {
self.rpc_client()
.get_fee_rate_governor()
.map_err(|e| e.into())
.map(|r| r.value)
}
fn get_signature_status(
&self,
signature: &Signature,
) -> TransportResult<Option<transaction::Result<()>>> {
let status = self
.rpc_client()
.get_signature_status(&signature.to_string())
.get_signature_status(&signature)
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
@ -468,7 +488,7 @@ impl SyncClient for ThinClient {
) -> TransportResult<Option<transaction::Result<()>>> {
let status = self
.rpc_client()
.get_signature_status_with_commitment(&signature.to_string(), commitment_config)
.get_signature_status_with_commitment(&signature, commitment_config)
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
@ -540,23 +560,26 @@ impl SyncClient for ThinClient {
signature: &Signature,
min_confirmed_blocks: usize,
) -> TransportResult<usize> {
Ok(self
.rpc_client()
.poll_for_signature_confirmation(signature, min_confirmed_blocks)?)
self.rpc_client()
.poll_for_signature_confirmation(signature, min_confirmed_blocks)
.map_err(|e| e.into())
}
fn poll_for_signature(&self, signature: &Signature) -> TransportResult<()> {
Ok(self.rpc_client().poll_for_signature(signature)?)
self.rpc_client()
.poll_for_signature(signature)
.map_err(|e| e.into())
}
fn get_new_blockhash(&self, blockhash: &Hash) -> TransportResult<(Hash, FeeCalculator)> {
let new_blockhash = self.rpc_client().get_new_blockhash(blockhash)?;
Ok(new_blockhash)
self.rpc_client()
.get_new_blockhash(blockhash)
.map_err(|e| e.into())
}
}
impl AsyncClient for ThinClient {
fn async_send_transaction(&self, transaction: Transaction) -> io::Result<Signature> {
fn async_send_transaction(&self, transaction: Transaction) -> TransportResult<Signature> {
let mut buf = vec![0; serialized_size(&transaction).unwrap() as usize];
let mut wr = std::io::Cursor::new(&mut buf[..]);
serialize_into(&mut wr, &transaction)
@ -571,7 +594,7 @@ impl AsyncClient for ThinClient {
keypairs: &T,
message: Message,
recent_blockhash: Hash,
) -> io::Result<Signature> {
) -> TransportResult<Signature> {
let transaction = Transaction::new(keypairs, message, recent_blockhash);
self.async_send_transaction(transaction)
}
@ -580,8 +603,8 @@ impl AsyncClient for ThinClient {
keypair: &Keypair,
instruction: Instruction,
recent_blockhash: Hash,
) -> io::Result<Signature> {
let message = Message::new(vec![instruction]);
) -> TransportResult<Signature> {
let message = Message::new(&[instruction]);
self.async_send_message(&[keypair], message, recent_blockhash)
}
fn async_transfer(
@ -590,7 +613,7 @@ impl AsyncClient for ThinClient {
keypair: &Keypair,
pubkey: &Pubkey,
recent_blockhash: Hash,
) -> io::Result<Signature> {
) -> TransportResult<Signature> {
let transfer_instruction =
system_instruction::transfer(&keypair.pubkey(), pubkey, lamports);
self.async_send_instruction(keypair, transfer_instruction, recent_blockhash)
@ -598,7 +621,8 @@ impl AsyncClient for ThinClient {
}
pub fn create_client((rpc, tpu): (SocketAddr, SocketAddr), range: (u16, u16)) -> ThinClient {
let (_, transactions_socket) = solana_net_utils::bind_in_range(range).unwrap();
let (_, transactions_socket) =
solana_net_utils::bind_in_range(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), range).unwrap();
ThinClient::new(rpc, tpu, transactions_socket)
}
@ -607,7 +631,8 @@ pub fn create_client_with_timeout(
range: (u16, u16),
timeout: Duration,
) -> ThinClient {
let (_, transactions_socket) = solana_net_utils::bind_in_range(range).unwrap();
let (_, transactions_socket) =
solana_net_utils::bind_in_range(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), range).unwrap();
ThinClient::new_socket_with_timeout(rpc, tpu, transactions_socket, timeout)
}

View File

@ -1,7 +1,7 @@
[package]
name = "solana-core"
description = "Blockchain, Rebuilt for Scale"
version = "1.0.0"
version = "1.0.25"
documentation = "https://docs.rs/solana"
homepage = "https://solana.com/"
readme = "../README.md"
@ -15,6 +15,7 @@ codecov = { repository = "solana-labs/solana", branch = "master", service = "git
[dependencies]
bincode = "1.2.1"
bv = { version = "0.11.1", features = ["serde"] }
bs58 = "0.3.0"
byteorder = "1.3.2"
chrono = { version = "0.4.10", features = ["serde"] }
@ -25,6 +26,7 @@ fs_extra = "1.1.0"
indexmap = "1.3"
itertools = "0.8.2"
jsonrpc-core = "14.0.5"
jsonrpc-core-client = { version = "14.0.5", features = ["ws"] }
jsonrpc-derive = "14.0.5"
jsonrpc-http-server = "14.0.6"
jsonrpc-pubsub = "14.0.6"
@ -32,41 +34,44 @@ jsonrpc-ws-server = "14.0.6"
libc = "0.2.66"
log = "0.4.8"
nix = "0.17.0"
num_cpus = "1.0.0"
num-traits = "0.2"
rand = "0.6.5"
rand_chacha = "0.1.1"
rand = "0.7.0"
rand_chacha = "0.2.2"
rayon = "1.2.0"
regex = "1.3.4"
serde = "1.0.104"
serde_derive = "1.0.103"
serde_json = "1.0.46"
solana-budget-program = { path = "../programs/budget", version = "1.0.0" }
solana-clap-utils = { path = "../clap-utils", version = "1.0.0" }
solana-client = { path = "../client", version = "1.0.0" }
solana-faucet = { path = "../faucet", version = "1.0.0" }
ed25519-dalek = "=1.0.0-pre.1"
solana-ledger = { path = "../ledger", version = "1.0.0" }
solana-logger = { path = "../logger", version = "1.0.0" }
solana-merkle-tree = { path = "../merkle-tree", version = "1.0.0" }
solana-metrics = { path = "../metrics", version = "1.0.0" }
solana-measure = { path = "../measure", version = "1.0.0" }
solana-net-utils = { path = "../net-utils", version = "1.0.0" }
solana-chacha-cuda = { path = "../chacha-cuda", version = "1.0.0" }
solana-perf = { path = "../perf", version = "1.0.0" }
solana-runtime = { path = "../runtime", version = "1.0.0" }
solana-sdk = { path = "../sdk", version = "1.0.0" }
solana-stake-program = { path = "../programs/stake", version = "1.0.0" }
solana-storage-program = { path = "../programs/storage", version = "1.0.0" }
solana-vote-program = { path = "../programs/vote", version = "1.0.0" }
solana-vote-signer = { path = "../vote-signer", version = "1.0.0" }
solana-sys-tuner = { path = "../sys-tuner", version = "1.0.0" }
sys-info = "0.5.9"
solana-budget-program = { path = "../programs/budget", version = "1.0.25" }
solana-clap-utils = { path = "../clap-utils", version = "1.0.25" }
solana-client = { path = "../client", version = "1.0.25" }
solana-transaction-status = { path = "../transaction-status", version = "1.0.25" }
solana-faucet = { path = "../faucet", version = "1.0.25" }
ed25519-dalek = "=1.0.0-pre.3"
solana-ledger = { path = "../ledger", version = "1.0.25" }
solana-logger = { path = "../logger", version = "1.0.25" }
solana-merkle-tree = { path = "../merkle-tree", version = "1.0.25" }
solana-metrics = { path = "../metrics", version = "1.0.25" }
solana-measure = { path = "../measure", version = "1.0.25" }
solana-net-utils = { path = "../net-utils", version = "1.0.25" }
solana-chacha-cuda = { path = "../chacha-cuda", version = "1.0.25" }
solana-perf = { path = "../perf", version = "1.0.25" }
solana-runtime = { path = "../runtime", version = "1.0.25" }
solana-sdk = { path = "../sdk", version = "1.0.25" }
solana-stake-program = { path = "../programs/stake", version = "1.0.25" }
solana-storage-program = { path = "../programs/storage", version = "1.0.25" }
solana-version = { path = "../version", version = "1.0.25" }
solana-vote-program = { path = "../programs/vote", version = "1.0.25" }
solana-vote-signer = { path = "../vote-signer", version = "1.0.25" }
solana-sys-tuner = { path = "../sys-tuner", version = "1.0.25" }
tempfile = "3.1.0"
thiserror = "1.0"
tokio = "0.1"
tokio-codec = "0.1"
tokio-fs = "0.1"
tokio-io = "0.1"
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.0.0" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.0.25" }
trees = "0.2.1"
[dev-dependencies]
@ -100,3 +105,6 @@ name = "cluster_info"
[[bench]]
name = "chacha"
required-features = ["chacha"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -5,6 +5,7 @@ extern crate test;
use rand::{thread_rng, Rng};
use solana_core::cluster_info::{ClusterInfo, Node};
use solana_core::contact_info::ContactInfo;
use solana_ledger::shred::{Shred, NONCE_SHRED_PAYLOAD_SIZE};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::timing::timestamp;
use std::collections::HashMap;
@ -20,9 +21,8 @@ fn broadcast_shreds_bench(bencher: &mut Bencher) {
let mut cluster_info = ClusterInfo::new_with_invalid_keypair(leader_info.info.clone());
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
const SHRED_SIZE: usize = 1024;
const NUM_SHREDS: usize = 32;
let shreds = vec![vec![0; SHRED_SIZE]; NUM_SHREDS];
let shreds = vec![vec![0; NONCE_SHRED_PAYLOAD_SIZE]; NUM_SHREDS];
let seeds = vec![[0u8; 32]; NUM_SHREDS];
let mut stakes = HashMap::new();
const NUM_PEERS: usize = 200;

View File

@ -5,11 +5,11 @@ extern crate test;
use solana_ledger::entry::{create_ticks, Entry};
use solana_ledger::shred::{
max_entries_per_n_shred, max_ticks_per_n_shreds, Shred, Shredder, RECOMMENDED_FEC_RATE,
SIZE_OF_DATA_SHRED_PAYLOAD,
SIZE_OF_NONCE_DATA_SHRED_PAYLOAD,
};
use solana_perf::test_tx;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::signature::Keypair;
use std::sync::Arc;
use test::Bencher;
@ -29,10 +29,11 @@ fn make_large_unchained_entries(txs_per_entry: u64, num_entries: u64) -> Vec<Ent
#[bench]
fn bench_shredder_ticks(bencher: &mut Bencher) {
let kp = Arc::new(Keypair::new());
let shred_size = SIZE_OF_DATA_SHRED_PAYLOAD;
let shred_size = SIZE_OF_NONCE_DATA_SHRED_PAYLOAD;
let num_shreds = ((1000 * 1000) + (shred_size - 1)) / shred_size;
// ~1Mb
let num_ticks = max_ticks_per_n_shreds(1) * num_shreds as u64;
let num_ticks =
max_ticks_per_n_shreds(1, Some(SIZE_OF_NONCE_DATA_SHRED_PAYLOAD)) * num_shreds as u64;
let entries = create_ticks(num_ticks, 0, Hash::default());
bencher.iter(|| {
let shredder = Shredder::new(1, 0, RECOMMENDED_FEC_RATE, kp.clone(), 0, 0).unwrap();
@ -43,10 +44,14 @@ fn bench_shredder_ticks(bencher: &mut Bencher) {
#[bench]
fn bench_shredder_large_entries(bencher: &mut Bencher) {
let kp = Arc::new(Keypair::new());
let shred_size = SIZE_OF_DATA_SHRED_PAYLOAD;
let shred_size = SIZE_OF_NONCE_DATA_SHRED_PAYLOAD;
let num_shreds = ((1000 * 1000) + (shred_size - 1)) / shred_size;
let txs_per_entry = 128;
let num_entries = max_entries_per_n_shred(&make_test_entry(txs_per_entry), num_shreds as u64);
let num_entries = max_entries_per_n_shred(
&make_test_entry(txs_per_entry),
num_shreds as u64,
Some(shred_size),
);
let entries = make_large_unchained_entries(txs_per_entry, num_entries);
// 1Mb
bencher.iter(|| {
@ -58,10 +63,10 @@ fn bench_shredder_large_entries(bencher: &mut Bencher) {
#[bench]
fn bench_deshredder(bencher: &mut Bencher) {
let kp = Arc::new(Keypair::new());
let shred_size = SIZE_OF_DATA_SHRED_PAYLOAD;
let shred_size = SIZE_OF_NONCE_DATA_SHRED_PAYLOAD;
// ~10Mb
let num_shreds = ((10000 * 1000) + (shred_size - 1)) / shred_size;
let num_ticks = max_ticks_per_n_shreds(1) * num_shreds as u64;
let num_ticks = max_ticks_per_n_shreds(1, Some(shred_size)) * num_shreds as u64;
let entries = create_ticks(num_ticks, 0, Hash::default());
let shredder = Shredder::new(1, 0, RECOMMENDED_FEC_RATE, kp, 0, 0).unwrap();
let data_shreds = shredder.entries_to_shreds(&entries, true, 0).0;
@ -73,7 +78,7 @@ fn bench_deshredder(bencher: &mut Bencher) {
#[bench]
fn bench_deserialize_hdr(bencher: &mut Bencher) {
let data = vec![0; SIZE_OF_DATA_SHRED_PAYLOAD];
let data = vec![0; SIZE_OF_NONCE_DATA_SHRED_PAYLOAD];
let shred = Shred::new_from_data(2, 1, 1, Some(&data), true, true, 0, 0, 1);

View File

@ -0,0 +1,263 @@
// Service to verify accounts hashes with other trusted validator nodes.
//
// Each interval, publish the snapshat hash which is the full accounts state
// hash on gossip. Monitor gossip for messages from validators in the --trusted-validators
// set and halt the node if a mismatch is detected.
use crate::cluster_info::{ClusterInfo, MAX_SNAPSHOT_HASHES};
use solana_ledger::{
snapshot_package::SnapshotPackage, snapshot_package::SnapshotPackageReceiver,
snapshot_package::SnapshotPackageSender,
};
use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey};
use std::collections::{HashMap, HashSet};
use std::{
sync::{
atomic::{AtomicBool, Ordering},
mpsc::RecvTimeoutError,
Arc, RwLock,
},
thread::{self, Builder, JoinHandle},
time::Duration,
};
pub struct AccountsHashVerifier {
t_accounts_hash_verifier: JoinHandle<()>,
}
impl AccountsHashVerifier {
pub fn new(
snapshot_package_receiver: SnapshotPackageReceiver,
snapshot_package_sender: Option<SnapshotPackageSender>,
exit: &Arc<AtomicBool>,
cluster_info: &Arc<RwLock<ClusterInfo>>,
trusted_validators: Option<HashSet<Pubkey>>,
halt_on_trusted_validators_accounts_hash_mismatch: bool,
fault_injection_rate_slots: u64,
) -> Self {
let exit = exit.clone();
let cluster_info = cluster_info.clone();
let t_accounts_hash_verifier = Builder::new()
.name("solana-accounts-hash".to_string())
.spawn(move || {
let mut hashes = vec![];
loop {
if exit.load(Ordering::Relaxed) {
break;
}
match snapshot_package_receiver.recv_timeout(Duration::from_secs(1)) {
Ok(snapshot_package) => {
Self::process_snapshot(
snapshot_package,
&cluster_info,
&trusted_validators,
halt_on_trusted_validators_accounts_hash_mismatch,
&snapshot_package_sender,
&mut hashes,
&exit,
fault_injection_rate_slots,
);
}
Err(RecvTimeoutError::Disconnected) => break,
Err(RecvTimeoutError::Timeout) => (),
}
}
})
.unwrap();
Self {
t_accounts_hash_verifier,
}
}
fn process_snapshot(
snapshot_package: SnapshotPackage,
cluster_info: &Arc<RwLock<ClusterInfo>>,
trusted_validators: &Option<HashSet<Pubkey>>,
halt_on_trusted_validator_accounts_hash_mismatch: bool,
snapshot_package_sender: &Option<SnapshotPackageSender>,
hashes: &mut Vec<(Slot, Hash)>,
exit: &Arc<AtomicBool>,
fault_injection_rate_slots: u64,
) {
if fault_injection_rate_slots != 0
&& snapshot_package.root % fault_injection_rate_slots == 0
{
// For testing, publish an invalid hash to gossip.
use rand::{thread_rng, Rng};
use solana_sdk::hash::extend_and_hash;
warn!("inserting fault at slot: {}", snapshot_package.root);
let rand = thread_rng().gen_range(0, 10);
let hash = extend_and_hash(&snapshot_package.hash, &[rand]);
hashes.push((snapshot_package.root, hash));
} else {
hashes.push((snapshot_package.root, snapshot_package.hash));
}
while hashes.len() > MAX_SNAPSHOT_HASHES {
hashes.remove(0);
}
if halt_on_trusted_validator_accounts_hash_mismatch {
let mut slot_to_hash = HashMap::new();
for (slot, hash) in hashes.iter() {
slot_to_hash.insert(*slot, *hash);
}
if Self::should_halt(&cluster_info, trusted_validators, &mut slot_to_hash) {
exit.store(true, Ordering::Relaxed);
}
}
if let Some(sender) = snapshot_package_sender.as_ref() {
if sender.send(snapshot_package).is_err() {}
}
cluster_info
.write()
.unwrap()
.push_accounts_hashes(hashes.clone());
}
fn should_halt(
cluster_info: &Arc<RwLock<ClusterInfo>>,
trusted_validators: &Option<HashSet<Pubkey>>,
slot_to_hash: &mut HashMap<Slot, Hash>,
) -> bool {
let mut verified_count = 0;
let mut highest_slot = 0;
if let Some(trusted_validators) = trusted_validators.as_ref() {
for trusted_validator in trusted_validators {
let cluster_info_r = cluster_info.read().unwrap();
if let Some(accounts_hashes) =
cluster_info_r.get_accounts_hash_for_node(trusted_validator)
{
for (slot, hash) in accounts_hashes {
if let Some(reference_hash) = slot_to_hash.get(slot) {
if *hash != *reference_hash {
error!("Trusted validator {} produced conflicting hashes for slot: {} ({} != {})",
trusted_validator,
slot,
hash,
reference_hash,
);
return true;
} else {
verified_count += 1;
}
} else {
highest_slot = std::cmp::max(*slot, highest_slot);
slot_to_hash.insert(*slot, *hash);
}
}
}
}
}
inc_new_counter_info!("accounts_hash_verifier-hashes_verified", verified_count);
datapoint_info!(
"accounts_hash_verifier",
("highest_slot_verified", highest_slot, i64),
);
false
}
pub fn join(self) -> thread::Result<()> {
self.t_accounts_hash_verifier.join()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cluster_info::make_accounts_hashes_message;
use crate::contact_info::ContactInfo;
use solana_sdk::{
hash::hash,
signature::{Keypair, Signer},
};
#[test]
fn test_should_halt() {
let keypair = Keypair::new();
let contact_info = ContactInfo::new_localhost(&keypair.pubkey(), 0);
let cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
let cluster_info = Arc::new(RwLock::new(cluster_info));
let mut trusted_validators = HashSet::new();
let mut slot_to_hash = HashMap::new();
assert!(!AccountsHashVerifier::should_halt(
&cluster_info,
&Some(trusted_validators.clone()),
&mut slot_to_hash,
));
let validator1 = Keypair::new();
let hash1 = hash(&[1]);
let hash2 = hash(&[2]);
{
let message = make_accounts_hashes_message(&validator1, vec![(0, hash1)]).unwrap();
let mut cluster_info_w = cluster_info.write().unwrap();
cluster_info_w.push_message(message);
}
slot_to_hash.insert(0, hash2);
trusted_validators.insert(validator1.pubkey());
assert!(AccountsHashVerifier::should_halt(
&cluster_info,
&Some(trusted_validators.clone()),
&mut slot_to_hash,
));
}
#[test]
fn test_max_hashes() {
solana_logger::setup();
use std::path::PathBuf;
use tempfile::TempDir;
let keypair = Keypair::new();
let contact_info = ContactInfo::new_localhost(&keypair.pubkey(), 0);
let cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
let cluster_info = Arc::new(RwLock::new(cluster_info));
let trusted_validators = HashSet::new();
let exit = Arc::new(AtomicBool::new(false));
let mut hashes = vec![];
for i in 0..MAX_SNAPSHOT_HASHES + 1 {
let snapshot_links = TempDir::new().unwrap();
let snapshot_package = SnapshotPackage {
hash: hash(&[i as u8]),
root: 100 + i as u64,
slot_deltas: vec![],
snapshot_links,
tar_output_file: PathBuf::from("."),
storages: vec![],
};
AccountsHashVerifier::process_snapshot(
snapshot_package,
&cluster_info,
&Some(trusted_validators.clone()),
false,
&None,
&mut hashes,
&exit,
0,
);
}
let cluster_info_r = cluster_info.read().unwrap();
let cluster_hashes = cluster_info_r
.get_accounts_hash_for_node(&keypair.pubkey())
.unwrap();
info!("{:?}", cluster_hashes);
assert_eq!(hashes.len(), MAX_SNAPSHOT_HASHES);
assert_eq!(cluster_hashes.len(), MAX_SNAPSHOT_HASHES);
assert_eq!(cluster_hashes[0], (101, hash(&[1])));
assert_eq!(
cluster_hashes[MAX_SNAPSHOT_HASHES - 1],
(
100 + MAX_SNAPSHOT_HASHES as u64,
hash(&[MAX_SNAPSHOT_HASHES as u8])
)
);
}
}

View File

@ -1017,7 +1017,6 @@ mod tests {
};
use crossbeam_channel::unbounded;
use itertools::Itertools;
use solana_client::rpc_response::{RpcEncodedTransaction, RpcTransactionWithStatusMeta};
use solana_ledger::{
blockstore::entries_to_test_shreds,
entry::{next_entry, Entry, EntrySlice},
@ -1030,6 +1029,7 @@ mod tests {
system_transaction,
transaction::TransactionError,
};
use solana_transaction_status::{EncodedTransaction, TransactionWithStatusMeta};
use std::{sync::atomic::Ordering, thread::sleep};
#[test]
@ -1975,18 +1975,28 @@ mod tests {
let confirmed_block = blockstore.get_confirmed_block(bank.slot(), None).unwrap();
assert_eq!(confirmed_block.transactions.len(), 3);
for RpcTransactionWithStatusMeta { transaction, meta } in
for TransactionWithStatusMeta { transaction, meta } in
confirmed_block.transactions.into_iter()
{
if let RpcEncodedTransaction::Json(transaction) = transaction {
if let EncodedTransaction::Json(transaction) = transaction {
if transaction.signatures[0] == success_signature.to_string() {
assert_eq!(meta.unwrap().status, Ok(()));
let meta = meta.unwrap();
assert_eq!(meta.err, None);
assert_eq!(meta.status, Ok(()));
} else if transaction.signatures[0] == ix_error_signature.to_string() {
let meta = meta.unwrap();
assert_eq!(
meta.unwrap().status,
meta.err,
Some(TransactionError::InstructionError(
0,
InstructionError::Custom(1)
))
);
assert_eq!(
meta.status,
Err(TransactionError::InstructionError(
0,
InstructionError::CustomError(1)
InstructionError::Custom(1)
))
);
} else {

View File

@ -390,7 +390,7 @@ mod test {
)));
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
let mut genesis_config = create_genesis_config(10_000).genesis_config;
genesis_config.ticks_per_slot = max_ticks_per_n_shreds(num_shreds_per_slot) + 1;
genesis_config.ticks_per_slot = max_ticks_per_n_shreds(num_shreds_per_slot, None) + 1;
let bank0 = Arc::new(Bank::new(&genesis_config));
(
blockstore,
@ -484,7 +484,11 @@ mod test {
// Interrupting the slot should cause the unfinished_slot and stats to reset
let num_shreds = 1;
assert!(num_shreds < num_shreds_per_slot);
let ticks1 = create_ticks(max_ticks_per_n_shreds(num_shreds), 0, genesis_config.hash());
let ticks1 = create_ticks(
max_ticks_per_n_shreds(num_shreds, None),
0,
genesis_config.hash(),
);
let receive_results = ReceiveResults {
entries: ticks1.clone(),
time_elapsed: Duration::new(2, 0),

File diff suppressed because it is too large Load Diff

View File

@ -51,7 +51,8 @@ impl ClusterInfoVoteListener {
if exit.load(Ordering::Relaxed) {
return Ok(());
}
if let Some(bank) = poh_recorder.lock().unwrap().bank() {
let poh_bank = poh_recorder.lock().unwrap().bank();
if let Some(bank) = poh_bank {
let last_ts = bank.last_vote_sync.load(Ordering::Relaxed);
let (votes, new_ts) = cluster_info.read().unwrap().get_votes(last_ts);
bank.last_vote_sync

View File

@ -1,3 +1,7 @@
use crate::consensus::VOTE_THRESHOLD_SIZE;
use solana_ledger::blockstore::Blockstore;
use solana_measure::measure::Measure;
use solana_metrics::inc_new_counter_info;
use solana_runtime::bank::Bank;
use solana_sdk::clock::Slot;
use solana_vote_program::{vote_state::VoteState, vote_state::MAX_LOCKOUT_HISTORY};
@ -10,9 +14,11 @@ use std::{
time::Duration,
};
pub type BlockCommitmentArray = [u64; MAX_LOCKOUT_HISTORY + 1];
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct BlockCommitment {
pub commitment: [u64; MAX_LOCKOUT_HISTORY],
pub commitment: BlockCommitmentArray,
}
impl BlockCommitment {
@ -25,23 +31,71 @@ impl BlockCommitment {
assert!(confirmation_count > 0 && confirmation_count <= MAX_LOCKOUT_HISTORY);
self.commitment[confirmation_count - 1]
}
pub fn increase_rooted_stake(&mut self, stake: u64) {
self.commitment[MAX_LOCKOUT_HISTORY] += stake;
}
pub fn get_rooted_stake(&self) -> u64 {
self.commitment[MAX_LOCKOUT_HISTORY]
}
#[cfg(test)]
pub(crate) fn new(commitment: [u64; MAX_LOCKOUT_HISTORY]) -> Self {
pub(crate) fn new(commitment: BlockCommitmentArray) -> Self {
Self { commitment }
}
}
#[derive(Debug, Default)]
pub struct BlockCommitmentCache {
block_commitment: HashMap<Slot, BlockCommitment>,
largest_confirmed_root: Slot,
total_stake: u64,
bank: Arc<Bank>,
blockstore: Arc<Blockstore>,
root: Slot,
}
impl std::fmt::Debug for BlockCommitmentCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BlockCommitmentCache")
.field("block_commitment", &self.block_commitment)
.field("total_stake", &self.total_stake)
.field(
"bank",
&format_args!("Bank({{current_slot: {:?}}})", self.bank.slot()),
)
.field("root", &self.root)
.finish()
}
}
impl BlockCommitmentCache {
pub fn new(block_commitment: HashMap<Slot, BlockCommitment>, total_stake: u64) -> Self {
pub fn new(
block_commitment: HashMap<Slot, BlockCommitment>,
largest_confirmed_root: Slot,
total_stake: u64,
bank: Arc<Bank>,
blockstore: Arc<Blockstore>,
root: Slot,
) -> Self {
Self {
block_commitment,
largest_confirmed_root,
total_stake,
bank,
blockstore,
root,
}
}
pub fn default_with_blockstore(blockstore: Arc<Blockstore>) -> Self {
Self {
block_commitment: HashMap::default(),
largest_confirmed_root: Slot::default(),
total_stake: u64::default(),
bank: Arc::new(Bank::default()),
blockstore,
root: Slot::default(),
}
}
@ -49,45 +103,98 @@ impl BlockCommitmentCache {
self.block_commitment.get(&slot)
}
pub fn largest_confirmed_root(&self) -> Slot {
self.largest_confirmed_root
}
pub fn total_stake(&self) -> u64 {
self.total_stake
}
pub fn get_block_with_depth_commitment(
&self,
minimum_depth: usize,
minimum_stake_percentage: f64,
) -> Option<Slot> {
self.block_commitment
.iter()
.filter(|&(_, block_commitment)| {
let fork_stake_minimum_depth: u64 = block_commitment.commitment[minimum_depth..]
.iter()
.cloned()
.sum();
fork_stake_minimum_depth as f64 / self.total_stake as f64
>= minimum_stake_percentage
})
.map(|(slot, _)| *slot)
.max()
pub fn bank(&self) -> Arc<Bank> {
self.bank.clone()
}
pub fn get_rooted_block_with_commitment(&self, minimum_stake_percentage: f64) -> Option<u64> {
self.get_block_with_depth_commitment(MAX_LOCKOUT_HISTORY - 1, minimum_stake_percentage)
pub fn slot(&self) -> Slot {
self.bank.slot()
}
pub fn root(&self) -> Slot {
self.root
}
pub fn get_confirmation_count(&self, slot: Slot) -> Option<usize> {
self.get_lockout_count(slot, VOTE_THRESHOLD_SIZE)
}
// Returns the lowest level at which at least `minimum_stake_percentage` of the total epoch
// stake is locked out
fn get_lockout_count(&self, slot: Slot, minimum_stake_percentage: f64) -> Option<usize> {
self.get_block_commitment(slot).map(|block_commitment| {
let iterator = block_commitment.commitment.iter().enumerate().rev();
let mut sum = 0;
for (i, stake) in iterator {
sum += stake;
if (sum as f64 / self.total_stake as f64) > minimum_stake_percentage {
return i + 1;
}
}
0
})
}
pub fn is_confirmed_rooted(&self, slot: Slot) -> bool {
slot <= self.largest_confirmed_root()
&& (self.blockstore.is_root(slot) || self.bank.status_cache_ancestors().contains(&slot))
}
#[cfg(test)]
pub fn new_for_tests_with_blockstore(blockstore: Arc<Blockstore>) -> Self {
let mut block_commitment: HashMap<Slot, BlockCommitment> = HashMap::new();
block_commitment.insert(0, BlockCommitment::default());
Self {
block_commitment,
blockstore,
total_stake: 42,
largest_confirmed_root: Slot::default(),
bank: Arc::new(Bank::default()),
root: Slot::default(),
}
}
pub(crate) fn set_get_largest_confirmed_root(&mut self, root: Slot) {
self.largest_confirmed_root = root;
}
}
pub struct CommitmentAggregationData {
bank: Arc<Bank>,
root: Slot,
total_staked: u64,
}
impl CommitmentAggregationData {
pub fn new(bank: Arc<Bank>, total_staked: u64) -> Self {
Self { bank, total_staked }
pub fn new(bank: Arc<Bank>, root: Slot, total_staked: u64) -> Self {
Self {
bank,
root,
total_staked,
}
}
}
fn get_largest_confirmed_root(mut rooted_stake: Vec<(Slot, u64)>, total_stake: u64) -> Slot {
rooted_stake.sort_by(|a, b| a.0.cmp(&b.0).reverse());
let mut stake_sum = 0;
for (root, stake) in rooted_stake {
stake_sum += stake;
if (stake_sum as f64 / total_stake as f64) > VOTE_THRESHOLD_SIZE {
return root;
}
}
0
}
pub struct AggregateCommitmentService {
t_commitment: JoinHandle<()>,
}
@ -144,18 +251,37 @@ impl AggregateCommitmentService {
continue;
}
let block_commitment = Self::aggregate_commitment(&ancestors, &aggregation_data.bank);
let mut aggregate_commitment_time = Measure::start("aggregate-commitment-ms");
let (block_commitment, rooted_stake) =
Self::aggregate_commitment(&ancestors, &aggregation_data.bank);
let mut new_block_commitment =
BlockCommitmentCache::new(block_commitment, aggregation_data.total_staked);
let largest_confirmed_root =
get_largest_confirmed_root(rooted_stake, aggregation_data.total_staked);
let mut new_block_commitment = BlockCommitmentCache::new(
block_commitment,
largest_confirmed_root,
aggregation_data.total_staked,
aggregation_data.bank,
block_commitment_cache.read().unwrap().blockstore.clone(),
aggregation_data.root,
);
let mut w_block_commitment_cache = block_commitment_cache.write().unwrap();
std::mem::swap(&mut *w_block_commitment_cache, &mut new_block_commitment);
aggregate_commitment_time.stop();
inc_new_counter_info!(
"aggregate-commitment-ms",
aggregate_commitment_time.as_ms() as usize
);
}
}
pub fn aggregate_commitment(ancestors: &[Slot], bank: &Bank) -> HashMap<Slot, BlockCommitment> {
pub fn aggregate_commitment(
ancestors: &[Slot],
bank: &Bank,
) -> (HashMap<Slot, BlockCommitment>, Vec<(Slot, u64)>) {
assert!(!ancestors.is_empty());
// Check ancestors is sorted
@ -164,6 +290,7 @@ impl AggregateCommitmentService {
}
let mut commitment = HashMap::new();
let mut rooted_stake: Vec<(Slot, u64)> = Vec::new();
for (_, (lamports, account)) in bank.vote_accounts().into_iter() {
if lamports == 0 {
continue;
@ -176,17 +303,19 @@ impl AggregateCommitmentService {
let vote_state = vote_state.unwrap();
Self::aggregate_commitment_for_vote_account(
&mut commitment,
&mut rooted_stake,
&vote_state,
ancestors,
lamports,
);
}
commitment
(commitment, rooted_stake)
}
fn aggregate_commitment_for_vote_account(
commitment: &mut HashMap<Slot, BlockCommitment>,
rooted_stake: &mut Vec<(Slot, u64)>,
vote_state: &VoteState,
ancestors: &[Slot],
lamports: u64,
@ -199,12 +328,13 @@ impl AggregateCommitmentService {
commitment
.entry(*a)
.or_insert_with(BlockCommitment::default)
.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports);
.increase_rooted_stake(lamports);
} else {
ancestors_index = i;
break;
}
}
rooted_stake.push((root, lamports));
}
for vote in &vote_state.votes {
@ -231,9 +361,10 @@ impl AggregateCommitmentService {
mod tests {
use super::*;
use crate::genesis_utils::{create_genesis_config, GenesisConfigInfo};
use solana_ledger::get_tmp_ledger_path;
use solana_sdk::pubkey::Pubkey;
use solana_stake_program::stake_state;
use solana_vote_program::vote_state;
use solana_vote_program::vote_state::{self, VoteStateVersions};
#[test]
fn test_block_commitment() {
@ -246,97 +377,98 @@ mod tests {
}
#[test]
fn test_get_block_with_depth_commitment() {
fn test_get_confirmations() {
let bank = Arc::new(Bank::default());
let ledger_path = get_tmp_ledger_path!();
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
// Build BlockCommitmentCache with votes at depths 0 and 1 for 2 slots
let mut cache0 = BlockCommitment::default();
cache0.increase_confirmation_stake(1, 15);
cache0.increase_confirmation_stake(2, 25);
cache0.increase_confirmation_stake(1, 5);
cache0.increase_confirmation_stake(2, 40);
let mut cache1 = BlockCommitment::default();
cache1.increase_confirmation_stake(1, 10);
cache1.increase_confirmation_stake(2, 20);
cache1.increase_confirmation_stake(1, 40);
cache1.increase_confirmation_stake(2, 5);
let mut cache2 = BlockCommitment::default();
cache2.increase_confirmation_stake(1, 20);
cache2.increase_confirmation_stake(2, 5);
let mut block_commitment = HashMap::new();
block_commitment.entry(0).or_insert(cache0.clone());
block_commitment.entry(1).or_insert(cache1.clone());
let block_commitment_cache = BlockCommitmentCache::new(block_commitment, 50);
block_commitment.entry(2).or_insert(cache2.clone());
let block_commitment_cache =
BlockCommitmentCache::new(block_commitment, 0, 50, bank, blockstore, 0);
// Neither slot has rooted votes
assert_eq!(
block_commitment_cache.get_rooted_block_with_commitment(0.1),
None
);
// Neither slot meets the minimum level of commitment 0.6 at depth 1
assert_eq!(
block_commitment_cache.get_block_with_depth_commitment(1, 0.6),
None
);
// Only slot 0 meets the minimum level of commitment 0.5 at depth 1
assert_eq!(
block_commitment_cache.get_block_with_depth_commitment(1, 0.5),
Some(0)
);
// If multiple slots meet the minimum level of commitment, method should return the most recent
assert_eq!(
block_commitment_cache.get_block_with_depth_commitment(1, 0.4),
Some(1)
);
// If multiple slots meet the minimum level of commitment, method should return the most recent
assert_eq!(
block_commitment_cache.get_block_with_depth_commitment(0, 0.6),
Some(1)
);
// Neither slot meets the minimum level of commitment 0.9 at depth 0
assert_eq!(
block_commitment_cache.get_block_with_depth_commitment(0, 0.9),
None
);
assert_eq!(block_commitment_cache.get_confirmation_count(0), Some(2));
assert_eq!(block_commitment_cache.get_confirmation_count(1), Some(1));
assert_eq!(block_commitment_cache.get_confirmation_count(2), Some(0),);
assert_eq!(block_commitment_cache.get_confirmation_count(3), None,);
}
#[test]
fn test_get_rooted_block_with_commitment() {
// Build BlockCommitmentCache with rooted votes
let mut cache0 = BlockCommitment::new([0; MAX_LOCKOUT_HISTORY]);
cache0.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, 40);
cache0.increase_confirmation_stake(MAX_LOCKOUT_HISTORY - 1, 10);
let mut cache1 = BlockCommitment::new([0; MAX_LOCKOUT_HISTORY]);
cache1.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, 30);
cache1.increase_confirmation_stake(MAX_LOCKOUT_HISTORY - 1, 10);
cache1.increase_confirmation_stake(MAX_LOCKOUT_HISTORY - 2, 10);
fn test_is_confirmed_rooted() {
let bank = Arc::new(Bank::default());
let ledger_path = get_tmp_ledger_path!();
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
blockstore.set_roots(&[0, 1]).unwrap();
// Build BlockCommitmentCache with rooted slots
let mut cache0 = BlockCommitment::default();
cache0.increase_rooted_stake(50);
let mut cache1 = BlockCommitment::default();
cache1.increase_rooted_stake(40);
let mut cache2 = BlockCommitment::default();
cache2.increase_rooted_stake(20);
let mut block_commitment = HashMap::new();
block_commitment.entry(0).or_insert(cache0.clone());
block_commitment.entry(1).or_insert(cache1.clone());
let block_commitment_cache = BlockCommitmentCache::new(block_commitment, 50);
block_commitment.entry(1).or_insert(cache0.clone());
block_commitment.entry(2).or_insert(cache1.clone());
block_commitment.entry(3).or_insert(cache2.clone());
let largest_confirmed_root = 1;
let block_commitment_cache = BlockCommitmentCache::new(
block_commitment,
largest_confirmed_root,
50,
bank,
blockstore,
0,
);
// Only slot 0 meets the minimum level of commitment 0.66 at root
assert_eq!(
block_commitment_cache.get_rooted_block_with_commitment(0.66),
Some(0)
);
// If multiple slots meet the minimum level of commitment, method should return the most recent
assert_eq!(
block_commitment_cache.get_rooted_block_with_commitment(0.6),
Some(1)
);
// Neither slot meets the minimum level of commitment 0.9 at root
assert_eq!(
block_commitment_cache.get_rooted_block_with_commitment(0.9),
None
);
assert!(block_commitment_cache.is_confirmed_rooted(0));
assert!(block_commitment_cache.is_confirmed_rooted(1));
assert!(!block_commitment_cache.is_confirmed_rooted(2));
assert!(!block_commitment_cache.is_confirmed_rooted(3));
}
#[test]
fn test_get_largest_confirmed_root() {
assert_eq!(get_largest_confirmed_root(vec![], 10), 0);
let mut rooted_stake = vec![];
rooted_stake.push((0, 5));
rooted_stake.push((1, 5));
assert_eq!(get_largest_confirmed_root(rooted_stake, 10), 0);
let mut rooted_stake = vec![];
rooted_stake.push((1, 5));
rooted_stake.push((0, 10));
rooted_stake.push((2, 5));
rooted_stake.push((1, 4));
assert_eq!(get_largest_confirmed_root(rooted_stake, 10), 1);
}
#[test]
fn test_aggregate_commitment_for_vote_account_1() {
let ancestors = vec![3, 4, 5, 7, 9, 11];
let mut commitment = HashMap::new();
let mut rooted_stake = vec![];
let lamports = 5;
let mut vote_state = VoteState::default();
let root = ancestors.last().unwrap();
vote_state.root_slot = Some(*root);
let root = ancestors.last().unwrap().clone();
vote_state.root_slot = Some(root);
AggregateCommitmentService::aggregate_commitment_for_vote_account(
&mut commitment,
&mut rooted_stake,
&vote_state,
&ancestors,
lamports,
@ -344,15 +476,17 @@ mod tests {
for a in ancestors {
let mut expected = BlockCommitment::default();
expected.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports);
expected.increase_rooted_stake(lamports);
assert_eq!(*commitment.get(&a).unwrap(), expected);
}
assert_eq!(rooted_stake[0], (root, lamports));
}
#[test]
fn test_aggregate_commitment_for_vote_account_2() {
let ancestors = vec![3, 4, 5, 7, 9, 11];
let mut commitment = HashMap::new();
let mut rooted_stake = vec![];
let lamports = 5;
let mut vote_state = VoteState::default();
@ -361,6 +495,7 @@ mod tests {
vote_state.process_slot_vote_unchecked(*ancestors.last().unwrap());
AggregateCommitmentService::aggregate_commitment_for_vote_account(
&mut commitment,
&mut rooted_stake,
&vote_state,
&ancestors,
lamports,
@ -369,7 +504,7 @@ mod tests {
for a in ancestors {
if a <= root {
let mut expected = BlockCommitment::default();
expected.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports);
expected.increase_rooted_stake(lamports);
assert_eq!(*commitment.get(&a).unwrap(), expected);
} else {
let mut expected = BlockCommitment::default();
@ -377,12 +512,14 @@ mod tests {
assert_eq!(*commitment.get(&a).unwrap(), expected);
}
}
assert_eq!(rooted_stake[0], (root, lamports));
}
#[test]
fn test_aggregate_commitment_for_vote_account_3() {
let ancestors = vec![3, 4, 5, 7, 9, 10, 11];
let mut commitment = HashMap::new();
let mut rooted_stake = vec![];
let lamports = 5;
let mut vote_state = VoteState::default();
@ -393,6 +530,7 @@ mod tests {
vote_state.process_slot_vote_unchecked(ancestors[6]);
AggregateCommitmentService::aggregate_commitment_for_vote_account(
&mut commitment,
&mut rooted_stake,
&vote_state,
&ancestors,
lamports,
@ -401,7 +539,7 @@ mod tests {
for (i, a) in ancestors.iter().enumerate() {
if *a <= root {
let mut expected = BlockCommitment::default();
expected.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports);
expected.increase_rooted_stake(lamports);
assert_eq!(*commitment.get(&a).unwrap(), expected);
} else if i <= 4 {
let mut expected = BlockCommitment::default();
@ -413,6 +551,7 @@ mod tests {
assert_eq!(*commitment.get(&a).unwrap(), expected);
}
}
assert_eq!(rooted_stake[0], (root, lamports));
}
#[test]
@ -422,6 +561,8 @@ mod tests {
mut genesis_config, ..
} = create_genesis_config(10_000);
let rooted_stake_amount = 40;
let sk1 = Pubkey::new_rand();
let pk1 = Pubkey::new_rand();
let mut vote_account1 = vote_state::create_account(&pk1, &Pubkey::new_rand(), 0, 100);
@ -432,12 +573,36 @@ mod tests {
let mut vote_account2 = vote_state::create_account(&pk2, &Pubkey::new_rand(), 0, 50);
let stake_account2 =
stake_state::create_account(&sk2, &pk2, &vote_account2, &genesis_config.rent, 50);
let sk3 = Pubkey::new_rand();
let pk3 = Pubkey::new_rand();
let mut vote_account3 = vote_state::create_account(&pk3, &Pubkey::new_rand(), 0, 1);
let stake_account3 = stake_state::create_account(
&sk3,
&pk3,
&vote_account3,
&genesis_config.rent,
rooted_stake_amount,
);
let sk4 = Pubkey::new_rand();
let pk4 = Pubkey::new_rand();
let mut vote_account4 = vote_state::create_account(&pk4, &Pubkey::new_rand(), 0, 1);
let stake_account4 = stake_state::create_account(
&sk4,
&pk4,
&vote_account4,
&genesis_config.rent,
rooted_stake_amount,
);
genesis_config.accounts.extend(vec![
(pk1, vote_account1.clone()),
(sk1, stake_account1),
(pk2, vote_account2.clone()),
(sk2, stake_account2),
(pk3, vote_account3.clone()),
(sk3, stake_account3),
(pk4, vote_account4.clone()),
(sk4, stake_account4),
]);
// Create bank
@ -446,16 +611,31 @@ mod tests {
let mut vote_state1 = VoteState::from(&vote_account1).unwrap();
vote_state1.process_slot_vote_unchecked(3);
vote_state1.process_slot_vote_unchecked(5);
vote_state1.to(&mut vote_account1).unwrap();
let versioned = VoteStateVersions::Current(Box::new(vote_state1));
VoteState::to(&versioned, &mut vote_account1).unwrap();
bank.store_account(&pk1, &vote_account1);
let mut vote_state2 = VoteState::from(&vote_account2).unwrap();
vote_state2.process_slot_vote_unchecked(9);
vote_state2.process_slot_vote_unchecked(10);
vote_state2.to(&mut vote_account2).unwrap();
let versioned = VoteStateVersions::Current(Box::new(vote_state2));
VoteState::to(&versioned, &mut vote_account2).unwrap();
bank.store_account(&pk2, &vote_account2);
let commitment = AggregateCommitmentService::aggregate_commitment(&ancestors, &bank);
let mut vote_state3 = VoteState::from(&vote_account3).unwrap();
vote_state3.root_slot = Some(1);
let versioned = VoteStateVersions::Current(Box::new(vote_state3));
VoteState::to(&versioned, &mut vote_account3).unwrap();
bank.store_account(&pk3, &vote_account3);
let mut vote_state4 = VoteState::from(&vote_account4).unwrap();
vote_state4.root_slot = Some(2);
let versioned = VoteStateVersions::Current(Box::new(vote_state4));
VoteState::to(&versioned, &mut vote_account4).unwrap();
bank.store_account(&pk4, &vote_account4);
let (commitment, rooted_stake) =
AggregateCommitmentService::aggregate_commitment(&ancestors, &bank);
for a in ancestors {
if a <= 3 {
@ -479,5 +659,7 @@ mod tests {
assert!(commitment.get(&a).is_none());
}
}
assert_eq!(rooted_stake.len(), 2);
assert_eq!(get_largest_confirmed_root(rooted_stake, 100), 1)
}
}

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