Compare commits

...

165 Commits

Author SHA1 Message Date
mergify[bot]
90d586a4f8 solana stakes now employs server-side filtering if only one vote account is provided (#12657)
(cherry picked from commit 9abaf6ec1d)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-10-03 02:00:49 +00:00
mergify[bot]
efcb5cd9f0 Fix zero-len slice translations (#12642) (#12656)
(cherry picked from commit d0aa8a6446)

Co-authored-by: Jack May <jack@solana.com>
2020-10-03 01:58:27 +00:00
mergify[bot]
ffa0ee69ca Weight push peers by how long we haven't pushed to them (#12620) (#12651)
(cherry picked from commit 71c469c72b)

Co-authored-by: sakridge <sakridge@gmail.com>
2020-10-02 22:27:35 +00:00
mergify[bot]
a983430ddb Check CPI program is executable (#12644) (#12649)
(cherry picked from commit adeb06e550)

Co-authored-by: Jack May <jack@solana.com>
2020-10-02 22:27:29 +00:00
mergify[bot]
bd94250fca Improve solana deploy (#12621) (#12646)
* Check program account before attempting to create it

* Use last_valid_slot to timeout status checks

* Include transaction history in RpcClient::get_signature_statuses requests

* Improve solana-deploy send-transactions

* Clippy

* Improve mock deploy test

* Review comments

(cherry picked from commit 19f385db76)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-10-02 20:44:26 +00:00
mergify[bot]
db5251a524 solana catchup now retries if the initial RPC connection fails (#12645)
(cherry picked from commit 978b26a9c5)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-10-02 20:42:51 +00:00
mergify[bot]
efb665071c limits number of threads in core/tests/crds_gossip.rs (#12615) (#12641)
crds_gossip tests start large networks, which with large thread-pools
will exhaust system resources, causing failures in ci tests:
https://buildkite.com/solana-labs/solana/builds/31953

The commit limits size of thread-pools in the test.

(cherry picked from commit 2c669f65f1)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2020-10-02 20:01:49 +00:00
Michael Vines
e69ee1ec64 Add GetConfirmedBlocksWithLimit RPC method
(cherry picked from commit 75b621160e)
2020-10-02 08:21:08 -07:00
mergify[bot]
307686eeba Add --no-port-check to validator (#12245) (#12638)
(cherry picked from commit aa70dbfc62)

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
2020-10-02 12:46:40 +00:00
mergify[bot]
de1e2f9c0c Add inflation subcommand (#12632)
(cherry picked from commit 42aeead6b4)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-10-02 07:14:02 +00:00
mergify[bot]
01f93003d3 Improve block command output (#12631)
(cherry picked from commit 14036ac580)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-10-02 07:03:29 +00:00
mergify[bot]
75219afc91 Document postBalance field (#12628)
(cherry picked from commit e03a64ae1b)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-10-02 05:38:11 +00:00
mergify[bot]
71526923a6 Expose validator cli arguments for pubsub buffer tuning (#12622)
(cherry picked from commit f41a73d76a)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-10-02 04:43:44 +00:00
mergify[bot]
29af9d1a36 Avoid overflow when computing rent distribution (bp #12112) (#12607)
* Avoid overflow when computing rent distribution (#12112)

* Avoid overflow when computing rent distribution

* Use assert_eq!....

* Fix tests

* Add test

* Use FeatureSet

* Add comments

* Address review comments

* Tweak a bit.

* Fix fmt

(cherry picked from commit e3773d919c)

# Conflicts:
#	runtime/src/bank.rs
#	runtime/src/feature_set.rs

* Fix conflict

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
2020-10-02 01:37:47 +00:00
mergify[bot]
46311181dc Add nonced-tx check to RpcClient (#12600) (#12604)
(cherry picked from commit 8f10e407ee)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-10-01 07:51:16 +00:00
mergify[bot]
17de653ce0 Move nonce utils from runtime to sdk (bp #12577) (#12583)
* runtime: Move prepare_if_nonce_account into accounts

(cherry picked from commit caec631344)

* Move nonced tx helpers to SDK

(cherry picked from commit 65b868f4eb)

* Move remaining nonce utils from runtime to SDK

(cherry picked from commit 3c7b9c2938)

# Conflicts:
#	runtime/src/bank.rs

* Fix conflict

Co-authored-by: Trent Nelson <trent@solana.com>
Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-10-01 06:25:25 +00:00
mergify[bot]
387ecdf70e Add ci env to travis config (#12608) (#12610)
Co-authored-by: publish-docs.sh <maintainers@solana.com>
(cherry picked from commit a17907b9a2)

Co-authored-by: Dan Albert <dan@solana.com>
2020-10-01 06:03:57 +00:00
mergify[bot]
fbe5a89e74 retains hash value of outdated responses received from pull requests (#12513) (#12603)
pull_response_fail_inserts has been increasing:
https://cdn.discordapp.com/attachments/478692221441409024/759096187587657778/pull_response_fail_insert.png
but for outdated values which fail to insert:
https://github.com/solana-labs/solana/blob/a5c3fc14b3/core/src/crds_gossip_pull.rs#L332-L344
https://github.com/solana-labs/solana/blob/a5c3fc14b3/core/src/crds.rs#L104-L108
are not recorded anywhere, and so the next pull request may obtain the
same redundant payload again, unnecessary taking bandwidth.

This commit holds on to the hashes of failed-inserts for a while, similar
to purged_values:
https://github.com/solana-labs/solana/blob/a5c3fc14b3/core/src/crds_gossip_pull.rs#L380
and filter them out for the next pull request:
https://github.com/solana-labs/solana/blob/a5c3fc14b3/core/src/crds_gossip_pull.rs#L204

(cherry picked from commit 1866521df6)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2020-10-01 01:47:20 +00:00
mergify[bot]
afbdcf3068 Include post balance information for rewards (#12598) (#12602)
* Include post balance information for rewards

* Add post-balance to stored Reward struct

* Handle extended Reward in bigtable

Co-authored-by: Michael Vines <mvines@gmail.com>
(cherry picked from commit c31a34fbcb)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-10-01 01:05:37 +00:00
mergify[bot]
2d1b995006 Use protobufs to store confirmed blocks in BigTable (#12526) (#12597)
* Use protobufs to store confirmed blocks in BigTable

* Cleanup

* Reorganize proto

* Clean up use statements

* Split out function for unit testing

* s/utils/convert

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
(cherry picked from commit ce598c5c98)

Co-authored-by: Justin Starry <justin@solana.com>
2020-09-30 19:37:02 +00:00
mergify[bot]
d9d3a95a72 Fix TransactionStatusMeta breakage in blockstore (#12587) (#12596)
* Add helper to facilitate deserializing legacy structs

* Use default_on_eof to fix blockstore vis-a-vis TransactionStatusMeta

* Add should-panic test and comments

(cherry picked from commit 865d01c38d)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-30 19:18:43 +00:00
Michael Vines
ea990fd259 Update devnet genesis hash 2020-09-30 11:37:51 -07:00
mergify[bot]
4f30f9c8cf Modernize python scripts (#12595)
(cherry picked from commit fce3c70b72)

Co-authored-by: Trent Nelson <trent@solana.com>
2020-09-30 18:24:49 +00:00
mergify[bot]
d13694d839 Tighten docs publishing flow (#12572) (#12594)
(cherry picked from commit ede19ef33b)

Co-authored-by: Dan Albert <dan@solana.com>
2020-09-30 18:23:49 +00:00
mergify[bot]
700c8c1ec1 epoch_rewards datapoint now includes the correct rewards epoch (previous epoch) (#12582)
(cherry picked from commit f57af4fec2)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-30 06:56:05 +00:00
mergify[bot]
33ace54b0f Fix banks RPC port (#12570) (#12574)
* Fix Banks RPC ports

* Add get_account_with_commitment

(cherry picked from commit d158d45051)

Co-authored-by: Greg Fitzgerald <greg@solana.com>
2020-09-30 01:23:13 +00:00
mergify[bot]
5d2f450b89 Tune the sys-tuner documentation (#12576)
(cherry picked from commit 6156dc300d)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-30 01:18:24 +00:00
mergify[bot]
55b0e9e9c7 builds crds filters in parallel (bp #12360) (#12571)
* builds crds filters in parallel (#12360)

Based on run-time profiles, the majority time of new_pull_requests is
spent building bloom filters, in hashing and bit-vec ops.

This commit builds crds filters in parallel using rayon constructs. The
added benchmark shows ~5x speedup (4-core machine, 8 threads).

(cherry picked from commit 537bbde22e)

# Conflicts:
#	core/Cargo.toml

* resolves mergify merge conflict

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2020-09-30 01:08:16 +00:00
mergify[bot]
6d1bea7fb4 Include active stake in 'epoch_rewards' datapoint (#12573)
(cherry picked from commit 82848d6c73)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-30 01:08:04 +00:00
mergify[bot]
d19ed8816e Query BigTable for block time if does not exist in blockstore (#12560) (#12565)
(cherry picked from commit 96a7d4dbd8)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-29 23:26:03 +00:00
mergify[bot]
af7f48a2fd Track inserted repair shreds (#12455) (#12563)
(cherry picked from commit ce98088457)

Co-authored-by: sakridge <sakridge@gmail.com>
2020-09-29 22:57:58 +00:00
Tyera Eulberg
0965389f41 Enable json output from solana feature status (#12554) (#12559) 2020-09-29 21:00:20 +00:00
Trent Nelson
24c60cf3db Bump version to v1.3.15 2020-09-29 20:57:08 +00:00
Michael Vines
c8f4bfca90 Notify but don't abort on unexpected stake account balance 2020-09-29 11:39:11 -07:00
Michael Vines
4a6b65ce53 Switch get_program_accounts to use base64 2020-09-29 18:21:19 +00:00
mergify[bot]
836ed842d6 Increase rpc pubsub max payload to unblock large account notifications (#12548) (#12551)
(cherry picked from commit 36d55c0667)

Co-authored-by: Justin Starry <justin@solana.com>
2020-09-29 17:20:31 +00:00
mergify[bot]
966d0f72bb Move process_instruction defs to runtime (#12507) (#12549)
(cherry picked from commit 2ff983647f)

Co-authored-by: Jack May <jack@solana.com>
2020-09-29 15:52:38 +00:00
mergify[bot]
a07e90516b separates out ClusterInfo::{gossip,listen} thread-pools (#12535) (#12547)
https://github.com/solana-labs/solana/pull/12402
moved gossip-work threads:
https://github.com/solana-labs/solana/blob/afd9bfc45/core/src/cluster_info.rs#L2330-L2334
to ClusterInfo::new as a new field in the ClusterInfo struct:
https://github.com/solana-labs/solana/blob/35208c5ee/core/src/cluster_info.rs#L249
So that they can be shared between listen and gossip threads:
https://github.com/solana-labs/solana/blob/afd9bfc45/core/src/gossip_service.rs#L54-L67

However, in testing https://github.com/solana-labs/solana/pull/12360
it turned out this will cause breakage:
https://buildkite.com/solana-labs/solana/builds/31646
https://buildkite.com/solana-labs/solana/builds/31651
https://buildkite.com/solana-labs/solana/builds/31655
Whereas with separate thread pools all is good. It might be the case
that one thread is slowing down the other by exhausting the thread-pool
whereas with separate thread-pools we get fair scheduling guarantees
from the os.

This commit reverts https://github.com/solana-labs/solana/pull/12402
and instead adds separate thread-pools for listen and gossip threads:
https://github.com/solana-labs/solana/blob/afd9bfc45/core/src/gossip_service.rs#L54-L67

(cherry picked from commit 0d5258b6d3)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2020-09-29 11:27:47 +00:00
mergify[bot]
bd2e09d55a patches bug in Crds::find_old_labels with pubkey specific timeout (#12528) (#12546)
Current code only returns values which are expired based on the default
timeout. Example from the added unit test:
  - value inserted at time 0
  - pubkey specific timeout = 1
  - default timeout = 3
Then at now = 2, the value is expired, but the function fails to return
the value because it compares with the default timeout.

(cherry picked from commit 57ed4e4657)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2020-09-29 10:13:13 +00:00
mergify[bot]
655577f9fe feature subcommand: display active stake by feature id when feature activation is not available (#12543)
(cherry picked from commit 322dbd894f)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-29 06:17:31 +00:00
Trent Nelson
3781ad259b clap-utils: Allow nonce/offline args to be global (bp #12538) 2020-09-29 04:51:33 +00:00
Trent Nelson
5ad5f8b458 cli-output: Add a path to handling --verbose and --quiet display (bp #12531) 2020-09-29 04:44:59 +00:00
mergify[bot]
5b322a995f Rpc -> proper optimistic confirmation (#12514) (#12537)
* Add service to track the most recent optimistically confirmed bank

* Plumb service into ClusterInfoVoteListener and ReplayStage

* Clean up test

* Use OptimisticallyConfirmedBank in RPC

* Remove superfluous notifications from RpcSubscriptions

* Use crossbeam to avoid mpsc recv_timeout panic

* Review comments

* Remove superfluous last_checked_slots, but pass in OptimisticallyConfirmedBank for complete correctness

(cherry picked from commit 89621adca7)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-29 03:49:18 +00:00
mergify[bot]
63d9f32bb4 purges old pending push messages more efficiently (#12522) (#12533)
(cherry picked from commit c94fe9236f)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2020-09-29 01:34:58 +00:00
mergify[bot]
a550d82202 Enable commitment arg on solana deploy (#12532) (#12534)
(cherry picked from commit 35208c5ee7)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-28 23:32:50 +00:00
mergify[bot]
4cf69365b2 Port BPFLoader2 activation to FeatureSet (bp #12490) (#12530)
* Cargo.lock

(cherry picked from commit 6071d0d206)

# Conflicts:
#	Cargo.lock

* Port BPFLoader2 activation to FeatureSet and rework built-in program activation

(cherry picked from commit 31696a1d72)

# Conflicts:
#	core/Cargo.toml
#	genesis-programs/Cargo.toml
#	genesis/Cargo.toml
#	ledger/Cargo.toml
#	local-cluster/Cargo.toml
#	runtime/src/bank.rs

* Add Builtin AbiExample

(cherry picked from commit 833ad20b01)

* Rebase

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-28 23:12:05 +00:00
mergify[bot]
873b4ee830 Add a couple feature tests (#12529)
(cherry picked from commit 2956cc5aed)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-28 20:31:09 +00:00
mergify[bot]
672d9c9f62 Add feature to resolve spl-token v2 multisig bug (#12525)
(cherry picked from commit f9a74b51ef)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-28 18:14:20 +00:00
Michael Vines
4bd29c1b32 Add pico-inflation feature
(cherry picked from commit aa5c008fa8)
2020-09-28 09:34:35 -07:00
mergify[bot]
72c082f55a Add precompile verification to preflight (#12486) (#12516)
(cherry picked from commit 6583c8cffe)

Co-authored-by: sakridge <sakridge@gmail.com>
2020-09-28 06:37:52 +00:00
mergify[bot]
d712a908c2 Port fix_recent_blockhashes_sysvar_delay to FeatureSet (#12503)
(cherry picked from commit 5d6410c1cb)

# Conflicts:
#	runtime/src/feature_set.rs

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-26 20:26:58 +00:00
mergify[bot]
e3ca1a81b4 Add copy-on-write executor cache (bp #12502) (#12511)
* Add copy-on-write executor cache (#12502)

* Add copy-on-write executor cache

* Add remove_executor function to the bank

(cherry picked from commit 965f653471)

# Conflicts:
#	runtime/src/bank.rs

* rebase

Co-authored-by: Jack May <jack@solana.com>
Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-26 20:26:50 +00:00
mergify[bot]
16bce553e4 Nit: bpf test cleanup (#12401) (#12508)
(cherry picked from commit 7c4822efb1)

Co-authored-by: Jack May <jack@solana.com>
2020-09-26 17:53:27 +00:00
mergify[bot]
bc3aa53e02 Runtime feature activation framework (bp #12376) (#12497)
* Runtime feature activation framework

(cherry picked from commit 93259f0bae)

# Conflicts:
#	runtime/src/bank.rs

* Add feature set identifier to gossiped version information

(cherry picked from commit 35f5f9fc7b)

# Conflicts:
#	Cargo.lock
#	version/Cargo.toml

* Port instructions sysvar and secp256k1 program activation to FeatureSet

(cherry picked from commit c10da16d7b)

# Conflicts:
#	runtime/src/bank.rs
#	runtime/src/message_processor.rs

* Add feature management commands

(cherry picked from commit 93ed0ab2bb)

# Conflicts:
#	Cargo.lock
#	cli/Cargo.toml

* Make test_process_rest_api less fragile

(cherry picked from commit 7526bb96f3)

* Remove id field

(cherry picked from commit cc6ba1e131)

* FeatureSet test

(cherry picked from commit 92406cf9a0)

* cargo fmt

(cherry picked from commit 199940d683)

* cli review feedback

(cherry picked from commit 3a2b8c5e5b)

* Rename active() to is_active()

(cherry picked from commit e39fac9f01)

* Resolve merge conflicts

* Remove continues from compute_active_feature_set()

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-26 17:49:53 +00:00
Michael Vines
6a698af235 Deerror 2020-09-25 22:19:09 -07:00
Michael Vines
7ec38bd71c Improve 'Failed to create snapshot archive' warning message
(cherry picked from commit 5dcf348098)
2020-09-25 21:06:05 -07:00
mergify[bot]
8e3882287a Add epoch rewards metric datapoint (bp #12505) (#12509)
* Add epoch rewards metric datapoint

(cherry picked from commit e50386f928)

# Conflicts:
#	runtime/src/bank.rs

* Update bank.rs

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-26 04:02:23 +00:00
mergify[bot]
0c4074049b Cleanup names, fix line dependent test (#12477) (#12482)
(cherry picked from commit b8c4b88188)

Co-authored-by: Jack May <jack@solana.com>
2020-09-26 01:08:55 +00:00
mergify[bot]
250d2ba74a Pre-construct cpi instruction recorders before message processing (#12467) (#12504)
(cherry picked from commit 1c970bb39f)

Co-authored-by: Justin Starry <justin@solana.com>
2020-09-26 00:40:32 +00:00
mergify[bot]
b96e0e3d27 Drain the entire compute budget (bp #12478) (#12492)
* Drain the entire compute budget (#12478)


(cherry picked from commit d00453f747)

* fix conflict

Co-authored-by: Jack May <jack@solana.com>
2020-09-25 23:22:19 +00:00
mergify[bot]
99b513d905 Bump rust-bpf to v0.2.4 (#12361) (#12501)
(cherry picked from commit 65049bd112)

Co-authored-by: Jack May <jack@solana.com>
2020-09-25 22:12:39 +00:00
mergify[bot]
e85c792f70 Add RPC notify and banking keys debug (bp #12396) (#12452)
* Add RPC notify and banking keys debug (#12396)

(cherry picked from commit 68e5a2ef56)

# Conflicts:
#	core/src/validator.rs

* Rebase

Co-authored-by: sakridge <sakridge@gmail.com>
Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-25 21:05:21 +00:00
Trent Nelson
b65a764593 Bump jsonrpc crates to 15.0.0 (bp #12491) 2020-09-25 19:49:10 +00:00
mergify[bot]
a514b0e77b Add ComputeBudget tuner (bp #12476) (#12483)
* Add ComputeBudget tuner (#12476)

(cherry picked from commit d326512121)

# Conflicts:
#	programs/bpf/Cargo.toml

* fix conflicts

Co-authored-by: Jack May <jack@solana.com>
2020-09-25 19:13:07 +00:00
mergify[bot]
179dd6ee59 Ignore cargo audit RUSTSEC-2020-0008 (#12489)
(cherry picked from commit cd5c7f30d5)

Co-authored-by: Jack May <jack@solana.com>
2020-09-25 10:01:23 -07:00
mergify[bot]
21ba2bad24 Add Signers impl for Vec<Box<dyn Signer>> (#12470)
(cherry picked from commit 07dfa37cce)

Co-authored-by: Trent Nelson <trent@solana.com>
2020-09-25 10:45:29 +00:00
mergify[bot]
64b6372f9c cli-output: Add CliTokenAccount type (bp #12466) (#12468)
* account-decoder: Add string format helpers to UiTokenAmount

(cherry picked from commit bb144bf758)

* cli-output: Add CliTokenAccount type

(cherry picked from commit d95bce2600)

Co-authored-by: Trent Nelson <trent@solana.com>
2020-09-25 06:05:19 +00:00
mergify[bot]
495ea7cd2f introduce RpcPerfSample and modify getPerformanceSamples output (#12434) (#12464)
* introduce RpcPerfSample and modify getPerformanceSamples output

* camelCase test results

(cherry picked from commit 1d04c1db94)

Co-authored-by: Josh <josh.hundley@gmail.com>
2020-09-24 22:45:05 +00:00
mergify[bot]
bb12d65102 Remove legacy inflation activation code (#12460)
(cherry picked from commit c4aee8c0a0)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-24 20:38:41 +00:00
mergify[bot]
72365bb9d2 moves gossip-work thread pool cons to ClusterInfo::new (#12402) (#12458)
(cherry picked from commit 42f1ef8acb)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2020-09-24 20:01:03 +00:00
mergify[bot]
c44f6981b1 adds an atomic variant of the bloom filter (#12422) (#12459)
For crds_gossip_pull, we want to parallelize build_crds_filters, which
requires concurrent writes to bloom filters.

This commit implements a variant of the bloom filter which uses atomics
for its bits vector and so is thread-safe.

(cherry picked from commit bb183938d9)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2020-09-24 19:49:10 +00:00
mergify[bot]
0213016999 Use publish=false (#12447) (#12453)
(cherry picked from commit a5c3fc14b3)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-24 16:58:18 +00:00
mergify[bot]
4e9e05f311 shards crds values based on their hash prefix (bp #12187) (#12312)
* shards crds values based on their hash prefix (#12187)

filter_crds_values checks every crds filter against every hash value:
https://github.com/solana-labs/solana/blob/ee646aa7/core/src/crds_gossip_pull.rs#L432
which can be inefficient if the filter's bit-mask only matches small
portion of the entire crds table.

This commit shards crds values into separate tables based on shard_bits
first bits of their hash prefix. Given a (mask, mask_bits) filter,
filtering crds can be done by inspecting only relevant shards.

If CrdsFilter.mask_bits <= shard_bits, then precisely only the crds
values which match (mask, mask_bits) bit pattern are traversed.
If CrdsFilter.mask_bits > shard_bits, then approximately only
1/2^shard_bits of crds values are inspected.

Benchmarking on a gce cluster of 20 nodes, I see ~10% improvement in
generate_pull_responses metric, but with larger clusters, crds table and
2^mask_bits are both larger, so the impact should be more significant.

(cherry picked from commit 9b866d79fb)

* bumps indexmap to 1.6.0

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2020-09-24 16:10:53 +00:00
mergify[bot]
7212bb12ea Record and store invoked instructions in transaction meta (#12311) (#12449)
* Record invoked instructions and store in transaction meta

* Enable cpi recording if transaction sender is some

* Rename invoked to innerInstructions

(cherry picked from commit 6601ec8f26)

Co-authored-by: Justin Starry <justin@solana.com>
2020-09-24 15:42:34 +00:00
mergify[bot]
9ff2378948 Remove transaction encoding from storage layer (bp #12404) (#12440)
* Remove transaction encoding from storage layer (#12404)

(cherry picked from commit 731a943239)

* Bump

Co-authored-by: Justin Starry <justin@solana.com>
2020-09-24 10:11:27 +00:00
Tyera Eulberg
ec4938a9f3 Bump version to 1.3.14 (#12444) 2020-09-24 07:42:54 +00:00
Tyera Eulberg
41b45ca281 Allow publishing of secp256k1 program 2020-09-24 00:05:11 -06:00
Trent Nelson
838aaee144 CLI: Factor out offline helpers (bp #12382) 2020-09-24 04:41:30 +00:00
mergify[bot]
c0e44b624e Docs: Set realistic stake warm-up expectations for validators (#12436)
(cherry picked from commit 215bbe85d8)

Co-authored-by: Trent Nelson <trent@solana.com>
2020-09-24 03:01:38 +00:00
mergify[bot]
41ca59ea0e Move dropping AppendVecs outside lock (#12408) (#12429)
* Move drop outside lock

Co-authored-by: Carl Lin <carl@solana.com>
(cherry picked from commit 55be8d4016)

Co-authored-by: carllin <wumu727@gmail.com>
2020-09-23 23:03:44 +00:00
mergify[bot]
a76e175fd0 RpcClient::get_multiple_accounts() now works (#12427)
(cherry picked from commit ff890c173c)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-23 21:56:23 +00:00
Tyera Eulberg
35a1ab981c v1.3: Backport block time updates (#12423)
* Submit a vote timestamp every vote (#10630)

* Submit a timestamp for every vote

* Submit at most one vote timestamp per second

* Submit a timestamp for every new vote

Co-authored-by: Tyera Eulberg <tyera@solana.com>

* Timestamp first vote (#11856)

* Cache block time in Blockstore (#11955)

* Add blockstore column to cache block times

* Add method to cache block time

* Add service to cache block time

* Update rpc getBlockTime to use new method, and refactor blockstore slightly

* Return block_time with confirmed block, if available

* Add measure and warning to cache-block-time

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-23 13:54:49 -06:00
mergify[bot]
0f3a555af5 Document getConfirmedSignaturesForAddress2 until param (#12424) (#12425)
(cherry picked from commit a713e3c92d)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-23 19:47:00 +00:00
mergify[bot]
5366a3c887 Fix solana-tokens check_payer_balances for distribute-stake (bp #12380) (#12403)
* Fix solana-tokens check_payer_balances for distribute-stake (#12380)

* Handle distribute-stakes properly

* Remove dry-run gating for balance checks

* Reword and simplify InsufficientFunds errors

* Split up test and add helpers

* Rename sol_for_fees -> unlocked_sol

* Refactor distribute_allocations to collect Messages

* Clippy

* Clean up dangling bids

(cherry picked from commit 6563726f22)

# Conflicts:
#	tokens/src/commands.rs

* Fix conflicts

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
2020-09-23 05:27:01 +00:00
Trent Nelson
486b657fca validator: Add --require-tower stub arg 2020-09-23 05:01:12 +00:00
mergify[bot]
e545bdcb51 Bump spl-token (bp #12395) (#12400)
* Bump spl-token (#12395)

(cherry picked from commit e1a212fb79)

# Conflicts:
#	Cargo.lock
#	account-decoder/Cargo.toml
#	core/Cargo.toml

* Fix conflicts

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
2020-09-23 01:08:20 +00:00
mergify[bot]
65e4aac306 Add blockstore column to store performance sampling data (bp #12251) (#12393)
* Add blockstore column to store performance sampling data (#12251)

* Add blockstore column to store performance sampling data

* introduce timer and write performance metrics to blockstore

* introduce getRecentPerformanceSamples rpc

* only run on rpc nodes enabled with transaction history

* add unit tests for get_recent_performance_samples

* remove RpcResponse from rpc call

* refactor to use Instant::now and elapsed for timer

* switch to root bank and ensure not negative subraction

* Add PerfSamples to purge/compaction

* refactor to use Instant::now and elapsed for timer

* switch to root bank and ensure not negative subraction

* remove duplicate constants

Co-authored-by: Tyera Eulberg <tyera@solana.com>
(cherry picked from commit 65a6bfad09)

# Conflicts:
#	core/src/validator.rs
#	ledger/src/blockstore.rs

* merge cherry pick of 65a6bfad0

Co-authored-by: Josh <josh.hundley@gmail.com>
2020-09-22 22:00:51 +00:00
mergify[bot]
339e72d8d2 Simplify cli node version output, display semver only by default (#12386)
(cherry picked from commit 4fa443becf)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-22 08:07:28 +00:00
mergify[bot]
0f3208dece Cleanup and feature gate instruction processing (#12359) (#12384)
(cherry picked from commit 22d8b3c3f8)

Co-authored-by: sakridge <sakridge@gmail.com>
2020-09-22 06:19:14 +00:00
mergify[bot]
a85a2839e4 Add way to look at tx instructions (#11943) (#12375)
Co-authored-by: sakridge <sakridge@gmail.com>
2020-09-22 00:59:46 +00:00
mergify[bot]
4fc9f12d7b CLI: Drop unused runtime dep (#12374)
(cherry picked from commit 6767264aa1)

Co-authored-by: Trent Nelson <trent@solana.com>
2020-09-21 20:38:44 +00:00
mergify[bot]
8af90c9c08 Add keccak-secp256k1 instruction (#11839) (#12368)
* Implement keccak-secp256k1 instruction

Verifies eth addreses with ecrecover function

* Move secp256k1 test

Co-authored-by: sakridge <sakridge@gmail.com>
2020-09-21 18:09:05 +00:00
mergify[bot]
7db0464d1b Bind to correct RPC addresses (#12358)
(cherry picked from commit 65b247a922)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-20 08:33:07 +00:00
Michael Vines
bc774e3ea6 Bump version to v1.3.13 2020-09-20 05:40:32 +00:00
Michael Vines
64af712723 Use validator_config for RPC address instead of cluster_info for port verification checks 2020-09-20 02:27:35 +00:00
Michael Vines
ea2611f3a9 Document that testnet has a faucet 2020-09-19 17:41:13 -07:00
mergify[bot]
a26e1f62bb validator/ cleanup (bp #12340) (#12352)
* validator/ cleanup

(cherry picked from commit 1a03afccb1)

# Conflicts:
#	core/src/validator.rs

* Move TestValidator into its own module

(cherry picked from commit 208dd1de3a)

# Conflicts:
#	core/src/validator.rs
#	tokens/tests/commands.rs

* Rebase

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-19 22:45:09 +00:00
mergify[bot]
54b87b34c3 Add get_token_account methods (#12346) (#12349)
(cherry picked from commit 28f2c15597)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-19 03:53:51 +00:00
mergify[bot]
12327c8683 Improve error message when .config/solana/id.json is not found (#12345)
(cherry picked from commit 0ed7b0561e)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-19 00:44:16 +00:00
Trent Nelson
86554c4945 Docs: Add SPL Token exchange integration (bp #12303) 2020-09-19 00:37:25 +00:00
Tyera Eulberg
95ed3641c6 Add blocktime column to blockstore (#12336) 2020-09-18 21:42:45 +00:00
mergify[bot]
83c775cee8 Unbreak 'Listening for Deposits' section (#12338) (#12339)
(cherry picked from commit 06906413ef)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-18 21:19:15 +00:00
mergify[bot]
9fbbb7b044 SendTransactionServices now exit their thread on channel drop instead of by a flag (bp #12333) (#12335)
* Give the duplicate send_transaction_service a different thread name

(cherry picked from commit 75c3690ccd)

* SendTransactionServices now exit their thread on channel drop instead of by a flag

(cherry picked from commit c4913e3c9e)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-18 18:32:06 +00:00
mergify[bot]
7656034bd4 Fix blockstore processor squash (#12319) (#12323)
(cherry picked from commit 3533e11786)

Co-authored-by: carllin <wumu727@gmail.com>
2020-09-18 05:34:37 +00:00
mergify[bot]
3ede265ff9 Add Pack and COption to sdk (bp #12294) (#12322)
* Add Pack and COption to sdk (#12294)

* Add COption to sdk

* Add Pack to sdk

* Except program_option from nits check

* No Default::default

(cherry picked from commit 58542cf7f6)

# Conflicts:
#	ci/nits.sh

* Fix conflict

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
2020-09-18 04:31:09 +00:00
mergify[bot]
a20e954a16 Restore --expected-shred-version argument for mainnet-beta (#12300)
(cherry picked from commit 9410eab2af)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-17 01:20:25 +00:00
mergify[bot]
1f69b125ce Remove client resends (#12290) (#12296)
* Remove resends from client send_tx methods

* Retry status queries until blockhash expires

(cherry picked from commit a79790dea6)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-17 01:01:40 +00:00
mergify[bot]
0196c83846 Fix panic in BanksServer (#12293) (#12295)
Fixes #12167

(cherry picked from commit 3ecb390b10)

Co-authored-by: Greg Fitzgerald <greg@solana.com>
2020-09-17 00:36:30 +00:00
Justin Starry
37175d0cdf Fix off-by-one max payload checks
(cherry picked from commit f6cda2579f)
2020-09-16 17:05:09 -07:00
mergify[bot]
a6e73acfa4 Rework snapshot download logic to be more forgiving when --expected-shred-version is not provided (#12289)
(cherry picked from commit 98cfe92745)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-16 21:37:57 +00:00
mergify[bot]
f00c504555 RPC: Limit request payload size to 50kB (#12287)
(cherry picked from commit 32dcce0ac1)

Co-authored-by: Trent Nelson <trent@solana.com>
2020-09-16 21:26:31 +00:00
mergify[bot]
19eb73d645 docs: Fix 'Description will go into a meta tag in head' meta tag (bp #12277) (#12279)
* Fix 'Description will go into a meta tag in head' meta tag

(cherry picked from commit 5d682d2e05)

* Update index.js

(cherry picked from commit c231bb7154)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-16 16:56:26 +00:00
mergify[bot]
d35dc79ee7 RPC sendTransaction now returns transaction logs on simulation failure (bp #12267) (#12276)
* RPC sendTransaction now returns transaction logs on simulation failure

(cherry picked from commit 749208fa32)

* Remove stale comment

(cherry picked from commit c6eea94edc)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-16 16:45:09 +00:00
mergify[bot]
2316846c53 solana-tokens: Add capability to perform the same transfer to a batch of recipients (bp #12259) (#12266)
* solana-tokens: Add capability to perform the same transfer to a batch of recipients (#12259)

* Add transfer-amount argument, use simplified input-csv

* Add transfer-amount to readme

(cherry picked from commit a48cc073cf)

# Conflicts:
#	tokens/src/commands.rs
#	tokens/tests/commands.rs

* Fix build

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
2020-09-16 06:21:40 +00:00
mergify[bot]
c77fe54629 CLI: Use Base58 encoding rather than deprecated Binary for TX decode (#12265)
(cherry picked from commit 83f93fed02)

Co-authored-by: Trent Nelson <trent@solana.com>
2020-09-16 05:43:38 +00:00
mergify[bot]
953c40a9e3 Improve solana-tokens UX (#12253) (#12260)
* Fix computed banks port

* Readme incorrect

* Return error if csv cannot be read

* Move column headers over columns

* Add dry-run check for sender/fee-payer balances

* Use clap requires method for paired args

* Write transaction-log anytime outfile is specified

* Replace campaign-name with required db-path

* Remove bids

* Exclude new_stake_account_address from logs for non-stake distributions

* Fix readme

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-16 04:50:55 +00:00
mergify[bot]
c10f14d60b Add lockups via solana-tokens (bp #11782) (#12263)
* Add lockups via solana-tokens (#11782)

* Allow stake distributions to update lockups

* Reorg

* Add lockup test

* Fix clippy warning

(cherry picked from commit 5553732ae2)

# Conflicts:
#	tokens/Cargo.toml
#	tokens/src/commands.rs
#	tokens/src/main.rs

* Fix build

Co-authored-by: Greg Fitzgerald <greg@solana.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
2020-09-16 03:33:39 +00:00
mergify[bot]
c3c3872f8d validator-info get/set no longer crash on invalid account data (#12258)
(cherry picked from commit 56282f0c01)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-16 01:17:45 +00:00
mergify[bot]
c539526f1e Add memory allocation support for C programs (#12254) (#12256)
(cherry picked from commit 5ab4109b7e)

Co-authored-by: Jack May <jack@solana.com>
2020-09-16 00:49:36 +00:00
mergify[bot]
8ea4c1c2c0 Friendlier error message for mapping failures (#12213) (#12255)
(cherry picked from commit 3d4b9bb00d)

Co-authored-by: sakridge <sakridge@gmail.com>
2020-09-16 00:21:11 +00:00
mergify[bot]
557fee8183 Make noop a real noop (bp #12196) (#12247)
* Make noop a real noop (#12196)

* Make noop a real noop

* nudge

(cherry picked from commit 555252f435)

* resolve conflicts

Co-authored-by: Jack May <jack@solana.com>
2020-09-15 23:16:15 +00:00
mergify[bot]
1957e960ac Add BPF test program instruction monitoring (bp #11984) (#12248)
* Add BPF test program instruction monitoring (#11984)


(cherry picked from commit fab2d44abd)

* fix conflicts

Co-authored-by: Jack May <jack@solana.com>
2020-09-15 23:16:01 +00:00
mergify[bot]
11ff80c64b Cache re-usable work performed by the loader (bp #12135) (#12216)
* Cache re-usable work performed by the loader (#12135)

(cherry picked from commit 3278d78f08)

# Conflicts:
#	programs/bpf/Cargo.toml
#	programs/bpf/tests/programs.rs
#	programs/bpf_loader/Cargo.toml

* resolve conflicts

Co-authored-by: Jack May <jack@solana.com>
2020-09-15 21:25:32 +00:00
Ryo Onodera
7267257073 Bump version to v1.3.12 (#12249) 2020-09-15 20:15:03 +00:00
mergify[bot]
f107b9b423 Really skip private rpc port reachable checks (#12239) (#12241)
(cherry picked from commit b85e8497b5)

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
2020-09-15 17:43:23 +00:00
mergify[bot]
1354a0c1a3 Drop the recommendation that --expected-shred-version be set by validators (#12244)
`--expected-shred-version` is another knob for users to get wrong and is
documentation that can get stale due to cluster restarts.  Turns out
it's also generally not required anymore either because:
1. The cluster entrypoint can always be expected to be using the correct
   shred version, and that shred version will be adopted by the new node
   (earlier this was not the case when the `solana-gossip spy` node on
   mainnet-beta.solana.com:8001 ran with shred version 0)
2. On a cluster restart, `--expected-bank-hash` is a much stronger
   assertion that the validator is starting from the correct place (and
   didn't exist when `--expected-shred-version` was first recommended)

(cherry picked from commit 4ada4d43f2)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-15 17:41:36 +00:00
Ryo Onodera
52ee9155b5 Bump version to v1.3.11 (#12238) 2020-09-15 10:25:22 +00:00
mergify[bot]
65a1884c7b Enable stricter check on rent-exempt accounts on testnet (#12224) (#12226)
(cherry picked from commit 241e6f1ecf)

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
2020-09-15 08:07:23 +00:00
mergify[bot]
c1a9c826f3 Enable retirement of rent collect in Bank::deposit() on testnet (#12223) (#12227)
(cherry picked from commit 629572831b)

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
2020-09-15 07:53:36 +00:00
mergify[bot]
d9d8ec480a Enable eager-rent-collect-across-gapped-epochs bugfix (#12219) (#12222)
(cherry picked from commit 7d48339b7c)

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
2020-09-15 06:14:35 +00:00
mergify[bot]
b5c7ad3a9b Add new validator options for running in more restrictive environments (bp #12191) (#12218)
* Add --restricted-repair-only-mode flag

(cherry picked from commit 63a67f415e)

* Add --gossip-validator argument

(cherry picked from commit daae638781)

* Documenet how to reduce validator port exposure

(cherry picked from commit c8f03c7f6d)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-15 04:26:23 +00:00
mergify[bot]
771ff65fb4 Faucet: Improve error handling (#12215)
(cherry picked from commit af2262cbba)

Co-authored-by: Trent Nelson <trent@solana.com>
2020-09-15 01:31:02 +00:00
mergify[bot]
a5bf59b92a patches default impl for crds filter (#12199) (#12200)
In CrdsFilter.mask all bits after mask_bits are set to 1:
https://github.com/solana-labs/solana/blob/555252f4/core/src/crds_gossip_pull.rs#L65
However the default implementation, sets both mask and mask_bits to zero
which is inconsistent with CrdsFilter::compute_mask for a mask_bits of
zero.

This commit changes the default implementation by setting mask to
`!0u64` (i.e all bits set to one). As a result, for the default crds
filter, `test_mask` will always return true, whereas previously it was
always returning false.
https://github.com/solana-labs/solana/blob/555252f4/core/src/crds_gossip_pull.rs#L85

This is only used in tests and benchmarks, but causes some benchmarks to
be misleading by short circuiting in this line:
https://github.com/solana-labs/solana/blob/555252f4/core/src/crds_gossip_pull.rs#L429

(cherry picked from commit d6ec03f13c)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2020-09-13 14:10:42 +00:00
mergify[bot]
8dc019ae98 Gate pointer alignment enforcement (bp #12176) (#12188)
* Gate pointer alignment enforcement (#12176)

(cherry picked from commit ae7b15f062)

# Conflicts:
#	programs/bpf/tests/programs.rs

* Fix conflicts

Co-authored-by: Jack May <jack@solana.com>
2020-09-12 00:06:39 +00:00
mergify[bot]
61dcab8c07 Update commitment options (#12173) (#12189)
(cherry picked from commit 3c69cd6d61)

Co-authored-by: carllin <wumu727@gmail.com>
2020-09-11 19:24:16 +00:00
mergify[bot]
640bf7015f Check bank capitalization (bp #11927) (#12184)
* Check bank capitalization (#11927)

* Check bank capitalization

* Simplify and unify capitalization calculation

* Improve and add tests

* Avoid overflow and inhibit automatic restart

* Fix test

* Tweak checked sum for cap. and add tests

* Fix broken build after merge conflicts..

* Rename to ClusterType

* Rename confusing method

* Clarify comment

* Verify cap. in rent and inflation tests

Co-authored-by: Stephen Akridge <sakridge@gmail.com>
(cherry picked from commit de4a613610)

# Conflicts:
#	Cargo.lock
#	accounts-bench/Cargo.toml

* Fix conflict 1/2

* Fix conflict 2/2

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
2020-09-11 18:04:10 +00:00
mergify[bot]
db1f57162a Fix propagation on startup from snapshot (#12177) (#12182)
(cherry picked from commit 9c490e06b0)

Co-authored-by: carllin <wumu727@gmail.com>
2020-09-11 10:04:55 +00:00
mergify[bot]
bbddffa805 solana-validator --rpc-bind-address argument now works as expected (bp #12168) (#12174)
* `solana-validator --rpc-bind-address` argument now works as expected

(cherry picked from commit 6f325d4594)

* Update bootstrap-validator.sh

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-10 22:02:50 +00:00
mergify[bot]
61cf432477 Update commitment options (#12171) (#12172)
(cherry picked from commit 361e5322e4)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-10 13:55:48 -06:00
mergify[bot]
9396618c12 Calc size ahead of time to alloc once (#12154) (#12169)
(cherry picked from commit fd47d38e59)

Co-authored-by: Jack May <jack@solana.com>
2020-09-10 19:17:20 +00:00
mergify[bot]
398f12dcc5 Program subscriptions now properly check results len and token program id (#12139) (#12141)
(cherry picked from commit 4431080066)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-09 22:23:11 +00:00
mergify[bot]
796624adf9 uses rust intrinsics to convert hashes to u64 (#12097) (#12133)
(cherry picked from commit 28f2fa3fd5)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2020-09-09 16:33:15 +00:00
mergify[bot]
f6e266eff0 Activate new bpf loader on devnet (#12124)
(cherry picked from commit f54941fa15)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-09 05:48:18 +00:00
mergify[bot]
ed237af4d8 Prevent unbound memory growth by blockstore_processor (#12110) (#12122)
* Prevent unbound memory growth by blockstore_processor

* Promote log to info! considering infrequency

* Exclude the time of freeing from interval...

* Skip not-shrinkable slots even if forced

* Add comment

(cherry picked from commit c274e26eb8)

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
2020-09-09 04:09:27 +00:00
Michael Vines
415c80c204 Bump version to v1.3.10 2020-09-09 01:29:39 +00:00
mergify[bot]
e45f1df5dd Reduce cap by rent's leftover as temporary measure (#12111) (#12118)
* Reduce cap by rent's leftover as temporary measure

* Reset testnet cap. on start and more logs

(cherry picked from commit 5b2442d54e)

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
2020-09-08 20:02:46 +00:00
mergify[bot]
d26a809f1f getMinimumBalanceForRentExemption now only responds to valid account lengths (#12116)
(cherry picked from commit 9e96180ce4)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-08 18:50:22 +00:00
mergify[bot]
d1fdc96969 Add support for deprecated loader (bp #11946) (#12114)
* Add support for deprecated loader (#11946)


(cherry picked from commit ae0fd3043a)

* fix version

Co-authored-by: Jack May <jack@solana.com>
2020-09-08 18:41:29 +00:00
mergify[bot]
a5cad340ed Forward transactions to the expected leader instead of your own TPU port (bp #12004) (#12108)
* Forward transactions to the expected leader instead of your own TPU port (#12004)

* Use PoHRecorder to send to the right leader

* cleanup

* fmt

* clippy

* Cleanup, fix bug

Co-authored-by: Carl <carl@solana.com>
(cherry picked from commit c67f8bd821)

# Conflicts:
#	banks-server/Cargo.toml

* Update Cargo.toml

Co-authored-by: anatoly yakovenko <anatoly@solana.com>
Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-08 16:51:33 +00:00
Ryo Onodera
2341394e8b Rename to ClusterType and restore devnet compat. (manual bp) (#12069)
* Rename to ClusterType and restore devnet compat.

* De-duplicate parse code and add comments

* Adjust default Devnet genesis & reduce it in tests
2020-09-08 23:54:54 +09:00
mergify[bot]
180224114a Fix RPC transaction method configs serialization (#12100) (#12102)
(cherry picked from commit 9940870c89)

Co-authored-by: Justin Starry <justin@solana.com>
2020-09-08 06:12:26 +00:00
Justin Starry
58312655b4 Fix signature subscription panic (#12077) (#12092) 2020-09-07 10:31:25 +00:00
mergify[bot]
f3f86f43ee Compress snapshot archive within the validator to reduce system dependencies, and default to zstd compression (bp #12085) (#12087)
* Compress snapshot archive within the validator to reduce system dependencies

(cherry picked from commit d3750b47d2)

* Default snapshot compression to zstd instead of bzip2 for quicker snapshot generation

(cherry picked from commit 9ade73841f)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-07 06:55:38 +00:00
mergify[bot]
aac38516da cli: add block and first-available-block commands (bp #12083) (#12084)
* Add first-available-block command

(cherry picked from commit 6677996369)

* Add block command

(cherry picked from commit 27752c4e4d)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-06 20:53:44 +00:00
mergify[bot]
cd139b8185 Fix bad predicate with malformed gossip votes (#12072) (#12078)
(cherry picked from commit eabc63cdcd)

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
2020-09-06 05:35:00 +00:00
mergify[bot]
6a74bc1297 validator: Add --enable-bigtable-ledger-upload flag (bp #12040) (#12057)
* Add --enable-bigtable-ledger-upload flag

(cherry picked from commit d8e2038dda)

* Relocate BigTable uploader to ledger/ crate

(cherry picked from commit 91a56caed2)

# Conflicts:
#	ledger/Cargo.toml

* Cargo.lock

(cherry picked from commit 2b8a521562)

* Add BigTableUploadService

(cherry picked from commit bc7731b969)

* Add BigTableUploadService

(cherry picked from commit bafdcf24f5)

* Add exit flag for bigtable upload operations

(cherry picked from commit d3611f74c8)

* Remove dead code

(cherry picked from commit cd3c134b58)

* Request correct access

(cherry picked from commit 4ba43c29ce)

* Add LARGEST_CONFIRMED_ROOT_UPLOAD_DELAY

(cherry picked from commit b64fb295a1)

* Resolve merge conflicts

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-05 17:09:32 +00:00
mergify[bot]
e9a2dd0bc1 Add --show-transactions flag to transaction-history command (#12071)
(cherry picked from commit 2332dd774f)

Co-authored-by: Michael Vines <mvines@gmail.com>
2020-09-05 16:52:38 +00:00
mergify[bot]
feed960ef3 Bigtable bug fixes (#12058) (#12060)
* Accommodate stricted get_bincode_cell in get_confirmed_signatures_for_address

* Sort signatures newest-oldest, even within slot

(cherry picked from commit 879c98efeb)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-05 03:31:35 +00:00
mergify[bot]
ae8acada8c Add unlock epochs for blake3 (#12054) (#12056)
Co-authored-by: Carl <carl@solana.com>
(cherry picked from commit a13efc52b3)

Co-authored-by: carllin <wumu727@gmail.com>
2020-09-04 23:33:53 +00:00
mergify[bot]
357a341db5 Bump getMultipleAccounts input limit (#12050) (#12052)
(cherry picked from commit 954b017f85)

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
2020-09-04 18:58:45 +00:00
mergify[bot]
d0f715cb23 adds new CrdsFilterSet type for Vec<CrdsFilter> (#12029) (#12048)
(cherry picked from commit 114c211b66)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2020-09-04 14:10:12 +00:00
mergify[bot]
0fc0378347 fix solana-install syntax (#12043)
```
solana-install info
solana-install deploy
solana-install update
solana-install run
```

(cherry picked from commit 38f36a7a7a)

Co-authored-by: pk <4514654+pkrasam@users.noreply.github.com>
2020-09-04 05:40:47 +00:00
mergify[bot]
aa3bdd3730 Revert signature-notification format change (bp #12032) (#12038)
* Revert signature-notification format change (#12032)

* Use untagged RpcSignatureResult enum to avoid breaking downstream consumers of current signature subscriptions

* Clean up client duplication

* Clippy

(cherry picked from commit 39246f9dd7)

# Conflicts:
#	core/src/rpc_pubsub.rs

* Fix conflicts

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
2020-09-04 01:34:49 +00:00
mergify[bot]
962aed0961 Mark a withdraw authority as non-circulating (#12033) (#12036)
(cherry picked from commit 2c091e4fca)

Co-authored-by: Greg Fitzgerald <greg@solana.com>
2020-09-04 00:46:10 +00:00
Josh
bd76810623 Bump version to 1.3.9 (#12034) 2020-09-03 16:55:24 -07:00
321 changed files with 17962 additions and 6713 deletions

View File

@@ -124,6 +124,8 @@ jobs:
- ~/.npm
before_install:
- source ci/env.sh
- .travis/channel_restriction.sh edge beta || travis_terminate 0
- .travis/affects.sh docs/ .travis || travis_terminate 0
- cd docs/
- source .travis/before_install.sh

17
.travis/channel_restriction.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
#
# Only proceed if we are on one of the channels passed in when calling this file
#
set -ex
eval "$(ci/channel-info.sh)"
for acceptable_channel in "$@"; do
if [[ "$CHANNEL" == "$acceptable_channel" ]]; then
exit 0
fi
done
echo "Not running from one of the following channels: $*"
exit 1

610
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@ members = [
"banks-server",
"clap-utils",
"cli-config",
"cli-output",
"client",
"core",
"dos",
@@ -18,7 +19,6 @@ members = [
"perf",
"validator",
"genesis",
"genesis-programs",
"gossip",
"install",
"keygen",
@@ -36,6 +36,7 @@ members = [
"net-shaper",
"notifier",
"poh-bench",
"programs/secp256k1",
"programs/bpf_loader",
"programs/budget",
"programs/config",

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-account-decoder"
version = "1.3.8"
version = "1.3.15"
description = "Solana account decoder"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -18,11 +18,11 @@ lazy_static = "1.4.0"
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.56"
solana-config-program = { path = "../programs/config", version = "1.3.8" }
solana-sdk = { path = "../sdk", version = "1.3.8" }
solana-stake-program = { path = "../programs/stake", version = "1.3.8" }
solana-vote-program = { path = "../programs/vote", version = "1.3.8" }
spl-token-v2-0 = { package = "spl-token", version = "2.0.3", features = ["skip-no-mangle"] }
solana-config-program = { path = "../programs/config", version = "1.3.15" }
solana-sdk = { path = "../sdk", version = "1.3.15" }
solana-stake-program = { path = "../programs/stake", version = "1.3.15" }
solana-vote-program = { path = "../programs/vote", version = "1.3.15" }
spl-token-v2-0 = { package = "spl-token", version = "2.0.6", features = ["skip-no-mangle"] }
thiserror = "1.0"
[package.metadata.docs.rs]

View File

@@ -4,9 +4,7 @@ use crate::{
};
use solana_sdk::pubkey::Pubkey;
use spl_token_v2_0::{
option::COption,
pack::Pack,
solana_sdk::pubkey::Pubkey as SplTokenPubkey,
solana_sdk::{program_option::COption, program_pack::Pack, pubkey::Pubkey as SplTokenPubkey},
state::{Account, AccountState, Mint, Multisig},
};
use std::str::FromStr;
@@ -156,6 +154,31 @@ pub struct UiTokenAmount {
pub amount: StringAmount,
}
impl UiTokenAmount {
pub fn real_number_string(&self) -> String {
let decimals = self.decimals as usize;
if decimals > 0 {
let amount = u64::from_str(&self.amount).unwrap_or(0);
// Left-pad zeros to decimals + 1, so we at least have an integer zero
let mut s = format!("{:01$}", amount, decimals + 1);
// Add the decimal point (Sorry, "," locales!)
s.insert(s.len() - decimals, '.');
s
} else {
self.amount.clone()
}
}
pub fn real_number_string_trimmed(&self) -> String {
let s = self.real_number_string();
let zeros_trimmed = s.trim_end_matches('0');
let decimal_trimmed = zeros_trimmed.trim_end_matches('.');
decimal_trimmed.to_string()
}
}
pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount {
// Use `amount_to_ui_amount()` once spl_token is bumped to a version that supports it: https://github.com/solana-labs/solana-program-library/pull/211
let amount_decimals = amount as f64 / 10_usize.pow(decimals as u32) as f64;
@@ -202,16 +225,14 @@ mod test {
let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
let owner_pubkey = SplTokenPubkey::new(&[3; 32]);
let mut account_data = vec![0; Account::get_packed_len()];
Account::unpack_unchecked_mut(&mut account_data, &mut |account: &mut Account| {
account.mint = mint_pubkey;
account.owner = owner_pubkey;
account.amount = 42;
account.state = AccountState::Initialized;
account.is_native = COption::None;
account.close_authority = COption::Some(owner_pubkey);
Ok(())
})
.unwrap();
let mut account = Account::unpack_unchecked(&account_data).unwrap();
account.mint = mint_pubkey;
account.owner = owner_pubkey;
account.amount = 42;
account.state = AccountState::Initialized;
account.is_native = COption::None;
account.close_authority = COption::Some(owner_pubkey);
Account::pack(account, &mut account_data).unwrap();
assert!(parse_token(&account_data, None).is_err());
assert_eq!(
@@ -234,15 +255,13 @@ mod test {
);
let mut mint_data = vec![0; Mint::get_packed_len()];
Mint::unpack_unchecked_mut(&mut mint_data, &mut |mint: &mut Mint| {
mint.mint_authority = COption::Some(owner_pubkey);
mint.supply = 42;
mint.decimals = 3;
mint.is_initialized = true;
mint.freeze_authority = COption::Some(owner_pubkey);
Ok(())
})
.unwrap();
let mut mint = Mint::unpack_unchecked(&mint_data).unwrap();
mint.mint_authority = COption::Some(owner_pubkey);
mint.supply = 42;
mint.decimals = 3;
mint.is_initialized = true;
mint.freeze_authority = COption::Some(owner_pubkey);
Mint::pack(mint, &mut mint_data).unwrap();
assert_eq!(
parse_token(&mint_data, None).unwrap(),
@@ -263,14 +282,13 @@ mod test {
signers[0] = signer1;
signers[1] = signer2;
signers[2] = signer3;
Multisig::unpack_unchecked_mut(&mut multisig_data, &mut |multisig: &mut Multisig| {
multisig.m = 2;
multisig.n = 3;
multisig.is_initialized = true;
multisig.signers = signers;
Ok(())
})
.unwrap();
let mut multisig = Multisig::unpack_unchecked(&multisig_data).unwrap();
multisig.m = 2;
multisig.n = 3;
multisig.is_initialized = true;
multisig.signers = signers;
Multisig::pack(multisig, &mut multisig_data).unwrap();
assert_eq!(
parse_token(&multisig_data, None).unwrap(),
TokenAccountType::Multisig(UiMultisig {
@@ -293,11 +311,9 @@ mod test {
fn test_get_token_account_mint() {
let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
let mut account_data = vec![0; Account::get_packed_len()];
Account::unpack_unchecked_mut(&mut account_data, &mut |account: &mut Account| {
account.mint = mint_pubkey;
Ok(())
})
.unwrap();
let mut account = Account::unpack_unchecked(&account_data).unwrap();
account.mint = mint_pubkey;
Account::pack(account, &mut account_data).unwrap();
let expected_mint_pubkey = Pubkey::new(&[2; 32]);
assert_eq!(
@@ -305,4 +321,20 @@ mod test {
Some(expected_mint_pubkey)
);
}
#[test]
fn test_ui_token_amount_real_string() {
let token_amount = token_amount_to_ui_amount(1, 0);
assert_eq!(&token_amount.real_number_string(), "1");
assert_eq!(&token_amount.real_number_string_trimmed(), "1");
let token_amount = token_amount_to_ui_amount(1, 9);
assert_eq!(&token_amount.real_number_string(), "0.000000001");
assert_eq!(&token_amount.real_number_string_trimmed(), "0.000000001");
let token_amount = token_amount_to_ui_amount(1_000_000_000, 9);
assert_eq!(&token_amount.real_number_string(), "1.000000000");
assert_eq!(&token_amount.real_number_string_trimmed(), "1");
let token_amount = token_amount_to_ui_amount(1_234_567_890, 3);
assert_eq!(&token_amount.real_number_string(), "1234567.890");
assert_eq!(&token_amount.real_number_string_trimmed(), "1234567.89");
}
}

View File

@@ -2,18 +2,20 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-accounts-bench"
version = "1.3.8"
version = "1.3.15"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
publish = false
[dependencies]
log = "0.4.6"
rayon = "1.4.0"
solana-logger = { path = "../logger", version = "1.3.8" }
solana-runtime = { path = "../runtime", version = "1.3.8" }
solana-measure = { path = "../measure", version = "1.3.8" }
solana-sdk = { path = "../sdk", version = "1.3.8" }
solana-logger = { path = "../logger", version = "1.3.15" }
solana-runtime = { path = "../runtime", version = "1.3.15" }
solana-measure = { path = "../measure", version = "1.3.15" }
solana-sdk = { path = "../sdk", version = "1.3.15" }
solana-version = { path = "../version", version = "1.3.15" }
rand = "0.7.0"
clap = "2.33.1"
crossbeam-channel = "0.4"

View File

@@ -1,20 +1,21 @@
use clap::{value_t, App, Arg};
use clap::{crate_description, crate_name, value_t, App, Arg};
use rayon::prelude::*;
use solana_measure::measure::Measure;
use solana_runtime::{
accounts::{create_test_accounts, update_accounts, Accounts},
accounts_index::Ancestors,
};
use solana_sdk::{genesis_config::OperatingMode, pubkey::Pubkey};
use solana_sdk::{genesis_config::ClusterType, pubkey::Pubkey};
use std::env;
use std::fs;
use std::path::PathBuf;
fn main() {
solana_logger::setup();
let matches = App::new("crate")
.about("about")
.version("version")
let matches = App::new(crate_name!())
.about(crate_description!())
.version(solana_version::version!())
.arg(
Arg::with_name("num_slots")
.long("num_slots")
@@ -50,11 +51,12 @@ fn main() {
let clean = matches.is_present("clean");
println!("clean: {:?}", clean);
let path = PathBuf::from("farf/accounts-bench");
let path = PathBuf::from(env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_owned()))
.join("accounts-bench");
if fs::remove_dir_all(path.clone()).is_err() {
println!("Warning: Couldn't remove {:?}", path);
}
let accounts = Accounts::new(vec![path], &OperatingMode::Preview);
let accounts = Accounts::new(vec![path], &ClusterType::Testnet);
println!("Creating {} accounts", num_accounts);
let mut create_time = Measure::start("create accounts");
let pubkeys: Vec<_> = (0..num_slots)
@@ -96,7 +98,7 @@ fn main() {
} else {
let mut pubkeys: Vec<Pubkey> = vec![];
let mut time = Measure::start("hash");
let hash = accounts.accounts_db.update_accounts_hash(0, &ancestors);
let hash = accounts.accounts_db.update_accounts_hash(0, &ancestors).0;
time.stop();
println!("hash: {} {}", hash, time);
create_test_accounts(&accounts, &mut pubkeys, 1, 0);

View File

@@ -2,10 +2,11 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-banking-bench"
version = "1.3.8"
version = "1.3.15"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
publish = false
[dependencies]
clap = "2.33.1"
@@ -13,16 +14,16 @@ crossbeam-channel = "0.4"
log = "0.4.6"
rand = "0.7.0"
rayon = "1.4.0"
solana-core = { path = "../core", version = "1.3.8" }
solana-clap-utils = { path = "../clap-utils", version = "1.3.8" }
solana-streamer = { path = "../streamer", version = "1.3.8" }
solana-perf = { path = "../perf", version = "1.3.8" }
solana-ledger = { path = "../ledger", version = "1.3.8" }
solana-logger = { path = "../logger", version = "1.3.8" }
solana-runtime = { path = "../runtime", version = "1.3.8" }
solana-measure = { path = "../measure", version = "1.3.8" }
solana-sdk = { path = "../sdk", version = "1.3.8" }
solana-version = { path = "../version", version = "1.3.8" }
solana-core = { path = "../core", version = "1.3.15" }
solana-clap-utils = { path = "../clap-utils", version = "1.3.15" }
solana-streamer = { path = "../streamer", version = "1.3.15" }
solana-perf = { path = "../perf", version = "1.3.15" }
solana-ledger = { path = "../ledger", version = "1.3.15" }
solana-logger = { path = "../logger", version = "1.3.15" }
solana-runtime = { path = "../runtime", version = "1.3.15" }
solana-measure = { path = "../measure", version = "1.3.15" }
solana-sdk = { path = "../sdk", version = "1.3.15" }
solana-version = { path = "../version", version = "1.3.15" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-banks-client"
version = "1.3.8"
version = "1.3.15"
description = "Solana banks client"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -12,15 +12,15 @@ edition = "2018"
async-trait = "0.1.36"
bincode = "1.3.1"
futures = "0.3"
solana-banks-interface = { path = "../banks-interface", version = "1.3.8" }
solana-sdk = { path = "../sdk", version = "1.3.8" }
solana-banks-interface = { path = "../banks-interface", version = "1.3.15" }
solana-sdk = { path = "../sdk", version = "1.3.15" }
tarpc = { version = "0.21.0", features = ["full"] }
tokio = "0.2"
tokio-serde = { version = "0.6", features = ["bincode"] }
[dev-dependencies]
solana-runtime = { path = "../runtime", version = "1.3.8" }
solana-banks-server = { path = "../banks-server", version = "1.3.8" }
solana-runtime = { path = "../runtime", version = "1.3.15" }
solana-banks-server = { path = "../banks-server", version = "1.3.15" }
[lib]
crate-type = ["lib"]

View File

@@ -71,6 +71,14 @@ pub trait BanksClientExt {
/// are said to be finalized. The cluster will not fork to a higher slot height.
async fn get_root_slot(&mut self) -> io::Result<Slot>;
/// Return the account at the given address at the slot corresponding to the given
/// commitment level. If the account is not found, None is returned.
async fn get_account_with_commitment(
&mut self,
address: Pubkey,
commitment: CommitmentLevel,
) -> io::Result<Option<Account>>;
/// Return the account at the given address at the time of the most recent root slot.
/// If the account is not found, None is returned.
async fn get_account(&mut self, address: Pubkey) -> io::Result<Option<Account>>;
@@ -130,13 +138,18 @@ impl BanksClientExt for BanksClient {
.await
}
async fn get_account_with_commitment(
&mut self,
address: Pubkey,
commitment: CommitmentLevel,
) -> io::Result<Option<Account>> {
self.get_account_with_commitment_and_context(context::current(), address, commitment)
.await
}
async fn get_account(&mut self, address: Pubkey) -> io::Result<Option<Account>> {
self.get_account_with_commitment_and_context(
context::current(),
address,
CommitmentLevel::default(),
)
.await
self.get_account_with_commitment(address, CommitmentLevel::default())
.await
}
async fn get_balance_with_commitment(

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-banks-interface"
version = "1.3.8"
version = "1.3.15"
description = "Solana banks RPC interface"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -10,7 +10,7 @@ edition = "2018"
[dependencies]
serde = { version = "1.0.112", features = ["derive"] }
solana-sdk = { path = "../sdk", version = "1.3.8" }
solana-sdk = { path = "../sdk", version = "1.3.15" }
tarpc = { version = "0.21.0", features = ["full"] }
[lib]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-banks-server"
version = "1.3.8"
version = "1.3.15"
description = "Solana banks server"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -11,9 +11,11 @@ edition = "2018"
[dependencies]
bincode = "1.3.1"
futures = "0.3"
solana-banks-interface = { path = "../banks-interface", version = "1.3.8" }
solana-runtime = { path = "../runtime", version = "1.3.8" }
solana-sdk = { path = "../sdk", version = "1.3.8" }
log = "0.4.8"
solana-banks-interface = { path = "../banks-interface", version = "1.3.15" }
solana-runtime = { path = "../runtime", version = "1.3.15" }
solana-sdk = { path = "../sdk", version = "1.3.15" }
solana-metrics = { path = "../metrics", version = "1.3.15" }
tarpc = { version = "0.21.0", features = ["full"] }
tokio = "0.2"
tokio-serde = { version = "0.6", features = ["bincode"] }

View File

@@ -1,3 +1,4 @@
use crate::send_transaction_service::{SendTransactionService, TransactionInfo};
use bincode::{deserialize, serialize};
use futures::{
future,
@@ -8,7 +9,6 @@ use solana_runtime::{
bank::Bank,
bank_forks::BankForks,
commitment::{BlockCommitmentCache, CommitmentSlots},
send_transaction_service::{SendTransactionService, TransactionInfo},
};
use solana_sdk::{
account::Account,
@@ -23,9 +23,8 @@ use solana_sdk::{
use std::{
collections::HashMap,
io,
net::SocketAddr,
net::{Ipv4Addr, SocketAddr},
sync::{
atomic::AtomicBool,
mpsc::{channel, Receiver, Sender},
Arc, RwLock,
},
@@ -245,19 +244,18 @@ pub async fn start_tcp_server(
.filter_map(|r| future::ready(r.ok()))
.map(server::BaseChannel::with_defaults)
// Limit channels to 1 per IP.
.max_channels_per_key(1, |t| t.as_ref().peer_addr().unwrap().ip())
.max_channels_per_key(1, |t| {
t.as_ref()
.peer_addr()
.map(|x| x.ip())
.unwrap_or_else(|_| Ipv4Addr::new(0, 0, 0, 0).into())
})
// serve is generated by the service attribute. It takes as input any type implementing
// the generated Banks trait.
.map(move |chan| {
let (sender, receiver) = channel();
let exit_send_transaction_service = Arc::new(AtomicBool::new(false));
SendTransactionService::new(
tpu_addr,
&bank_forks,
&exit_send_transaction_service,
receiver,
);
SendTransactionService::new(tpu_addr, &bank_forks, receiver);
let server =
BanksServer::new(bank_forks.clone(), block_commitment_cache.clone(), sender);

View File

@@ -1,2 +1,6 @@
pub mod banks_server;
pub mod rpc_banks_service;
pub mod send_transaction_service;
#[macro_use]
extern crate solana_metrics;

View File

@@ -1,13 +1,13 @@
use crate::{bank::Bank, bank_forks::BankForks};
// TODO: Merge this implementation with the one at `core/src/send_transaction_service.rs`
use log::*;
use solana_metrics::{datapoint_warn, inc_new_counter_info};
use solana_runtime::{bank::Bank, bank_forks::BankForks};
use solana_sdk::{clock::Slot, signature::Signature};
use std::{
collections::HashMap,
net::{SocketAddr, UdpSocket},
sync::{
atomic::{AtomicBool, Ordering},
mpsc::Receiver,
mpsc::{Receiver, RecvTimeoutError},
Arc, RwLock,
},
thread::{self, Builder, JoinHandle},
@@ -50,10 +50,9 @@ impl SendTransactionService {
pub fn new(
tpu_address: SocketAddr,
bank_forks: &Arc<RwLock<BankForks>>,
exit: &Arc<AtomicBool>,
receiver: Receiver<TransactionInfo>,
) -> Self {
let thread = Self::retry_thread(receiver, bank_forks.clone(), tpu_address, exit.clone());
let thread = Self::retry_thread(receiver, bank_forks.clone(), tpu_address);
Self { thread }
}
@@ -61,7 +60,6 @@ impl SendTransactionService {
receiver: Receiver<TransactionInfo>,
bank_forks: Arc<RwLock<BankForks>>,
tpu_address: SocketAddr,
exit: Arc<AtomicBool>,
) -> JoinHandle<()> {
let mut last_status_check = Instant::now();
let mut transactions = HashMap::new();
@@ -70,20 +68,20 @@ impl SendTransactionService {
Builder::new()
.name("send-tx-svc".to_string())
.spawn(move || loop {
if exit.load(Ordering::Relaxed) {
break;
}
if let Ok(transaction_info) = receiver.recv_timeout(Duration::from_secs(1)) {
Self::send_transaction(
&send_socket,
&tpu_address,
&transaction_info.wire_transaction,
);
if transactions.len() < MAX_TRANSACTION_QUEUE_SIZE {
transactions.insert(transaction_info.signature, transaction_info);
} else {
datapoint_warn!("send_transaction_service-queue-overflow");
match receiver.recv_timeout(Duration::from_secs(1)) {
Err(RecvTimeoutError::Disconnected) => break,
Err(RecvTimeoutError::Timeout) => {}
Ok(transaction_info) => {
Self::send_transaction(
&send_socket,
&tpu_address,
&transaction_info.wire_transaction,
);
if transactions.len() < MAX_TRANSACTION_QUEUE_SIZE {
transactions.insert(transaction_info.signature, transaction_info);
} else {
datapoint_warn!("send_transaction_service-queue-overflow");
}
}
}
@@ -193,20 +191,17 @@ mod test {
let tpu_address = "127.0.0.1:0".parse().unwrap();
let bank = Bank::default();
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
let exit = Arc::new(AtomicBool::new(false));
let (_sender, receiver) = channel();
let (sender, receiver) = channel();
let send_tranaction_service =
SendTransactionService::new(tpu_address, &bank_forks, &exit, receiver);
SendTransactionService::new(tpu_address, &bank_forks, receiver);
exit.store(true, Ordering::Relaxed);
drop(sender);
send_tranaction_service.join().unwrap();
}
#[test]
fn process_transactions() {
solana_logger::setup();
let (genesis_config, mint_keypair) = create_genesis_config(4);
let bank = Bank::new(&genesis_config);
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-bench-exchange"
version = "1.3.8"
version = "1.3.15"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -18,21 +18,21 @@ rand = "0.7.0"
rayon = "1.4.0"
serde_json = "1.0.56"
serde_yaml = "0.8.13"
solana-clap-utils = { path = "../clap-utils", version = "1.3.8" }
solana-core = { path = "../core", version = "1.3.8" }
solana-genesis = { path = "../genesis", version = "1.3.8" }
solana-client = { path = "../client", version = "1.3.8" }
solana-faucet = { path = "../faucet", version = "1.3.8" }
solana-exchange-program = { path = "../programs/exchange", version = "1.3.8" }
solana-logger = { path = "../logger", version = "1.3.8" }
solana-metrics = { path = "../metrics", version = "1.3.8" }
solana-net-utils = { path = "../net-utils", version = "1.3.8" }
solana-runtime = { path = "../runtime", version = "1.3.8" }
solana-sdk = { path = "../sdk", version = "1.3.8" }
solana-version = { path = "../version", version = "1.3.8" }
solana-clap-utils = { path = "../clap-utils", version = "1.3.15" }
solana-core = { path = "../core", version = "1.3.15" }
solana-genesis = { path = "../genesis", version = "1.3.15" }
solana-client = { path = "../client", version = "1.3.15" }
solana-faucet = { path = "../faucet", version = "1.3.15" }
solana-exchange-program = { path = "../programs/exchange", version = "1.3.15" }
solana-logger = { path = "../logger", version = "1.3.15" }
solana-metrics = { path = "../metrics", version = "1.3.15" }
solana-net-utils = { path = "../net-utils", version = "1.3.15" }
solana-runtime = { path = "../runtime", version = "1.3.15" }
solana-sdk = { path = "../sdk", version = "1.3.15" }
solana-version = { path = "../version", version = "1.3.15" }
[dev-dependencies]
solana-local-cluster = { path = "../local-cluster", version = "1.3.8" }
solana-local-cluster = { path = "../local-cluster", version = "1.3.15" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -2,18 +2,19 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-bench-streamer"
version = "1.3.8"
version = "1.3.15"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
publish = false
[dependencies]
clap = "2.33.1"
solana-clap-utils = { path = "../clap-utils", version = "1.3.8" }
solana-streamer = { path = "../streamer", version = "1.3.8" }
solana-logger = { path = "../logger", version = "1.3.8" }
solana-net-utils = { path = "../net-utils", version = "1.3.8" }
solana-version = { path = "../version", version = "1.3.8" }
solana-clap-utils = { path = "../clap-utils", version = "1.3.15" }
solana-streamer = { path = "../streamer", version = "1.3.15" }
solana-logger = { path = "../logger", version = "1.3.15" }
solana-net-utils = { path = "../net-utils", version = "1.3.15" }
solana-version = { path = "../version", version = "1.3.15" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -27,7 +27,7 @@ fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
let mut num = 0;
for p in &msgs.packets {
let a = p.meta.addr();
assert!(p.meta.size < PACKET_DATA_SIZE);
assert!(p.meta.size <= PACKET_DATA_SIZE);
send.send_to(&p.data[..p.meta.size], &a).unwrap();
num += 1;
}

View File

@@ -2,10 +2,11 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-bench-tps"
version = "1.3.8"
version = "1.3.15"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
publish = false
[dependencies]
bincode = "1.3.1"
@@ -14,23 +15,23 @@ log = "0.4.8"
rayon = "1.4.0"
serde_json = "1.0.56"
serde_yaml = "0.8.13"
solana-clap-utils = { path = "../clap-utils", version = "1.3.8" }
solana-core = { path = "../core", version = "1.3.8" }
solana-genesis = { path = "../genesis", version = "1.3.8" }
solana-client = { path = "../client", version = "1.3.8" }
solana-faucet = { path = "../faucet", version = "1.3.8" }
solana-logger = { path = "../logger", version = "1.3.8" }
solana-metrics = { path = "../metrics", version = "1.3.8" }
solana-measure = { path = "../measure", version = "1.3.8" }
solana-net-utils = { path = "../net-utils", version = "1.3.8" }
solana-runtime = { path = "../runtime", version = "1.3.8" }
solana-sdk = { path = "../sdk", version = "1.3.8" }
solana-version = { path = "../version", version = "1.3.8" }
solana-clap-utils = { path = "../clap-utils", version = "1.3.15" }
solana-core = { path = "../core", version = "1.3.15" }
solana-genesis = { path = "../genesis", version = "1.3.15" }
solana-client = { path = "../client", version = "1.3.15" }
solana-faucet = { path = "../faucet", version = "1.3.15" }
solana-logger = { path = "../logger", version = "1.3.15" }
solana-metrics = { path = "../metrics", version = "1.3.15" }
solana-measure = { path = "../measure", version = "1.3.15" }
solana-net-utils = { path = "../net-utils", version = "1.3.15" }
solana-runtime = { path = "../runtime", version = "1.3.15" }
solana-sdk = { path = "../sdk", version = "1.3.15" }
solana-version = { path = "../version", version = "1.3.15" }
[dev-dependencies]
serial_test = "0.4.0"
serial_test_derive = "0.4.0"
solana-local-cluster = { path = "../local-cluster", version = "1.3.8" }
solana-local-cluster = { path = "../local-cluster", version = "1.3.15" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -26,6 +26,7 @@ declare print_free_tree=(
':runtime/src/**.rs'
':sdk/bpf/rust/rust-utils/**.rs'
':sdk/**.rs'
':^sdk/src/program_option.rs'
':programs/**.rs'
':^**bin**.rs'
':^**bench**.rs'

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2.7
#!/usr/bin/env python3
#
# This script figures the order in which workspace crates must be published to
# crates.io. Along the way it also ensures there are no circular dependencies
@@ -42,24 +42,26 @@ def get_packages():
sys.exit(1)
# Order dependencies
deleted_dependencies = []
sorted_dependency_graph = []
max_iterations = pow(len(dependency_graph),2)
while dependency_graph:
while len(deleted_dependencies) < len(dependency_graph):
if max_iterations == 0:
# One day be more helpful and find the actual cycle for the user...
sys.exit('Error: Circular dependency suspected between these packages: \n {}\n'.format('\n '.join(dependency_graph.keys())))
max_iterations -= 1
for package, dependencies in dependency_graph.items():
for dependency in dependencies:
if dependency in dependency_graph:
break
else:
del dependency_graph[package]
deleted_dependencies.append(package)
sorted_dependency_graph.append((package, manifest_path[package]))
return sorted_dependency_graph
for package, manifest in get_packages():
print os.path.relpath(manifest)
print(os.path.relpath(manifest))

View File

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

View File

@@ -15,7 +15,7 @@ pub fn commitment_arg_with_default<'a, 'b>(default_value: &'static str) -> Arg<'
Arg::with_name(COMMITMENT_ARG.name)
.long(COMMITMENT_ARG.long)
.takes_value(true)
.possible_values(&["recent", "single", "root", "max"])
.possible_values(&["recent", "single", "singleGossip", "root", "max"])
.default_value(default_value)
.value_name("COMMITMENT_LEVEL")
.help(COMMITMENT_ARG.help)

View File

@@ -0,0 +1,19 @@
use crate::{input_validators, ArgConstant};
use clap::Arg;
pub const FEE_PAYER_ARG: ArgConstant<'static> = ArgConstant {
name: "fee_payer",
long: "fee-payer",
help: "Specify the fee-payer account. This may be a keypair file, the ASK keyword \n\
or the pubkey of an offline signer, provided an appropriate --signer argument \n\
is also passed. Defaults to the client keypair.",
};
pub fn fee_payer_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(FEE_PAYER_ARG.name)
.long(FEE_PAYER_ARG.long)
.takes_value(true)
.value_name("KEYPAIR")
.validator(input_validators::is_valid_signer)
.help(FEE_PAYER_ARG.help)
}

View File

@@ -8,6 +8,7 @@ use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
clock::UnixTimestamp,
commitment_config::CommitmentConfig,
genesis_config::ClusterType,
native_token::sol_to_lamports,
pubkey::Pubkey,
signature::{read_keypair_file, Keypair, Signature, Signer},
@@ -178,6 +179,10 @@ pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> {
value_of(matches, name).map(sol_to_lamports)
}
pub fn cluster_type_of(matches: &ArgMatches<'_>, name: &str) -> Option<ClusterType> {
value_of(matches, name)
}
pub fn commitment_of(matches: &ArgMatches<'_>, name: &str) -> Option<CommitmentConfig> {
matches.value_of(name).map(|value| match value {
"max" => CommitmentConfig::max(),

View File

@@ -11,6 +11,7 @@ use solana_remote_wallet::{
remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager},
};
use solana_sdk::{
hash::Hash,
pubkey::Pubkey,
signature::{
keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair,
@@ -25,6 +26,81 @@ use std::{
sync::Arc,
};
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 type CliSigners = Vec<Box<dyn Signer>>;
pub type SignerIndex = usize;
pub struct CliSignerInfo {
pub signers: CliSigners,
}
impl CliSignerInfo {
pub fn index_of(&self, pubkey: Option<Pubkey>) -> Option<usize> {
if let Some(pubkey) = pubkey {
self.signers
.iter()
.position(|signer| signer.pubkey() == pubkey)
} else {
Some(0)
}
}
}
pub struct DefaultSigner {
pub arg_name: String,
pub path: String,
}
impl DefaultSigner {
pub fn generate_unique_signers(
&self,
bulk_signers: Vec<Option<Box<dyn Signer>>>,
matches: &ArgMatches<'_>,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliSignerInfo, Box<dyn error::Error>> {
let mut unique_signers = vec![];
// Determine if the default signer is needed
if bulk_signers.iter().any(|signer| signer.is_none()) {
let default_signer = self.signer_from_path(matches, wallet_manager)?;
unique_signers.push(default_signer);
}
for signer in bulk_signers.into_iter() {
if let Some(signer) = signer {
if !unique_signers.iter().any(|s| s == &signer) {
unique_signers.push(signer);
}
}
}
Ok(CliSignerInfo {
signers: unique_signers,
})
}
pub fn signer_from_path(
&self,
matches: &ArgMatches,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<Box<dyn Signer>, Box<dyn std::error::Error>> {
signer_from_path(matches, &self.path, &self.arg_name, wallet_manager)
}
}
pub enum KeypairUrl {
Ask,
Filepath(String),
@@ -78,7 +154,7 @@ pub fn signer_from_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),
format!("could not read keypair file \"{}\". Run \"solana-keygen new\" to create a keypair file: {}", path, e),
)
.into()),
Ok(file) => Ok(Box::new(file)),
@@ -149,7 +225,7 @@ pub fn resolve_signer_from_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),
format!("could not read keypair file \"{}\". Run \"solana-keygen new\" to create a keypair file: {}", path, e),
)
.into()),
Ok(_) => Ok(Some(path.to_string())),

View File

@@ -24,7 +24,9 @@ impl std::fmt::Debug for DisplayError {
}
pub mod commitment;
pub mod fee_payer;
pub mod input_parsers;
pub mod input_validators;
pub mod keypair;
pub mod nonce;
pub mod offline;

50
clap-utils/src/nonce.rs Normal file
View File

@@ -0,0 +1,50 @@
use crate::{input_validators::*, offline::BLOCKHASH_ARG, ArgConstant};
use clap::{App, Arg};
pub const NONCE_ARG: ArgConstant<'static> = ArgConstant {
name: "nonce",
long: "nonce",
help: "Provide the nonce account to use when creating a nonced \n\
transaction. Nonced transactions are useful when a transaction \n\
requires a lengthy signing process. Learn more about nonced \n\
transactions at https://docs.solana.com/offline-signing/durable-nonce",
};
pub const NONCE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
name: "nonce_authority",
long: "nonce-authority",
help: "Provide the nonce authority keypair to use when signing a nonced transaction",
};
fn nonce_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(NONCE_ARG.name)
.long(NONCE_ARG.long)
.takes_value(true)
.value_name("PUBKEY")
.requires(BLOCKHASH_ARG.name)
.validator(is_valid_pubkey)
.help(NONCE_ARG.help)
}
pub fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(NONCE_AUTHORITY_ARG.name)
.long(NONCE_AUTHORITY_ARG.long)
.takes_value(true)
.value_name("KEYPAIR")
.validator(is_valid_signer)
.help(NONCE_AUTHORITY_ARG.help)
}
pub trait NonceArgs {
fn nonce_args(self, global: bool) -> Self;
}
impl NonceArgs for App<'_, '_> {
fn nonce_args(self, global: bool) -> Self {
self.arg(nonce_arg().global(global)).arg(
nonce_authority_arg()
.requires(NONCE_ARG.name)
.global(global),
)
}
}

View File

@@ -1,4 +1,5 @@
use crate::ArgConstant;
use crate::{input_validators::*, ArgConstant};
use clap::{App, Arg};
pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant {
name: "blockhash",
@@ -17,3 +18,43 @@ pub const SIGNER_ARG: ArgConstant<'static> = ArgConstant {
long: "signer",
help: "Provide a public-key/signature pair for the transaction",
};
pub 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)
}
pub 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, global: bool) -> Self;
}
impl OfflineArgs for App<'_, '_> {
fn offline_args(self, global: bool) -> Self {
self.arg(blockhash_arg().global(global))
.arg(sign_only_arg().global(global))
.arg(signer_arg().global(global))
}
}

View File

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

View File

@@ -82,8 +82,8 @@ impl Config {
return "".to_string();
}
let mut url = json_rpc_url.unwrap();
let port = url.port_or_known_default().unwrap_or(80);
url.set_port(Some(port + 2)).expect("unable to set port");
let port = url.port().unwrap_or(8899);
url.set_port(Some(port + 3)).expect("unable to set port");
url.to_string()
}
@@ -138,21 +138,21 @@ mod test {
fn compute_rpc_banks_url() {
assert_eq!(
Config::compute_rpc_banks_url(&"http://devnet.solana.com"),
"http://devnet.solana.com:82/".to_string()
"http://devnet.solana.com:8902/".to_string()
);
assert_eq!(
Config::compute_rpc_banks_url(&"https://devnet.solana.com"),
"https://devnet.solana.com:445/".to_string()
"https://devnet.solana.com:8902/".to_string()
);
assert_eq!(
Config::compute_rpc_banks_url(&"http://example.com:8899"),
"http://example.com:8901/".to_string()
"http://example.com:8902/".to_string()
);
assert_eq!(
Config::compute_rpc_banks_url(&"https://example.com:1234"),
"https://example.com:1236/".to_string()
"https://example.com:1237/".to_string()
);
assert_eq!(Config::compute_rpc_banks_url(&"garbage"), String::new());

29
cli-output/Cargo.toml Normal file
View File

@@ -0,0 +1,29 @@
[package]
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-cli-output"
description = "Blockchain, Rebuilt for Scale"
version = "1.3.15"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
chrono = { version = "0.4.11", features = ["serde"] }
console = "0.11.3"
humantime = "2.0.1"
Inflector = "0.11.4"
indicatif = "0.15.0"
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.56"
solana-account-decoder = { path = "../account-decoder", version = "1.3.15" }
solana-clap-utils = { path = "../clap-utils", version = "1.3.15" }
solana-client = { path = "../client", version = "1.3.15" }
solana-sdk = { path = "../sdk", version = "1.3.15" }
solana-stake-program = { path = "../programs/stake", version = "1.3.15" }
solana-transaction-status = { path = "../transaction-status", version = "1.3.15" }
solana-vote-program = { path = "../programs/vote", version = "1.3.15" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,20 +1,26 @@
use crate::{
cli::build_balance_message,
display::{format_labeled_address, writeln_name_value},
display::{build_balance_message, format_labeled_address, writeln_name_value},
QuietDisplay, VerboseDisplay,
};
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
use console::{style, Emoji};
use inflector::cases::titlecase::to_title_case;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use solana_account_decoder::parse_token::UiTokenAccount;
use solana_clap_utils::keypair::SignOnly;
use solana_client::rpc_response::{
RpcAccountBalance, RpcKeyedAccount, RpcSupply, RpcVoteAccountInfo,
};
use solana_sdk::{
clock::{self, Epoch, Slot, UnixTimestamp},
epoch_info::EpochInfo,
hash::Hash,
native_token::lamports_to_sol,
pubkey::Pubkey,
signature::Signature,
stake_history::StakeHistoryEntry,
transaction::Transaction,
};
use solana_stake_program::stake_state::{Authorized, Lockup};
use solana_vote_program::{
@@ -24,6 +30,7 @@ use solana_vote_program::{
use std::{
collections::{BTreeMap, HashMap},
fmt,
str::FromStr,
time::Duration,
};
@@ -34,15 +41,27 @@ pub enum OutputFormat {
Display,
Json,
JsonCompact,
DisplayQuiet,
DisplayVerbose,
}
impl OutputFormat {
pub fn formatted_string<T>(&self, item: &T) -> String
where
T: Serialize + fmt::Display,
T: Serialize + fmt::Display + QuietDisplay + VerboseDisplay,
{
match self {
OutputFormat::Display => format!("{}", item),
OutputFormat::DisplayQuiet => {
let mut s = String::new();
QuietDisplay::write_str(item, &mut s).unwrap();
s
}
OutputFormat::DisplayVerbose => {
let mut s = String::new();
VerboseDisplay::write_str(item, &mut s).unwrap();
s
}
OutputFormat::Json => serde_json::to_string_pretty(item).unwrap(),
OutputFormat::JsonCompact => serde_json::to_value(item).unwrap().to_string(),
}
@@ -57,6 +76,9 @@ pub struct CliAccount {
pub use_lamports_unit: bool,
}
impl QuietDisplay for CliAccount {}
impl VerboseDisplay for CliAccount {}
impl fmt::Display for CliAccount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?;
@@ -99,6 +121,9 @@ pub struct CliBlockProduction {
pub verbose: bool,
}
impl QuietDisplay for CliBlockProduction {}
impl VerboseDisplay for CliBlockProduction {}
impl fmt::Display for CliBlockProduction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?;
@@ -203,6 +228,9 @@ impl From<EpochInfo> for CliEpochInfo {
}
}
impl QuietDisplay for CliEpochInfo {}
impl VerboseDisplay for CliEpochInfo {}
impl fmt::Display for CliEpochInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?;
@@ -281,6 +309,9 @@ pub struct CliValidators {
pub use_lamports_unit: bool,
}
impl QuietDisplay for CliValidators {}
impl VerboseDisplay for CliValidators {}
impl fmt::Display for CliValidators {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn write_vote_account(
@@ -300,7 +331,7 @@ impl fmt::Display for CliValidators {
writeln!(
f,
"{} {:<44} {:<44} {:>3}% {:>8} {:>10} {:>10} {:>17} {}",
"{} {:<44} {:<44} {:>3}% {:>8} {:>10} {:>10} {:>8} {}",
if delinquent {
WARNING.to_string()
} else {
@@ -359,7 +390,7 @@ impl fmt::Display for CliValidators {
for (version, info) in self.stake_by_version.iter() {
writeln!(
f,
"{:<16} - {:3} current validators ({:>5.2}%){}",
"{:<8} - {:3} current validators ({:>5.2}%){}",
version,
info.current_validators,
100. * info.current_active_stake as f64 / self.total_active_stake as f64,
@@ -380,7 +411,7 @@ impl fmt::Display for CliValidators {
f,
"{}",
style(format!(
" {:<44} {:<38} {} {} {} {:>10} {:^17} {}",
" {:<44} {:<38} {} {} {} {:>10} {:^8} {}",
"Identity Pubkey",
"Vote Account Pubkey",
"Commission",
@@ -469,6 +500,9 @@ pub struct CliNonceAccount {
pub use_lamports_unit: bool,
}
impl QuietDisplay for CliNonceAccount {}
impl VerboseDisplay for CliNonceAccount {}
impl fmt::Display for CliNonceAccount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
@@ -506,6 +540,9 @@ impl CliStakeVec {
}
}
impl QuietDisplay for CliStakeVec {}
impl VerboseDisplay for CliStakeVec {}
impl fmt::Display for CliStakeVec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for state in &self.0 {
@@ -524,6 +561,9 @@ pub struct CliKeyedStakeState {
pub stake_state: CliStakeState,
}
impl QuietDisplay for CliKeyedStakeState {}
impl VerboseDisplay for CliKeyedStakeState {}
impl fmt::Display for CliKeyedStakeState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "Stake Pubkey: {}", self.stake_pubkey)?;
@@ -562,6 +602,9 @@ pub struct CliStakeState {
pub deactivating_stake: Option<u64>,
}
impl QuietDisplay for CliStakeState {}
impl VerboseDisplay for CliStakeState {}
impl fmt::Display for CliStakeState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn show_authorized(f: &mut fmt::Formatter, authorized: &CliAuthorized) -> fmt::Result {
@@ -738,6 +781,9 @@ pub struct CliStakeHistory {
pub use_lamports_unit: bool,
}
impl QuietDisplay for CliStakeHistory {}
impl VerboseDisplay for CliStakeHistory {}
impl fmt::Display for CliStakeHistory {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?;
@@ -832,6 +878,9 @@ impl CliValidatorInfoVec {
}
}
impl QuietDisplay for CliValidatorInfoVec {}
impl VerboseDisplay for CliValidatorInfoVec {}
impl fmt::Display for CliValidatorInfoVec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.0.is_empty() {
@@ -853,6 +902,9 @@ pub struct CliValidatorInfo {
pub info: Map<String, Value>,
}
impl QuietDisplay for CliValidatorInfo {}
impl VerboseDisplay for CliValidatorInfo {}
impl fmt::Display for CliValidatorInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln_name_value(f, "Validator Identity Pubkey:", &self.identity_pubkey)?;
@@ -886,6 +938,9 @@ pub struct CliVoteAccount {
pub use_lamports_unit: bool,
}
impl QuietDisplay for CliVoteAccount {}
impl VerboseDisplay for CliVoteAccount {}
impl fmt::Display for CliVoteAccount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
@@ -935,6 +990,9 @@ pub struct CliAuthorizedVoters {
authorized_voters: BTreeMap<Epoch, String>,
}
impl QuietDisplay for CliAuthorizedVoters {}
impl VerboseDisplay for CliAuthorizedVoters {}
impl fmt::Display for CliAuthorizedVoters {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.authorized_voters)
@@ -984,6 +1042,9 @@ pub struct CliBlockTime {
pub timestamp: UnixTimestamp,
}
impl QuietDisplay for CliBlockTime {}
impl VerboseDisplay for CliBlockTime {}
impl fmt::Display for CliBlockTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln_name_value(f, "Block:", &self.slot.to_string())?;
@@ -1012,6 +1073,9 @@ pub struct CliSignOnlyData {
pub bad_sig: Vec<String>,
}
impl QuietDisplay for CliSignOnlyData {}
impl VerboseDisplay for CliSignOnlyData {}
impl fmt::Display for CliSignOnlyData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?;
@@ -1044,6 +1108,9 @@ pub struct CliSignature {
pub signature: String,
}
impl QuietDisplay for CliSignature {}
impl VerboseDisplay for CliSignature {}
impl fmt::Display for CliSignature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?;
@@ -1058,6 +1125,9 @@ pub struct CliAccountBalances {
pub accounts: Vec<RpcAccountBalance>,
}
impl QuietDisplay for CliAccountBalances {}
impl VerboseDisplay for CliAccountBalances {}
impl fmt::Display for CliAccountBalances {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
@@ -1100,6 +1170,9 @@ impl From<RpcSupply> for CliSupply {
}
}
impl QuietDisplay for CliSupply {}
impl VerboseDisplay for CliSupply {}
impl fmt::Display for CliSupply {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln_name_value(f, "Total:", &format!("{} SOL", lamports_to_sol(self.total)))?;
@@ -1133,6 +1206,9 @@ pub struct CliFees {
pub last_valid_slot: Slot,
}
impl QuietDisplay for CliFees {}
impl VerboseDisplay for CliFees {}
impl fmt::Display for CliFees {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln_name_value(f, "Blockhash:", &self.blockhash)?;
@@ -1145,3 +1221,240 @@ impl fmt::Display for CliFees {
Ok(())
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliTokenAccount {
pub address: String,
#[serde(flatten)]
pub token_account: UiTokenAccount,
}
impl QuietDisplay for CliTokenAccount {}
impl VerboseDisplay for CliTokenAccount {}
impl fmt::Display for CliTokenAccount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f)?;
writeln_name_value(f, "Address:", &self.address)?;
let account = &self.token_account;
writeln_name_value(
f,
"Balance:",
&account.token_amount.real_number_string_trimmed(),
)?;
let mint = format!(
"{}{}",
account.mint,
if account.is_native { " (native)" } else { "" }
);
writeln_name_value(f, "Mint:", &mint)?;
writeln_name_value(f, "Owner:", &account.owner)?;
writeln_name_value(f, "State:", &format!("{:?}", account.state))?;
if let Some(delegate) = &account.delegate {
writeln!(f, "Delegation:")?;
writeln_name_value(f, " Delegate:", delegate)?;
let allowance = account.delegated_amount.as_ref().unwrap();
writeln_name_value(f, " Allowance:", &allowance.real_number_string_trimmed())?;
}
writeln_name_value(
f,
"Close authority:",
&account.close_authority.as_ref().unwrap_or(&String::new()),
)?;
Ok(())
}
}
pub fn return_signers(
tx: &Transaction,
output_format: &OutputFormat,
) -> Result<String, Box<dyn std::error::Error>> {
let verify_results = tx.verify_with_results();
let mut signers = Vec::new();
let mut absent = Vec::new();
let mut bad_sig = Vec::new();
tx.signatures
.iter()
.zip(tx.message.account_keys.iter())
.zip(verify_results.into_iter())
.for_each(|((sig, key), res)| {
if res {
signers.push(format!("{}={}", key, sig))
} else if *sig == Signature::default() {
absent.push(key.to_string());
} else {
bad_sig.push(key.to_string());
}
});
let cli_command = CliSignOnlyData {
blockhash: tx.message.recent_blockhash.to_string(),
signers,
absent,
bad_sig,
};
Ok(output_format.formatted_string(&cli_command))
}
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 mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
let signer_strings = object.get("signers");
if let Some(sig_strings) = signer_strings {
present_signers = sig_strings
.as_array()
.unwrap()
.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 mut absent_signers: Vec<Pubkey> = Vec::new();
let signer_strings = object.get("absent");
if let Some(sig_strings) = signer_strings {
absent_signers = sig_strings
.as_array()
.unwrap()
.iter()
.map(|val| {
let s = val.as_str().unwrap();
Pubkey::from_str(s).unwrap()
})
.collect();
}
let mut bad_signers: Vec<Pubkey> = Vec::new();
let signer_strings = object.get("badSig");
if let Some(sig_strings) = signer_strings {
bad_signers = sig_strings
.as_array()
.unwrap()
.iter()
.map(|val| {
let s = val.as_str().unwrap();
Pubkey::from_str(s).unwrap()
})
.collect();
}
SignOnly {
blockhash,
present_signers,
absent_signers,
bad_signers,
}
}
#[cfg(test)]
mod tests {
use super::*;
use solana_sdk::{
message::Message,
pubkey::Pubkey,
signature::{keypair_from_seed, NullSigner, Signature, Signer, SignerError},
system_instruction,
transaction::Transaction,
};
#[test]
fn test_return_signers() {
struct BadSigner {
pubkey: Pubkey,
}
impl BadSigner {
pub fn new(pubkey: Pubkey) -> Self {
Self { pubkey }
}
}
impl Signer for BadSigner {
fn try_pubkey(&self) -> Result<Pubkey, SignerError> {
Ok(self.pubkey)
}
fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, SignerError> {
Ok(Signature::new(&[1u8; 64]))
}
}
let present: Box<dyn Signer> = Box::new(keypair_from_seed(&[2u8; 32]).unwrap());
let absent: Box<dyn Signer> = Box::new(NullSigner::new(&Pubkey::new(&[3u8; 32])));
let bad: Box<dyn Signer> = Box::new(BadSigner::new(Pubkey::new(&[4u8; 32])));
let to = Pubkey::new(&[5u8; 32]);
let nonce = Pubkey::new(&[6u8; 32]);
let from = present.pubkey();
let fee_payer = absent.pubkey();
let nonce_auth = bad.pubkey();
let mut tx = Transaction::new_unsigned(Message::new_with_nonce(
vec![system_instruction::transfer(&from, &to, 42)],
Some(&fee_payer),
&nonce,
&nonce_auth,
));
let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()];
let blockhash = Hash::new(&[7u8; 32]);
tx.try_partial_sign(&signers, blockhash).unwrap();
let res = return_signers(&tx, &OutputFormat::JsonCompact).unwrap();
let sign_only = parse_sign_only_reply_string(&res);
assert_eq!(sign_only.blockhash, blockhash);
assert_eq!(sign_only.present_signers[0].0, present.pubkey());
assert_eq!(sign_only.absent_signers[0], absent.pubkey());
assert_eq!(sign_only.bad_signers[0], bad.pubkey());
}
#[test]
fn test_verbose_quiet_output_formats() {
#[derive(Deserialize, Serialize)]
struct FallbackToDisplay {}
impl std::fmt::Display for FallbackToDisplay {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "display")
}
}
impl QuietDisplay for FallbackToDisplay {}
impl VerboseDisplay for FallbackToDisplay {}
let f = FallbackToDisplay {};
assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "display");
assert_eq!(
&OutputFormat::DisplayVerbose.formatted_string(&f),
"display"
);
#[derive(Deserialize, Serialize)]
struct DiscreteVerbosityDisplay {}
impl std::fmt::Display for DiscreteVerbosityDisplay {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "display")
}
}
impl QuietDisplay for DiscreteVerbosityDisplay {
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
write!(w, "quiet")
}
}
impl VerboseDisplay for DiscreteVerbosityDisplay {
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
write!(w, "verbose")
}
}
let f = DiscreteVerbosityDisplay {};
assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "quiet");
assert_eq!(
&OutputFormat::DisplayVerbose.formatted_string(&f),
"verbose"
);
}
}

View File

@@ -1,4 +1,3 @@
use crate::cli::SettingType;
use console::style;
use indicatif::{ProgressBar, ProgressStyle};
use solana_sdk::{
@@ -8,6 +7,24 @@ use solana_sdk::{
use solana_transaction_status::UiTransactionStatusMeta;
use std::{collections::HashMap, fmt, io};
pub fn build_balance_message(lamports: u64, use_lamports_unit: bool, show_unit: bool) -> String {
if use_lamports_unit {
let ess = if lamports == 1 { "" } else { "s" };
let unit = if show_unit {
format!(" lamport{}", ess)
} else {
"".to_string()
};
format!("{:?}{}", lamports, unit)
} else {
let sol = lamports_to_sol(lamports);
let sol_str = format!("{:.9}", sol);
let pretty_sol = sol_str.trim_end_matches('0').trim_end_matches('.');
let unit = if show_unit { " SOL" } else { "" };
format!("{}{}", pretty_sol, unit)
}
}
// Pretty print a "name value"
pub fn println_name_value(name: &str, value: &str) {
let styled_value = if value == "" {
@@ -40,21 +57,6 @@ pub fn format_labeled_address(pubkey: &str, address_labels: &HashMap<String, Str
}
}
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(
blockhash: &Hash,
signers: &[String],
@@ -162,7 +164,7 @@ pub fn write_transaction<W: io::Write>(
)?;
writeln!(
w,
"{} Fee: {} SOL",
"{} Fee: {}",
prefix,
lamports_to_sol(transaction_status.fee)
)?;
@@ -179,7 +181,7 @@ pub fn write_transaction<W: io::Write>(
if pre == post {
writeln!(
w,
"{} Account {} balance: {} SOL",
"{} Account {} balance: {}",
prefix,
i,
lamports_to_sol(*pre)
@@ -187,7 +189,7 @@ pub fn write_transaction<W: io::Write>(
} else {
writeln!(
w,
"{} Account {} balance: {} SOL -> {} SOL",
"{} Account {} balance: {} -> {}",
prefix,
i,
lamports_to_sol(*pre),

15
cli-output/src/lib.rs Normal file
View File

@@ -0,0 +1,15 @@
mod cli_output;
pub mod display;
pub use cli_output::*;
pub trait QuietDisplay: std::fmt::Display {
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
write!(w, "{}", self)
}
}
pub trait VerboseDisplay: std::fmt::Display {
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
write!(w, "{}", self)
}
}

View File

@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2018"
name = "solana-cli"
description = "Blockchain, Rebuilt for Scale"
version = "1.3.8"
version = "1.3.15"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -27,29 +27,30 @@ reqwest = { version = "0.10.6", default-features = false, features = ["blocking"
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.56"
solana-account-decoder = { path = "../account-decoder", version = "1.3.8" }
solana-budget-program = { path = "../programs/budget", version = "1.3.8" }
solana-clap-utils = { path = "../clap-utils", version = "1.3.8" }
solana-cli-config = { path = "../cli-config", version = "1.3.8" }
solana-client = { path = "../client", version = "1.3.8" }
solana-config-program = { path = "../programs/config", version = "1.3.8" }
solana-faucet = { path = "../faucet", version = "1.3.8" }
solana-logger = { path = "../logger", version = "1.3.8" }
solana-net-utils = { path = "../net-utils", version = "1.3.8" }
solana-remote-wallet = { path = "../remote-wallet", version = "1.3.8" }
solana-runtime = { path = "../runtime", version = "1.3.8" }
solana-sdk = { path = "../sdk", version = "1.3.8" }
solana-stake-program = { path = "../programs/stake", version = "1.3.8" }
solana-transaction-status = { path = "../transaction-status", version = "1.3.8" }
solana-version = { path = "../version", version = "1.3.8" }
solana-vote-program = { path = "../programs/vote", version = "1.3.8" }
solana-vote-signer = { path = "../vote-signer", version = "1.3.8" }
solana-account-decoder = { path = "../account-decoder", version = "1.3.15" }
solana-budget-program = { path = "../programs/budget", version = "1.3.15" }
solana-clap-utils = { path = "../clap-utils", version = "1.3.15" }
solana-cli-config = { path = "../cli-config", version = "1.3.15" }
solana-cli-output = { path = "../cli-output", version = "1.3.15" }
solana-client = { path = "../client", version = "1.3.15" }
solana-config-program = { path = "../programs/config", version = "1.3.15" }
solana-faucet = { path = "../faucet", version = "1.3.15" }
solana-logger = { path = "../logger", version = "1.3.15" }
solana-net-utils = { path = "../net-utils", version = "1.3.15" }
solana-remote-wallet = { path = "../remote-wallet", version = "1.3.15" }
solana-runtime = { path = "../runtime", version = "1.3.15" }
solana-sdk = { path = "../sdk", version = "1.3.15" }
solana-stake-program = { path = "../programs/stake", version = "1.3.15" }
solana-transaction-status = { path = "../transaction-status", version = "1.3.15" }
solana-version = { path = "../version", version = "1.3.15" }
solana-vote-program = { path = "../programs/vote", version = "1.3.15" }
solana-vote-signer = { path = "../vote-signer", version = "1.3.15" }
thiserror = "1.0.20"
url = "2.1.1"
[dev-dependencies]
solana-core = { path = "../core", version = "1.3.8" }
solana-budget-program = { path = "../programs/budget", version = "1.3.8" }
solana-core = { path = "../core", version = "1.3.15" }
solana-budget-program = { path = "../programs/budget", version = "1.3.15" }
tempfile = "3.1.0"
[[bin]]

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,29 @@
use crate::{
cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
cli_output::*,
display::{format_labeled_address, new_spinner_progress_bar, println_name_value},
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
};
use chrono::{Local, TimeZone};
use clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
use console::{style, Emoji};
use solana_clap_utils::{
commitment::commitment_arg, input_parsers::*, input_validators::*, keypair::signer_from_path,
commitment::commitment_arg, input_parsers::*, input_validators::*, keypair::DefaultSigner,
};
use solana_cli_output::{
display::{
format_labeled_address, new_spinner_progress_bar, println_name_value, println_transaction,
},
*,
};
use solana_client::{
pubsub_client::{PubsubClient, SlotInfoMessage},
client_error::ClientErrorKind,
pubsub_client::PubsubClient,
rpc_client::{GetConfirmedSignaturesForAddress2Config, RpcClient},
rpc_config::{RpcLargestAccountsConfig, RpcLargestAccountsFilter},
rpc_config::{
RpcAccountInfoConfig, RpcLargestAccountsConfig, RpcLargestAccountsFilter,
RpcProgramAccountsConfig,
},
rpc_filter,
rpc_response::SlotInfo,
};
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
@@ -32,6 +43,7 @@ use solana_sdk::{
},
transaction::Transaction,
};
use solana_transaction_status::UiTransactionEncoding;
use std::{
collections::{BTreeMap, HashMap, VecDeque},
net::SocketAddr,
@@ -53,6 +65,19 @@ pub trait ClusterQuerySubCommands {
impl ClusterQuerySubCommands for App<'_, '_> {
fn cluster_query_subcommands(self) -> Self {
self.subcommand(
SubCommand::with_name("block")
.about("Get a confirmed block")
.arg(
Arg::with_name("slot")
.long("slot")
.validator(is_slot)
.value_name("SLOT")
.takes_value(true)
.index(1)
.required(true),
),
)
.subcommand(
SubCommand::with_name("catchup")
.about("Wait for a validator to catch up to the cluster")
.arg(
@@ -87,6 +112,10 @@ impl ClusterQuerySubCommands for App<'_, '_> {
.about("Get the version of the cluster entrypoint"),
)
.subcommand(SubCommand::with_name("fees").about("Display current cluster fees"))
.subcommand(
SubCommand::with_name("first-available-block")
.about("Get the first available block in the storage"),
)
.subcommand(SubCommand::with_name("block-time")
.about("Get estimated production time of a block")
.alias("get-block-time")
@@ -282,6 +311,12 @@ impl ClusterQuerySubCommands for App<'_, '_> {
.takes_value(true)
.help("Start with the first signature older than this one"),
)
.arg(
Arg::with_name("show_transactions")
.long("show-transactions")
.takes_value(false)
.help("Display the full transactions"),
)
)
}
}
@@ -305,7 +340,7 @@ pub fn parse_catchup(
pub fn parse_cluster_ping(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let lamports = value_t_or_exit!(matches, "lamports", u64);
@@ -323,12 +358,15 @@ pub fn parse_cluster_ping(
count,
timeout,
},
signers: vec![signer_from_path(
matches,
default_signer_path,
"keypair",
wallet_manager,
)?],
signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
})
}
pub fn parse_get_block(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
let slot = value_t_or_exit!(matches, "slot", Slot);
Ok(CliCommandInfo {
command: CliCommand::GetBlock { slot },
signers: vec![],
})
}
@@ -453,6 +491,7 @@ pub fn parse_transaction_history(
None => None,
};
let limit = value_t_or_exit!(matches, "limit", usize);
let show_transactions = matches.is_present("show_transactions");
Ok(CliCommandInfo {
command: CliCommand::TransactionHistory {
@@ -460,6 +499,7 @@ pub fn parse_transaction_history(
before,
until,
limit,
show_transactions,
},
signers: vec![],
})
@@ -502,7 +542,20 @@ pub fn process_catchup(
RpcClient::new_socket(rpc_addr)
};
let reported_node_pubkey = node_client.get_identity()?;
let reported_node_pubkey = loop {
match node_client.get_identity() {
Ok(reported_node_pubkey) => break reported_node_pubkey,
Err(err) => {
if let ClientErrorKind::Reqwest(err) = err.kind() {
progress_bar.set_message(&format!("Connection failed: {}", err));
sleep(Duration::from_secs(sleep_interval as u64));
continue;
}
return Err(Box::new(err));
}
}
};
if reported_node_pubkey != *node_pubkey {
return Err(format!(
"The identity reported by node RPC URL does not match. Expected: {:?}. Reported: {:?}",
@@ -585,9 +638,14 @@ pub fn process_cluster_date(rpc_client: &RpcClient, config: &CliConfig) -> Proce
}
}
pub fn process_cluster_version(rpc_client: &RpcClient) -> ProcessResult {
pub fn process_cluster_version(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
let remote_version = rpc_client.get_version()?;
Ok(remote_version.solana_core)
if config.verbose {
Ok(format!("{:?}", remote_version))
} else {
Ok(remote_version.to_string())
}
}
pub fn process_fees(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
@@ -602,6 +660,11 @@ pub fn process_fees(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult
Ok(config.output_format.formatted_string(&fees))
}
pub fn process_first_available_block(rpc_client: &RpcClient) -> ProcessResult {
let first_available_block = rpc_client.get_first_available_block()?;
Ok(format!("{}", first_available_block))
}
pub fn process_leader_schedule(rpc_client: &RpcClient) -> ProcessResult {
let epoch_info = rpc_client.get_epoch_info()?;
let first_slot_in_epoch = epoch_info.absolute_slot - epoch_info.slot_index;
@@ -637,6 +700,59 @@ pub fn process_leader_schedule(rpc_client: &RpcClient) -> ProcessResult {
Ok("".to_string())
}
pub fn process_get_block(rpc_client: &RpcClient, _config: &CliConfig, slot: Slot) -> ProcessResult {
let mut block =
rpc_client.get_confirmed_block_with_encoding(slot, UiTransactionEncoding::Base64)?;
println!("Slot: {}", slot);
println!("Parent Slot: {}", block.parent_slot);
println!("Blockhash: {}", block.blockhash);
println!("Previous Blockhash: {}", block.previous_blockhash);
if let Some(block_time) = block.block_time {
println!("Block Time: {:?}", Local.timestamp(block_time, 0));
}
if !block.rewards.is_empty() {
block.rewards.sort_by(|a, b| a.pubkey.cmp(&b.pubkey));
println!("Rewards:",);
println!(
" {:<44} {:<15} {:<13} {:>14}",
"Address", "Amount", "New Balance", "Percent Change"
);
for reward in block.rewards {
let sign = if reward.lamports < 0 { "-" } else { "" };
println!(
" {:<44} {:>15} {}",
reward.pubkey,
format!(
"{}{:<14.4}",
sign,
lamports_to_sol(reward.lamports.abs() as u64)
),
if reward.post_balance == 0 {
" - -".to_string()
} else {
format!(
"{:<12.4} {:>13.4}%",
lamports_to_sol(reward.post_balance),
reward.lamports.abs() as f64
/ (reward.post_balance as f64 - reward.lamports as f64)
)
}
);
}
}
for (index, transaction_with_meta) in block.transactions.iter().enumerate() {
println!("Transaction {}:", index);
println_transaction(
&transaction_with_meta.transaction.decode().unwrap(),
&transaction_with_meta.meta,
" ",
);
}
Ok("".to_string())
}
pub fn process_get_block_time(
rpc_client: &RpcClient,
config: &CliConfig,
@@ -1033,7 +1149,7 @@ pub fn process_live_slots(url: &str) -> ProcessResult {
})?;
*/
let mut current: Option<SlotInfoMessage> = None;
let mut current: Option<SlotInfo> = None;
let mut message = "".to_string();
let slot_progress = new_spinner_progress_bar();
@@ -1164,17 +1280,50 @@ pub fn process_show_stakes(
let progress_bar = new_spinner_progress_bar();
progress_bar.set_message("Fetching stake accounts...");
let all_stake_accounts = rpc_client.get_program_accounts(&solana_stake_program::id())?;
let mut program_accounts_config = RpcProgramAccountsConfig {
filters: None,
account_config: RpcAccountInfoConfig {
encoding: Some(solana_account_decoder::UiAccountEncoding::Base64),
..RpcAccountInfoConfig::default()
},
};
if let Some(vote_account_pubkeys) = vote_account_pubkeys {
// Use server-side filtering if only one vote account is provided
if vote_account_pubkeys.len() == 1 {
program_accounts_config.filters = Some(vec![
// Filter by `StakeState::Stake(_, _)`
rpc_filter::RpcFilterType::Memcmp(rpc_filter::Memcmp {
offset: 0,
bytes: rpc_filter::MemcmpEncodedBytes::Binary(
bs58::encode([2, 0, 0, 0]).into_string(),
),
encoding: Some(rpc_filter::MemcmpEncoding::Binary),
}),
// Filter by `Delegation::voter_pubkey`, which begins at byte offset 124
rpc_filter::RpcFilterType::Memcmp(rpc_filter::Memcmp {
offset: 124,
bytes: rpc_filter::MemcmpEncodedBytes::Binary(
vote_account_pubkeys[0].to_string(),
),
encoding: Some(rpc_filter::MemcmpEncoding::Binary),
}),
]);
}
}
let all_stake_accounts = rpc_client
.get_program_accounts_with_config(&solana_stake_program::id(), program_accounts_config)?;
let stake_history_account = rpc_client.get_account(&stake_history::id())?;
progress_bar.finish_and_clear();
let clock_account = rpc_client.get_account(&sysvar::clock::id())?;
let clock: Clock = Sysvar::from_account(&clock_account).ok_or_else(|| {
CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
})?;
progress_bar.finish_and_clear();
let stake_history = StakeHistory::from_account(&stake_history_account).ok_or_else(|| {
CliError::RpcRequestError("Failed to deserialize stake history".to_string())
})?;
let clock: Clock = Sysvar::from_account(&clock_account).ok_or_else(|| {
CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
})?;
let mut stake_accounts: Vec<CliKeyedStakeState> = vec![];
for (stake_pubkey, stake_account) in all_stake_accounts {
@@ -1322,6 +1471,7 @@ pub fn process_transaction_history(
before: Option<Signature>,
until: Option<Signature>,
limit: usize,
show_transactions: bool,
) -> ProcessResult {
let results = rpc_client.get_confirmed_signatures_for_address2_with_config(
address,
@@ -1349,6 +1499,28 @@ pub fn process_transaction_history(
} else {
println!("{}", result.signature);
}
if show_transactions {
if let Ok(signature) = result.signature.parse::<Signature>() {
match rpc_client
.get_confirmed_transaction(&signature, UiTransactionEncoding::Base64)
{
Ok(confirmed_transaction) => {
println_transaction(
&confirmed_transaction
.transaction
.transaction
.decode()
.expect("Successful decode"),
&confirmed_transaction.transaction.meta,
" ",
);
}
Err(err) => println!(" Unable to get confirmed transaction details: {}", err),
}
}
println!();
}
}
Ok(transactions_found)
}
@@ -1371,12 +1543,16 @@ mod tests {
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 default_signer = DefaultSigner {
path: default_keypair_file,
arg_name: String::new(),
};
let test_cluster_version = test_commands
.clone()
.get_matches_from(vec!["test", "cluster-date"]);
assert_eq!(
parse_command(&test_cluster_version, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_cluster_version, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::ClusterDate,
signers: vec![],
@@ -1387,7 +1563,7 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "cluster-version"]);
assert_eq!(
parse_command(&test_cluster_version, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_cluster_version, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::ClusterVersion,
signers: vec![],
@@ -1396,7 +1572,7 @@ mod tests {
let test_fees = test_commands.clone().get_matches_from(vec!["test", "fees"]);
assert_eq!(
parse_command(&test_fees, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_fees, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::Fees,
signers: vec![],
@@ -1409,7 +1585,7 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "block-time", &slot.to_string()]);
assert_eq!(
parse_command(&test_get_block_time, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_get_block_time, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::GetBlockTime { slot: Some(slot) },
signers: vec![],
@@ -1420,7 +1596,7 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "epoch"]);
assert_eq!(
parse_command(&test_get_epoch, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_get_epoch, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::GetEpoch,
signers: vec![],
@@ -1431,7 +1607,7 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "epoch-info"]);
assert_eq!(
parse_command(&test_get_epoch_info, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_get_epoch_info, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::GetEpochInfo,
signers: vec![],
@@ -1442,7 +1618,7 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "genesis-hash"]);
assert_eq!(
parse_command(&test_get_genesis_hash, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_get_genesis_hash, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::GetGenesisHash,
signers: vec![],
@@ -1451,7 +1627,7 @@ mod tests {
let test_get_slot = test_commands.clone().get_matches_from(vec!["test", "slot"]);
assert_eq!(
parse_command(&test_get_slot, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_get_slot, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::GetSlot,
signers: vec![],
@@ -1462,7 +1638,7 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "total-supply"]);
assert_eq!(
parse_command(&test_total_supply, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_total_supply, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::TotalSupply,
signers: vec![],
@@ -1473,7 +1649,7 @@ mod tests {
.clone()
.get_matches_from(vec!["test", "transaction-count"]);
assert_eq!(
parse_command(&test_transaction_count, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_transaction_count, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::GetTransactionCount,
signers: vec![],
@@ -1493,7 +1669,7 @@ mod tests {
"max",
]);
assert_eq!(
parse_command(&test_ping, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_ping, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::Ping {
lamports: 1,

365
cli/src/feature.rs Normal file
View File

@@ -0,0 +1,365 @@
use crate::{
cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
};
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use console::style;
use serde::{Deserialize, Serialize};
use solana_clap_utils::{input_parsers::*, input_validators::*, keypair::*};
use solana_cli_output::{QuietDisplay, VerboseDisplay};
use solana_client::{client_error::ClientError, rpc_client::RpcClient};
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_runtime::{
feature::{self, Feature},
feature_set::FEATURE_NAMES,
};
use solana_sdk::{
clock::Slot, message::Message, pubkey::Pubkey, system_instruction, transaction::Transaction,
};
use std::{collections::HashMap, fmt, sync::Arc};
#[derive(Debug, PartialEq)]
pub enum FeatureCliCommand {
Status { features: Vec<Pubkey> },
Activate { feature: Pubkey },
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "status", content = "sinceSlot")]
pub enum CliFeatureStatus {
Inactive,
Pending,
Active(Slot),
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliFeature {
pub id: String,
pub description: String,
#[serde(flatten)]
pub status: CliFeatureStatus,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CliFeatures {
pub features: Vec<CliFeature>,
pub feature_activation_allowed: bool,
#[serde(skip)]
pub inactive: bool,
}
impl fmt::Display for CliFeatures {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.features.len() > 1 {
writeln!(
f,
"{}",
style(format!(
"{:<44} {:<40} {}",
"Feature", "Description", "Status"
))
.bold()
)?;
}
for feature in &self.features {
writeln!(
f,
"{:<44} {:<40} {}",
feature.id,
feature.description,
match feature.status {
CliFeatureStatus::Inactive => style("inactive".to_string()).red(),
CliFeatureStatus::Pending => style("activation pending".to_string()).yellow(),
CliFeatureStatus::Active(activation_slot) =>
style(format!("active since slot {}", activation_slot)).green(),
}
)?;
}
if self.inactive && !self.feature_activation_allowed {
writeln!(
f,
"{}",
style("\nFeature activation is not allowed at this time")
.bold()
.red()
)?;
}
Ok(())
}
}
impl QuietDisplay for CliFeatures {}
impl VerboseDisplay for CliFeatures {}
pub trait FeatureSubCommands {
fn feature_subcommands(self) -> Self;
}
impl FeatureSubCommands for App<'_, '_> {
fn feature_subcommands(self) -> Self {
self.subcommand(
SubCommand::with_name("feature")
.about("Runtime feature management")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name("status")
.about("Query runtime feature status")
.arg(
Arg::with_name("features")
.value_name("ADDRESS")
.validator(is_valid_pubkey)
.index(1)
.multiple(true)
.help("Feature status to query [default: all known features]"),
),
)
.subcommand(
SubCommand::with_name("activate")
.about("Activate a runtime feature")
.arg(
Arg::with_name("feature")
.value_name("FEATURE_KEYPAIR")
.validator(is_valid_signer)
.index(1)
.required(true)
.help("The signer for the feature to activate"),
),
),
)
}
}
fn known_feature(feature: &Pubkey) -> Result<(), CliError> {
if FEATURE_NAMES.contains_key(feature) {
Ok(())
} else {
Err(CliError::BadParameter(format!(
"Unknown feature: {}",
feature
)))
}
}
pub fn parse_feature_subcommand(
matches: &ArgMatches<'_>,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let response = match matches.subcommand() {
("activate", Some(matches)) => {
let (feature_signer, feature) = signer_of(matches, "feature", wallet_manager)?;
let mut signers = vec![default_signer.signer_from_path(matches, wallet_manager)?];
signers.push(feature_signer.unwrap());
let feature = feature.unwrap();
known_feature(&feature)?;
CliCommandInfo {
command: CliCommand::Feature(FeatureCliCommand::Activate { feature }),
signers,
}
}
("status", Some(matches)) => {
let mut features = if let Some(features) = pubkeys_of(matches, "features") {
for feature in &features {
known_feature(feature)?;
}
features
} else {
FEATURE_NAMES.keys().cloned().collect()
};
features.sort();
CliCommandInfo {
command: CliCommand::Feature(FeatureCliCommand::Status { features }),
signers: vec![],
}
}
_ => unreachable!(),
};
Ok(response)
}
pub fn process_feature_subcommand(
rpc_client: &RpcClient,
config: &CliConfig,
feature_subcommand: &FeatureCliCommand,
) -> ProcessResult {
match feature_subcommand {
FeatureCliCommand::Status { features } => process_status(rpc_client, config, features),
FeatureCliCommand::Activate { feature } => process_activate(rpc_client, config, *feature),
}
}
fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, u64>, ClientError> {
// Validator identity -> feature set
let feature_set_map = rpc_client
.get_cluster_nodes()?
.into_iter()
.map(|contact_info| (contact_info.pubkey, contact_info.feature_set))
.collect::<HashMap<_, _>>();
let vote_accounts = rpc_client.get_vote_accounts()?;
let total_active_stake: u64 = vote_accounts
.current
.iter()
.chain(vote_accounts.delinquent.iter())
.map(|vote_account| vote_account.activated_stake)
.sum();
// Sum all active stake by feature set
let mut active_stake_by_feature_set = HashMap::new();
for vote_account in vote_accounts.current {
if let Some(Some(feature_set)) = feature_set_map.get(&vote_account.node_pubkey) {
*active_stake_by_feature_set.entry(*feature_set).or_default() +=
vote_account.activated_stake;
} else {
*active_stake_by_feature_set
.entry(0 /* "unknown" */)
.or_default() += vote_account.activated_stake;
}
}
// Convert active stake to a percentage so the caller doesn't need `total_active_stake`
for (_, val) in active_stake_by_feature_set.iter_mut() {
*val = *val * 100 / total_active_stake;
}
Ok(active_stake_by_feature_set)
}
// Feature activation is only allowed when 95% of the active stake is on the current feature set
fn feature_activation_allowed(rpc_client: &RpcClient) -> Result<bool, ClientError> {
let my_feature_set = solana_version::Version::default().feature_set;
let active_stake_by_feature_set = active_stake_by_feature_set(rpc_client)?;
let feature_activation_allowed = active_stake_by_feature_set
.get(&my_feature_set)
.map(|percentage| *percentage >= 95)
.unwrap_or(false);
if !feature_activation_allowed {
println!("\n{}", style("Stake By Feature Set:").bold());
for (feature_set, percentage) in active_stake_by_feature_set.iter() {
if *feature_set == 0 {
println!("unknown - {}%", percentage);
} else {
println!(
"{} - {}% {}",
feature_set,
percentage,
if *feature_set == my_feature_set {
" <-- me"
} else {
""
}
);
}
}
}
Ok(feature_activation_allowed)
}
fn process_status(
rpc_client: &RpcClient,
config: &CliConfig,
feature_ids: &[Pubkey],
) -> ProcessResult {
let mut features: Vec<CliFeature> = vec![];
let mut inactive = false;
for (i, account) in rpc_client
.get_multiple_accounts(feature_ids)?
.into_iter()
.enumerate()
{
let feature_id = &feature_ids[i];
let feature_name = FEATURE_NAMES.get(feature_id).unwrap();
if let Some(account) = account {
if let Some(feature) = Feature::from_account(&account) {
let feature_status = match feature.activated_at {
None => CliFeatureStatus::Pending,
Some(activation_slot) => CliFeatureStatus::Active(activation_slot),
};
features.push(CliFeature {
id: feature_id.to_string(),
description: feature_name.to_string(),
status: feature_status,
});
continue;
}
}
inactive = true;
features.push(CliFeature {
id: feature_id.to_string(),
description: feature_name.to_string(),
status: CliFeatureStatus::Inactive,
});
}
let feature_set = CliFeatures {
features,
feature_activation_allowed: feature_activation_allowed(rpc_client)?,
inactive,
};
Ok(config.output_format.formatted_string(&feature_set))
}
fn process_activate(
rpc_client: &RpcClient,
config: &CliConfig,
feature_id: Pubkey,
) -> ProcessResult {
let account = rpc_client
.get_multiple_accounts(&[feature_id])?
.into_iter()
.next()
.unwrap();
if let Some(account) = account {
if Feature::from_account(&account).is_some() {
return Err(format!("{} has already been activated", feature_id).into());
}
}
if !feature_activation_allowed(rpc_client)? {
return Err("Feature activation is not allowed at this time".into());
}
let rent = rpc_client.get_minimum_balance_for_rent_exemption(Feature::size_of())?;
let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let (message, _) = resolve_spend_tx_and_check_account_balance(
rpc_client,
false,
SpendAmount::Some(rent),
&fee_calculator,
&config.signers[0].pubkey(),
|lamports| {
Message::new(
&[
system_instruction::transfer(
&config.signers[0].pubkey(),
&feature_id,
lamports,
),
system_instruction::allocate(&feature_id, Feature::size_of() as u64),
system_instruction::assign(&feature_id, &feature::id()),
],
Some(&config.signers[0].pubkey()),
)
},
config.commitment,
)?;
let mut transaction = Transaction::new_unsigned(message);
transaction.try_sign(&config.signers, blockhash)?;
println!(
"Activating {} ({})",
FEATURE_NAMES.get(&feature_id).unwrap(),
feature_id
);
rpc_client.send_and_confirm_transaction_with_spinner(&transaction)?;
Ok("".to_string())
}

89
cli/src/inflation.rs Normal file
View File

@@ -0,0 +1,89 @@
use crate::cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult};
use clap::{App, ArgMatches, SubCommand};
use console::style;
use solana_clap_utils::keypair::*;
use solana_client::rpc_client::RpcClient;
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use std::sync::Arc;
#[derive(Debug, PartialEq)]
pub enum InflationCliCommand {
Show,
}
pub trait InflationSubCommands {
fn inflation_subcommands(self) -> Self;
}
impl InflationSubCommands for App<'_, '_> {
fn inflation_subcommands(self) -> Self {
self.subcommand(SubCommand::with_name("inflation").about("Show inflation information"))
}
}
pub fn parse_inflation_subcommand(
_matches: &ArgMatches<'_>,
_default_signer: &DefaultSigner,
_wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
Ok(CliCommandInfo {
command: CliCommand::Inflation(InflationCliCommand::Show),
signers: vec![],
})
}
pub fn process_inflation_subcommand(
rpc_client: &RpcClient,
_config: &CliConfig,
inflation_subcommand: &InflationCliCommand,
) -> ProcessResult {
assert_eq!(*inflation_subcommand, InflationCliCommand::Show);
let governor = rpc_client.get_inflation_governor()?;
let current_inflation_rate = rpc_client.get_inflation_rate()?;
println!("{}", style("Inflation Governor:").bold());
if (governor.initial - governor.terminal).abs() < f64::EPSILON {
println!(
"Fixed APR: {:>5.2}%",
governor.terminal * 100.
);
} else {
println!("Initial APR: {:>5.2}%", governor.initial * 100.);
println!(
"Terminal APR: {:>5.2}%",
governor.terminal * 100.
);
println!("Rate reduction per year: {:>5.2}%", governor.taper * 100.);
}
if governor.foundation_term > 0. {
println!("Foundation percentage: {:>5.2}%", governor.foundation);
println!(
"Foundation term: {:.1} years",
governor.foundation_term
);
}
println!(
"\n{}",
style(format!(
"Inflation for Epoch {}:",
current_inflation_rate.epoch
))
.bold()
);
println!(
"Total APR: {:>5.2}%",
current_inflation_rate.total * 100.
);
println!(
"Staking APR: {:>5.2}%",
current_inflation_rate.validator * 100.
);
println!(
"Foundation APR: {:>5.2}%",
current_inflation_rate.foundation * 100.
);
Ok("".to_string())
}

View File

@@ -18,16 +18,14 @@ macro_rules! pubkey {
};
}
#[macro_use]
extern crate serde_derive;
pub mod checks;
pub mod cli;
pub mod cli_output;
pub mod cluster_query;
pub mod display;
pub mod feature;
pub mod inflation;
pub mod nonce;
pub mod offline;
pub mod spend_utils;
pub mod stake;
pub mod test_utils;

View File

@@ -5,22 +5,37 @@ use clap::{
use console::style;
use solana_clap_utils::{
commitment::COMMITMENT_ARG, input_parsers::commitment_of, input_validators::is_url,
keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, DisplayError,
commitment::COMMITMENT_ARG,
input_parsers::commitment_of,
input_validators::is_url,
keypair::{CliSigners, DefaultSigner, SKIP_SEED_PHRASE_VALIDATION_ARG},
DisplayError,
};
use solana_cli::{
cli::{
app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners,
DEFAULT_RPC_TIMEOUT_SECONDS,
},
cli_output::OutputFormat,
display::{println_name_value, println_name_value_or},
use solana_cli::cli::{
app, parse_command, process_command, CliCommandInfo, CliConfig, SettingType,
DEFAULT_RPC_TIMEOUT_SECONDS,
};
use solana_cli_config::{Config, CONFIG_FILE};
use solana_cli_output::{display::println_name_value, OutputFormat};
use solana_client::rpc_config::RpcSendTransactionConfig;
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use std::{collections::HashMap, error, path::PathBuf, sync::Arc, time::Duration};
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(),
);
}
fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error>> {
let parse_args = match matches.subcommand() {
("config", Some(matches)) => {
@@ -139,13 +154,19 @@ pub fn parse_args<'a>(
matches.value_of("json_rpc_url").unwrap_or(""),
&config.json_rpc_url,
);
let default_signer_arg_name = "keypair".to_string();
let (_, default_signer_path) = CliConfig::compute_keypair_path_setting(
matches.value_of("keypair").unwrap_or(""),
matches.value_of(&default_signer_arg_name).unwrap_or(""),
&config.keypair_path,
);
let default_signer = DefaultSigner {
arg_name: default_signer_arg_name,
path: default_signer_path.clone(),
};
let CliCommandInfo { command, signers } =
parse_command(&matches, &default_signer_path, &mut wallet_manager)?;
parse_command(&matches, &default_signer, &mut wallet_manager)?;
let output_format = matches
.value_of("output_format")

View File

@@ -1,29 +1,26 @@
use crate::{
checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
cli::{
generate_unique_signers, log_instruction_custom_error, CliCommand, CliCommandInfo,
CliConfig, CliError, ProcessResult, SignerIndex,
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
ProcessResult,
},
cli_output::CliNonceAccount,
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
};
use clap::{App, Arg, ArgMatches, SubCommand};
use solana_clap_utils::{
input_parsers::*, input_validators::*, offline::BLOCKHASH_ARG, ArgConstant,
input_parsers::*,
input_validators::*,
keypair::{DefaultSigner, SignerIndex},
nonce::*,
};
use solana_client::rpc_client::RpcClient;
use solana_cli_output::CliNonceAccount;
use solana_client::{nonce_utils::*, rpc_client::RpcClient};
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account::Account,
account_utils::StateMut,
commitment_config::CommitmentConfig,
hash::Hash,
message::Message,
nonce::{
self,
state::{Data, Versions},
State,
},
nonce::{self, State},
pubkey::Pubkey,
system_instruction::{
advance_nonce_account, authorize_nonce_account, create_nonce_account,
@@ -33,64 +30,11 @@ use solana_sdk::{
transaction::Transaction,
};
use std::sync::Arc;
use thiserror::Error;
#[derive(Debug, Error, PartialEq)]
pub enum CliNonceError {
#[error("invalid account owner")]
InvalidAccountOwner,
#[error("invalid account data")]
InvalidAccountData,
#[error("unexpected account data size")]
UnexpectedDataSize,
#[error("query hash does not match stored hash")]
InvalidHash,
#[error("query authority does not match account authority")]
InvalidAuthority,
#[error("invalid state for requested operation")]
InvalidStateForOperation,
#[error("client error: {0}")]
Client(String),
}
pub const NONCE_ARG: ArgConstant<'static> = ArgConstant {
name: "nonce",
long: "nonce",
help: "Provide the nonce account to use when creating a nonced \n\
transaction. Nonced transactions are useful when a transaction \n\
requires a lengthy signing process. Learn more about nonced \n\
transactions at https://docs.solana.com/offline-signing/durable-nonce",
};
pub const NONCE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
name: "nonce_authority",
long: "nonce-authority",
help: "Provide the nonce authority keypair to use when signing a nonced transaction",
};
pub trait NonceSubCommands {
fn nonce_subcommands(self) -> Self;
}
pub fn nonce_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(NONCE_ARG.name)
.long(NONCE_ARG.long)
.takes_value(true)
.value_name("PUBKEY")
.requires(BLOCKHASH_ARG.name)
.validator(is_valid_pubkey)
.help(NONCE_ARG.help)
}
pub fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(NONCE_AUTHORITY_ARG.name)
.long(NONCE_AUTHORITY_ARG.long)
.takes_value(true)
.value_name("KEYPAIR")
.validator(is_valid_signer)
.help(NONCE_AUTHORITY_ARG.help)
}
impl NonceSubCommands for App<'_, '_> {
fn nonce_subcommands(self) -> Self {
self.subcommand(
@@ -220,64 +164,9 @@ impl NonceSubCommands for App<'_, '_> {
}
}
pub fn get_account(
rpc_client: &RpcClient,
nonce_pubkey: &Pubkey,
) -> Result<Account, CliNonceError> {
get_account_with_commitment(rpc_client, nonce_pubkey, CommitmentConfig::default())
}
pub fn get_account_with_commitment(
rpc_client: &RpcClient,
nonce_pubkey: &Pubkey,
commitment: CommitmentConfig,
) -> Result<Account, CliNonceError> {
rpc_client
.get_account_with_commitment(nonce_pubkey, commitment)
.map_err(|e| CliNonceError::Client(format!("{}", e)))
.and_then(|result| {
result.value.ok_or_else(|| {
CliNonceError::Client(format!("AccountNotFound: pubkey={}", nonce_pubkey))
})
})
.and_then(|a| match account_identity_ok(&a) {
Ok(()) => Ok(a),
Err(e) => Err(e),
})
}
pub fn account_identity_ok(account: &Account) -> Result<(), CliNonceError> {
if account.owner != system_program::id() {
Err(CliNonceError::InvalidAccountOwner)
} else if account.data.is_empty() {
Err(CliNonceError::UnexpectedDataSize)
} else {
Ok(())
}
}
pub fn state_from_account(account: &Account) -> Result<State, CliNonceError> {
account_identity_ok(account)?;
StateMut::<Versions>::state(account)
.map_err(|_| CliNonceError::InvalidAccountData)
.map(|v| v.convert_to_current())
}
pub fn data_from_account(account: &Account) -> Result<Data, CliNonceError> {
account_identity_ok(account)?;
state_from_account(account).and_then(|ref s| data_from_state(s).map(|d| d.clone()))
}
pub fn data_from_state(state: &State) -> Result<&Data, CliNonceError> {
match state {
State::Uninitialized => Err(CliNonceError::InvalidStateForOperation),
State::Initialized(data) => Ok(data),
}
}
pub fn parse_authorize_nonce_account(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
@@ -286,10 +175,9 @@ pub fn parse_authorize_nonce_account(
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
let signer_info = default_signer.generate_unique_signers(
vec![payer_provided, nonce_authority],
matches,
default_signer_path,
wallet_manager,
)?;
@@ -305,7 +193,7 @@ pub fn parse_authorize_nonce_account(
pub fn parse_nonce_create_account(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let (nonce_account, nonce_account_pubkey) =
@@ -315,10 +203,9 @@ pub fn parse_nonce_create_account(
let nonce_authority = pubkey_of_signer(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
let signer_info = default_signer.generate_unique_signers(
vec![payer_provided, nonce_account],
matches,
default_signer_path,
wallet_manager,
)?;
@@ -348,7 +235,7 @@ pub fn parse_get_nonce(
pub fn parse_new_nonce(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
@@ -356,10 +243,9 @@ pub fn parse_new_nonce(
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
let signer_info = default_signer.generate_unique_signers(
vec![payer_provided, nonce_authority],
matches,
default_signer_path,
wallet_manager,
)?;
@@ -391,7 +277,7 @@ pub fn parse_show_nonce_account(
pub fn parse_withdraw_from_nonce_account(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
@@ -402,10 +288,9 @@ pub fn parse_withdraw_from_nonce_account(
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
let signer_info = default_signer.generate_unique_signers(
vec![payer_provided, nonce_authority],
matches,
default_signer_path,
wallet_manager,
)?;
@@ -429,14 +314,14 @@ pub fn check_nonce_account(
match state_from_account(nonce_account)? {
State::Initialized(ref data) => {
if &data.blockhash != nonce_hash {
Err(CliNonceError::InvalidHash.into())
Err(Error::InvalidHash.into())
} else if nonce_authority != &data.authority {
Err(CliNonceError::InvalidAuthority.into())
Err(Error::InvalidAuthority.into())
} else {
Ok(())
}
}
State::Uninitialized => Err(CliNonceError::InvalidStateForOperation.into()),
State::Uninitialized => Err(Error::InvalidStateForOperation.into()),
}
}
@@ -691,9 +576,10 @@ mod tests {
use crate::cli::{app, parse_command};
use solana_sdk::{
account::Account,
account_utils::StateMut,
fee_calculator::FeeCalculator,
hash::hash,
nonce::{self, State},
nonce::{self, state::Versions, State},
signature::{read_keypair_file, write_keypair, Keypair, Signer},
system_program,
};
@@ -710,6 +596,10 @@ mod tests {
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 default_signer = DefaultSigner {
path: default_keypair_file.clone(),
arg_name: String::new(),
};
let (keypair_file, mut tmp_file) = make_tmp_file();
let nonce_account_keypair = Keypair::new();
write_keypair(&nonce_account_keypair, tmp_file.as_file_mut()).unwrap();
@@ -728,12 +618,7 @@ mod tests {
&Pubkey::default().to_string(),
]);
assert_eq!(
parse_command(
&test_authorize_nonce_account,
&default_keypair_file,
&mut None
)
.unwrap(),
parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::AuthorizeNonceAccount {
nonce_account: nonce_account_pubkey,
@@ -754,12 +639,7 @@ mod tests {
&authority_keypair_file,
]);
assert_eq!(
parse_command(
&test_authorize_nonce_account,
&default_keypair_file,
&mut None
)
.unwrap(),
parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::AuthorizeNonceAccount {
nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
@@ -781,7 +661,7 @@ mod tests {
"50",
]);
assert_eq!(
parse_command(&test_create_nonce_account, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateNonceAccount {
nonce_account: 1,
@@ -806,7 +686,7 @@ mod tests {
&authority_keypair_file,
]);
assert_eq!(
parse_command(&test_create_nonce_account, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateNonceAccount {
nonce_account: 1,
@@ -828,7 +708,7 @@ mod tests {
&nonce_account_string,
]);
assert_eq!(
parse_command(&test_get_nonce, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_get_nonce, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::GetNonce(nonce_account_keypair.pubkey()),
signers: vec![],
@@ -842,7 +722,7 @@ mod tests {
.get_matches_from(vec!["test", "new-nonce", &keypair_file]);
let nonce_account = read_keypair_file(&keypair_file).unwrap();
assert_eq!(
parse_command(&test_new_nonce, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::NewNonce {
nonce_account: nonce_account.pubkey(),
@@ -862,7 +742,7 @@ mod tests {
]);
let nonce_account = read_keypair_file(&keypair_file).unwrap();
assert_eq!(
parse_command(&test_new_nonce, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::NewNonce {
nonce_account: nonce_account.pubkey(),
@@ -882,7 +762,7 @@ mod tests {
&nonce_account_string,
]);
assert_eq!(
parse_command(&test_show_nonce_account, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_show_nonce_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::ShowNonceAccount {
nonce_account_pubkey: nonce_account_keypair.pubkey(),
@@ -903,7 +783,7 @@ mod tests {
assert_eq!(
parse_command(
&test_withdraw_from_nonce_account,
&default_keypair_file,
&default_signer,
&mut None
)
.unwrap(),
@@ -931,7 +811,7 @@ mod tests {
assert_eq!(
parse_command(
&test_withdraw_from_nonce_account,
&default_keypair_file,
&default_signer,
&mut None
)
.unwrap(),
@@ -966,14 +846,14 @@ mod tests {
if let CliError::InvalidNonce(err) =
check_nonce_account(&invalid_owner.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
{
assert_eq!(err, CliNonceError::InvalidAccountOwner,);
assert_eq!(err, Error::InvalidAccountOwner,);
}
let invalid_data = Account::new_data(1, &"invalid", &system_program::ID);
if let CliError::InvalidNonce(err) =
check_nonce_account(&invalid_data.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
{
assert_eq!(err, CliNonceError::InvalidAccountData,);
assert_eq!(err, Error::InvalidAccountData,);
}
let data = Versions::new_current(State::Initialized(nonce::state::Data {
@@ -985,7 +865,7 @@ mod tests {
if let CliError::InvalidNonce(err) =
check_nonce_account(&invalid_hash.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
{
assert_eq!(err, CliNonceError::InvalidHash,);
assert_eq!(err, Error::InvalidHash,);
}
let data = Versions::new_current(State::Initialized(nonce::state::Data {
@@ -997,7 +877,7 @@ mod tests {
if let CliError::InvalidNonce(err) =
check_nonce_account(&invalid_authority.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
{
assert_eq!(err, CliNonceError::InvalidAuthority,);
assert_eq!(err, Error::InvalidAuthority,);
}
let data = Versions::new_current(State::Uninitialized);
@@ -1005,7 +885,7 @@ mod tests {
if let CliError::InvalidNonce(err) =
check_nonce_account(&invalid_state.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
{
assert_eq!(err, CliNonceError::InvalidStateForOperation,);
assert_eq!(err, Error::InvalidStateForOperation,);
}
}
@@ -1017,14 +897,14 @@ mod tests {
let system_account = Account::new(1, 0, &system_program::id());
assert_eq!(
account_identity_ok(&system_account),
Err(CliNonceError::UnexpectedDataSize),
Err(Error::UnexpectedDataSize),
);
let other_program = Pubkey::new(&[1u8; 32]);
let other_account_no_data = Account::new(1, 0, &other_program);
assert_eq!(
account_identity_ok(&other_account_no_data),
Err(CliNonceError::InvalidAccountOwner),
Err(Error::InvalidAccountOwner),
);
}
@@ -1049,7 +929,7 @@ mod tests {
let wrong_data_size_account = Account::new(1, 1, &system_program::id());
assert_eq!(
state_from_account(&wrong_data_size_account),
Err(CliNonceError::InvalidAccountData),
Err(Error::InvalidAccountData),
);
}
@@ -1059,11 +939,11 @@ mod tests {
let state = state_from_account(&nonce_account).unwrap();
assert_eq!(
data_from_state(&state),
Err(CliNonceError::InvalidStateForOperation)
Err(Error::InvalidStateForOperation)
);
assert_eq!(
data_from_account(&nonce_account),
Err(CliNonceError::InvalidStateForOperation)
Err(Error::InvalidStateForOperation)
);
let data = nonce::state::Data {

View File

@@ -1,129 +0,0 @@
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 mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
let signer_strings = object.get("signers");
if let Some(sig_strings) = signer_strings {
present_signers = sig_strings
.as_array()
.unwrap()
.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 mut absent_signers: Vec<Pubkey> = Vec::new();
let signer_strings = object.get("absent");
if let Some(sig_strings) = signer_strings {
absent_signers = sig_strings
.as_array()
.unwrap()
.iter()
.map(|val| {
let s = val.as_str().unwrap();
Pubkey::from_str(s).unwrap()
})
.collect();
}
let mut bad_signers: Vec<Pubkey> = Vec::new();
let signer_strings = object.get("badSig");
if let Some(sig_strings) = signer_strings {
bad_signers = sig_strings
.as_array()
.unwrap()
.iter()
.map(|val| {
let s = val.as_str().unwrap();
Pubkey::from_str(s).unwrap()
})
.collect();
}
SignOnly {
blockhash,
present_signers,
absent_signers,
bad_signers,
}
}

View File

@@ -1,18 +1,29 @@
use crate::{
checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
cli::{
fee_payer_arg, generate_unique_signers, log_instruction_custom_error, nonce_authority_arg,
return_signers, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
SignerIndex, FEE_PAYER_ARG,
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
ProcessResult,
},
cli_output::{CliStakeHistory, CliStakeHistoryEntry, CliStakeState, CliStakeType},
nonce::{self, check_nonce_account, nonce_arg, NONCE_ARG, NONCE_AUTHORITY_ARG},
offline::{blockhash_query::BlockhashQuery, *},
nonce::check_nonce_account,
spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount},
};
use clap::{App, Arg, ArgGroup, ArgMatches, SubCommand};
use solana_clap_utils::{input_parsers::*, input_validators::*, offline::*, ArgConstant};
use solana_client::{rpc_client::RpcClient, rpc_request::DELINQUENT_VALIDATOR_SLOT_DISTANCE};
use solana_clap_utils::{
fee_payer::{fee_payer_arg, FEE_PAYER_ARG},
input_parsers::*,
input_validators::*,
keypair::{DefaultSigner, SignerIndex},
nonce::*,
offline::*,
ArgConstant,
};
use solana_cli_output::{
return_signers, CliStakeHistory, CliStakeHistoryEntry, CliStakeState, CliStakeType,
};
use solana_client::{
blockhash_query::BlockhashQuery, nonce_utils, rpc_client::RpcClient,
rpc_request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
};
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
account_utils::StateMut,
@@ -143,9 +154,8 @@ impl StakeSubCommands for App<'_, '_> {
.validator(is_valid_signer)
.help("Source account of funds [default: cli config keypair]"),
)
.offline_args()
.arg(nonce_arg())
.arg(nonce_authority_arg())
.offline_args(false)
.nonce_args(false)
.arg(fee_payer_arg())
)
.subcommand(
@@ -173,9 +183,8 @@ impl StakeSubCommands for App<'_, '_> {
"The vote account to which the stake will be delegated")
)
.arg(stake_authority_arg())
.offline_args()
.arg(nonce_arg())
.arg(nonce_authority_arg())
.offline_args(false)
.nonce_args(false)
.arg(fee_payer_arg())
)
.subcommand(
@@ -204,9 +213,8 @@ impl StakeSubCommands for App<'_, '_> {
)
.arg(stake_authority_arg())
.arg(withdraw_authority_arg())
.offline_args()
.arg(nonce_arg())
.arg(nonce_authority_arg())
.offline_args(false)
.nonce_args(false)
.arg(fee_payer_arg())
)
.subcommand(
@@ -220,9 +228,8 @@ impl StakeSubCommands for App<'_, '_> {
"Stake account to be deactivated. ")
)
.arg(stake_authority_arg())
.offline_args()
.arg(nonce_arg())
.arg(nonce_authority_arg())
.offline_args(false)
.nonce_args(false)
.arg(fee_payer_arg())
)
.subcommand(
@@ -261,9 +268,8 @@ impl StakeSubCommands for App<'_, '_> {
.help("Seed for address generation; if specified, the resulting account will be at a derived address of the SPLIT STAKE ACCOUNT pubkey")
)
.arg(stake_authority_arg())
.offline_args()
.arg(nonce_arg())
.arg(nonce_authority_arg())
.offline_args(false)
.nonce_args(false)
.arg(fee_payer_arg())
)
.subcommand(
@@ -284,9 +290,8 @@ impl StakeSubCommands for App<'_, '_> {
"Source stake account for the merge. If successful, this stake account will no longer exist after the merge")
)
.arg(stake_authority_arg())
.offline_args()
.arg(nonce_arg())
.arg(nonce_authority_arg())
.offline_args(false)
.nonce_args(false)
.arg(fee_payer_arg())
)
.subcommand(
@@ -316,9 +321,8 @@ impl StakeSubCommands for App<'_, '_> {
.help("The amount to withdraw from the stake account, in SOL")
)
.arg(withdraw_authority_arg())
.offline_args()
.arg(nonce_arg())
.arg(nonce_authority_arg())
.offline_args(false)
.nonce_args(false)
.arg(fee_payer_arg())
.arg(
Arg::with_name("custodian")
@@ -372,9 +376,8 @@ impl StakeSubCommands for App<'_, '_> {
.validator(is_valid_signer)
.help("Keypair of the existing custodian [default: cli config pubkey]")
)
.offline_args()
.arg(nonce_arg())
.arg(nonce_authority_arg())
.offline_args(false)
.nonce_args(false)
.arg(fee_payer_arg())
)
.subcommand(
@@ -411,7 +414,7 @@ impl StakeSubCommands for App<'_, '_> {
pub fn parse_stake_create_account(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let seed = matches.value_of("seed").map(|s| s.to_string());
@@ -436,7 +439,7 @@ pub fn parse_stake_create_account(
bulk_signers.push(nonce_authority);
}
let signer_info =
generate_unique_signers(bulk_signers, matches, default_signer_path, wallet_manager)?;
default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
Ok(CliCommandInfo {
command: CliCommand::CreateStakeAccount {
@@ -463,7 +466,7 @@ pub fn parse_stake_create_account(
pub fn parse_stake_delegate_stake(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let stake_account_pubkey =
@@ -485,7 +488,7 @@ pub fn parse_stake_delegate_stake(
bulk_signers.push(nonce_authority);
}
let signer_info =
generate_unique_signers(bulk_signers, matches, default_signer_path, wallet_manager)?;
default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
Ok(CliCommandInfo {
command: CliCommand::DelegateStake {
@@ -505,7 +508,7 @@ pub fn parse_stake_delegate_stake(
pub fn parse_stake_authorize(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let stake_account_pubkey =
@@ -557,7 +560,7 @@ pub fn parse_stake_authorize(
bulk_signers.push(nonce_authority);
}
let signer_info =
generate_unique_signers(bulk_signers, matches, default_signer_path, wallet_manager)?;
default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
let new_authorizations = new_authorizations
.into_iter()
@@ -588,7 +591,7 @@ pub fn parse_stake_authorize(
pub fn parse_split_stake(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let stake_account_pubkey =
@@ -612,7 +615,7 @@ pub fn parse_split_stake(
bulk_signers.push(nonce_authority);
}
let signer_info =
generate_unique_signers(bulk_signers, matches, default_signer_path, wallet_manager)?;
default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
Ok(CliCommandInfo {
command: CliCommand::SplitStake {
@@ -633,7 +636,7 @@ pub fn parse_split_stake(
pub fn parse_merge_stake(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let stake_account_pubkey =
@@ -655,7 +658,7 @@ pub fn parse_merge_stake(
bulk_signers.push(nonce_authority);
}
let signer_info =
generate_unique_signers(bulk_signers, matches, default_signer_path, wallet_manager)?;
default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
Ok(CliCommandInfo {
command: CliCommand::MergeStake {
@@ -674,7 +677,7 @@ pub fn parse_merge_stake(
pub fn parse_stake_deactivate_stake(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let stake_account_pubkey =
@@ -693,7 +696,7 @@ pub fn parse_stake_deactivate_stake(
bulk_signers.push(nonce_authority);
}
let signer_info =
generate_unique_signers(bulk_signers, matches, default_signer_path, wallet_manager)?;
default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
Ok(CliCommandInfo {
command: CliCommand::DeactivateStake {
@@ -711,7 +714,7 @@ pub fn parse_stake_deactivate_stake(
pub fn parse_stake_withdraw_stake(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let stake_account_pubkey =
@@ -737,7 +740,7 @@ pub fn parse_stake_withdraw_stake(
bulk_signers.push(custodian);
}
let signer_info =
generate_unique_signers(bulk_signers, matches, default_signer_path, wallet_manager)?;
default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
Ok(CliCommandInfo {
command: CliCommand::WithdrawStake {
@@ -758,7 +761,7 @@ pub fn parse_stake_withdraw_stake(
pub fn parse_stake_set_lockup(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let stake_account_pubkey =
@@ -781,7 +784,7 @@ pub fn parse_stake_set_lockup(
bulk_signers.push(nonce_authority);
}
let signer_info =
generate_unique_signers(bulk_signers, matches, default_signer_path, wallet_manager)?;
default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
Ok(CliCommandInfo {
command: CliCommand::StakeSetLockup {
@@ -934,8 +937,11 @@ pub fn process_create_stake_account(
}
if let Some(nonce_account) = &nonce_account {
let nonce_account =
nonce::get_account_with_commitment(rpc_client, nonce_account, config.commitment)?;
let nonce_account = nonce_utils::get_account_with_commitment(
rpc_client,
nonce_account,
config.commitment,
)?;
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
}
}
@@ -943,7 +949,7 @@ pub fn process_create_stake_account(
let mut tx = Transaction::new_unsigned(message);
if sign_only {
tx.try_partial_sign(&config.signers, recent_blockhash)?;
return_signers(&tx, &config)
return_signers(&tx, &config.output_format)
} else {
tx.try_sign(&config.signers, recent_blockhash)?;
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
@@ -1002,12 +1008,15 @@ pub fn process_stake_authorize(
if sign_only {
tx.try_partial_sign(&config.signers, recent_blockhash)?;
return_signers(&tx, &config)
return_signers(&tx, &config.output_format)
} else {
tx.try_sign(&config.signers, recent_blockhash)?;
if let Some(nonce_account) = &nonce_account {
let nonce_account =
nonce::get_account_with_commitment(rpc_client, nonce_account, config.commitment)?;
let nonce_account = nonce_utils::get_account_with_commitment(
rpc_client,
nonce_account,
config.commitment,
)?;
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
}
check_account_for_fee_with_commitment(
@@ -1062,12 +1071,15 @@ pub fn process_deactivate_stake_account(
if sign_only {
tx.try_partial_sign(&config.signers, recent_blockhash)?;
return_signers(&tx, &config)
return_signers(&tx, &config.output_format)
} else {
tx.try_sign(&config.signers, recent_blockhash)?;
if let Some(nonce_account) = &nonce_account {
let nonce_account =
nonce::get_account_with_commitment(rpc_client, nonce_account, config.commitment)?;
let nonce_account = nonce_utils::get_account_with_commitment(
rpc_client,
nonce_account,
config.commitment,
)?;
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
}
check_account_for_fee_with_commitment(
@@ -1131,12 +1143,15 @@ pub fn process_withdraw_stake(
if sign_only {
tx.try_partial_sign(&config.signers, recent_blockhash)?;
return_signers(&tx, &config)
return_signers(&tx, &config.output_format)
} else {
tx.try_sign(&config.signers, recent_blockhash)?;
if let Some(nonce_account) = &nonce_account {
let nonce_account =
nonce::get_account_with_commitment(rpc_client, nonce_account, config.commitment)?;
let nonce_account = nonce_utils::get_account_with_commitment(
rpc_client,
nonce_account,
config.commitment,
)?;
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
}
check_account_for_fee_with_commitment(
@@ -1271,12 +1286,15 @@ pub fn process_split_stake(
if sign_only {
tx.try_partial_sign(&config.signers, recent_blockhash)?;
return_signers(&tx, &config)
return_signers(&tx, &config.output_format)
} else {
tx.try_sign(&config.signers, recent_blockhash)?;
if let Some(nonce_account) = &nonce_account {
let nonce_account =
nonce::get_account_with_commitment(rpc_client, nonce_account, config.commitment)?;
let nonce_account = nonce_utils::get_account_with_commitment(
rpc_client,
nonce_account,
config.commitment,
)?;
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
}
check_account_for_fee_with_commitment(
@@ -1370,12 +1388,15 @@ pub fn process_merge_stake(
if sign_only {
tx.try_partial_sign(&config.signers, recent_blockhash)?;
return_signers(&tx, &config)
return_signers(&tx, &config.output_format)
} else {
tx.try_sign(&config.signers, recent_blockhash)?;
if let Some(nonce_account) = &nonce_account {
let nonce_account =
nonce::get_account_with_commitment(rpc_client, nonce_account, config.commitment)?;
let nonce_account = nonce_utils::get_account_with_commitment(
rpc_client,
nonce_account,
config.commitment,
)?;
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
}
check_account_for_fee_with_commitment(
@@ -1433,12 +1454,15 @@ pub fn process_stake_set_lockup(
if sign_only {
tx.try_partial_sign(&config.signers, recent_blockhash)?;
return_signers(&tx, &config)
return_signers(&tx, &config.output_format)
} else {
tx.try_sign(&config.signers, recent_blockhash)?;
if let Some(nonce_account) = &nonce_account {
let nonce_account =
nonce::get_account_with_commitment(rpc_client, nonce_account, config.commitment)?;
let nonce_account = nonce_utils::get_account_with_commitment(
rpc_client,
nonce_account,
config.commitment,
)?;
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
}
check_account_for_fee_with_commitment(
@@ -1718,12 +1742,15 @@ pub fn process_delegate_stake(
if sign_only {
tx.try_partial_sign(&config.signers, recent_blockhash)?;
return_signers(&tx, &config)
return_signers(&tx, &config.output_format)
} else {
tx.try_sign(&config.signers, recent_blockhash)?;
if let Some(nonce_account) = &nonce_account {
let nonce_account =
nonce::get_account_with_commitment(rpc_client, nonce_account, config.commitment)?;
let nonce_account = nonce_utils::get_account_with_commitment(
rpc_client,
nonce_account,
config.commitment,
)?;
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
}
check_account_for_fee_with_commitment(
@@ -1746,6 +1773,7 @@ pub fn process_delegate_stake(
mod tests {
use super::*;
use crate::cli::{app, parse_command};
use solana_client::blockhash_query;
use solana_sdk::{
hash::Hash,
signature::{
@@ -1766,6 +1794,10 @@ mod tests {
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 default_signer = DefaultSigner {
path: default_keypair_file.clone(),
arg_name: String::new(),
};
let (keypair_file, mut tmp_file) = make_tmp_file();
let stake_account_keypair = Keypair::new();
write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
@@ -1793,7 +1825,7 @@ mod tests {
&new_withdraw_string,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -1827,7 +1859,7 @@ mod tests {
&withdraw_authority_keypair_file,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -1865,7 +1897,7 @@ mod tests {
&withdraw_authority_keypair_file,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -1895,7 +1927,7 @@ mod tests {
&new_stake_string,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -1919,7 +1951,7 @@ mod tests {
&stake_authority_keypair_file,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -1949,7 +1981,7 @@ mod tests {
&withdraw_authority_keypair_file,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -1976,7 +2008,7 @@ mod tests {
&new_withdraw_string,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -2004,7 +2036,7 @@ mod tests {
&withdraw_authority_keypair_file,
]);
assert_eq!(
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -2042,7 +2074,7 @@ mod tests {
"--sign-only",
]);
assert_eq!(
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -2075,7 +2107,7 @@ mod tests {
&pubkey.to_string(),
]);
assert_eq!(
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -2121,7 +2153,7 @@ mod tests {
&pubkey2.to_string(),
]);
assert_eq!(
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -2153,7 +2185,7 @@ mod tests {
&blockhash_string,
]);
assert_eq!(
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -2190,7 +2222,7 @@ mod tests {
&nonce_keypair_file,
]);
assert_eq!(
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -2226,7 +2258,7 @@ mod tests {
&fee_payer_keypair_file,
]);
assert_eq!(
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -2260,7 +2292,7 @@ mod tests {
&signer,
]);
assert_eq!(
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::StakeAuthorize {
stake_account_pubkey,
@@ -2301,7 +2333,7 @@ mod tests {
"43",
]);
assert_eq!(
parse_command(&test_create_stake_account, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateStakeAccount {
stake_account: 1,
@@ -2342,12 +2374,7 @@ mod tests {
]);
assert_eq!(
parse_command(
&test_create_stake_account2,
&default_keypair_file,
&mut None
)
.unwrap(),
parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateStakeAccount {
stake_account: 1,
@@ -2400,12 +2427,7 @@ mod tests {
]);
assert_eq!(
parse_command(
&test_create_stake_account2,
&default_keypair_file,
&mut None
)
.unwrap(),
parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateStakeAccount {
stake_account: 1,
@@ -2441,7 +2463,7 @@ mod tests {
&vote_account_string,
]);
assert_eq!(
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DelegateStake {
stake_account_pubkey,
@@ -2470,7 +2492,7 @@ mod tests {
&stake_authority_keypair_file,
]);
assert_eq!(
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DelegateStake {
stake_account_pubkey,
@@ -2501,7 +2523,7 @@ mod tests {
&vote_account_string,
]);
assert_eq!(
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DelegateStake {
stake_account_pubkey,
@@ -2530,7 +2552,7 @@ mod tests {
&blockhash_string,
]);
assert_eq!(
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DelegateStake {
stake_account_pubkey,
@@ -2560,7 +2582,7 @@ mod tests {
"--sign-only",
]);
assert_eq!(
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DelegateStake {
stake_account_pubkey,
@@ -2594,7 +2616,7 @@ mod tests {
&key1.to_string(),
]);
assert_eq!(
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DelegateStake {
stake_account_pubkey,
@@ -2640,7 +2662,7 @@ mod tests {
&key2.to_string(),
]);
assert_eq!(
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DelegateStake {
stake_account_pubkey,
@@ -2677,7 +2699,7 @@ mod tests {
&fee_payer_keypair_file,
]);
assert_eq!(
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DelegateStake {
stake_account_pubkey,
@@ -2707,7 +2729,7 @@ mod tests {
]);
assert_eq!(
parse_command(&test_withdraw_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::WithdrawStake {
stake_account_pubkey,
@@ -2737,7 +2759,7 @@ mod tests {
]);
assert_eq!(
parse_command(&test_withdraw_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::WithdrawStake {
stake_account_pubkey,
@@ -2772,7 +2794,7 @@ mod tests {
]);
assert_eq!(
parse_command(&test_withdraw_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::WithdrawStake {
stake_account_pubkey,
@@ -2815,7 +2837,7 @@ mod tests {
]);
assert_eq!(
parse_command(&test_withdraw_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::WithdrawStake {
stake_account_pubkey,
@@ -2848,7 +2870,7 @@ mod tests {
&stake_account_string,
]);
assert_eq!(
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DeactivateStake {
stake_account_pubkey,
@@ -2872,7 +2894,7 @@ mod tests {
&stake_authority_keypair_file,
]);
assert_eq!(
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DeactivateStake {
stake_account_pubkey,
@@ -2903,7 +2925,7 @@ mod tests {
&blockhash_string,
]);
assert_eq!(
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DeactivateStake {
stake_account_pubkey,
@@ -2930,7 +2952,7 @@ mod tests {
"--sign-only",
]);
assert_eq!(
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DeactivateStake {
stake_account_pubkey,
@@ -2961,7 +2983,7 @@ mod tests {
&key1.to_string(),
]);
assert_eq!(
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DeactivateStake {
stake_account_pubkey,
@@ -3004,7 +3026,7 @@ mod tests {
&key2.to_string(),
]);
assert_eq!(
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DeactivateStake {
stake_account_pubkey,
@@ -3035,7 +3057,7 @@ mod tests {
&fee_payer_keypair_file,
]);
assert_eq!(
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::DeactivateStake {
stake_account_pubkey,
@@ -3069,7 +3091,7 @@ mod tests {
"50",
]);
assert_eq!(
parse_command(&test_split_stake_account, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::SplitStake {
stake_account_pubkey: stake_account_keypair.pubkey(),
@@ -3130,7 +3152,7 @@ mod tests {
&stake_signer,
]);
assert_eq!(
parse_command(&test_split_stake_account, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::SplitStake {
stake_account_pubkey: stake_account_keypair.pubkey(),
@@ -3170,7 +3192,7 @@ mod tests {
&source_stake_account_pubkey.to_string(),
]);
assert_eq!(
parse_command(&test_merge_stake_account, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_merge_stake_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::MergeStake {
stake_account_pubkey: stake_account_keypair.pubkey(),

View File

@@ -1,6 +1,5 @@
use crate::{
cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
cli_output::{CliValidatorInfo, CliValidatorInfoVec},
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
};
use bincode::deserialize;
@@ -13,8 +12,9 @@ use solana_account_decoder::validator_info::{
use solana_clap_utils::{
input_parsers::pubkey_of,
input_validators::{is_pubkey, is_url},
keypair::signer_from_path,
keypair::DefaultSigner,
};
use solana_cli_output::{CliValidatorInfo, CliValidatorInfoVec};
use solana_client::rpc_client::RpcClient;
use solana_config_program::{config_instruction, get_config_data, ConfigKeys, ConfigState};
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
@@ -212,7 +212,7 @@ impl ValidatorInfoSubCommands for App<'_, '_> {
pub fn parse_validator_info_command(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let info_pubkey = pubkey_of(matches, "info_pubkey");
@@ -224,12 +224,7 @@ pub fn parse_validator_info_command(
force_keybase: matches.is_present("force"),
info_pubkey,
},
signers: vec![signer_from_path(
matches,
default_signer_path,
"keypair",
wallet_manager,
)?],
signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
})
}
@@ -271,10 +266,12 @@ pub fn process_set_validator_info(
let all_config = rpc_client.get_program_accounts(&solana_config_program::id())?;
let existing_account = all_config
.iter()
.filter(|(_, account)| {
let key_list: ConfigKeys = deserialize(&account.data).map_err(|_| false).unwrap();
key_list.keys.contains(&(validator_info::id(), false))
})
.filter(
|(_, account)| match deserialize::<ConfigKeys>(&account.data) {
Ok(key_list) => key_list.keys.contains(&(validator_info::id(), false)),
Err(_) => false,
},
)
.find(|(pubkey, account)| {
let (validator_pubkey, _) = parse_validator_info(&pubkey, &account).unwrap();
validator_pubkey == config.signers[0].pubkey()
@@ -385,10 +382,10 @@ pub fn process_get_validator_info(
all_config
.into_iter()
.filter(|(_, validator_info_account)| {
let key_list: ConfigKeys = deserialize(&validator_info_account.data)
.map_err(|_| false)
.unwrap();
key_list.keys.contains(&(validator_info::id(), false))
match deserialize::<ConfigKeys>(&validator_info_account.data) {
Ok(key_list) => key_list.keys.contains(&(validator_info::id(), false)),
Err(_) => false,
}
})
.collect()
};

View File

@@ -1,14 +1,19 @@
use crate::{
checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
cli::{
generate_unique_signers, log_instruction_custom_error, CliCommand, CliCommandInfo,
CliConfig, CliError, ProcessResult, SignerIndex,
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
ProcessResult,
},
cli_output::{CliEpochVotingHistory, CliLockout, CliVoteAccount},
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
};
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
use solana_clap_utils::{commitment::commitment_arg, input_parsers::*, input_validators::*};
use solana_clap_utils::{
commitment::commitment_arg,
input_parsers::*,
input_validators::*,
keypair::{DefaultSigner, SignerIndex},
};
use solana_cli_output::{CliEpochVotingHistory, CliLockout, CliVoteAccount};
use solana_client::rpc_client::RpcClient;
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
use solana_sdk::{
@@ -246,7 +251,7 @@ impl VoteSubCommands for App<'_, '_> {
pub fn parse_create_vote_account(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let (vote_account, vote_account_pubkey) = signer_of(matches, "vote_account", wallet_manager)?;
@@ -258,10 +263,9 @@ pub fn parse_create_vote_account(
let authorized_withdrawer = pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
let signer_info = default_signer.generate_unique_signers(
vec![payer_provided, vote_account, identity_account],
matches,
default_signer_path,
wallet_manager,
)?;
@@ -280,7 +284,7 @@ pub fn parse_create_vote_account(
pub fn parse_vote_authorize(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
vote_authorize: VoteAuthorize,
) -> Result<CliCommandInfo, CliError> {
@@ -291,10 +295,9 @@ pub fn parse_vote_authorize(
let (authorized, _) = signer_of(matches, "authorized", wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
let signer_info = default_signer.generate_unique_signers(
vec![payer_provided, authorized],
matches,
default_signer_path,
wallet_manager,
)?;
@@ -310,7 +313,7 @@ pub fn parse_vote_authorize(
pub fn parse_vote_update_validator(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let vote_account_pubkey =
@@ -321,10 +324,9 @@ pub fn parse_vote_update_validator(
signer_of(matches, "authorized_withdrawer", wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
let signer_info = default_signer.generate_unique_signers(
vec![payer_provided, authorized_withdrawer, new_identity_account],
matches,
default_signer_path,
wallet_manager,
)?;
@@ -340,7 +342,7 @@ pub fn parse_vote_update_validator(
pub fn parse_vote_update_commission(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let vote_account_pubkey =
@@ -350,10 +352,9 @@ pub fn parse_vote_update_commission(
let commission = value_t_or_exit!(matches, "commission", u8);
let payer_provided = None;
let signer_info = generate_unique_signers(
let signer_info = default_signer.generate_unique_signers(
vec![payer_provided, authorized_withdrawer],
matches,
default_signer_path,
wallet_manager,
)?;
@@ -385,7 +386,7 @@ pub fn parse_vote_get_account_command(
pub fn parse_withdraw_from_vote_account(
matches: &ArgMatches<'_>,
default_signer_path: &str,
default_signer: &DefaultSigner,
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
let vote_account_pubkey =
@@ -398,10 +399,9 @@ pub fn parse_withdraw_from_vote_account(
signer_of(matches, "authorized_withdrawer", wallet_manager)?;
let payer_provided = None;
let signer_info = generate_unique_signers(
let signer_info = default_signer.generate_unique_signers(
vec![payer_provided, withdraw_authority],
matches,
default_signer_path,
wallet_manager,
)?;
@@ -794,6 +794,10 @@ mod tests {
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 default_signer = DefaultSigner {
path: default_keypair_file.clone(),
arg_name: String::new(),
};
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
"test",
@@ -803,7 +807,7 @@ mod tests {
&pubkey2_string,
]);
assert_eq!(
parse_command(&test_authorize_voter, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::VoteAuthorize {
vote_account_pubkey: pubkey,
@@ -826,7 +830,7 @@ mod tests {
&pubkey2_string,
]);
assert_eq!(
parse_command(&test_authorize_voter, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::VoteAuthorize {
vote_account_pubkey: pubkey,
@@ -856,7 +860,7 @@ mod tests {
"10",
]);
assert_eq!(
parse_command(&test_create_vote_account, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: 1,
@@ -885,7 +889,7 @@ mod tests {
&identity_keypair_file,
]);
assert_eq!(
parse_command(&test_create_vote_account2, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_create_vote_account2, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: 1,
@@ -918,7 +922,7 @@ mod tests {
&authed.to_string(),
]);
assert_eq!(
parse_command(&test_create_vote_account3, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_create_vote_account3, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: 1,
@@ -949,7 +953,7 @@ mod tests {
&authed.to_string(),
]);
assert_eq!(
parse_command(&test_create_vote_account4, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_create_vote_account4, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::CreateVoteAccount {
vote_account: 1,
@@ -975,7 +979,7 @@ mod tests {
&keypair_file,
]);
assert_eq!(
parse_command(&test_update_validator, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_update_validator, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::VoteUpdateValidator {
vote_account_pubkey: pubkey,
@@ -998,7 +1002,7 @@ mod tests {
&keypair_file,
]);
assert_eq!(
parse_command(&test_update_commission, &default_keypair_file, &mut None).unwrap(),
parse_command(&test_update_commission, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::VoteUpdateCommission {
vote_account_pubkey: pubkey,
@@ -1021,12 +1025,7 @@ mod tests {
"42",
]);
assert_eq!(
parse_command(
&test_withdraw_from_vote_account,
&default_keypair_file,
&mut None
)
.unwrap(),
parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::WithdrawFromVoteAccount {
vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
@@ -1047,12 +1046,7 @@ mod tests {
"ALL",
]);
assert_eq!(
parse_command(
&test_withdraw_from_vote_account,
&default_keypair_file,
&mut None
)
.unwrap(),
parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::WithdrawFromVoteAccount {
vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
@@ -1078,12 +1072,7 @@ mod tests {
&withdraw_authority_file,
]);
assert_eq!(
parse_command(
&test_withdraw_from_vote_account,
&default_keypair_file,
&mut None
)
.unwrap(),
parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
CliCommandInfo {
command: CliCommand::WithdrawFromVoteAccount {
vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),

View File

@@ -1,7 +1,7 @@
use serde_json::Value;
use solana_cli::cli::{process_command, CliCommand, CliConfig};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::TestValidator;
use solana_core::test_validator::TestValidator;
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::{
bpf_loader,

View File

@@ -1,17 +1,16 @@
use solana_cli::{
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
cli_output::OutputFormat,
nonce,
offline::{
blockhash_query::{self, BlockhashQuery},
parse_sign_only_reply_string,
},
spend_utils::SpendAmount,
test_utils::{check_ready, check_recent_balance},
};
use solana_client::rpc_client::RpcClient;
use solana_cli_output::{parse_sign_only_reply_string, OutputFormat};
use solana_client::{
blockhash_query::{self, BlockhashQuery},
nonce_utils,
rpc_client::RpcClient,
};
use solana_core::contact_info::ContactInfo;
use solana_core::validator::{TestValidator, TestValidatorOptions};
use solana_core::test_validator::{TestValidator, TestValidatorOptions};
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::{
commitment_config::CommitmentConfig,
@@ -302,11 +301,14 @@ fn test_create_account_with_seed() {
check_recent_balance(0, &rpc_client, &to_address);
// Fetch nonce hash
let nonce_hash =
nonce::get_account_with_commitment(&rpc_client, &nonce_address, CommitmentConfig::recent())
.and_then(|ref a| nonce::data_from_account(a))
.unwrap()
.blockhash;
let nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_address,
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
// Test by creating transfer TX with nonce, fully offline
let mut authority_config = CliConfig::recent_for_tests();

View File

@@ -2,17 +2,16 @@ use chrono::prelude::*;
use serde_json::Value;
use solana_cli::{
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig, PayCommand},
cli_output::OutputFormat,
nonce,
offline::{
blockhash_query::{self, BlockhashQuery},
parse_sign_only_reply_string,
},
spend_utils::SpendAmount,
test_utils::check_recent_balance,
};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::TestValidator;
use solana_cli_output::{parse_sign_only_reply_string, OutputFormat};
use solana_client::{
blockhash_query::{self, BlockhashQuery},
nonce_utils,
rpc_client::RpcClient,
};
use solana_core::test_validator::TestValidator;
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::{
commitment_config::CommitmentConfig,
@@ -411,12 +410,12 @@ fn test_nonced_pay_tx() {
check_recent_balance(minimum_nonce_balance, &rpc_client, &nonce_account.pubkey());
// Fetch nonce hash
let nonce_hash = nonce::get_account_with_commitment(
let nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
@@ -438,12 +437,12 @@ fn test_nonced_pay_tx() {
check_recent_balance(10, &rpc_client, &bob_pubkey);
// Verify that nonce has been used
let nonce_hash2 = nonce::get_account_with_commitment(
let nonce_hash2 = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
assert_ne!(nonce_hash, nonce_hash2);

View File

@@ -1,6 +1,6 @@
use solana_cli::cli::{process_command, CliCommand, CliConfig};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::TestValidator;
use solana_core::test_validator::TestValidator;
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair};
use std::{fs::remove_dir_all, sync::mpsc::channel};

View File

@@ -1,16 +1,15 @@
use solana_cli::{
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
cli_output::OutputFormat,
nonce,
offline::{
blockhash_query::{self, BlockhashQuery},
parse_sign_only_reply_string,
},
spend_utils::SpendAmount,
test_utils::{check_ready, check_recent_balance},
};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::{TestValidator, TestValidatorOptions};
use solana_cli_output::{parse_sign_only_reply_string, OutputFormat};
use solana_client::{
blockhash_query::{self, BlockhashQuery},
nonce_utils,
rpc_client::RpcClient,
};
use solana_core::test_validator::{TestValidator, TestValidatorOptions};
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::{
account_utils::StateMut,
@@ -503,12 +502,12 @@ fn test_nonced_stake_delegation_and_deactivation() {
process_command(&config).unwrap();
// Fetch nonce hash
let nonce_hash = nonce::get_account_with_commitment(
let nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
@@ -531,12 +530,12 @@ fn test_nonced_stake_delegation_and_deactivation() {
process_command(&config).unwrap();
// Fetch nonce hash
let nonce_hash = nonce::get_account_with_commitment(
let nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
@@ -770,12 +769,12 @@ fn test_stake_authorize() {
process_command(&config).unwrap();
// Fetch nonce hash
let nonce_hash = nonce::get_account_with_commitment(
let nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
@@ -824,12 +823,12 @@ fn test_stake_authorize() {
};
assert_eq!(current_authority, online_authority_pubkey);
let new_nonce_hash = nonce::get_account_with_commitment(
let new_nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
assert_ne!(nonce_hash, new_nonce_hash);
@@ -1069,12 +1068,12 @@ fn test_stake_split() {
check_recent_balance(minimum_nonce_balance, &rpc_client, &nonce_account.pubkey());
// Fetch nonce hash
let nonce_hash = nonce::get_account_with_commitment(
let nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
@@ -1338,12 +1337,12 @@ fn test_stake_set_lockup() {
check_recent_balance(minimum_nonce_balance, &rpc_client, &nonce_account_pubkey);
// Fetch nonce hash
let nonce_hash = nonce::get_account_with_commitment(
let nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
@@ -1465,12 +1464,12 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
process_command(&config).unwrap();
// Fetch nonce hash
let nonce_hash = nonce::get_account_with_commitment(
let nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
@@ -1520,12 +1519,12 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
check_recent_balance(50_000, &rpc_client, &stake_pubkey);
// Fetch nonce hash
let nonce_hash = nonce::get_account_with_commitment(
let nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
@@ -1568,12 +1567,12 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
check_recent_balance(42, &rpc_client, &recipient_pubkey);
// Fetch nonce hash
let nonce_hash = nonce::get_account_with_commitment(
let nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;

View File

@@ -1,16 +1,15 @@
use solana_cli::{
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
cli_output::OutputFormat,
nonce,
offline::{
blockhash_query::{self, BlockhashQuery},
parse_sign_only_reply_string,
},
spend_utils::SpendAmount,
test_utils::{check_ready, check_recent_balance},
};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::{TestValidator, TestValidatorOptions};
use solana_cli_output::{parse_sign_only_reply_string, OutputFormat};
use solana_client::{
blockhash_query::{self, BlockhashQuery},
nonce_utils,
rpc_client::RpcClient,
};
use solana_core::test_validator::{TestValidator, TestValidatorOptions};
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::{
commitment_config::CommitmentConfig,
@@ -153,12 +152,12 @@ fn test_transfer() {
check_recent_balance(49_987 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
// Fetch nonce hash
let nonce_hash = nonce::get_account_with_commitment(
let nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
@@ -181,12 +180,12 @@ fn test_transfer() {
process_command(&config).unwrap();
check_recent_balance(49_976 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
check_recent_balance(30, &rpc_client, &recipient_pubkey);
let new_nonce_hash = nonce::get_account_with_commitment(
let new_nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;
assert_ne!(nonce_hash, new_nonce_hash);
@@ -202,12 +201,12 @@ fn test_transfer() {
check_recent_balance(49_975 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
// Fetch nonce hash
let nonce_hash = nonce::get_account_with_commitment(
let nonce_hash = nonce_utils::get_account_with_commitment(
&rpc_client,
&nonce_account.pubkey(),
CommitmentConfig::recent(),
)
.and_then(|ref a| nonce::data_from_account(a))
.and_then(|ref a| nonce_utils::data_from_account(a))
.unwrap()
.blockhash;

View File

@@ -1,11 +1,13 @@
use solana_cli::{
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
offline::{blockhash_query::BlockhashQuery, *},
spend_utils::SpendAmount,
test_utils::check_recent_balance,
};
use solana_client::rpc_client::RpcClient;
use solana_core::validator::TestValidator;
use solana_client::{
blockhash_query::{self, BlockhashQuery},
rpc_client::RpcClient,
};
use solana_core::test_validator::TestValidator;
use solana_faucet::faucet::run_local_faucet;
use solana_sdk::{
account_utils::StateMut,

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-client"
version = "1.3.8"
version = "1.3.15"
description = "Solana Client"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -11,28 +11,30 @@ edition = "2018"
[dependencies]
bincode = "1.3.1"
bs58 = "0.3.1"
clap = "2.33.0"
indicatif = "0.15.0"
jsonrpc-core = "14.2.0"
jsonrpc-core = "15.0.0"
log = "0.4.8"
rayon = "1.4.0"
reqwest = { version = "0.10.6", default-features = false, features = ["blocking", "rustls-tls", "json"] }
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.56"
solana-account-decoder = { path = "../account-decoder", version = "1.3.8" }
solana-net-utils = { path = "../net-utils", version = "1.3.8" }
solana-sdk = { path = "../sdk", version = "1.3.8" }
solana-transaction-status = { path = "../transaction-status", version = "1.3.8" }
solana-vote-program = { path = "../programs/vote", version = "1.3.8" }
solana-account-decoder = { path = "../account-decoder", version = "1.3.15" }
solana-clap-utils = { path = "../clap-utils", version = "1.3.15" }
solana-net-utils = { path = "../net-utils", version = "1.3.15" }
solana-sdk = { path = "../sdk", version = "1.3.15" }
solana-transaction-status = { path = "../transaction-status", version = "1.3.15" }
solana-vote-program = { path = "../programs/vote", version = "1.3.15" }
thiserror = "1.0"
tungstenite = "0.10.1"
url = "2.1.1"
[dev-dependencies]
assert_matches = "1.3.0"
jsonrpc-core = "14.2.0"
jsonrpc-http-server = "14.2.0"
solana-logger = { path = "../logger", version = "1.3.8" }
jsonrpc-core = "15.0.0"
jsonrpc-http-server = "15.0.0"
solana-logger = { path = "../logger", version = "1.3.15" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,5 +1,13 @@
use super::*;
use solana_sdk::commitment_config::CommitmentConfig;
use crate::{nonce_utils, rpc_client::RpcClient};
use clap::ArgMatches;
use solana_clap_utils::{
input_parsers::{pubkey_of, value_of},
nonce::*,
offline::*,
};
use solana_sdk::{
commitment_config::CommitmentConfig, fee_calculator::FeeCalculator, hash::Hash, pubkey::Pubkey,
};
#[derive(Debug, PartialEq)]
pub enum Source {
@@ -21,8 +29,8 @@ impl Source {
Ok((res.0, res.1))
}
Self::NonceAccount(ref pubkey) => {
let data = nonce::get_account_with_commitment(rpc_client, pubkey, commitment)
.and_then(|ref a| nonce::data_from_account(a))?;
let data = nonce_utils::get_account_with_commitment(rpc_client, pubkey, commitment)
.and_then(|ref a| nonce_utils::data_from_account(a))?;
Ok((data.blockhash, data.fee_calculator))
}
}
@@ -42,8 +50,8 @@ impl Source {
Ok(res)
}
Self::NonceAccount(ref pubkey) => {
let res = nonce::get_account_with_commitment(rpc_client, pubkey, commitment)?;
let res = nonce::data_from_account(&res)?;
let res = nonce_utils::get_account_with_commitment(rpc_client, pubkey, commitment)?;
let res = nonce_utils::data_from_account(&res)?;
Ok(Some(res)
.filter(|d| d.blockhash == *blockhash)
.map(|d| d.fee_calculator))
@@ -75,7 +83,7 @@ impl BlockhashQuery {
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);
let nonce_account = pubkey_of(matches, NONCE_ARG.name);
BlockhashQuery::new(blockhash, sign_only, nonce_account)
}
@@ -108,17 +116,15 @@ impl Default for BlockhashQuery {
#[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_account_decoder::{UiAccount, UiAccountEncoding};
use solana_client::{
use crate::{
blockhash_query,
rpc_request::RpcRequest,
rpc_response::{Response, RpcFeeCalculator, RpcResponseContext},
};
use solana_sdk::{
account::Account, fee_calculator::FeeCalculator, hash::hash, nonce, system_program,
};
use clap::App;
use serde_json::{self, json, Value};
use solana_account_decoder::{UiAccount, UiAccountEncoding};
use solana_sdk::{account::Account, hash::hash, nonce, system_program};
use std::collections::HashMap;
#[test]
@@ -172,8 +178,8 @@ mod tests {
#[test]
fn test_blockhash_query_new_from_matches_ok() {
let test_commands = App::new("blockhash_query_test")
.arg(nonce_arg())
.offline_args();
.nonce_args(false)
.offline_args(false);
let blockhash = hash(&[1u8]);
let blockhash_string = blockhash.to_string();

View File

@@ -1,9 +1,11 @@
#[macro_use]
extern crate serde_derive;
pub mod blockhash_query;
pub mod client_error;
pub mod http_sender;
pub mod mock_sender;
pub mod nonce_utils;
pub mod perf_utils;
pub mod pubsub_client;
pub mod rpc_client;

View File

@@ -94,9 +94,15 @@ impl RpcSender for MockSender {
err,
})
};
let statuses: Vec<Option<TransactionStatus>> = params.as_array().unwrap()[0]
.as_array()
.unwrap()
.iter()
.map(|_| status.clone())
.collect();
serde_json::to_value(Response {
context: RpcResponseContext { slot: 1 },
value: vec![status],
value: statuses,
})?
}
RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)),

82
client/src/nonce_utils.rs Normal file
View File

@@ -0,0 +1,82 @@
use crate::rpc_client::RpcClient;
use solana_sdk::{
account::Account,
account_utils::StateMut,
commitment_config::CommitmentConfig,
nonce::{
state::{Data, Versions},
State,
},
pubkey::Pubkey,
system_program,
};
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum Error {
#[error("invalid account owner")]
InvalidAccountOwner,
#[error("invalid account data")]
InvalidAccountData,
#[error("unexpected account data size")]
UnexpectedDataSize,
#[error("query hash does not match stored hash")]
InvalidHash,
#[error("query authority does not match account authority")]
InvalidAuthority,
#[error("invalid state for requested operation")]
InvalidStateForOperation,
#[error("client error: {0}")]
Client(String),
}
pub fn get_account(rpc_client: &RpcClient, nonce_pubkey: &Pubkey) -> Result<Account, Error> {
get_account_with_commitment(rpc_client, nonce_pubkey, CommitmentConfig::default())
}
pub fn get_account_with_commitment(
rpc_client: &RpcClient,
nonce_pubkey: &Pubkey,
commitment: CommitmentConfig,
) -> Result<Account, Error> {
rpc_client
.get_account_with_commitment(nonce_pubkey, commitment)
.map_err(|e| Error::Client(format!("{}", e)))
.and_then(|result| {
result
.value
.ok_or_else(|| Error::Client(format!("AccountNotFound: pubkey={}", nonce_pubkey)))
})
.and_then(|a| match account_identity_ok(&a) {
Ok(()) => Ok(a),
Err(e) => Err(e),
})
}
pub fn account_identity_ok(account: &Account) -> Result<(), Error> {
if account.owner != system_program::id() {
Err(Error::InvalidAccountOwner)
} else if account.data.is_empty() {
Err(Error::UnexpectedDataSize)
} else {
Ok(())
}
}
pub fn state_from_account(account: &Account) -> Result<State, Error> {
account_identity_ok(account)?;
StateMut::<Versions>::state(account)
.map_err(|_| Error::InvalidAccountData)
.map(|v| v.convert_to_current())
}
pub fn data_from_account(account: &Account) -> Result<Data, Error> {
account_identity_ok(account)?;
state_from_account(account).and_then(|ref s| data_from_state(s).map(|d| d.clone()))
}
pub fn data_from_state(state: &State) -> Result<&Data, Error> {
match state {
State::Uninitialized => Err(Error::InvalidStateForOperation),
State::Initialized(data) => Ok(data),
}
}

View File

@@ -1,13 +1,12 @@
use crate::rpc_response::{Response as RpcResponse, RpcSignatureResult, SlotInfo};
use log::*;
use serde::{de::DeserializeOwned, Deserialize};
use serde::de::DeserializeOwned;
use serde_json::{
json,
value::Value::{Number, Object},
Map, Value,
};
use solana_sdk::{
commitment_config::CommitmentConfig, signature::Signature, transaction::TransactionError,
};
use solana_sdk::signature::Signature;
use std::{
marker::PhantomData,
sync::{
@@ -21,7 +20,7 @@ use thiserror::Error;
use tungstenite::{client::AutoStream, connect, Message, WebSocket};
use url::{ParseError, Url};
type PubsubSignatureResponse = PubsubClientSubscription<RpcResponse<SignatureResult>>;
type PubsubSignatureResponse = PubsubClientSubscription<RpcResponse<RpcSignatureResult>>;
#[derive(Debug, Error)]
pub enum PubsubClientError {
@@ -38,45 +37,6 @@ pub enum PubsubClientError {
UnexpectedMessageError,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase", tag = "type", content = "result")]
pub enum SignatureResult {
ProcessedSignatureResult(ProcessedSignatureResult),
ReceivedSignature,
}
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub struct RpcResponseContext {
pub slot: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ProcessedSignatureResult {
pub err: Option<TransactionError>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RpcResponse<T> {
pub context: RpcResponseContext,
pub value: T,
}
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub struct SlotInfoMessage {
pub parent: u64,
pub root: u64,
pub slot: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcSignatureSubscribeConfig {
#[serde(flatten)]
pub commitment: Option<CommitmentConfig>,
pub enable_received_notification: Option<bool>,
}
pub struct PubsubClientSubscription<T>
where
T: DeserializeOwned,
@@ -186,22 +146,16 @@ pub struct PubsubClient {}
impl PubsubClient {
pub fn slot_subscribe(
url: &str,
) -> Result<
(
PubsubClientSubscription<SlotInfoMessage>,
Receiver<SlotInfoMessage>,
),
PubsubClientError,
> {
) -> Result<(PubsubClientSubscription<SlotInfo>, Receiver<SlotInfo>), PubsubClientError> {
let url = Url::parse(url)?;
let (socket, _response) = connect(url)?;
let (sender, receiver) = channel::<SlotInfoMessage>();
let (sender, receiver) = channel::<SlotInfo>();
let socket = Arc::new(RwLock::new(socket));
let socket_clone = socket.clone();
let exit = Arc::new(AtomicBool::new(false));
let exit_clone = exit.clone();
let subscription_id = PubsubClientSubscription::<SlotInfoMessage>::send_subscribe(
let subscription_id = PubsubClientSubscription::<SlotInfo>::send_subscribe(
&socket_clone,
json!({
"jsonrpc":"2.0","id":1,"method":format!("{}Subscribe", SLOT_OPERATION),"params":[]
@@ -216,11 +170,11 @@ impl PubsubClient {
break;
}
let message: Result<SlotInfoMessage, PubsubClientError> =
let message: Result<SlotInfo, PubsubClientError> =
PubsubClientSubscription::read_message(&socket_clone);
if let Ok(msg) = message {
match sender.send(msg.clone()) {
match sender.send(msg) {
Ok(_) => (),
Err(err) => {
info!("receive error: {:?}", err);
@@ -236,7 +190,7 @@ impl PubsubClient {
info!("websocket - exited receive loop");
});
let result: PubsubClientSubscription<SlotInfoMessage> = PubsubClientSubscription {
let result: PubsubClientSubscription<SlotInfo> = PubsubClientSubscription {
message_type: PhantomData,
operation: SLOT_OPERATION,
socket,
@@ -254,13 +208,13 @@ impl PubsubClient {
) -> Result<
(
PubsubSignatureResponse,
Receiver<RpcResponse<SignatureResult>>,
Receiver<RpcResponse<RpcSignatureResult>>,
),
PubsubClientError,
> {
let url = Url::parse(url)?;
let (socket, _response) = connect(url)?;
let (sender, receiver) = channel::<RpcResponse<SignatureResult>>();
let (sender, receiver) = channel::<RpcResponse<RpcSignatureResult>>();
let socket = Arc::new(RwLock::new(socket));
let socket_clone = socket.clone();
@@ -277,7 +231,7 @@ impl PubsubClient {
})
.to_string();
let subscription_id =
PubsubClientSubscription::<RpcResponse<SignatureResult>>::send_subscribe(
PubsubClientSubscription::<RpcResponse<RpcSignatureResult>>::send_subscribe(
&socket_clone,
body,
)
@@ -289,7 +243,7 @@ impl PubsubClient {
break;
}
let message: Result<RpcResponse<SignatureResult>, PubsubClientError> =
let message: Result<RpcResponse<RpcSignatureResult>, PubsubClientError> =
PubsubClientSubscription::read_message(&socket_clone);
if let Ok(msg) = message {
@@ -309,7 +263,7 @@ impl PubsubClient {
info!("websocket - exited receive loop");
});
let result: PubsubClientSubscription<RpcResponse<SignatureResult>> =
let result: PubsubClientSubscription<RpcResponse<RpcSignatureResult>> =
PubsubClientSubscription {
message_type: PhantomData,
operation: SIGNATURE_OPERATION,

View File

@@ -5,7 +5,7 @@ use crate::{
rpc_config::RpcAccountInfoConfig,
rpc_config::{
RpcGetConfirmedSignaturesForAddress2Config, RpcLargestAccountsConfig,
RpcSendTransactionConfig, RpcTokenAccountsFilter,
RpcProgramAccountsConfig, RpcSendTransactionConfig, RpcTokenAccountsFilter,
},
rpc_request::{RpcError, RpcRequest, TokenAccountsFilter},
rpc_response::*,
@@ -15,7 +15,10 @@ use bincode::serialize;
use indicatif::{ProgressBar, ProgressStyle};
use log::*;
use serde_json::{json, Value};
use solana_account_decoder::{parse_token::UiTokenAmount, UiAccount, UiAccountEncoding};
use solana_account_decoder::{
parse_token::{TokenAccountType, UiTokenAccount, UiTokenAmount},
UiAccount, UiAccountData, UiAccountEncoding,
};
use solana_sdk::{
account::Account,
clock::{
@@ -29,10 +32,10 @@ use solana_sdk::{
hash::Hash,
pubkey::Pubkey,
signature::Signature,
transaction::{self, Transaction},
transaction::{self, uses_durable_nonce, Transaction},
};
use solana_transaction_status::{
ConfirmedBlock, ConfirmedTransaction, TransactionStatus, UiTransactionEncoding,
EncodedConfirmedBlock, EncodedConfirmedTransaction, TransactionStatus, UiTransactionEncoding,
};
use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY;
use std::{
@@ -161,6 +164,19 @@ impl RpcClient {
self.send(RpcRequest::GetSignatureStatuses, json!([signatures]))
}
pub fn get_signature_statuses_with_history(
&self,
signatures: &[Signature],
) -> RpcResult<Vec<Option<TransactionStatus>>> {
let signatures: Vec<_> = signatures.iter().map(|s| s.to_string()).collect();
self.send(
RpcRequest::GetSignatureStatuses,
json!([signatures, {
"searchTransactionHistory": true
}]),
)
}
pub fn get_signature_status_with_commitment(
&self,
signature: &Signature,
@@ -245,7 +261,7 @@ impl RpcClient {
self.send(RpcRequest::GetClusterNodes, Value::Null)
}
pub fn get_confirmed_block(&self, slot: Slot) -> ClientResult<ConfirmedBlock> {
pub fn get_confirmed_block(&self, slot: Slot) -> ClientResult<EncodedConfirmedBlock> {
self.get_confirmed_block_with_encoding(slot, UiTransactionEncoding::Json)
}
@@ -253,7 +269,7 @@ impl RpcClient {
&self,
slot: Slot,
encoding: UiTransactionEncoding,
) -> ClientResult<ConfirmedBlock> {
) -> ClientResult<EncodedConfirmedBlock> {
self.send(RpcRequest::GetConfirmedBlock, json!([slot, encoding]))
}
@@ -268,6 +284,17 @@ impl RpcClient {
)
}
pub fn get_confirmed_blocks_with_limit(
&self,
start_slot: Slot,
limit: usize,
) -> ClientResult<Vec<Slot>> {
self.send(
RpcRequest::GetConfirmedBlocksWithLimit,
json!([start_slot, limit]),
)
}
pub fn get_confirmed_signatures_for_address(
&self,
address: &Pubkey,
@@ -323,7 +350,7 @@ impl RpcClient {
&self,
signature: &Signature,
encoding: UiTransactionEncoding,
) -> ClientResult<ConfirmedTransaction> {
) -> ClientResult<EncodedConfirmedTransaction> {
self.send(
RpcRequest::GetConfirmedTransaction,
json!([signature.to_string(), encoding]),
@@ -411,44 +438,48 @@ impl RpcClient {
&self,
transaction: &Transaction,
) -> ClientResult<Signature> {
let mut send_retries = 20;
loop {
let mut status_retries = 15;
let signature = self.send_transaction(transaction)?;
let status = loop {
let status = self.get_signature_status(&signature)?;
if status.is_none() {
status_retries -= 1;
if status_retries == 0 {
break status;
}
} else {
let signature = self.send_transaction(transaction)?;
let recent_blockhash = if uses_durable_nonce(transaction).is_some() {
self.get_recent_blockhash_with_commitment(CommitmentConfig::recent())?
.value
.0
} else {
transaction.message.recent_blockhash
};
let status = loop {
let status = self.get_signature_status(&signature)?;
if status.is_none() {
if self
.get_fee_calculator_for_blockhash_with_commitment(
&recent_blockhash,
CommitmentConfig::recent(),
)?
.value
.is_none()
{
break status;
}
if cfg!(not(test)) {
// Retry twice a second
sleep(Duration::from_millis(500));
}
};
send_retries = if let Some(result) = status.clone() {
match result {
Ok(_) => return Ok(signature),
Err(_) => 0,
}
} else {
send_retries - 1
};
if send_retries == 0 {
if let Some(err) = status {
return Err(err.unwrap_err().into());
} else {
return Err(
RpcError::ForUser("unable to confirm transaction. \
This can happen in situations such as transaction expiration \
and insufficient fee-payer funds".to_string()).into(),
);
}
break status;
}
if cfg!(not(test)) {
// Retry twice a second
sleep(Duration::from_millis(500));
}
};
if let Some(result) = status {
match result {
Ok(_) => Ok(signature),
Err(err) => Err(err.into()),
}
} else {
Err(RpcError::ForUser(
"unable to confirm transaction. \
This can happen in situations such as transaction expiration \
and insufficient fee-payer funds"
.to_string(),
)
.into())
}
}
@@ -516,14 +547,14 @@ impl RpcClient {
data_slice: None,
};
let pubkeys: Vec<_> = pubkeys.iter().map(|pubkey| pubkey.to_string()).collect();
let response = self.send(RpcRequest::GetMultipleAccounts, json!([[pubkeys], config]))?;
let response = self.send(RpcRequest::GetMultipleAccounts, json!([pubkeys, config]))?;
let Response {
context,
value: accounts,
} = serde_json::from_value::<Response<Option<UiAccount>>>(response)?;
} = serde_json::from_value::<Response<Vec<Option<UiAccount>>>>(response)?;
let accounts: Vec<Option<Account>> = accounts
.iter()
.map(|rpc_account| rpc_account.decode())
.into_iter()
.map(|rpc_account| rpc_account.map(|a| a.decode()).flatten())
.collect();
Ok(Response {
context,
@@ -571,8 +602,27 @@ impl RpcClient {
}
pub fn get_program_accounts(&self, pubkey: &Pubkey) -> ClientResult<Vec<(Pubkey, Account)>> {
let accounts: Vec<RpcKeyedAccount> =
self.send(RpcRequest::GetProgramAccounts, json!([pubkey.to_string()]))?;
self.get_program_accounts_with_config(
pubkey,
RpcProgramAccountsConfig {
filters: None,
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64),
..RpcAccountInfoConfig::default()
},
},
)
}
pub fn get_program_accounts_with_config(
&self,
pubkey: &Pubkey,
config: RpcProgramAccountsConfig,
) -> ClientResult<Vec<(Pubkey, Account)>> {
let accounts: Vec<RpcKeyedAccount> = self.send(
RpcRequest::GetProgramAccounts,
json!([pubkey.to_string(), config]),
)?;
parse_keyed_accounts(accounts, RpcRequest::GetProgramAccounts)
}
@@ -709,6 +759,10 @@ impl RpcClient {
.into())
}
pub fn get_first_available_block(&self) -> ClientResult<Slot> {
self.send(RpcRequest::GetFirstAvailableBlock, Value::Null)
}
pub fn get_genesis_hash(&self) -> ClientResult<Hash> {
let hash_str: String = self.send(RpcRequest::GetGenesisHash, Value::Null)?;
let hash = hash_str.parse().map_err(|_| {
@@ -720,6 +774,67 @@ impl RpcClient {
Ok(hash)
}
pub fn get_token_account(&self, pubkey: &Pubkey) -> ClientResult<Option<UiTokenAccount>> {
Ok(self
.get_token_account_with_commitment(pubkey, CommitmentConfig::default())?
.value)
}
pub fn get_token_account_with_commitment(
&self,
pubkey: &Pubkey,
commitment_config: CommitmentConfig,
) -> RpcResult<Option<UiTokenAccount>> {
let config = RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::JsonParsed),
commitment: Some(commitment_config),
data_slice: None,
};
let response = self.sender.send(
RpcRequest::GetAccountInfo,
json!([pubkey.to_string(), config]),
);
response
.map(|result_json| {
if result_json.is_null() {
return Err(
RpcError::ForUser(format!("AccountNotFound: pubkey={}", pubkey)).into(),
);
}
let Response {
context,
value: rpc_account,
} = serde_json::from_value::<Response<Option<UiAccount>>>(result_json)?;
trace!("Response account {:?} {:?}", pubkey, rpc_account);
let response = {
if let Some(rpc_account) = rpc_account {
if let UiAccountData::Json(account_data) = rpc_account.data {
let token_account_type: TokenAccountType =
serde_json::from_value(account_data.parsed)?;
if let TokenAccountType::Account(token_account) = token_account_type {
return Ok(Response {
context,
value: Some(token_account),
});
}
}
}
Err(Into::<ClientError>::into(RpcError::ForUser(format!(
"Account could not be parsed as token account: pubkey={}",
pubkey
))))
};
response?
})
.map_err(|err| {
Into::<ClientError>::into(RpcError::ForUser(format!(
"AccountNotFound: pubkey={}: {}",
pubkey, err
)))
})?
}
pub fn get_token_account_balance(&self, pubkey: &Pubkey) -> ClientResult<UiTokenAmount> {
Ok(self
.get_token_account_balance_with_commitment(pubkey, CommitmentConfig::default())?
@@ -1039,62 +1154,54 @@ impl RpcClient {
let progress_bar = new_spinner_progress_bar();
let mut send_retries = 20;
let signature = loop {
progress_bar.set_message(&format!(
"[{}/{}] Finalizing transaction {}",
confirmations, desired_confirmations, transaction.signatures[0],
));
let mut status_retries = 15;
let (signature, status) = loop {
let signature = self.send_transaction_with_config(transaction, config)?;
// Get recent commitment in order to count confirmations for successful transactions
let status = self
.get_signature_status_with_commitment(&signature, CommitmentConfig::recent())?;
if status.is_none() {
status_retries -= 1;
if status_retries == 0 {
break (signature, status);
}
} else {
progress_bar.set_message(&format!(
"[{}/{}] Finalizing transaction {}",
confirmations, desired_confirmations, transaction.signatures[0],
));
let recent_blockhash = if uses_durable_nonce(transaction).is_some() {
self.get_recent_blockhash_with_commitment(CommitmentConfig::recent())?
.value
.0
} else {
transaction.message.recent_blockhash
};
let signature = self.send_transaction_with_config(transaction, config)?;
let (signature, status) = loop {
// Get recent commitment in order to count confirmations for successful transactions
let status =
self.get_signature_status_with_commitment(&signature, CommitmentConfig::recent())?;
if status.is_none() {
if self
.get_fee_calculator_for_blockhash_with_commitment(
&recent_blockhash,
CommitmentConfig::recent(),
)?
.value
.is_none()
{
break (signature, status);
}
if cfg!(not(test)) {
sleep(Duration::from_millis(500));
}
};
send_retries = if let Some(result) = status.clone() {
match result {
Ok(_) => 0,
// If transaction errors, return right away; no point in counting confirmations
Err(_) => 0,
}
} else {
send_retries - 1
};
if send_retries == 0 {
if let Some(result) = status {
match result {
Ok(_) => {
break signature;
}
Err(err) => {
return Err(err.into());
}
}
} else {
return Err(RpcError::ForUser(
"unable to confirm transaction. \
This can happen in situations such as transaction \
expiration and insufficient fee-payer funds"
.to_string(),
)
.into());
}
break (signature, status);
}
if cfg!(not(test)) {
sleep(Duration::from_millis(500));
}
};
if let Some(result) = status {
if let Err(err) = result {
return Err(err.into());
}
} else {
return Err(RpcError::ForUser(
"unable to confirm transaction. \
This can happen in situations such as transaction expiration \
and insufficient fee-payer funds"
.to_string(),
)
.into());
}
let now = Instant::now();
loop {
match commitment.commitment {

View File

@@ -1,6 +1,9 @@
use crate::rpc_filter::RpcFilterType;
use solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig};
use solana_sdk::{clock::Epoch, commitment_config::CommitmentConfig};
use solana_sdk::{
clock::Epoch,
commitment_config::{CommitmentConfig, CommitmentLevel},
};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -11,14 +14,17 @@ pub struct RpcSignatureStatusConfig {
#[derive(Debug, Default, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcSendTransactionConfig {
#[serde(default)]
pub skip_preflight: bool,
pub preflight_commitment: Option<CommitmentConfig>,
pub preflight_commitment: Option<CommitmentLevel>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcSimulateTransactionConfig {
#[serde(default)]
pub sig_verify: bool,
#[serde(flatten)]
pub commitment: Option<CommitmentConfig>,
}

View File

@@ -13,6 +13,7 @@ pub enum RpcRequest {
GetClusterNodes,
GetConfirmedBlock,
GetConfirmedBlocks,
GetConfirmedBlocksWithLimit,
GetConfirmedSignaturesForAddress,
GetConfirmedSignaturesForAddress2,
GetConfirmedTransaction,
@@ -21,6 +22,7 @@ pub enum RpcRequest {
GetFeeCalculatorForBlockhash,
GetFeeRateGovernor,
GetFees,
GetFirstAvailableBlock,
GetGenesisHash,
GetIdentity,
GetInflationGovernor,
@@ -66,6 +68,7 @@ impl fmt::Display for RpcRequest {
RpcRequest::GetClusterNodes => "getClusterNodes",
RpcRequest::GetConfirmedBlock => "getConfirmedBlock",
RpcRequest::GetConfirmedBlocks => "getConfirmedBlocks",
RpcRequest::GetConfirmedBlocksWithLimit => "getConfirmedBlocksWithLimit",
RpcRequest::GetConfirmedSignaturesForAddress => "getConfirmedSignaturesForAddress",
RpcRequest::GetConfirmedSignaturesForAddress2 => "getConfirmedSignaturesForAddress2",
RpcRequest::GetConfirmedTransaction => "getConfirmedTransaction",
@@ -74,6 +77,7 @@ impl fmt::Display for RpcRequest {
RpcRequest::GetFeeCalculatorForBlockhash => "getFeeCalculatorForBlockhash",
RpcRequest::GetFeeRateGovernor => "getFeeRateGovernor",
RpcRequest::GetFees => "getFees",
RpcRequest::GetFirstAvailableBlock => "getFirstAvailableBlock",
RpcRequest::GetGenesisHash => "getGenesisHash",
RpcRequest::GetIdentity => "getIdentity",
RpcRequest::GetInflationGovernor => "getInflationGovernor",
@@ -116,7 +120,7 @@ pub const MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS: usize = 256;
pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE: u64 = 10_000;
pub const MAX_GET_CONFIRMED_BLOCKS_RANGE: u64 = 500_000;
pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT: usize = 1_000;
pub const MAX_MULTIPLE_ACCOUNTS: usize = 20;
pub const MAX_MULTIPLE_ACCOUNTS: usize = 100;
pub const NUM_LARGEST_ACCOUNTS: usize = 20;
// Validators that are this number of slots behind are considered delinquent

View File

@@ -7,7 +7,7 @@ use solana_sdk::{
transaction::{Result, TransactionError},
};
use solana_transaction_status::ConfirmedTransactionStatusWithSignature;
use std::{collections::HashMap, net::SocketAddr};
use std::{collections::HashMap, fmt, net::SocketAddr};
pub type RpcResult<T> = client_error::Result<Response<T>>;
@@ -94,11 +94,18 @@ pub struct RpcKeyedAccount {
pub account: UiAccount,
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
pub struct SlotInfo {
pub slot: Slot,
pub parent: Slot,
pub root: Slot,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase", tag = "type", content = "result")]
#[serde(rename_all = "camelCase", untagged)]
pub enum RpcSignatureResult {
ProcessedSignatureResult(ProcessedSignatureResult),
ReceivedSignature,
ProcessedSignature(ProcessedSignatureResult),
ReceivedSignature(ReceivedSignatureResult),
}
#[derive(Serialize, Deserialize, Clone, Debug)]
@@ -108,6 +115,13 @@ pub struct ProcessedSignatureResult {
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub enum ReceivedSignatureResult {
ReceivedSignature,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcContactInfo {
/// Pubkey of the node as a base-58 string
pub pubkey: String,
@@ -119,16 +133,37 @@ pub struct RpcContactInfo {
pub rpc: Option<SocketAddr>,
/// Software version
pub version: Option<String>,
/// First 4 bytes of the FeatureSet identifier
pub feature_set: Option<u32>,
}
/// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot
pub type RpcLeaderSchedule = HashMap<String, Vec<usize>>;
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct RpcVersionInfo {
/// The current version of solana-core
pub solana_core: String,
/// first 4 bytes of the FeatureSet identifier
pub feature_set: Option<u32>,
}
impl fmt::Debug for RpcVersionInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.solana_core)
}
}
impl fmt::Display for RpcVersionInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(version) = self.solana_core.split_whitespace().next() {
// Display just the semver if possible
write!(f, "{}", version)
} else {
write!(f, "{}", self.solana_core)
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
@@ -245,6 +280,15 @@ pub struct RpcConfirmedTransactionStatusWithSignature {
pub memo: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcPerfSample {
pub slot: Slot,
pub num_transactions: u64,
pub num_slots: u64,
pub sample_period_secs: u16,
}
impl From<ConfirmedTransactionStatusWithSignature> for RpcConfirmedTransactionStatusWithSignature {
fn from(value: ConfirmedTransactionStatusWithSignature) -> Self {
let ConfirmedTransactionStatusWithSignature {

View File

@@ -1,7 +1,7 @@
[package]
name = "solana-core"
description = "Blockchain, Rebuilt for Scale"
version = "1.3.8"
version = "1.3.15"
documentation = "https://docs.rs/solana"
homepage = "https://solana.com/"
readme = "../README.md"
@@ -24,14 +24,14 @@ crossbeam-channel = "0.4"
ed25519-dalek = "=1.0.0-pre.4"
fs_extra = "1.1.0"
flate2 = "1.0"
indexmap = "1.4"
indexmap = { version = "1.5", features = ["rayon"] }
itertools = "0.9.0"
jsonrpc-core = "14.2.0"
jsonrpc-core-client = { version = "14.2.0", features = ["ws"] }
jsonrpc-derive = "14.2.1"
jsonrpc-http-server = "14.2.0"
jsonrpc-pubsub = "14.2.0"
jsonrpc-ws-server = "14.2.0"
jsonrpc-core = "15.0.0"
jsonrpc-core-client = { version = "15.0.0", features = ["ws"] }
jsonrpc-derive = "15.0.0"
jsonrpc-http-server = "15.0.0"
jsonrpc-pubsub = "15.0.0"
jsonrpc-ws-server = "15.0.0"
log = "0.4.8"
num_cpus = "1.13.0"
num-traits = "0.2"
@@ -43,39 +43,38 @@ regex = "1.3.9"
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.56"
solana-account-decoder = { path = "../account-decoder", version = "1.3.8" }
solana-banks-server = { path = "../banks-server", version = "1.3.8" }
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.3.8" }
solana-budget-program = { path = "../programs/budget", version = "1.3.8" }
solana-clap-utils = { path = "../clap-utils", version = "1.3.8" }
solana-client = { path = "../client", version = "1.3.8" }
solana-faucet = { path = "../faucet", version = "1.3.8" }
solana-genesis-programs = { path = "../genesis-programs", version = "1.3.8" }
solana-ledger = { path = "../ledger", version = "1.3.8" }
solana-logger = { path = "../logger", version = "1.3.8" }
solana-merkle-tree = { path = "../merkle-tree", version = "1.3.8" }
solana-metrics = { path = "../metrics", version = "1.3.8" }
solana-measure = { path = "../measure", version = "1.3.8" }
solana-net-utils = { path = "../net-utils", version = "1.3.8" }
solana-perf = { path = "../perf", version = "1.3.8" }
solana-runtime = { path = "../runtime", version = "1.3.8" }
solana-sdk = { path = "../sdk", version = "1.3.8" }
solana-sdk-macro-frozen-abi = { path = "../sdk/macro-frozen-abi", version = "1.3.8" }
solana-stake-program = { path = "../programs/stake", version = "1.3.8" }
solana-storage-bigtable = { path = "../storage-bigtable", version = "1.3.8" }
solana-streamer = { path = "../streamer", version = "1.3.8" }
solana-sys-tuner = { path = "../sys-tuner", version = "1.3.8" }
solana-transaction-status = { path = "../transaction-status", version = "1.3.8" }
solana-version = { path = "../version", version = "1.3.8" }
solana-vote-program = { path = "../programs/vote", version = "1.3.8" }
solana-vote-signer = { path = "../vote-signer", version = "1.3.8" }
spl-token-v2-0 = { package = "spl-token", version = "2.0.3", features = ["skip-no-mangle"] }
solana-account-decoder = { path = "../account-decoder", version = "1.3.15" }
solana-banks-server = { path = "../banks-server", version = "1.3.15" }
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.3.15" }
solana-budget-program = { path = "../programs/budget", version = "1.3.15" }
solana-clap-utils = { path = "../clap-utils", version = "1.3.15" }
solana-client = { path = "../client", version = "1.3.15" }
solana-faucet = { path = "../faucet", version = "1.3.15" }
solana-ledger = { path = "../ledger", version = "1.3.15" }
solana-logger = { path = "../logger", version = "1.3.15" }
solana-merkle-tree = { path = "../merkle-tree", version = "1.3.15" }
solana-metrics = { path = "../metrics", version = "1.3.15" }
solana-measure = { path = "../measure", version = "1.3.15" }
solana-net-utils = { path = "../net-utils", version = "1.3.15" }
solana-perf = { path = "../perf", version = "1.3.15" }
solana-runtime = { path = "../runtime", version = "1.3.15" }
solana-sdk = { path = "../sdk", version = "1.3.15" }
solana-sdk-macro-frozen-abi = { path = "../sdk/macro-frozen-abi", version = "1.3.15" }
solana-stake-program = { path = "../programs/stake", version = "1.3.15" }
solana-storage-bigtable = { path = "../storage-bigtable", version = "1.3.15" }
solana-streamer = { path = "../streamer", version = "1.3.15" }
solana-sys-tuner = { path = "../sys-tuner", version = "1.3.15" }
solana-transaction-status = { path = "../transaction-status", version = "1.3.15" }
solana-version = { path = "../version", version = "1.3.15" }
solana-vote-program = { path = "../programs/vote", version = "1.3.15" }
solana-vote-signer = { path = "../vote-signer", version = "1.3.15" }
spl-token-v2-0 = { package = "spl-token", version = "2.0.6", features = ["skip-no-mangle"] }
tempfile = "3.1.0"
thiserror = "1.0"
tokio_01 = { version = "0.1", package = "tokio" }
tokio_fs_01 = { version = "0.1", package = "tokio-fs" }
tokio_io_01 = { version = "0.1", package = "tokio-io" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.3.8" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.3.15" }
tokio = { version = "0.2.22", features = ["full"] }
trees = "0.2.1"
@@ -96,6 +95,12 @@ name = "banking_stage"
[[bench]]
name = "blockstore"
[[bench]]
name = "crds_gossip_pull"
[[bench]]
name = "crds_shards"
[[bench]]
name = "gen_keys"

View File

@@ -0,0 +1,53 @@
#![feature(test)]
extern crate test;
use rand::{thread_rng, Rng};
use rayon::ThreadPoolBuilder;
use solana_core::cluster_info::MAX_BLOOM_SIZE;
use solana_core::crds::Crds;
use solana_core::crds_gossip_pull::{CrdsFilter, CrdsGossipPull};
use solana_core::crds_value::CrdsValue;
use solana_sdk::hash::Hash;
use test::Bencher;
#[bench]
fn bench_hash_as_u64(bencher: &mut Bencher) {
let mut rng = thread_rng();
let hashes: Vec<_> = std::iter::repeat_with(|| Hash::new_rand(&mut rng))
.take(1000)
.collect();
bencher.iter(|| {
hashes
.iter()
.map(CrdsFilter::hash_as_u64)
.collect::<Vec<_>>()
});
}
#[bench]
fn bench_build_crds_filters(bencher: &mut Bencher) {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
let mut rng = thread_rng();
let mut crds_gossip_pull = CrdsGossipPull::default();
let mut crds = Crds::default();
for _ in 0..50_000 {
crds_gossip_pull
.purged_values
.push_back((Hash::new_rand(&mut rng), rng.gen()));
}
let mut num_inserts = 0;
for _ in 0..90_000 {
if crds
.insert(CrdsValue::new_rand(&mut rng), rng.gen())
.is_ok()
{
num_inserts += 1;
}
}
assert_eq!(num_inserts, 90_000);
bencher.iter(|| {
let filters = crds_gossip_pull.build_crds_filters(&thread_pool, &crds, MAX_BLOOM_SIZE);
assert_eq!(filters.len(), 128);
});
}

View File

@@ -0,0 +1,69 @@
#![feature(test)]
extern crate test;
use rand::{thread_rng, Rng};
use solana_core::contact_info::ContactInfo;
use solana_core::crds::VersionedCrdsValue;
use solana_core::crds_shards::CrdsShards;
use solana_core::crds_value::{CrdsData, CrdsValue};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::timing::timestamp;
use test::Bencher;
const CRDS_SHARDS_BITS: u32 = 8;
fn new_test_crds_value() -> VersionedCrdsValue {
let data = CrdsData::ContactInfo(ContactInfo::new_localhost(&Pubkey::new_rand(), timestamp()));
VersionedCrdsValue::new(timestamp(), CrdsValue::new_unsigned(data))
}
fn bench_crds_shards_find(bencher: &mut Bencher, num_values: usize, mask_bits: u32) {
let values: Vec<VersionedCrdsValue> = std::iter::repeat_with(new_test_crds_value)
.take(num_values)
.collect();
let mut shards = CrdsShards::new(CRDS_SHARDS_BITS);
for (index, value) in values.iter().enumerate() {
assert!(shards.insert(index, value));
}
let mut rng = thread_rng();
bencher.iter(|| {
let mask = rng.gen();
let _hits = shards.find(mask, mask_bits).count();
});
}
#[bench]
fn bench_crds_shards_find_0(bencher: &mut Bencher) {
bench_crds_shards_find(bencher, 100_000, 0);
}
#[bench]
fn bench_crds_shards_find_1(bencher: &mut Bencher) {
bench_crds_shards_find(bencher, 100_000, 1);
}
#[bench]
fn bench_crds_shards_find_3(bencher: &mut Bencher) {
bench_crds_shards_find(bencher, 100_000, 3);
}
#[bench]
fn bench_crds_shards_find_5(bencher: &mut Bencher) {
bench_crds_shards_find(bencher, 100_000, 5);
}
#[bench]
fn bench_crds_shards_find_7(bencher: &mut Bencher) {
bench_crds_shards_find(bencher, 100_000, 7);
}
#[bench]
fn bench_crds_shards_find_8(bencher: &mut Bencher) {
bench_crds_shards_find(bencher, 100_000, 8);
}
#[bench]
fn bench_crds_shards_find_9(bencher: &mut Bencher) {
bench_crds_shards_find(bencher, 100_000, 9);
}

View File

@@ -530,8 +530,19 @@ impl BankingStage {
} else {
vec![]
};
let (mut loaded_accounts, results, mut retryable_txs, tx_count, signature_count) =
bank.load_and_execute_transactions(batch, MAX_PROCESSING_AGE, None);
let (
mut loaded_accounts,
results,
inner_instructions,
mut retryable_txs,
tx_count,
signature_count,
) = bank.load_and_execute_transactions(
batch,
MAX_PROCESSING_AGE,
None,
transaction_status_sender.is_some(),
);
load_execute_time.stop();
let freeze_lock = bank.freeze_lock();
@@ -568,6 +579,7 @@ impl BankingStage {
batch.iteration_order_vec(),
tx_results.processing_results,
TransactionBalancesSet::new(pre_balances, post_balances),
inner_instructions,
sender,
);
}
@@ -724,6 +736,7 @@ impl BankingStage {
fn transactions_from_packets(
msgs: &Packets,
transaction_indexes: &[usize],
secp256k1_program_enabled: bool,
) -> (Vec<Transaction>, Vec<usize>) {
let packets = Packets::new(
transaction_indexes
@@ -733,8 +746,26 @@ impl BankingStage {
);
let transactions = Self::deserialize_transactions(&packets);
let maybe_secp_verified_transactions: Vec<_> = if secp256k1_program_enabled {
transactions
.into_iter()
.map(|tx| {
if let Some(tx) = tx {
if tx.verify_precompiles().is_ok() {
Some(tx)
} else {
None
}
} else {
None
}
})
.collect()
} else {
transactions
};
Self::filter_transaction_indexes(transactions, &transaction_indexes)
Self::filter_transaction_indexes(maybe_secp_verified_transactions, &transaction_indexes)
}
/// This function filters pending packets that are still valid
@@ -783,8 +814,11 @@ impl BankingStage {
transaction_status_sender: Option<TransactionStatusSender>,
gossip_vote_sender: &ReplayVoteSender,
) -> (usize, usize, Vec<usize>) {
let (transactions, transaction_to_packet_indexes) =
Self::transactions_from_packets(msgs, &packet_indexes);
let (transactions, transaction_to_packet_indexes) = Self::transactions_from_packets(
msgs,
&packet_indexes,
bank.secp256k1_program_enabled(),
);
debug!(
"bank: {} filtered transactions {}",
bank.slot(),
@@ -833,8 +867,11 @@ impl BankingStage {
}
}
let (transactions, transaction_to_packet_indexes) =
Self::transactions_from_packets(msgs, &transaction_indexes);
let (transactions, transaction_to_packet_indexes) = Self::transactions_from_packets(
msgs,
&transaction_indexes,
bank.secp256k1_program_enabled(),
);
let tx_count = transaction_to_packet_indexes.len();
@@ -1061,7 +1098,7 @@ mod tests {
system_transaction,
transaction::TransactionError,
};
use solana_transaction_status::{EncodedTransaction, TransactionWithStatusMeta};
use solana_transaction_status::TransactionWithStatusMeta;
use std::{sync::atomic::Ordering, thread::sleep};
#[test]
@@ -2007,36 +2044,25 @@ mod tests {
transaction_status_service.join().unwrap();
let confirmed_block = blockstore.get_confirmed_block(bank.slot(), None).unwrap();
let confirmed_block = blockstore.get_confirmed_block(bank.slot()).unwrap();
assert_eq!(confirmed_block.transactions.len(), 3);
for TransactionWithStatusMeta { transaction, meta } in
confirmed_block.transactions.into_iter()
{
if let EncodedTransaction::Json(transaction) = transaction {
if transaction.signatures[0] == success_signature.to_string() {
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.err,
Some(TransactionError::InstructionError(
0,
InstructionError::Custom(1)
))
);
assert_eq!(
meta.status,
Err(TransactionError::InstructionError(
0,
InstructionError::Custom(1)
))
);
} else {
assert_eq!(meta, None);
}
if transaction.signatures[0] == success_signature {
assert_eq!(meta.unwrap().status, Ok(()));
} else if transaction.signatures[0] == ix_error_signature {
let meta = meta.unwrap();
assert_eq!(
meta.status,
Err(TransactionError::InstructionError(
0,
InstructionError::Custom(1)
))
);
} else {
assert_eq!(meta, None);
}
}
}

View File

@@ -0,0 +1,93 @@
use solana_ledger::blockstore::Blockstore;
use solana_runtime::commitment::BlockCommitmentCache;
use std::{
sync::atomic::{AtomicBool, Ordering},
sync::{Arc, RwLock},
thread::{self, Builder, JoinHandle},
};
use tokio::runtime;
// Delay uploading the largest confirmed root for this many slots. This is done in an attempt to
// ensure that the `CacheBlockTimeService` has had enough time to add the block time for the root
// before it's uploaded to BigTable.
//
// A more direct connection between CacheBlockTimeService and BigTableUploadService would be
// preferable...
const LARGEST_CONFIRMED_ROOT_UPLOAD_DELAY: usize = 100;
pub struct BigTableUploadService {
thread: JoinHandle<()>,
}
impl BigTableUploadService {
pub fn new(
runtime_handle: runtime::Handle,
bigtable_ledger_storage: solana_storage_bigtable::LedgerStorage,
blockstore: Arc<Blockstore>,
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
exit: Arc<AtomicBool>,
) -> Self {
info!("Starting BigTable upload service");
let thread = Builder::new()
.name("bigtable-upload".to_string())
.spawn(move || {
Self::run(
runtime_handle,
bigtable_ledger_storage,
blockstore,
block_commitment_cache,
exit,
)
})
.unwrap();
Self { thread }
}
fn run(
runtime: runtime::Handle,
bigtable_ledger_storage: solana_storage_bigtable::LedgerStorage,
blockstore: Arc<Blockstore>,
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
exit: Arc<AtomicBool>,
) {
let mut start_slot = 0;
loop {
if exit.load(Ordering::Relaxed) {
break;
}
let end_slot = block_commitment_cache
.read()
.unwrap()
.highest_confirmed_root()
.saturating_sub(LARGEST_CONFIRMED_ROOT_UPLOAD_DELAY as u64);
if end_slot <= start_slot {
std::thread::sleep(std::time::Duration::from_secs(1));
continue;
}
let result = runtime.block_on(solana_ledger::bigtable_upload::upload_confirmed_blocks(
blockstore.clone(),
bigtable_ledger_storage.clone(),
start_slot,
Some(end_slot),
true,
exit.clone(),
));
match result {
Ok(()) => start_slot = end_slot,
Err(err) => {
warn!("bigtable: upload_confirmed_blocks: {}", err);
std::thread::sleep(std::time::Duration::from_secs(2));
}
}
}
}
pub fn join(self) -> thread::Result<()> {
self.thread.join()
}
}

44
core/src/builtins.rs Normal file
View File

@@ -0,0 +1,44 @@
use solana_runtime::{
bank::{Builtin, Builtins, Entrypoint},
feature_set,
};
use solana_sdk::{genesis_config::ClusterType, pubkey::Pubkey};
/// Builtin programs that are always available
fn genesis_builtins(cluster_type: ClusterType) -> Vec<Builtin> {
let builtins = if cluster_type != ClusterType::MainnetBeta {
vec![
solana_bpf_loader_deprecated_program!(),
solana_bpf_loader_program!(),
]
} else {
// Remove this `else` block and the `cluster_type` argument to this function once
// `feature_set::bpf_loader2_program::id()` is active on Mainnet Beta
vec![solana_bpf_loader_deprecated_program!()]
};
builtins
.into_iter()
.map(|b| Builtin::new(&b.0, b.1, Entrypoint::Loader(b.2)))
.collect()
}
/// Builtin programs activated dynamically by feature
fn feature_builtins() -> Vec<(Builtin, Pubkey)> {
let builtins = vec![(
solana_bpf_loader_program!(),
feature_set::bpf_loader2_program::id(),
)];
builtins
.into_iter()
.map(|(b, p)| (Builtin::new(&b.0, b.1, Entrypoint::Loader(b.2)), p))
.collect()
}
pub(crate) fn get(cluster_type: ClusterType) -> Builtins {
Builtins {
genesis_builtins: genesis_builtins(cluster_type),
feature_builtins: feature_builtins(),
}
}

View File

@@ -0,0 +1,76 @@
use crossbeam_channel::{Receiver, RecvTimeoutError, Sender};
use solana_ledger::blockstore::Blockstore;
use solana_measure::measure::Measure;
use solana_runtime::bank::Bank;
use solana_sdk::timing::slot_duration_from_slots_per_year;
use std::{
collections::HashMap,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
thread::{self, Builder, JoinHandle},
time::Duration,
};
pub type CacheBlockTimeReceiver = Receiver<Arc<Bank>>;
pub type CacheBlockTimeSender = Sender<Arc<Bank>>;
pub struct CacheBlockTimeService {
thread_hdl: JoinHandle<()>,
}
const CACHE_BLOCK_TIME_WARNING_MS: u64 = 150;
impl CacheBlockTimeService {
#[allow(clippy::new_ret_no_self)]
pub fn new(
cache_block_time_receiver: CacheBlockTimeReceiver,
blockstore: Arc<Blockstore>,
exit: &Arc<AtomicBool>,
) -> Self {
let exit = exit.clone();
let thread_hdl = Builder::new()
.name("solana-cache-block-time".to_string())
.spawn(move || loop {
if exit.load(Ordering::Relaxed) {
break;
}
let recv_result = cache_block_time_receiver.recv_timeout(Duration::from_secs(1));
match recv_result {
Err(RecvTimeoutError::Disconnected) => {
break;
}
Ok(bank) => {
let mut cache_block_time_timer = Measure::start("cache_block_time_timer");
Self::cache_block_time(bank, &blockstore);
cache_block_time_timer.stop();
if cache_block_time_timer.as_ms() > CACHE_BLOCK_TIME_WARNING_MS {
warn!(
"cache_block_time operation took: {}ms",
cache_block_time_timer.as_ms()
);
}
}
_ => {}
}
})
.unwrap();
Self { thread_hdl }
}
fn cache_block_time(bank: Arc<Bank>, blockstore: &Arc<Blockstore>) {
let slot_duration = slot_duration_from_slots_per_year(bank.slots_per_year());
let epoch = bank.epoch_schedule().get_epoch(bank.slot());
let stakes = HashMap::new();
let stakes = bank.epoch_vote_accounts(epoch).unwrap_or(&stakes);
if let Err(e) = blockstore.cache_block_time(bank.slot(), slot_duration, stakes) {
error!("cache_block_time failed: slot {:?} {:?}", bank.slot(), e);
}
}
pub fn join(self) -> thread::Result<()> {
self.thread_hdl.join()
}
}

View File

@@ -36,7 +36,7 @@ use core::cmp;
use itertools::Itertools;
use rayon::iter::IntoParallelIterator;
use rayon::iter::ParallelIterator;
use rayon::ThreadPool;
use rayon::{ThreadPool, ThreadPoolBuilder};
use solana_ledger::staking_utils;
use solana_measure::measure::Measure;
use solana_measure::thread_mem_usage;
@@ -358,7 +358,7 @@ pub fn make_accounts_hashes_message(
}
// TODO These messages should go through the gpu pipeline for spam filtering
#[frozen_abi(digest = "CnN1gW2K2TRydGc84eYnQJwdTADPjQf6LJLZ4RP1QeoH")]
#[frozen_abi(digest = "3ZHQscZ9SgxKh45idzHv3hiagyyPRtDgeySmJn171PTi")]
#[derive(Serialize, Deserialize, Debug, AbiEnumVisitor, AbiExample)]
#[allow(clippy::large_enum_variant)]
enum Protocol {
@@ -421,7 +421,7 @@ impl ClusterInfo {
gossip.set_shred_version(me.my_shred_version());
}
me.insert_self();
me.push_self(&HashMap::new());
me.push_self(&HashMap::new(), None);
me
}
@@ -453,13 +453,17 @@ impl ClusterInfo {
self.insert_self()
}
fn push_self(&self, stakes: &HashMap<Pubkey, u64>) {
fn push_self(
&self,
stakes: &HashMap<Pubkey, u64>,
gossip_validators: Option<&HashSet<Pubkey>>,
) {
let now = timestamp();
self.my_contact_info.write().unwrap().wallclock = now;
let entry =
CrdsValue::new_signed(CrdsData::ContactInfo(self.my_contact_info()), &self.keypair);
let mut w_gossip = self.gossip.write().unwrap();
w_gossip.refresh_push_active_set(stakes);
w_gossip.refresh_push_active_set(stakes, gossip_validators);
w_gossip.process_push_message(&self.id(), vec![entry], now);
}
@@ -558,7 +562,7 @@ impl ClusterInfo {
}
let ip_addr = node.gossip.ip();
Some(format!(
"{:15} {:2}| {:5} | {:44} |{:^15}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {}\n",
"{:15} {:2}| {:5} | {:44} |{:^9}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {}\n",
if ContactInfo::is_valid_address(&node.gossip) {
ip_addr.to_string()
} else {
@@ -590,8 +594,8 @@ impl ClusterInfo {
format!(
"IP Address |Age(ms)| Node identifier \
| Version |Gossip| TPU |TPUfwd| TVU |TVUfwd|Repair|ServeR| RPC |PubSub|ShredVer\n\
------------------+-------+----------------------------------------------+---------------+\
| Version |Gossip| TPU |TPUfwd| TVU |TVUfwd|Repair|ServeR| RPC |PubSub|ShredVer\n\
------------------+-------+----------------------------------------------+---------+\
------+------+------+------+------+------+------+------+------+--------\n\
{}\
Nodes: {}{}{}",
@@ -879,7 +883,8 @@ impl ClusterInfo {
}
pub fn get_node_version(&self, pubkey: &Pubkey) -> Option<solana_version::Version> {
self.gossip
let version = self
.gossip
.read()
.unwrap()
.crds
@@ -887,7 +892,21 @@ impl ClusterInfo {
.get(&CrdsValueLabel::Version(*pubkey))
.map(|x| x.value.version())
.flatten()
.map(|version| version.version.clone())
.map(|version| version.version.clone());
if version.is_none() {
self.gossip
.read()
.unwrap()
.crds
.table
.get(&CrdsValueLabel::LegacyVersion(*pubkey))
.map(|x| x.value.legacy_version())
.flatten()
.map(|version| version.version.clone().into())
} else {
version
}
}
/// all validators that have a valid rpc port regardless of `shred_version`.
@@ -1266,6 +1285,7 @@ impl ClusterInfo {
// If the network entrypoint hasn't been discovered yet, add it to the crds table
fn append_entrypoint_to_pulls(
&self,
thread_pool: &ThreadPool,
pulls: &mut Vec<(Pubkey, CrdsFilter, SocketAddr, CrdsValue)>,
) {
let pull_from_entrypoint = {
@@ -1316,7 +1336,7 @@ impl ClusterInfo {
.unwrap_or_else(|| panic!("self_id invalid {}", self.id()));
r_gossip
.pull
.build_crds_filters(&r_gossip.crds, MAX_BLOOM_SIZE)
.build_crds_filters(thread_pool, &r_gossip.crds, MAX_BLOOM_SIZE)
.into_iter()
.for_each(|filter| pulls.push((id, filter, gossip, self_info.clone())));
}
@@ -1363,13 +1383,18 @@ impl ClusterInfo {
messages
}
fn new_pull_requests(&self, stakes: &HashMap<Pubkey, u64>) -> Vec<(SocketAddr, Protocol)> {
fn new_pull_requests(
&self,
thread_pool: &ThreadPool,
gossip_validators: Option<&HashSet<Pubkey>>,
stakes: &HashMap<Pubkey, u64>,
) -> Vec<(SocketAddr, Protocol)> {
let now = timestamp();
let mut pulls: Vec<_> = {
let r_gossip =
self.time_gossip_read_lock("new_pull_reqs", &self.stats.new_pull_requests);
r_gossip
.new_pull_request(now, stakes, MAX_BLOOM_SIZE)
.new_pull_request(thread_pool, now, gossip_validators, stakes, MAX_BLOOM_SIZE)
.ok()
.into_iter()
.filter_map(|(peer, filters, me)| {
@@ -1387,7 +1412,7 @@ impl ClusterInfo {
.flatten()
.collect()
};
self.append_entrypoint_to_pulls(&mut pulls);
self.append_entrypoint_to_pulls(thread_pool, &mut pulls);
self.stats
.new_pull_requests_count
.add_relaxed(pulls.len() as u64);
@@ -1430,27 +1455,38 @@ impl ClusterInfo {
// Generate new push and pull requests
fn generate_new_gossip_requests(
&self,
thread_pool: &ThreadPool,
gossip_validators: Option<&HashSet<Pubkey>>,
stakes: &HashMap<Pubkey, u64>,
generate_pull_requests: bool,
) -> Vec<(SocketAddr, Protocol)> {
let pulls: Vec<_> = if generate_pull_requests {
self.new_pull_requests(stakes)
let mut pulls: Vec<_> = if generate_pull_requests {
self.new_pull_requests(&thread_pool, gossip_validators, stakes)
} else {
vec![]
};
let pushes: Vec<_> = self.new_push_requests();
vec![pulls, pushes].into_iter().flatten().collect()
let mut pushes: Vec<_> = self.new_push_requests();
pulls.append(&mut pushes);
pulls
}
/// At random pick a node and try to get updated changes from them
fn run_gossip(
&self,
thread_pool: &ThreadPool,
gossip_validators: Option<&HashSet<Pubkey>>,
recycler: &PacketsRecycler,
stakes: &HashMap<Pubkey, u64>,
sender: &PacketSender,
generate_pull_requests: bool,
) -> Result<()> {
let reqs = self.generate_new_gossip_requests(&stakes, generate_pull_requests);
let reqs = self.generate_new_gossip_requests(
thread_pool,
gossip_validators,
&stakes,
generate_pull_requests,
);
if !reqs.is_empty() {
let packets = to_packets_with_destination(recycler.clone(), &reqs);
sender.send(packets)?;
@@ -1519,9 +1555,15 @@ impl ClusterInfo {
self: Arc<Self>,
bank_forks: Option<Arc<RwLock<BankForks>>>,
sender: PacketSender,
gossip_validators: Option<HashSet<Pubkey>>,
exit: &Arc<AtomicBool>,
) -> JoinHandle<()> {
let exit = exit.clone();
let thread_pool = ThreadPoolBuilder::new()
.num_threads(std::cmp::min(get_thread_count(), 8))
.thread_name(|i| format!("ClusterInfo::gossip-{}", i))
.build()
.unwrap();
Builder::new()
.name("solana-gossip".to_string())
.spawn(move || {
@@ -1549,7 +1591,14 @@ impl ClusterInfo {
None => HashMap::new(),
};
let _ = self.run_gossip(&recycler, &stakes, &sender, generate_pull_requests);
let _ = self.run_gossip(
&thread_pool,
gossip_validators.as_ref(),
&recycler,
&stakes,
&sender,
generate_pull_requests,
);
if exit.load(Ordering::Relaxed) {
return;
}
@@ -1561,7 +1610,7 @@ impl ClusterInfo {
//TODO: possibly tune this parameter
//we saw a deadlock passing an self.read().unwrap().timeout into sleep
if start - last_push > CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS / 2 {
self.push_self(&stakes);
self.push_self(&stakes, gossip_validators.as_ref());
last_push = timestamp();
}
let elapsed = timestamp() - start;
@@ -1884,16 +1933,20 @@ impl ClusterInfo {
let filtered_len = crds_values.len();
let mut pull_stats = ProcessPullStats::default();
let (filtered_pulls, filtered_pulls_expired_timeout) = self
let (filtered_pulls, filtered_pulls_expired_timeout, failed_inserts) = self
.time_gossip_read_lock("filter_pull_resp", &self.stats.filter_pull_response)
.filter_pull_responses(timeouts, crds_values, timestamp(), &mut pull_stats);
if !filtered_pulls.is_empty() || !filtered_pulls_expired_timeout.is_empty() {
if !filtered_pulls.is_empty()
|| !filtered_pulls_expired_timeout.is_empty()
|| !failed_inserts.is_empty()
{
self.time_gossip_write_lock("process_pull_resp", &self.stats.process_pull_response)
.process_pull_responses(
from,
filtered_pulls,
filtered_pulls_expired_timeout,
failed_inserts,
timestamp(),
&mut pull_stats,
);
@@ -2107,9 +2160,13 @@ impl ClusterInfo {
fn print_reset_stats(&self, last_print: &mut Instant) {
if last_print.elapsed().as_millis() > 2000 {
let (table_size, purged_values_size) = {
let (table_size, purged_values_size, failed_inserts_size) = {
let r_gossip = self.gossip.read().unwrap();
(r_gossip.crds.table.len(), r_gossip.pull.purged_values.len())
(
r_gossip.crds.table.len(),
r_gossip.pull.purged_values.len(),
r_gossip.pull.failed_inserts.len(),
)
};
datapoint_info!(
"cluster_info_stats",
@@ -2136,6 +2193,7 @@ impl ClusterInfo {
),
("table_size", table_size as i64, i64),
("purged_values_size", purged_values_size as i64, i64),
("failed_inserts_size", failed_inserts_size as i64, i64),
);
datapoint_info!(
"cluster_info_stats2",
@@ -2307,7 +2365,7 @@ impl ClusterInfo {
Builder::new()
.name("solana-listen".to_string())
.spawn(move || {
let thread_pool = rayon::ThreadPoolBuilder::new()
let thread_pool = ThreadPoolBuilder::new()
.num_threads(std::cmp::min(get_thread_count(), 8))
.thread_name(|i| format!("sol-gossip-work-{}", i))
.build()
@@ -2694,6 +2752,7 @@ mod tests {
#[test]
fn test_cluster_spy_gossip() {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
//check that gossip doesn't try to push to invalid addresses
let node = Node::new_localhost();
let (spy, _, _) = ClusterInfo::spy_node(&Pubkey::new_rand(), 0);
@@ -2703,8 +2762,9 @@ mod tests {
.gossip
.write()
.unwrap()
.refresh_push_active_set(&HashMap::new());
let reqs = cluster_info.generate_new_gossip_requests(&HashMap::new(), true);
.refresh_push_active_set(&HashMap::new(), None);
let reqs =
cluster_info.generate_new_gossip_requests(&thread_pool, None, &HashMap::new(), true);
//assert none of the addrs are invalid.
reqs.iter().all(|(addr, _)| {
let res = ContactInfo::is_valid_address(addr);
@@ -2831,6 +2891,7 @@ mod tests {
//when constructed with keypairs
#[test]
fn test_gossip_signature_verification() {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
//create new cluster info, leader, and peer
let keypair = Keypair::new();
let peer_keypair = Keypair::new();
@@ -2842,7 +2903,7 @@ mod tests {
.gossip
.write()
.unwrap()
.refresh_push_active_set(&HashMap::new());
.refresh_push_active_set(&HashMap::new(), None);
//check that all types of gossip messages are signed correctly
let (_, push_messages) = cluster_info
.gossip
@@ -2859,7 +2920,13 @@ mod tests {
.gossip
.write()
.unwrap()
.new_pull_request(timestamp(), &HashMap::new(), MAX_BLOOM_SIZE)
.new_pull_request(
&thread_pool,
timestamp(),
None,
&HashMap::new(),
MAX_BLOOM_SIZE,
)
.ok()
.unwrap();
assert!(val.verify());
@@ -3070,6 +3137,7 @@ mod tests {
#[test]
fn test_append_entrypoint_to_pulls() {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
let node_keypair = Arc::new(Keypair::new());
let cluster_info = ClusterInfo::new(
ContactInfo::new_localhost(&node_keypair.pubkey(), timestamp()),
@@ -3078,7 +3146,7 @@ mod tests {
let entrypoint_pubkey = Pubkey::new_rand();
let entrypoint = ContactInfo::new_localhost(&entrypoint_pubkey, timestamp());
cluster_info.set_entrypoint(entrypoint.clone());
let pulls = cluster_info.new_pull_requests(&HashMap::new());
let pulls = cluster_info.new_pull_requests(&thread_pool, None, &HashMap::new());
assert_eq!(1, pulls.len() as u64);
match pulls.get(0) {
Some((addr, msg)) => {
@@ -3105,7 +3173,7 @@ mod tests {
vec![entrypoint_crdsvalue],
&timeouts,
);
let pulls = cluster_info.new_pull_requests(&HashMap::new());
let pulls = cluster_info.new_pull_requests(&thread_pool, None, &HashMap::new());
assert_eq!(1, pulls.len() as u64);
assert_eq!(*cluster_info.entrypoint.read().unwrap(), Some(entrypoint));
}
@@ -3228,6 +3296,7 @@ mod tests {
#[test]
fn test_pull_from_entrypoint_if_not_present() {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
let node_keypair = Arc::new(Keypair::new());
let cluster_info = ClusterInfo::new(
ContactInfo::new_localhost(&node_keypair.pubkey(), timestamp()),
@@ -3248,7 +3317,7 @@ mod tests {
// Pull request 1: `other_node` is present but `entrypoint` was just added (so it has a
// fresh timestamp). There should only be one pull request to `other_node`
let pulls = cluster_info.new_pull_requests(&stakes);
let pulls = cluster_info.new_pull_requests(&thread_pool, None, &stakes);
assert_eq!(1, pulls.len() as u64);
assert_eq!(pulls.get(0).unwrap().0, other_node.gossip);
@@ -3261,14 +3330,14 @@ mod tests {
.as_mut()
.unwrap()
.wallclock = 0;
let pulls = cluster_info.new_pull_requests(&stakes);
let pulls = cluster_info.new_pull_requests(&thread_pool, None, &stakes);
assert_eq!(2, pulls.len() as u64);
assert_eq!(pulls.get(0).unwrap().0, other_node.gossip);
assert_eq!(pulls.get(1).unwrap().0, entrypoint.gossip);
// Pull request 3: `other_node` is present and `entrypoint` was just pulled from. There should
// only be one pull request to `other_node`
let pulls = cluster_info.new_pull_requests(&stakes);
let pulls = cluster_info.new_pull_requests(&thread_pool, None, &stakes);
assert_eq!(1, pulls.len() as u64);
assert_eq!(pulls.get(0).unwrap().0, other_node.gossip);
}

View File

@@ -2,6 +2,7 @@ use crate::{
cluster_info::{ClusterInfo, GOSSIP_SLEEP_MILLIS},
crds_value::CrdsValueLabel,
optimistic_confirmation_verifier::OptimisticConfirmationVerifier,
optimistically_confirmed_bank_tracker::{BankNotification, BankNotificationSender},
poh_recorder::PohRecorder,
pubkey_references::LockedPubkeyReferences,
result::{Error, Result},
@@ -248,6 +249,7 @@ impl ClusterInfoVoteListener {
verified_vote_sender: VerifiedVoteSender,
replay_votes_receiver: ReplayVoteReceiver,
blockstore: Arc<Blockstore>,
bank_notification_sender: Option<BankNotificationSender>,
) -> Self {
let exit_ = exit.clone();
@@ -293,6 +295,7 @@ impl ClusterInfoVoteListener {
verified_vote_sender,
replay_votes_receiver,
blockstore,
bank_notification_sender,
);
})
.unwrap();
@@ -420,6 +423,7 @@ impl ClusterInfoVoteListener {
verified_vote_sender: VerifiedVoteSender,
replay_votes_receiver: ReplayVoteReceiver,
blockstore: Arc<Blockstore>,
bank_notification_sender: Option<BankNotificationSender>,
) -> Result<()> {
let mut optimistic_confirmation_verifier =
OptimisticConfirmationVerifier::new(bank_forks.read().unwrap().root());
@@ -451,6 +455,7 @@ impl ClusterInfoVoteListener {
&subscriptions,
&verified_vote_sender,
&replay_votes_receiver,
&bank_notification_sender,
);
if let Err(e) = optimistic_confirmed_slots {
@@ -485,6 +490,7 @@ impl ClusterInfoVoteListener {
subscriptions,
verified_vote_sender,
replay_votes_receiver,
&None,
)
}
@@ -495,6 +501,7 @@ impl ClusterInfoVoteListener {
subscriptions: &RpcSubscriptions,
verified_vote_sender: &VerifiedVoteSender,
replay_votes_receiver: &ReplayVoteReceiver,
bank_notification_sender: &Option<BankNotificationSender>,
) -> Result<Vec<(Slot, Hash)>> {
let mut sel = Select::new();
sel.recv(gossip_vote_txs_receiver);
@@ -523,6 +530,7 @@ impl ClusterInfoVoteListener {
root_bank,
subscriptions,
verified_vote_sender,
bank_notification_sender,
));
} else {
remaining_wait_time = remaining_wait_time
@@ -543,6 +551,7 @@ impl ClusterInfoVoteListener {
diff: &mut HashMap<Slot, HashMap<Arc<Pubkey>, bool>>,
new_optimistic_confirmed_slots: &mut Vec<(Slot, Hash)>,
is_gossip_vote: bool,
bank_notification_sender: &Option<BankNotificationSender>,
) {
if vote.slots.is_empty() {
return;
@@ -595,7 +604,13 @@ impl ClusterInfoVoteListener {
if is_confirmed {
new_optimistic_confirmed_slots.push((*slot, last_vote_hash));
// Notify subscribers about new optimistic confirmation
subscriptions.notify_gossip_subscribers(*slot);
if let Some(sender) = bank_notification_sender {
sender
.send(BankNotification::OptimisticallyConfirmed(*slot))
.unwrap_or_else(|err| {
warn!("bank_notification_sender failed: {:?}", err)
});
}
}
if !is_new && !is_gossip_vote {
@@ -636,16 +651,17 @@ impl ClusterInfoVoteListener {
root_bank: &Bank,
subscriptions: &RpcSubscriptions,
verified_vote_sender: &VerifiedVoteSender,
bank_notification_sender: &Option<BankNotificationSender>,
) -> Vec<(Slot, Hash)> {
let mut diff: HashMap<Slot, HashMap<Arc<Pubkey>, bool>> = HashMap::new();
let mut new_optimistic_confirmed_slots = vec![];
// Process votes from gossip and ReplayStage
for (i, (vote_pubkey, vote, _)) in gossip_vote_txs
for (is_gossip, (vote_pubkey, vote, _)) in gossip_vote_txs
.iter()
.filter_map(|gossip_tx| {
vote_transaction::parse_vote_transaction(gossip_tx).filter(
|(vote_pubkey, vote, _)| {
vote_transaction::parse_vote_transaction(gossip_tx)
.filter(|(vote_pubkey, vote, _)| {
if vote.slots.is_empty() {
return false;
}
@@ -671,11 +687,10 @@ impl ClusterInfoVoteListener {
}
true
},
)
})
.map(|v| (true, v))
})
.chain(replayed_votes)
.enumerate()
.chain(replayed_votes.into_iter().map(|v| (false, v)))
{
Self::update_new_votes(
vote,
@@ -686,7 +701,8 @@ impl ClusterInfoVoteListener {
verified_vote_sender,
&mut diff,
&mut new_optimistic_confirmed_slots,
i < gossip_vote_txs.len(),
is_gossip,
bank_notification_sender,
);
}
@@ -770,6 +786,7 @@ impl ClusterInfoVoteListener {
#[cfg(test)]
mod tests {
use super::*;
use crate::optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank;
use solana_perf::packet;
use solana_runtime::{
bank::Bank,
@@ -997,6 +1014,7 @@ mod tests {
&subscriptions,
&verified_vote_sender,
&replay_votes_receiver,
&None,
)
.unwrap();
@@ -1025,6 +1043,7 @@ mod tests {
&subscriptions,
&verified_vote_sender,
&replay_votes_receiver,
&None,
)
.unwrap();
@@ -1102,6 +1121,7 @@ mod tests {
&subscriptions,
&verified_vote_sender,
&replay_votes_receiver,
&None,
)
.unwrap();
@@ -1220,6 +1240,7 @@ mod tests {
&subscriptions,
&verified_vote_sender,
&replay_votes_receiver,
&None,
)
.unwrap();
@@ -1314,6 +1335,7 @@ mod tests {
&subscriptions,
&verified_vote_sender,
&replay_votes_receiver,
&None,
);
}
let slot_vote_tracker = vote_tracker.get_slot_vote_tracker(vote_slot).unwrap();
@@ -1424,13 +1446,16 @@ mod tests {
);
let bank = Bank::new(&genesis_config);
let exit = Arc::new(AtomicBool::new(false));
let bank_forks = BankForks::new(bank);
let bank = bank_forks.get(0).unwrap().clone();
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
let bank = bank_forks.read().unwrap().get(0).unwrap().clone();
let vote_tracker = VoteTracker::new(&bank);
let optimistically_confirmed_bank =
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
let subscriptions = Arc::new(RpcSubscriptions::new(
&exit,
Arc::new(RwLock::new(bank_forks)),
bank_forks,
Arc::new(RwLock::new(BlockCommitmentCache::default())),
optimistically_confirmed_bank,
));
// Send a vote to process, should add a reference to the pubkey for that voter
@@ -1461,6 +1486,7 @@ mod tests {
&bank,
&subscriptions,
&verified_vote_sender,
&None,
);
let ref_count = Arc::strong_count(
&vote_tracker
@@ -1530,6 +1556,7 @@ mod tests {
&new_root_bank,
&subscriptions,
&verified_vote_sender,
&None,
);
// Check new replay vote pubkey first
@@ -1579,12 +1606,15 @@ mod tests {
let bank = Bank::new(&genesis_config);
let vote_tracker = VoteTracker::new(&bank);
let exit = Arc::new(AtomicBool::new(false));
let bank_forks = BankForks::new(bank);
let bank = bank_forks.get(0).unwrap().clone();
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
let bank = bank_forks.read().unwrap().get(0).unwrap().clone();
let optimistically_confirmed_bank =
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
let subscriptions = Arc::new(RpcSubscriptions::new(
&exit,
Arc::new(RwLock::new(bank_forks)),
bank_forks,
Arc::new(RwLock::new(BlockCommitmentCache::default())),
optimistically_confirmed_bank,
));
// Integrity Checks

View File

@@ -13,9 +13,7 @@ use solana_sdk::{
};
use solana_vote_program::{
vote_instruction,
vote_state::{
BlockTimestamp, Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY, TIMESTAMP_SLOT_INTERVAL,
},
vote_state::{BlockTimestamp, Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY},
};
use std::{
collections::{HashMap, HashSet},
@@ -345,6 +343,22 @@ impl Tower {
last_vote
}
fn maybe_timestamp(&mut self, current_slot: Slot) -> Option<UnixTimestamp> {
if current_slot > self.last_timestamp.slot
|| self.last_timestamp.slot == 0 && current_slot == self.last_timestamp.slot
{
let timestamp = Utc::now().timestamp();
if timestamp >= self.last_timestamp.timestamp {
self.last_timestamp = BlockTimestamp {
slot: current_slot,
timestamp,
};
return Some(timestamp);
}
}
None
}
pub fn root(&self) -> Option<Slot> {
self.lockouts.root_slot
}
@@ -653,21 +667,6 @@ impl Tower {
);
}
}
fn maybe_timestamp(&mut self, current_slot: Slot) -> Option<UnixTimestamp> {
if self.last_timestamp.slot == 0
|| self.last_timestamp.slot < (current_slot - (current_slot % TIMESTAMP_SLOT_INTERVAL))
{
let timestamp = Utc::now().timestamp();
self.last_timestamp = BlockTimestamp {
slot: current_slot,
timestamp,
};
Some(timestamp)
} else {
None
}
}
}
#[cfg(test)]
@@ -694,12 +693,7 @@ pub mod test {
vote_state::{Vote, VoteStateVersions, MAX_LOCKOUT_HISTORY},
vote_transaction,
};
use std::{
collections::HashMap,
rc::Rc,
sync::RwLock,
{thread::sleep, time::Duration},
};
use std::{collections::HashMap, rc::Rc, sync::RwLock};
use trees::{tr, Tree, TreeWalk};
pub(crate) struct VoteSimulator {
@@ -1832,17 +1826,15 @@ pub mod test {
#[test]
fn test_maybe_timestamp() {
let mut tower = Tower::default();
assert!(tower.maybe_timestamp(TIMESTAMP_SLOT_INTERVAL).is_some());
let BlockTimestamp { slot, timestamp } = tower.last_timestamp;
assert!(tower.maybe_timestamp(0).is_some());
assert!(tower.maybe_timestamp(1).is_some());
assert!(tower.maybe_timestamp(0).is_none()); // Refuse to timestamp an older slot
assert!(tower.maybe_timestamp(1).is_none()); // Refuse to timestamp the same slot twice
assert_eq!(tower.maybe_timestamp(1), None);
assert_eq!(tower.maybe_timestamp(slot), None);
assert_eq!(tower.maybe_timestamp(slot + 1), None);
tower.last_timestamp.timestamp -= 1; // Move last_timestamp into the past
assert!(tower.maybe_timestamp(2).is_some()); // slot 2 gets a timestamp
sleep(Duration::from_secs(1));
assert!(tower
.maybe_timestamp(slot + TIMESTAMP_SLOT_INTERVAL + 1)
.is_some());
assert!(tower.last_timestamp.timestamp > timestamp);
tower.last_timestamp.timestamp += 1_000_000; // Move last_timestamp well into the future
assert!(tower.maybe_timestamp(3).is_none()); // slot 3 gets no timestamp
}
}

View File

@@ -24,22 +24,24 @@
//! A value is updated to a new version if the labels match, and the value
//! wallclock is later, or the value hash is greater.
use crate::crds_gossip_pull::CrdsFilter;
use crate::crds_shards::CrdsShards;
use crate::crds_value::{CrdsValue, CrdsValueLabel};
use bincode::serialize;
use indexmap::map::IndexMap;
use indexmap::map::{Entry, IndexMap};
use solana_sdk::hash::{hash, Hash};
use solana_sdk::pubkey::Pubkey;
use std::cmp;
use std::collections::HashMap;
use std::ops::Index;
const CRDS_SHARDS_BITS: u32 = 8;
#[derive(Clone)]
pub struct Crds {
/// Stores the map of labels and values
pub table: IndexMap<CrdsValueLabel, VersionedCrdsValue>,
pub num_inserts: usize,
pub masks: IndexMap<CrdsValueLabel, u64>,
pub shards: CrdsShards,
}
#[derive(PartialEq, Debug)]
@@ -89,7 +91,7 @@ impl Default for Crds {
Crds {
table: IndexMap::new(),
num_inserts: 0,
masks: IndexMap::new(),
shards: CrdsShards::new(CRDS_SHARDS_BITS),
}
}
}
@@ -103,19 +105,13 @@ impl Crds {
&self,
value: CrdsValue,
local_timestamp: u64,
) -> Option<VersionedCrdsValue> {
) -> (bool, VersionedCrdsValue) {
let new_value = self.new_versioned(local_timestamp, value);
let label = new_value.value.label();
let would_insert = self
.table
.get(&label)
.map(|current| new_value > *current)
.unwrap_or(true);
if would_insert {
Some(new_value)
} else {
None
}
// New value is outdated and fails to insert, if it already exists in
// the table with a more recent wallclock.
let outdated = matches!(self.table.get(&label), Some(current) if new_value <= *current);
(!outdated, new_value)
}
/// insert the new value, returns the old value if insert succeeds
pub fn insert_versioned(
@@ -123,23 +119,28 @@ impl Crds {
new_value: VersionedCrdsValue,
) -> Result<Option<VersionedCrdsValue>, CrdsError> {
let label = new_value.value.label();
let wallclock = new_value.value.wallclock();
let do_insert = self
.table
.get(&label)
.map(|current| new_value > *current)
.unwrap_or(true);
if do_insert {
self.masks.insert(
label.clone(),
CrdsFilter::hash_as_u64(&new_value.value_hash),
);
let old = self.table.insert(label, new_value);
self.num_inserts += 1;
Ok(old)
} else {
trace!("INSERT FAILED data: {} new.wallclock: {}", label, wallclock,);
Err(CrdsError::InsertFailed)
match self.table.entry(label) {
Entry::Vacant(entry) => {
assert!(self.shards.insert(entry.index(), &new_value));
entry.insert(new_value);
self.num_inserts += 1;
Ok(None)
}
Entry::Occupied(mut entry) if *entry.get() < new_value => {
let index = entry.index();
assert!(self.shards.remove(index, entry.get()));
assert!(self.shards.insert(index, &new_value));
self.num_inserts += 1;
Ok(Some(entry.insert(new_value)))
}
_ => {
trace!(
"INSERT FAILED data: {} new.wallclock: {}",
new_value.value.label(),
new_value.value.wallclock(),
);
Err(CrdsError::InsertFailed)
}
}
}
pub fn insert(
@@ -178,18 +179,14 @@ impl Crds {
now: u64,
timeouts: &HashMap<Pubkey, u64>,
) -> Vec<CrdsValueLabel> {
let min_ts = *timeouts
let default_timeout = *timeouts
.get(&Pubkey::default())
.expect("must have default timeout");
self.table
.iter()
.filter_map(|(k, v)| {
if now < v.local_timestamp
|| (timeouts.get(&k.pubkey()).is_some()
&& now - v.local_timestamp < timeouts[&k.pubkey()])
{
None
} else if now - v.local_timestamp >= min_ts {
let timeout = timeouts.get(&k.pubkey()).unwrap_or(&default_timeout);
if v.local_timestamp.saturating_add(*timeout) <= now {
Some(k)
} else {
None
@@ -200,8 +197,16 @@ impl Crds {
}
pub fn remove(&mut self, key: &CrdsValueLabel) {
self.table.swap_remove(key);
self.masks.swap_remove(key);
if let Some((index, _, value)) = self.table.swap_remove_full(key) {
assert!(self.shards.remove(index, &value));
// The previously last element in the table is now moved to the
// 'index' position. Shards need to be updated accordingly.
if index < self.table.len() {
let value = self.table.index(index);
assert!(self.shards.remove(self.table.len(), value));
assert!(self.shards.insert(index, value));
}
}
}
}
@@ -210,6 +215,7 @@ mod test {
use super::*;
use crate::contact_info::ContactInfo;
use crate::crds_value::CrdsData;
use rand::{thread_rng, Rng};
#[test]
fn test_insert() {
@@ -294,6 +300,24 @@ mod test {
assert_eq!(crds.find_old_labels(4, &set), vec![val.label()]);
}
#[test]
fn test_find_old_records_with_override() {
let mut rng = thread_rng();
let mut crds = Crds::default();
let mut timeouts = HashMap::new();
let val = CrdsValue::new_rand(&mut rng);
timeouts.insert(Pubkey::default(), 3);
assert_eq!(crds.insert(val.clone(), 0), Ok(None));
assert!(crds.find_old_labels(2, &timeouts).is_empty());
timeouts.insert(val.pubkey(), 1);
assert_eq!(crds.find_old_labels(2, &timeouts), vec![val.label()]);
timeouts.insert(val.pubkey(), u64::MAX);
assert!(crds.find_old_labels(2, &timeouts).is_empty());
timeouts.insert(Pubkey::default(), 1);
assert!(crds.find_old_labels(2, &timeouts).is_empty());
timeouts.remove(&val.pubkey());
assert_eq!(crds.find_old_labels(2, &timeouts), vec![val.label()]);
}
#[test]
fn test_remove_default() {
let mut crds = Crds::default();
let val = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::default()));
@@ -329,6 +353,45 @@ mod test {
assert_eq!(crds.find_old_labels(3, &set), vec![val.label()]);
}
#[test]
fn test_crds_shards() {
fn check_crds_shards(crds: &Crds) {
crds.shards
.check(&crds.table.values().cloned().collect::<Vec<_>>());
}
let mut crds = Crds::default();
let pubkeys: Vec<_> = std::iter::repeat_with(Pubkey::new_rand).take(256).collect();
let mut rng = thread_rng();
let mut num_inserts = 0;
for _ in 0..4096 {
let pubkey = pubkeys[rng.gen_range(0, pubkeys.len())];
let value = VersionedCrdsValue::new(
rng.gen(), // local_timestamp
CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&pubkey,
rng.gen(), // now
))),
);
if crds.insert_versioned(value).is_ok() {
check_crds_shards(&crds);
num_inserts += 1;
}
}
assert_eq!(num_inserts, crds.num_inserts);
assert!(num_inserts > 700);
assert!(crds.table.len() > 200);
assert!(num_inserts > crds.table.len());
check_crds_shards(&crds);
// Remove values one by one and assert that shards stay valid.
while !crds.table.is_empty() {
let index = rng.gen_range(0, crds.table.len());
let key = crds.table.get_index(index).unwrap().0.clone();
crds.remove(&key);
check_crds_shards(&crds);
}
}
#[test]
fn test_remove_staked() {
let mut crds = Crds::default();

View File

@@ -10,7 +10,8 @@ use crate::{
crds_gossip_push::{CrdsGossipPush, CRDS_GOSSIP_NUM_ACTIVE},
crds_value::{CrdsValue, CrdsValueLabel},
};
use solana_sdk::pubkey::Pubkey;
use rayon::ThreadPool;
use solana_sdk::{hash::Hash, pubkey::Pubkey};
use std::collections::{HashMap, HashSet};
///The min size for bloom filters
@@ -115,10 +116,15 @@ impl CrdsGossip {
/// refresh the push active set
/// * ratio - number of actives to rotate
pub fn refresh_push_active_set(&mut self, stakes: &HashMap<Pubkey, u64>) {
pub fn refresh_push_active_set(
&mut self,
stakes: &HashMap<Pubkey, u64>,
gossip_validators: Option<&HashSet<Pubkey>>,
) {
self.push.refresh_push_active_set(
&self.crds,
stakes,
gossip_validators,
&self.id,
self.shred_version,
self.pull.pull_request_time.len(),
@@ -129,15 +135,19 @@ impl CrdsGossip {
/// generate a random request
pub fn new_pull_request(
&self,
thread_pool: &ThreadPool,
now: u64,
gossip_validators: Option<&HashSet<Pubkey>>,
stakes: &HashMap<Pubkey, u64>,
bloom_size: usize,
) -> Result<(Pubkey, Vec<CrdsFilter>, CrdsValue), CrdsGossipError> {
self.pull.new_pull_request(
thread_pool,
&self.crds,
&self.id,
self.shred_version,
now,
gossip_validators,
stakes,
bloom_size,
)
@@ -170,7 +180,7 @@ impl CrdsGossip {
response: Vec<CrdsValue>,
now: u64,
process_pull_stats: &mut ProcessPullStats,
) -> (Vec<VersionedCrdsValue>, Vec<VersionedCrdsValue>) {
) -> (Vec<VersionedCrdsValue>, Vec<VersionedCrdsValue>, Vec<Hash>) {
self.pull
.filter_pull_responses(&self.crds, timeouts, response, now, process_pull_stats)
}
@@ -181,6 +191,7 @@ impl CrdsGossip {
from: &Pubkey,
responses: Vec<VersionedCrdsValue>,
responses_expired_timeout: Vec<VersionedCrdsValue>,
failed_inserts: Vec<Hash>,
now: u64,
process_pull_stats: &mut ProcessPullStats,
) {
@@ -189,6 +200,7 @@ impl CrdsGossip {
from,
responses,
responses_expired_timeout,
failed_inserts,
now,
process_pull_stats,
);
@@ -228,6 +240,7 @@ impl CrdsGossip {
let min = now - 5 * self.pull.crds_timeout;
self.pull.purge_purged(min);
}
self.pull.purge_failed_inserts(now);
rv
}
}
@@ -271,7 +284,7 @@ mod test {
0,
)
.unwrap();
crds_gossip.refresh_push_active_set(&HashMap::new());
crds_gossip.refresh_push_active_set(&HashMap::new(), None);
let now = timestamp();
//incorrect dest
let mut res = crds_gossip.process_prune_msg(

View File

@@ -16,26 +16,41 @@ use crate::crds_gossip_error::CrdsGossipError;
use crate::crds_value::{CrdsValue, CrdsValueLabel};
use rand::distributions::{Distribution, WeightedIndex};
use rand::Rng;
use solana_runtime::bloom::Bloom;
use rayon::{prelude::*, ThreadPool};
use solana_runtime::bloom::{AtomicBloom, Bloom};
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use std::cmp;
use std::collections::VecDeque;
use std::collections::{HashMap, HashSet};
use std::convert::TryInto;
use std::ops::Index;
pub const CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS: u64 = 15000;
// The maximum age of a value received over pull responses
pub const CRDS_GOSSIP_PULL_MSG_TIMEOUT_MS: u64 = 60000;
// Retention period of hashes of received outdated values.
const FAILED_INSERTS_RETENTION_MS: u64 = 20_000;
pub const FALSE_RATE: f64 = 0.1f64;
pub const KEYS: f64 = 8f64;
#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, AbiExample)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, AbiExample)]
pub struct CrdsFilter {
pub filter: Bloom<Hash>,
mask: u64,
mask_bits: u32,
}
impl Default for CrdsFilter {
fn default() -> Self {
CrdsFilter {
filter: Bloom::default(),
mask: !0u64,
mask_bits: 0u32,
}
}
}
impl solana_sdk::sanitize::Sanitize for CrdsFilter {
fn sanitize(&self) -> std::result::Result<(), solana_sdk::sanitize::SanitizeError> {
self.filter.sanitize()?;
@@ -58,48 +73,6 @@ impl CrdsFilter {
}
}
// CrdsFilter::new_complete_set returns a vector of filters. Given a hash
// value, this function returns the filter within that vector which
// corresponds to the item.
// TODO: Consider making Vec<CrdsFilter> a separate type, and implementing
// this (and new_complete_set) function as methods on that type.
pub fn get_crds_filter<'a>(
filters: &'a mut Vec<CrdsFilter>,
item: &Hash,
) -> Option<&'a mut CrdsFilter> {
if let Some(filter) = filters.first() {
let shift = 64 - filter.mask_bits.min(64);
let index = CrdsFilter::hash_as_u64(item)
.checked_shr(shift)
.unwrap_or(0u64);
filters.get_mut(index as usize)
} else {
None
}
}
// generates a vec of filters that together hold a complete set of Hashes
pub fn new_complete_set(num_items: usize, max_bytes: usize) -> Vec<Self> {
// Note: get_crds_filter above relies on the order of filters and
// bit-mask pattern here. If changed, update get_crds_filter
// accordingly.
let max_bits = (max_bytes * 8) as f64;
let max_items = Self::max_items(max_bits, FALSE_RATE, KEYS);
let mask_bits = Self::mask_bits(num_items as f64, max_items as f64);
// for each possible mask combination, generate a new filter.
let mut filters = vec![];
for seed in 0..2u64.pow(mask_bits) {
let filter = Bloom::random(max_items as usize, FALSE_RATE, max_bits as usize);
let mask = Self::compute_mask(seed, mask_bits);
let filter = CrdsFilter {
filter,
mask,
mask_bits,
};
filters.push(filter)
}
filters
}
fn compute_mask(seed: u64, mask_bits: u32) -> u64 {
assert!(seed <= 2u64.pow(mask_bits));
let seed: u64 = seed.checked_shl(64 - mask_bits).unwrap_or(0x0);
@@ -116,12 +89,8 @@ impl CrdsFilter {
((num_items / max_items).log2().ceil()).max(0.0) as u32
}
pub fn hash_as_u64(item: &Hash) -> u64 {
let arr = item.as_ref();
let mut accum = 0;
for (i, val) in arr.iter().enumerate().take(8) {
accum |= (u64::from(*val)) << (i * 8) as u64;
}
accum
let buf = item.as_ref()[..8].try_into().unwrap();
u64::from_le_bytes(buf)
}
pub fn test_mask_u64(&self, item: u64, ones: u64) -> bool {
let bits = item | ones;
@@ -149,6 +118,48 @@ impl CrdsFilter {
}
}
/// A vector of crds filters that together hold a complete set of Hashes.
struct CrdsFilterSet {
filters: Vec<AtomicBloom<Hash>>,
mask_bits: u32,
}
impl CrdsFilterSet {
fn new(num_items: usize, max_bytes: usize) -> Self {
let max_bits = (max_bytes * 8) as f64;
let max_items = CrdsFilter::max_items(max_bits, FALSE_RATE, KEYS);
let mask_bits = CrdsFilter::mask_bits(num_items as f64, max_items as f64);
let filters = std::iter::repeat_with(|| {
Bloom::random(max_items as usize, FALSE_RATE, max_bits as usize).into()
})
.take(1 << mask_bits)
.collect();
Self { filters, mask_bits }
}
fn add(&self, hash_value: Hash) {
let index = CrdsFilter::hash_as_u64(&hash_value)
.checked_shr(64 - self.mask_bits)
.unwrap_or(0);
self.filters[index as usize].add(&hash_value);
}
}
impl Into<Vec<CrdsFilter>> for CrdsFilterSet {
fn into(self) -> Vec<CrdsFilter> {
let mask_bits = self.mask_bits;
self.filters
.into_iter()
.enumerate()
.map(|(seed, filter)| CrdsFilter {
filter: filter.into(),
mask: CrdsFilter::compute_mask(seed as u64, mask_bits),
mask_bits,
})
.collect()
}
}
#[derive(Default)]
pub struct ProcessPullStats {
pub success: usize,
@@ -163,6 +174,11 @@ pub struct CrdsGossipPull {
pub pull_request_time: HashMap<Pubkey, u64>,
/// hash and insert time
pub purged_values: VecDeque<(Hash, u64)>,
// Hash value and record time (ms) of the pull responses which failed to be
// inserted in crds table; Preserved to stop the sender to send back the
// same outdated payload again by adding them to the filter for the next
// pull request.
pub failed_inserts: VecDeque<(Hash, u64)>,
pub crds_timeout: u64,
pub msg_timeout: u64,
pub num_pulls: usize,
@@ -173,6 +189,7 @@ impl Default for CrdsGossipPull {
Self {
purged_values: VecDeque::new(),
pull_request_time: HashMap::new(),
failed_inserts: VecDeque::new(),
crds_timeout: CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS,
msg_timeout: CRDS_GOSSIP_PULL_MSG_TIMEOUT_MS,
num_pulls: 0,
@@ -183,18 +200,27 @@ impl CrdsGossipPull {
/// generate a random request
pub fn new_pull_request(
&self,
thread_pool: &ThreadPool,
crds: &Crds,
self_id: &Pubkey,
self_shred_version: u16,
now: u64,
gossip_validators: Option<&HashSet<Pubkey>>,
stakes: &HashMap<Pubkey, u64>,
bloom_size: usize,
) -> Result<(Pubkey, Vec<CrdsFilter>, CrdsValue), CrdsGossipError> {
let options = self.pull_options(crds, &self_id, self_shred_version, now, stakes);
let options = self.pull_options(
crds,
&self_id,
self_shred_version,
now,
gossip_validators,
stakes,
);
if options.is_empty() {
return Err(CrdsGossipError::NoPeers);
}
let filters = self.build_crds_filters(crds, bloom_size);
let filters = self.build_crds_filters(thread_pool, crds, bloom_size);
let index = WeightedIndex::new(options.iter().map(|weighted| weighted.0)).unwrap();
let random = index.sample(&mut rand::thread_rng());
let self_info = crds
@@ -209,6 +235,7 @@ impl CrdsGossipPull {
self_id: &Pubkey,
self_shred_version: u16,
now: u64,
gossip_validators: Option<&HashSet<Pubkey>>,
stakes: &HashMap<Pubkey, u64>,
) -> Vec<(f32, &'a ContactInfo)> {
crds.table
@@ -218,6 +245,8 @@ impl CrdsGossipPull {
v.id != *self_id
&& ContactInfo::is_valid_address(&v.gossip)
&& (self_shred_version == 0 || self_shred_version == v.shred_version)
&& gossip_validators
.map_or(true, |gossip_validators| gossip_validators.contains(&v.id))
})
.map(|item| {
let max_weight = f32::from(u16::max_value()) - 1.0;
@@ -273,9 +302,10 @@ impl CrdsGossipPull {
// Checks if responses should be inserted and
// returns those responses converted to VersionedCrdsValue
// Separated in two vecs as:
// Separated in three vecs as:
// .0 => responses that update the owner timestamp
// .1 => responses that do not update the owner timestamp
// .2 => hash value of outdated values which will fail to insert.
pub fn filter_pull_responses(
&self,
crds: &Crds,
@@ -283,9 +313,18 @@ impl CrdsGossipPull {
responses: Vec<CrdsValue>,
now: u64,
stats: &mut ProcessPullStats,
) -> (Vec<VersionedCrdsValue>, Vec<VersionedCrdsValue>) {
) -> (Vec<VersionedCrdsValue>, Vec<VersionedCrdsValue>, Vec<Hash>) {
let mut versioned = vec![];
let mut versioned_expired_timestamp = vec![];
let mut failed_inserts = vec![];
let mut maybe_push = |response, values: &mut Vec<VersionedCrdsValue>| {
let (push, value) = crds.would_insert(response, now);
if push {
values.push(value);
} else {
failed_inserts.push(value.value_hash)
}
};
for r in responses {
let owner = r.label().pubkey();
// Check if the crds value is older than the msg_timeout
@@ -316,24 +355,17 @@ impl CrdsGossipPull {
if crds.lookup(&CrdsValueLabel::ContactInfo(owner)).is_none() {
stats.timeout_count += 1;
stats.failed_timeout += 1;
continue;
} else {
// Silently insert this old value without bumping record timestamps
match crds.would_insert(r, now) {
Some(resp) => versioned_expired_timestamp.push(resp),
None => stats.failed_insert += 1,
}
continue;
maybe_push(r, &mut versioned_expired_timestamp);
}
continue;
}
}
}
match crds.would_insert(r, now) {
Some(resp) => versioned.push(resp),
None => stats.failed_insert += 1,
}
maybe_push(r, &mut versioned);
}
(versioned, versioned_expired_timestamp)
(versioned, versioned_expired_timestamp, failed_inserts)
}
/// process a vec of pull responses
@@ -343,63 +375,93 @@ impl CrdsGossipPull {
from: &Pubkey,
responses: Vec<VersionedCrdsValue>,
responses_expired_timeout: Vec<VersionedCrdsValue>,
mut failed_inserts: Vec<Hash>,
now: u64,
stats: &mut ProcessPullStats,
) -> Vec<(CrdsValueLabel, Hash, u64)> {
let mut success = vec![];
let mut owners = HashSet::new();
for r in responses_expired_timeout {
stats.failed_insert += crds.insert_versioned(r).is_err() as usize;
let value_hash = r.value_hash;
if crds.insert_versioned(r).is_err() {
failed_inserts.push(value_hash);
}
}
for r in responses {
let owner = r.value.label().pubkey();
let label = r.value.label();
let wc = r.value.wallclock();
let hash = r.value_hash;
let old = crds.insert_versioned(r);
if old.is_err() {
stats.failed_insert += 1;
} else {
stats.success += 1;
self.num_pulls += 1;
success.push((label, hash, wc));
match crds.insert_versioned(r) {
Err(_) => failed_inserts.push(hash),
Ok(old) => {
stats.success += 1;
self.num_pulls += 1;
owners.insert(label.pubkey());
success.push((label, hash, wc));
if let Some(val) = old {
self.purged_values
.push_back((val.value_hash, val.local_timestamp))
}
}
}
old.ok().map(|opt| {
owners.insert(owner);
opt.map(|val| {
self.purged_values
.push_back((val.value_hash, val.local_timestamp))
})
});
}
owners.insert(*from);
for owner in owners {
crds.update_record_timestamp(&owner, now);
}
stats.failed_insert += failed_inserts.len();
self.purge_failed_inserts(now);
self.failed_inserts
.extend(failed_inserts.into_iter().zip(std::iter::repeat(now)));
success
}
pub fn purge_failed_inserts(&mut self, now: u64) {
if FAILED_INSERTS_RETENTION_MS < now {
let cutoff = now - FAILED_INSERTS_RETENTION_MS;
let outdated = self
.failed_inserts
.iter()
.take_while(|(_, ts)| *ts < cutoff)
.count();
self.failed_inserts.drain(..outdated);
}
}
// build a set of filters of the current crds table
// num_filters - used to increase the likelyhood of a value in crds being added to some filter
pub fn build_crds_filters(&self, crds: &Crds, bloom_size: usize) -> Vec<CrdsFilter> {
pub fn build_crds_filters(
&self,
thread_pool: &ThreadPool,
crds: &Crds,
bloom_size: usize,
) -> Vec<CrdsFilter> {
const PAR_MIN_LENGTH: usize = 512;
let num = cmp::max(
CRDS_GOSSIP_DEFAULT_BLOOM_ITEMS,
crds.table.values().count() + self.purged_values.len(),
crds.table.len() + self.purged_values.len() + self.failed_inserts.len(),
);
let mut filters = CrdsFilter::new_complete_set(num, bloom_size);
let mut add_value_hash = |value_hash| {
if let Some(filter) = CrdsFilter::get_crds_filter(&mut filters, value_hash) {
debug_assert!(filter.test_mask(value_hash));
filter.filter.add(value_hash);
}
};
for v in crds.table.values() {
add_value_hash(&v.value_hash);
}
for (value_hash, _insert_timestamp) in &self.purged_values {
add_value_hash(&value_hash);
}
filters
let filters = CrdsFilterSet::new(num, bloom_size);
thread_pool.install(|| {
crds.table
.par_values()
.with_min_len(PAR_MIN_LENGTH)
.map(|v| v.value_hash)
.chain(
self.purged_values
.par_iter()
.with_min_len(PAR_MIN_LENGTH)
.map(|(v, _)| *v),
)
.chain(
self.failed_inserts
.par_iter()
.with_min_len(PAR_MIN_LENGTH)
.map(|(v, _)| *v),
)
.for_each(|v| filters.add(v));
});
filters.into()
}
/// filter values that fail the bloom filter up to max_bytes
@@ -409,52 +471,44 @@ impl CrdsGossipPull {
filters: &[(CrdsValue, CrdsFilter)],
now: u64,
) -> Vec<Vec<CrdsValue>> {
let mut ret = vec![vec![]; filters.len()];
let msg_timeout = CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS;
let jitter = rand::thread_rng().gen_range(0, msg_timeout / 4);
let start = filters.len();
//skip filters from callers that are too old
let future = now.saturating_add(msg_timeout);
let past = now.saturating_sub(msg_timeout);
let recent: Vec<_> = filters
let mut dropped_requests = 0;
let mut total_skipped = 0;
let ret = filters
.iter()
.enumerate()
.filter(|(_, (caller, _))| caller.wallclock() < future && caller.wallclock() >= past)
.map(|(caller, filter)| {
let caller_wallclock = caller.wallclock();
if caller_wallclock >= future || caller_wallclock < past {
dropped_requests += 1;
return vec![];
}
let caller_wallclock = caller_wallclock.checked_add(jitter).unwrap_or(0);
crds.shards
.find(filter.mask, filter.mask_bits)
.filter_map(|index| {
let item = crds.table.index(index);
debug_assert!(filter.test_mask(&item.value_hash));
//skip values that are too new
if item.value.wallclock() > caller_wallclock {
total_skipped += 1;
None
} else if filter.filter_contains(&item.value_hash) {
None
} else {
Some(item.value.clone())
}
})
.collect()
})
.collect();
inc_new_counter_info!(
"gossip_filter_crds_values-dropped_requests",
start - recent.len()
dropped_requests
);
if recent.is_empty() {
return ret;
}
let mut total_skipped = 0;
let mask_ones: Vec<_> = recent
.iter()
.map(|(_i, (_caller, filter))| (!0u64).checked_shr(filter.mask_bits).unwrap_or(!0u64))
.collect();
for (label, mask) in crds.masks.iter() {
recent
.iter()
.zip(mask_ones.iter())
.for_each(|((i, (caller, filter)), mask_ones)| {
if filter.test_mask_u64(*mask, *mask_ones) {
let item = crds.table.get(label).unwrap();
//skip values that are too new
if item.value.wallclock()
> caller.wallclock().checked_add(jitter).unwrap_or_else(|| 0)
{
total_skipped += 1;
return;
}
if !filter.filter_contains(&item.value_hash) {
ret[*i].push(item.value.clone());
}
}
});
}
inc_new_counter_info!("gossip_filter_crds_values-dropped_values", total_skipped);
ret
}
@@ -524,13 +578,14 @@ impl CrdsGossipPull {
now: u64,
) -> (usize, usize, usize) {
let mut stats = ProcessPullStats::default();
let (versioned, versioned_expired_timeout) =
let (versioned, versioned_expired_timeout, failed_inserts) =
self.filter_pull_responses(crds, timeouts, response, now, &mut stats);
self.process_pull_responses(
crds,
from,
versioned,
versioned_expired_timeout,
failed_inserts,
now,
&mut stats,
);
@@ -544,14 +599,51 @@ impl CrdsGossipPull {
#[cfg(test)]
mod test {
use super::*;
use crate::cluster_info::MAX_BLOOM_SIZE;
use crate::contact_info::ContactInfo;
use crate::crds_value::{CrdsData, Vote};
use itertools::Itertools;
use rand::{thread_rng, RngCore};
use rand::thread_rng;
use rayon::ThreadPoolBuilder;
use solana_perf::test_tx::test_tx;
use solana_sdk::hash::{hash, HASH_BYTES};
use solana_sdk::packet::PACKET_DATA_SIZE;
#[test]
fn test_hash_as_u64() {
let arr: Vec<u8> = (0..HASH_BYTES).map(|i| i as u8 + 1).collect();
let hash = Hash::new(&arr);
assert_eq!(CrdsFilter::hash_as_u64(&hash), 0x807060504030201);
}
#[test]
fn test_hash_as_u64_random() {
fn hash_as_u64_bitops(hash: &Hash) -> u64 {
let mut out = 0;
for (i, val) in hash.as_ref().iter().enumerate().take(8) {
out |= (u64::from(*val)) << (i * 8) as u64;
}
out
}
let mut rng = thread_rng();
for _ in 0..100 {
let hash = Hash::new_rand(&mut rng);
assert_eq!(CrdsFilter::hash_as_u64(&hash), hash_as_u64_bitops(&hash));
}
}
#[test]
fn test_crds_filter_default() {
let filter = CrdsFilter::default();
let mask = CrdsFilter::compute_mask(0, filter.mask_bits);
assert_eq!(filter.mask, mask);
let mut rng = thread_rng();
for _ in 0..10 {
let hash = Hash::new_rand(&mut rng);
assert!(filter.test_mask(&hash));
}
}
#[test]
fn test_new_pull_with_stakes() {
let mut crds = Crds::default();
@@ -572,7 +664,7 @@ mod test {
stakes.insert(id, i * 100);
}
let now = 1024;
let mut options = node.pull_options(&crds, &me.label().pubkey(), 0, now, &stakes);
let mut options = node.pull_options(&crds, &me.label().pubkey(), 0, now, None, &stakes);
assert!(!options.is_empty());
options.sort_by(|(weight_l, _), (weight_r, _)| weight_r.partial_cmp(weight_l).unwrap());
// check that the highest stake holder is also the heaviest weighted.
@@ -622,7 +714,7 @@ mod test {
// shred version 123 should ignore nodes with versions 0 and 456
let options = node
.pull_options(&crds, &me.label().pubkey(), 123, 0, &stakes)
.pull_options(&crds, &me.label().pubkey(), 123, 0, None, &stakes)
.iter()
.map(|(_, c)| c.id)
.collect::<Vec<_>>();
@@ -632,7 +724,7 @@ mod test {
// spy nodes will see all
let options = node
.pull_options(&crds, &spy.label().pubkey(), 0, 0, &stakes)
.pull_options(&crds, &spy.label().pubkey(), 0, 0, None, &stakes)
.iter()
.map(|(_, c)| c.id)
.collect::<Vec<_>>();
@@ -643,40 +735,100 @@ mod test {
}
#[test]
fn test_crds_filter_get_crds_filter() {
let mut filters =
CrdsFilter::new_complete_set(/*num_items=*/ 9672788, /*max_bytes=*/ 8196);
assert_eq!(filters.len(), 1024);
let mut bytes = [0u8; HASH_BYTES];
fn test_pulls_only_from_allowed() {
let mut crds = Crds::default();
let stakes = HashMap::new();
let node = CrdsGossipPull::default();
let gossip = socketaddr!("127.0.0.1:1234");
let me = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo {
id: Pubkey::new_rand(),
gossip,
..ContactInfo::default()
}));
let node_123 = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo {
id: Pubkey::new_rand(),
gossip,
..ContactInfo::default()
}));
crds.insert(me.clone(), 0).unwrap();
crds.insert(node_123.clone(), 0).unwrap();
// Empty gossip_validators -- will pull from nobody
let mut gossip_validators = HashSet::new();
let options = node.pull_options(
&crds,
&me.label().pubkey(),
0,
0,
Some(&gossip_validators),
&stakes,
);
assert!(options.is_empty());
// Unknown pubkey in gossip_validators -- will pull from nobody
gossip_validators.insert(Pubkey::new_rand());
let options = node.pull_options(
&crds,
&me.label().pubkey(),
0,
0,
Some(&gossip_validators),
&stakes,
);
assert!(options.is_empty());
// node_123 pubkey in gossip_validators -- will pull from it
gossip_validators.insert(node_123.pubkey());
let options = node.pull_options(
&crds,
&me.label().pubkey(),
0,
0,
Some(&gossip_validators),
&stakes,
);
assert_eq!(options.len(), 1);
assert_eq!(options[0].1.id, node_123.pubkey());
}
#[test]
fn test_crds_filter_set_add() {
let mut rng = thread_rng();
for _ in 0..100 {
rng.fill_bytes(&mut bytes);
let hash_value = Hash::new(&bytes);
let filter = CrdsFilter::get_crds_filter(&mut filters, &hash_value)
.unwrap()
.clone();
assert!(filter.test_mask(&hash_value));
// Validate that the returned filter is the *unique* filter which
// corresponds to the hash value (i.e. test_mask returns true).
let crds_filter_set =
CrdsFilterSet::new(/*num_items=*/ 9672788, /*max_bytes=*/ 8196);
let hash_values: Vec<_> = std::iter::repeat_with(|| Hash::new_rand(&mut rng))
.take(1024)
.collect();
for hash_value in &hash_values {
crds_filter_set.add(*hash_value);
}
let filters: Vec<CrdsFilter> = crds_filter_set.into();
assert_eq!(filters.len(), 1024);
for hash_value in hash_values {
let mut num_hits = 0;
for f in &filters {
if *f == filter {
let mut false_positives = 0;
for filter in &filters {
if filter.test_mask(&hash_value) {
num_hits += 1;
assert!(f.test_mask(&hash_value));
} else {
assert!(!f.test_mask(&hash_value));
assert!(filter.contains(&hash_value));
assert!(filter.filter.contains(&hash_value));
} else if filter.filter.contains(&hash_value) {
false_positives += 1;
}
}
assert_eq!(num_hits, 1);
assert!(false_positives < 5);
}
}
#[test]
fn test_crds_filter_new_complete_set() {
// Validates invariances required by CrdsFilter::get_crds_filter in the
// vector of filters generated by CrdsFilter::new_complete_set.
let filters =
CrdsFilter::new_complete_set(/*num_items=*/ 55345017, /*max_bytes=*/ 4098);
fn test_crds_filter_set_new() {
// Validates invariances required by CrdsFilterSet::get in the
// vector of filters generated by CrdsFilterSet::new.
let filters: Vec<CrdsFilter> =
CrdsFilterSet::new(/*num_items=*/ 55345017, /*max_bytes=*/ 4098).into();
assert_eq!(filters.len(), 16384);
let mask_bits = filters[0].mask_bits;
let right_shift = 64 - mask_bits;
@@ -689,8 +841,62 @@ mod test {
}
}
#[test]
fn test_build_crds_filter() {
let mut rng = thread_rng();
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
let mut crds_gossip_pull = CrdsGossipPull::default();
let mut crds = Crds::default();
for _ in 0..10_000 {
crds_gossip_pull
.purged_values
.push_back((Hash::new_rand(&mut rng), rng.gen()));
}
let mut num_inserts = 0;
for _ in 0..20_000 {
if crds
.insert(CrdsValue::new_rand(&mut rng), rng.gen())
.is_ok()
{
num_inserts += 1;
}
}
assert_eq!(num_inserts, 20_000);
let filters = crds_gossip_pull.build_crds_filters(&thread_pool, &crds, MAX_BLOOM_SIZE);
assert_eq!(filters.len(), 32);
let hash_values: Vec<_> = crds
.table
.values()
.map(|v| v.value_hash)
.chain(
crds_gossip_pull
.purged_values
.iter()
.map(|(value_hash, _)| value_hash)
.cloned(),
)
.collect();
assert_eq!(hash_values.len(), 10_000 + 20_000);
let mut false_positives = 0;
for hash_value in hash_values {
let mut num_hits = 0;
for filter in &filters {
if filter.test_mask(&hash_value) {
num_hits += 1;
assert!(filter.contains(&hash_value));
assert!(filter.filter.contains(&hash_value));
} else if filter.filter.contains(&hash_value) {
false_positives += 1;
}
}
assert_eq!(num_hits, 1);
}
assert!(false_positives < 50_000, "fp: {}", false_positives);
}
#[test]
fn test_new_pull_request() {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
let mut crds = Crds::default();
let entry = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&Pubkey::new_rand(),
@@ -699,13 +905,31 @@ mod test {
let id = entry.label().pubkey();
let node = CrdsGossipPull::default();
assert_eq!(
node.new_pull_request(&crds, &id, 0, 0, &HashMap::new(), PACKET_DATA_SIZE),
node.new_pull_request(
&thread_pool,
&crds,
&id,
0,
0,
None,
&HashMap::new(),
PACKET_DATA_SIZE
),
Err(CrdsGossipError::NoPeers)
);
crds.insert(entry.clone(), 0).unwrap();
assert_eq!(
node.new_pull_request(&crds, &id, 0, 0, &HashMap::new(), PACKET_DATA_SIZE),
node.new_pull_request(
&thread_pool,
&crds,
&id,
0,
0,
None,
&HashMap::new(),
PACKET_DATA_SIZE
),
Err(CrdsGossipError::NoPeers)
);
@@ -714,7 +938,16 @@ mod test {
0,
)));
crds.insert(new.clone(), 0).unwrap();
let req = node.new_pull_request(&crds, &id, 0, 0, &HashMap::new(), PACKET_DATA_SIZE);
let req = node.new_pull_request(
&thread_pool,
&crds,
&id,
0,
0,
None,
&HashMap::new(),
PACKET_DATA_SIZE,
);
let (to, _, self_info) = req.unwrap();
assert_eq!(to, new.label().pubkey());
assert_eq!(self_info, entry);
@@ -722,6 +955,7 @@ mod test {
#[test]
fn test_new_mark_creation_time() {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
let mut crds = Crds::default();
let entry = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&Pubkey::new_rand(),
@@ -747,10 +981,12 @@ mod test {
// odds of getting the other request should be 1 in u64::max_value()
for _ in 0..10 {
let req = node.new_pull_request(
&thread_pool,
&crds,
&node_pubkey,
0,
u64::max_value(),
None,
&HashMap::new(),
PACKET_DATA_SIZE,
);
@@ -762,6 +998,7 @@ mod test {
#[test]
fn test_generate_pull_responses() {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
let mut node_crds = Crds::default();
let entry = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&Pubkey::new_rand(),
@@ -776,10 +1013,12 @@ mod test {
)));
node_crds.insert(new, 0).unwrap();
let req = node.new_pull_request(
&thread_pool,
&node_crds,
&node_pubkey,
0,
0,
None,
&HashMap::new(),
PACKET_DATA_SIZE,
);
@@ -822,6 +1061,7 @@ mod test {
#[test]
fn test_process_pull_request() {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
let mut node_crds = Crds::default();
let entry = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&Pubkey::new_rand(),
@@ -836,10 +1076,12 @@ mod test {
)));
node_crds.insert(new, 0).unwrap();
let req = node.new_pull_request(
&thread_pool,
&node_crds,
&node_pubkey,
0,
0,
None,
&HashMap::new(),
PACKET_DATA_SIZE,
);
@@ -869,6 +1111,7 @@ mod test {
}
#[test]
fn test_process_pull_request_response() {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
let mut node_crds = Crds::default();
let entry = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&Pubkey::new_rand(),
@@ -910,10 +1153,12 @@ mod test {
for _ in 0..30 {
// there is a chance of a false positive with bloom filters
let req = node.new_pull_request(
&thread_pool,
&node_crds,
&node_pubkey,
0,
0,
None,
&HashMap::new(),
PACKET_DATA_SIZE,
);
@@ -963,6 +1208,7 @@ mod test {
}
#[test]
fn test_gossip_purge() {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
let mut node_crds = Crds::default();
let entry = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&Pubkey::new_rand(),
@@ -995,7 +1241,7 @@ mod test {
// there is a chance of a false positive with bloom filters
// assert that purged value is still in the set
// chance of 30 consecutive false positives is 0.1^30
let filters = node.build_crds_filters(&node_crds, PACKET_DATA_SIZE);
let filters = node.build_crds_filters(&thread_pool, &node_crds, PACKET_DATA_SIZE);
assert!(filters.iter().any(|filter| filter.contains(&value_hash)));
}
@@ -1039,7 +1285,7 @@ mod test {
}
#[test]
fn test_crds_filter_complete_set_add_mask() {
let mut filters = CrdsFilter::new_complete_set(1000, 10);
let mut filters: Vec<CrdsFilter> = CrdsFilterSet::new(1000, 10).into();
assert!(filters.iter().all(|f| f.mask_bits > 0));
let mut h: Hash = Hash::default();
// rev to make the hash::default() miss on the first few test_masks

View File

@@ -37,6 +37,9 @@ pub const CRDS_GOSSIP_PRUNE_MSG_TIMEOUT_MS: u64 = 500;
pub const CRDS_GOSSIP_PRUNE_STAKE_THRESHOLD_PCT: f64 = 0.15;
pub const CRDS_GOSSIP_PRUNE_MIN_INGRESS_NODES: usize = 2;
// 10 minutes
const MAX_PUSHED_TO_TIMEOUT_MS: u64 = 10 * 60 * 1000;
#[derive(Clone)]
pub struct CrdsGossipPush {
/// max bytes per message
@@ -50,6 +53,8 @@ pub struct CrdsGossipPush {
/// This cache represents a lagging view of which validators
/// currently have this node in their `active_set`
received_cache: HashMap<Pubkey, HashMap<Pubkey, (bool, u64)>>,
last_pushed_to: HashMap<Pubkey, u64>,
last_pushed_to_cleanup_ts: u64,
pub num_active: usize,
pub push_fanout: usize,
pub msg_timeout: u64,
@@ -67,6 +72,8 @@ impl Default for CrdsGossipPush {
active_set: IndexMap::new(),
push_messages: HashMap::new(),
received_cache: HashMap::new(),
last_pushed_to: HashMap::new(),
last_pushed_to_cleanup_ts: 0,
num_active: CRDS_GOSSIP_NUM_ACTIVE,
push_fanout: CRDS_GOSSIP_PUSH_FANOUT,
msg_timeout: CRDS_GOSSIP_PUSH_MSG_TIMEOUT_MS,
@@ -254,6 +261,15 @@ impl CrdsGossipPush {
self.push_messages.remove(&v.label());
}
}
for target_pubkey in push_messages.keys() {
*self.last_pushed_to.entry(*target_pubkey).or_insert(0) = now;
}
if now - self.last_pushed_to_cleanup_ts > MAX_PUSHED_TO_TIMEOUT_MS {
self.last_pushed_to
.retain(|_id, timestamp| now - *timestamp > MAX_PUSHED_TO_TIMEOUT_MS);
self.last_pushed_to_cleanup_ts = now;
}
push_messages
}
@@ -280,6 +296,7 @@ impl CrdsGossipPush {
&mut self,
crds: &Crds,
stakes: &HashMap<Pubkey, u64>,
gossip_validators: Option<&HashSet<Pubkey>>,
self_id: &Pubkey,
self_shred_version: u16,
network_size: usize,
@@ -288,7 +305,13 @@ impl CrdsGossipPush {
let need = Self::compute_need(self.num_active, self.active_set.len(), ratio);
let mut new_items = HashMap::new();
let options: Vec<_> = self.push_options(crds, &self_id, self_shred_version, stakes);
let options: Vec<_> = self.push_options(
crds,
&self_id,
self_shred_version,
stakes,
gossip_validators,
);
if options.is_empty() {
return;
}
@@ -336,6 +359,7 @@ impl CrdsGossipPush {
self_id: &Pubkey,
self_shred_version: u16,
stakes: &HashMap<Pubkey, u64>,
gossip_validators: Option<&HashSet<Pubkey>>,
) -> Vec<(f32, &'a ContactInfo)> {
crds.table
.values()
@@ -345,11 +369,14 @@ impl CrdsGossipPush {
info.id != *self_id
&& ContactInfo::is_valid_address(&info.gossip)
&& self_shred_version == info.shred_version
&& gossip_validators.map_or(true, |gossip_validators| {
gossip_validators.contains(&info.id)
})
})
.map(|(info, value)| {
.map(|(info, _value)| {
let max_weight = f32::from(u16::max_value()) - 1.0;
let last_updated: u64 = value.local_timestamp;
let since = ((timestamp() - last_updated) / 1024) as u32;
let last_pushed_to: u64 = *self.last_pushed_to.get(&info.id).unwrap_or(&0);
let since = ((timestamp() - last_pushed_to) / 1024) as u32;
let stake = get_stake(&info.id, stakes);
let weight = get_weight(max_weight, since, stake);
(weight, info)
@@ -359,34 +386,19 @@ impl CrdsGossipPush {
/// purge old pending push messages
pub fn purge_old_pending_push_messages(&mut self, crds: &Crds, min_time: u64) {
let old_msgs: Vec<CrdsValueLabel> = self
.push_messages
.iter()
.filter_map(|(k, hash)| {
if let Some(versioned) = crds.lookup_versioned(k) {
if versioned.value.wallclock() < min_time || versioned.value_hash != *hash {
Some(k)
} else {
None
}
} else {
Some(k)
}
})
.cloned()
.collect();
for k in old_msgs {
self.push_messages.remove(&k);
}
self.push_messages.retain(|k, hash| {
matches!(crds.lookup_versioned(k), Some(versioned) if
versioned.value.wallclock() >= min_time
&& versioned.value_hash == *hash)
});
}
/// purge received push message cache
pub fn purge_old_received_cache(&mut self, min_time: u64) {
self.received_cache
.iter_mut()
.for_each(|v| v.1.retain(|_, v| v.1 > min_time));
self.received_cache.retain(|_, v| !v.is_empty());
self.received_cache.retain(|_, v| {
v.retain(|_, (_, t)| *t > min_time);
!v.is_empty()
});
}
}
@@ -552,7 +564,7 @@ mod test {
)));
assert_eq!(crds.insert(value1.clone(), 0), Ok(None));
push.refresh_push_active_set(&crds, &HashMap::new(), &Pubkey::default(), 0, 1, 1);
push.refresh_push_active_set(&crds, &HashMap::new(), None, &Pubkey::default(), 0, 1, 1);
assert!(push.active_set.get(&value1.label().pubkey()).is_some());
let value2 = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
@@ -562,7 +574,7 @@ mod test {
assert!(push.active_set.get(&value2.label().pubkey()).is_none());
assert_eq!(crds.insert(value2.clone(), 0), Ok(None));
for _ in 0..30 {
push.refresh_push_active_set(&crds, &HashMap::new(), &Pubkey::default(), 0, 1, 1);
push.refresh_push_active_set(&crds, &HashMap::new(), None, &Pubkey::default(), 0, 1, 1);
if push.active_set.get(&value2.label().pubkey()).is_some() {
break;
}
@@ -575,14 +587,15 @@ mod test {
));
assert_eq!(crds.insert(value2.clone(), 0), Ok(None));
}
push.refresh_push_active_set(&crds, &HashMap::new(), &Pubkey::default(), 0, 1, 1);
push.refresh_push_active_set(&crds, &HashMap::new(), None, &Pubkey::default(), 0, 1, 1);
assert_eq!(push.active_set.len(), push.num_active);
}
#[test]
fn test_active_set_refresh_with_bank() {
solana_logger::setup();
let time = timestamp() - 1024; //make sure there's at least a 1 second delay
let mut crds = Crds::default();
let push = CrdsGossipPush::default();
let mut push = CrdsGossipPush::default();
let mut stakes = HashMap::new();
for i in 1..=100 {
let peer = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
@@ -592,8 +605,9 @@ mod test {
let id = peer.label().pubkey();
crds.insert(peer.clone(), time).unwrap();
stakes.insert(id, i * 100);
push.last_pushed_to.insert(id, time);
}
let mut options = push.push_options(&crds, &Pubkey::default(), 0, &stakes);
let mut options = push.push_options(&crds, &Pubkey::default(), 0, &stakes, None);
assert!(!options.is_empty());
options.sort_by(|(weight_l, _), (weight_r, _)| weight_r.partial_cmp(weight_l).unwrap());
// check that the highest stake holder is also the heaviest weighted.
@@ -643,7 +657,7 @@ mod test {
// shred version 123 should ignore nodes with versions 0 and 456
let options = node
.push_options(&crds, &me.label().pubkey(), 123, &stakes)
.push_options(&crds, &me.label().pubkey(), 123, &stakes, None)
.iter()
.map(|(_, c)| c.id)
.collect::<Vec<_>>();
@@ -653,12 +667,71 @@ mod test {
// spy nodes should not push to people on different shred versions
let options = node
.push_options(&crds, &spy.label().pubkey(), 0, &stakes)
.push_options(&crds, &spy.label().pubkey(), 0, &stakes, None)
.iter()
.map(|(_, c)| c.id)
.collect::<Vec<_>>();
assert!(options.is_empty());
}
#[test]
fn test_pushes_only_to_allowed() {
let mut crds = Crds::default();
let stakes = HashMap::new();
let node = CrdsGossipPush::default();
let gossip = socketaddr!("127.0.0.1:1234");
let me = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo {
id: Pubkey::new_rand(),
gossip,
..ContactInfo::default()
}));
let node_123 = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo {
id: Pubkey::new_rand(),
gossip,
..ContactInfo::default()
}));
crds.insert(me.clone(), 0).unwrap();
crds.insert(node_123.clone(), 0).unwrap();
// Unknown pubkey in gossip_validators -- will push to nobody
let mut gossip_validators = HashSet::new();
let options = node.push_options(
&crds,
&me.label().pubkey(),
0,
&stakes,
Some(&gossip_validators),
);
assert!(options.is_empty());
// Unknown pubkey in gossip_validators -- will push to nobody
gossip_validators.insert(Pubkey::new_rand());
let options = node.push_options(
&crds,
&me.label().pubkey(),
0,
&stakes,
Some(&gossip_validators),
);
assert!(options.is_empty());
// node_123 pubkey in gossip_validators -- will push to it
gossip_validators.insert(node_123.pubkey());
let options = node.push_options(
&crds,
&me.label().pubkey(),
0,
&stakes,
Some(&gossip_validators),
);
assert_eq!(options.len(), 1);
assert_eq!(options[0].1.id, node_123.pubkey());
}
#[test]
fn test_new_push_messages() {
let mut crds = Crds::default();
@@ -668,7 +741,7 @@ mod test {
0,
)));
assert_eq!(crds.insert(peer.clone(), 0), Ok(None));
push.refresh_push_active_set(&crds, &HashMap::new(), &Pubkey::default(), 0, 1, 1);
push.refresh_push_active_set(&crds, &HashMap::new(), None, &Pubkey::default(), 0, 1, 1);
let new_msg = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&Pubkey::new_rand(),
@@ -705,7 +778,7 @@ mod test {
push.process_push_message(&mut crds, &Pubkey::default(), peer_3.clone(), 0),
Ok(None)
);
push.refresh_push_active_set(&crds, &HashMap::new(), &Pubkey::default(), 0, 1, 1);
push.refresh_push_active_set(&crds, &HashMap::new(), None, &Pubkey::default(), 0, 1, 1);
// push 3's contact info to 1 and 2 and 3
let new_msg = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
@@ -728,7 +801,7 @@ mod test {
0,
)));
assert_eq!(crds.insert(peer.clone(), 0), Ok(None));
push.refresh_push_active_set(&crds, &HashMap::new(), &Pubkey::default(), 0, 1, 1);
push.refresh_push_active_set(&crds, &HashMap::new(), None, &Pubkey::default(), 0, 1, 1);
let new_msg = CrdsValue::new_unsigned(CrdsData::ContactInfo(ContactInfo::new_localhost(
&Pubkey::new_rand(),
@@ -755,7 +828,7 @@ mod test {
0,
)));
assert_eq!(crds.insert(peer, 0), Ok(None));
push.refresh_push_active_set(&crds, &HashMap::new(), &Pubkey::default(), 0, 1, 1);
push.refresh_push_active_set(&crds, &HashMap::new(), None, &Pubkey::default(), 0, 1, 1);
let mut ci = ContactInfo::new_localhost(&Pubkey::new_rand(), 0);
ci.wallclock = 1;

233
core/src/crds_shards.rs Normal file
View File

@@ -0,0 +1,233 @@
use crate::crds::VersionedCrdsValue;
use crate::crds_gossip_pull::CrdsFilter;
use indexmap::map::IndexMap;
use std::cmp::Ordering;
use std::ops::{Index, IndexMut};
#[derive(Clone)]
pub struct CrdsShards {
// shards[k] includes crds values which the first shard_bits of their hash
// value is equal to k. Each shard is a mapping from crds values indices to
// their hash value.
shards: Vec<IndexMap<usize, u64>>,
shard_bits: u32,
}
impl CrdsShards {
pub fn new(shard_bits: u32) -> Self {
CrdsShards {
shards: vec![IndexMap::new(); 1 << shard_bits],
shard_bits,
}
}
#[must_use]
pub fn insert(&mut self, index: usize, value: &VersionedCrdsValue) -> bool {
let hash = CrdsFilter::hash_as_u64(&value.value_hash);
self.shard_mut(hash).insert(index, hash).is_none()
}
#[must_use]
pub fn remove(&mut self, index: usize, value: &VersionedCrdsValue) -> bool {
let hash = CrdsFilter::hash_as_u64(&value.value_hash);
self.shard_mut(hash).swap_remove(&index).is_some()
}
/// Returns indices of all crds values which the first 'mask_bits' of their
/// hash value is equal to 'mask'.
pub fn find(&self, mask: u64, mask_bits: u32) -> impl Iterator<Item = usize> + '_ {
let ones = (!0u64).checked_shr(mask_bits).unwrap_or(0);
let mask = mask | ones;
match self.shard_bits.cmp(&mask_bits) {
Ordering::Less => {
let pred = move |(&index, hash)| {
if hash | ones == mask {
Some(index)
} else {
None
}
};
Iter::Less(self.shard(mask).iter().filter_map(pred))
}
Ordering::Equal => Iter::Equal(self.shard(mask).keys().cloned()),
Ordering::Greater => {
let count = 1 << (self.shard_bits - mask_bits);
let end = self.shard_index(mask) + 1;
Iter::Greater(
self.shards[end - count..end]
.iter()
.flat_map(IndexMap::keys)
.cloned(),
)
}
}
}
#[inline]
fn shard_index(&self, hash: u64) -> usize {
hash.checked_shr(64 - self.shard_bits).unwrap_or(0) as usize
}
#[inline]
fn shard(&self, hash: u64) -> &IndexMap<usize, u64> {
let shard_index = self.shard_index(hash);
self.shards.index(shard_index)
}
#[inline]
fn shard_mut(&mut self, hash: u64) -> &mut IndexMap<usize, u64> {
let shard_index = self.shard_index(hash);
self.shards.index_mut(shard_index)
}
// Checks invariants in the shards tables against the crds table.
#[cfg(test)]
pub fn check(&self, crds: &[VersionedCrdsValue]) {
let mut indices: Vec<_> = self
.shards
.iter()
.flat_map(IndexMap::keys)
.cloned()
.collect();
indices.sort_unstable();
assert_eq!(indices, (0..crds.len()).collect::<Vec<_>>());
for (shard_index, shard) in self.shards.iter().enumerate() {
for (&index, &hash) in shard {
assert_eq!(hash, CrdsFilter::hash_as_u64(&crds[index].value_hash));
assert_eq!(
shard_index as u64,
hash.checked_shr(64 - self.shard_bits).unwrap_or(0)
);
}
}
}
}
// Wrapper for 3 types of iterators we get when comparing shard_bits and
// mask_bits in find method. This is to avoid Box<dyn Iterator<Item =...>>
// which involves dynamic dispatch and is relatively slow.
enum Iter<R, S, T> {
Less(R),
Equal(S),
Greater(T),
}
impl<R, S, T> Iterator for Iter<R, S, T>
where
R: Iterator<Item = usize>,
S: Iterator<Item = usize>,
T: Iterator<Item = usize>,
{
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Greater(iter) => iter.next(),
Self::Less(iter) => iter.next(),
Self::Equal(iter) => iter.next(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::contact_info::ContactInfo;
use crate::crds_value::{CrdsData, CrdsValue};
use rand::{thread_rng, Rng};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::timing::timestamp;
use std::collections::HashSet;
use std::ops::Index;
fn new_test_crds_value() -> VersionedCrdsValue {
let data =
CrdsData::ContactInfo(ContactInfo::new_localhost(&Pubkey::new_rand(), timestamp()));
VersionedCrdsValue::new(timestamp(), CrdsValue::new_unsigned(data))
}
// Returns true if the first mask_bits most significant bits of hash is the
// same as the given bit mask.
fn check_mask(value: &VersionedCrdsValue, mask: u64, mask_bits: u32) -> bool {
let hash = CrdsFilter::hash_as_u64(&value.value_hash);
let ones = (!0u64).checked_shr(mask_bits).unwrap_or(0u64);
(hash | ones) == (mask | ones)
}
// Manual filtering by scanning all the values.
fn filter_crds_values(
values: &[VersionedCrdsValue],
mask: u64,
mask_bits: u32,
) -> HashSet<usize> {
values
.iter()
.enumerate()
.filter_map(|(index, value)| {
if check_mask(value, mask, mask_bits) {
Some(index)
} else {
None
}
})
.collect()
}
#[test]
fn test_crds_shards_round_trip() {
let mut rng = thread_rng();
// Generate some random hash and crds value labels.
let mut values: Vec<_> = std::iter::repeat_with(new_test_crds_value)
.take(4096)
.collect();
// Insert everything into the crds shards.
let mut shards = CrdsShards::new(5);
for (index, value) in values.iter().enumerate() {
assert!(shards.insert(index, value));
}
shards.check(&values);
// Remove some of the values.
for _ in 0..512 {
let index = rng.gen_range(0, values.len());
let value = values.swap_remove(index);
assert!(shards.remove(index, &value));
if index < values.len() {
let value = values.index(index);
assert!(shards.remove(values.len(), value));
assert!(shards.insert(index, value));
}
shards.check(&values);
}
// Random masks.
for _ in 0..10 {
let mask = rng.gen();
for mask_bits in 0..12 {
let mut set = filter_crds_values(&values, mask, mask_bits);
for index in shards.find(mask, mask_bits) {
assert!(set.remove(&index));
}
assert!(set.is_empty());
}
}
// Existing hash values.
for (index, value) in values.iter().enumerate() {
let mask = CrdsFilter::hash_as_u64(&value.value_hash);
let hits: Vec<_> = shards.find(mask, 64).collect();
assert_eq!(hits, vec![index]);
}
// Remove everything.
while !values.is_empty() {
let index = rng.gen_range(0, values.len());
let value = values.swap_remove(index);
assert!(shards.remove(index, &value));
if index < values.len() {
let value = values.index(index);
assert!(shards.remove(values.len(), value));
assert!(shards.insert(index, value));
}
if index % 5 == 0 {
shards.check(&values);
}
}
}
}

View File

@@ -75,6 +75,7 @@ pub enum CrdsData {
SnapshotHashes(SnapshotHash),
AccountsHashes(SnapshotHash),
EpochSlots(EpochSlotsIndex, EpochSlots),
LegacyVersion(LegacyVersion),
Version(Version),
}
@@ -102,6 +103,7 @@ impl Sanitize for CrdsData {
}
val.sanitize()
}
CrdsData::LegacyVersion(version) => version.sanitize(),
CrdsData::Version(version) => version.sanitize(),
}
}
@@ -208,6 +210,23 @@ impl Vote {
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, AbiExample)]
pub struct LegacyVersion {
pub from: Pubkey,
pub wallclock: u64,
pub version: solana_version::LegacyVersion,
}
impl Sanitize for LegacyVersion {
fn sanitize(&self) -> Result<(), SanitizeError> {
if self.wallclock >= MAX_WALLCLOCK {
return Err(SanitizeError::ValueOutOfBounds);
}
self.from.sanitize()?;
self.version.sanitize()
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, AbiExample)]
pub struct Version {
pub from: Pubkey,
@@ -245,6 +264,7 @@ pub enum CrdsValueLabel {
SnapshotHashes(Pubkey),
EpochSlots(EpochSlotsIndex, Pubkey),
AccountsHashes(Pubkey),
LegacyVersion(Pubkey),
Version(Pubkey),
}
@@ -257,6 +277,7 @@ impl fmt::Display for CrdsValueLabel {
CrdsValueLabel::SnapshotHashes(_) => write!(f, "SnapshotHash({})", self.pubkey()),
CrdsValueLabel::EpochSlots(ix, _) => write!(f, "EpochSlots({}, {})", ix, self.pubkey()),
CrdsValueLabel::AccountsHashes(_) => write!(f, "AccountsHashes({})", self.pubkey()),
CrdsValueLabel::LegacyVersion(_) => write!(f, "LegacyVersion({})", self.pubkey()),
CrdsValueLabel::Version(_) => write!(f, "Version({})", self.pubkey()),
}
}
@@ -271,6 +292,7 @@ impl CrdsValueLabel {
CrdsValueLabel::SnapshotHashes(p) => *p,
CrdsValueLabel::EpochSlots(_, p) => *p,
CrdsValueLabel::AccountsHashes(p) => *p,
CrdsValueLabel::LegacyVersion(p) => *p,
CrdsValueLabel::Version(p) => *p,
}
}
@@ -289,6 +311,17 @@ impl CrdsValue {
value.sign(keypair);
value
}
/// New random crds value for tests and benchmarks.
pub fn new_rand<R: ?Sized>(rng: &mut R) -> CrdsValue
where
R: rand::Rng,
{
let now = rng.gen();
let contact_info = ContactInfo::new_localhost(&Pubkey::new_rand(), now);
Self::new_signed(CrdsData::ContactInfo(contact_info), &Keypair::new())
}
/// Totally unsecure unverifiable wallclock of the node that generated this message
/// Latest wallclock is always picked.
/// This is used to time out push messages.
@@ -300,6 +333,7 @@ impl CrdsValue {
CrdsData::SnapshotHashes(hash) => hash.wallclock,
CrdsData::AccountsHashes(hash) => hash.wallclock,
CrdsData::EpochSlots(_, p) => p.wallclock,
CrdsData::LegacyVersion(version) => version.wallclock,
CrdsData::Version(version) => version.wallclock,
}
}
@@ -311,6 +345,7 @@ impl CrdsValue {
CrdsData::SnapshotHashes(hash) => hash.from,
CrdsData::AccountsHashes(hash) => hash.from,
CrdsData::EpochSlots(_, p) => p.from,
CrdsData::LegacyVersion(version) => version.from,
CrdsData::Version(version) => version.from,
}
}
@@ -322,6 +357,7 @@ impl CrdsValue {
CrdsData::SnapshotHashes(_) => CrdsValueLabel::SnapshotHashes(self.pubkey()),
CrdsData::AccountsHashes(_) => CrdsValueLabel::AccountsHashes(self.pubkey()),
CrdsData::EpochSlots(ix, _) => CrdsValueLabel::EpochSlots(*ix, self.pubkey()),
CrdsData::LegacyVersion(_) => CrdsValueLabel::LegacyVersion(self.pubkey()),
CrdsData::Version(_) => CrdsValueLabel::Version(self.pubkey()),
}
}
@@ -373,6 +409,13 @@ impl CrdsValue {
}
}
pub fn legacy_version(&self) -> Option<&LegacyVersion> {
match &self.data {
CrdsData::LegacyVersion(legacy_version) => Some(legacy_version),
_ => None,
}
}
pub fn version(&self) -> Option<&Version> {
match &self.data {
CrdsData::Version(version) => Some(version),
@@ -387,6 +430,7 @@ impl CrdsValue {
CrdsValueLabel::LowestSlot(*key),
CrdsValueLabel::SnapshotHashes(*key),
CrdsValueLabel::AccountsHashes(*key),
CrdsValueLabel::LegacyVersion(*key),
CrdsValueLabel::Version(*key),
];
labels.extend((0..MAX_VOTES).map(|ix| CrdsValueLabel::Vote(ix, *key)));
@@ -438,7 +482,7 @@ mod test {
#[test]
fn test_labels() {
let mut hits = [false; 5 + MAX_VOTES as usize + MAX_EPOCH_SLOTS as usize];
let mut hits = [false; 6 + MAX_VOTES as usize + MAX_EPOCH_SLOTS as usize];
// this method should cover all the possible labels
for v in &CrdsValue::record_labels(&Pubkey::default()) {
match v {
@@ -446,10 +490,11 @@ mod test {
CrdsValueLabel::LowestSlot(_) => hits[1] = true,
CrdsValueLabel::SnapshotHashes(_) => hits[2] = true,
CrdsValueLabel::AccountsHashes(_) => hits[3] = true,
CrdsValueLabel::Version(_) => hits[4] = true,
CrdsValueLabel::Vote(ix, _) => hits[*ix as usize + 5] = true,
CrdsValueLabel::LegacyVersion(_) => hits[4] = true,
CrdsValueLabel::Version(_) => hits[5] = true,
CrdsValueLabel::Vote(ix, _) => hits[*ix as usize + 6] = true,
CrdsValueLabel::EpochSlots(ix, _) => {
hits[*ix as usize + MAX_VOTES as usize + 5] = true
hits[*ix as usize + MAX_VOTES as usize + 6] = true
}
}
}

View File

@@ -6,15 +6,22 @@ use rand::{thread_rng, Rng};
use solana_client::thin_client::{create_client, ThinClient};
use solana_perf::recycler::Recycler;
use solana_runtime::bank_forks::BankForks;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signer},
};
use solana_streamer::streamer;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, UdpSocket};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::channel;
use std::sync::{Arc, RwLock};
use std::thread::{self, sleep, JoinHandle};
use std::time::{Duration, Instant};
use std::{
collections::HashSet,
net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, UdpSocket},
sync::{
atomic::{AtomicBool, Ordering},
mpsc::channel,
{Arc, RwLock},
},
thread::{self, sleep, JoinHandle},
time::{Duration, Instant},
};
pub struct GossipService {
thread_hdls: Vec<JoinHandle<()>>,
@@ -25,6 +32,7 @@ impl GossipService {
cluster_info: &Arc<ClusterInfo>,
bank_forks: Option<Arc<RwLock<BankForks>>>,
gossip_socket: UdpSocket,
gossip_validators: Option<HashSet<Pubkey>>,
exit: &Arc<AtomicBool>,
) -> Self {
let (request_sender, request_receiver) = channel();
@@ -50,7 +58,13 @@ impl GossipService {
response_sender.clone(),
exit,
);
let t_gossip = ClusterInfo::gossip(cluster_info.clone(), bank_forks, response_sender, exit);
let t_gossip = ClusterInfo::gossip(
cluster_info.clone(),
bank_forks,
response_sender,
gossip_validators,
exit,
);
let thread_hdls = vec![t_receiver, t_responder, t_listen, t_gossip];
Self { thread_hdls }
}
@@ -265,7 +279,7 @@ fn make_gossip_node(
cluster_info.set_entrypoint(ContactInfo::new_gossip_entry_point(entrypoint));
}
let cluster_info = Arc::new(cluster_info);
let gossip_service = GossipService::new(&cluster_info, None, gossip_socket, &exit);
let gossip_service = GossipService::new(&cluster_info, None, gossip_socket, None, &exit);
(gossip_service, ip_echo, cluster_info)
}
@@ -284,7 +298,7 @@ mod tests {
let tn = Node::new_localhost();
let cluster_info = ClusterInfo::new_with_invalid_keypair(tn.info.clone());
let c = Arc::new(cluster_info);
let d = GossipService::new(&c, None, tn.sockets.gossip, &exit);
let d = GossipService::new(&c, None, tn.sockets.gossip, None, &exit);
exit.store(true, Ordering::Relaxed);
d.join().unwrap();
}

View File

@@ -6,14 +6,21 @@
//! command-line tools to spin up validators and a Rust library
//!
#[macro_use]
extern crate solana_bpf_loader_program;
pub mod accounts_background_service;
pub mod accounts_hash_verifier;
pub mod banking_stage;
pub mod bigtable_upload_service;
pub mod broadcast_stage;
mod builtins;
pub mod cache_block_time_service;
pub mod cluster_info_vote_listener;
pub mod commitment_service;
pub mod completed_data_sets_service;
mod deprecated;
pub mod sample_performance_service;
pub mod shred_fetch_stage;
#[macro_use]
pub mod contact_info;
@@ -27,6 +34,7 @@ pub mod crds_gossip;
pub mod crds_gossip_error;
pub mod crds_gossip_pull;
pub mod crds_gossip_push;
pub mod crds_shards;
pub mod crds_value;
pub mod epoch_slots;
pub mod fetch_stage;
@@ -38,6 +46,7 @@ pub mod ledger_cleanup_service;
pub mod local_vote_signer_service;
pub mod non_circulating_supply;
pub mod optimistic_confirmation_verifier;
pub mod optimistically_confirmed_bank_tracker;
pub mod poh_recorder;
pub mod poh_service;
pub mod progress_map;
@@ -57,12 +66,14 @@ pub mod rpc_pubsub;
pub mod rpc_pubsub_service;
pub mod rpc_service;
pub mod rpc_subscriptions;
pub mod send_transaction_service;
pub mod serve_repair;
pub mod serve_repair_service;
pub mod sigverify;
pub mod sigverify_shreds;
pub mod sigverify_stage;
pub mod snapshot_packager_service;
pub mod test_validator;
pub mod tpu;
pub mod transaction_status_service;
pub mod tree_diff;

View File

@@ -18,7 +18,7 @@ pub fn calculate_non_circulating_supply(bank: &Arc<Bank>) -> NonCirculatingSuppl
let withdraw_authority_list = withdraw_authority();
let clock = bank.clock();
let stake_accounts = bank.get_program_accounts(Some(&solana_stake_program::id()));
let stake_accounts = bank.get_program_accounts(&solana_stake_program::id());
for (pubkey, account) in stake_accounts.iter() {
let stake_account = StakeState::from(&account).unwrap_or_default();
match stake_account {
@@ -78,6 +78,7 @@ solana_sdk::pubkeys!(
"3o6xgkJ9sTmDeQWyfj3sxwon18fXJB9PV5LDc8sfgR4a",
"GumSE5HsMV5HCwBTv2D2D81yy9x17aDkvobkqAfTRgmo",
"AzVV9ZZDxTgW4wWfJmsG6ytaHpQGSe1yz76Nyy84VbQF",
"8CUUMKYNGxdgYio5CLHRHyzMEhhVRMcqefgE6dLqnVRK",
]
);
@@ -98,7 +99,7 @@ mod tests {
use solana_sdk::{
account::Account,
epoch_schedule::EpochSchedule,
genesis_config::{GenesisConfig, OperatingMode},
genesis_config::{ClusterType, GenesisConfig},
};
use solana_stake_program::stake_state::{Authorized, Lockup, Meta, StakeState};
use std::{collections::BTreeMap, sync::Arc};
@@ -149,7 +150,7 @@ mod tests {
let genesis_config = GenesisConfig {
accounts,
epoch_schedule: EpochSchedule::new(slots_per_epoch),
operating_mode: OperatingMode::Stable,
cluster_type: ClusterType::MainnetBeta,
..GenesisConfig::default()
};
let mut bank = Arc::new(Bank::new(&genesis_config));

View File

@@ -0,0 +1,294 @@
//! The `optimistically_confirmed_bank_tracker` module implements a threaded service to track the
//! most recent optimistically confirmed bank for use in rpc services, and triggers gossip
//! subscription notifications
use crate::rpc_subscriptions::RpcSubscriptions;
use crossbeam_channel::{Receiver, RecvTimeoutError, Sender};
use solana_runtime::{bank::Bank, bank_forks::BankForks};
use solana_sdk::clock::Slot;
use std::{
collections::HashSet,
sync::{
atomic::{AtomicBool, Ordering},
Arc, RwLock,
},
thread::{self, Builder, JoinHandle},
time::Duration,
};
pub struct OptimisticallyConfirmedBank {
pub bank: Arc<Bank>,
}
impl OptimisticallyConfirmedBank {
pub fn locked_from_bank_forks_root(bank_forks: &Arc<RwLock<BankForks>>) -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(Self {
bank: bank_forks.read().unwrap().root_bank().clone(),
}))
}
}
pub enum BankNotification {
OptimisticallyConfirmed(Slot),
Frozen(Arc<Bank>),
Root(Arc<Bank>),
}
impl std::fmt::Debug for BankNotification {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
BankNotification::OptimisticallyConfirmed(slot) => {
write!(f, "OptimisticallyConfirmed({:?})", slot)
}
BankNotification::Frozen(bank) => write!(f, "Frozen({})", bank.slot()),
BankNotification::Root(bank) => write!(f, "Root({})", bank.slot()),
}
}
}
pub type BankNotificationReceiver = Receiver<BankNotification>;
pub type BankNotificationSender = Sender<BankNotification>;
pub struct OptimisticallyConfirmedBankTracker {
thread_hdl: JoinHandle<()>,
}
impl OptimisticallyConfirmedBankTracker {
pub fn new(
receiver: BankNotificationReceiver,
exit: &Arc<AtomicBool>,
bank_forks: Arc<RwLock<BankForks>>,
optimistically_confirmed_bank: Arc<RwLock<OptimisticallyConfirmedBank>>,
subscriptions: Arc<RpcSubscriptions>,
) -> Self {
let exit_ = exit.clone();
let mut pending_optimistically_confirmed_banks = HashSet::new();
let thread_hdl = Builder::new()
.name("solana-optimistic-bank-tracker".to_string())
.spawn(move || loop {
if exit_.load(Ordering::Relaxed) {
break;
}
if let Err(RecvTimeoutError::Disconnected) = Self::recv_notification(
&receiver,
&bank_forks,
&optimistically_confirmed_bank,
&subscriptions,
&mut pending_optimistically_confirmed_banks,
) {
break;
}
})
.unwrap();
Self { thread_hdl }
}
fn recv_notification(
receiver: &Receiver<BankNotification>,
bank_forks: &Arc<RwLock<BankForks>>,
optimistically_confirmed_bank: &Arc<RwLock<OptimisticallyConfirmedBank>>,
subscriptions: &Arc<RpcSubscriptions>,
mut pending_optimistically_confirmed_banks: &mut HashSet<Slot>,
) -> Result<(), RecvTimeoutError> {
let notification = receiver.recv_timeout(Duration::from_secs(1))?;
Self::process_notification(
notification,
bank_forks,
optimistically_confirmed_bank,
subscriptions,
&mut pending_optimistically_confirmed_banks,
);
Ok(())
}
pub(crate) fn process_notification(
notification: BankNotification,
bank_forks: &Arc<RwLock<BankForks>>,
optimistically_confirmed_bank: &Arc<RwLock<OptimisticallyConfirmedBank>>,
subscriptions: &Arc<RpcSubscriptions>,
pending_optimistically_confirmed_banks: &mut HashSet<Slot>,
) {
debug!("received bank notification: {:?}", notification);
match notification {
BankNotification::OptimisticallyConfirmed(slot) => {
if let Some(bank) = bank_forks
.read()
.unwrap()
.get(slot)
.filter(|b| b.is_frozen())
{
let mut w_optimistically_confirmed_bank =
optimistically_confirmed_bank.write().unwrap();
if bank.slot() > w_optimistically_confirmed_bank.bank.slot() {
w_optimistically_confirmed_bank.bank = bank.clone();
subscriptions.notify_gossip_subscribers(slot);
}
drop(w_optimistically_confirmed_bank);
} else if slot > bank_forks.read().unwrap().root_bank().slot() {
pending_optimistically_confirmed_banks.insert(slot);
}
}
BankNotification::Frozen(bank) => {
let frozen_slot = bank.slot();
if pending_optimistically_confirmed_banks.remove(&bank.slot()) {
let mut w_optimistically_confirmed_bank =
optimistically_confirmed_bank.write().unwrap();
if frozen_slot > w_optimistically_confirmed_bank.bank.slot() {
w_optimistically_confirmed_bank.bank = bank;
subscriptions.notify_gossip_subscribers(frozen_slot);
}
drop(w_optimistically_confirmed_bank);
}
}
BankNotification::Root(bank) => {
let root_slot = bank.slot();
let mut w_optimistically_confirmed_bank =
optimistically_confirmed_bank.write().unwrap();
if root_slot > w_optimistically_confirmed_bank.bank.slot() {
w_optimistically_confirmed_bank.bank = bank;
}
drop(w_optimistically_confirmed_bank);
pending_optimistically_confirmed_banks.retain(|&s| s > root_slot);
}
}
}
pub fn close(self) -> thread::Result<()> {
self.join()
}
pub fn join(self) -> thread::Result<()> {
self.thread_hdl.join()
}
}
#[cfg(test)]
mod tests {
use super::*;
use solana_ledger::genesis_utils::{create_genesis_config, GenesisConfigInfo};
use solana_runtime::commitment::BlockCommitmentCache;
use solana_sdk::pubkey::Pubkey;
#[test]
fn test_process_notification() {
let exit = Arc::new(AtomicBool::new(false));
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(100);
let bank = Bank::new(&genesis_config);
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
let bank0 = bank_forks.read().unwrap().get(0).unwrap().clone();
let bank1 = Bank::new_from_parent(&bank0, &Pubkey::default(), 1);
bank_forks.write().unwrap().insert(bank1);
let bank1 = bank_forks.read().unwrap().get(1).unwrap().clone();
let bank2 = Bank::new_from_parent(&bank1, &Pubkey::default(), 2);
bank_forks.write().unwrap().insert(bank2);
let bank2 = bank_forks.read().unwrap().get(2).unwrap().clone();
let bank3 = Bank::new_from_parent(&bank2, &Pubkey::default(), 3);
bank_forks.write().unwrap().insert(bank3);
let optimistically_confirmed_bank =
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
let block_commitment_cache = Arc::new(RwLock::new(BlockCommitmentCache::default()));
let subscriptions = Arc::new(RpcSubscriptions::new(
&exit,
bank_forks.clone(),
block_commitment_cache,
optimistically_confirmed_bank.clone(),
));
let mut pending_optimistically_confirmed_banks = HashSet::new();
assert_eq!(optimistically_confirmed_bank.read().unwrap().bank.slot(), 0);
OptimisticallyConfirmedBankTracker::process_notification(
BankNotification::OptimisticallyConfirmed(2),
&bank_forks,
&optimistically_confirmed_bank,
&subscriptions,
&mut pending_optimistically_confirmed_banks,
);
assert_eq!(optimistically_confirmed_bank.read().unwrap().bank.slot(), 2);
// Test max optimistically confirmed bank remains in the cache
OptimisticallyConfirmedBankTracker::process_notification(
BankNotification::OptimisticallyConfirmed(1),
&bank_forks,
&optimistically_confirmed_bank,
&subscriptions,
&mut pending_optimistically_confirmed_banks,
);
assert_eq!(optimistically_confirmed_bank.read().unwrap().bank.slot(), 2);
// Test bank will only be cached when frozen
OptimisticallyConfirmedBankTracker::process_notification(
BankNotification::OptimisticallyConfirmed(3),
&bank_forks,
&optimistically_confirmed_bank,
&subscriptions,
&mut pending_optimistically_confirmed_banks,
);
assert_eq!(optimistically_confirmed_bank.read().unwrap().bank.slot(), 2);
assert_eq!(pending_optimistically_confirmed_banks.len(), 1);
assert_eq!(pending_optimistically_confirmed_banks.contains(&3), true);
// Test bank will only be cached when frozen
let bank3 = bank_forks.read().unwrap().get(3).unwrap().clone();
OptimisticallyConfirmedBankTracker::process_notification(
BankNotification::Frozen(bank3),
&bank_forks,
&optimistically_confirmed_bank,
&subscriptions,
&mut pending_optimistically_confirmed_banks,
);
assert_eq!(optimistically_confirmed_bank.read().unwrap().bank.slot(), 3);
// Test higher root will be cached and clear pending_optimistically_confirmed_banks
let bank3 = bank_forks.read().unwrap().get(3).unwrap().clone();
let bank4 = Bank::new_from_parent(&bank3, &Pubkey::default(), 4);
bank_forks.write().unwrap().insert(bank4);
OptimisticallyConfirmedBankTracker::process_notification(
BankNotification::OptimisticallyConfirmed(4),
&bank_forks,
&optimistically_confirmed_bank,
&subscriptions,
&mut pending_optimistically_confirmed_banks,
);
assert_eq!(optimistically_confirmed_bank.read().unwrap().bank.slot(), 3);
assert_eq!(pending_optimistically_confirmed_banks.len(), 1);
assert_eq!(pending_optimistically_confirmed_banks.contains(&4), true);
let bank4 = bank_forks.read().unwrap().get(4).unwrap().clone();
let bank5 = Bank::new_from_parent(&bank4, &Pubkey::default(), 5);
bank_forks.write().unwrap().insert(bank5);
let bank5 = bank_forks.read().unwrap().get(5).unwrap().clone();
OptimisticallyConfirmedBankTracker::process_notification(
BankNotification::Root(bank5),
&bank_forks,
&optimistically_confirmed_bank,
&subscriptions,
&mut pending_optimistically_confirmed_banks,
);
assert_eq!(optimistically_confirmed_bank.read().unwrap().bank.slot(), 5);
assert_eq!(pending_optimistically_confirmed_banks.len(), 0);
assert_eq!(pending_optimistically_confirmed_banks.contains(&4), false);
// Banks <= root do not get added to pending list, even if not frozen
let bank5 = bank_forks.read().unwrap().get(5).unwrap().clone();
let bank6 = Bank::new_from_parent(&bank5, &Pubkey::default(), 6);
bank_forks.write().unwrap().insert(bank6);
let bank5 = bank_forks.read().unwrap().get(5).unwrap().clone();
let bank7 = Bank::new_from_parent(&bank5, &Pubkey::default(), 7);
bank_forks.write().unwrap().insert(bank7);
bank_forks.write().unwrap().set_root(7, &None, None);
OptimisticallyConfirmedBankTracker::process_notification(
BankNotification::OptimisticallyConfirmed(6),
&bank_forks,
&optimistically_confirmed_bank,
&subscriptions,
&mut pending_optimistically_confirmed_banks,
);
assert_eq!(optimistically_confirmed_bank.read().unwrap().bank.slot(), 5);
assert_eq!(pending_optimistically_confirmed_banks.len(), 0);
assert_eq!(pending_optimistically_confirmed_banks.contains(&6), false);
}
}

View File

@@ -168,7 +168,7 @@ impl ForkProgress {
num_dropped_blocks_on_fork: u64,
) -> Self {
let validator_fork_info = {
if bank.collector_id() == my_pubkey && bank.slot() > 0 {
if bank.collector_id() == my_pubkey {
let stake = bank.epoch_vote_account_stake(voting_pubkey);
Some(ValidatorStakeInfo::new(
*voting_pubkey,

View File

@@ -3,6 +3,7 @@
use crate::{
bank_weight_fork_choice::BankWeightForkChoice,
broadcast_stage::RetransmitSlotsSender,
cache_block_time_service::CacheBlockTimeSender,
cluster_info::ClusterInfo,
cluster_info_vote_listener::VoteTracker,
cluster_slots::ClusterSlots,
@@ -10,6 +11,7 @@ use crate::{
consensus::{ComputedBankState, Stake, SwitchForkDecision, Tower, VotedStakes},
fork_choice::{ForkChoice, SelectVoteAndResetForkResult},
heaviest_subtree_fork_choice::HeaviestSubtreeForkChoice,
optimistically_confirmed_bank_tracker::{BankNotification, BankNotificationSender},
poh_recorder::{PohRecorder, GRACE_TICKS_FACTOR, MAX_GRACE_SLOTS},
progress_map::{ForkProgress, ProgressMap, PropagatedStats},
pubkey_references::PubkeyReferences,
@@ -33,7 +35,7 @@ use solana_runtime::{
};
use solana_sdk::{
clock::{Slot, NUM_CONSECUTIVE_LEADER_SLOTS},
genesis_config::OperatingMode,
genesis_config::ClusterType,
hash::Hash,
pubkey::Pubkey,
signature::{Keypair, Signer},
@@ -106,6 +108,8 @@ pub struct ReplayStageConfig {
pub block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
pub transaction_status_sender: Option<TransactionStatusSender>,
pub rewards_recorder_sender: Option<RewardsRecorderSender>,
pub cache_block_time_sender: Option<CacheBlockTimeSender>,
pub bank_notification_sender: Option<BankNotificationSender>,
}
#[derive(Default)]
@@ -235,6 +239,8 @@ impl ReplayStage {
block_commitment_cache,
transaction_status_sender,
rewards_recorder_sender,
cache_block_time_sender,
bank_notification_sender,
} = config;
trace!("replay stage");
@@ -281,7 +287,7 @@ impl ReplayStage {
let root_bank = bank_forks.read().unwrap().root_bank().clone();
let root = root_bank.slot();
let unlock_heaviest_subtree_fork_choice_slot =
Self::get_unlock_heaviest_subtree_fork_choice(root_bank.operating_mode());
Self::get_unlock_heaviest_subtree_fork_choice(root_bank.cluster_type());
let mut heaviest_subtree_fork_choice =
HeaviestSubtreeForkChoice::new_from_frozen_banks(root, &frozen_banks);
let mut bank_weight_fork_choice = BankWeightForkChoice::default();
@@ -341,8 +347,8 @@ impl ReplayStage {
transaction_status_sender.clone(),
&verify_recyclers,
&mut heaviest_subtree_fork_choice,
&subscriptions,
&replay_vote_sender,
&bank_notification_sender,
);
replay_active_banks_time.stop();
Self::report_memory(&allocated, "replay_active_banks", start);
@@ -494,6 +500,8 @@ impl ReplayStage {
&subscriptions,
&block_commitment_cache,
&mut heaviest_subtree_fork_choice,
&cache_block_time_sender,
&bank_notification_sender,
)?;
};
voting_time.stop();
@@ -1004,6 +1012,8 @@ impl ReplayStage {
subscriptions: &Arc<RpcSubscriptions>,
block_commitment_cache: &Arc<RwLock<BlockCommitmentCache>>,
heaviest_subtree_fork_choice: &mut HeaviestSubtreeForkChoice,
cache_block_time_sender: &Option<CacheBlockTimeSender>,
bank_notification_sender: &Option<BankNotificationSender>,
) -> Result<()> {
if bank.is_empty() {
inc_new_counter_info!("replay_stage-voted_empty_bank", 1);
@@ -1019,7 +1029,7 @@ impl ReplayStage {
.expect("Root bank doesn't exist")
.clone();
let mut rooted_banks = root_bank.parents();
rooted_banks.push(root_bank);
rooted_banks.push(root_bank.clone());
let rooted_slots: Vec<_> = rooted_banks.iter().map(|bank| bank.slot()).collect();
// Call leader schedule_cache.set_root() before blockstore.set_root() because
// bank_forks.root is consumed by repair_service to update gossip, so we don't want to
@@ -1029,6 +1039,12 @@ impl ReplayStage {
blockstore
.set_roots(&rooted_slots)
.expect("Ledger set roots failed");
Self::cache_block_times(
blockstore,
bank_forks,
&rooted_slots,
cache_block_time_sender,
);
let highest_confirmed_root = Some(
block_commitment_cache
.read()
@@ -1045,6 +1061,11 @@ impl ReplayStage {
heaviest_subtree_fork_choice,
);
subscriptions.notify_roots(rooted_slots);
if let Some(sender) = bank_notification_sender {
sender
.send(BankNotification::Root(root_bank))
.unwrap_or_else(|err| warn!("bank_notification_sender failed: {:?}", err));
}
latest_root_senders.iter().for_each(|s| {
if let Err(e) = s.send(new_root) {
trace!("latest root send failed: {:?}", e);
@@ -1129,7 +1150,7 @@ impl ReplayStage {
let node_keypair = cluster_info.keypair.clone();
// Send our last few votes along with the new one
let vote_ix = if bank.slot() > Self::get_unlock_switch_vote_slot(bank.operating_mode()) {
let vote_ix = if bank.slot() > Self::get_unlock_switch_vote_slot(bank.cluster_type()) {
switch_fork_decision
.to_vote_instruction(
vote,
@@ -1211,8 +1232,8 @@ impl ReplayStage {
transaction_status_sender: Option<TransactionStatusSender>,
verify_recyclers: &VerifyRecyclers,
heaviest_subtree_fork_choice: &mut HeaviestSubtreeForkChoice,
subscriptions: &Arc<RpcSubscriptions>,
replay_vote_sender: &ReplayVoteSender,
bank_notification_sender: &Option<BankNotificationSender>,
) -> bool {
let mut did_complete_bank = false;
let mut tx_count = 0;
@@ -1283,7 +1304,11 @@ impl ReplayStage {
bank.freeze();
heaviest_subtree_fork_choice
.add_new_leaf_slot(bank.slot(), Some(bank.parent_slot()));
subscriptions.notify_frozen(bank.slot());
if let Some(sender) = bank_notification_sender {
sender
.send(BankNotification::Frozen(bank.clone()))
.unwrap_or_else(|err| warn!("bank_notification_sender failed: {:?}", err));
}
} else {
trace!(
"bank {} not completed tick_height: {}, max_tick_height: {}",
@@ -1583,13 +1608,13 @@ impl ReplayStage {
loop {
// These cases mean confirmation of propagation on any earlier
// leader blocks must have been reached
if current_leader_slot == None || current_leader_slot.unwrap() <= root {
if current_leader_slot == None || current_leader_slot.unwrap() < root {
break;
}
let leader_propagated_stats = progress
.get_propagated_stats_mut(current_leader_slot.unwrap())
.expect("current_leader_slot > root, so must exist in the progress map");
.expect("current_leader_slot >= root, so must exist in the progress map");
// If a descendant has reached propagation threshold, then
// all its ancestor banks have also reached propagation
@@ -1855,23 +1880,55 @@ impl ReplayStage {
}
}
pub fn get_unlock_switch_vote_slot(operating_mode: OperatingMode) -> Slot {
match operating_mode {
OperatingMode::Development => 0,
// 400_000 slots into epoch 61
OperatingMode::Stable => 26_752_000,
// Epoch 63
OperatingMode::Preview => 21_692_256,
fn cache_block_times(
blockstore: &Arc<Blockstore>,
bank_forks: &Arc<RwLock<BankForks>>,
rooted_slots: &[Slot],
cache_block_time_sender: &Option<CacheBlockTimeSender>,
) {
if let Some(cache_block_time_sender) = cache_block_time_sender {
for slot in rooted_slots {
if blockstore
.get_block_time(*slot)
.unwrap_or_default()
.is_none()
{
if let Some(rooted_bank) = bank_forks.read().unwrap().get(*slot) {
cache_block_time_sender
.send(rooted_bank.clone())
.unwrap_or_else(|err| {
warn!("cache_block_time_sender failed: {:?}", err)
});
} else {
error!(
"rooted_bank {:?} not available in BankForks; block time not cached",
slot
);
}
}
}
}
}
pub fn get_unlock_heaviest_subtree_fork_choice(operating_mode: OperatingMode) -> Slot {
match operating_mode {
OperatingMode::Development => 0,
// 400_000 slots into epoch 61
OperatingMode::Stable => 26_752_000,
pub fn get_unlock_switch_vote_slot(cluster_type: ClusterType) -> Slot {
match cluster_type {
ClusterType::Development => 0,
ClusterType::Devnet => 0,
// Epoch 63
OperatingMode::Preview => 21_692_256,
ClusterType::Testnet => 21_692_256,
// 400_000 slots into epoch 61
ClusterType::MainnetBeta => 26_752_000,
}
}
pub fn get_unlock_heaviest_subtree_fork_choice(cluster_type: ClusterType) -> Slot {
match cluster_type {
ClusterType::Development => 0,
ClusterType::Devnet => 0,
// Epoch 63
ClusterType::Testnet => 21_692_256,
// 400_000 slots into epoch 61
ClusterType::MainnetBeta => 26_752_000,
}
}
@@ -1887,6 +1944,7 @@ pub(crate) mod tests {
use crate::{
consensus::test::{initialize_state, VoteSimulator},
consensus::Tower,
optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
progress_map::ValidatorStakeInfo,
replay_stage::ReplayStage,
transaction_status_service::TransactionStatusService,
@@ -1918,7 +1976,7 @@ pub(crate) mod tests {
system_transaction,
transaction::TransactionError,
};
use solana_transaction_status::{EncodedTransaction, TransactionWithStatusMeta};
use solana_transaction_status::TransactionWithStatusMeta;
use solana_vote_program::{
vote_state::{VoteState, VoteStateVersions},
vote_transaction,
@@ -1999,11 +2057,14 @@ pub(crate) mod tests {
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank0)));
// RpcSubscriptions
let optimistically_confirmed_bank =
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
let exit = Arc::new(AtomicBool::new(false));
let rpc_subscriptions = Arc::new(RpcSubscriptions::new(
&exit,
bank_forks.clone(),
Arc::new(RwLock::new(BlockCommitmentCache::default())),
optimistically_confirmed_bank,
));
ReplayBlockstoreComponents {
@@ -2494,6 +2555,7 @@ pub(crate) mod tests {
&exit,
bank_forks.clone(),
block_commitment_cache.clone(),
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
));
let (lockouts_sender, _) =
AggregateCommitmentService::new(&exit, block_commitment_cache.clone(), subscriptions);
@@ -2658,36 +2720,26 @@ pub(crate) mod tests {
blockstore.clone(),
);
let confirmed_block = blockstore.get_confirmed_block(slot, None).unwrap();
let confirmed_block = blockstore.get_confirmed_block(slot).unwrap();
assert_eq!(confirmed_block.transactions.len(), 3);
for TransactionWithStatusMeta { transaction, meta } in
confirmed_block.transactions.into_iter()
{
if let EncodedTransaction::Json(transaction) = transaction {
if transaction.signatures[0] == signatures[0].to_string() {
let meta = meta.unwrap();
assert_eq!(meta.err, None);
assert_eq!(meta.status, Ok(()));
} else if transaction.signatures[0] == signatures[1].to_string() {
let meta = meta.unwrap();
assert_eq!(
meta.err,
Some(TransactionError::InstructionError(
0,
InstructionError::Custom(1)
))
);
assert_eq!(
meta.status,
Err(TransactionError::InstructionError(
0,
InstructionError::Custom(1)
))
);
} else {
assert_eq!(meta, None);
}
if transaction.signatures[0] == signatures[0] {
let meta = meta.unwrap();
assert_eq!(meta.status, Ok(()));
} else if transaction.signatures[0] == signatures[1] {
let meta = meta.unwrap();
assert_eq!(
meta.status,
Err(TransactionError::InstructionError(
0,
InstructionError::Custom(1)
))
);
} else {
assert_eq!(meta, None);
}
}
}
@@ -3276,6 +3328,10 @@ pub(crate) mod tests {
let stake_per_validator = 10_000;
let (mut bank_forks, mut progress_map, _) =
initialize_state(&keypairs, stake_per_validator);
progress_map
.get_propagated_stats_mut(0)
.unwrap()
.is_leader_slot = true;
bank_forks.set_root(0, &None, None);
let total_epoch_stake = bank_forks.root_bank().total_epoch_stake();
@@ -3353,6 +3409,10 @@ pub(crate) mod tests {
let stake_per_validator = 10_000;
let (mut bank_forks, mut progress_map, _) =
initialize_state(&keypairs, stake_per_validator);
progress_map
.get_propagated_stats_mut(0)
.unwrap()
.is_leader_slot = true;
bank_forks.set_root(0, &None, None);
let total_epoch_stake = num_validators as u64 * stake_per_validator;
@@ -3685,6 +3745,70 @@ pub(crate) mod tests {
}
}
#[test]
fn test_leader_snapshot_restart_propagation() {
let ReplayBlockstoreComponents {
validator_voting_keys,
mut progress,
bank_forks,
leader_schedule_cache,
..
} = replay_blockstore_components();
let root_bank = bank_forks.read().unwrap().root_bank().clone();
let my_pubkey = leader_schedule_cache
.slot_leader_at(root_bank.slot(), Some(&root_bank))
.unwrap();
// Check that we are the leader of the root bank
assert!(
progress
.get_propagated_stats(root_bank.slot())
.unwrap()
.is_leader_slot
);
let ancestors = bank_forks.read().unwrap().ancestors();
// Freeze bank so it shows up in frozen banks
root_bank.freeze();
let mut frozen_banks: Vec<_> = bank_forks
.read()
.unwrap()
.frozen_banks()
.values()
.cloned()
.collect();
// Compute bank stats, make sure vote is propagated back to starting root bank
let vote_tracker = VoteTracker::default();
// Add votes
for vote_key in validator_voting_keys.values() {
vote_tracker.insert_vote(root_bank.slot(), Arc::new(*vote_key));
}
assert!(!progress.is_propagated(root_bank.slot()));
// Update propagation status
let tower = Tower::new_for_tests(0, 0.67);
ReplayStage::compute_bank_stats(
&my_pubkey,
&ancestors,
&mut frozen_banks,
&tower,
&mut progress,
&vote_tracker,
&ClusterSlots::default(),
&bank_forks,
&mut PubkeyReferences::default(),
&mut HeaviestSubtreeForkChoice::new_from_bank_forks(&bank_forks.read().unwrap()),
&mut BankWeightForkChoice::default(),
);
// Check status is true
assert!(progress.is_propagated(root_bank.slot()));
}
fn setup_forks() -> (RwLock<BankForks>, ProgressMap) {
/*
Build fork structure:

View File

@@ -1,5 +1,6 @@
use crossbeam_channel::{Receiver, RecvTimeoutError, Sender};
use solana_ledger::blockstore::Blockstore;
use solana_runtime::bank::RewardInfo;
use solana_sdk::{clock::Slot, pubkey::Pubkey};
use solana_transaction_status::Reward;
use std::{
@@ -11,8 +12,8 @@ use std::{
time::Duration,
};
pub type RewardsRecorderReceiver = Receiver<(Slot, Vec<(Pubkey, i64)>)>;
pub type RewardsRecorderSender = Sender<(Slot, Vec<(Pubkey, i64)>)>;
pub type RewardsRecorderReceiver = Receiver<(Slot, Vec<(Pubkey, RewardInfo)>)>;
pub type RewardsRecorderSender = Sender<(Slot, Vec<(Pubkey, RewardInfo)>)>;
pub struct RewardsRecorderService {
thread_hdl: JoinHandle<()>,
@@ -49,9 +50,10 @@ impl RewardsRecorderService {
let (slot, rewards) = rewards_receiver.recv_timeout(Duration::from_secs(1))?;
let rpc_rewards = rewards
.into_iter()
.map(|(pubkey, lamports)| Reward {
.map(|(pubkey, reward_info)| Reward {
pubkey: pubkey.to_string(),
lamports,
lamports: reward_info.lamports,
post_balance: reward_info.post_balance,
})
.collect();

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,13 @@
use jsonrpc_core::{Error, ErrorCode};
use solana_client::rpc_response::RpcSimulateTransactionResult;
use solana_sdk::clock::Slot;
const JSON_RPC_SERVER_ERROR_1: i64 = -32001;
const JSON_RPC_SERVER_ERROR_2: i64 = -32002;
const JSON_RPC_SERVER_ERROR_3: i64 = -32003;
const JSON_RPC_SERVER_ERROR_4: i64 = -32004;
const JSON_RPC_SERVER_ERROR_5: i64 = -32005;
const JSON_RPC_SERVER_ERROR_6: i64 = -32006;
pub enum RpcCustomError {
BlockCleanedUp {
@@ -13,11 +16,14 @@ pub enum RpcCustomError {
},
SendTransactionPreflightFailure {
message: String,
result: RpcSimulateTransactionResult,
},
SendTransactionIsNotSigned,
TransactionSignatureVerificationFailure,
BlockNotAvailable {
slot: Slot,
},
RpcNodeUnhealthy,
TransactionPrecompileVerificationFailure(solana_sdk::transaction::TransactionError),
}
impl From<RpcCustomError> for Error {
@@ -34,19 +40,29 @@ impl From<RpcCustomError> for Error {
),
data: None,
},
RpcCustomError::SendTransactionPreflightFailure { message } => Self {
RpcCustomError::SendTransactionPreflightFailure { message, result } => Self {
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_2),
message,
data: None,
data: Some(serde_json::json!(result)),
},
RpcCustomError::SendTransactionIsNotSigned => Self {
RpcCustomError::TransactionSignatureVerificationFailure => Self {
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_3),
message: "Transaction is not signed".to_string(),
message: "Transaction signature verification failure".to_string(),
data: None,
},
RpcCustomError::BlockNotAvailable { slot } => Self {
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_4),
message: format!("Block not available for slot {}", slot,),
message: format!("Block not available for slot {}", slot),
data: None,
},
RpcCustomError::RpcNodeUnhealthy => Self {
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_5),
message: "RPC node is unhealthy".to_string(),
data: None,
},
RpcCustomError::TransactionPrecompileVerificationFailure(e) => Self {
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_6),
message: format!("Transaction precompile verification failure {:?}", e),
data: None,
},
}

View File

@@ -1,13 +1,13 @@
//! The `pubsub` module implements a threaded subscription service on client RPC request
use crate::rpc_subscriptions::{RpcSubscriptions, RpcVote, SlotInfo};
use crate::rpc_subscriptions::{RpcSubscriptions, RpcVote};
use jsonrpc_core::{Error, ErrorCode, Result};
use jsonrpc_derive::rpc;
use jsonrpc_pubsub::{typed::Subscriber, Session, SubscriptionId};
use solana_account_decoder::UiAccount;
use solana_client::{
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSignatureSubscribeConfig},
rpc_response::{Response as RpcResponse, RpcKeyedAccount, RpcSignatureResult},
rpc_response::{Response as RpcResponse, RpcKeyedAccount, RpcSignatureResult, SlotInfo},
};
#[cfg(test)]
use solana_runtime::bank_forks::BankForks;
@@ -354,6 +354,7 @@ mod tests {
use super::*;
use crate::{
cluster_info_vote_listener::{ClusterInfoVoteListener, VoteTracker},
optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
rpc_subscriptions::tests::robust_poll_or_panic,
};
use crossbeam_channel::unbounded;
@@ -362,7 +363,7 @@ mod tests {
use serial_test_derive::serial;
use solana_account_decoder::{parse_account_data::parse_account_data, UiAccountEncoding};
use solana_budget_program::{self, budget_instruction};
use solana_client::rpc_response::ProcessedSignatureResult;
use solana_client::rpc_response::{ProcessedSignatureResult, ReceivedSignatureResult};
use solana_runtime::{
bank::Bank,
bank_forks::BankForks,
@@ -428,6 +429,7 @@ mod tests {
&Arc::new(AtomicBool::new(false)),
bank_forks.clone(),
Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests())),
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
)),
uid: Arc::new(atomic::AtomicUsize::default()),
};
@@ -444,7 +446,7 @@ mod tests {
// Test signature confirmation notification
let (response, _) = robust_poll_or_panic(receiver);
let expected_res =
RpcSignatureResult::ProcessedSignatureResult(ProcessedSignatureResult { err: None });
RpcSignatureResult::ProcessedSignature(ProcessedSignatureResult { err: None });
let expected = json!({
"jsonrpc": "2.0",
"method": "signatureNotification",
@@ -476,7 +478,8 @@ mod tests {
.notify_signatures_received((received_slot, vec![tx.signatures[0]]));
// Test signature confirmation notification
let (response, _) = robust_poll_or_panic(receiver);
let expected_res = RpcSignatureResult::ReceivedSignature;
let expected_res =
RpcSignatureResult::ReceivedSignature(ReceivedSignatureResult::ReceivedSignature);
let expected = json!({
"jsonrpc": "2.0",
"method": "signatureNotification",
@@ -569,6 +572,7 @@ mod tests {
Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests_with_slots(
1, 1,
))),
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
)),
uid: Arc::new(atomic::AtomicUsize::default()),
};
@@ -679,6 +683,7 @@ mod tests {
Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests_with_slots(
1, 1,
))),
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
)),
uid: Arc::new(atomic::AtomicUsize::default()),
};
@@ -801,6 +806,7 @@ mod tests {
&exit,
bank_forks.clone(),
Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests())),
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
);
rpc.subscriptions = Arc::new(subscriptions);
let session = create_session();
@@ -850,8 +856,12 @@ mod tests {
let exit = Arc::new(AtomicBool::new(false));
let block_commitment_cache = Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests()));
let subscriptions =
RpcSubscriptions::new(&exit, bank_forks.clone(), block_commitment_cache);
let subscriptions = RpcSubscriptions::new(
&exit,
bank_forks.clone(),
block_commitment_cache,
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
);
rpc.subscriptions = Arc::new(subscriptions);
let session = create_session();
let (subscriber, _id_receiver, receiver) = Subscriber::new_test("accountNotification");
@@ -994,7 +1004,14 @@ mod tests {
let (subscriber, _id_receiver, receiver) = Subscriber::new_test("voteNotification");
// Setup Subscriptions
let subscriptions = RpcSubscriptions::new(&exit, bank_forks, block_commitment_cache);
let optimistically_confirmed_bank =
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
let subscriptions = RpcSubscriptions::new(
&exit,
bank_forks,
block_commitment_cache,
optimistically_confirmed_bank,
);
rpc.subscriptions = Arc::new(subscriptions);
rpc.vote_subscribe(session, subscriber);

View File

@@ -16,12 +16,35 @@ use std::{
time::Duration,
};
#[derive(Debug, Clone)]
pub struct PubSubConfig {
// See the corresponding fields in
// https://github.com/paritytech/ws-rs/blob/be4d47575bae55c60d9f51b47480d355492a94fc/src/lib.rs#L131
// for a complete description of each field in this struct
pub max_connections: usize,
pub max_fragment_size: usize,
pub max_in_buffer_capacity: usize,
pub max_out_buffer_capacity: usize,
}
impl Default for PubSubConfig {
fn default() -> Self {
Self {
max_connections: 1000, // Arbitrary, default of 100 is too low
max_fragment_size: 50 * 1024, // 50KB
max_in_buffer_capacity: 50 * 1024, // 50KB
max_out_buffer_capacity: 15 * 1024 * 1024, // max account size (10MB), then 5MB extra for base64 encoding overhead/etc
}
}
}
pub struct PubSubService {
thread_hdl: JoinHandle<()>,
}
impl PubSubService {
pub fn new(
pubsub_config: PubSubConfig,
subscriptions: &Arc<RpcSubscriptions>,
pubsub_addr: SocketAddr,
exit: &Arc<AtomicBool>,
@@ -29,6 +52,20 @@ impl PubSubService {
info!("rpc_pubsub bound to {:?}", pubsub_addr);
let rpc = RpcSolPubSubImpl::new(subscriptions.clone());
let exit_ = exit.clone();
// TODO: Once https://github.com/paritytech/jsonrpc/pull/594 lands, use
// `ServerBuilder::max_in_buffer_capacity()` and `Server::max_out_buffer_capacity() methods
// instead of only `ServerBuilder::max_payload`
let max_payload = *[
pubsub_config.max_fragment_size,
pubsub_config.max_in_buffer_capacity,
pubsub_config.max_out_buffer_capacity,
]
.iter()
.max()
.unwrap();
info!("rpc_pubsub max_payload: {}", max_payload);
let thread_hdl = Builder::new()
.name("solana-pubsub".to_string())
.spawn(move || {
@@ -36,19 +73,24 @@ impl PubSubService {
io.extend_with(rpc.to_delegate());
let server = ServerBuilder::with_meta_extractor(io, |context: &RequestContext| {
info!("New pubsub connection");
let session = Arc::new(Session::new(context.sender()));
session.on_drop(|| {
info!("Pubsub connection dropped");
});
session
info!("New pubsub connection");
let session = Arc::new(Session::new(context.sender()));
session.on_drop(|| {
info!("Pubsub connection dropped");
});
session
})
.max_connections(1000) // Arbitrary, default of 100 is too low
.max_payload(10 * 1024 * 1024 + 1024) // max account size (10MB) + extra (1K)
.max_connections(pubsub_config.max_connections)
.max_payload(max_payload)
.start(&pubsub_addr);
if let Err(e) = server {
warn!("Pubsub service unavailable error: {:?}. \nAlso, check that port {} is not already in use by another application", e, pubsub_addr.port());
warn!(
"Pubsub service unavailable error: {:?}. \n\
Also, check that port {} is not already in use by another application",
e,
pubsub_addr.port()
);
return;
}
while !exit_.load(Ordering::Relaxed) {
@@ -72,6 +114,7 @@ impl PubSubService {
#[cfg(test)]
mod tests {
use super::*;
use crate::optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank;
use solana_runtime::{
bank::Bank,
bank_forks::BankForks,
@@ -90,12 +133,16 @@ mod tests {
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
let bank = Bank::new(&genesis_config);
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
let optimistically_confirmed_bank =
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
let subscriptions = Arc::new(RpcSubscriptions::new(
&exit,
bank_forks,
Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests())),
optimistically_confirmed_bank,
));
let pubsub_service = PubSubService::new(&subscriptions, pubsub_addr, &exit);
let pubsub_service =
PubSubService::new(PubSubConfig::default(), &subscriptions, pubsub_addr, &exit);
let thread = pubsub_service.thread_hdl.thread();
assert_eq!(thread.name().unwrap(), "solana-pubsub");
}

View File

@@ -1,6 +1,15 @@
//! The `rpc_service` module implements the Solana JSON RPC service.
use crate::{cluster_info::ClusterInfo, rpc::*, rpc_health::*, validator::ValidatorExit};
use crate::{
bigtable_upload_service::BigTableUploadService,
cluster_info::ClusterInfo,
optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
poh_recorder::PohRecorder,
rpc::*,
rpc_health::*,
send_transaction_service::{LeaderInfo, SendTransactionService},
validator::ValidatorExit,
};
use jsonrpc_core::MetaIoHandler;
use jsonrpc_http_server::{
hyper, AccessControlAllowOrigin, CloseHandle, DomainsValidation, RequestMiddleware,
@@ -11,7 +20,6 @@ use solana_ledger::blockstore::Blockstore;
use solana_runtime::{
bank_forks::{BankForks, SnapshotConfig},
commitment::BlockCommitmentCache,
send_transaction_service::SendTransactionService,
snapshot_utils,
};
use solana_sdk::{hash::Hash, native_token::lamports_to_sol, pubkey::Pubkey};
@@ -20,7 +28,7 @@ use std::{
net::SocketAddr,
path::{Path, PathBuf},
sync::atomic::{AtomicBool, Ordering},
sync::{mpsc::channel, Arc, RwLock},
sync::{mpsc::channel, Arc, Mutex, RwLock},
thread::{self, Builder, JoinHandle},
};
use tokio::runtime;
@@ -236,11 +244,13 @@ impl JsonRpcService {
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
blockstore: Arc<Blockstore>,
cluster_info: Arc<ClusterInfo>,
poh_recorder: Option<Arc<Mutex<PohRecorder>>>,
genesis_hash: Hash,
ledger_path: &Path,
validator_exit: Arc<RwLock<Option<ValidatorExit>>>,
trusted_validators: Option<HashSet<Pubkey>>,
override_health_check: Arc<AtomicBool>,
optimistically_confirmed_bank: Arc<RwLock<OptimisticallyConfirmedBank>>,
) -> Self {
info!("rpc bound to {:?}", rpc_addr);
info!("rpc configuration: {:?}", config);
@@ -260,20 +270,37 @@ impl JsonRpcService {
.build()
.expect("Runtime");
let bigtable_ledger_storage = if config.enable_bigtable_ledger_storage {
runtime
.block_on(solana_storage_bigtable::LedgerStorage::new(false))
.map(|x| {
info!("BigTable ledger storage initialized");
Some(x)
})
.unwrap_or_else(|err| {
error!("Failed to initialize BigTable ledger storage: {:?}", err);
None
})
} else {
None
};
let exit_bigtable_ledger_upload_service = Arc::new(AtomicBool::new(false));
let (bigtable_ledger_storage, _bigtable_ledger_upload_service) =
if config.enable_bigtable_ledger_storage || config.enable_bigtable_ledger_upload {
runtime
.block_on(solana_storage_bigtable::LedgerStorage::new(
!config.enable_bigtable_ledger_upload,
))
.map(|bigtable_ledger_storage| {
info!("BigTable ledger storage initialized");
let bigtable_ledger_upload_service = Arc::new(BigTableUploadService::new(
runtime.handle().clone(),
bigtable_ledger_storage.clone(),
blockstore.clone(),
block_commitment_cache.clone(),
exit_bigtable_ledger_upload_service.clone(),
));
(
Some(bigtable_ledger_storage),
Some(bigtable_ledger_upload_service),
)
})
.unwrap_or_else(|err| {
error!("Failed to initialize BigTable ledger storage: {:?}", err);
(None, None)
})
} else {
(None, None)
};
let (request_processor, receiver) = JsonRpcRequestProcessor::new(
config,
@@ -282,17 +309,19 @@ impl JsonRpcService {
blockstore,
validator_exit.clone(),
health.clone(),
cluster_info,
cluster_info.clone(),
genesis_hash,
&runtime,
bigtable_ledger_storage,
optimistically_confirmed_bank,
);
let exit_send_transaction_service = Arc::new(AtomicBool::new(false));
let leader_info =
poh_recorder.map(|recorder| LeaderInfo::new(cluster_info.clone(), recorder));
let _send_transaction_service = Arc::new(SendTransactionService::new(
tpu_address,
&bank_forks,
&exit_send_transaction_service,
leader_info,
receiver,
));
@@ -325,6 +354,7 @@ impl JsonRpcService {
]))
.cors_max_age(86400)
.request_middleware(request_middleware)
.max_request_body_size(MAX_REQUEST_PAYLOAD_SIZE)
.start_http(&rpc_addr);
if let Err(e) = server {
@@ -340,7 +370,7 @@ impl JsonRpcService {
let server = server.unwrap();
close_handle_sender.send(server.close_handle()).unwrap();
server.wait();
exit_send_transaction_service.store(true, Ordering::Relaxed);
exit_bigtable_ledger_upload_service.store(true, Ordering::Relaxed);
})
.unwrap();
@@ -386,7 +416,7 @@ mod tests {
use solana_runtime::{
bank::Bank, bank_forks::CompressionType, snapshot_utils::SnapshotVersion,
};
use solana_sdk::{genesis_config::OperatingMode, signature::Signer};
use solana_sdk::{genesis_config::ClusterType, signature::Signer};
use std::net::{IpAddr, Ipv4Addr};
#[test]
@@ -409,6 +439,8 @@ mod tests {
let ledger_path = get_tmp_ledger_path!();
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
let block_commitment_cache = Arc::new(RwLock::new(BlockCommitmentCache::default()));
let optimistically_confirmed_bank =
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
let mut rpc_service = JsonRpcService::new(
rpc_addr,
JsonRpcConfig::default(),
@@ -417,11 +449,13 @@ mod tests {
block_commitment_cache,
blockstore,
cluster_info,
None,
Hash::default(),
&PathBuf::from("farf"),
validator_exit,
None,
Arc::new(AtomicBool::new(false)),
optimistically_confirmed_bank,
);
let thread = rpc_service.thread_hdl.thread();
assert_eq!(thread.name().unwrap(), "solana-jsonrpc");
@@ -441,7 +475,7 @@ mod tests {
let GenesisConfigInfo {
mut genesis_config, ..
} = create_genesis_config(10_000);
genesis_config.operating_mode = OperatingMode::Stable;
genesis_config.cluster_type = ClusterType::MainnetBeta;
let bank = Bank::new(&genesis_config);
Arc::new(RwLock::new(BankForks::new(bank)))
}
@@ -452,11 +486,7 @@ mod tests {
assert_eq!(None, process_rest(&bank_forks, "not-a-supported-rest-api"));
assert_eq!(
Some("0.000010127".to_string()),
process_rest(&bank_forks, "/v0/circulating-supply")
);
assert_eq!(
Some("0.000010127".to_string()),
process_rest(&bank_forks, "/v0/circulating-supply"),
process_rest(&bank_forks, "/v0/total-supply")
);
}

View File

@@ -1,6 +1,9 @@
//! The `pubsub` module implements a threaded subscription service on client RPC request
use crate::rpc::{get_parsed_token_account, get_parsed_token_accounts};
use crate::{
optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
rpc::{get_parsed_token_account, get_parsed_token_accounts},
};
use core::hash::Hash;
use jsonrpc_core::futures::Future;
use jsonrpc_pubsub::{
@@ -13,9 +16,11 @@ use solana_client::{
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSignatureSubscribeConfig},
rpc_filter::RpcFilterType,
rpc_response::{
ProcessedSignatureResult, Response, RpcKeyedAccount, RpcResponseContext, RpcSignatureResult,
ProcessedSignatureResult, ReceivedSignatureResult, Response, RpcKeyedAccount,
RpcResponseContext, RpcSignatureResult, SlotInfo,
},
};
use solana_measure::measure::Measure;
use solana_runtime::{
bank::Bank,
bank_forks::BankForks,
@@ -47,13 +52,6 @@ use tokio_01::runtime::{Builder as RuntimeBuilder, Runtime, TaskExecutor};
const RECEIVE_DELAY_MILLIS: u64 = 100;
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub struct SlotInfo {
pub slot: Slot,
pub parent: Slot,
pub root: Slot,
}
// A more human-friendly version of Vote, with the bank state signature base58 encoded.
#[derive(Serialize, Deserialize, Debug)]
pub struct RpcVote {
@@ -66,7 +64,6 @@ enum NotificationEntry {
Slot(SlotInfo),
Vote(Vote),
Root(Slot),
Frozen(Slot),
Bank(CommitmentSlots),
Gossip(Slot),
SignaturesReceived((Slot, Vec<Signature>)),
@@ -76,7 +73,6 @@ impl std::fmt::Debug for NotificationEntry {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
NotificationEntry::Root(root) => write!(f, "Root({})", root),
NotificationEntry::Frozen(slot) => write!(f, "Frozen({})", slot),
NotificationEntry::Vote(vote) => write!(f, "Vote({:?})", vote),
NotificationEntry::Slot(slot_info) => write!(f, "Slot({:?})", slot_info),
NotificationEntry::Bank(commitment_slots) => {
@@ -291,9 +287,7 @@ fn filter_signature_result(
) -> (Box<dyn Iterator<Item = RpcSignatureResult>>, Slot) {
(
Box::new(result.into_iter().map(|result| {
RpcSignatureResult::ProcessedSignatureResult(ProcessedSignatureResult {
err: result.err(),
})
RpcSignatureResult::ProcessedSignature(ProcessedSignatureResult { err: result.err() })
})),
last_notified_slot,
)
@@ -301,7 +295,7 @@ fn filter_signature_result(
fn filter_program_results(
accounts: Vec<(Pubkey, Account)>,
_program_id: &Pubkey,
program_id: &Pubkey,
last_notified_slot: Slot,
config: Option<ProgramConfig>,
bank: Option<Arc<Bank>>,
@@ -309,24 +303,27 @@ fn filter_program_results(
let config = config.unwrap_or_default();
let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary);
let filters = config.filters;
let accounts_is_empty = accounts.is_empty();
let keyed_accounts = accounts.into_iter().filter(move |(_, account)| {
filters.iter().all(|filter_type| match filter_type {
RpcFilterType::DataSize(size) => account.data.len() as u64 == *size,
RpcFilterType::Memcmp(compare) => compare.bytes_match(&account.data),
})
});
let accounts: Box<dyn Iterator<Item = RpcKeyedAccount>> =
if encoding == UiAccountEncoding::JsonParsed {
let bank = bank.unwrap(); // If !accounts.is_empty(), bank must be Some
Box::new(get_parsed_token_accounts(bank, keyed_accounts))
} else {
Box::new(
keyed_accounts.map(move |(pubkey, account)| RpcKeyedAccount {
pubkey: pubkey.to_string(),
account: UiAccount::encode(&pubkey, account, encoding.clone(), None, None),
}),
)
};
let accounts: Box<dyn Iterator<Item = RpcKeyedAccount>> = if program_id == &spl_token_id_v2_0()
&& encoding == UiAccountEncoding::JsonParsed
&& !accounts_is_empty
{
let bank = bank.unwrap(); // If !accounts_is_empty, bank must be Some
Box::new(get_parsed_token_accounts(bank, keyed_accounts))
} else {
Box::new(
keyed_accounts.map(move |(pubkey, account)| RpcKeyedAccount {
pubkey: pubkey.to_string(),
account: UiAccount::encode(&pubkey, account, encoding.clone(), None, None),
}),
)
};
(accounts, last_notified_slot)
}
@@ -350,7 +347,7 @@ pub struct RpcSubscriptions {
notifier_runtime: Option<Runtime>,
bank_forks: Arc<RwLock<BankForks>>,
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
last_checked_slots: Arc<RwLock<HashMap<CommitmentLevel, Slot>>>,
optimistically_confirmed_bank: Arc<RwLock<OptimisticallyConfirmedBank>>,
exit: Arc<AtomicBool>,
}
@@ -367,6 +364,7 @@ impl RpcSubscriptions {
exit: &Arc<AtomicBool>,
bank_forks: Arc<RwLock<BankForks>>,
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
optimistically_confirmed_bank: Arc<RwLock<OptimisticallyConfirmedBank>>,
) -> Self {
let (notification_sender, notification_receiver): (
Sender<NotificationEntry>,
@@ -400,9 +398,6 @@ impl RpcSubscriptions {
};
let _subscriptions = subscriptions.clone();
let last_checked_slots = Arc::new(RwLock::new(HashMap::new()));
let _last_checked_slots = last_checked_slots.clone();
let notifier_runtime = RuntimeBuilder::new()
.core_threads(1)
.name_prefix("solana-rpc-notifier-")
@@ -419,7 +414,6 @@ impl RpcSubscriptions {
notification_receiver,
_subscriptions,
_bank_forks,
_last_checked_slots,
);
})
.unwrap();
@@ -431,16 +425,19 @@ impl RpcSubscriptions {
t_cleanup: Some(t_cleanup),
bank_forks,
block_commitment_cache,
last_checked_slots,
optimistically_confirmed_bank,
exit: exit.clone(),
}
}
pub fn default_with_bank_forks(bank_forks: Arc<RwLock<BankForks>>) -> Self {
let optimistically_confirmed_bank =
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
Self::new(
&Arc::new(AtomicBool::new(false)),
bank_forks,
Arc::new(RwLock::new(BlockCommitmentCache::default())),
optimistically_confirmed_bank,
)
}
@@ -450,7 +447,7 @@ impl RpcSubscriptions {
account_subscriptions: Arc<RpcAccountSubscriptions>,
notifier: &RpcNotifier,
commitment_slots: &CommitmentSlots,
) {
) -> HashSet<SubscriptionId> {
let subscriptions = account_subscriptions.read().unwrap();
check_commitment_and_notify(
&subscriptions,
@@ -460,7 +457,7 @@ impl RpcSubscriptions {
Bank::get_account_modified_slot,
filter_account_result,
notifier,
);
)
}
fn check_program(
@@ -469,7 +466,7 @@ impl RpcSubscriptions {
program_subscriptions: Arc<RpcProgramSubscriptions>,
notifier: &RpcNotifier,
commitment_slots: &CommitmentSlots,
) {
) -> HashSet<SubscriptionId> {
let subscriptions = program_subscriptions.read().unwrap();
check_commitment_and_notify(
&subscriptions,
@@ -479,7 +476,7 @@ impl RpcSubscriptions {
Bank::get_program_accounts_modified_since_parent,
filter_program_results,
notifier,
);
)
}
fn check_signature(
@@ -488,7 +485,7 @@ impl RpcSubscriptions {
signature_subscriptions: Arc<RpcSignatureSubscriptions>,
notifier: &RpcNotifier,
commitment_slots: &CommitmentSlots,
) {
) -> HashSet<SubscriptionId> {
let mut subscriptions = signature_subscriptions.write().unwrap();
let notified_ids = check_commitment_and_notify(
&subscriptions,
@@ -505,6 +502,7 @@ impl RpcSubscriptions {
subscriptions.remove(&signature);
}
}
notified_ids
}
pub fn add_account_subscription(
@@ -532,12 +530,12 @@ impl RpcSubscriptions {
.read()
.unwrap()
.highest_confirmed_slot(),
CommitmentLevel::SingleGossip => *self
.last_checked_slots
CommitmentLevel::SingleGossip => self
.optimistically_confirmed_bank
.read()
.unwrap()
.get(&CommitmentLevel::SingleGossip)
.unwrap_or(&0),
.bank
.slot(),
};
let last_notified_slot = if let Some((_account, slot)) = self
.bank_forks
@@ -642,7 +640,7 @@ impl RpcSubscriptions {
) {
let (commitment, enable_received_notification) = signature_subscribe_config
.map(|config| (config.commitment, config.enable_received_notification))
.unwrap_or((None, Some(false)));
.unwrap_or_default();
let commitment_level = commitment
.unwrap_or_else(CommitmentConfig::recent)
@@ -727,10 +725,6 @@ impl RpcSubscriptions {
self.enqueue_notification(NotificationEntry::Vote(vote.clone()));
}
pub fn notify_frozen(&self, frozen_slot: Slot) {
self.enqueue_notification(NotificationEntry::Frozen(frozen_slot));
}
pub fn add_root_subscription(&self, sub_id: SubscriptionId, subscriber: Subscriber<Slot>) {
let sink = subscriber.assign_id(sub_id.clone()).unwrap();
let mut subscriptions = self.subscriptions.root_subscriptions.write().unwrap();
@@ -772,9 +766,7 @@ impl RpcSubscriptions {
notification_receiver: Receiver<NotificationEntry>,
subscriptions: Subscriptions,
bank_forks: Arc<RwLock<BankForks>>,
last_checked_slots: Arc<RwLock<HashMap<CommitmentLevel, Slot>>>,
) {
let mut pending_gossip_notifications = HashSet::new();
loop {
if exit.load(Ordering::Relaxed) {
break;
@@ -782,6 +774,7 @@ impl RpcSubscriptions {
match notification_receiver.recv_timeout(Duration::from_millis(RECEIVE_DELAY_MILLIS)) {
Ok(notification_entry) => match notification_entry {
NotificationEntry::Slot(slot_info) => {
debug!("slot notify: {:?}", slot_info);
let subscriptions = subscriptions.slot_subscriptions.read().unwrap();
for (_, sink) in subscriptions.iter() {
notifier.notify(slot_info, sink);
@@ -791,6 +784,7 @@ impl RpcSubscriptions {
// unlike `NotificationEntry::Gossip`, which also accounts for slots seen
// in VoteState's from bank states built in ReplayStage.
NotificationEntry::Vote(ref vote_info) => {
debug!("vote notify: {:?}", vote_info);
let subscriptions = subscriptions.vote_subscriptions.read().unwrap();
for (_, sink) in subscriptions.iter() {
notifier.notify(
@@ -804,16 +798,11 @@ impl RpcSubscriptions {
}
}
NotificationEntry::Root(root) => {
debug!("root notify: {:?}", root);
let subscriptions = subscriptions.root_subscriptions.read().unwrap();
for (_, sink) in subscriptions.iter() {
notifier.notify(root, sink);
}
// Prune old pending notifications
pending_gossip_notifications = pending_gossip_notifications
.into_iter()
.filter(|&s| s > root)
.collect();
}
NotificationEntry::Bank(commitment_slots) => {
RpcSubscriptions::notify_accounts_programs_signatures(
@@ -823,38 +812,16 @@ impl RpcSubscriptions {
&bank_forks,
&commitment_slots,
&notifier,
"bank",
)
}
NotificationEntry::Frozen(slot) => {
if pending_gossip_notifications.remove(&slot) {
Self::process_gossip_notification(
slot,
&notifier,
&subscriptions,
&bank_forks,
&last_checked_slots,
);
}
}
NotificationEntry::Gossip(slot) => {
let bank_frozen = bank_forks
.read()
.unwrap()
.get(slot)
.filter(|b| b.is_frozen())
.is_some();
if !bank_frozen {
pending_gossip_notifications.insert(slot);
} else {
Self::process_gossip_notification(
slot,
&notifier,
&subscriptions,
&bank_forks,
&last_checked_slots,
);
}
Self::process_gossip_notification(
slot,
&notifier,
&subscriptions,
&bank_forks,
);
}
NotificationEntry::SignaturesReceived(slot_signatures) => {
RpcSubscriptions::process_signatures_received(
@@ -880,23 +847,7 @@ impl RpcSubscriptions {
notifier: &RpcNotifier,
subscriptions: &Subscriptions,
bank_forks: &Arc<RwLock<BankForks>>,
last_checked_slots: &Arc<RwLock<HashMap<CommitmentLevel, Slot>>>,
) {
let mut last_checked_slots_lock = last_checked_slots.write().unwrap();
let last_checked_slot = last_checked_slots_lock
.get(&CommitmentLevel::SingleGossip)
.cloned()
.unwrap_or_default();
if slot > last_checked_slot {
last_checked_slots_lock.insert(CommitmentLevel::SingleGossip, slot);
} else {
// Avoid sending stale or duplicate notifications
return;
}
drop(last_checked_slots_lock);
let commitment_slots = CommitmentSlots {
highest_confirmed_slot: slot,
..CommitmentSlots::default()
@@ -908,6 +859,7 @@ impl RpcSubscriptions {
bank_forks,
&commitment_slots,
&notifier,
"gossip",
);
}
@@ -918,46 +870,76 @@ impl RpcSubscriptions {
bank_forks: &Arc<RwLock<BankForks>>,
commitment_slots: &CommitmentSlots,
notifier: &RpcNotifier,
source: &'static str,
) {
let mut accounts_time = Measure::start("accounts");
let pubkeys: Vec<_> = {
let subs = account_subscriptions.read().unwrap();
subs.keys().cloned().collect()
};
let mut num_pubkeys_notified = 0;
for pubkey in &pubkeys {
Self::check_account(
num_pubkeys_notified += Self::check_account(
pubkey,
bank_forks,
account_subscriptions.clone(),
&notifier,
&commitment_slots,
);
)
.len();
}
accounts_time.stop();
let mut programs_time = Measure::start("programs");
let programs: Vec<_> = {
let subs = program_subscriptions.read().unwrap();
subs.keys().cloned().collect()
};
let mut num_programs_notified = 0;
for program_id in &programs {
Self::check_program(
num_programs_notified += Self::check_program(
program_id,
bank_forks,
program_subscriptions.clone(),
&notifier,
&commitment_slots,
);
)
.len();
}
programs_time.stop();
let mut signatures_time = Measure::start("signatures");
let signatures: Vec<_> = {
let subs = signature_subscriptions.read().unwrap();
subs.keys().cloned().collect()
};
let mut num_signatures_notified = 0;
for signature in &signatures {
Self::check_signature(
num_signatures_notified += Self::check_signature(
signature,
bank_forks,
signature_subscriptions.clone(),
&notifier,
&commitment_slots,
)
.len();
}
signatures_time.stop();
let total_notified = num_pubkeys_notified + num_programs_notified + num_signatures_notified;
let total_ms = accounts_time.as_ms() + programs_time.as_ms() + signatures_time.as_ms();
if total_notified > 0 || total_ms > 10 {
debug!(
"notified({}): accounts: {} / {} ({}) programs: {} / {} ({}) signatures: {} / {} ({})",
source,
pubkeys.len(),
num_pubkeys_notified,
accounts_time,
programs.len(),
num_programs_notified,
programs_time,
signatures.len(),
num_signatures_notified,
signatures_time,
);
}
}
@@ -978,15 +960,15 @@ impl RpcSubscriptions {
},
) in hashmap.iter()
{
if is_received_notification_enabled
.expect("All signature subscriptions must have this config field set")
{
if is_received_notification_enabled.unwrap_or_default() {
notifier.notify(
Response {
context: RpcResponseContext {
slot: *received_slot,
},
value: RpcSignatureResult::ReceivedSignature,
value: RpcSignatureResult::ReceivedSignature(
ReceivedSignatureResult::ReceivedSignature,
),
},
&sink,
);
@@ -1019,6 +1001,9 @@ impl RpcSubscriptions {
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::optimistically_confirmed_bank_tracker::{
BankNotification, OptimisticallyConfirmedBank, OptimisticallyConfirmedBankTracker,
};
use jsonrpc_core::futures::{self, stream::Stream};
use jsonrpc_pubsub::typed::Subscriber;
use serial_test_derive::serial;
@@ -1084,6 +1069,7 @@ pub(crate) mod tests {
Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests_with_slots(
1, 1,
))),
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
);
subscriptions.add_account_subscription(
alice.pubkey(),
@@ -1182,10 +1168,13 @@ pub(crate) mod tests {
Subscriber::new_test("programNotification");
let sub_id = SubscriptionId::Number(0 as u64);
let exit = Arc::new(AtomicBool::new(false));
let optimistically_confirmed_bank =
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
let subscriptions = RpcSubscriptions::new(
&exit,
bank_forks,
Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests())),
optimistically_confirmed_bank,
);
subscriptions.add_program_subscription(
solana_budget_program::id(),
@@ -1292,10 +1281,13 @@ pub(crate) mod tests {
);
let exit = Arc::new(AtomicBool::new(false));
let optimistically_confirmed_bank =
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
let subscriptions = RpcSubscriptions::new(
&exit,
bank_forks,
Arc::new(RwLock::new(block_commitment_cache)),
optimistically_confirmed_bank,
);
let (past_bank_sub1, _id_receiver, past_bank_recv1) =
@@ -1371,8 +1363,9 @@ pub(crate) mod tests {
.notify_signatures_received((received_slot, vec![unprocessed_tx.signatures[0]]));
subscriptions.notify_subscribers(commitment_slots);
let expected_res =
RpcSignatureResult::ProcessedSignatureResult(ProcessedSignatureResult { err: None });
let received_expected_res = RpcSignatureResult::ReceivedSignature;
RpcSignatureResult::ProcessedSignature(ProcessedSignatureResult { err: None });
let received_expected_res =
RpcSignatureResult::ReceivedSignature(ReceivedSignatureResult::ReceivedSignature);
struct Notification {
slot: Slot,
id: u64,
@@ -1447,10 +1440,13 @@ pub(crate) mod tests {
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
let bank = Bank::new(&genesis_config);
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
let optimistically_confirmed_bank =
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
let subscriptions = RpcSubscriptions::new(
&exit,
bank_forks,
Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests())),
optimistically_confirmed_bank,
);
subscriptions.add_slot_subscription(sub_id.clone(), subscriber);
@@ -1495,10 +1491,13 @@ pub(crate) mod tests {
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
let bank = Bank::new(&genesis_config);
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
let optimistically_confirmed_bank =
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
let subscriptions = RpcSubscriptions::new(
&exit,
bank_forks,
Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests())),
optimistically_confirmed_bank,
);
subscriptions.add_root_subscription(sub_id.clone(), subscriber);
@@ -1597,18 +1596,23 @@ pub(crate) mod tests {
bank_forks.write().unwrap().insert(bank2);
let alice = Keypair::new();
let optimistically_confirmed_bank =
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
let mut pending_optimistically_confirmed_banks = HashSet::new();
let (subscriber0, _id_receiver, transport_receiver0) =
Subscriber::new_test("accountNotification");
let (subscriber1, _id_receiver, transport_receiver1) =
Subscriber::new_test("accountNotification");
let exit = Arc::new(AtomicBool::new(false));
let subscriptions = RpcSubscriptions::new(
let subscriptions = Arc::new(RpcSubscriptions::new(
&exit,
bank_forks.clone(),
Arc::new(RwLock::new(BlockCommitmentCache::new_for_tests_with_slots(
1, 1,
))),
);
optimistically_confirmed_bank.clone(),
));
let sub_id0 = SubscriptionId::Number(0 as u64);
subscriptions.add_account_subscription(
alice.pubkey(),
@@ -1652,10 +1656,22 @@ pub(crate) mod tests {
.unwrap();
// First, notify the unfrozen bank first to queue pending notification
subscriptions.notify_gossip_subscribers(2);
OptimisticallyConfirmedBankTracker::process_notification(
BankNotification::OptimisticallyConfirmed(2),
&bank_forks,
&optimistically_confirmed_bank,
&subscriptions,
&mut pending_optimistically_confirmed_banks,
);
// Now, notify the frozen bank and ensure its notifications are processed
subscriptions.notify_gossip_subscribers(1);
OptimisticallyConfirmedBankTracker::process_notification(
BankNotification::OptimisticallyConfirmed(1),
&bank_forks,
&optimistically_confirmed_bank,
&subscriptions,
&mut pending_optimistically_confirmed_banks,
);
let (response, _) = robust_poll_or_panic(transport_receiver0);
let expected = json!({
@@ -1690,7 +1706,14 @@ pub(crate) mod tests {
subscriber1,
);
subscriptions.notify_frozen(2);
let bank2 = bank_forks.read().unwrap().get(2).unwrap().clone();
OptimisticallyConfirmedBankTracker::process_notification(
BankNotification::Frozen(bank2),
&bank_forks,
&optimistically_confirmed_bank,
&subscriptions,
&mut pending_optimistically_confirmed_banks,
);
let (response, _) = robust_poll_or_panic(transport_receiver1);
let expected = json!({
"jsonrpc": "2.0",

View File

@@ -0,0 +1,104 @@
use solana_ledger::{blockstore::Blockstore, blockstore_meta::PerfSample};
use solana_runtime::bank_forks::BankForks;
use std::{
sync::{
atomic::{AtomicBool, Ordering},
Arc, RwLock,
},
thread::{self, sleep, Builder, JoinHandle},
time::{Duration, Instant},
};
const SAMPLE_INTERVAL: u64 = 60;
const SLEEP_INTERVAL: u64 = 500;
pub struct SamplePerformanceSnapshot {
pub num_transactions: u64,
pub num_slots: u64,
}
pub struct SamplePerformanceService {
thread_hdl: JoinHandle<()>,
}
impl SamplePerformanceService {
#[allow(clippy::new_ret_no_self)]
pub fn new(
bank_forks: &Arc<RwLock<BankForks>>,
blockstore: &Arc<Blockstore>,
exit: &Arc<AtomicBool>,
) -> Self {
let exit = exit.clone();
let blockstore = blockstore.clone();
let bank_forks = bank_forks.clone();
info!("Starting SamplePerformance service");
let thread_hdl = Builder::new()
.name("sample-performance".to_string())
.spawn(move || {
Self::run(bank_forks, &blockstore, exit);
})
.unwrap();
Self { thread_hdl }
}
pub fn run(
bank_forks: Arc<RwLock<BankForks>>,
blockstore: &Arc<Blockstore>,
exit: Arc<AtomicBool>,
) {
let forks = bank_forks.read().unwrap();
let bank = forks.root_bank().clone();
let highest_slot = forks.highest_slot();
drop(forks);
let mut sample_snapshot = SamplePerformanceSnapshot {
num_transactions: bank.transaction_count(),
num_slots: highest_slot,
};
let mut now = Instant::now();
loop {
if exit.load(Ordering::Relaxed) {
break;
}
let elapsed = now.elapsed();
if elapsed.as_secs() >= SAMPLE_INTERVAL {
now = Instant::now();
let bank_forks = bank_forks.read().unwrap();
let bank = bank_forks.root_bank().clone();
let highest_slot = bank_forks.highest_slot();
drop(bank_forks);
let perf_sample = PerfSample {
num_slots: highest_slot
.checked_sub(sample_snapshot.num_slots)
.unwrap_or_default(),
num_transactions: bank
.transaction_count()
.checked_sub(sample_snapshot.num_transactions)
.unwrap_or_default(),
sample_period_secs: elapsed.as_secs() as u16,
};
if let Err(e) = blockstore.write_perf_sample(highest_slot, &perf_sample) {
error!("write_perf_sample failed: slot {:?} {:?}", highest_slot, e);
}
sample_snapshot = SamplePerformanceSnapshot {
num_transactions: bank.transaction_count(),
num_slots: highest_slot,
};
}
sleep(Duration::from_millis(SLEEP_INTERVAL));
}
}
pub fn join(self) -> thread::Result<()> {
self.thread_hdl.join()
}
}

View File

@@ -0,0 +1,404 @@
// TODO: Merge this implementation with the one at `banks-server/src/send_transaction_service.rs`
use crate::cluster_info::ClusterInfo;
use crate::poh_recorder::PohRecorder;
use log::*;
use solana_metrics::{datapoint_warn, inc_new_counter_info};
use solana_runtime::{bank::Bank, bank_forks::BankForks};
use solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature};
use std::sync::Mutex;
use std::{
collections::HashMap,
net::{SocketAddr, UdpSocket},
sync::{
mpsc::{Receiver, RecvTimeoutError},
Arc, RwLock,
},
thread::{self, Builder, JoinHandle},
time::{Duration, Instant},
};
/// Maximum size of the transaction queue
const MAX_TRANSACTION_QUEUE_SIZE: usize = 10_000; // This seems like a lot but maybe it needs to be bigger one day
pub struct SendTransactionService {
thread: JoinHandle<()>,
}
pub struct TransactionInfo {
pub signature: Signature,
pub wire_transaction: Vec<u8>,
pub last_valid_slot: Slot,
}
impl TransactionInfo {
pub fn new(signature: Signature, wire_transaction: Vec<u8>, last_valid_slot: Slot) -> Self {
Self {
signature,
wire_transaction,
last_valid_slot,
}
}
}
pub struct LeaderInfo {
cluster_info: Arc<ClusterInfo>,
poh_recorder: Arc<Mutex<PohRecorder>>,
recent_peers: HashMap<Pubkey, SocketAddr>,
}
impl LeaderInfo {
pub fn new(cluster_info: Arc<ClusterInfo>, poh_recorder: Arc<Mutex<PohRecorder>>) -> Self {
Self {
cluster_info,
poh_recorder,
recent_peers: HashMap::new(),
}
}
pub fn refresh_recent_peers(&mut self) {
self.recent_peers = self
.cluster_info
.tpu_peers()
.into_iter()
.map(|ci| (ci.id, ci.tpu))
.collect();
}
pub fn get_leader_tpu(&self) -> Option<&SocketAddr> {
self.poh_recorder
.lock()
.unwrap()
.leader_after_n_slots(0)
.and_then(|leader| self.recent_peers.get(&leader))
}
}
#[derive(Default, Debug, PartialEq)]
struct ProcessTransactionsResult {
rooted: u64,
expired: u64,
retried: u64,
failed: u64,
retained: u64,
}
impl SendTransactionService {
pub fn new(
tpu_address: SocketAddr,
bank_forks: &Arc<RwLock<BankForks>>,
leader_info: Option<LeaderInfo>,
receiver: Receiver<TransactionInfo>,
) -> Self {
let thread = Self::retry_thread(tpu_address, receiver, bank_forks.clone(), leader_info);
Self { thread }
}
fn retry_thread(
tpu_address: SocketAddr,
receiver: Receiver<TransactionInfo>,
bank_forks: Arc<RwLock<BankForks>>,
mut leader_info: Option<LeaderInfo>,
) -> JoinHandle<()> {
let mut last_status_check = Instant::now();
let mut transactions = HashMap::new();
let send_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
if let Some(leader_info) = leader_info.as_mut() {
leader_info.refresh_recent_peers();
}
Builder::new()
.name("send-tx-sv2".to_string())
.spawn(move || loop {
match receiver.recv_timeout(Duration::from_secs(1)) {
Err(RecvTimeoutError::Disconnected) => break,
Err(RecvTimeoutError::Timeout) => {}
Ok(transaction_info) => {
let address = leader_info
.as_ref()
.and_then(|leader_info| leader_info.get_leader_tpu())
.unwrap_or(&tpu_address);
Self::send_transaction(
&send_socket,
address,
&transaction_info.wire_transaction,
);
if transactions.len() < MAX_TRANSACTION_QUEUE_SIZE {
transactions.insert(transaction_info.signature, transaction_info);
} else {
datapoint_warn!("send_transaction_service-queue-overflow");
}
}
}
if Instant::now().duration_since(last_status_check).as_secs() >= 5 {
if !transactions.is_empty() {
datapoint_info!(
"send_transaction_service-queue-size",
("len", transactions.len(), i64)
);
let bank_forks = bank_forks.read().unwrap();
let root_bank = bank_forks.root_bank();
let working_bank = bank_forks.working_bank();
let _result = Self::process_transactions(
&working_bank,
&root_bank,
&send_socket,
&tpu_address,
&mut transactions,
&leader_info,
);
}
last_status_check = Instant::now();
if let Some(leader_info) = leader_info.as_mut() {
leader_info.refresh_recent_peers();
}
}
})
.unwrap()
}
fn process_transactions(
working_bank: &Arc<Bank>,
root_bank: &Arc<Bank>,
send_socket: &UdpSocket,
tpu_address: &SocketAddr,
transactions: &mut HashMap<Signature, TransactionInfo>,
leader_info: &Option<LeaderInfo>,
) -> ProcessTransactionsResult {
let mut result = ProcessTransactionsResult::default();
transactions.retain(|signature, transaction_info| {
if root_bank.has_signature(signature) {
info!("Transaction is rooted: {}", signature);
result.rooted += 1;
inc_new_counter_info!("send_transaction_service-rooted", 1);
false
} else if transaction_info.last_valid_slot < root_bank.slot() {
info!("Dropping expired transaction: {}", signature);
result.expired += 1;
inc_new_counter_info!("send_transaction_service-expired", 1);
false
} else {
match working_bank.get_signature_status_slot(signature) {
None => {
// Transaction is unknown to the working bank, it might have been
// dropped or landed in another fork. Re-send it
info!("Retrying transaction: {}", signature);
result.retried += 1;
inc_new_counter_info!("send_transaction_service-retry", 1);
Self::send_transaction(
&send_socket,
leader_info
.as_ref()
.and_then(|leader_info| leader_info.get_leader_tpu())
.unwrap_or(&tpu_address),
&transaction_info.wire_transaction,
);
true
}
Some((_slot, status)) => {
if status.is_err() {
info!("Dropping failed transaction: {}", signature);
result.failed += 1;
inc_new_counter_info!("send_transaction_service-failed", 1);
false
} else {
result.retained += 1;
true
}
}
}
}
});
result
}
fn send_transaction(
send_socket: &UdpSocket,
tpu_address: &SocketAddr,
wire_transaction: &[u8],
) {
if let Err(err) = send_socket.send_to(wire_transaction, tpu_address) {
warn!("Failed to send transaction to {}: {:?}", tpu_address, err);
}
}
pub fn join(self) -> thread::Result<()> {
self.thread.join()
}
}
#[cfg(test)]
mod test {
use super::*;
use solana_sdk::{
genesis_config::create_genesis_config, pubkey::Pubkey, signature::Signer,
system_transaction,
};
use std::sync::mpsc::channel;
#[test]
fn service_exit() {
let tpu_address = "127.0.0.1:0".parse().unwrap();
let bank = Bank::default();
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
let (sender, receiver) = channel();
let send_tranaction_service =
SendTransactionService::new(tpu_address, &bank_forks, None, receiver);
drop(sender);
send_tranaction_service.join().unwrap();
}
#[test]
fn process_transactions() {
solana_logger::setup();
let (genesis_config, mint_keypair) = create_genesis_config(4);
let bank = Bank::new(&genesis_config);
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
let send_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
let tpu_address = "127.0.0.1:0".parse().unwrap();
let root_bank = Arc::new(Bank::new_from_parent(
&bank_forks.read().unwrap().working_bank(),
&Pubkey::default(),
1,
));
let rooted_signature = root_bank
.transfer(1, &mint_keypair, &mint_keypair.pubkey())
.unwrap();
let working_bank = Arc::new(Bank::new_from_parent(&root_bank, &Pubkey::default(), 2));
let non_rooted_signature = working_bank
.transfer(2, &mint_keypair, &mint_keypair.pubkey())
.unwrap();
let failed_signature = {
let blockhash = working_bank.last_blockhash();
let transaction =
system_transaction::transfer(&mint_keypair, &Pubkey::default(), 1, blockhash);
let signature = transaction.signatures[0];
working_bank.process_transaction(&transaction).unwrap_err();
signature
};
let mut transactions = HashMap::new();
info!("Expired transactions are dropped..");
transactions.insert(
Signature::default(),
TransactionInfo::new(Signature::default(), vec![], root_bank.slot() - 1),
);
let result = SendTransactionService::process_transactions(
&working_bank,
&root_bank,
&send_socket,
&tpu_address,
&mut transactions,
&None,
);
assert!(transactions.is_empty());
assert_eq!(
result,
ProcessTransactionsResult {
expired: 1,
..ProcessTransactionsResult::default()
}
);
info!("Rooted transactions are dropped...");
transactions.insert(
rooted_signature,
TransactionInfo::new(rooted_signature, vec![], working_bank.slot()),
);
let result = SendTransactionService::process_transactions(
&working_bank,
&root_bank,
&send_socket,
&tpu_address,
&mut transactions,
&None,
);
assert!(transactions.is_empty());
assert_eq!(
result,
ProcessTransactionsResult {
rooted: 1,
..ProcessTransactionsResult::default()
}
);
info!("Failed transactions are dropped...");
transactions.insert(
failed_signature,
TransactionInfo::new(failed_signature, vec![], working_bank.slot()),
);
let result = SendTransactionService::process_transactions(
&working_bank,
&root_bank,
&send_socket,
&tpu_address,
&mut transactions,
&None,
);
assert!(transactions.is_empty());
assert_eq!(
result,
ProcessTransactionsResult {
failed: 1,
..ProcessTransactionsResult::default()
}
);
info!("Non-rooted transactions are kept...");
transactions.insert(
non_rooted_signature,
TransactionInfo::new(non_rooted_signature, vec![], working_bank.slot()),
);
let result = SendTransactionService::process_transactions(
&working_bank,
&root_bank,
&send_socket,
&tpu_address,
&mut transactions,
&None,
);
assert_eq!(transactions.len(), 1);
assert_eq!(
result,
ProcessTransactionsResult {
retained: 1,
..ProcessTransactionsResult::default()
}
);
transactions.clear();
info!("Unknown transactions are retried...");
transactions.insert(
Signature::default(),
TransactionInfo::new(Signature::default(), vec![], working_bank.slot()),
);
let result = SendTransactionService::process_transactions(
&working_bank,
&root_bank,
&send_socket,
&tpu_address,
&mut transactions,
&None,
);
assert_eq!(transactions.len(), 1);
assert_eq!(
result,
ProcessTransactionsResult {
retried: 1,
..ProcessTransactionsResult::default()
}
);
}
}

107
core/src/test_validator.rs Normal file
View File

@@ -0,0 +1,107 @@
use crate::{
cluster_info::Node,
contact_info::ContactInfo,
gossip_service::discover_cluster,
validator::{Validator, ValidatorConfig},
};
use solana_ledger::create_new_tmp_ledger;
use solana_sdk::{
hash::Hash,
pubkey::Pubkey,
signature::{Keypair, Signer},
};
use std::{path::PathBuf, sync::Arc};
pub struct TestValidator {
pub server: Validator,
pub leader_data: ContactInfo,
pub alice: Keypair,
pub ledger_path: PathBuf,
pub genesis_hash: Hash,
pub vote_pubkey: Pubkey,
}
pub struct TestValidatorOptions {
pub fees: u64,
pub bootstrap_validator_lamports: u64,
pub mint_lamports: u64,
}
impl Default for TestValidatorOptions {
fn default() -> Self {
use solana_ledger::genesis_utils::BOOTSTRAP_VALIDATOR_LAMPORTS;
TestValidatorOptions {
fees: 0,
bootstrap_validator_lamports: BOOTSTRAP_VALIDATOR_LAMPORTS,
mint_lamports: 1_000_000,
}
}
}
impl TestValidator {
pub fn run() -> Self {
Self::run_with_options(TestValidatorOptions::default())
}
pub fn run_with_options(options: TestValidatorOptions) -> Self {
use solana_ledger::genesis_utils::{
create_genesis_config_with_leader_ex, GenesisConfigInfo,
};
use solana_sdk::fee_calculator::FeeRateGovernor;
let TestValidatorOptions {
fees,
bootstrap_validator_lamports,
mint_lamports,
} = options;
let node_keypair = Arc::new(Keypair::new());
let node = Node::new_localhost_with_pubkey(&node_keypair.pubkey());
let contact_info = node.info.clone();
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
voting_keypair,
} = create_genesis_config_with_leader_ex(
mint_lamports,
&contact_info.id,
&Keypair::new(),
&Pubkey::new_rand(),
42,
bootstrap_validator_lamports,
solana_sdk::genesis_config::ClusterType::Development,
);
genesis_config
.native_instruction_processors
.push(solana_budget_program!());
genesis_config.rent.lamports_per_byte_year = 1;
genesis_config.rent.exemption_threshold = 1.0;
genesis_config.fee_rate_governor = FeeRateGovernor::new(fees, 0);
let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_config);
let config = ValidatorConfig {
rpc_addrs: Some((node.info.rpc, node.info.rpc_pubsub, node.info.rpc_banks)),
..ValidatorConfig::default()
};
let vote_pubkey = voting_keypair.pubkey();
let node = Validator::new(
node,
&node_keypair,
&ledger_path,
&voting_keypair.pubkey(),
vec![Arc::new(voting_keypair)],
None,
&config,
);
discover_cluster(&contact_info.gossip, 1).expect("Node startup failed");
TestValidator {
server: node,
leader_data: contact_info,
alice: mint_keypair,
ledger_path,
genesis_hash: blockhash,
vote_pubkey,
}
}
}

View File

@@ -7,6 +7,7 @@ use crate::{
cluster_info::ClusterInfo,
cluster_info_vote_listener::{ClusterInfoVoteListener, VerifiedVoteSender, VoteTracker},
fetch_stage::FetchStage,
optimistically_confirmed_bank_tracker::BankNotificationSender,
poh_recorder::{PohRecorder, WorkingBankEntry},
rpc_subscriptions::RpcSubscriptions,
sigverify::TransactionSigVerifier,
@@ -57,6 +58,7 @@ impl Tpu {
verified_vote_sender: VerifiedVoteSender,
replay_vote_receiver: ReplayVoteReceiver,
replay_vote_sender: ReplayVoteSender,
bank_notification_sender: Option<BankNotificationSender>,
) -> Self {
let (packet_sender, packet_receiver) = channel();
let fetch_stage = FetchStage::new_with_sender(
@@ -85,6 +87,7 @@ impl Tpu {
verified_vote_sender,
replay_vote_receiver,
blockstore.clone(),
bank_notification_sender,
);
let banking_stage = BankingStage::new(

View File

@@ -1,11 +1,12 @@
use crossbeam_channel::{Receiver, RecvTimeoutError};
use itertools::izip;
use solana_ledger::{blockstore::Blockstore, blockstore_processor::TransactionStatusBatch};
use solana_runtime::{
bank::{Bank, HashAgeKind},
nonce_utils,
transaction_utils::OrderedIterator,
};
use solana_transaction_status::TransactionStatusMeta;
use solana_sdk::nonce;
use solana_transaction_status::{InnerInstructions, TransactionStatusMeta};
use std::{
sync::{
atomic::{AtomicBool, Ordering},
@@ -54,19 +55,27 @@ impl TransactionStatusService {
iteration_order,
statuses,
balances,
inner_instructions,
} = write_transaction_status_receiver.recv_timeout(Duration::from_secs(1))?;
let slot = bank.slot();
for ((((_, transaction), (status, hash_age_kind)), pre_balances), post_balances) in
OrderedIterator::new(&transactions, iteration_order.as_deref())
.zip(statuses)
.zip(balances.pre_balances)
.zip(balances.post_balances)
{
for (
(_, transaction),
(status, hash_age_kind),
pre_balances,
post_balances,
inner_instructions,
) in izip!(
OrderedIterator::new(&transactions, iteration_order.as_deref()),
statuses,
balances.pre_balances,
balances.post_balances,
inner_instructions
) {
if Bank::can_commit(&status) && !transaction.signatures.is_empty() {
let fee_calculator = match hash_age_kind {
Some(HashAgeKind::DurableNonce(_, account)) => {
nonce_utils::fee_calculator_of(&account)
nonce::utils::fee_calculator_of(&account)
}
_ => bank.get_fee_calculator(&transaction.message().recent_blockhash),
}
@@ -74,6 +83,19 @@ impl TransactionStatusService {
let fee = fee_calculator.calculate_fee(transaction.message());
let (writable_keys, readonly_keys) =
transaction.message.get_account_keys_by_lock_type();
let inner_instructions = inner_instructions.map(|inner_instructions| {
inner_instructions
.into_iter()
.enumerate()
.map(|(index, instructions)| InnerInstructions {
index: index as u8,
instructions,
})
.filter(|i| !i.instructions.is_empty())
.collect()
});
blockstore
.write_transaction_status(
slot,
@@ -85,6 +107,7 @@ impl TransactionStatusService {
fee,
pre_balances,
post_balances,
inner_instructions,
},
)
.expect("Expect database write to succeed");

View File

@@ -5,11 +5,13 @@ use crate::{
accounts_background_service::AccountsBackgroundService,
accounts_hash_verifier::AccountsHashVerifier,
broadcast_stage::RetransmitSlotsSender,
cache_block_time_service::CacheBlockTimeSender,
cluster_info::ClusterInfo,
cluster_info_vote_listener::{VerifiedVoteReceiver, VoteTracker},
cluster_slots::ClusterSlots,
completed_data_sets_service::CompletedDataSetsSender,
ledger_cleanup_service::LedgerCleanupService,
optimistically_confirmed_bank_tracker::BankNotificationSender,
poh_recorder::PohRecorder,
replay_stage::{ReplayStage, ReplayStageConfig},
retransmit_stage::RetransmitStage,
@@ -96,12 +98,14 @@ impl Tvu {
cfg: Option<Arc<AtomicBool>>,
transaction_status_sender: Option<TransactionStatusSender>,
rewards_recorder_sender: Option<RewardsRecorderSender>,
cache_block_time_sender: Option<CacheBlockTimeSender>,
snapshot_package_sender: Option<AccountsPackageSender>,
vote_tracker: Arc<VoteTracker>,
retransmit_slots_sender: RetransmitSlotsSender,
verified_vote_receiver: VerifiedVoteReceiver,
replay_vote_sender: ReplayVoteSender,
completed_data_sets_sender: CompletedDataSetsSender,
bank_notification_sender: Option<BankNotificationSender>,
tvu_config: TvuConfig,
) -> Self {
let keypair: Arc<Keypair> = cluster_info.keypair.clone();
@@ -191,6 +195,8 @@ impl Tvu {
block_commitment_cache,
transaction_status_sender,
rewards_recorder_sender,
cache_block_time_sender,
bank_notification_sender,
};
let replay_stage = ReplayStage::new(
@@ -249,6 +255,7 @@ pub mod tests {
use crate::{
banking_stage::create_test_recorder,
cluster_info::{ClusterInfo, Node},
optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
};
use serial_test_derive::serial;
use solana_ledger::{
@@ -317,6 +324,7 @@ pub mod tests {
&exit,
bank_forks.clone(),
block_commitment_cache.clone(),
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks),
)),
&poh_recorder,
&leader_schedule_cache,
@@ -327,11 +335,13 @@ pub mod tests {
None,
None,
None,
None,
Arc::new(VoteTracker::new(&bank)),
retransmit_slots_sender,
verified_vote_receiver,
replay_vote_sender,
completed_data_sets_sender,
None,
TvuConfig::default(),
);
exit.store(true, Ordering::Relaxed);

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