Compare commits

...

56 Commits

Author SHA1 Message Date
Jon Cinque
f2561ea547 v1.10-ci: Fix downstream build (#24404) 2022-04-16 12:45:01 +02:00
mergify[bot]
b98e133f2d Add Ident case (#24390) (#24402)
(cherry picked from commit a0e3e3c193)

Co-authored-by: Tyera Eulberg <tyera@solana.com>
2022-04-15 22:48:36 -06:00
Trent Nelson
25274e8a33 rpc-pubsub: reduce metrics/log spam
(cherry picked from commit 50fba01842)
2022-04-15 22:21:44 -06:00
mergify[bot]
3bf00e4af5 cli: sort option for validators by version (#24237)
(cherry picked from commit 91993d89b0)

# Conflicts:
#	Cargo.lock
#	cli-output/Cargo.toml
#	programs/bpf/Cargo.lock

Co-authored-by: Trent Nelson <trent@solana.com>
2022-04-15 09:19:12 +00:00
mergify[bot]
c289cd2a4b Do not require default keypair to exist for bench-tps (#24356) (#24365)
(cherry picked from commit 5e8c12ebdf)

Co-authored-by: Tyera Eulberg <tyera@solana.com>
2022-04-15 03:09:34 +00:00
mergify[bot]
84ac4ff57f add metrics around rewards (#24160) (#24168)
(cherry picked from commit 48d1af01c8)

Co-authored-by: Jeff Washington (jwash) <wash678@gmail.com>
2022-04-14 23:04:56 +00:00
mergify[bot]
58ef6b31f9 Quic client stats (#24195) (#24305)
* Add metrics to connection-cache to measure cache hits and misses

* Add congestion stats

* Add more client stats

* Review comments

Co-authored-by: Ryan Leung <ryan.leung@solana.com>

Co-authored-by: sakridge <sakridge@gmail.com>
Co-authored-by: Ryan Leung <ryan.leung@solana.com>
2022-04-14 16:42:20 -04:00
mergify[bot]
304cd65ecb Support quic in bench-tps (#24295) (#24317)
* Update comment

* Use connection_cache in tpu_client

* Add --tpu-use-quic to bench-tps

* Use connection_cache async send

(cherry picked from commit 26899359d1)

Co-authored-by: Tyera Eulberg <tyera@solana.com>
2022-04-13 22:56:29 -06:00
mergify[bot]
3bee921088 Add a stringified credential option for LedgerStorage (#24314) (#24324)
* add a stringified credential option for LedgerStorage

* fix clippy::useless-format warning

* change CredentialOption to enum CredentialType

* rename credential_option to credential_type

* restore LedgerStorage new fn signature

* fmt

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

Co-authored-by: Rachael Pai <komimi.p@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
2022-04-13 22:17:56 -06:00
mergify[bot]
35d4f390ad Add RpcClient support to bench-tps (#24297) (#24320)
* Impl BenchTpsClient for RpcClient

* Support RpcClient in bench-tps

(cherry picked from commit 96e3555e93)

Co-authored-by: Tyera Eulberg <tyera@solana.com>
2022-04-13 19:33:11 -06:00
mergify[bot]
785481ace4 Async send for send transaction service (backport #24265) (#24323)
* Async send for send transaction service (#24265)

* async send

(cherry picked from commit 474080608a)

# Conflicts:
#	client/Cargo.toml

* Fix conflicts

Co-authored-by: anatoly yakovenko <anatoly@solana.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
2022-04-13 19:29:03 -06:00
mergify[bot]
0e6ba29859 Use LRU in connection-cache (#24109) (#24322)
Switch to using LRU for connection-cache

Co-authored-by: ryleung-solana <91908731+ryleung-solana@users.noreply.github.com>
2022-04-13 21:21:19 -04:00
mergify[bot]
ec1d06240c Add ability to interact with a Bigtable with a custom instance id (backport #23779) (#24325)
* Refactor validator bigtable config

(cherry picked from commit 63ee00e647)

* bigtable: add a config ctor for `LedgerStorage`

(cherry picked from commit f513195468)

* bigtable: allow custom instance names

(cherry picked from commit 9b32b72990)

# Conflicts:
#	validator/Cargo.toml

* Add ability to query bigtable via solana-test-validator, with hidden params

(cherry picked from commit 9c60991cd3)

* Fix conflicts

Co-authored-by: Tyera Eulberg <tyera@solana.com>
Co-authored-by: Trent Nelson <trent@solana.com>
2022-04-13 19:00:47 -06:00
mergify[bot]
32dea4427b Bump bpf-tools to v1.25 (#24290)
- Tweak linker script
  Ensure that all read only sections end up in one segment, and
  everything else in other segments. Discard .eh_frame, .hash and
  .gnu.hash since they are unused.
- Don't create invalid string slices in stdout/stderr on Solana
- Report exceeded stack size as a warning if dynamic frames are off
- Native support for signed division in SBF
  Adds BPF_SDIV, which is enabled only for the SBF subtarget.
- Introduce dynamic stack frames and the SBFv2 flag
  Dynamic stack frames  are currently opt-in and enabled setting
  cpu=sbfv2. When sbfv2 is used, ELF files are flagged with
  e_flags=EF_SBF_V2 so the runtime can detect it and react
  accordingly.

(cherry picked from commit 6b611e1c52)

Co-authored-by: Dmitri Makarov <dmakarov@alumni.stanford.edu>
2022-04-13 20:26:13 +00:00
mergify[bot]
9aa95870fa Make tpu_use_quic a flag only without argument (#24018) (#24027)
(cherry picked from commit 98525ddea9)

Co-authored-by: Lijun Wang <83639177+lijunwangs@users.noreply.github.com>
2022-04-13 13:00:49 -06:00
Dmitri Makarov
d48e9b3a7b Bump sbf-tools version to v1.24
(cherry picked from commit 689064a4f4)
2022-04-13 09:15:12 -07:00
Dmitri Makarov
95a279f310 Double the chunk size for sending the program binary data in tx
(cherry picked from commit 03ed334ebb)

# Conflicts:
#	programs/bpf/tests/programs.rs
2022-04-13 09:15:12 -07:00
mergify[bot]
1700820583 Add TpuClient support to bench-tps (backport #24227) (#24284)
* Add TpuClient support to bench-tps (#24227)

* Add fallible send methods, and rpc_client helper

* Add helper to return RpcClient url

* Implement BenchTpsClient for TpuClient

* Add cli rpc and identity handling

* Handle different kinds of clients in main, use TpuClient

* Add tpu_client integration test

(cherry picked from commit 8487030ea6)

# Conflicts:
#	bench-tps/Cargo.toml

* Fix conflicts

Co-authored-by: Tyera Eulberg <tyera@solana.com>
2022-04-12 13:12:33 -06:00
mergify[bot]
0745738eb1 Add resolver = 2 to fix Windows build error on Travis CI (#24196) (#24259)
(cherry picked from commit a5e740431a)

Co-authored-by: Will Hickey <will.hickey@solana.com>
2022-04-12 14:05:18 -05:00
mergify[bot]
d02bf12976 Add BenchTpsClient trait (backport #24208) (#24256)
* Add BenchTpsClient trait (#24208)

* Add BenchTpsClient

* Impl BenchTpsClient for used clients

* Use BenchTpsClient in do_bench

* Update integration test to use faucet via rpc

* Support keypairs from file that are not prefunded

* Remove old perf-utils

(cherry picked from commit 3871c85fd7)

# Conflicts:
#	bench-tps/Cargo.toml

* Fix conflicts

Co-authored-by: Tyera Eulberg <tyera@solana.com>
2022-04-12 07:45:46 +00:00
mergify[bot]
587d45769d Thin client quic (#23973) (#24266)
Change thin-client to use connection-cache

(cherry picked from commit 8b72200afb)

Co-authored-by: ryleung-solana <91908731+ryleung-solana@users.noreply.github.com>
2022-04-11 23:54:39 -06:00
mergify[bot]
f495024591 Move helpers to solana-cli-config (#24246) (#24250)
* Add solana-cli-utils crate

* Use cli-utils in cli

* Move println fn to cli-output

* Use cli-config instead

(cherry picked from commit 8a73badf3d)

Co-authored-by: Tyera Eulberg <tyera@solana.com>
2022-04-11 22:17:01 -06:00
mergify[bot]
28a681a7bf Remove duplicate increment (#24219) (#24226)
(cherry picked from commit ff3b6d2b8b)

Co-authored-by: carllin <carl@solana.com>
2022-04-11 17:19:48 -04:00
Tyera Eulberg
15acdcc19a Bump version to v1.10.9 (#24253) 2022-04-11 13:30:11 -06:00
mergify[bot]
623ac6567b AcctIdx: fix infinite loop (#23806) (#23816)
(cherry picked from commit 965ab9186d)

Co-authored-by: Jeff Washington (jwash) <wash678@gmail.com>
2022-04-11 09:54:11 -05:00
Trent Nelson
a628034eb5 Bump version to v1.10.8 2022-04-09 00:06:32 -06:00
Christian Kamm
8bce2dd446 Address review comments
(cherry picked from commit a058f348a2)
2022-04-08 19:22:35 -05:00
Christian Kamm
60020632c1 Unittest for cost tracker after process_and_record_transactions
(cherry picked from commit 2ed29771f2)
2022-04-08 19:22:35 -05:00
Christian Kamm
864253a85b Adjustments to cost_tracker updates
- don't store pending tx signatures and costs in CostTracker
- apply tx costs to global state immediately again
- go from commit_or_cancel to update_or_remove, where the cost tracker
  is either updated with the true costs for successful tx, or the costs
  of a retryable tx is removed
- move the function into qos_service and hold the cost tracker lock for
  the whole loop

(cherry picked from commit 924b8ea1eb)
2022-04-08 19:22:35 -05:00
Tao Zhu
637ac7933b - Only commit successfully executed transactions' cost to cost_tracker;
- In-fly transactions are pended in cost_tracker until being committed
  or cancelled;

(cherry picked from commit 9e07272af8)
2022-04-08 19:22:35 -05:00
Tyera Eulberg
5a29e95f71 v1.10: Bump tonic, tonic-build, prost, and etcd-client (#24157)
* Bump tonic, prost, and etcd-client

* Restore doc ignores
2022-04-08 10:21:53 -06:00
mergify[bot]
ba72f347e4 Move duplicate-block proposal (#24167) (#24181)
(cherry picked from commit fbe5e51a16)

Co-authored-by: Tyera Eulberg <tyera@solana.com>
2022-04-07 23:57:44 +00:00
mergify[bot]
720ad85632 providing clarity on airdrop amount constraints (#24115) (#24178)
* providing clarity on airdrop amount constraints

This change is in response to a review of a PR in the `solana-program-library` found here: https://github.com/solana-labs/solana-program-library/pull/3062

* replaced static limits with info on how to find them

* removed trailing whitespace

(cherry picked from commit 781094edb2)

Co-authored-by: T.J. Kyner <78994885+tjkyner@users.noreply.github.com>
2022-04-07 23:02:18 +00:00
mergify[bot]
e5623d288e removes legacy weighted_shuffle and weighted_best methods (#24125) (#24139)
Older weighted_shuffle is based on a heuristic which results in biased
samples as shown in:
https://github.com/solana-labs/solana/pull/18343
and can be replaced with WeightedShuffle.

Also, as described in:
https://github.com/solana-labs/solana/pull/13919
weighted_best can be replaced with rand::distributions::WeightedIndex,
or WeightdShuffle::first.

(cherry picked from commit db23295e1c)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2022-04-07 00:52:40 +00:00
Tyera Eulberg
ad530d73ce Bump lru crate (#24151) 2022-04-06 16:45:27 -06:00
mergify[bot]
36122a27af reduces gossip crds stats (#24132) (#24144)
(cherry picked from commit cd09390367)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2022-04-06 17:24:16 +00:00
mergify[bot]
a6ded6a5ed removes turbine legacy code and already activated features (backport #24080) (#24117) 2022-04-06 01:12:18 +00:00
mergify[bot]
c5541efdc2 Set drop callback on first root bank (#23999) (#24129)
(cherry picked from commit 4ea59d8cb4)

Co-authored-by: carllin <carl@solana.com>
2022-04-05 20:32:17 +00:00
mergify[bot]
3f3e1b30d6 removes outdated and flaky test_skip_repair from retransmit-stage (#24121) (#24126)
test_skip_repair in retransmit-stage is no longer relevant because
following: https://github.com/solana-labs/solana/pull/19233
repair packets are filtered out earlier in window-service and so
retransmit stage does not know if a shred is repaired or not.
Also, following turbine peer shuffle changes:
https://github.com/solana-labs/solana/pull/24080
the test has become flaky since it does not take into account how peers
are shuffled for each shred.

(cherry picked from commit 2282571493)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2022-04-05 19:08:18 +00:00
mergify[bot]
5365b939bf Implement get_account_with_config (#23997). (#24095) (#24113)
(cherry picked from commit 41f2fd7fca)

Co-authored-by: hana <81144685+2501babe@users.noreply.github.com>
2022-04-05 00:47:44 +00:00
Will Hickey
1b6de0f08d Bump version to v1.10.7 (#24105) 2022-04-04 11:20:53 -05:00
mergify[bot]
8d5c7b7d89 hides implementation details of vote-accounts from public interface (#24087) (#24102)
(cherry picked from commit ef3e3dce7a)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2022-04-04 15:08:21 +00:00
mergify[bot]
ca1a282a60 demotes WeightedShuffle failures to error metrics (#24079) (#24088)
Since call-sites are calling unwrap anyways, panicking seems too punitive
for our use cases.

(cherry picked from commit 7cb3b6cbe2)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2022-04-03 18:10:00 +00:00
mergify[bot]
3f661f25fb improves Stakes::activate_epoch performance (#24068) (#24081)
Tested with mainnet stakes obtained from the ledger at 5 recent epoch
boundaries, this code is ~30% faster than current master.

Current code:
  epoch: 289, elapsed: 82901us
  epoch: 290, elapsed: 80525us
  epoch: 291, elapsed: 79122us
  epoch: 292, elapsed: 79961us
  epoch: 293, elapsed: 78965us

This commit:
  epoch: 289, elapsed: 61710us
  epoch: 290, elapsed: 55721us
  epoch: 291, elapsed: 55886us
  epoch: 292, elapsed: 55399us
  epoch: 293, elapsed: 56803us

(cherry picked from commit fa7eb7f30c)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2022-04-03 13:44:19 +00:00
mergify[bot]
b157a9111f Note this is a modified backport that does not SAVE the new fields, but does load them. (#24074)
Original:
Start saving/loading prior_roots(_with_hash) to snapshot (#23844)

    * Start saving/loading prior_roots(_with_hash) to snapshot

    * Update runtime/src/accounts_index.rs

    Co-authored-by: Michael Vines <mvines@gmail.com>

    * Update runtime/src/accounts_index.rs

    Co-authored-by: Michael Vines <mvines@gmail.com>

    * update comment

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

Co-authored-by: Jeff Washington (jwash) <wash678@gmail.com>
2022-04-02 17:22:33 +00:00
mergify[bot]
f2f20af768 Fix typo in documentation (#24076) (#24077)
(cherry picked from commit 4968e7d38c)

Co-authored-by: blake <572337+bartenbach@users.noreply.github.com>
2022-04-02 13:35:39 +00:00
mergify[bot]
a8855386c1 zk-token-sdk: handle edge cases for transfer with fee (#23804) (#23818)
* zk-token-sdk: handle edge cases for transfer with fee

* zk-token-sdk: clippy

* zk-token-sdk: clippy

* zk-token-sdk: cargo fmt

(cherry picked from commit 10eeafd3d6)

Co-authored-by: samkim-crypto <skim13@cs.stanford.edu>
2022-04-01 20:02:10 -04:00
mergify[bot]
6048b71640 Revert voting service to use UDP instead of QUIC (backport #24032) (#24052)
* Revert voting service to use UDP instead of QUIC (#24032)

(cherry picked from commit df4d92f9cf)

# Conflicts:
#	core/src/voting_service.rs

* resolve merge conflicts

Co-authored-by: Pankaj Garg <pankaj@solana.com>
2022-04-01 18:52:27 +00:00
mergify[bot]
4a4a1db836 expands lifetime of SlotStats (#23872) (#24002)
Current slot stats are removed when the slot is full or every 30 seconds
if the slot is before root:
https://github.com/solana-labs/solana/blob/493a8e234/ledger/src/blockstore.rs#L2017-L2027

In order to track if the slot is ultimately marked as dead or rooted and
emit more metrics, this commit expands lifetime of SlotStats while
bounding total size of cache using an LRU eviction policy.

(cherry picked from commit 1f9c89c1e8)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2022-04-01 14:50:12 +00:00
mergify[bot]
c7889f8def uses first_coding_index for erasure meta obtained from coding shreds (#23974) (#24001)
Now that nodes correctly populate position field in coding shreds, and
first_coding_index in erasure meta, the old code to maintain backward
compatibility can be removed.
The commit is working towards changing erasure coding schema to 32:64.

(cherry picked from commit cda3d66b21)

Co-authored-by: behzad nouri <behzadnouri@gmail.com>
2022-04-01 14:49:39 +00:00
Michael Vines
832f524687 Update Version CrdsData on node identity changes
(cherry picked from commit 7ef18f220a)
2022-03-28 19:57:48 -07:00
Will Hickey
a639282c0f Bump version to 1.10.6 (#23969) 2022-03-28 10:56:01 -05:00
mergify[bot]
5eb085fcaf Implement forwarding via TpuConnection (#23817) (#23936)
(cherry picked from commit 6b85c2104c)

Co-authored-by: ryleung-solana <91908731+ryleung-solana@users.noreply.github.com>
2022-03-28 16:38:44 +02:00
mergify[bot]
c66d086db1 fix: thread enforce_ulimit_nofile config down when opening blockstore (#23925) (#23958)
(cherry picked from commit f44c8f296f)

Co-authored-by: Steven Luscher <steveluscher@users.noreply.github.com>
2022-03-26 20:09:49 +00:00
mergify[bot]
0c740ebba6 Specify if archive size datapoint is for full or incremental snapshots (#23941) (#23957)
(cherry picked from commit 31b707b625)

Co-authored-by: Brooks Prumo <brooks@solana.com>
2022-03-26 19:25:39 +00:00
Will Hickey
fd49ed1959 Bump version to 1.10.5 (#23955) 2022-03-26 11:34:12 -05:00
248 changed files with 5084 additions and 3627 deletions

704
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -88,3 +88,6 @@ members = [
exclude = [
"programs/bpf",
]
# This prevents a Travis CI error when building for Windows.
resolver = "2"

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-account-decoder"
version = "1.10.4"
version = "1.10.9"
description = "Solana account decoder"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -19,9 +19,9 @@ lazy_static = "1.4.0"
serde = "1.0.136"
serde_derive = "1.0.103"
serde_json = "1.0.79"
solana-config-program = { path = "../programs/config", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.4" }
solana-config-program = { path = "../programs/config", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.9" }
spl-token = { version = "=3.2.0", features = ["no-entrypoint"] }
thiserror = "1.0"
zstd = "0.11.1"

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-accounts-bench"
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -12,11 +12,11 @@ publish = false
clap = "2.33.1"
log = "0.4.14"
rayon = "1.5.1"
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-measure = { path = "../measure", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.9" }
solana-measure = { path = "../measure", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-accounts-cluster-bench"
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -13,25 +13,25 @@ clap = "2.33.1"
log = "0.4.14"
rand = "0.7.0"
rayon = "1.5.1"
solana-account-decoder = { path = "../account-decoder", version = "=1.10.4" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.4" }
solana-client = { path = "../client", version = "=1.10.4" }
solana-faucet = { path = "../faucet", version = "=1.10.4" }
solana-gossip = { path = "../gossip", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-measure = { path = "../measure", version = "=1.10.4" }
solana-net-utils = { path = "../net-utils", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-streamer = { path = "../streamer", version = "=1.10.4" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-account-decoder = { path = "../account-decoder", version = "=1.10.9" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.9" }
solana-client = { path = "../client", version = "=1.10.9" }
solana-faucet = { path = "../faucet", version = "=1.10.9" }
solana-gossip = { path = "../gossip", version = "=1.10.9" }
solana-logger = { path = "../logger", version = "=1.10.9" }
solana-measure = { path = "../measure", version = "=1.10.9" }
solana-net-utils = { path = "../net-utils", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-streamer = { path = "../streamer", version = "=1.10.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
spl-token = { version = "=3.2.0", features = ["no-entrypoint"] }
[dev-dependencies]
solana-core = { path = "../core", version = "=1.10.4" }
solana-local-cluster = { path = "../local-cluster", version = "=1.10.4" }
solana-test-validator = { path = "../test-validator", version = "=1.10.4" }
solana-core = { path = "../core", version = "=1.10.9" }
solana-local-cluster = { path = "../local-cluster", version = "=1.10.9" }
solana-test-validator = { path = "../test-validator", version = "=1.10.9" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-banking-bench"
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -14,17 +14,17 @@ crossbeam-channel = "0.5"
log = "0.4.14"
rand = "0.7.0"
rayon = "1.5.1"
solana-core = { path = "../core", version = "=1.10.4" }
solana-gossip = { path = "../gossip", version = "=1.10.4" }
solana-ledger = { path = "../ledger", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-measure = { path = "../measure", version = "=1.10.4" }
solana-perf = { path = "../perf", version = "=1.10.4" }
solana-poh = { path = "../poh", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-streamer = { path = "../streamer", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-core = { path = "../core", version = "=1.10.9" }
solana-gossip = { path = "../gossip", version = "=1.10.9" }
solana-ledger = { path = "../ledger", version = "=1.10.9" }
solana-logger = { path = "../logger", version = "=1.10.9" }
solana-measure = { path = "../measure", version = "=1.10.9" }
solana-perf = { path = "../perf", version = "=1.10.9" }
solana-poh = { path = "../poh", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-streamer = { path = "../streamer", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-banks-client"
version = "1.10.4"
version = "1.10.9"
description = "Solana banks client"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -12,17 +12,17 @@ edition = "2021"
[dependencies]
borsh = "0.9.3"
futures = "0.3"
solana-banks-interface = { path = "../banks-interface", version = "=1.10.4" }
solana-program = { path = "../sdk/program", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-banks-interface = { path = "../banks-interface", version = "=1.10.9" }
solana-program = { path = "../sdk/program", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
tarpc = { version = "0.27.2", features = ["full"] }
thiserror = "1.0"
tokio = { version = "1", features = ["full"] }
tokio-serde = { version = "0.8", features = ["bincode"] }
[dev-dependencies]
solana-banks-server = { path = "../banks-server", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-banks-server = { path = "../banks-server", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
[lib]
crate-type = ["lib"]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-banks-interface"
version = "1.10.4"
version = "1.10.9"
description = "Solana banks RPC interface"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -11,7 +11,7 @@ edition = "2021"
[dependencies]
serde = { version = "1.0.136", features = ["derive"] }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
tarpc = { version = "0.27.2", features = ["full"] }
[lib]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-banks-server"
version = "1.10.4"
version = "1.10.9"
description = "Solana banks server"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -13,10 +13,10 @@ edition = "2021"
bincode = "1.3.3"
crossbeam-channel = "0.5"
futures = "0.3"
solana-banks-interface = { path = "../banks-interface", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-send-transaction-service = { path = "../send-transaction-service", version = "=1.10.4" }
solana-banks-interface = { path = "../banks-interface", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-send-transaction-service = { path = "../send-transaction-service", version = "=1.10.9" }
tarpc = { version = "0.27.2", features = ["full"] }
tokio = { version = "1", features = ["full"] }
tokio-serde = { version = "0.8", features = ["bincode"] }

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-bench-streamer"
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -11,9 +11,9 @@ publish = false
[dependencies]
clap = "2.33.1"
crossbeam-channel = "0.5"
solana-net-utils = { path = "../net-utils", version = "=1.10.4" }
solana-streamer = { path = "../streamer", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-net-utils = { path = "../net-utils", version = "=1.10.9" }
solana-streamer = { path = "../streamer", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-bench-tps"
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -15,23 +15,28 @@ log = "0.4.14"
rayon = "1.5.1"
serde_json = "1.0.79"
serde_yaml = "0.8.23"
solana-client = { path = "../client", version = "=1.10.4" }
solana-core = { path = "../core", version = "=1.10.4" }
solana-faucet = { path = "../faucet", version = "=1.10.4" }
solana-genesis = { path = "../genesis", version = "=1.10.4" }
solana-gossip = { path = "../gossip", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-measure = { path = "../measure", version = "=1.10.4" }
solana-metrics = { path = "../metrics", version = "=1.10.4" }
solana-net-utils = { path = "../net-utils", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-streamer = { path = "../streamer", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.9" }
solana-cli-config = { path = "../cli-config", version = "=1.10.9" }
solana-client = { path = "../client", version = "=1.10.9" }
solana-core = { path = "../core", version = "=1.10.9" }
solana-faucet = { path = "../faucet", version = "=1.10.9" }
solana-genesis = { path = "../genesis", version = "=1.10.9" }
solana-gossip = { path = "../gossip", version = "=1.10.9" }
solana-logger = { path = "../logger", version = "=1.10.9" }
solana-measure = { path = "../measure", version = "=1.10.9" }
solana-metrics = { path = "../metrics", version = "=1.10.9" }
solana-net-utils = { path = "../net-utils", version = "=1.10.9" }
solana-rpc = { path = "../rpc", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-streamer = { path = "../streamer", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
thiserror = "1.0"
[dev-dependencies]
serial_test = "0.6.0"
solana-local-cluster = { path = "../local-cluster", version = "=1.10.4" }
solana-local-cluster = { path = "../local-cluster", version = "=1.10.9" }
solana-test-validator = { path = "../test-validator", version = "=1.10.9" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,19 +1,21 @@
use {
crate::cli::Config,
crate::{
bench_tps_client::*,
cli::Config,
perf_utils::{sample_txs, SampleStats},
},
log::*,
rayon::prelude::*,
solana_client::perf_utils::{sample_txs, SampleStats},
solana_core::gen_keys::GenKeys,
solana_faucet::faucet::request_airdrop_transaction,
solana_measure::measure::Measure,
solana_metrics::{self, datapoint_info},
solana_sdk::{
client::Client,
clock::{DEFAULT_MS_PER_SLOT, DEFAULT_S_PER_SLOT, MAX_PROCESSING_AGE},
commitment_config::CommitmentConfig,
hash::Hash,
instruction::{AccountMeta, Instruction},
message::Message,
native_token::Sol,
pubkey::Pubkey,
signature::{Keypair, Signer},
system_instruction, system_transaction,
@@ -22,7 +24,6 @@ use {
},
std::{
collections::{HashSet, VecDeque},
net::SocketAddr,
process::exit,
sync::{
atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering},
@@ -38,16 +39,9 @@ const MAX_TX_QUEUE_AGE: u64 = (MAX_PROCESSING_AGE as f64 * DEFAULT_S_PER_SLOT) a
pub const MAX_SPENDS_PER_TX: u64 = 4;
#[derive(Debug)]
pub enum BenchTpsError {
AirdropFailure,
}
pub type Result<T> = std::result::Result<T, BenchTpsError>;
pub type SharedTransactions = Arc<RwLock<VecDeque<Vec<(Transaction, u64)>>>>;
fn get_latest_blockhash<T: Client>(client: &T) -> Hash {
fn get_latest_blockhash<T: BenchTpsClient>(client: &T) -> Hash {
loop {
match client.get_latest_blockhash_with_commitment(CommitmentConfig::processed()) {
Ok((blockhash, _)) => return blockhash,
@@ -61,7 +55,7 @@ fn get_latest_blockhash<T: Client>(client: &T) -> Hash {
fn wait_for_target_slots_per_epoch<T>(target_slots_per_epoch: u64, client: &Arc<T>)
where
T: 'static + Client + Send + Sync,
T: 'static + BenchTpsClient + Send + Sync,
{
if target_slots_per_epoch != 0 {
info!(
@@ -91,7 +85,7 @@ fn create_sampler_thread<T>(
maxes: &Arc<RwLock<Vec<(String, SampleStats)>>>,
) -> JoinHandle<()>
where
T: 'static + Client + Send + Sync,
T: 'static + BenchTpsClient + Send + Sync,
{
info!("Sampling TPS every {} second...", sample_period);
let exit_signal = exit_signal.clone();
@@ -169,7 +163,7 @@ fn create_sender_threads<T>(
shared_tx_active_thread_count: &Arc<AtomicIsize>,
) -> Vec<JoinHandle<()>>
where
T: 'static + Client + Send + Sync,
T: 'static + BenchTpsClient + Send + Sync,
{
(0..threads)
.map(|_| {
@@ -197,7 +191,7 @@ where
pub fn do_bench_tps<T>(client: Arc<T>, config: Config, gen_keypairs: Vec<Keypair>) -> u64
where
T: 'static + Client + Send + Sync,
T: 'static + BenchTpsClient + Send + Sync,
{
let Config {
id,
@@ -391,7 +385,7 @@ fn generate_txs(
}
}
fn get_new_latest_blockhash<T: Client>(client: &Arc<T>, blockhash: &Hash) -> Option<Hash> {
fn get_new_latest_blockhash<T: BenchTpsClient>(client: &Arc<T>, blockhash: &Hash) -> Option<Hash> {
let start = Instant::now();
while start.elapsed().as_secs() < 5 {
if let Ok(new_blockhash) = client.get_latest_blockhash() {
@@ -407,7 +401,7 @@ fn get_new_latest_blockhash<T: Client>(client: &Arc<T>, blockhash: &Hash) -> Opt
None
}
fn poll_blockhash<T: Client>(
fn poll_blockhash<T: BenchTpsClient>(
exit_signal: &Arc<AtomicBool>,
blockhash: &Arc<RwLock<Hash>>,
client: &Arc<T>,
@@ -449,7 +443,7 @@ fn poll_blockhash<T: Client>(
}
}
fn do_tx_transfers<T: Client>(
fn do_tx_transfers<T: BenchTpsClient>(
exit_signal: &Arc<AtomicBool>,
shared_txs: &SharedTransactions,
shared_tx_thread_count: &Arc<AtomicIsize>,
@@ -467,11 +461,7 @@ fn do_tx_transfers<T: Client>(
};
if let Some(txs0) = txs {
shared_tx_thread_count.fetch_add(1, Ordering::Relaxed);
info!(
"Transferring 1 unit {} times... to {}",
txs0.len(),
client.as_ref().tpu_addr(),
);
info!("Transferring 1 unit {} times...", txs0.len());
let tx_len = txs0.len();
let transfer_start = Instant::now();
let mut old_transactions = false;
@@ -487,7 +477,7 @@ fn do_tx_transfers<T: Client>(
transactions.push(tx.0);
}
if let Err(error) = client.async_send_batch(transactions) {
if let Err(error) = client.send_batch(transactions) {
warn!("send_batch_sync in do_tx_transfers failed: {}", error);
}
@@ -514,7 +504,11 @@ fn do_tx_transfers<T: Client>(
}
}
fn verify_funding_transfer<T: Client>(client: &Arc<T>, tx: &Transaction, amount: u64) -> bool {
fn verify_funding_transfer<T: BenchTpsClient>(
client: &Arc<T>,
tx: &Transaction,
amount: u64,
) -> bool {
for a in &tx.message().account_keys[1..] {
match client.get_balance_with_commitment(a, CommitmentConfig::processed()) {
Ok(balance) => return balance >= amount,
@@ -525,7 +519,7 @@ fn verify_funding_transfer<T: Client>(client: &Arc<T>, tx: &Transaction, amount:
}
trait FundingTransactions<'a> {
fn fund<T: 'static + Client + Send + Sync>(
fn fund<T: 'static + BenchTpsClient + Send + Sync>(
&mut self,
client: &Arc<T>,
to_fund: &[(&'a Keypair, Vec<(Pubkey, u64)>)],
@@ -533,12 +527,16 @@ trait FundingTransactions<'a> {
);
fn make(&mut self, to_fund: &[(&'a Keypair, Vec<(Pubkey, u64)>)]);
fn sign(&mut self, blockhash: Hash);
fn send<T: Client>(&self, client: &Arc<T>);
fn verify<T: 'static + Client + Send + Sync>(&mut self, client: &Arc<T>, to_lamports: u64);
fn send<T: BenchTpsClient>(&self, client: &Arc<T>);
fn verify<T: 'static + BenchTpsClient + Send + Sync>(
&mut self,
client: &Arc<T>,
to_lamports: u64,
);
}
impl<'a> FundingTransactions<'a> for Vec<(&'a Keypair, Transaction)> {
fn fund<T: 'static + Client + Send + Sync>(
fn fund<T: 'static + BenchTpsClient + Send + Sync>(
&mut self,
client: &Arc<T>,
to_fund: &[(&'a Keypair, Vec<(Pubkey, u64)>)],
@@ -607,16 +605,20 @@ impl<'a> FundingTransactions<'a> for Vec<(&'a Keypair, Transaction)> {
debug!("sign {} txs: {}us", self.len(), sign_txs.as_us());
}
fn send<T: Client>(&self, client: &Arc<T>) {
fn send<T: BenchTpsClient>(&self, client: &Arc<T>) {
let mut send_txs = Measure::start("send_txs");
self.iter().for_each(|(_, tx)| {
client.async_send_transaction(tx.clone()).expect("transfer");
client.send_transaction(tx.clone()).expect("transfer");
});
send_txs.stop();
debug!("send {} txs: {}us", self.len(), send_txs.as_us());
}
fn verify<T: 'static + Client + Send + Sync>(&mut self, client: &Arc<T>, to_lamports: u64) {
fn verify<T: 'static + BenchTpsClient + Send + Sync>(
&mut self,
client: &Arc<T>,
to_lamports: u64,
) {
let starting_txs = self.len();
let verified_txs = Arc::new(AtomicUsize::new(0));
let too_many_failures = Arc::new(AtomicBool::new(false));
@@ -691,7 +693,7 @@ impl<'a> FundingTransactions<'a> for Vec<(&'a Keypair, Transaction)> {
/// fund the dests keys by spending all of the source keys into MAX_SPENDS_PER_TX
/// on every iteration. This allows us to replay the transfers because the source is either empty,
/// or full
pub fn fund_keys<T: 'static + Client + Send + Sync>(
pub fn fund_keys<T: 'static + BenchTpsClient + Send + Sync>(
client: Arc<T>,
source: &Keypair,
dests: &[Keypair],
@@ -733,75 +735,6 @@ pub fn fund_keys<T: 'static + Client + Send + Sync>(
}
}
pub fn airdrop_lamports<T: Client>(
client: &T,
faucet_addr: &SocketAddr,
id: &Keypair,
desired_balance: u64,
) -> Result<()> {
let starting_balance = client.get_balance(&id.pubkey()).unwrap_or(0);
metrics_submit_lamport_balance(starting_balance);
info!("starting balance {}", starting_balance);
if starting_balance < desired_balance {
let airdrop_amount = desired_balance - starting_balance;
info!(
"Airdropping {:?} lamports from {} for {}",
airdrop_amount,
faucet_addr,
id.pubkey(),
);
let blockhash = get_latest_blockhash(client);
match request_airdrop_transaction(faucet_addr, &id.pubkey(), airdrop_amount, blockhash) {
Ok(transaction) => {
let mut tries = 0;
loop {
tries += 1;
let signature = client.async_send_transaction(transaction.clone()).unwrap();
let result = client.poll_for_signature_confirmation(&signature, 1);
if result.is_ok() {
break;
}
if tries >= 5 {
panic!(
"Error requesting airdrop: to addr: {:?} amount: {} {:?}",
faucet_addr, airdrop_amount, result
)
}
}
}
Err(err) => {
panic!(
"Error requesting airdrop: {:?} to addr: {:?} amount: {}",
err, faucet_addr, airdrop_amount
);
}
};
let current_balance = client
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::processed())
.unwrap_or_else(|e| {
info!("airdrop error {}", e);
starting_balance
});
info!("current balance {}...", current_balance);
metrics_submit_lamport_balance(current_balance);
if current_balance - starting_balance != airdrop_amount {
info!(
"Airdrop failed! {} {} {}",
id.pubkey(),
current_balance,
starting_balance
);
return Err(BenchTpsError::AirdropFailure);
}
}
Ok(())
}
fn compute_and_report_stats(
maxes: &Arc<RwLock<Vec<(String, SampleStats)>>>,
sample_period: u64,
@@ -885,15 +818,33 @@ pub fn generate_keypairs(seed_keypair: &Keypair, count: u64) -> (Vec<Keypair>, u
(rnd.gen_n_keypairs(total_keys), extra)
}
pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
pub fn generate_and_fund_keypairs<T: 'static + BenchTpsClient + Send + Sync>(
client: Arc<T>,
faucet_addr: Option<SocketAddr>,
funding_key: &Keypair,
keypair_count: usize,
lamports_per_account: u64,
) -> Result<Vec<Keypair>> {
let rent = client.get_minimum_balance_for_rent_exemption(0)?;
let lamports_per_account = lamports_per_account + rent;
info!("Creating {} keypairs...", keypair_count);
let (mut keypairs, extra) = generate_keypairs(funding_key, keypair_count as u64);
fund_keypairs(client, funding_key, &keypairs, extra, lamports_per_account)?;
// 'generate_keypairs' generates extra keys to be able to have size-aligned funding batches for fund_keys.
keypairs.truncate(keypair_count);
Ok(keypairs)
}
pub fn fund_keypairs<T: 'static + BenchTpsClient + Send + Sync>(
client: Arc<T>,
funding_key: &Keypair,
keypairs: &[Keypair],
extra: u64,
lamports_per_account: u64,
) -> Result<()> {
let rent = client.get_minimum_balance_for_rent_exemption(0)?;
info!("Get lamports...");
// Sample the first keypair, to prevent lamport loss on repeated solana-bench-tps executions
@@ -901,7 +852,7 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
let first_keypair_balance = client.get_balance(&first_key).unwrap_or(0);
// Sample the last keypair, to check if funding was already completed
let last_key = keypairs[keypair_count - 1].pubkey();
let last_key = keypairs[keypairs.len() - 1].pubkey();
let last_keypair_balance = client.get_balance(&last_key).unwrap_or(0);
// Repeated runs will eat up keypair balances from transaction fees. In order to quickly
@@ -930,24 +881,35 @@ pub fn generate_and_fund_keypairs<T: 'static + Client + Send + Sync>(
funding_key_balance, max_fee, lamports_per_account, extra, total
);
if client.get_balance(&funding_key.pubkey()).unwrap_or(0) < total {
airdrop_lamports(client.as_ref(), &faucet_addr.unwrap(), funding_key, total)?;
if funding_key_balance < total + rent {
error!(
"funder has {}, needed {}",
Sol(funding_key_balance),
Sol(total)
);
let latest_blockhash = get_latest_blockhash(client.as_ref());
if client
.request_airdrop_with_blockhash(
&funding_key.pubkey(),
total + rent - funding_key_balance,
&latest_blockhash,
)
.is_err()
{
return Err(BenchTpsError::AirdropFailure);
}
}
fund_keys(
client,
funding_key,
&keypairs,
keypairs,
total,
max_fee,
lamports_per_account,
);
}
// 'generate_keypairs' generates extra keys to be able to have size-aligned funding batches for fund_keys.
keypairs.truncate(keypair_count);
Ok(keypairs)
Ok(())
}
#[cfg(test)]
@@ -956,14 +918,14 @@ mod tests {
super::*,
solana_runtime::{bank::Bank, bank_client::BankClient},
solana_sdk::{
client::SyncClient, fee_calculator::FeeRateGovernor,
genesis_config::create_genesis_config,
fee_calculator::FeeRateGovernor, genesis_config::create_genesis_config,
native_token::sol_to_lamports,
},
};
#[test]
fn test_bench_tps_bank_client() {
let (genesis_config, id) = create_genesis_config(10_000);
let (genesis_config, id) = create_genesis_config(sol_to_lamports(10_000.0));
let bank = Bank::new_for_tests(&genesis_config);
let client = Arc::new(BankClient::new(bank));
@@ -976,48 +938,49 @@ mod tests {
let keypair_count = config.tx_count * config.keypair_multiplier;
let keypairs =
generate_and_fund_keypairs(client.clone(), None, &config.id, keypair_count, 20)
.unwrap();
generate_and_fund_keypairs(client.clone(), &config.id, keypair_count, 20).unwrap();
do_bench_tps(client, config, keypairs);
}
#[test]
fn test_bench_tps_fund_keys() {
let (genesis_config, id) = create_genesis_config(10_000);
let (genesis_config, id) = create_genesis_config(sol_to_lamports(10_000.0));
let bank = Bank::new_for_tests(&genesis_config);
let client = Arc::new(BankClient::new(bank));
let keypair_count = 20;
let lamports = 20;
let rent = client.get_minimum_balance_for_rent_exemption(0).unwrap();
let keypairs =
generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports).unwrap();
generate_and_fund_keypairs(client.clone(), &id, keypair_count, lamports).unwrap();
for kp in &keypairs {
assert_eq!(
client
.get_balance_with_commitment(&kp.pubkey(), CommitmentConfig::processed())
.unwrap(),
lamports
lamports + rent
);
}
}
#[test]
fn test_bench_tps_fund_keys_with_fees() {
let (mut genesis_config, id) = create_genesis_config(10_000);
let (mut genesis_config, id) = create_genesis_config(sol_to_lamports(10_000.0));
let fee_rate_governor = FeeRateGovernor::new(11, 0);
genesis_config.fee_rate_governor = fee_rate_governor;
let bank = Bank::new_for_tests(&genesis_config);
let client = Arc::new(BankClient::new(bank));
let keypair_count = 20;
let lamports = 20;
let rent = client.get_minimum_balance_for_rent_exemption(0).unwrap();
let keypairs =
generate_and_fund_keypairs(client.clone(), None, &id, keypair_count, lamports).unwrap();
generate_and_fund_keypairs(client.clone(), &id, keypair_count, lamports).unwrap();
for kp in &keypairs {
assert_eq!(client.get_balance(&kp.pubkey()).unwrap(), lamports);
assert_eq!(client.get_balance(&kp.pubkey()).unwrap(), lamports + rent);
}
}
}

View File

@@ -0,0 +1,87 @@
use {
solana_client::{client_error::ClientError, tpu_client::TpuSenderError},
solana_sdk::{
commitment_config::CommitmentConfig, epoch_info::EpochInfo, hash::Hash, message::Message,
pubkey::Pubkey, signature::Signature, transaction::Transaction, transport::TransportError,
},
thiserror::Error,
};
#[derive(Error, Debug)]
pub enum BenchTpsError {
#[error("Airdrop failure")]
AirdropFailure,
#[error("IO error: {0:?}")]
IoError(#[from] std::io::Error),
#[error("Client error: {0:?}")]
ClientError(#[from] ClientError),
#[error("TpuClient error: {0:?}")]
TpuSenderError(#[from] TpuSenderError),
#[error("Transport error: {0:?}")]
TransportError(#[from] TransportError),
#[error("Custom error: {0}")]
Custom(String),
}
pub(crate) type Result<T> = std::result::Result<T, BenchTpsError>;
pub trait BenchTpsClient {
/// Send a signed transaction without confirmation
fn send_transaction(&self, transaction: Transaction) -> Result<Signature>;
/// Send a batch of signed transactions without confirmation.
fn send_batch(&self, transactions: Vec<Transaction>) -> Result<()>;
/// Get latest blockhash
fn get_latest_blockhash(&self) -> Result<Hash>;
/// Get latest blockhash and its last valid block height, using explicit commitment
fn get_latest_blockhash_with_commitment(
&self,
commitment_config: CommitmentConfig,
) -> Result<(Hash, u64)>;
/// Get transaction count
fn get_transaction_count(&self) -> Result<u64>;
/// Get transaction count, using explicit commitment
fn get_transaction_count_with_commitment(
&self,
commitment_config: CommitmentConfig,
) -> Result<u64>;
/// Get epoch info
fn get_epoch_info(&self) -> Result<EpochInfo>;
/// Get account balance
fn get_balance(&self, pubkey: &Pubkey) -> Result<u64>;
/// Get account balance, using explicit commitment
fn get_balance_with_commitment(
&self,
pubkey: &Pubkey,
commitment_config: CommitmentConfig,
) -> Result<u64>;
/// Calculate the fee for a `Message`
fn get_fee_for_message(&self, message: &Message) -> Result<u64>;
/// Get the rent-exempt minimum for an account
fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> Result<u64>;
/// Return the address of client
fn addr(&self) -> String;
/// Request, submit, and confirm an airdrop transaction
fn request_airdrop_with_blockhash(
&self,
pubkey: &Pubkey,
lamports: u64,
recent_blockhash: &Hash,
) -> Result<Signature>;
}
mod bank_client;
mod rpc_client;
mod thin_client;
mod tpu_client;

View File

@@ -0,0 +1,85 @@
use {
crate::bench_tps_client::{BenchTpsClient, BenchTpsError, Result},
solana_runtime::bank_client::BankClient,
solana_sdk::{
client::{AsyncClient, SyncClient},
commitment_config::CommitmentConfig,
epoch_info::EpochInfo,
hash::Hash,
message::Message,
pubkey::Pubkey,
signature::Signature,
transaction::Transaction,
},
};
impl BenchTpsClient for BankClient {
fn send_transaction(&self, transaction: Transaction) -> Result<Signature> {
AsyncClient::async_send_transaction(self, transaction).map_err(|err| err.into())
}
fn send_batch(&self, transactions: Vec<Transaction>) -> Result<()> {
AsyncClient::async_send_batch(self, transactions).map_err(|err| err.into())
}
fn get_latest_blockhash(&self) -> Result<Hash> {
SyncClient::get_latest_blockhash(self).map_err(|err| err.into())
}
fn get_latest_blockhash_with_commitment(
&self,
commitment_config: CommitmentConfig,
) -> Result<(Hash, u64)> {
SyncClient::get_latest_blockhash_with_commitment(self, commitment_config)
.map_err(|err| err.into())
}
fn get_transaction_count(&self) -> Result<u64> {
SyncClient::get_transaction_count(self).map_err(|err| err.into())
}
fn get_transaction_count_with_commitment(
&self,
commitment_config: CommitmentConfig,
) -> Result<u64> {
SyncClient::get_transaction_count_with_commitment(self, commitment_config)
.map_err(|err| err.into())
}
fn get_epoch_info(&self) -> Result<EpochInfo> {
SyncClient::get_epoch_info(self).map_err(|err| err.into())
}
fn get_balance(&self, pubkey: &Pubkey) -> Result<u64> {
SyncClient::get_balance(self, pubkey).map_err(|err| err.into())
}
fn get_balance_with_commitment(
&self,
pubkey: &Pubkey,
commitment_config: CommitmentConfig,
) -> Result<u64> {
SyncClient::get_balance_with_commitment(self, pubkey, commitment_config)
.map_err(|err| err.into())
}
fn get_fee_for_message(&self, message: &Message) -> Result<u64> {
SyncClient::get_fee_for_message(self, message).map_err(|err| err.into())
}
fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> Result<u64> {
SyncClient::get_minimum_balance_for_rent_exemption(self, data_len).map_err(|err| err.into())
}
fn addr(&self) -> String {
"Local BankClient".to_string()
}
fn request_airdrop_with_blockhash(
&self,
_pubkey: &Pubkey,
_lamports: u64,
_recent_blockhash: &Hash,
) -> Result<Signature> {
// BankClient doesn't support airdrops
Err(BenchTpsError::AirdropFailure)
}
}

View File

@@ -0,0 +1,83 @@
use {
crate::bench_tps_client::{BenchTpsClient, Result},
solana_client::rpc_client::RpcClient,
solana_sdk::{
commitment_config::CommitmentConfig, epoch_info::EpochInfo, hash::Hash, message::Message,
pubkey::Pubkey, signature::Signature, transaction::Transaction,
},
};
impl BenchTpsClient for RpcClient {
fn send_transaction(&self, transaction: Transaction) -> Result<Signature> {
RpcClient::send_transaction(self, &transaction).map_err(|err| err.into())
}
fn send_batch(&self, transactions: Vec<Transaction>) -> Result<()> {
for transaction in transactions {
BenchTpsClient::send_transaction(self, transaction)?;
}
Ok(())
}
fn get_latest_blockhash(&self) -> Result<Hash> {
RpcClient::get_latest_blockhash(self).map_err(|err| err.into())
}
fn get_latest_blockhash_with_commitment(
&self,
commitment_config: CommitmentConfig,
) -> Result<(Hash, u64)> {
RpcClient::get_latest_blockhash_with_commitment(self, commitment_config)
.map_err(|err| err.into())
}
fn get_transaction_count(&self) -> Result<u64> {
RpcClient::get_transaction_count(self).map_err(|err| err.into())
}
fn get_transaction_count_with_commitment(
&self,
commitment_config: CommitmentConfig,
) -> Result<u64> {
RpcClient::get_transaction_count_with_commitment(self, commitment_config)
.map_err(|err| err.into())
}
fn get_epoch_info(&self) -> Result<EpochInfo> {
RpcClient::get_epoch_info(self).map_err(|err| err.into())
}
fn get_balance(&self, pubkey: &Pubkey) -> Result<u64> {
RpcClient::get_balance(self, pubkey).map_err(|err| err.into())
}
fn get_balance_with_commitment(
&self,
pubkey: &Pubkey,
commitment_config: CommitmentConfig,
) -> Result<u64> {
RpcClient::get_balance_with_commitment(self, pubkey, commitment_config)
.map(|res| res.value)
.map_err(|err| err.into())
}
fn get_fee_for_message(&self, message: &Message) -> Result<u64> {
RpcClient::get_fee_for_message(self, message).map_err(|err| err.into())
}
fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> Result<u64> {
RpcClient::get_minimum_balance_for_rent_exemption(self, data_len).map_err(|err| err.into())
}
fn addr(&self) -> String {
self.url()
}
fn request_airdrop_with_blockhash(
&self,
pubkey: &Pubkey,
lamports: u64,
recent_blockhash: &Hash,
) -> Result<Signature> {
RpcClient::request_airdrop_with_blockhash(self, pubkey, lamports, recent_blockhash)
.map_err(|err| err.into())
}
}

View File

@@ -0,0 +1,86 @@
use {
crate::bench_tps_client::{BenchTpsClient, Result},
solana_client::thin_client::ThinClient,
solana_sdk::{
client::{AsyncClient, Client, SyncClient},
commitment_config::CommitmentConfig,
epoch_info::EpochInfo,
hash::Hash,
message::Message,
pubkey::Pubkey,
signature::Signature,
transaction::Transaction,
},
};
impl BenchTpsClient for ThinClient {
fn send_transaction(&self, transaction: Transaction) -> Result<Signature> {
AsyncClient::async_send_transaction(self, transaction).map_err(|err| err.into())
}
fn send_batch(&self, transactions: Vec<Transaction>) -> Result<()> {
AsyncClient::async_send_batch(self, transactions).map_err(|err| err.into())
}
fn get_latest_blockhash(&self) -> Result<Hash> {
SyncClient::get_latest_blockhash(self).map_err(|err| err.into())
}
fn get_latest_blockhash_with_commitment(
&self,
commitment_config: CommitmentConfig,
) -> Result<(Hash, u64)> {
SyncClient::get_latest_blockhash_with_commitment(self, commitment_config)
.map_err(|err| err.into())
}
fn get_transaction_count(&self) -> Result<u64> {
SyncClient::get_transaction_count(self).map_err(|err| err.into())
}
fn get_transaction_count_with_commitment(
&self,
commitment_config: CommitmentConfig,
) -> Result<u64> {
SyncClient::get_transaction_count_with_commitment(self, commitment_config)
.map_err(|err| err.into())
}
fn get_epoch_info(&self) -> Result<EpochInfo> {
SyncClient::get_epoch_info(self).map_err(|err| err.into())
}
fn get_balance(&self, pubkey: &Pubkey) -> Result<u64> {
SyncClient::get_balance(self, pubkey).map_err(|err| err.into())
}
fn get_balance_with_commitment(
&self,
pubkey: &Pubkey,
commitment_config: CommitmentConfig,
) -> Result<u64> {
SyncClient::get_balance_with_commitment(self, pubkey, commitment_config)
.map_err(|err| err.into())
}
fn get_fee_for_message(&self, message: &Message) -> Result<u64> {
SyncClient::get_fee_for_message(self, message).map_err(|err| err.into())
}
fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> Result<u64> {
SyncClient::get_minimum_balance_for_rent_exemption(self, data_len).map_err(|err| err.into())
}
fn addr(&self) -> String {
Client::tpu_addr(self)
}
fn request_airdrop_with_blockhash(
&self,
pubkey: &Pubkey,
lamports: u64,
recent_blockhash: &Hash,
) -> Result<Signature> {
self.rpc_client()
.request_airdrop_with_blockhash(pubkey, lamports, recent_blockhash)
.map_err(|err| err.into())
}
}

View File

@@ -0,0 +1,99 @@
use {
crate::bench_tps_client::{BenchTpsClient, Result},
solana_client::tpu_client::TpuClient,
solana_sdk::{
commitment_config::CommitmentConfig, epoch_info::EpochInfo, hash::Hash, message::Message,
pubkey::Pubkey, signature::Signature, transaction::Transaction,
},
};
impl BenchTpsClient for TpuClient {
fn send_transaction(&self, transaction: Transaction) -> Result<Signature> {
let signature = transaction.signatures[0];
self.try_send_transaction(&transaction)?;
Ok(signature)
}
fn send_batch(&self, transactions: Vec<Transaction>) -> Result<()> {
for transaction in transactions {
BenchTpsClient::send_transaction(self, transaction)?;
}
Ok(())
}
fn get_latest_blockhash(&self) -> Result<Hash> {
self.rpc_client()
.get_latest_blockhash()
.map_err(|err| err.into())
}
fn get_latest_blockhash_with_commitment(
&self,
commitment_config: CommitmentConfig,
) -> Result<(Hash, u64)> {
self.rpc_client()
.get_latest_blockhash_with_commitment(commitment_config)
.map_err(|err| err.into())
}
fn get_transaction_count(&self) -> Result<u64> {
self.rpc_client()
.get_transaction_count()
.map_err(|err| err.into())
}
fn get_transaction_count_with_commitment(
&self,
commitment_config: CommitmentConfig,
) -> Result<u64> {
self.rpc_client()
.get_transaction_count_with_commitment(commitment_config)
.map_err(|err| err.into())
}
fn get_epoch_info(&self) -> Result<EpochInfo> {
self.rpc_client().get_epoch_info().map_err(|err| err.into())
}
fn get_balance(&self, pubkey: &Pubkey) -> Result<u64> {
self.rpc_client()
.get_balance(pubkey)
.map_err(|err| err.into())
}
fn get_balance_with_commitment(
&self,
pubkey: &Pubkey,
commitment_config: CommitmentConfig,
) -> Result<u64> {
self.rpc_client()
.get_balance_with_commitment(pubkey, commitment_config)
.map(|res| res.value)
.map_err(|err| err.into())
}
fn get_fee_for_message(&self, message: &Message) -> Result<u64> {
self.rpc_client()
.get_fee_for_message(message)
.map_err(|err| err.into())
}
fn get_minimum_balance_for_rent_exemption(&self, data_len: usize) -> Result<u64> {
self.rpc_client()
.get_minimum_balance_for_rent_exemption(data_len)
.map_err(|err| err.into())
}
fn addr(&self) -> String {
self.rpc_client().url()
}
fn request_airdrop_with_blockhash(
&self,
pubkey: &Pubkey,
lamports: u64,
recent_blockhash: &Hash,
) -> Result<Signature> {
self.rpc_client()
.request_airdrop_with_blockhash(pubkey, lamports, recent_blockhash)
.map_err(|err| err.into())
}
}

View File

@@ -1,6 +1,7 @@
use {
clap::{crate_description, crate_name, App, Arg, ArgMatches},
solana_faucet::faucet::FAUCET_PORT,
solana_clap_utils::input_validators::{is_url, is_url_or_moniker},
solana_cli_config::{ConfigInput, CONFIG_FILE},
solana_sdk::{
fee_calculator::FeeRateGovernor,
pubkey::Pubkey,
@@ -11,10 +12,28 @@ use {
const NUM_LAMPORTS_PER_ACCOUNT_DEFAULT: u64 = solana_sdk::native_token::LAMPORTS_PER_SOL;
pub enum ExternalClientType {
// Submits transactions to an Rpc node using an RpcClient
RpcClient,
// Submits transactions directly to leaders using a ThinClient, broadcasting to multiple
// leaders when num_nodes > 1
ThinClient,
// Submits transactions directly to leaders using a TpuClient, broadcasting to upcoming leaders
// via TpuClient default configuration
TpuClient,
}
impl Default for ExternalClientType {
fn default() -> Self {
Self::ThinClient
}
}
/// Holds the configuration for a single run of the benchmark
pub struct Config {
pub entrypoint_addr: SocketAddr,
pub faucet_addr: SocketAddr,
pub json_rpc_url: String,
pub websocket_url: String,
pub id: Keypair,
pub threads: usize,
pub num_nodes: usize,
@@ -31,13 +50,16 @@ pub struct Config {
pub num_lamports_per_account: u64,
pub target_slots_per_epoch: u64,
pub target_node: Option<Pubkey>,
pub external_client_type: ExternalClientType,
pub use_quic: bool,
}
impl Default for Config {
fn default() -> Config {
Config {
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
faucet_addr: SocketAddr::from(([127, 0, 0, 1], FAUCET_PORT)),
json_rpc_url: ConfigInput::default().json_rpc_url,
websocket_url: ConfigInput::default().websocket_url,
id: Keypair::new(),
threads: 4,
num_nodes: 1,
@@ -54,6 +76,8 @@ impl Default for Config {
num_lamports_per_account: NUM_LAMPORTS_PER_ACCOUNT_DEFAULT,
target_slots_per_epoch: 0,
target_node: None,
external_client_type: ExternalClientType::default(),
use_quic: false,
}
}
}
@@ -62,6 +86,42 @@ impl Default for Config {
pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
App::new(crate_name!()).about(crate_description!())
.version(version)
.arg({
let arg = Arg::with_name("config_file")
.short("C")
.long("config")
.value_name("FILEPATH")
.takes_value(true)
.global(true)
.help("Configuration file to use");
if let Some(ref config_file) = *CONFIG_FILE {
arg.default_value(config_file)
} else {
arg
}
})
.arg(
Arg::with_name("json_rpc_url")
.short("u")
.long("url")
.value_name("URL_OR_MONIKER")
.takes_value(true)
.global(true)
.validator(is_url_or_moniker)
.help(
"URL for Solana's JSON RPC or moniker (or their first letter): \
[mainnet-beta, testnet, devnet, localhost]",
),
)
.arg(
Arg::with_name("websocket_url")
.long("ws")
.value_name("URL")
.takes_value(true)
.global(true)
.validator(is_url)
.help("WebSocket URL for the solana cluster"),
)
.arg(
Arg::with_name("entrypoint")
.short("n")
@@ -76,7 +136,8 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
.long("faucet")
.value_name("HOST:PORT")
.takes_value(true)
.help("Location of the faucet; defaults to entrypoint:FAUCET_PORT"),
.hidden(true)
.help("Deprecated. BenchTps no longer queries the faucet directly"),
)
.arg(
Arg::with_name("identity")
@@ -191,6 +252,27 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
"Wait until epochs are this many slots long.",
),
)
.arg(
Arg::with_name("rpc_client")
.long("use-rpc-client")
.conflicts_with("tpu_client")
.takes_value(false)
.help("Submit transactions with a RpcClient")
)
.arg(
Arg::with_name("tpu_client")
.long("use-tpu-client")
.conflicts_with("rpc_client")
.takes_value(false)
.help("Submit transactions with a TpuClient")
)
.arg(
Arg::with_name("tpu_use_quic")
.long("tpu-use-quic")
.takes_value(false)
.help("Submit transactions via QUIC; only affects ThinClient (default) \
or TpuClient sends"),
)
}
/// Parses a clap `ArgMatches` structure into a `Config`
@@ -201,6 +283,45 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
pub fn extract_args(matches: &ArgMatches) -> Config {
let mut args = Config::default();
let config = if let Some(config_file) = matches.value_of("config_file") {
solana_cli_config::Config::load(config_file).unwrap_or_default()
} else {
solana_cli_config::Config::default()
};
let (_, json_rpc_url) = ConfigInput::compute_json_rpc_url_setting(
matches.value_of("json_rpc_url").unwrap_or(""),
&config.json_rpc_url,
);
args.json_rpc_url = json_rpc_url;
let (_, websocket_url) = ConfigInput::compute_websocket_url_setting(
matches.value_of("websocket_url").unwrap_or(""),
&config.websocket_url,
matches.value_of("json_rpc_url").unwrap_or(""),
&config.json_rpc_url,
);
args.websocket_url = websocket_url;
let (_, id_path) = ConfigInput::compute_keypair_path_setting(
matches.value_of("identity").unwrap_or(""),
&config.keypair_path,
);
if let Ok(id) = read_keypair_file(id_path) {
args.id = id;
} else if matches.is_present("identity") {
panic!("could not parse identity path");
}
if matches.is_present("tpu_client") {
args.external_client_type = ExternalClientType::TpuClient;
} else if matches.is_present("rpc_client") {
args.external_client_type = ExternalClientType::RpcClient;
}
if matches.is_present("tpu_use_quic") {
args.use_quic = true;
}
if let Some(addr) = matches.value_of("entrypoint") {
args.entrypoint_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e);
@@ -208,18 +329,6 @@ pub fn extract_args(matches: &ArgMatches) -> Config {
});
}
if let Some(addr) = matches.value_of("faucet") {
args.faucet_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
eprintln!("failed to parse faucet address: {}", e);
exit(1)
});
}
if matches.is_present("identity") {
args.id = read_keypair_file(matches.value_of("identity").unwrap())
.expect("can't read client identity");
}
if let Some(t) = matches.value_of("threads") {
args.threads = t.to_string().parse().expect("can't parse threads");
}

72
bench-tps/src/keypairs.rs Normal file
View File

@@ -0,0 +1,72 @@
use {
crate::{
bench::{fund_keypairs, generate_and_fund_keypairs},
bench_tps_client::BenchTpsClient,
},
log::*,
solana_genesis::Base64Account,
solana_sdk::signature::{Keypair, Signer},
std::{collections::HashMap, fs::File, path::Path, process::exit, sync::Arc},
};
pub fn get_keypairs<T>(
client: Arc<T>,
id: &Keypair,
keypair_count: usize,
num_lamports_per_account: u64,
client_ids_and_stake_file: &str,
read_from_client_file: bool,
) -> Vec<Keypair>
where
T: 'static + BenchTpsClient + Send + Sync,
{
if read_from_client_file {
let path = Path::new(client_ids_and_stake_file);
let file = File::open(path).unwrap();
info!("Reading {}", client_ids_and_stake_file);
let accounts: HashMap<String, Base64Account> = serde_yaml::from_reader(file).unwrap();
let mut keypairs = vec![];
let mut last_balance = 0;
accounts
.into_iter()
.for_each(|(keypair, primordial_account)| {
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
keypairs.push(Keypair::from_bytes(&bytes).unwrap());
last_balance = primordial_account.balance;
});
if keypairs.len() < keypair_count {
eprintln!(
"Expected {} accounts in {}, only received {} (--tx_count mismatch?)",
keypair_count,
client_ids_and_stake_file,
keypairs.len(),
);
exit(1);
}
// Sort keypairs so that do_bench_tps() uses the same subset of accounts for each run.
// This prevents the amount of storage needed for bench-tps accounts from creeping up
// across multiple runs.
keypairs.sort_by_key(|x| x.pubkey().to_string());
fund_keypairs(
client,
id,
&keypairs,
keypairs.len().saturating_sub(keypair_count) as u64,
last_balance,
)
.unwrap_or_else(|e| {
eprintln!("Error could not fund keys: {:?}", e);
exit(1);
});
keypairs
} else {
generate_and_fund_keypairs(client, id, keypair_count, num_lamports_per_account)
.unwrap_or_else(|e| {
eprintln!("Error could not fund keys: {:?}", e);
exit(1);
})
}
}

View File

@@ -1,3 +1,6 @@
#![allow(clippy::integer_arithmetic)]
pub mod bench;
pub mod bench_tps_client;
pub mod cli;
pub mod keypairs;
mod perf_utils;

View File

@@ -2,15 +2,19 @@
use {
log::*,
solana_bench_tps::{
bench::{do_bench_tps, generate_and_fund_keypairs, generate_keypairs},
cli,
bench::{do_bench_tps, generate_keypairs},
cli::{self, ExternalClientType},
keypairs::get_keypairs,
},
solana_client::{
connection_cache,
rpc_client::RpcClient,
tpu_client::{TpuClient, TpuClientConfig},
},
solana_genesis::Base64Account,
solana_gossip::gossip_service::{discover_cluster, get_client, get_multi_client},
solana_sdk::{
fee_calculator::FeeRateGovernor,
signature::{Keypair, Signer},
system_program,
commitment_config::CommitmentConfig, fee_calculator::FeeRateGovernor, system_program,
},
solana_streamer::socket::SocketAddrSpace,
std::{collections::HashMap, fs::File, io::prelude::*, path::Path, process::exit, sync::Arc},
@@ -28,7 +32,8 @@ fn main() {
let cli::Config {
entrypoint_addr,
faucet_addr,
json_rpc_url,
websocket_url,
id,
num_nodes,
tx_count,
@@ -40,6 +45,8 @@ fn main() {
multi_client,
num_lamports_per_account,
target_node,
external_client_type,
use_quic,
..
} = &cli_config;
@@ -75,83 +82,93 @@ fn main() {
}
info!("Connecting to the cluster");
let nodes = discover_cluster(entrypoint_addr, *num_nodes, SocketAddrSpace::Unspecified)
.unwrap_or_else(|err| {
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
exit(1);
});
let client = if *multi_client {
let (client, num_clients) = get_multi_client(&nodes, &SocketAddrSpace::Unspecified);
if nodes.len() < num_clients {
eprintln!(
"Error: Insufficient nodes discovered. Expecting {} or more",
num_nodes
);
exit(1);
}
Arc::new(client)
} else if let Some(target_node) = target_node {
info!("Searching for target_node: {:?}", target_node);
let mut target_client = None;
for node in nodes {
if node.id == *target_node {
target_client = Some(Arc::new(get_client(&[node], &SocketAddrSpace::Unspecified)));
break;
}
}
target_client.unwrap_or_else(|| {
eprintln!("Target node {} not found", target_node);
exit(1);
})
} else {
Arc::new(get_client(&nodes, &SocketAddrSpace::Unspecified))
};
let keypairs = if *read_from_client_file {
let path = Path::new(&client_ids_and_stake_file);
let file = File::open(path).unwrap();
info!("Reading {}", client_ids_and_stake_file);
let accounts: HashMap<String, Base64Account> = serde_yaml::from_reader(file).unwrap();
let mut keypairs = vec![];
let mut last_balance = 0;
accounts
.into_iter()
.for_each(|(keypair, primordial_account)| {
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
keypairs.push(Keypair::from_bytes(&bytes).unwrap());
last_balance = primordial_account.balance;
});
if keypairs.len() < keypair_count {
eprintln!(
"Expected {} accounts in {}, only received {} (--tx_count mismatch?)",
match external_client_type {
ExternalClientType::RpcClient => {
let client = Arc::new(RpcClient::new_with_commitment(
json_rpc_url.to_string(),
CommitmentConfig::confirmed(),
));
let keypairs = get_keypairs(
client.clone(),
id,
keypair_count,
*num_lamports_per_account,
client_ids_and_stake_file,
keypairs.len(),
*read_from_client_file,
);
exit(1);
do_bench_tps(client, cli_config, keypairs);
}
// Sort keypairs so that do_bench_tps() uses the same subset of accounts for each run.
// This prevents the amount of storage needed for bench-tps accounts from creeping up
// across multiple runs.
keypairs.sort_by_key(|x| x.pubkey().to_string());
keypairs
} else {
generate_and_fund_keypairs(
client.clone(),
Some(*faucet_addr),
id,
keypair_count,
*num_lamports_per_account,
)
.unwrap_or_else(|e| {
eprintln!("Error could not fund keys: {:?}", e);
exit(1);
})
};
do_bench_tps(client, cli_config, keypairs);
ExternalClientType::ThinClient => {
let nodes = discover_cluster(entrypoint_addr, *num_nodes, SocketAddrSpace::Unspecified)
.unwrap_or_else(|err| {
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
exit(1);
});
if *use_quic {
connection_cache::set_use_quic(true);
}
let client = if *multi_client {
let (client, num_clients) = get_multi_client(&nodes, &SocketAddrSpace::Unspecified);
if nodes.len() < num_clients {
eprintln!(
"Error: Insufficient nodes discovered. Expecting {} or more",
num_nodes
);
exit(1);
}
Arc::new(client)
} else if let Some(target_node) = target_node {
info!("Searching for target_node: {:?}", target_node);
let mut target_client = None;
for node in nodes {
if node.id == *target_node {
target_client =
Some(Arc::new(get_client(&[node], &SocketAddrSpace::Unspecified)));
break;
}
}
target_client.unwrap_or_else(|| {
eprintln!("Target node {} not found", target_node);
exit(1);
})
} else {
Arc::new(get_client(&nodes, &SocketAddrSpace::Unspecified))
};
let keypairs = get_keypairs(
client.clone(),
id,
keypair_count,
*num_lamports_per_account,
client_ids_and_stake_file,
*read_from_client_file,
);
do_bench_tps(client, cli_config, keypairs);
}
ExternalClientType::TpuClient => {
let rpc_client = Arc::new(RpcClient::new_with_commitment(
json_rpc_url.to_string(),
CommitmentConfig::confirmed(),
));
if *use_quic {
connection_cache::set_use_quic(true);
}
let client = Arc::new(
TpuClient::new(rpc_client, websocket_url, TpuClientConfig::default())
.unwrap_or_else(|err| {
eprintln!("Could not create TpuClient {:?}", err);
exit(1);
}),
);
let keypairs = get_keypairs(
client.clone(),
id,
keypair_count,
*num_lamports_per_account,
client_ids_and_stake_file,
*read_from_client_file,
);
do_bench_tps(client, cli_config, keypairs);
}
}
}

View File

@@ -1,6 +1,7 @@
use {
crate::bench_tps_client::BenchTpsClient,
log::*,
solana_sdk::{client::Client, commitment_config::CommitmentConfig, timing::duration_as_s},
solana_sdk::{commitment_config::CommitmentConfig, timing::duration_as_s},
std::{
sync::{
atomic::{AtomicBool, Ordering},
@@ -27,7 +28,7 @@ pub fn sample_txs<T>(
sample_period: u64,
client: &Arc<T>,
) where
T: Client,
T: BenchTpsClient,
{
let mut max_tps = 0.0;
let mut total_elapsed;
@@ -81,10 +82,7 @@ pub fn sample_txs<T>(
elapsed: total_elapsed,
txs: total_txs,
};
sample_stats
.write()
.unwrap()
.push((client.tpu_addr(), stats));
sample_stats.write().unwrap().push((client.addr(), stats));
return;
}
sleep(Duration::from_secs(sample_period));

View File

@@ -6,16 +6,24 @@ use {
bench::{do_bench_tps, generate_and_fund_keypairs},
cli::Config,
},
solana_client::thin_client::create_client,
solana_client::{
rpc_client::RpcClient,
thin_client::create_client,
tpu_client::{TpuClient, TpuClientConfig},
},
solana_core::validator::ValidatorConfig,
solana_faucet::faucet::run_local_faucet_with_port,
solana_gossip::cluster_info::VALIDATOR_PORT_RANGE,
solana_faucet::faucet::{run_local_faucet, run_local_faucet_with_port},
solana_local_cluster::{
local_cluster::{ClusterConfig, LocalCluster},
validator_configs::make_identical_validator_configs,
},
solana_sdk::signature::{Keypair, Signer},
solana_rpc::rpc::JsonRpcConfig,
solana_sdk::{
commitment_config::CommitmentConfig,
signature::{Keypair, Signer},
},
solana_streamer::socket::SocketAddrSpace,
solana_test_validator::TestValidator,
std::{sync::Arc, time::Duration},
};
@@ -23,13 +31,34 @@ fn test_bench_tps_local_cluster(config: Config) {
let native_instruction_processors = vec![];
solana_logger::setup();
let faucet_keypair = Keypair::new();
let faucet_pubkey = faucet_keypair.pubkey();
let (addr_sender, addr_receiver) = unbounded();
run_local_faucet_with_port(faucet_keypair, addr_sender, None, 0);
let faucet_addr = addr_receiver
.recv_timeout(Duration::from_secs(2))
.expect("run_local_faucet")
.expect("faucet_addr");
const NUM_NODES: usize = 1;
let mut validator_config = ValidatorConfig::default_for_test();
validator_config.rpc_config = JsonRpcConfig {
faucet_addr: Some(faucet_addr),
..JsonRpcConfig::default_for_test()
};
let cluster = LocalCluster::new(
&mut ClusterConfig {
node_stakes: vec![999_990; NUM_NODES],
cluster_lamports: 200_000_000,
validator_configs: make_identical_validator_configs(
&ValidatorConfig::default_for_test(),
&ValidatorConfig {
rpc_config: JsonRpcConfig {
faucet_addr: Some(faucet_addr),
..JsonRpcConfig::default_for_test()
},
..ValidatorConfig::default_for_test()
},
NUM_NODES,
),
native_instruction_processors,
@@ -38,31 +67,55 @@ fn test_bench_tps_local_cluster(config: Config) {
SocketAddrSpace::Unspecified,
);
let faucet_keypair = Keypair::new();
cluster.transfer(
&cluster.funding_keypair,
&faucet_keypair.pubkey(),
100_000_000,
);
cluster.transfer(&cluster.funding_keypair, &faucet_pubkey, 100_000_000);
let client = Arc::new(create_client(
(cluster.entry_point_info.rpc, cluster.entry_point_info.tpu),
VALIDATOR_PORT_RANGE,
));
let (addr_sender, addr_receiver) = unbounded();
run_local_faucet_with_port(faucet_keypair, addr_sender, None, 0);
let faucet_addr = addr_receiver
.recv_timeout(Duration::from_secs(2))
.expect("run_local_faucet")
.expect("faucet_addr");
let client = Arc::new(create_client((
cluster.entry_point_info.rpc,
cluster.entry_point_info.tpu,
)));
let lamports_per_account = 100;
let keypair_count = config.tx_count * config.keypair_multiplier;
let keypairs = generate_and_fund_keypairs(
client.clone(),
&config.id,
keypair_count,
lamports_per_account,
)
.unwrap();
let _total = do_bench_tps(client, config, keypairs);
#[cfg(not(debug_assertions))]
assert!(_total > 100);
}
fn test_bench_tps_test_validator(config: Config) {
solana_logger::setup();
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let faucet_addr = run_local_faucet(mint_keypair, None);
let test_validator =
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
let rpc_client = Arc::new(RpcClient::new_with_commitment(
test_validator.rpc_url(),
CommitmentConfig::processed(),
));
let websocket_url = test_validator.rpc_pubsub_url();
let client =
Arc::new(TpuClient::new(rpc_client, &websocket_url, TpuClientConfig::default()).unwrap());
let lamports_per_account = 100;
let keypair_count = config.tx_count * config.keypair_multiplier;
let keypairs = generate_and_fund_keypairs(
client.clone(),
Some(faucet_addr),
&config.id,
keypair_count,
lamports_per_account,
@@ -84,3 +137,13 @@ fn test_bench_tps_local_cluster_solana() {
..Config::default()
});
}
#[test]
#[serial]
fn test_bench_tps_tpu_client() {
test_bench_tps_test_validator(Config {
tx_count: 100,
duration: Duration::from_secs(10),
..Config::default()
});
}

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-bloom"
version = "1.10.4"
version = "1.10.9"
description = "Solana bloom filter"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -17,9 +17,9 @@ rand = "0.7.0"
rayon = "1.5.1"
serde = { version = "1.0.136", features = ["rc"] }
serde_derive = "1.0.103"
solana-frozen-abi = { path = "../frozen-abi", version = "=1.10.4" }
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-frozen-abi = { path = "../frozen-abi", version = "=1.10.9" }
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
[lib]
crate-type = ["lib"]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-bucket-map"
version = "1.10.4"
version = "1.10.9"
description = "solana-bucket-map"
homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-bucket-map"
@@ -15,14 +15,14 @@ log = { version = "0.4.11" }
memmap2 = "0.5.3"
modular-bitfield = "0.11.2"
rand = "0.7.0"
solana-measure = { path = "../measure", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-measure = { path = "../measure", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
tempfile = "3.3.0"
[dev-dependencies]
fs_extra = "1.2.0"
rayon = "1.5.0"
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.9" }
[lib]
crate-type = ["lib"]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-clap-utils"
version = "1.10.4"
version = "1.10.9"
description = "Solana utilities for the clap"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -13,9 +13,9 @@ edition = "2021"
chrono = "0.4"
clap = "2.33.0"
rpassword = "6.0"
solana-perf = { path = "../perf", version = "=1.10.4" }
solana-remote-wallet = { path = "../remote-wallet", version = "=1.10.4", default-features = false }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-perf = { path = "../perf", version = "=1.10.9" }
solana-remote-wallet = { path = "../remote-wallet", version = "=1.10.9", default-features = false }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
thiserror = "1.0.30"
tiny-bip39 = "0.8.2"
uriparse = "0.6.3"

View File

@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-cli-config"
description = "Blockchain, Rebuilt for Scale"
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -15,6 +15,8 @@ lazy_static = "1.4.0"
serde = "1.0.136"
serde_derive = "1.0.103"
serde_yaml = "0.8.23"
solana-clap-utils = { path = "../clap-utils", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
url = "2.2.2"
[dev-dependencies]

View File

@@ -0,0 +1,126 @@
use {
crate::Config, solana_clap_utils::input_validators::normalize_to_url_if_moniker,
solana_sdk::commitment_config::CommitmentConfig, std::str::FromStr,
};
pub enum SettingType {
Explicit,
Computed,
SystemDefault,
}
pub struct ConfigInput {
pub json_rpc_url: String,
pub websocket_url: String,
pub keypair_path: String,
pub commitment: CommitmentConfig,
}
impl ConfigInput {
fn default_keypair_path() -> String {
Config::default().keypair_path
}
fn default_json_rpc_url() -> String {
Config::default().json_rpc_url
}
fn default_websocket_url() -> String {
Config::default().websocket_url
}
fn default_commitment() -> CommitmentConfig {
CommitmentConfig::confirmed()
}
fn first_nonempty_setting(
settings: std::vec::Vec<(SettingType, String)>,
) -> (SettingType, String) {
settings
.into_iter()
.find(|(_, value)| !value.is_empty())
.expect("no nonempty setting")
}
fn first_setting_is_some<T>(
settings: std::vec::Vec<(SettingType, Option<T>)>,
) -> (SettingType, T) {
let (setting_type, setting_option) = settings
.into_iter()
.find(|(_, value)| value.is_some())
.expect("all settings none");
(setting_type, setting_option.unwrap())
}
pub fn compute_websocket_url_setting(
websocket_cmd_url: &str,
websocket_cfg_url: &str,
json_rpc_cmd_url: &str,
json_rpc_cfg_url: &str,
) -> (SettingType, String) {
Self::first_nonempty_setting(vec![
(SettingType::Explicit, websocket_cmd_url.to_string()),
(SettingType::Explicit, websocket_cfg_url.to_string()),
(
SettingType::Computed,
Config::compute_websocket_url(&normalize_to_url_if_moniker(json_rpc_cmd_url)),
),
(
SettingType::Computed,
Config::compute_websocket_url(&normalize_to_url_if_moniker(json_rpc_cfg_url)),
),
(SettingType::SystemDefault, Self::default_websocket_url()),
])
}
pub fn compute_json_rpc_url_setting(
json_rpc_cmd_url: &str,
json_rpc_cfg_url: &str,
) -> (SettingType, String) {
let (setting_type, url_or_moniker) = Self::first_nonempty_setting(vec![
(SettingType::Explicit, json_rpc_cmd_url.to_string()),
(SettingType::Explicit, json_rpc_cfg_url.to_string()),
(SettingType::SystemDefault, Self::default_json_rpc_url()),
]);
(setting_type, normalize_to_url_if_moniker(&url_or_moniker))
}
pub fn compute_keypair_path_setting(
keypair_cmd_path: &str,
keypair_cfg_path: &str,
) -> (SettingType, String) {
Self::first_nonempty_setting(vec![
(SettingType::Explicit, keypair_cmd_path.to_string()),
(SettingType::Explicit, keypair_cfg_path.to_string()),
(SettingType::SystemDefault, Self::default_keypair_path()),
])
}
pub fn compute_commitment_config(
commitment_cmd: &str,
commitment_cfg: &str,
) -> (SettingType, CommitmentConfig) {
Self::first_setting_is_some(vec![
(
SettingType::Explicit,
CommitmentConfig::from_str(commitment_cmd).ok(),
),
(
SettingType::Explicit,
CommitmentConfig::from_str(commitment_cfg).ok(),
),
(SettingType::SystemDefault, Some(Self::default_commitment())),
])
}
}
impl Default for ConfigInput {
fn default() -> ConfigInput {
ConfigInput {
json_rpc_url: Self::default_json_rpc_url(),
websocket_url: Self::default_websocket_url(),
keypair_path: Self::default_keypair_path(),
commitment: CommitmentConfig::confirmed(),
}
}
}

View File

@@ -55,12 +55,16 @@
extern crate lazy_static;
mod config;
pub use config::{Config, CONFIG_FILE};
mod config_input;
use std::{
fs::{create_dir_all, File},
io::{self, Write},
path::Path,
};
pub use {
config::{Config, CONFIG_FILE},
config_input::{ConfigInput, SettingType},
};
/// Load a value from a file in YAML format.
///

View File

@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-cli-output"
description = "Blockchain, Rebuilt for Scale"
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -17,14 +17,16 @@ clap = "2.33.0"
console = "0.15.0"
humantime = "2.0.1"
indicatif = "0.16.2"
semver = "1.0.6"
serde = "1.0.136"
serde_json = "1.0.79"
solana-account-decoder = { path = "../account-decoder", version = "=1.10.4" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.4" }
solana-client = { path = "../client", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.4" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.4" }
solana-account-decoder = { path = "../account-decoder", version = "=1.10.9" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.9" }
solana-client = { path = "../client", version = "=1.10.9" }
solana-cli-config = { path = "../cli-config", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.9" }
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
[dev-dependencies]

View File

@@ -356,6 +356,7 @@ pub enum CliValidatorsSortOrder {
SkipRate,
Stake,
VoteAccount,
Version,
}
#[derive(Serialize, Deserialize)]
@@ -494,6 +495,22 @@ impl fmt::Display for CliValidators {
CliValidatorsSortOrder::Stake => {
sorted_validators.sort_by_key(|a| a.activated_stake);
}
CliValidatorsSortOrder::Version => {
sorted_validators.sort_by(|a, b| {
use std::cmp::Ordering;
let a_version = semver::Version::parse(a.version.as_str()).ok();
let b_version = semver::Version::parse(b.version.as_str()).ok();
match (a_version, b_version) {
(None, None) => a.version.cmp(&b.version),
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
(Some(va), Some(vb)) => match va.cmp(&vb) {
Ordering::Equal => a.activated_stake.cmp(&b.activated_stake),
ordering => ordering,
},
}
});
}
}
if self.validators_reverse_sort {

View File

@@ -3,6 +3,7 @@ use {
chrono::{DateTime, Local, NaiveDateTime, SecondsFormat, TimeZone, Utc},
console::style,
indicatif::{ProgressBar, ProgressStyle},
solana_cli_config::SettingType,
solana_sdk::{
clock::UnixTimestamp,
hash::Hash,
@@ -103,6 +104,21 @@ pub fn writeln_name_value(f: &mut dyn fmt::Write, name: &str, value: &str) -> fm
writeln!(f, "{} {}", style(name).bold(), styled_value)
}
pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
let description = match setting_type {
SettingType::Explicit => "",
SettingType::Computed => "(computed)",
SettingType::SystemDefault => "(default)",
};
println!(
"{} {} {}",
style(name).bold(),
style(value),
style(description).italic(),
);
}
pub fn format_labeled_address(pubkey: &str, address_labels: &HashMap<String, String>) -> String {
let label = address_labels.get(pubkey);
match label {

View File

@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-cli"
description = "Blockchain, Rebuilt for Scale"
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -27,29 +27,29 @@ semver = "1.0.6"
serde = "1.0.136"
serde_derive = "1.0.103"
serde_json = "1.0.79"
solana-account-decoder = { path = "../account-decoder", version = "=1.10.4" }
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.10.4" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.4" }
solana-cli-config = { path = "../cli-config", version = "=1.10.4" }
solana-cli-output = { path = "../cli-output", version = "=1.10.4" }
solana-client = { path = "../client", version = "=1.10.4" }
solana-config-program = { path = "../programs/config", version = "=1.10.4" }
solana-faucet = { path = "../faucet", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-program-runtime = { path = "../program-runtime", version = "=1.10.4" }
solana-remote-wallet = { path = "../remote-wallet", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.4" }
solana-account-decoder = { path = "../account-decoder", version = "=1.10.9" }
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.10.9" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.9" }
solana-cli-config = { path = "../cli-config", version = "=1.10.9" }
solana-cli-output = { path = "../cli-output", version = "=1.10.9" }
solana-client = { path = "../client", version = "=1.10.9" }
solana-config-program = { path = "../programs/config", version = "=1.10.9" }
solana-faucet = { path = "../faucet", version = "=1.10.9" }
solana-logger = { path = "../logger", version = "=1.10.9" }
solana-program-runtime = { path = "../program-runtime", version = "=1.10.9" }
solana-remote-wallet = { path = "../remote-wallet", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.9" }
solana_rbpf = "=0.2.24"
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
thiserror = "1.0.30"
tiny-bip39 = "0.8.2"
[dev-dependencies]
solana-streamer = { path = "../streamer", version = "=1.10.4" }
solana-test-validator = { path = "../test-validator", version = "=1.10.4" }
solana-streamer = { path = "../streamer", version = "=1.10.9" }
solana-test-validator = { path = "../test-validator", version = "=1.10.9" }
tempfile = "3.3.0"
[[bin]]

View File

@@ -7,7 +7,8 @@ use {
log::*,
num_traits::FromPrimitive,
serde_json::{self, Value},
solana_clap_utils::{self, input_parsers::*, input_validators::*, keypair::*},
solana_clap_utils::{self, input_parsers::*, keypair::*},
solana_cli_config::ConfigInput,
solana_cli_output::{
display::println_name_value, CliSignature, CliValidatorsSortOrder, OutputFormat,
},
@@ -456,129 +457,23 @@ impl From<nonce_utils::Error> for CliError {
}
}
pub enum SettingType {
Explicit,
Computed,
SystemDefault,
}
pub struct CliConfig<'a> {
pub command: CliCommand,
pub json_rpc_url: String,
pub websocket_url: String,
pub signers: Vec<&'a dyn Signer>,
pub keypair_path: String,
pub commitment: CommitmentConfig,
pub signers: Vec<&'a dyn Signer>,
pub rpc_client: Option<Arc<RpcClient>>,
pub rpc_timeout: Duration,
pub verbose: bool,
pub output_format: OutputFormat,
pub commitment: CommitmentConfig,
pub send_transaction_config: RpcSendTransactionConfig,
pub confirm_transaction_initial_timeout: Duration,
pub address_labels: HashMap<String, String>,
}
impl CliConfig<'_> {
fn default_keypair_path() -> String {
solana_cli_config::Config::default().keypair_path
}
fn default_json_rpc_url() -> String {
solana_cli_config::Config::default().json_rpc_url
}
fn default_websocket_url() -> String {
solana_cli_config::Config::default().websocket_url
}
fn default_commitment() -> CommitmentConfig {
CommitmentConfig::confirmed()
}
fn first_nonempty_setting(
settings: std::vec::Vec<(SettingType, String)>,
) -> (SettingType, String) {
settings
.into_iter()
.find(|(_, value)| !value.is_empty())
.expect("no nonempty setting")
}
fn first_setting_is_some<T>(
settings: std::vec::Vec<(SettingType, Option<T>)>,
) -> (SettingType, T) {
let (setting_type, setting_option) = settings
.into_iter()
.find(|(_, value)| value.is_some())
.expect("all settings none");
(setting_type, setting_option.unwrap())
}
pub fn compute_websocket_url_setting(
websocket_cmd_url: &str,
websocket_cfg_url: &str,
json_rpc_cmd_url: &str,
json_rpc_cfg_url: &str,
) -> (SettingType, String) {
Self::first_nonempty_setting(vec![
(SettingType::Explicit, websocket_cmd_url.to_string()),
(SettingType::Explicit, websocket_cfg_url.to_string()),
(
SettingType::Computed,
solana_cli_config::Config::compute_websocket_url(&normalize_to_url_if_moniker(
json_rpc_cmd_url,
)),
),
(
SettingType::Computed,
solana_cli_config::Config::compute_websocket_url(&normalize_to_url_if_moniker(
json_rpc_cfg_url,
)),
),
(SettingType::SystemDefault, Self::default_websocket_url()),
])
}
pub fn compute_json_rpc_url_setting(
json_rpc_cmd_url: &str,
json_rpc_cfg_url: &str,
) -> (SettingType, String) {
let (setting_type, url_or_moniker) = Self::first_nonempty_setting(vec![
(SettingType::Explicit, json_rpc_cmd_url.to_string()),
(SettingType::Explicit, json_rpc_cfg_url.to_string()),
(SettingType::SystemDefault, Self::default_json_rpc_url()),
]);
(setting_type, normalize_to_url_if_moniker(&url_or_moniker))
}
pub fn compute_keypair_path_setting(
keypair_cmd_path: &str,
keypair_cfg_path: &str,
) -> (SettingType, String) {
Self::first_nonempty_setting(vec![
(SettingType::Explicit, keypair_cmd_path.to_string()),
(SettingType::Explicit, keypair_cfg_path.to_string()),
(SettingType::SystemDefault, Self::default_keypair_path()),
])
}
pub fn compute_commitment_config(
commitment_cmd: &str,
commitment_cfg: &str,
) -> (SettingType, CommitmentConfig) {
Self::first_setting_is_some(vec![
(
SettingType::Explicit,
CommitmentConfig::from_str(commitment_cmd).ok(),
),
(
SettingType::Explicit,
CommitmentConfig::from_str(commitment_cfg).ok(),
),
(SettingType::SystemDefault, Some(Self::default_commitment())),
])
}
pub(crate) fn pubkey(&self) -> Result<Pubkey, SignerError> {
if !self.signers.is_empty() {
self.signers[0].try_pubkey()
@@ -609,15 +504,15 @@ impl Default for CliConfig<'_> {
pubkey: Some(Pubkey::default()),
use_lamports_unit: false,
},
json_rpc_url: Self::default_json_rpc_url(),
websocket_url: Self::default_websocket_url(),
json_rpc_url: ConfigInput::default().json_rpc_url,
websocket_url: ConfigInput::default().websocket_url,
keypair_path: ConfigInput::default().keypair_path,
commitment: ConfigInput::default().commitment,
signers: Vec::new(),
keypair_path: Self::default_keypair_path(),
rpc_client: None,
rpc_timeout: Duration::from_secs(u64::from_str(DEFAULT_RPC_TIMEOUT_SECONDS).unwrap()),
verbose: false,
output_format: OutputFormat::Display,
commitment: CommitmentConfig::confirmed(),
send_transaction_config: RpcSendTransactionConfig::default(),
confirm_transaction_initial_timeout: Duration::from_secs(
u64::from_str(DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS).unwrap(),

View File

@@ -379,6 +379,7 @@ impl ClusterQuerySubCommands for App<'_, '_> {
"root",
"skip-rate",
"stake",
"version",
"vote-account",
])
.default_value("stake")
@@ -650,6 +651,7 @@ pub fn parse_show_validators(matches: &ArgMatches<'_>) -> Result<CliCommandInfo,
"skip-rate" => CliValidatorsSortOrder::SkipRate,
"stake" => CliValidatorsSortOrder::Stake,
"vote-account" => CliValidatorsSortOrder::VoteAccount,
"version" => CliValidatorsSortOrder::Version,
_ => unreachable!(),
};

View File

@@ -8,30 +8,18 @@ use {
},
solana_cli::{
clap_app::get_clap_app,
cli::{parse_command, process_command, CliCommandInfo, CliConfig, SettingType},
cli::{parse_command, process_command, CliCommandInfo, CliConfig},
},
solana_cli_config::{Config, ConfigInput},
solana_cli_output::{
display::{println_name_value, println_name_value_or},
OutputFormat,
},
solana_cli_config::Config,
solana_cli_output::{display::println_name_value, OutputFormat},
solana_client::rpc_config::RpcSendTransactionConfig,
solana_remote_wallet::remote_wallet::RemoteWalletManager,
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)) => {
@@ -50,17 +38,18 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
match matches.subcommand() {
("get", Some(subcommand_matches)) => {
let (url_setting_type, json_rpc_url) =
CliConfig::compute_json_rpc_url_setting("", &config.json_rpc_url);
let (ws_setting_type, websocket_url) = CliConfig::compute_websocket_url_setting(
"",
&config.websocket_url,
"",
&config.json_rpc_url,
);
ConfigInput::compute_json_rpc_url_setting("", &config.json_rpc_url);
let (ws_setting_type, websocket_url) =
ConfigInput::compute_websocket_url_setting(
"",
&config.websocket_url,
"",
&config.json_rpc_url,
);
let (keypair_setting_type, keypair_path) =
CliConfig::compute_keypair_path_setting("", &config.keypair_path);
ConfigInput::compute_keypair_path_setting("", &config.keypair_path);
let (commitment_setting_type, commitment) =
CliConfig::compute_commitment_config("", &config.commitment);
ConfigInput::compute_commitment_config("", &config.commitment);
if let Some(field) = subcommand_matches.value_of("specific_setting") {
let (field_name, value, setting_type) = match field {
@@ -107,17 +96,18 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
config.save(config_file)?;
let (url_setting_type, json_rpc_url) =
CliConfig::compute_json_rpc_url_setting("", &config.json_rpc_url);
let (ws_setting_type, websocket_url) = CliConfig::compute_websocket_url_setting(
"",
&config.websocket_url,
"",
&config.json_rpc_url,
);
ConfigInput::compute_json_rpc_url_setting("", &config.json_rpc_url);
let (ws_setting_type, websocket_url) =
ConfigInput::compute_websocket_url_setting(
"",
&config.websocket_url,
"",
&config.json_rpc_url,
);
let (keypair_setting_type, keypair_path) =
CliConfig::compute_keypair_path_setting("", &config.keypair_path);
ConfigInput::compute_keypair_path_setting("", &config.keypair_path);
let (commitment_setting_type, commitment) =
CliConfig::compute_commitment_config("", &config.commitment);
ConfigInput::compute_commitment_config("", &config.commitment);
println_name_value("Config File:", config_file);
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
@@ -158,7 +148,7 @@ pub fn parse_args<'a>(
} else {
Config::default()
};
let (_, json_rpc_url) = CliConfig::compute_json_rpc_url_setting(
let (_, json_rpc_url) = ConfigInput::compute_json_rpc_url_setting(
matches.value_of("json_rpc_url").unwrap_or(""),
&config.json_rpc_url,
);
@@ -171,14 +161,14 @@ pub fn parse_args<'a>(
let confirm_transaction_initial_timeout =
Duration::from_secs(confirm_transaction_initial_timeout);
let (_, websocket_url) = CliConfig::compute_websocket_url_setting(
let (_, websocket_url) = ConfigInput::compute_websocket_url_setting(
matches.value_of("websocket_url").unwrap_or(""),
&config.websocket_url,
matches.value_of("json_rpc_url").unwrap_or(""),
&config.json_rpc_url,
);
let default_signer_arg_name = "keypair".to_string();
let (_, default_signer_path) = CliConfig::compute_keypair_path_setting(
let (_, default_signer_path) = ConfigInput::compute_keypair_path_setting(
matches.value_of(&default_signer_arg_name).unwrap_or(""),
&config.keypair_path,
);
@@ -201,7 +191,7 @@ pub fn parse_args<'a>(
let verbose = matches.is_present("verbose");
let output_format = OutputFormat::from_matches(matches, "output_format", verbose);
let (_, commitment) = CliConfig::compute_commitment_config(
let (_, commitment) = ConfigInput::compute_commitment_config(
matches.value_of("commitment").unwrap_or(""),
&config.commitment,
);

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-client-test"
version = "1.10.4"
version = "1.10.9"
description = "Solana RPC Test"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -14,25 +14,25 @@ publish = false
futures-util = "0.3.21"
serde_json = "1.0.79"
serial_test = "0.6.0"
solana-client = { path = "../client", version = "=1.10.4" }
solana-ledger = { path = "../ledger", version = "=1.10.4" }
solana-measure = { path = "../measure", version = "=1.10.4" }
solana-merkle-tree = { path = "../merkle-tree", version = "=1.10.4" }
solana-metrics = { path = "../metrics", version = "=1.10.4" }
solana-perf = { path = "../perf", version = "=1.10.4" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.10.4" }
solana-rpc = { path = "../rpc", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-streamer = { path = "../streamer", version = "=1.10.4" }
solana-test-validator = { path = "../test-validator", version = "=1.10.4" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-client = { path = "../client", version = "=1.10.9" }
solana-ledger = { path = "../ledger", version = "=1.10.9" }
solana-measure = { path = "../measure", version = "=1.10.9" }
solana-merkle-tree = { path = "../merkle-tree", version = "=1.10.9" }
solana-metrics = { path = "../metrics", version = "=1.10.9" }
solana-perf = { path = "../perf", version = "=1.10.9" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.10.9" }
solana-rpc = { path = "../rpc", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-streamer = { path = "../streamer", version = "=1.10.9" }
solana-test-validator = { path = "../test-validator", version = "=1.10.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
systemstat = "0.1.10"
tokio = { version = "1", features = ["full"] }
[dev-dependencies]
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.9" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-client"
version = "1.10.4"
version = "1.10.9"
description = "Solana Client"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -25,7 +25,9 @@ itertools = "0.10.2"
jsonrpc-core = "18.0.0"
lazy_static = "1.4.0"
log = "0.4.14"
lru = "0.7.5"
quinn = "0.8.0"
quinn-proto = "0.8.0"
rand = "0.7.0"
rand_chacha = "0.2.2"
rayon = "1.5.1"
@@ -35,16 +37,17 @@ semver = "1.0.6"
serde = "1.0.136"
serde_derive = "1.0.103"
serde_json = "1.0.79"
solana-account-decoder = { path = "../account-decoder", version = "=1.10.4" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.4" }
solana-faucet = { path = "../faucet", version = "=1.10.4" }
solana-measure = { path = "../measure", version = "=1.10.4" }
solana-net-utils = { path = "../net-utils", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-streamer = { path = "../streamer", version = "=1.10.4" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.4" }
solana-account-decoder = { path = "../account-decoder", version = "=1.10.9" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.9" }
solana-faucet = { path = "../faucet", version = "=1.10.9" }
solana-measure = { path = "../measure", version = "=1.10.9" }
solana-metrics = { path = "../metrics", version = "=1.10.9" }
solana-net-utils = { path = "../net-utils", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-streamer = { path = "../streamer", version = "=1.10.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.9" }
thiserror = "1.0"
tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1.8"
@@ -55,7 +58,7 @@ url = "2.2.2"
[dev-dependencies]
assert_matches = "1.5.0"
jsonrpc-http-server = "18.0.0"
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.9" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@@ -1,13 +1,21 @@
use {
crate::{
quic_client::QuicTpuConnection, tpu_connection::TpuConnection, udp_client::UdpTpuConnection,
quic_client::QuicTpuConnection,
tpu_connection::{ClientStats, TpuConnection},
udp_client::UdpTpuConnection,
},
lazy_static::lazy_static,
solana_sdk::{transaction::VersionedTransaction, transport::TransportError},
lru::LruCache,
solana_net_utils::VALIDATOR_PORT_RANGE,
solana_sdk::{
timing::AtomicInterval, transaction::VersionedTransaction, transport::TransportError,
},
std::{
collections::{hash_map::Entry, BTreeMap, HashMap},
net::{SocketAddr, UdpSocket},
sync::{Arc, Mutex},
net::{IpAddr, Ipv4Addr, SocketAddr},
sync::{
atomic::{AtomicU64, Ordering},
Arc, Mutex,
},
},
};
@@ -20,27 +28,108 @@ enum Connection {
Quic(Arc<QuicTpuConnection>),
}
#[derive(Default)]
struct ConnectionCacheStats {
cache_hits: AtomicU64,
cache_misses: AtomicU64,
sent_packets: AtomicU64,
total_batches: AtomicU64,
batch_success: AtomicU64,
batch_failure: AtomicU64,
// Need to track these separately per-connection
// because we need to track the base stat value from quinn
total_client_stats: ClientStats,
}
const CONNECTION_STAT_SUBMISSION_INTERVAL: u64 = 2000;
impl ConnectionCacheStats {
fn add_client_stats(&self, client_stats: &ClientStats, num_packets: usize, is_success: bool) {
self.total_client_stats.total_connections.fetch_add(
client_stats.total_connections.load(Ordering::Relaxed),
Ordering::Relaxed,
);
self.total_client_stats.connection_reuse.fetch_add(
client_stats.connection_reuse.load(Ordering::Relaxed),
Ordering::Relaxed,
);
self.sent_packets
.fetch_add(num_packets as u64, Ordering::Relaxed);
self.total_batches.fetch_add(1, Ordering::Relaxed);
if is_success {
self.batch_success.fetch_add(1, Ordering::Relaxed);
} else {
self.batch_failure.fetch_add(1, Ordering::Relaxed);
}
}
fn report(&self) {
datapoint_info!(
"quic-client-connection-stats",
(
"cache_hits",
self.cache_hits.swap(0, Ordering::Relaxed),
i64
),
(
"cache_misses",
self.cache_misses.swap(0, Ordering::Relaxed),
i64
),
(
"total_connections",
self.total_client_stats
.total_connections
.swap(0, Ordering::Relaxed),
i64
),
(
"connection_reuse",
self.total_client_stats
.connection_reuse
.swap(0, Ordering::Relaxed),
i64
),
(
"congestion_events",
self.total_client_stats.congestion_events.load_and_reset(),
i64
),
(
"tx_streams_blocked_uni",
self.total_client_stats
.tx_streams_blocked_uni
.load_and_reset(),
i64
),
(
"tx_data_blocked",
self.total_client_stats.tx_data_blocked.load_and_reset(),
i64
),
(
"tx_acks",
self.total_client_stats.tx_acks.load_and_reset(),
i64
),
);
}
}
struct ConnMap {
// Keeps track of the connection associated with an addr and the last time it was used
map: HashMap<SocketAddr, (Connection, u64)>,
// Helps to find the least recently used connection. The search and inserts are O(log(n))
// but since we're bounding the size of the collections, this should be constant
// (and hopefully negligible) time. In theory, we can do this in constant time
// with a queue implemented as a doubly-linked list (and all the
// HashMap entries holding a "pointer" to the corresponding linked-list node),
// so we can push, pop and bump a used connection back to the end of the queue in O(1) time, but
// that seems non-"Rust-y" and low bang/buck. This is still pretty terrible though...
last_used_times: BTreeMap<u64, SocketAddr>,
ticks: u64,
map: LruCache<SocketAddr, Connection>,
stats: Arc<ConnectionCacheStats>,
last_stats: AtomicInterval,
use_quic: bool,
}
impl ConnMap {
pub fn new() -> Self {
Self {
map: HashMap::new(),
last_used_times: BTreeMap::new(),
ticks: 0,
map: LruCache::new(MAX_CONNECTIONS),
stats: Arc::new(ConnectionCacheStats::default()),
last_stats: AtomicInterval::default(),
use_quic: false,
}
}
@@ -59,53 +148,75 @@ pub fn set_use_quic(use_quic: bool) {
map.set_use_quic(use_quic);
}
#[allow(dead_code)]
// TODO: see https://github.com/solana-labs/solana/issues/23661
// remove lazy_static and optimize and refactor this
fn get_connection(addr: &SocketAddr) -> Connection {
fn get_connection(addr: &SocketAddr) -> (Connection, Arc<ConnectionCacheStats>) {
let mut map = (*CONNECTION_MAP).lock().unwrap();
let ticks = map.ticks;
let use_quic = map.use_quic;
let (conn, target_ticks) = match map.map.entry(*addr) {
Entry::Occupied(mut entry) => {
let mut pair = entry.get_mut();
let old_ticks = pair.1;
pair.1 = ticks;
(pair.0.clone(), old_ticks)
}
Entry::Vacant(entry) => {
let send_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
// TODO: see https://github.com/solana-labs/solana/issues/23659
// make it configurable (e.g. via the command line) whether to use UDP or Quic
let conn = if use_quic {
if map
.last_stats
.should_update(CONNECTION_STAT_SUBMISSION_INTERVAL)
{
map.stats.report();
}
let (connection, hit, maybe_stats) = match map.map.get(addr) {
Some(connection) => {
let mut stats = None;
// update connection stats
if let Connection::Quic(conn) = connection {
stats = conn.stats().map(|s| (conn.base_stats(), s));
}
(connection.clone(), true, stats)
}
None => {
let (_, send_socket) = solana_net_utils::bind_in_range(
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
VALIDATOR_PORT_RANGE,
)
.unwrap();
let connection = if map.use_quic {
Connection::Quic(Arc::new(QuicTpuConnection::new(send_socket, *addr)))
} else {
Connection::Udp(Arc::new(UdpTpuConnection::new(send_socket, *addr)))
};
entry.insert((conn.clone(), ticks));
(conn, ticks)
map.map.put(*addr, connection.clone());
(connection, false, None)
}
};
let num_connections = map.map.len();
if num_connections > MAX_CONNECTIONS {
let (old_ticks, target_addr) = {
let (old_ticks, target_addr) = map.last_used_times.iter().next().unwrap();
(*old_ticks, *target_addr)
};
map.map.remove(&target_addr);
map.last_used_times.remove(&old_ticks);
if let Some((connection_stats, new_stats)) = maybe_stats {
map.stats.total_client_stats.congestion_events.update_stat(
&connection_stats.congestion_events,
new_stats.path.congestion_events,
);
map.stats
.total_client_stats
.tx_streams_blocked_uni
.update_stat(
&connection_stats.tx_streams_blocked_uni,
new_stats.frame_tx.streams_blocked_uni,
);
map.stats.total_client_stats.tx_data_blocked.update_stat(
&connection_stats.tx_data_blocked,
new_stats.frame_tx.data_blocked,
);
map.stats
.total_client_stats
.tx_acks
.update_stat(&connection_stats.tx_acks, new_stats.frame_tx.acks);
}
if target_ticks != ticks {
map.last_used_times.remove(&target_ticks);
if hit {
map.stats.cache_hits.fetch_add(1, Ordering::Relaxed);
} else {
map.stats.cache_misses.fetch_add(1, Ordering::Relaxed);
}
map.last_used_times.insert(ticks, *addr);
map.ticks += 1;
conn
(connection, map.stats.clone())
}
// TODO: see https://github.com/solana-labs/solana/issues/23851
@@ -121,44 +232,67 @@ pub fn send_wire_transaction_batch(
packets: &[&[u8]],
addr: &SocketAddr,
) -> Result<(), TransportError> {
let conn = get_connection(addr);
match conn {
Connection::Udp(conn) => conn.send_wire_transaction_batch(packets),
Connection::Quic(conn) => conn.send_wire_transaction_batch(packets),
}
let (conn, stats) = get_connection(addr);
let client_stats = ClientStats::default();
let r = match conn {
Connection::Udp(conn) => conn.send_wire_transaction_batch(packets, &client_stats),
Connection::Quic(conn) => conn.send_wire_transaction_batch(packets, &client_stats),
};
stats.add_client_stats(&client_stats, packets.len(), r.is_ok());
r
}
pub fn send_wire_transaction_async(
packets: Vec<u8>,
addr: &SocketAddr,
) -> Result<(), TransportError> {
let (conn, stats) = get_connection(addr);
let client_stats = Arc::new(ClientStats::default());
let r = match conn {
Connection::Udp(conn) => conn.send_wire_transaction_async(packets, client_stats.clone()),
Connection::Quic(conn) => conn.send_wire_transaction_async(packets, client_stats.clone()),
};
stats.add_client_stats(&client_stats, 1, r.is_ok());
r
}
pub fn send_wire_transaction(
wire_transaction: &[u8],
addr: &SocketAddr,
) -> Result<(), TransportError> {
let conn = get_connection(addr);
match conn {
Connection::Udp(conn) => conn.send_wire_transaction(wire_transaction),
Connection::Quic(conn) => conn.send_wire_transaction(wire_transaction),
}
send_wire_transaction_batch(&[wire_transaction], addr)
}
pub fn serialize_and_send_transaction(
transaction: &VersionedTransaction,
addr: &SocketAddr,
) -> Result<(), TransportError> {
let conn = get_connection(addr);
match conn {
Connection::Udp(conn) => conn.serialize_and_send_transaction(transaction),
Connection::Quic(conn) => conn.serialize_and_send_transaction(transaction),
}
let (conn, stats) = get_connection(addr);
let client_stats = ClientStats::default();
let r = match conn {
Connection::Udp(conn) => conn.serialize_and_send_transaction(transaction, &client_stats),
Connection::Quic(conn) => conn.serialize_and_send_transaction(transaction, &client_stats),
};
stats.add_client_stats(&client_stats, 1, r.is_ok());
r
}
pub fn par_serialize_and_send_transaction_batch(
transactions: &[VersionedTransaction],
addr: &SocketAddr,
) -> Result<(), TransportError> {
let conn = get_connection(addr);
match conn {
Connection::Udp(conn) => conn.par_serialize_and_send_transaction_batch(transactions),
Connection::Quic(conn) => conn.par_serialize_and_send_transaction_batch(transactions),
}
let (conn, stats) = get_connection(addr);
let client_stats = ClientStats::default();
let r = match conn {
Connection::Udp(conn) => {
conn.par_serialize_and_send_transaction_batch(transactions, &client_stats)
}
Connection::Quic(conn) => {
conn.par_serialize_and_send_transaction_batch(transactions, &client_stats)
}
};
stats.add_client_stats(&client_stats, transactions.len(), r.is_ok());
r
}
#[cfg(test)]
@@ -206,7 +340,7 @@ mod tests {
// be lazy and not connect until first use or handle connection errors somehow
// (without crashing, as would be required in a real practical validator)
let first_addr = get_addr(&mut rng);
assert!(ip(get_connection(&first_addr)) == first_addr.ip());
assert!(ip(get_connection(&first_addr).0) == first_addr.ip());
let addrs = (0..MAX_CONNECTIONS)
.into_iter()
.map(|_| {
@@ -218,11 +352,11 @@ mod tests {
{
let map = (*CONNECTION_MAP).lock().unwrap();
addrs.iter().for_each(|a| {
let conn = map.map.get(a).expect("Address not found");
assert!(a.ip() == ip(conn.0.clone()));
let conn = map.map.peek(a).expect("Address not found");
assert!(a.ip() == ip(conn.clone()));
});
assert!(map.map.get(&first_addr).is_none());
assert!(map.map.peek(&first_addr).is_none());
}
// Test that get_connection updates which connection is next up for eviction
@@ -235,7 +369,7 @@ mod tests {
get_connection(&get_addr(&mut rng));
let map = (*CONNECTION_MAP).lock().unwrap();
assert!(map.map.get(&addrs[0]).is_some());
assert!(map.map.get(&addrs[1]).is_none());
assert!(map.map.peek(&addrs[0]).is_some());
assert!(map.map.peek(&addrs[1]).is_none());
}
}

View File

@@ -197,6 +197,10 @@ impl RpcSender for HttpSender {
return Ok(json["result"].take());
}
}
fn url(&self) -> String {
self.url.clone()
}
}
#[cfg(test)]

View File

@@ -9,7 +9,6 @@ pub(crate) mod http_sender;
pub(crate) mod mock_sender;
pub mod nonblocking;
pub mod nonce_utils;
pub mod perf_utils;
pub mod pubsub_client;
pub mod quic_client;
pub mod rpc_cache;
@@ -28,6 +27,9 @@ pub mod tpu_connection;
pub mod transaction_executor;
pub mod udp_client;
#[macro_use]
extern crate solana_metrics;
pub mod mock_sender_for_cli {
/// Magic `SIGNATURE` value used by `solana-cli` unit tests.
/// Please don't use this constant.

View File

@@ -468,4 +468,8 @@ impl RpcSender for MockSender {
};
Ok(val)
}
fn url(&self) -> String {
format!("MockSender: {}", self.url)
}
}

View File

@@ -502,6 +502,11 @@ impl RpcClient {
Self::new_with_timeout(url, timeout)
}
/// Get the configured url of the client's sender
pub fn url(&self) -> String {
self.sender.url()
}
async fn get_node_version(&self) -> Result<semver::Version, RpcError> {
let r_node_version = self.node_version.read().await;
if let Some(version) = &*r_node_version {
@@ -3727,6 +3732,61 @@ impl RpcClient {
commitment: Some(self.maybe_map_commitment(commitment_config).await?),
data_slice: None,
};
self.get_account_with_config(pubkey, config).await
}
/// Returns all information associated with the account of the provided pubkey.
///
/// If the account does not exist, this method returns `Ok(None)`.
///
/// To get multiple accounts at once, use the [`get_multiple_accounts_with_config`] method.
///
/// [`get_multiple_accounts_with_config`]: RpcClient::get_multiple_accounts_with_config
///
/// # RPC Reference
///
/// This method is built on the [`getAccountInfo`] RPC method.
///
/// [`getAccountInfo`]: https://docs.solana.com/developing/clients/jsonrpc-api#getaccountinfo
///
/// # Examples
///
/// ```
/// # use solana_client::{
/// # rpc_client::{self, RpcClient},
/// # rpc_config::RpcAccountInfoConfig,
/// # client_error::ClientError,
/// # };
/// # use solana_sdk::{
/// # signature::Signer,
/// # signer::keypair::Keypair,
/// # pubkey::Pubkey,
/// # commitment_config::CommitmentConfig,
/// # };
/// # use solana_account_decoder::UiAccountEncoding;
/// # use std::str::FromStr;
/// # let mocks = rpc_client::create_rpc_client_mocks();
/// # let rpc_client = RpcClient::new_mock_with_mocks("succeeds".to_string(), mocks);
/// let alice_pubkey = Pubkey::from_str("BgvYtJEfmZYdVKiptmMjxGzv8iQoo4MWjsP3QsTkhhxa").unwrap();
/// let commitment_config = CommitmentConfig::processed();
/// let config = RpcAccountInfoConfig {
/// encoding: Some(UiAccountEncoding::Base64),
/// commitment: Some(commitment_config),
/// .. RpcAccountInfoConfig::default()
/// };
/// let account = rpc_client.get_account_with_config(
/// &alice_pubkey,
/// config,
/// )?;
/// assert!(account.value.is_some());
/// # Ok::<(), ClientError>(())
/// ```
pub async fn get_account_with_config(
&self,
pubkey: &Pubkey,
config: RpcAccountInfoConfig,
) -> RpcResult<Option<Account>> {
let response = self
.send(
RpcRequest::GetAccountInfo,

View File

@@ -2,18 +2,24 @@
//! an interface for sending transactions which is restricted by the server's flow control.
use {
crate::{client_error::ClientErrorKind, tpu_connection::TpuConnection},
crate::{
client_error::ClientErrorKind,
tpu_connection::{ClientStats, TpuConnection},
},
async_mutex::Mutex,
futures::future::join_all,
itertools::Itertools,
lazy_static::lazy_static,
log::*,
quinn::{ClientConfig, Endpoint, EndpointConfig, NewConnection, WriteError},
quinn_proto::ConnectionStats,
solana_sdk::{
quic::{QUIC_MAX_CONCURRENT_STREAMS, QUIC_PORT_OFFSET},
transport::Result as TransportResult,
},
std::{
net::{SocketAddr, UdpSocket},
sync::Arc,
sync::{atomic::Ordering, Arc},
},
tokio::runtime::Runtime,
};
@@ -39,18 +45,34 @@ impl rustls::client::ServerCertVerifier for SkipServerVerification {
Ok(rustls::client::ServerCertVerified::assertion())
}
}
lazy_static! {
static ref RUNTIME: Runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
}
struct QuicClient {
runtime: Runtime,
endpoint: Endpoint,
connection: Arc<Mutex<Option<Arc<NewConnection>>>>,
addr: SocketAddr,
stats: Arc<ClientStats>,
}
pub struct QuicTpuConnection {
client: Arc<QuicClient>,
}
impl QuicTpuConnection {
pub fn stats(&self) -> Option<ConnectionStats> {
self.client.stats()
}
pub fn base_stats(&self) -> Arc<ClientStats> {
self.client.stats.clone()
}
}
impl TpuConnection for QuicTpuConnection {
fn new(client_socket: UdpSocket, tpu_addr: SocketAddr) -> Self {
let tpu_addr = SocketAddr::new(tpu_addr.ip(), tpu_addr.port() + QUIC_PORT_OFFSET);
@@ -63,35 +85,59 @@ impl TpuConnection for QuicTpuConnection {
&self.client.addr
}
fn send_wire_transaction<T>(&self, wire_transaction: T) -> TransportResult<()>
fn send_wire_transaction<T>(
&self,
wire_transaction: T,
stats: &ClientStats,
) -> TransportResult<()>
where
T: AsRef<[u8]>,
{
let _guard = self.client.runtime.enter();
let send_buffer = self.client.send_buffer(wire_transaction);
self.client.runtime.block_on(send_buffer)?;
let _guard = RUNTIME.enter();
let send_buffer = self.client.send_buffer(wire_transaction, stats);
RUNTIME.block_on(send_buffer)?;
Ok(())
}
fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
fn send_wire_transaction_batch<T>(
&self,
buffers: &[T],
stats: &ClientStats,
) -> TransportResult<()>
where
T: AsRef<[u8]>,
{
let _guard = self.client.runtime.enter();
let send_batch = self.client.send_batch(buffers);
self.client.runtime.block_on(send_batch)?;
let _guard = RUNTIME.enter();
let send_batch = self.client.send_batch(buffers, stats);
RUNTIME.block_on(send_batch)?;
Ok(())
}
fn send_wire_transaction_async(
&self,
wire_transaction: Vec<u8>,
stats: Arc<ClientStats>,
) -> TransportResult<()> {
let _guard = RUNTIME.enter();
//drop and detach the task
let client = self.client.clone();
inc_new_counter_info!("send_wire_transaction_async", 1);
let _ = RUNTIME.spawn(async move {
let send_buffer = client.send_buffer(wire_transaction, &stats);
if let Err(e) = send_buffer.await {
inc_new_counter_warn!("send_wire_transaction_async_fail", 1);
warn!("Failed to send transaction async to {:?}", e);
} else {
inc_new_counter_info!("send_wire_transaction_async_pass", 1);
}
});
Ok(())
}
}
impl QuicClient {
pub fn new(client_socket: UdpSocket, addr: SocketAddr) -> Self {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let _guard = runtime.enter();
let _guard = RUNTIME.enter();
let crypto = rustls::ClientConfig::builder()
.with_safe_defaults()
@@ -100,18 +146,24 @@ impl QuicClient {
let create_endpoint = QuicClient::create_endpoint(EndpointConfig::default(), client_socket);
let mut endpoint = runtime.block_on(create_endpoint);
let mut endpoint = RUNTIME.block_on(create_endpoint);
endpoint.set_default_client_config(ClientConfig::new(Arc::new(crypto)));
Self {
runtime,
endpoint,
connection: Arc::new(Mutex::new(None)),
addr,
stats: Arc::new(ClientStats::default()),
}
}
pub fn stats(&self) -> Option<ConnectionStats> {
let conn_guard = self.connection.lock();
let x = RUNTIME.block_on(conn_guard);
x.as_ref().map(|c| c.connection.stats())
}
// If this function becomes public, it should be changed to
// not expose details of the specific Quic implementation we're using
async fn create_endpoint(config: EndpointConfig, client_socket: UdpSocket) -> Endpoint {
@@ -128,18 +180,35 @@ impl QuicClient {
Ok(())
}
async fn make_connection(&self, stats: &ClientStats) -> Result<Arc<NewConnection>, WriteError> {
let connecting = self.endpoint.connect(self.addr, "connect").unwrap();
stats.total_connections.fetch_add(1, Ordering::Relaxed);
let connecting_result = connecting.await;
if connecting_result.is_err() {
stats.connection_errors.fetch_add(1, Ordering::Relaxed);
}
let connection = connecting_result?;
Ok(Arc::new(connection))
}
// Attempts to send data, connecting/reconnecting as necessary
// On success, returns the connection used to successfully send the data
async fn _send_buffer(&self, data: &[u8]) -> Result<Arc<NewConnection>, WriteError> {
async fn _send_buffer(
&self,
data: &[u8],
stats: &ClientStats,
) -> Result<Arc<NewConnection>, WriteError> {
let connection = {
let mut conn_guard = self.connection.lock().await;
let maybe_conn = (*conn_guard).clone();
match maybe_conn {
Some(conn) => conn.clone(),
Some(conn) => {
stats.connection_reuse.fetch_add(1, Ordering::Relaxed);
conn.clone()
}
None => {
let connecting = self.endpoint.connect(self.addr, "connect").unwrap();
let connection = Arc::new(connecting.await?);
let connection = self.make_connection(stats).await?;
*conn_guard = Some(connection.clone());
connection
}
@@ -149,8 +218,7 @@ impl QuicClient {
Ok(()) => Ok(connection),
_ => {
let connection = {
let connecting = self.endpoint.connect(self.addr, "connect").unwrap();
let connection = Arc::new(connecting.await?);
let connection = self.make_connection(stats).await?;
let mut conn_guard = self.connection.lock().await;
*conn_guard = Some(connection.clone());
connection
@@ -161,15 +229,19 @@ impl QuicClient {
}
}
pub async fn send_buffer<T>(&self, data: T) -> Result<(), ClientErrorKind>
pub async fn send_buffer<T>(&self, data: T, stats: &ClientStats) -> Result<(), ClientErrorKind>
where
T: AsRef<[u8]>,
{
self._send_buffer(data.as_ref()).await?;
self._send_buffer(data.as_ref(), stats).await?;
Ok(())
}
pub async fn send_batch<T>(&self, buffers: &[T]) -> Result<(), ClientErrorKind>
pub async fn send_batch<T>(
&self,
buffers: &[T],
stats: &ClientStats,
) -> Result<(), ClientErrorKind>
where
T: AsRef<[u8]>,
{
@@ -187,7 +259,7 @@ impl QuicClient {
if buffers.is_empty() {
return Ok(());
}
let connection = self._send_buffer(buffers[0].as_ref()).await?;
let connection = self._send_buffer(buffers[0].as_ref(), stats).await?;
// Used to avoid dereferencing the Arc multiple times below
// by just getting a reference to the NewConnection once

View File

@@ -535,6 +535,11 @@ impl RpcClient {
Self::new_with_timeout(url, timeout)
}
/// Get the configured url of the client's sender
pub fn url(&self) -> String {
self.rpc_client.url()
}
/// Get the configured default [commitment level][cl].
///
/// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment
@@ -3245,6 +3250,60 @@ impl RpcClient {
)
}
/// Returns all information associated with the account of the provided pubkey.
///
/// If the account does not exist, this method returns `Ok(None)`.
///
/// To get multiple accounts at once, use the [`get_multiple_accounts_with_config`] method.
///
/// [`get_multiple_accounts_with_config`]: RpcClient::get_multiple_accounts_with_config
///
/// # RPC Reference
///
/// This method is built on the [`getAccountInfo`] RPC method.
///
/// [`getAccountInfo`]: https://docs.solana.com/developing/clients/jsonrpc-api#getaccountinfo
///
/// # Examples
///
/// ```
/// # use solana_client::{
/// # rpc_client::{self, RpcClient},
/// # rpc_config::RpcAccountInfoConfig,
/// # client_error::ClientError,
/// # };
/// # use solana_sdk::{
/// # signature::Signer,
/// # signer::keypair::Keypair,
/// # pubkey::Pubkey,
/// # commitment_config::CommitmentConfig,
/// # };
/// # use solana_account_decoder::UiAccountEncoding;
/// # use std::str::FromStr;
/// # let mocks = rpc_client::create_rpc_client_mocks();
/// # let rpc_client = RpcClient::new_mock_with_mocks("succeeds".to_string(), mocks);
/// let alice_pubkey = Pubkey::from_str("BgvYtJEfmZYdVKiptmMjxGzv8iQoo4MWjsP3QsTkhhxa").unwrap();
/// let commitment_config = CommitmentConfig::processed();
/// let config = RpcAccountInfoConfig {
/// encoding: Some(UiAccountEncoding::Base64),
/// commitment: Some(commitment_config),
/// .. RpcAccountInfoConfig::default()
/// };
/// let account = rpc_client.get_account_with_config(
/// &alice_pubkey,
/// config,
/// )?;
/// assert!(account.value.is_some());
/// # Ok::<(), ClientError>(())
/// ```
pub fn get_account_with_config(
&self,
pubkey: &Pubkey,
config: RpcAccountInfoConfig,
) -> RpcResult<Option<Account>> {
self.invoke(self.rpc_client.get_account_with_config(pubkey, config))
}
/// Get the max slot seen from retransmit stage.
///
/// # RPC Reference

View File

@@ -32,4 +32,5 @@ pub trait RpcSender {
params: serde_json::Value,
) -> Result<serde_json::Value>;
fn get_transport_stats(&self) -> RpcTransportStats;
fn url(&self) -> String;
}

View File

@@ -5,8 +5,13 @@
use {
crate::{
rpc_client::RpcClient, rpc_config::RpcProgramAccountsConfig, rpc_response::Response,
tpu_connection::TpuConnection, udp_client::UdpTpuConnection,
connection_cache::{
par_serialize_and_send_transaction_batch, send_wire_transaction,
serialize_and_send_transaction,
},
rpc_client::RpcClient,
rpc_config::RpcProgramAccountsConfig,
rpc_response::Response,
},
log::*,
solana_sdk::{
@@ -29,7 +34,7 @@ use {
},
std::{
io,
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
net::SocketAddr,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
RwLock,
@@ -118,67 +123,55 @@ impl ClientOptimizer {
}
/// An object for querying and sending transactions to the network.
pub struct ThinClient<C: 'static + TpuConnection> {
pub struct ThinClient {
rpc_clients: Vec<RpcClient>,
tpu_connections: Vec<C>,
tpu_addrs: Vec<SocketAddr>,
optimizer: ClientOptimizer,
}
impl<C: 'static + TpuConnection> ThinClient<C> {
impl ThinClient {
/// Create a new ThinClient that will interface with the Rpc at `rpc_addr` using TCP
/// and the Tpu at `tpu_addr` over `transactions_socket` using Quic or UDP
/// (currently hardcoded to UDP)
pub fn new(rpc_addr: SocketAddr, tpu_addr: SocketAddr, transactions_socket: UdpSocket) -> Self {
let tpu_connection = C::new(transactions_socket, tpu_addr);
Self::new_from_client(RpcClient::new_socket(rpc_addr), tpu_connection)
pub fn new(rpc_addr: SocketAddr, tpu_addr: SocketAddr) -> Self {
Self::new_from_client(RpcClient::new_socket(rpc_addr), tpu_addr)
}
pub fn new_socket_with_timeout(
rpc_addr: SocketAddr,
tpu_addr: SocketAddr,
transactions_socket: UdpSocket,
timeout: Duration,
) -> Self {
let rpc_client = RpcClient::new_socket_with_timeout(rpc_addr, timeout);
let tpu_connection = C::new(transactions_socket, tpu_addr);
Self::new_from_client(rpc_client, tpu_connection)
Self::new_from_client(rpc_client, tpu_addr)
}
fn new_from_client(rpc_client: RpcClient, tpu_connection: C) -> Self {
fn new_from_client(rpc_client: RpcClient, tpu_addr: SocketAddr) -> Self {
Self {
rpc_clients: vec![rpc_client],
tpu_connections: vec![tpu_connection],
tpu_addrs: vec![tpu_addr],
optimizer: ClientOptimizer::new(0),
}
}
pub fn new_from_addrs(
rpc_addrs: Vec<SocketAddr>,
tpu_addrs: Vec<SocketAddr>,
transactions_socket: UdpSocket,
) -> Self {
pub fn new_from_addrs(rpc_addrs: Vec<SocketAddr>, tpu_addrs: Vec<SocketAddr>) -> Self {
assert!(!rpc_addrs.is_empty());
assert_eq!(rpc_addrs.len(), tpu_addrs.len());
let rpc_clients: Vec<_> = rpc_addrs.into_iter().map(RpcClient::new_socket).collect();
let optimizer = ClientOptimizer::new(rpc_clients.len());
let tpu_connections: Vec<_> = tpu_addrs
.into_iter()
.map(|tpu_addr| C::new(transactions_socket.try_clone().unwrap(), tpu_addr))
.collect();
Self {
rpc_clients,
tpu_connections,
tpu_addrs,
optimizer,
}
}
fn tpu_connection(&self) -> &C {
&self.tpu_connections[self.optimizer.best()]
fn tpu_addr(&self) -> &SocketAddr {
&self.tpu_addrs[self.optimizer.best()]
}
fn rpc_client(&self) -> &RpcClient {
pub fn rpc_client(&self) -> &RpcClient {
&self.rpc_clients[self.optimizer.best()]
}
@@ -220,8 +213,7 @@ impl<C: 'static + TpuConnection> ThinClient<C> {
while now.elapsed().as_secs() < wait_time as u64 {
if num_confirmed == 0 {
// Send the transaction if there has been no confirmation (e.g. the first time)
self.tpu_connection()
.send_wire_transaction(&wire_transaction)?;
send_wire_transaction(&wire_transaction, self.tpu_addr())?;
}
if let Ok(confirmed_blocks) = self.poll_for_signature_confirmation(
@@ -316,13 +308,13 @@ impl<C: 'static + TpuConnection> ThinClient<C> {
}
}
impl<C: 'static + TpuConnection> Client for ThinClient<C> {
impl Client for ThinClient {
fn tpu_addr(&self) -> String {
self.tpu_connection().tpu_addr().to_string()
self.tpu_addr().to_string()
}
}
impl<C: 'static + TpuConnection> SyncClient for ThinClient<C> {
impl SyncClient for ThinClient {
fn send_and_confirm_message<T: Signers>(
&self,
keypairs: &T,
@@ -602,18 +594,16 @@ impl<C: 'static + TpuConnection> SyncClient for ThinClient<C> {
}
}
impl<C: 'static + TpuConnection> AsyncClient for ThinClient<C> {
impl AsyncClient for ThinClient {
fn async_send_transaction(&self, transaction: Transaction) -> TransportResult<Signature> {
let transaction = VersionedTransaction::from(transaction);
self.tpu_connection()
.serialize_and_send_transaction(&transaction)?;
serialize_and_send_transaction(&transaction, self.tpu_addr())?;
Ok(transaction.signatures[0])
}
fn async_send_batch(&self, transactions: Vec<Transaction>) -> TransportResult<()> {
let batch: Vec<VersionedTransaction> = transactions.into_iter().map(Into::into).collect();
self.tpu_connection()
.par_serialize_and_send_transaction_batch(&batch[..])?;
par_serialize_and_send_transaction_batch(&batch[..], self.tpu_addr())?;
Ok(())
}
@@ -648,23 +638,15 @@ impl<C: 'static + TpuConnection> AsyncClient for ThinClient<C> {
}
}
pub fn create_client(
(rpc, tpu): (SocketAddr, SocketAddr),
range: (u16, u16),
) -> ThinClient<UdpTpuConnection> {
let (_, transactions_socket) =
solana_net_utils::bind_in_range(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), range).unwrap();
ThinClient::<UdpTpuConnection>::new(rpc, tpu, transactions_socket)
pub fn create_client((rpc, tpu): (SocketAddr, SocketAddr)) -> ThinClient {
ThinClient::new(rpc, tpu)
}
pub fn create_client_with_timeout(
(rpc, tpu): (SocketAddr, SocketAddr),
range: (u16, u16),
timeout: Duration,
) -> ThinClient<UdpTpuConnection> {
let (_, transactions_socket) =
solana_net_utils::bind_in_range(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), range).unwrap();
ThinClient::<UdpTpuConnection>::new_socket_with_timeout(rpc, tpu, transactions_socket, timeout)
) -> ThinClient {
ThinClient::new_socket_with_timeout(rpc, tpu, timeout)
}
#[cfg(test)]

View File

@@ -1,6 +1,7 @@
use {
crate::{
client_error::ClientError,
connection_cache::send_wire_transaction_async,
pubsub_client::{PubsubClient, PubsubClientError, PubsubClientSubscription},
rpc_client::RpcClient,
rpc_request::MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS,
@@ -17,6 +18,7 @@ use {
signature::SignerError,
signers::Signers,
transaction::{Transaction, TransactionError},
transport::{Result as TransportResult, TransportError},
},
std::{
collections::{HashMap, HashSet, VecDeque},
@@ -73,7 +75,7 @@ impl Default for TpuClientConfig {
/// Client which sends transactions directly to the current leader's TPU port over UDP.
/// The client uses RPC to determine the current leader and fetch node contact info
pub struct TpuClient {
send_socket: UdpSocket,
_deprecated: UdpSocket, // TpuClient now uses the connection_cache to choose a send_socket
fanout_slots: u64,
leader_tpu_service: LeaderTpuService,
exit: Arc<AtomicBool>,
@@ -85,25 +87,48 @@ impl TpuClient {
/// size
pub fn send_transaction(&self, transaction: &Transaction) -> bool {
let wire_transaction = serialize(transaction).expect("serialization should succeed");
self.send_wire_transaction(&wire_transaction)
self.send_wire_transaction(wire_transaction)
}
/// Send a wire transaction to the current and upcoming leader TPUs according to fanout size
pub fn send_wire_transaction(&self, wire_transaction: &[u8]) -> bool {
let mut sent = false;
pub fn send_wire_transaction(&self, wire_transaction: Vec<u8>) -> bool {
self.try_send_wire_transaction(wire_transaction).is_ok()
}
/// Serialize and send transaction to the current and upcoming leader TPUs according to fanout
/// size
/// Returns the last error if all sends fail
pub fn try_send_transaction(&self, transaction: &Transaction) -> TransportResult<()> {
let wire_transaction = serialize(transaction).expect("serialization should succeed");
self.try_send_wire_transaction(wire_transaction)
}
/// Send a wire transaction to the current and upcoming leader TPUs according to fanout size
/// Returns the last error if all sends fail
fn try_send_wire_transaction(&self, wire_transaction: Vec<u8>) -> TransportResult<()> {
let mut last_error: Option<TransportError> = None;
let mut some_success = false;
for tpu_address in self
.leader_tpu_service
.leader_tpu_sockets(self.fanout_slots)
{
if self
.send_socket
.send_to(wire_transaction, tpu_address)
.is_ok()
{
sent = true;
let result = send_wire_transaction_async(wire_transaction.clone(), &tpu_address);
if let Err(err) = result {
last_error = Some(err);
} else {
some_success = true;
}
}
sent
if !some_success {
Err(if let Some(err) = last_error {
err
} else {
std::io::Error::new(std::io::ErrorKind::Other, "No sends attempted").into()
})
} else {
Ok(())
}
}
/// Create a new client that disconnects when dropped
@@ -117,7 +142,7 @@ impl TpuClient {
LeaderTpuService::new(rpc_client.clone(), websocket_url, exit.clone())?;
Ok(Self {
send_socket: UdpSocket::bind("0.0.0.0:0").unwrap(),
_deprecated: UdpSocket::bind("0.0.0.0:0").unwrap(),
fanout_slots: config.fanout_slots.min(MAX_FANOUT_SLOTS).max(1),
leader_tpu_service,
exit,
@@ -266,6 +291,10 @@ impl TpuClient {
}
Err(TpuSenderError::Custom("Max retries exceeded".into()))
}
pub fn rpc_client(&self) -> &RpcClient {
&self.rpc_client
}
}
impl Drop for TpuClient {

View File

@@ -1,9 +1,26 @@
use {
rayon::iter::{IntoParallelIterator, ParallelIterator},
solana_metrics::MovingStat,
solana_sdk::{transaction::VersionedTransaction, transport::Result as TransportResult},
std::net::{SocketAddr, UdpSocket},
std::{
net::{SocketAddr, UdpSocket},
sync::{atomic::AtomicU64, Arc},
},
};
#[derive(Default)]
pub struct ClientStats {
pub total_connections: AtomicU64,
pub connection_reuse: AtomicU64,
pub connection_errors: AtomicU64,
// these will be the last values of these stats
pub congestion_events: MovingStat,
pub tx_streams_blocked_uni: MovingStat,
pub tx_data_blocked: MovingStat,
pub tx_acks: MovingStat,
}
pub trait TpuConnection {
fn new(client_socket: UdpSocket, tpu_addr: SocketAddr) -> Self;
@@ -12,29 +29,45 @@ pub trait TpuConnection {
fn serialize_and_send_transaction(
&self,
transaction: &VersionedTransaction,
stats: &ClientStats,
) -> TransportResult<()> {
let wire_transaction =
bincode::serialize(transaction).expect("serialize Transaction in send_batch");
self.send_wire_transaction(&wire_transaction)
self.send_wire_transaction(&wire_transaction, stats)
}
fn send_wire_transaction<T>(&self, wire_transaction: T) -> TransportResult<()>
fn send_wire_transaction<T>(
&self,
wire_transaction: T,
stats: &ClientStats,
) -> TransportResult<()>
where
T: AsRef<[u8]>;
fn send_wire_transaction_async(
&self,
wire_transaction: Vec<u8>,
stats: Arc<ClientStats>,
) -> TransportResult<()>;
fn par_serialize_and_send_transaction_batch(
&self,
transactions: &[VersionedTransaction],
stats: &ClientStats,
) -> TransportResult<()> {
let buffers = transactions
.into_par_iter()
.map(|tx| bincode::serialize(&tx).expect("serialize Transaction in send_batch"))
.collect::<Vec<_>>();
self.send_wire_transaction_batch(&buffers)
self.send_wire_transaction_batch(&buffers, stats)
}
fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
fn send_wire_transaction_batch<T>(
&self,
buffers: &[T],
stats: &ClientStats,
) -> TransportResult<()>
where
T: AsRef<[u8]>;
}

View File

@@ -2,11 +2,14 @@
//! an interface for sending transactions
use {
crate::tpu_connection::TpuConnection,
crate::tpu_connection::{ClientStats, TpuConnection},
core::iter::repeat,
solana_sdk::transport::Result as TransportResult,
solana_streamer::sendmmsg::batch_send,
std::net::{SocketAddr, UdpSocket},
std::{
net::{SocketAddr, UdpSocket},
sync::Arc,
},
};
pub struct UdpTpuConnection {
@@ -26,7 +29,11 @@ impl TpuConnection for UdpTpuConnection {
&self.addr
}
fn send_wire_transaction<T>(&self, wire_transaction: T) -> TransportResult<()>
fn send_wire_transaction<T>(
&self,
wire_transaction: T,
_stats: &ClientStats,
) -> TransportResult<()>
where
T: AsRef<[u8]>,
{
@@ -34,7 +41,20 @@ impl TpuConnection for UdpTpuConnection {
Ok(())
}
fn send_wire_transaction_batch<T>(&self, buffers: &[T]) -> TransportResult<()>
fn send_wire_transaction_async(
&self,
wire_transaction: Vec<u8>,
_stats: Arc<ClientStats>,
) -> TransportResult<()> {
self.socket.send_to(wire_transaction.as_ref(), self.addr)?;
Ok(())
}
fn send_wire_transaction_batch<T>(
&self,
buffers: &[T],
_stats: &ClientStats,
) -> TransportResult<()>
where
T: AsRef<[u8]>,
{

View File

@@ -1,7 +1,7 @@
[package]
name = "solana-core"
description = "Blockchain, Rebuilt for Scale"
version = "1.10.4"
version = "1.10.9"
homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-core"
readme = "../README.md"
@@ -21,42 +21,42 @@ bs58 = "0.4.0"
chrono = { version = "0.4.11", features = ["serde"] }
crossbeam-channel = "0.5"
dashmap = { version = "4.0.2", features = ["rayon", "raw-api"] }
etcd-client = { version = "0.8.4", features = ["tls"] }
etcd-client = { version = "0.9.0", features = ["tls"] }
fs_extra = "1.2.0"
histogram = "0.6.9"
itertools = "0.10.3"
log = "0.4.14"
lru = "0.7.3"
lru = "0.7.5"
rand = "0.7.0"
rand_chacha = "0.2.2"
rayon = "1.5.1"
retain_mut = "0.1.7"
serde = "1.0.136"
serde_derive = "1.0.103"
solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.10.4" }
solana-bloom = { path = "../bloom", version = "=1.10.4" }
solana-client = { path = "../client", version = "=1.10.4" }
solana-entry = { path = "../entry", version = "=1.10.4" }
solana-frozen-abi = { path = "../frozen-abi", version = "=1.10.4" }
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.10.4" }
solana-geyser-plugin-manager = { path = "../geyser-plugin-manager", version = "=1.10.4" }
solana-gossip = { path = "../gossip", version = "=1.10.4" }
solana-ledger = { path = "../ledger", version = "=1.10.4" }
solana-measure = { path = "../measure", version = "=1.10.4" }
solana-metrics = { path = "../metrics", version = "=1.10.4" }
solana-net-utils = { path = "../net-utils", version = "=1.10.4" }
solana-perf = { path = "../perf", version = "=1.10.4" }
solana-poh = { path = "../poh", version = "=1.10.4" }
solana-program-runtime = { path = "../program-runtime", version = "=1.10.4" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.10.4" }
solana-replica-lib = { path = "../replica-lib", version = "=1.10.4" }
solana-rpc = { path = "../rpc", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-send-transaction-service = { path = "../send-transaction-service", version = "=1.10.4" }
solana-streamer = { path = "../streamer", version = "=1.10.4" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.4" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.4" }
solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.10.9" }
solana-bloom = { path = "../bloom", version = "=1.10.9" }
solana-client = { path = "../client", version = "=1.10.9" }
solana-entry = { path = "../entry", version = "=1.10.9" }
solana-frozen-abi = { path = "../frozen-abi", version = "=1.10.9" }
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.10.9" }
solana-geyser-plugin-manager = { path = "../geyser-plugin-manager", version = "=1.10.9" }
solana-gossip = { path = "../gossip", version = "=1.10.9" }
solana-ledger = { path = "../ledger", version = "=1.10.9" }
solana-measure = { path = "../measure", version = "=1.10.9" }
solana-metrics = { path = "../metrics", version = "=1.10.9" }
solana-net-utils = { path = "../net-utils", version = "=1.10.9" }
solana-perf = { path = "../perf", version = "=1.10.9" }
solana-poh = { path = "../poh", version = "=1.10.9" }
solana-program-runtime = { path = "../program-runtime", version = "=1.10.9" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.10.9" }
solana-replica-lib = { path = "../replica-lib", version = "=1.10.9" }
solana-rpc = { path = "../rpc", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-send-transaction-service = { path = "../send-transaction-service", version = "=1.10.9" }
solana-streamer = { path = "../streamer", version = "=1.10.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.9" }
sys-info = "0.9.1"
tempfile = "3.3.0"
thiserror = "1.0"
@@ -69,10 +69,10 @@ raptorq = "1.6.5"
reqwest = { version = "0.11.10", default-features = false, features = ["blocking", "rustls-tls", "json"] }
serde_json = "1.0.79"
serial_test = "0.6.0"
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-program-runtime = { path = "../program-runtime", version = "=1.10.4" }
solana-stake-program = { path = "../programs/stake", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.9" }
solana-program-runtime = { path = "../program-runtime", version = "=1.10.9" }
solana-stake-program = { path = "../programs/stake", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
static_assertions = "1.1.0"
systemstat = "0.1.10"

View File

@@ -9,7 +9,12 @@ use {
retransmit_stage::RetransmitStage,
},
solana_gossip::contact_info::ContactInfo,
solana_sdk::{clock::Slot, hash::hashv, pubkey::Pubkey, signature::Signature},
solana_ledger::{
genesis_utils::{create_genesis_config, GenesisConfigInfo},
shred::Shred,
},
solana_runtime::bank::Bank,
solana_sdk::pubkey::Pubkey,
test::Bencher,
};
@@ -26,87 +31,48 @@ fn make_cluster_nodes<R: Rng>(
fn get_retransmit_peers_deterministic(
cluster_nodes: &ClusterNodes<RetransmitStage>,
slot: &Slot,
shred: &mut Shred,
slot_leader: &Pubkey,
root_bank: &Bank,
num_simulated_shreds: usize,
) {
for i in 0..num_simulated_shreds {
// see Shred::seed
let shred_seed = hashv(&[
&slot.to_le_bytes(),
&(i as u32).to_le_bytes(),
&slot_leader.to_bytes(),
])
.to_bytes();
let (_neighbors, _children) = cluster_nodes.get_retransmit_peers_deterministic(
shred_seed,
solana_gossip::cluster_info::DATA_PLANE_FANOUT,
shred.common_header.index = i as u32;
let (_neighbors, _children) = cluster_nodes.get_retransmit_peers(
*slot_leader,
);
}
}
fn get_retransmit_peers_compat(
cluster_nodes: &ClusterNodes<RetransmitStage>,
slot_leader: &Pubkey,
signatures: &[Signature],
) {
for signature in signatures.iter() {
// see Shred::seed
let signature = signature.as_ref();
let offset = signature.len().checked_sub(32).unwrap();
let shred_seed = signature[offset..].try_into().unwrap();
let (_neighbors, _children) = cluster_nodes.get_retransmit_peers_compat(
shred_seed,
shred,
root_bank,
solana_gossip::cluster_info::DATA_PLANE_FANOUT,
*slot_leader,
);
}
}
fn get_retransmit_peers_deterministic_wrapper(b: &mut Bencher, unstaked_ratio: Option<(u32, u32)>) {
let mut rng = rand::thread_rng();
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
let bank = Bank::new_for_benches(&genesis_config);
let (nodes, cluster_nodes) = make_cluster_nodes(&mut rng, unstaked_ratio);
let slot_leader = nodes[1..].choose(&mut rng).unwrap().id;
let slot = rand::random::<u64>();
let mut shred = Shred::new_empty_data_shred();
shred.common_header.slot = slot;
b.iter(|| {
get_retransmit_peers_deterministic(
&cluster_nodes,
&slot,
&mut shred,
&slot_leader,
&bank,
NUM_SIMULATED_SHREDS,
)
});
}
fn get_retransmit_peers_compat_wrapper(b: &mut Bencher, unstaked_ratio: Option<(u32, u32)>) {
let mut rng = rand::thread_rng();
let (nodes, cluster_nodes) = make_cluster_nodes(&mut rng, unstaked_ratio);
let slot_leader = nodes[1..].choose(&mut rng).unwrap().id;
let signatures: Vec<_> = std::iter::repeat_with(Signature::new_unique)
.take(NUM_SIMULATED_SHREDS)
.collect();
b.iter(|| get_retransmit_peers_compat(&cluster_nodes, &slot_leader, &signatures));
}
#[bench]
fn bench_get_retransmit_peers_deterministic_unstaked_ratio_1_2(b: &mut Bencher) {
get_retransmit_peers_deterministic_wrapper(b, Some((1, 2)));
}
#[bench]
fn bench_get_retransmit_peers_compat_unstaked_ratio_1_2(b: &mut Bencher) {
get_retransmit_peers_compat_wrapper(b, Some((1, 2)));
}
#[bench]
fn bench_get_retransmit_peers_deterministic_unstaked_ratio_1_32(b: &mut Bencher) {
get_retransmit_peers_deterministic_wrapper(b, Some((1, 32)));
}
#[bench]
fn bench_get_retransmit_peers_compat_unstaked_ratio_1_32(b: &mut Bencher) {
get_retransmit_peers_compat_wrapper(b, Some((1, 32)));
}

View File

@@ -14,6 +14,7 @@ use {
histogram::Histogram,
itertools::Itertools,
retain_mut::RetainMut,
solana_client::connection_cache::send_wire_transaction_batch,
solana_entry::entry::hash_transactions,
solana_gossip::{cluster_info::ClusterInfo, contact_info::ContactInfo},
solana_ledger::blockstore_processor::TransactionStatusSender,
@@ -51,8 +52,8 @@ use {
transaction::{
self, AddressLoader, SanitizedTransaction, TransactionError, VersionedTransaction,
},
transport::TransportError,
},
solana_streamer::sendmmsg::{batch_send, SendPktsError},
solana_transaction_status::token_balances::{
collect_token_balances, TransactionTokenBalancesSet,
},
@@ -60,7 +61,7 @@ use {
cmp,
collections::HashMap,
env,
net::{SocketAddr, UdpSocket},
net::SocketAddr,
sync::{
atomic::{AtomicU64, AtomicUsize, Ordering},
Arc, Mutex, RwLock,
@@ -482,11 +483,10 @@ impl BankingStage {
/// Forwards all valid, unprocessed packets in the buffer, up to a rate limit. Returns
/// the number of successfully forwarded packets in second part of tuple
fn forward_buffered_packets(
socket: &std::net::UdpSocket,
tpu_forwards: &std::net::SocketAddr,
packets: Vec<&Packet>,
data_budget: &DataBudget,
) -> (std::io::Result<()>, usize) {
) -> (std::result::Result<(), TransportError>, usize) {
const INTERVAL_MS: u64 = 100;
const MAX_BYTES_PER_SECOND: usize = 10_000 * 1200;
const MAX_BYTES_PER_INTERVAL: usize = MAX_BYTES_PER_SECOND * INTERVAL_MS as usize / 1000;
@@ -502,18 +502,35 @@ impl BankingStage {
.iter()
.filter_map(|p| {
if !p.meta.forwarded() && data_budget.take(p.meta.size) {
Some((&p.data[..p.meta.size], tpu_forwards))
Some(&p.data[..p.meta.size])
} else {
None
}
})
.collect();
// TODO: see https://github.com/solana-labs/solana/issues/23819
// fix this so returns the correct number of succeeded packets
// when there's an error sending the batch. This was left as-is for now
// in favor of shipping Quic support, which was considered higher-priority
if !packet_vec.is_empty() {
inc_new_counter_info!("banking_stage-forwarded_packets", packet_vec.len());
if let Err(SendPktsError::IoError(ioerr, num_failed)) = batch_send(socket, &packet_vec)
{
return (Err(ioerr), packet_vec.len().saturating_sub(num_failed));
let mut measure = Measure::start("banking_stage-forward-us");
let res = send_wire_transaction_batch(&packet_vec, tpu_forwards);
measure.stop();
inc_new_counter_info!(
"banking_stage-forward-us",
measure.as_us() as usize,
1000,
1000
);
if let Err(err) = res {
inc_new_counter_info!("banking_stage-forward_packets-failed-batches", 1);
return (Err(err), 0);
}
}
@@ -766,7 +783,6 @@ impl BankingStage {
#[allow(clippy::too_many_arguments)]
fn process_buffered_packets(
my_pubkey: &Pubkey,
socket: &std::net::UdpSocket,
poh_recorder: &Arc<Mutex<PohRecorder>>,
cluster_info: &ClusterInfo,
buffered_packet_batches: &mut UnprocessedPacketBatches,
@@ -846,7 +862,6 @@ impl BankingStage {
cluster_info,
buffered_packet_batches,
poh_recorder,
socket,
false,
data_budget,
slot_metrics_tracker,
@@ -865,7 +880,6 @@ impl BankingStage {
cluster_info,
buffered_packet_batches,
poh_recorder,
socket,
true,
data_budget,
slot_metrics_tracker,
@@ -887,7 +901,6 @@ impl BankingStage {
cluster_info: &ClusterInfo,
buffered_packet_batches: &mut UnprocessedPacketBatches,
poh_recorder: &Arc<Mutex<PohRecorder>>,
socket: &UdpSocket,
hold: bool,
data_budget: &DataBudget,
slot_metrics_tracker: &mut LeaderSlotMetricsTracker,
@@ -913,7 +926,7 @@ impl BankingStage {
Self::filter_valid_packets_for_forwarding(buffered_packet_batches.iter());
let forwardable_packets_len = forwardable_packets.len();
let (_forward_result, sucessful_forwarded_packets_count) =
Self::forward_buffered_packets(socket, &addr, forwardable_packets, data_budget);
Self::forward_buffered_packets(&addr, forwardable_packets, data_budget);
let failed_forwarded_packets_count =
forwardable_packets_len.saturating_sub(sucessful_forwarded_packets_count);
@@ -958,7 +971,6 @@ impl BankingStage {
cost_model: Arc<RwLock<CostModel>>,
) {
let recorder = poh_recorder.lock().unwrap().recorder();
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
let mut buffered_packet_batches = UnprocessedPacketBatches::with_capacity(batch_limit);
let mut banking_stage_stats = BankingStageStats::new(id);
let qos_service = QosService::new(cost_model, id);
@@ -970,7 +982,6 @@ impl BankingStage {
|_| {
Self::process_buffered_packets(
&my_pubkey,
&socket,
poh_recorder,
cluster_info,
&mut buffered_packet_batches,
@@ -1342,38 +1353,28 @@ impl BankingStage {
gossip_vote_sender: &ReplayVoteSender,
qos_service: &QosService,
) -> ProcessTransactionBatchOutput {
let ((transactions_qos_results, cost_model_throttled_transactions_count), cost_model_time) =
Measure::this(
|_| {
let tx_costs = qos_service.compute_transaction_costs(txs.iter());
let mut cost_model_time = Measure::start("cost_model");
let (transactions_qos_results, num_included) =
qos_service.select_transactions_per_cost(txs.iter(), tx_costs.iter(), bank);
let transaction_costs = qos_service.compute_transaction_costs(txs.iter());
let cost_model_throttled_transactions_count =
txs.len().saturating_sub(num_included);
let (transactions_qos_results, num_included) =
qos_service.select_transactions_per_cost(txs.iter(), transaction_costs.iter(), bank);
qos_service.accumulate_estimated_transaction_costs(
&Self::accumulate_batched_transaction_costs(
tx_costs.iter(),
transactions_qos_results.iter(),
),
);
(
transactions_qos_results,
cost_model_throttled_transactions_count,
)
},
(),
"cost_model",
);
let cost_model_throttled_transactions_count = txs.len().saturating_sub(num_included);
qos_service.accumulate_estimated_transaction_costs(
&Self::accumulate_batched_transaction_costs(
transaction_costs.iter(),
transactions_qos_results.iter(),
),
);
cost_model_time.stop();
// Only lock accounts for those transactions are selected for the block;
// Once accounts are locked, other threads cannot encode transactions that will modify the
// same account state
let mut lock_time = Measure::start("lock_time");
let batch =
bank.prepare_sanitized_batch_with_results(txs, transactions_qos_results.into_iter());
let batch = bank.prepare_sanitized_batch_with_results(txs, transactions_qos_results.iter());
lock_time.stop();
// retryable_txs includes AccountInUse, WouldExceedMaxBlockCostLimit
@@ -1388,21 +1389,31 @@ impl BankingStage {
gossip_vote_sender,
);
let mut unlock_time = Measure::start("unlock_time");
// Once the accounts are new transactions can enter the pipeline to process them
drop(batch);
unlock_time.stop();
let ExecuteAndCommitTransactionsOutput {
ref mut retryable_transaction_indexes,
ref execute_and_commit_timings,
..
} = execute_and_commit_transactions_output;
// TODO: This does not revert the cost tracker changes from all unexecuted transactions
// yet: For example tx that are too old will not be included in the block, but are not
// retryable.
QosService::update_or_remove_transaction_costs(
transaction_costs.iter(),
transactions_qos_results.iter(),
retryable_transaction_indexes,
bank,
);
retryable_transaction_indexes
.iter_mut()
.for_each(|x| *x += chunk_offset);
let mut unlock_time = Measure::start("unlock_time");
// Once the accounts are new transactions can enter the pipeline to process them
drop(batch);
unlock_time.stop();
let (cu, us) =
Self::accumulate_execute_units_and_time(&execute_and_commit_timings.execute_timings);
qos_service.accumulate_actual_execute_cu(cu);
@@ -2887,6 +2898,131 @@ mod tests {
Blockstore::destroy(ledger_path.path()).unwrap();
}
#[test]
fn test_bank_process_and_record_transactions_cost_tracker() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_slow_genesis_config(10_000);
let bank = Arc::new(Bank::new_no_wallclock_throttle_for_tests(&genesis_config));
let pubkey = solana_sdk::pubkey::new_rand();
let ledger_path = get_tmp_ledger_path_auto_delete!();
{
let blockstore = Blockstore::open(ledger_path.path())
.expect("Expected to be able to open database ledger");
let (poh_recorder, _entry_receiver, record_receiver) = PohRecorder::new(
bank.tick_height(),
bank.last_blockhash(),
bank.clone(),
Some((4, 4)),
bank.ticks_per_slot(),
&pubkey,
&Arc::new(blockstore),
&Arc::new(LeaderScheduleCache::new_from_bank(&bank)),
&Arc::new(PohConfig::default()),
Arc::new(AtomicBool::default()),
);
let recorder = poh_recorder.recorder();
let poh_recorder = Arc::new(Mutex::new(poh_recorder));
let poh_simulator = simulate_poh(record_receiver, &poh_recorder);
poh_recorder.lock().unwrap().set_bank(&bank);
let (gossip_vote_sender, _gossip_vote_receiver) = unbounded();
let qos_service = QosService::new(Arc::new(RwLock::new(CostModel::default())), 1);
let get_block_cost = || bank.read_cost_tracker().unwrap().block_cost();
let get_tx_count = || bank.read_cost_tracker().unwrap().transaction_count();
assert_eq!(get_block_cost(), 0);
assert_eq!(get_tx_count(), 0);
//
// TEST: cost tracker's block cost increases when successfully processing a tx
//
let transactions = sanitize_transactions(vec![system_transaction::transfer(
&mint_keypair,
&pubkey,
1,
genesis_config.hash(),
)]);
let process_transactions_batch_output = BankingStage::process_and_record_transactions(
&bank,
&transactions,
&recorder,
0,
None,
&gossip_vote_sender,
&qos_service,
);
let ExecuteAndCommitTransactionsOutput {
executed_with_successful_result_count,
commit_transactions_result,
..
} = process_transactions_batch_output.execute_and_commit_transactions_output;
assert_eq!(executed_with_successful_result_count, 1);
assert!(commit_transactions_result.is_ok());
let single_transfer_cost = get_block_cost();
assert_ne!(single_transfer_cost, 0);
assert_eq!(get_tx_count(), 1);
//
// TEST: When a tx in a batch can't be executed (here because of account
// locks), then its cost does not affect the cost tracker.
//
let allocate_keypair = Keypair::new();
let transactions = sanitize_transactions(vec![
system_transaction::transfer(&mint_keypair, &pubkey, 2, genesis_config.hash()),
// intentionally use a tx that has a different cost
system_transaction::allocate(
&mint_keypair,
&allocate_keypair,
genesis_config.hash(),
1,
),
]);
let process_transactions_batch_output = BankingStage::process_and_record_transactions(
&bank,
&transactions,
&recorder,
0,
None,
&gossip_vote_sender,
&qos_service,
);
let ExecuteAndCommitTransactionsOutput {
executed_with_successful_result_count,
commit_transactions_result,
retryable_transaction_indexes,
..
} = process_transactions_batch_output.execute_and_commit_transactions_output;
assert_eq!(executed_with_successful_result_count, 1);
assert!(commit_transactions_result.is_ok());
assert_eq!(retryable_transaction_indexes, vec![1]);
assert_eq!(get_block_cost(), 2 * single_transfer_cost);
assert_eq!(get_tx_count(), 2);
poh_recorder
.lock()
.unwrap()
.is_exited
.store(true, Ordering::Relaxed);
let _ = poh_simulator.join();
}
Blockstore::destroy(ledger_path.path()).unwrap();
}
fn simulate_poh(
record_receiver: CrossbeamReceiver<Record>,
poh_recorder: &Arc<Mutex<PohRecorder>>,
@@ -3835,7 +3971,6 @@ mod tests {
let local_node = Node::new_localhost_with_pubkey(validator_pubkey);
let cluster_info = new_test_cluster_info(local_node.info);
let send_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
let recv_socket = &local_node.sockets.tpu_forwards[0];
let test_cases = vec![
@@ -3857,7 +3992,6 @@ mod tests {
&cluster_info,
&mut unprocessed_packet_batches,
&poh_recorder,
&send_socket,
true,
&data_budget,
&mut LeaderSlotMetricsTracker::new(0),
@@ -3935,7 +4069,6 @@ mod tests {
let local_node = Node::new_localhost_with_pubkey(validator_pubkey);
let cluster_info = new_test_cluster_info(local_node.info);
let send_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
let recv_socket = &local_node.sockets.tpu_forwards[0];
let test_cases = vec![
@@ -3969,7 +4102,6 @@ mod tests {
&cluster_info,
&mut unprocessed_packet_batches,
&poh_recorder,
&send_socket,
hold,
&DataBudget::default(),
&mut LeaderSlotMetricsTracker::new(0),

View File

@@ -10,13 +10,12 @@ use {
crds::GossipRoute,
crds_gossip_pull::CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS,
crds_value::{CrdsData, CrdsValue},
weighted_shuffle::{weighted_best, weighted_shuffle, WeightedShuffle},
weighted_shuffle::WeightedShuffle,
},
solana_ledger::shred::Shred,
solana_runtime::bank::Bank,
solana_sdk::{
clock::{Epoch, Slot},
feature_set,
pubkey::Pubkey,
signature::Keypair,
timing::timestamp,
@@ -56,10 +55,6 @@ pub struct ClusterNodes<T> {
// Reverse index from nodes pubkey to their index in self.nodes.
index: HashMap<Pubkey, /*index:*/ usize>,
weighted_shuffle: WeightedShuffle</*stake:*/ u64>,
// Weights and indices for sampling peers. weighted_{shuffle,best} expect
// weights >= 1. For backward compatibility we use max(1, stake) for
// weights and exclude nodes with no contact-info.
compat_index: Vec<(/*weight:*/ u64, /*index:*/ usize)>,
_phantom: PhantomData<T>,
}
@@ -92,14 +87,15 @@ impl Node {
impl<T> ClusterNodes<T> {
pub(crate) fn num_peers(&self) -> usize {
self.compat_index.len()
self.nodes.len().saturating_sub(1)
}
// A peer is considered live if they generated their contact info recently.
pub(crate) fn num_peers_live(&self, now: u64) -> usize {
self.compat_index
self.nodes
.iter()
.filter_map(|(_, index)| self.nodes[*index].contact_info())
.filter(|node| node.pubkey() != self.pubkey)
.filter_map(|node| node.contact_info())
.filter(|node| {
let elapsed = if node.wallclock < now {
now - node.wallclock
@@ -120,20 +116,12 @@ impl ClusterNodes<BroadcastStage> {
pub(crate) fn get_broadcast_addrs(
&self,
shred: &Shred,
root_bank: &Bank,
_root_bank: &Bank,
fanout: usize,
socket_addr_space: &SocketAddrSpace,
) -> Vec<SocketAddr> {
const MAX_CONTACT_INFO_AGE: Duration = Duration::from_secs(2 * 60);
let shred_seed = shred.seed(self.pubkey, root_bank);
if !enable_turbine_peers_shuffle_patch(shred.slot(), root_bank) {
if let Some(node) = self.get_broadcast_peer(shred_seed) {
if socket_addr_space.check(&node.tvu) {
return vec![node.tvu];
}
}
return Vec::default();
}
let shred_seed = shred.seed(self.pubkey);
let mut rng = ChaChaRng::from_seed(shred_seed);
let index = match self.weighted_shuffle.first(&mut rng) {
None => return Vec::default(),
@@ -175,20 +163,6 @@ impl ClusterNodes<BroadcastStage> {
.filter(|addr| ContactInfo::is_valid_address(addr, socket_addr_space))
.collect()
}
/// Returns the root of turbine broadcast tree, which the leader sends the
/// shred to.
fn get_broadcast_peer(&self, shred_seed: [u8; 32]) -> Option<&ContactInfo> {
if self.compat_index.is_empty() {
None
} else {
let index = weighted_best(&self.compat_index, shred_seed);
match &self.nodes[index].node {
NodeId::ContactInfo(node) => Some(node),
NodeId::Pubkey(_) => panic!("this should not happen!"),
}
}
}
}
impl ClusterNodes<RetransmitStage> {
@@ -223,32 +197,17 @@ impl ClusterNodes<RetransmitStage> {
.collect()
}
fn get_retransmit_peers(
pub fn get_retransmit_peers(
&self,
slot_leader: Pubkey,
shred: &Shred,
root_bank: &Bank,
_root_bank: &Bank,
fanout: usize,
) -> (
Vec<&Node>, // neighbors
Vec<&Node>, // children
) {
let shred_seed = shred.seed(slot_leader, root_bank);
if !enable_turbine_peers_shuffle_patch(shred.slot(), root_bank) {
return self.get_retransmit_peers_compat(shred_seed, fanout, slot_leader);
}
self.get_retransmit_peers_deterministic(shred_seed, fanout, slot_leader)
}
pub fn get_retransmit_peers_deterministic(
&self,
shred_seed: [u8; 32],
fanout: usize,
slot_leader: Pubkey,
) -> (
Vec<&Node>, // neighbors
Vec<&Node>, // children
) {
let shred_seed = shred.seed(slot_leader);
let mut weighted_shuffle = self.weighted_shuffle.clone();
// Exclude slot leader from list of nodes.
if slot_leader == self.pubkey {
@@ -271,46 +230,6 @@ impl ClusterNodes<RetransmitStage> {
debug_assert_eq!(neighbors[self_index % fanout].pubkey(), self.pubkey);
(neighbors, children)
}
pub fn get_retransmit_peers_compat(
&self,
shred_seed: [u8; 32],
fanout: usize,
slot_leader: Pubkey,
) -> (
Vec<&Node>, // neighbors
Vec<&Node>, // children
) {
// Exclude leader from list of nodes.
let (weights, index): (Vec<u64>, Vec<usize>) = if slot_leader == self.pubkey {
error!("retransmit from slot leader: {}", slot_leader);
self.compat_index.iter().copied().unzip()
} else {
self.compat_index
.iter()
.filter(|(_, i)| self.nodes[*i].pubkey() != slot_leader)
.copied()
.unzip()
};
let index: Vec<_> = {
let shuffle = weighted_shuffle(weights.into_iter(), shred_seed);
shuffle.into_iter().map(|i| index[i]).collect()
};
let self_index = index
.iter()
.position(|i| self.nodes[*i].pubkey() == self.pubkey)
.unwrap();
let (neighbors, children) = compute_retransmit_peers(fanout, self_index, &index);
// Assert that the node itself is included in the set of neighbors, at
// the right offset.
debug_assert_eq!(
self.nodes[neighbors[self_index % fanout]].pubkey(),
self.pubkey
);
let neighbors = neighbors.into_iter().map(|i| &self.nodes[i]).collect();
let children = children.into_iter().map(|i| &self.nodes[i]).collect();
(neighbors, children)
}
}
pub fn new_cluster_nodes<T: 'static>(
@@ -326,30 +245,15 @@ pub fn new_cluster_nodes<T: 'static>(
.collect();
let broadcast = TypeId::of::<T>() == TypeId::of::<BroadcastStage>();
let stakes: Vec<u64> = nodes.iter().map(|node| node.stake).collect();
let mut weighted_shuffle = WeightedShuffle::new(&stakes).unwrap();
let mut weighted_shuffle = WeightedShuffle::new("cluster-nodes", &stakes);
if broadcast {
weighted_shuffle.remove_index(index[&self_pubkey]);
}
// For backward compatibility:
// * nodes which do not have contact-info are excluded.
// * stakes are floored at 1.
// The sorting key here should be equivalent to
// solana_gossip::deprecated::sorted_stakes_with_index.
// Leader itself is excluded when sampling broadcast peers.
let compat_index = nodes
.iter()
.enumerate()
.filter(|(_, node)| node.contact_info().is_some())
.filter(|(_, node)| !broadcast || node.pubkey() != self_pubkey)
.sorted_by_key(|(_, node)| Reverse((node.stake.max(1), node.pubkey())))
.map(|(index, node)| (node.stake.max(1), index))
.collect();
ClusterNodes {
pubkey: self_pubkey,
nodes,
index,
weighted_shuffle,
compat_index,
_phantom: PhantomData::default(),
}
}
@@ -387,21 +291,6 @@ fn get_nodes(cluster_info: &ClusterInfo, stakes: &HashMap<Pubkey, u64>) -> Vec<N
.collect()
}
fn enable_turbine_peers_shuffle_patch(shred_slot: Slot, root_bank: &Bank) -> bool {
let feature_slot = root_bank
.feature_set
.activated_slot(&feature_set::turbine_peers_shuffle::id());
match feature_slot {
None => false,
Some(feature_slot) => {
let epoch_schedule = root_bank.epoch_schedule();
let feature_epoch = epoch_schedule.get_epoch(feature_slot);
let shred_epoch = epoch_schedule.get_epoch(shred_slot);
feature_epoch < shred_epoch
}
}
}
impl<T> ClusterNodesCache<T> {
pub fn new(
// Capacity of underlying LRU-cache in terms of number of epochs.
@@ -528,42 +417,16 @@ pub fn make_test_cluster<R: Rng>(
#[cfg(test)]
mod tests {
use {
super::*,
solana_gossip::deprecated::{
shuffle_peers_and_index, sorted_retransmit_peers_and_stakes, sorted_stakes_with_index,
},
};
// Legacy methods copied for testing backward compatibility.
fn get_broadcast_peers(
cluster_info: &ClusterInfo,
stakes: Option<&HashMap<Pubkey, u64>>,
) -> (Vec<ContactInfo>, Vec<(u64, usize)>) {
let mut peers = cluster_info.tvu_peers();
let peers_and_stakes = stake_weight_peers(&mut peers, stakes);
(peers, peers_and_stakes)
}
fn stake_weight_peers(
peers: &mut Vec<ContactInfo>,
stakes: Option<&HashMap<Pubkey, u64>>,
) -> Vec<(u64, usize)> {
peers.dedup();
sorted_stakes_with_index(peers, stakes)
}
use super::*;
#[test]
fn test_cluster_nodes_retransmit() {
let mut rng = rand::thread_rng();
let (nodes, stakes, cluster_info) = make_test_cluster(&mut rng, 1_000, None);
let this_node = cluster_info.my_contact_info();
// ClusterInfo::tvu_peers excludes the node itself.
assert_eq!(cluster_info.tvu_peers().len(), nodes.len() - 1);
let cluster_nodes = new_cluster_nodes::<RetransmitStage>(&cluster_info, &stakes);
// All nodes with contact-info should be in the index.
assert_eq!(cluster_nodes.compat_index.len(), nodes.len());
// Staked nodes with no contact-info should be included.
assert!(cluster_nodes.nodes.len() > nodes.len());
// Assert that all nodes keep their contact-info.
@@ -583,56 +446,6 @@ mod tests {
}
}
}
let (peers, stakes_and_index) =
sorted_retransmit_peers_and_stakes(&cluster_info, Some(&stakes));
assert_eq!(stakes_and_index.len(), peers.len());
assert_eq!(cluster_nodes.compat_index.len(), peers.len());
for (i, node) in cluster_nodes
.compat_index
.iter()
.map(|(_, i)| &cluster_nodes.nodes[*i])
.enumerate()
{
let (stake, index) = stakes_and_index[i];
// Wallclock may be update by ClusterInfo::push_self.
if node.pubkey() == this_node.id {
assert_eq!(this_node.id, peers[index].id)
} else {
assert_eq!(node.contact_info().unwrap(), &peers[index]);
}
assert_eq!(node.stake.max(1), stake);
}
let slot_leader = nodes[1..].choose(&mut rng).unwrap().id;
// Remove slot leader from peers indices.
let stakes_and_index: Vec<_> = stakes_and_index
.into_iter()
.filter(|(_stake, index)| peers[*index].id != slot_leader)
.collect();
assert_eq!(peers.len(), stakes_and_index.len() + 1);
let mut shred_seed = [0u8; 32];
rng.fill(&mut shred_seed[..]);
let (self_index, shuffled_peers_and_stakes) =
shuffle_peers_and_index(&this_node.id, &peers, &stakes_and_index, shred_seed);
let shuffled_index: Vec<_> = shuffled_peers_and_stakes
.into_iter()
.map(|(_, index)| index)
.collect();
assert_eq!(this_node.id, peers[shuffled_index[self_index]].id);
for fanout in 1..200 {
let (neighbors_indices, children_indices) =
compute_retransmit_peers(fanout, self_index, &shuffled_index);
let (neighbors, children) =
cluster_nodes.get_retransmit_peers_compat(shred_seed, fanout, slot_leader);
assert_eq!(children.len(), children_indices.len());
for (node, index) in children.into_iter().zip(children_indices) {
assert_eq!(*node.contact_info().unwrap(), peers[index]);
}
assert_eq!(neighbors.len(), neighbors_indices.len());
assert_eq!(neighbors[0].pubkey(), peers[neighbors_indices[0]].id);
for (node, index) in neighbors.into_iter().zip(neighbors_indices).skip(1) {
assert_eq!(*node.contact_info().unwrap(), peers[index]);
}
}
}
#[test]
@@ -644,7 +457,6 @@ mod tests {
let cluster_nodes = ClusterNodes::<BroadcastStage>::new(&cluster_info, &stakes);
// All nodes with contact-info should be in the index.
// Excluding this node itself.
assert_eq!(cluster_nodes.compat_index.len() + 1, nodes.len());
// Staked nodes with no contact-info should be included.
assert!(cluster_nodes.nodes.len() > nodes.len());
// Assert that all nodes keep their contact-info.
@@ -664,25 +476,5 @@ mod tests {
}
}
}
let (peers, peers_and_stakes) = get_broadcast_peers(&cluster_info, Some(&stakes));
assert_eq!(peers_and_stakes.len(), peers.len());
assert_eq!(cluster_nodes.compat_index.len(), peers.len());
for (i, node) in cluster_nodes
.compat_index
.iter()
.map(|(_, i)| &cluster_nodes.nodes[*i])
.enumerate()
{
let (stake, index) = peers_and_stakes[i];
assert_eq!(node.contact_info().unwrap(), &peers[index]);
assert_eq!(node.stake.max(1), stake);
}
for _ in 0..100 {
let mut shred_seed = [0u8; 32];
rng.fill(&mut shred_seed[..]);
let index = weighted_best(&peers_and_stakes, shred_seed);
let peer = cluster_nodes.get_broadcast_peer(shred_seed).unwrap();
assert_eq!(*peer, peers[index]);
}
}
}

View File

@@ -24,7 +24,6 @@ impl LeaderExecuteAndCommitTimings {
saturating_add_assign!(self.record_us, other.record_us);
saturating_add_assign!(self.commit_us, other.commit_us);
saturating_add_assign!(self.find_and_send_votes_us, other.find_and_send_votes_us);
saturating_add_assign!(self.commit_us, other.commit_us);
self.record_transactions_timings
.accumulate(&other.record_transactions_timings);
self.execute_timings.accumulate(&other.execute_timings);

View File

@@ -133,7 +133,7 @@ impl QosService {
let mut num_included = 0;
let select_results = transactions
.zip(transactions_costs)
.map(|(tx, cost)| match cost_tracker.try_add(tx, cost) {
.map(|(tx, cost)| match cost_tracker.try_add(cost) {
Ok(current_block_cost) => {
debug!("slot {:?}, transaction {:?}, cost {:?}, fit into current block, current block cost {}", bank.slot(), tx, cost, current_block_cost);
self.metrics.stats.selected_txs_count.fetch_add(1, Ordering::Relaxed);
@@ -170,6 +170,35 @@ impl QosService {
(select_results, num_included)
}
/// Update the transaction cost in the cost_tracker with the real cost for
/// transactions that were executed successfully;
/// Otherwise remove the cost from the cost tracker, therefore preventing cost_tracker
/// being inflated with unsuccessfully executed transactions.
pub fn update_or_remove_transaction_costs<'a>(
transaction_costs: impl Iterator<Item = &'a TransactionCost>,
transaction_qos_results: impl Iterator<Item = &'a transaction::Result<()>>,
retryable_transaction_indexes: &[usize],
bank: &Arc<Bank>,
) {
let mut cost_tracker = bank.write_cost_tracker().unwrap();
transaction_costs
.zip(transaction_qos_results)
.enumerate()
.for_each(|(index, (tx_cost, qos_inclusion_result))| {
// Only transactions that the qos service incuded have been added to the
// cost tracker.
if qos_inclusion_result.is_ok() && retryable_transaction_indexes.contains(&index) {
cost_tracker.remove(tx_cost);
} else {
// TODO: Update the cost tracker with the actual execution compute units.
// Will have to plumb it in next; For now, keep estimated costs.
//
// let actual_execution_cost = 0;
// cost_tracker.update_execution_cost(tx_cost, actual_execution_cost);
}
});
}
// metrics are reported by bank slot
pub fn report_metrics(&self, bank: Arc<Bank>) {
self.report_sender

View File

@@ -525,85 +525,7 @@ impl RetransmitStage {
#[cfg(test)]
mod tests {
use {
super::*,
solana_gossip::contact_info::ContactInfo,
solana_ledger::{
blockstore_processor::{test_process_blockstore, ProcessOptions},
create_new_tmp_ledger,
genesis_utils::{create_genesis_config, GenesisConfigInfo},
},
solana_net_utils::find_available_port_in_range,
solana_sdk::signature::Keypair,
solana_streamer::socket::SocketAddrSpace,
std::net::{IpAddr, Ipv4Addr},
};
#[test]
fn test_skip_repair() {
solana_logger::setup();
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(123);
let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_config);
let blockstore = Blockstore::open(&ledger_path).unwrap();
let opts = ProcessOptions {
accounts_db_test_hash_calculation: true,
full_leader_cache: true,
..ProcessOptions::default()
};
let (bank_forks, leader_schedule_cache) =
test_process_blockstore(&genesis_config, &blockstore, opts);
let leader_schedule_cache = Arc::new(leader_schedule_cache);
let bank_forks = Arc::new(RwLock::new(bank_forks));
let mut me = ContactInfo::new_localhost(&solana_sdk::pubkey::new_rand(), 0);
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
let port = find_available_port_in_range(ip_addr, (8000, 10000)).unwrap();
let me_retransmit = UdpSocket::bind(format!("127.0.0.1:{}", port)).unwrap();
// need to make sure tvu and tpu are valid addresses
me.tvu_forwards = me_retransmit.local_addr().unwrap();
let port = find_available_port_in_range(ip_addr, (8000, 10000)).unwrap();
me.tvu = UdpSocket::bind(format!("127.0.0.1:{}", port))
.unwrap()
.local_addr()
.unwrap();
// This fixes the order of nodes returned by shuffle_peers_and_index,
// and makes turbine retransmit tree deterministic for the purpose of
// the test.
let other = std::iter::repeat_with(solana_sdk::pubkey::new_rand)
.find(|pk| me.id < *pk)
.unwrap();
let other = ContactInfo::new_localhost(&other, 0);
let cluster_info = ClusterInfo::new(
other,
Arc::new(Keypair::new()),
SocketAddrSpace::Unspecified,
);
cluster_info.insert_info(me);
let retransmit_socket = Arc::new(vec![UdpSocket::bind("0.0.0.0:0").unwrap()]);
let cluster_info = Arc::new(cluster_info);
let (retransmit_sender, retransmit_receiver) = unbounded();
let _retransmit_sender = retransmit_sender.clone();
let _t_retransmit = retransmitter(
retransmit_socket,
bank_forks,
leader_schedule_cache,
cluster_info,
retransmit_receiver,
Arc::default(), // MaxSlots
None,
);
let shred = Shred::new_from_data(0, 0, 0, None, true, true, 0, 0x20, 0);
// it should send this over the sockets.
retransmit_sender.send(vec![shred]).unwrap();
let mut packet_batch = PacketBatch::new(vec![]);
solana_streamer::packet::recv_from(&mut packet_batch, &me_retransmit, 1).unwrap();
assert_eq!(packet_batch.packets.len(), 1);
assert!(!packet_batch.packets[0].meta.repair());
}
use super::*;
#[test]
fn test_already_received() {

View File

@@ -17,7 +17,7 @@ use {
solana_gossip::{
cluster_info::{ClusterInfo, ClusterInfoError},
contact_info::ContactInfo,
weighted_shuffle::{weighted_best, weighted_shuffle},
weighted_shuffle::WeightedShuffle,
},
solana_ledger::{
ancestor_iterator::{AncestorIterator, AncestorIteratorWithHash},
@@ -525,16 +525,17 @@ impl ServeRepair {
if repair_peers.is_empty() {
return Err(ClusterInfoError::NoPeers.into());
}
let weights = cluster_slots.compute_weights_exclude_nonfrozen(slot, &repair_peers);
let mut sampled_validators = weighted_shuffle(
weights.into_iter().map(|(stake, _i)| stake),
solana_sdk::pubkey::new_rand().to_bytes(),
);
sampled_validators.truncate(ANCESTOR_HASH_REPAIR_SAMPLE_SIZE);
Ok(sampled_validators
let (weights, index): (Vec<_>, Vec<_>) = cluster_slots
.compute_weights_exclude_nonfrozen(slot, &repair_peers)
.into_iter()
.unzip();
let peers = WeightedShuffle::new("repair_request_ancestor_hashes", &weights)
.shuffle(&mut rand::thread_rng())
.take(ANCESTOR_HASH_REPAIR_SAMPLE_SIZE)
.map(|i| index[i])
.map(|i| (repair_peers[i].id, repair_peers[i].serve_repair))
.collect())
.collect();
Ok(peers)
}
pub fn repair_request_duplicate_compute_best_peer(
@@ -547,8 +548,12 @@ impl ServeRepair {
if repair_peers.is_empty() {
return Err(ClusterInfoError::NoPeers.into());
}
let weights = cluster_slots.compute_weights_exclude_nonfrozen(slot, &repair_peers);
let n = weighted_best(&weights, solana_sdk::pubkey::new_rand().to_bytes());
let (weights, index): (Vec<_>, Vec<_>) = cluster_slots
.compute_weights_exclude_nonfrozen(slot, &repair_peers)
.into_iter()
.unzip();
let k = WeightedIndex::new(weights)?.sample(&mut rand::thread_rng());
let n = index[k];
Ok((repair_peers[n].id, repair_peers[n].serve_repair))
}

View File

@@ -40,7 +40,8 @@ use {
},
solana_runtime::{
accounts_background_service::{
AbsRequestHandler, AbsRequestSender, AccountsBackgroundService, SnapshotRequestHandler,
AbsRequestHandler, AbsRequestSender, AccountsBackgroundService, DroppedSlotsReceiver,
SnapshotRequestHandler,
},
accounts_db::AccountShrinkThreshold,
bank_forks::BankForks,
@@ -57,7 +58,6 @@ use {
},
solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Keypair},
std::{
boxed::Box,
collections::HashSet,
net::UdpSocket,
sync::{atomic::AtomicBool, Arc, Mutex, RwLock},
@@ -148,6 +148,7 @@ impl Tvu {
last_full_snapshot_slot: Option<Slot>,
block_metadata_notifier: Option<BlockMetadataNotifierLock>,
wait_to_vote_slot: Option<Slot>,
pruned_banks_receiver: DroppedSlotsReceiver,
) -> Self {
let TvuSockets {
repair: repair_socket,
@@ -248,23 +249,6 @@ impl Tvu {
}
};
let (pruned_banks_sender, pruned_banks_receiver) = unbounded();
// Before replay starts, set the callbacks in each of the banks in BankForks
// Note after this callback is created, only the AccountsBackgroundService should be calling
// AccountsDb::purge_slot() to clean up dropped banks.
let callback = bank_forks
.read()
.unwrap()
.root_bank()
.rc
.accounts
.accounts_db
.create_drop_bank_callback(pruned_banks_sender);
for bank in bank_forks.read().unwrap().banks().values() {
bank.set_callback(Some(Box::new(callback.clone())));
}
let accounts_background_request_sender = AbsRequestSender::new(snapshot_request_sender);
let accounts_background_request_handler = AbsRequestHandler {
@@ -465,6 +449,7 @@ pub mod tests {
let tower = Tower::default();
let accounts_package_channel = unbounded();
let max_complete_transaction_status_slot = Arc::new(AtomicU64::default());
let (_pruned_banks_sender, pruned_banks_receiver) = unbounded();
let tvu = Tvu::new(
&vote_keypair.pubkey(),
Arc::new(RwLock::new(vec![Arc::new(vote_keypair)])),
@@ -514,6 +499,7 @@ pub mod tests {
None,
None,
None,
pruned_banks_receiver,
);
exit.store(true, Ordering::Relaxed);
tvu.join().unwrap();

View File

@@ -67,6 +67,7 @@ use {
transaction_status_service::TransactionStatusService,
},
solana_runtime::{
accounts_background_service::DroppedSlotsReceiver,
accounts_db::{AccountShrinkThreshold, AccountsDbConfig},
accounts_index::AccountSecondaryIndexes,
accounts_update_notifier_interface::AccountsUpdateNotifier,
@@ -503,6 +504,7 @@ impl Validator {
},
blockstore_process_options,
blockstore_root_scan,
pruned_banks_receiver,
) = load_blockstore(
config,
ledger_path,
@@ -522,6 +524,7 @@ impl Validator {
config.snapshot_config.as_ref(),
accounts_package_channel.0.clone(),
blockstore_root_scan,
pruned_banks_receiver.clone(),
);
let last_full_snapshot_slot =
last_full_snapshot_slot.or_else(|| starting_snapshot_hashes.map(|x| x.full.hash.0));
@@ -927,6 +930,7 @@ impl Validator {
last_full_snapshot_slot,
block_metadata_notifier,
config.wait_to_vote_slot,
pruned_banks_receiver,
);
let tpu = Tpu::new(
@@ -1261,6 +1265,7 @@ fn load_blockstore(
TransactionHistoryServices,
blockstore_processor::ProcessOptions,
BlockstoreRootScan,
DroppedSlotsReceiver,
) {
info!("loading ledger from {:?}...", ledger_path);
*start_progress.write().unwrap() = ValidatorStartProgress::LoadingLedger;
@@ -1298,6 +1303,7 @@ fn load_blockstore(
BlockstoreOptions {
recovery_mode: config.wal_recovery_mode.clone(),
column_options: config.ledger_column_options.clone(),
enforce_ulimit_nofile: config.enforce_ulimit_nofile,
..BlockstoreOptions::default()
},
)
@@ -1338,19 +1344,23 @@ fn load_blockstore(
TransactionHistoryServices::default()
};
let (mut bank_forks, mut leader_schedule_cache, starting_snapshot_hashes) =
bank_forks_utils::load_bank_forks(
&genesis_config,
&blockstore,
config.account_paths.clone(),
config.account_shrink_paths.clone(),
config.snapshot_config.as_ref(),
&process_options,
transaction_history_services
.cache_block_meta_sender
.as_ref(),
accounts_update_notifier,
);
let (
mut bank_forks,
mut leader_schedule_cache,
starting_snapshot_hashes,
pruned_banks_receiver,
) = bank_forks_utils::load_bank_forks(
&genesis_config,
&blockstore,
config.account_paths.clone(),
config.account_shrink_paths.clone(),
config.snapshot_config.as_ref(),
&process_options,
transaction_history_services
.cache_block_meta_sender
.as_ref(),
accounts_update_notifier,
);
leader_schedule_cache.set_fixed_leader_schedule(config.fixed_leader_schedule.clone());
bank_forks.set_snapshot_config(config.snapshot_config.clone());
@@ -1372,9 +1382,11 @@ fn load_blockstore(
transaction_history_services,
process_options,
blockstore_root_scan,
pruned_banks_receiver,
)
}
#[allow(clippy::too_many_arguments)]
fn process_blockstore(
blockstore: &Blockstore,
bank_forks: &mut BankForks,
@@ -1385,6 +1397,7 @@ fn process_blockstore(
snapshot_config: Option<&SnapshotConfig>,
accounts_package_sender: AccountsPackageSender,
blockstore_root_scan: BlockstoreRootScan,
pruned_banks_receiver: DroppedSlotsReceiver,
) -> Option<Slot> {
let last_full_snapshot_slot = blockstore_processor::process_blockstore_from_root(
blockstore,
@@ -1395,6 +1408,7 @@ fn process_blockstore(
cache_block_meta_sender,
snapshot_config,
accounts_package_sender,
pruned_banks_receiver,
)
.unwrap_or_else(|err| {
error!("Failed to load ledger: {:?}", err);

View File

@@ -1,7 +1,6 @@
use {
crate::tower_storage::{SavedTowerVersions, TowerStorage},
crossbeam_channel::Receiver,
solana_client::connection_cache,
solana_gossip::cluster_info::ClusterInfo,
solana_measure::measure::Measure,
solana_poh::poh_recorder::PohRecorder,
@@ -87,13 +86,7 @@ impl VotingService {
} else {
crate::banking_stage::next_leader_tpu(cluster_info, poh_recorder)
};
let mut measure = Measure::start("vote_tx_send-ms");
let target_address = target_address.unwrap_or_else(|| cluster_info.my_contact_info().tpu);
let wire_vote_tx = bincode::serialize(vote_op.tx()).expect("vote serialization failure");
let _ = connection_cache::send_wire_transaction(&wire_vote_tx, &target_address);
measure.stop();
inc_new_counter_info!("vote_tx_send-ms", measure.as_ms() as usize);
let _ = cluster_info.send_transaction(vote_op.tx(), target_address);
match vote_op {
VoteOp::PushVote {

View File

@@ -179,6 +179,7 @@ module.exports = {
"proposals/block-confirmation",
"proposals/cluster-test-framework",
"proposals/embedding-move",
"proposals/handle-duplicate-block",
"proposals/interchain-transaction-verification",
"proposals/ledger-replication-to-implement",
"proposals/optimistic-confirmation-and-slashing",

View File

@@ -33,6 +33,14 @@ solana airdrop 1 <RECIPIENT_ACCOUNT_ADDRESS> --url https://api.devnet.solana.com
where you replace the text `<RECIPIENT_ACCOUNT_ADDRESS>` with your base58-encoded
public key/wallet address.
A response with the signature of the transaction will be returned. If the balance
of the address does not change by the expected amount, run the following command
for more information on what potentially went wrong:
```bash
solana confirm -v <TRANSACTION_SIGNATURE>
```
#### Check your balance
Confirm the airdrop was successful by checking the account's balance.

View File

@@ -3059,7 +3059,7 @@ curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d '
Result:
```json
{ "jsonrpc": "2.0", "result": { "solana-core": "1.10.4" }, "id": 1 }
{ "jsonrpc": "2.0", "result": { "solana-core": "1.10.9" }, "id": 1 }
```
### getVoteAccounts

View File

@@ -1,3 +1,7 @@
---
title: Handle Duplicate Block
---
# Leader Duplicate Block Slashing
This design describes how the cluster slashes leaders that produce duplicate

View File

@@ -95,7 +95,7 @@ solana-validator ... \
```
Note that once running your validator *will terminate* if it's not able to write
its tower into etcd before submitting a vote transactioin, so it's essential
its tower into etcd before submitting a vote transaction, so it's essential
that your etcd endpoint remain accessible at all times.
### Secondary Validator

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-dos"
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -15,18 +15,18 @@ clap = {version = "3.1.5", features = ["derive", "cargo"]}
log = "0.4.14"
rand = "0.7.0"
serde = "1.0.136"
solana-client = { path = "../client", version = "=1.10.4" }
solana-core = { path = "../core", version = "=1.10.4" }
solana-gossip = { path = "../gossip", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-net-utils = { path = "../net-utils", version = "=1.10.4" }
solana-perf = { path = "../perf", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-streamer = { path = "../streamer", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-client = { path = "../client", version = "=1.10.9" }
solana-core = { path = "../core", version = "=1.10.9" }
solana-gossip = { path = "../gossip", version = "=1.10.9" }
solana-logger = { path = "../logger", version = "=1.10.9" }
solana-net-utils = { path = "../net-utils", version = "=1.10.9" }
solana-perf = { path = "../perf", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-streamer = { path = "../streamer", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dev-dependencies]
solana-local-cluster = { path = "../local-cluster", version = "=1.10.4" }
solana-local-cluster = { path = "../local-cluster", version = "=1.10.9" }

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-download-utils"
version = "1.10.4"
version = "1.10.9"
description = "Solana Download Utils"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -14,8 +14,8 @@ console = "0.15.0"
indicatif = "0.16.2"
log = "0.4.14"
reqwest = { version = "0.11.10", default-features = false, features = ["blocking", "rustls-tls", "json"] }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
[lib]
crate-type = ["lib"]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-entry"
version = "1.10.4"
version = "1.10.9"
description = "Solana Entry"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -18,16 +18,16 @@ log = "0.4.11"
rand = "0.7.0"
rayon = "1.5.1"
serde = "1.0.136"
solana-measure = { path = "../measure", version = "=1.10.4" }
solana-merkle-tree = { path = "../merkle-tree", version = "=1.10.4" }
solana-metrics = { path = "../metrics", version = "=1.10.4" }
solana-perf = { path = "../perf", version = "=1.10.4" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-measure = { path = "../measure", version = "=1.10.9" }
solana-merkle-tree = { path = "../merkle-tree", version = "=1.10.9" }
solana-metrics = { path = "../metrics", version = "=1.10.9" }
solana-perf = { path = "../perf", version = "=1.10.9" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
[dev-dependencies]
matches = "0.1.9"
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.9" }
[lib]
crate-type = ["lib"]

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-faucet"
version = "1.10.4"
version = "1.10.9"
description = "Solana Faucet"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -17,12 +17,12 @@ crossbeam-channel = "0.5"
log = "0.4.14"
serde = "1.0.136"
serde_derive = "1.0.103"
solana-clap-utils = { path = "../clap-utils", version = "=1.10.4" }
solana-cli-config = { path = "../cli-config", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-metrics = { path = "../metrics", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.9" }
solana-cli-config = { path = "../cli-config", version = "=1.10.9" }
solana-logger = { path = "../logger", version = "=1.10.9" }
solana-metrics = { path = "../metrics", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
thiserror = "1.0"
tokio = { version = "1", features = ["full"] }

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-frozen-abi"
version = "1.10.4"
version = "1.10.9"
description = "Solana Frozen ABI"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -18,7 +18,7 @@ serde = "1.0.136"
serde_derive = "1.0.103"
serde_bytes = "0.11"
sha2 = "0.10.2"
solana-frozen-abi-macro = { path = "macro", version = "=1.10.4" }
solana-frozen-abi-macro = { path = "macro", version = "=1.10.9" }
thiserror = "1.0"
[target.'cfg(not(target_arch = "bpf"))'.dependencies]
@@ -27,7 +27,7 @@ im = { version = "15.0.0", features = ["rayon", "serde"] }
memmap2 = "0.5.3"
[target.'cfg(not(target_arch = "bpf"))'.dev-dependencies]
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.9" }
[build-dependencies]
rustc_version = "0.4"

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-frozen-abi-macro"
version = "1.10.4"
version = "1.10.9"
description = "Solana Frozen ABI Macro"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-genesis-utils"
version = "1.10.4"
version = "1.10.9"
description = "Solana Genesis Utils"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -10,9 +10,9 @@ documentation = "https://docs.rs/solana-download-utils"
edition = "2021"
[dependencies]
solana-download-utils = { path = "../download-utils", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-download-utils = { path = "../download-utils", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
[lib]
crate-type = ["lib"]

View File

@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-genesis"
description = "Blockchain, Rebuilt for Scale"
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -15,16 +15,16 @@ clap = "2.33.1"
serde = "1.0.136"
serde_json = "1.0.79"
serde_yaml = "0.8.23"
solana-clap-utils = { path = "../clap-utils", version = "=1.10.4" }
solana-cli-config = { path = "../cli-config", version = "=1.10.4" }
solana-entry = { path = "../entry", version = "=1.10.4" }
solana-ledger = { path = "../ledger", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-stake-program = { path = "../programs/stake", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.4" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.9" }
solana-cli-config = { path = "../cli-config", version = "=1.10.9" }
solana-entry = { path = "../entry", version = "=1.10.9" }
solana-ledger = { path = "../ledger", version = "=1.10.9" }
solana-logger = { path = "../logger", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-stake-program = { path = "../programs/stake", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.9" }
tempfile = "3.3.0"
[[bin]]

View File

@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-geyser-plugin-interface"
description = "The Solana Geyser plugin interface."
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -11,8 +11,8 @@ documentation = "https://docs.rs/solana-geyser-plugin-interface"
[dependencies]
log = "0.4.11"
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.9" }
thiserror = "1.0.30"
[package.metadata.docs.rs]

View File

@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-geyser-plugin-manager"
description = "The Solana Geyser plugin manager."
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -16,13 +16,13 @@ json5 = "0.4.1"
libloading = "0.7.3"
log = "0.4.11"
serde_json = "1.0.79"
solana-geyser-plugin-interface = { path = "../geyser-plugin-interface", version = "=1.10.4" }
solana-measure = { path = "../measure", version = "=1.10.4" }
solana-metrics = { path = "../metrics", version = "=1.10.4" }
solana-rpc = { path = "../rpc", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.4" }
solana-geyser-plugin-interface = { path = "../geyser-plugin-interface", version = "=1.10.9" }
solana-measure = { path = "../measure", version = "=1.10.9" }
solana-metrics = { path = "../metrics", version = "=1.10.9" }
solana-rpc = { path = "../rpc", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.9" }
thiserror = "1.0.30"
[package.metadata.docs.rs]

View File

@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-gossip"
description = "Blockchain, Rebuilt for Scale"
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -18,7 +18,7 @@ flate2 = "1.0"
indexmap = { version = "1.8", features = ["rayon"] }
itertools = "0.10.3"
log = "0.4.14"
lru = "0.7.3"
lru = "0.7.5"
matches = "0.1.9"
num-traits = "0.2"
rand = "0.7.0"
@@ -27,24 +27,24 @@ rayon = "1.5.1"
serde = "1.0.136"
serde_bytes = "0.11"
serde_derive = "1.0.103"
solana-bloom = { path = "../bloom", version = "=1.10.4" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.4" }
solana-client = { path = "../client", version = "=1.10.4" }
solana-entry = { path = "../entry", version = "=1.10.4" }
solana-frozen-abi = { path = "../frozen-abi", version = "=1.10.4" }
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.10.4" }
solana-ledger = { path = "../ledger", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-measure = { path = "../measure", version = "=1.10.4" }
solana-metrics = { path = "../metrics", version = "=1.10.4" }
solana-net-utils = { path = "../net-utils", version = "=1.10.4" }
solana-perf = { path = "../perf", version = "=1.10.4" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-streamer = { path = "../streamer", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.4" }
solana-bloom = { path = "../bloom", version = "=1.10.9" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.9" }
solana-client = { path = "../client", version = "=1.10.9" }
solana-entry = { path = "../entry", version = "=1.10.9" }
solana-frozen-abi = { path = "../frozen-abi", version = "=1.10.9" }
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.10.9" }
solana-ledger = { path = "../ledger", version = "=1.10.9" }
solana-logger = { path = "../logger", version = "=1.10.9" }
solana-measure = { path = "../measure", version = "=1.10.9" }
solana-metrics = { path = "../metrics", version = "=1.10.9" }
solana-net-utils = { path = "../net-utils", version = "=1.10.9" }
solana-perf = { path = "../perf", version = "=1.10.9" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-streamer = { path = "../streamer", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.9" }
thiserror = "1.0"
[dev-dependencies]

View File

@@ -5,7 +5,7 @@ extern crate test;
use {
rand::{Rng, SeedableRng},
rand_chacha::ChaChaRng,
solana_gossip::weighted_shuffle::{weighted_shuffle, WeightedShuffle},
solana_gossip::weighted_shuffle::WeightedShuffle,
std::iter::repeat_with,
test::Bencher,
};
@@ -15,25 +15,13 @@ fn make_weights<R: Rng>(rng: &mut R) -> Vec<u64> {
}
#[bench]
fn bench_weighted_shuffle_old(bencher: &mut Bencher) {
fn bench_weighted_shuffle(bencher: &mut Bencher) {
let mut seed = [0u8; 32];
let mut rng = rand::thread_rng();
let weights = make_weights(&mut rng);
bencher.iter(|| {
rng.fill(&mut seed[..]);
weighted_shuffle::<u64, &u64, std::slice::Iter<'_, u64>>(weights.iter(), seed);
});
}
#[bench]
fn bench_weighted_shuffle_new(bencher: &mut Bencher) {
let mut seed = [0u8; 32];
let mut rng = rand::thread_rng();
let weights = make_weights(&mut rng);
bencher.iter(|| {
rng.fill(&mut seed[..]);
let shuffle = WeightedShuffle::new(&weights).unwrap();
shuffle
WeightedShuffle::new("", &weights)
.shuffle(&mut ChaChaRng::from_seed(seed))
.collect::<Vec<_>>()
});

View File

@@ -12,6 +12,13 @@
//! * layer 2 - Everyone else, if layer 1 is `2^10`, layer 2 should be able to fit `2^20` number of nodes.
//!
//! Bank needs to provide an interface for us to query the stake weight
#[deprecated(
since = "1.10.6",
note = "Please use `solana_net_utils::{MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, VALIDATOR_PORT_RANGE}` instead"
)]
#[allow(deprecated)]
pub use solana_net_utils::{MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, VALIDATOR_PORT_RANGE};
use {
crate::{
cluster_info_metrics::{
@@ -92,9 +99,6 @@ use {
},
};
pub const VALIDATOR_PORT_RANGE: PortRange = (8000, 10_000);
pub const MINIMUM_VALIDATOR_PORT_RANGE_WIDTH: u16 = 12; // VALIDATOR_PORT_RANGE must be at least this wide
/// The Data plane fanout size, also used as the neighborhood size
pub const DATA_PLANE_FANOUT: usize = 200;
/// milliseconds we sleep for between gossip requests
@@ -635,6 +639,10 @@ impl ClusterInfo {
self.my_contact_info.write().unwrap().id = id;
self.insert_self();
self.push_message(CrdsValue::new_signed(
CrdsData::Version(Version::new(self.id())),
&self.keypair(),
));
self.push_self(&HashMap::new(), None);
}
@@ -2011,7 +2019,7 @@ impl ClusterInfo {
return packet_batch;
}
let mut rng = rand::thread_rng();
let shuffle = WeightedShuffle::new(&scores).unwrap().shuffle(&mut rng);
let shuffle = WeightedShuffle::new("handle-pull-requests", &scores).shuffle(&mut rng);
let mut total_bytes = 0;
let mut sent = 0;
for (addr, response) in shuffle.map(|i| &responses[i]) {
@@ -3071,6 +3079,7 @@ mod tests {
rand::{seq::SliceRandom, SeedableRng},
rand_chacha::ChaChaRng,
solana_ledger::shred::Shredder,
solana_net_utils::MINIMUM_VALIDATOR_PORT_RANGE_WIDTH,
solana_sdk::signature::{Keypair, Signer},
solana_vote_program::{vote_instruction, vote_state::Vote},
std::{

View File

@@ -449,46 +449,28 @@ pub(crate) fn submit_gossip_stats(
i64
),
);
let counts: Vec<_> = crds_stats
.pull
.counts
.iter()
.zip(crds_stats.push.counts.iter())
.map(|(a, b)| a + b)
.collect();
datapoint_info!(
"cluster_info_crds_stats",
("ContactInfo", counts[0], i64),
("ContactInfo-push", crds_stats.push.counts[0], i64),
("ContactInfo-pull", crds_stats.pull.counts[0], i64),
("Vote", counts[1], i64),
("Vote-push", crds_stats.push.counts[1], i64),
("Vote-pull", crds_stats.pull.counts[1], i64),
("LowestSlot", counts[2], i64),
("LowestSlot-push", crds_stats.push.counts[2], i64),
("LowestSlot-pull", crds_stats.pull.counts[2], i64),
("SnapshotHashes", counts[3], i64),
("SnapshotHashes-push", crds_stats.push.counts[3], i64),
("SnapshotHashes-pull", crds_stats.pull.counts[3], i64),
("AccountsHashes", counts[4], i64),
("AccountsHashes-push", crds_stats.push.counts[4], i64),
("AccountsHashes-pull", crds_stats.pull.counts[4], i64),
("EpochSlots", counts[5], i64),
("EpochSlots-push", crds_stats.push.counts[5], i64),
("EpochSlots-pull", crds_stats.pull.counts[5], i64),
("LegacyVersion", counts[6], i64),
("LegacyVersion-push", crds_stats.push.counts[6], i64),
("LegacyVersion-pull", crds_stats.pull.counts[6], i64),
("Version", counts[7], i64),
("Version-push", crds_stats.push.counts[7], i64),
("Version-pull", crds_stats.pull.counts[7], i64),
("NodeInstance", counts[8], i64),
("NodeInstance-push", crds_stats.push.counts[8], i64),
("NodeInstance-pull", crds_stats.pull.counts[8], i64),
("DuplicateShred", counts[9], i64),
("DuplicateShred-push", crds_stats.push.counts[9], i64),
("DuplicateShred-pull", crds_stats.pull.counts[9], i64),
("IncrementalSnapshotHashes", counts[10], i64),
(
"IncrementalSnapshotHashes-push",
crds_stats.push.counts[10],
@@ -499,7 +481,6 @@ pub(crate) fn submit_gossip_stats(
crds_stats.pull.counts[10],
i64
),
("all", counts.iter().sum::<usize>(), i64),
(
"all-push",
crds_stats.push.counts.iter().sum::<usize>(),
@@ -511,46 +492,28 @@ pub(crate) fn submit_gossip_stats(
i64
),
);
let fails: Vec<_> = crds_stats
.pull
.fails
.iter()
.zip(crds_stats.push.fails.iter())
.map(|(a, b)| a + b)
.collect();
datapoint_info!(
"cluster_info_crds_stats_fails",
("ContactInfo", fails[0], i64),
("ContactInfo-push", crds_stats.push.fails[0], i64),
("ContactInfo-pull", crds_stats.pull.fails[0], i64),
("Vote", fails[1], i64),
("Vote-push", crds_stats.push.fails[1], i64),
("Vote-pull", crds_stats.pull.fails[1], i64),
("LowestSlot", fails[2], i64),
("LowestSlot-push", crds_stats.push.fails[2], i64),
("LowestSlot-pull", crds_stats.pull.fails[2], i64),
("SnapshotHashes", fails[3], i64),
("SnapshotHashes-push", crds_stats.push.fails[3], i64),
("SnapshotHashes-pull", crds_stats.pull.fails[3], i64),
("AccountsHashes", fails[4], i64),
("AccountsHashes-push", crds_stats.push.fails[4], i64),
("AccountsHashes-pull", crds_stats.pull.fails[4], i64),
("EpochSlots", fails[5], i64),
("EpochSlots-push", crds_stats.push.fails[5], i64),
("EpochSlots-pull", crds_stats.pull.fails[5], i64),
("LegacyVersion", fails[6], i64),
("LegacyVersion-push", crds_stats.push.fails[6], i64),
("LegacyVersion-pull", crds_stats.pull.fails[6], i64),
("Version", fails[7], i64),
("Version-push", crds_stats.push.fails[7], i64),
("Version-pull", crds_stats.pull.fails[7], i64),
("NodeInstance", fails[8], i64),
("NodeInstance-push", crds_stats.push.fails[8], i64),
("NodeInstance-pull", crds_stats.pull.fails[8], i64),
("DuplicateShred", fails[9], i64),
("DuplicateShred-push", crds_stats.push.fails[9], i64),
("DuplicateShred-pull", crds_stats.pull.fails[9], i64),
("IncrementalSnapshotHashes", fails[10], i64),
(
"IncrementalSnapshotHashes-push",
crds_stats.push.fails[10],
@@ -561,24 +524,19 @@ pub(crate) fn submit_gossip_stats(
crds_stats.pull.fails[10],
i64
),
("all", fails.iter().sum::<usize>(), i64),
("all-push", crds_stats.push.fails.iter().sum::<usize>(), i64),
("all-pull", crds_stats.pull.fails.iter().sum::<usize>(), i64),
);
if !log::log_enabled!(log::Level::Trace) {
return;
}
submit_vote_stats("cluster_info_crds_stats_votes_pull", &crds_stats.pull.votes);
submit_vote_stats("cluster_info_crds_stats_votes_push", &crds_stats.push.votes);
let votes: HashMap<Slot, usize> = crds_stats
.pull
.votes
.into_iter()
.map(|(slot, num_votes)| (*slot, *num_votes))
.chain(
crds_stats
.push
.votes
.into_iter()
.map(|(slot, num_votes)| (*slot, *num_votes)),
)
.chain(crds_stats.push.votes.into_iter())
.into_grouping_map()
.aggregate(|acc, _slot, num_votes| Some(acc.unwrap_or_default() + num_votes));
submit_vote_stats("cluster_info_crds_stats_votes", &votes);
@@ -589,12 +547,12 @@ where
I: IntoIterator<Item = (&'a Slot, /*num-votes:*/ &'a usize)>,
{
// Submit vote stats only for the top most voted slots.
const NUM_SLOTS: usize = 20;
const NUM_SLOTS: usize = 10;
let mut votes: Vec<_> = votes.into_iter().map(|(k, v)| (*k, *v)).collect();
if votes.len() > NUM_SLOTS {
votes.select_nth_unstable_by_key(NUM_SLOTS, |(_, num)| Reverse(*num));
}
for (slot, num_votes) in votes.into_iter().take(NUM_SLOTS) {
datapoint_info!(name, ("slot", slot, i64), ("num_votes", num_votes, i64),);
datapoint_trace!(name, ("slot", slot, i64), ("num_votes", num_votes, i64));
}
}

View File

@@ -246,8 +246,7 @@ impl CrdsGossipPull {
return Err(CrdsGossipError::NoPeers);
}
let mut rng = rand::thread_rng();
let mut peers = WeightedShuffle::new(&weights)
.unwrap()
let mut peers = WeightedShuffle::new("pull-options", &weights)
.shuffle(&mut rng)
.map(|i| peers[i]);
let peer = {

View File

@@ -169,8 +169,7 @@ impl CrdsGossipPush {
.filter(|(_, stake)| *stake > 0)
.collect();
let weights: Vec<_> = peers.iter().map(|(_, stake)| *stake).collect();
WeightedShuffle::new(&weights)
.unwrap()
WeightedShuffle::new("prune-received-cache", &weights)
.shuffle(&mut rng)
.map(move |i| peers[i])
};
@@ -370,7 +369,7 @@ impl CrdsGossipPush {
return;
}
let num_bloom_items = MIN_NUM_BLOOM_ITEMS.max(network_size);
let shuffle = WeightedShuffle::new(&weights).unwrap().shuffle(&mut rng);
let shuffle = WeightedShuffle::new("push-options", &weights).shuffle(&mut rng);
let mut active_set = self.active_set.write().unwrap();
let need = Self::compute_need(self.num_active, active_set.len(), ratio);
for peer in shuffle.map(|i| peers[i]) {

View File

@@ -1,11 +1,4 @@
use {
crate::{
cluster_info::ClusterInfo, contact_info::ContactInfo, weighted_shuffle::weighted_shuffle,
},
itertools::Itertools,
solana_sdk::{clock::Slot, pubkey::Pubkey},
std::collections::HashMap,
};
use solana_sdk::clock::Slot;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, AbiExample, AbiEnumVisitor)]
enum CompressionType {
@@ -26,74 +19,3 @@ pub(crate) struct EpochIncompleteSlots {
compression: CompressionType,
compressed_list: Vec<u8>,
}
// Legacy methods copied for testing backward compatibility.
pub fn sorted_retransmit_peers_and_stakes(
cluster_info: &ClusterInfo,
stakes: Option<&HashMap<Pubkey, u64>>,
) -> (Vec<ContactInfo>, Vec<(u64, usize)>) {
let mut peers = cluster_info.tvu_peers();
// insert "self" into this list for the layer and neighborhood computation
peers.push(cluster_info.my_contact_info());
let stakes_and_index = sorted_stakes_with_index(&peers, stakes);
(peers, stakes_and_index)
}
pub fn sorted_stakes_with_index(
peers: &[ContactInfo],
stakes: Option<&HashMap<Pubkey, u64>>,
) -> Vec<(u64, usize)> {
let stakes_and_index: Vec<_> = peers
.iter()
.enumerate()
.map(|(i, c)| {
// For stake weighted shuffle a valid weight is atleast 1. Weight 0 is
// assumed to be missing entry. So let's make sure stake weights are atleast 1
let stake = 1.max(
stakes
.as_ref()
.map_or(1, |stakes| *stakes.get(&c.id).unwrap_or(&1)),
);
(stake, i)
})
.sorted_by(|(l_stake, l_info), (r_stake, r_info)| {
if r_stake == l_stake {
peers[*r_info].id.cmp(&peers[*l_info].id)
} else {
r_stake.cmp(l_stake)
}
})
.collect();
stakes_and_index
}
pub fn shuffle_peers_and_index(
id: &Pubkey,
peers: &[ContactInfo],
stakes_and_index: &[(u64, usize)],
seed: [u8; 32],
) -> (usize, Vec<(u64, usize)>) {
let shuffled_stakes_and_index = stake_weighted_shuffle(stakes_and_index, seed);
let self_index = shuffled_stakes_and_index
.iter()
.enumerate()
.find_map(|(i, (_stake, index))| {
if peers[*index].id == *id {
Some(i)
} else {
None
}
})
.unwrap();
(self_index, shuffled_stakes_and_index)
}
fn stake_weighted_shuffle(stakes_and_index: &[(u64, usize)], seed: [u8; 32]) -> Vec<(u64, usize)> {
let stake_weights = stakes_and_index.iter().map(|(w, _)| *w);
let shuffle = weighted_shuffle(stake_weights, seed);
shuffle.iter().map(|x| stakes_and_index[*x]).collect()
}

View File

@@ -1,16 +1,10 @@
//! The `gossip_service` module implements the network control plane.
use {
crate::{
cluster_info::{ClusterInfo, VALIDATOR_PORT_RANGE},
contact_info::ContactInfo,
},
crate::{cluster_info::ClusterInfo, contact_info::ContactInfo},
crossbeam_channel::{unbounded, Sender},
rand::{thread_rng, Rng},
solana_client::{
thin_client::{create_client, ThinClient},
udp_client::UdpTpuConnection,
},
solana_client::thin_client::{create_client, ThinClient},
solana_perf::recycler::Recycler,
solana_runtime::bank_forks::BankForks,
solana_sdk::{
@@ -20,7 +14,7 @@ use {
solana_streamer::{socket::SocketAddrSpace, streamer},
std::{
collections::HashSet,
net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, UdpSocket},
net::{SocketAddr, TcpListener, UdpSocket},
sync::{
atomic::{AtomicBool, Ordering},
Arc, RwLock,
@@ -197,51 +191,37 @@ pub fn discover(
}
/// Creates a ThinClient per valid node
pub fn get_clients(
nodes: &[ContactInfo],
socket_addr_space: &SocketAddrSpace,
) -> Vec<ThinClient<UdpTpuConnection>> {
pub fn get_clients(nodes: &[ContactInfo], socket_addr_space: &SocketAddrSpace) -> Vec<ThinClient> {
nodes
.iter()
.filter_map(|node| ContactInfo::valid_client_facing_addr(node, socket_addr_space))
.map(|addrs| create_client(addrs, VALIDATOR_PORT_RANGE))
.map(create_client)
.collect()
}
/// Creates a ThinClient by selecting a valid node at random
pub fn get_client(
nodes: &[ContactInfo],
socket_addr_space: &SocketAddrSpace,
) -> ThinClient<UdpTpuConnection> {
pub fn get_client(nodes: &[ContactInfo], socket_addr_space: &SocketAddrSpace) -> ThinClient {
let nodes: Vec<_> = nodes
.iter()
.filter_map(|node| ContactInfo::valid_client_facing_addr(node, socket_addr_space))
.collect();
let select = thread_rng().gen_range(0, nodes.len());
create_client(nodes[select], VALIDATOR_PORT_RANGE)
create_client(nodes[select])
}
pub fn get_multi_client(
nodes: &[ContactInfo],
socket_addr_space: &SocketAddrSpace,
) -> (ThinClient<UdpTpuConnection>, usize) {
) -> (ThinClient, usize) {
let addrs: Vec<_> = nodes
.iter()
.filter_map(|node| ContactInfo::valid_client_facing_addr(node, socket_addr_space))
.collect();
let rpc_addrs: Vec<_> = addrs.iter().map(|addr| addr.0).collect();
let tpu_addrs: Vec<_> = addrs.iter().map(|addr| addr.1).collect();
let (_, transactions_socket) = solana_net_utils::bind_in_range(
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
VALIDATOR_PORT_RANGE,
)
.unwrap();
let num_nodes = tpu_addrs.len();
(
//TODO: make it configurable whether to use quic
ThinClient::<UdpTpuConnection>::new_from_addrs(rpc_addrs, tpu_addrs, transactions_socket),
num_nodes,
)
(ThinClient::new_from_addrs(rpc_addrs, tpu_addrs), num_nodes)
}
fn spy(

View File

@@ -13,7 +13,7 @@ pub mod crds_gossip_pull;
pub mod crds_gossip_push;
pub mod crds_shards;
pub mod crds_value;
pub mod deprecated;
mod deprecated;
pub mod duplicate_shred;
pub mod epoch_slots;
pub mod gossip_error;

View File

@@ -1,26 +1,14 @@
//! The `weighted_shuffle` module provides an iterator over shuffled weights.
use {
itertools::Itertools,
num_traits::{CheckedAdd, FromPrimitive, ToPrimitive},
num_traits::CheckedAdd,
rand::{
distributions::uniform::{SampleUniform, UniformSampler},
Rng, SeedableRng,
},
rand_chacha::ChaChaRng,
std::{
borrow::Borrow,
iter,
ops::{AddAssign, Div, Sub, SubAssign},
Rng,
},
std::ops::{AddAssign, Sub, SubAssign},
};
#[derive(Debug)]
pub enum WeightedShuffleError<T> {
NegativeWeight(T),
SumOverflow,
}
/// Implements an iterator where indices are shuffled according to their
/// weights:
/// - Returned indices are unique in the range [0, weights.len()).
@@ -43,34 +31,48 @@ impl<T> WeightedShuffle<T>
where
T: Copy + Default + PartialOrd + AddAssign + CheckedAdd,
{
/// Returns error if:
/// - any of the weights are negative.
/// - sum of weights overflows.
pub fn new(weights: &[T]) -> Result<Self, WeightedShuffleError<T>> {
/// If weights are negative or overflow the total sum
/// they are treated as zero.
pub fn new(name: &'static str, weights: &[T]) -> Self {
let size = weights.len() + 1;
let zero = <T as Default>::default();
let mut arr = vec![zero; size];
let mut sum = zero;
let mut zeros = Vec::default();
let mut num_negative = 0;
let mut num_overflow = 0;
for (mut k, &weight) in (1usize..).zip(weights) {
#[allow(clippy::neg_cmp_op_on_partial_ord)]
// weight < zero does not work for NaNs.
if !(weight >= zero) {
return Err(WeightedShuffleError::NegativeWeight(weight));
zeros.push(k - 1);
num_negative += 1;
continue;
}
if weight == zero {
zeros.push(k - 1);
continue;
}
sum = sum
.checked_add(&weight)
.ok_or(WeightedShuffleError::SumOverflow)?;
sum = match sum.checked_add(&weight) {
Some(val) => val,
None => {
zeros.push(k - 1);
num_overflow += 1;
continue;
}
};
while k < size {
arr[k] += weight;
k += k & k.wrapping_neg();
}
}
Ok(Self { arr, sum, zeros })
if num_negative > 0 {
datapoint_error!("weighted-shuffle-negative", (name, num_negative, i64));
}
if num_overflow > 0 {
datapoint_error!("weighted-shuffle-overflow", (name, num_overflow, i64));
}
Self { arr, sum, zeros }
}
}
@@ -174,68 +176,12 @@ where
}
}
/// Returns a list of indexes shuffled based on the input weights
/// Note - The sum of all weights must not exceed `u64::MAX`
pub fn weighted_shuffle<T, B, F>(weights: F, seed: [u8; 32]) -> Vec<usize>
where
T: Copy + PartialOrd + iter::Sum + Div<T, Output = T> + FromPrimitive + ToPrimitive,
B: Borrow<T>,
F: Iterator<Item = B> + Clone,
{
let total_weight: T = weights.clone().map(|x| *x.borrow()).sum();
let mut rng = ChaChaRng::from_seed(seed);
weights
.enumerate()
.map(|(i, weight)| {
let weight = weight.borrow();
// This generates an "inverse" weight but it avoids floating point math
let x = (total_weight / *weight)
.to_u64()
.expect("values > u64::max are not supported");
(
i,
// capture the u64 into u128s to prevent overflow
rng.gen_range(1, u128::from(std::u16::MAX)) * u128::from(x),
)
})
// sort in ascending order
.sorted_by(|(_, l_val), (_, r_val)| l_val.cmp(r_val))
.map(|x| x.0)
.collect()
}
/// Returns the highest index after computing a weighted shuffle.
/// Saves doing any sorting for O(n) max calculation.
// TODO: Remove in favor of rand::distributions::WeightedIndex.
pub fn weighted_best(weights_and_indexes: &[(u64, usize)], seed: [u8; 32]) -> usize {
if weights_and_indexes.is_empty() {
return 0;
}
let mut rng = ChaChaRng::from_seed(seed);
let total_weight: u64 = weights_and_indexes.iter().map(|x| x.0).sum();
let mut lowest_weight = std::u128::MAX;
let mut best_index = 0;
for v in weights_and_indexes {
// This generates an "inverse" weight but it avoids floating point math
let x = (total_weight / v.0)
.to_u64()
.expect("values > u64::max are not supported");
// capture the u64 into u128s to prevent overflow
let computed_weight = rng.gen_range(1, u128::from(std::u16::MAX)) * u128::from(x);
// The highest input weight maps to the lowest computed weight
if computed_weight < lowest_weight {
lowest_weight = computed_weight;
best_index = v.1;
}
}
best_index
}
#[cfg(test)]
mod tests {
use {
super::*,
rand::SeedableRng,
rand_chacha::ChaChaRng,
std::{convert::TryInto, iter::repeat_with},
};
@@ -272,78 +218,12 @@ mod tests {
shuffle
}
#[test]
fn test_weighted_shuffle_iterator() {
let mut test_set = [0; 6];
let mut count = 0;
let shuffle = weighted_shuffle(vec![50, 10, 2, 1, 1, 1].into_iter(), [0x5a; 32]);
shuffle.into_iter().for_each(|x| {
assert_eq!(test_set[x], 0);
test_set[x] = 1;
count += 1;
});
assert_eq!(count, 6);
}
#[test]
fn test_weighted_shuffle_iterator_large() {
let mut test_set = [0; 100];
let mut test_weights = vec![0; 100];
(0..100).for_each(|i| test_weights[i] = (i + 1) as u64);
let mut count = 0;
let shuffle = weighted_shuffle(test_weights.into_iter(), [0xa5; 32]);
shuffle.into_iter().for_each(|x| {
assert_eq!(test_set[x], 0);
test_set[x] = 1;
count += 1;
});
assert_eq!(count, 100);
}
#[test]
fn test_weighted_shuffle_compare() {
let shuffle = weighted_shuffle(vec![50, 10, 2, 1, 1, 1].into_iter(), [0x5a; 32]);
let shuffle1 = weighted_shuffle(vec![50, 10, 2, 1, 1, 1].into_iter(), [0x5a; 32]);
shuffle1
.into_iter()
.zip(shuffle.into_iter())
.for_each(|(x, y)| {
assert_eq!(x, y);
});
}
#[test]
fn test_weighted_shuffle_imbalanced() {
let mut weights = vec![std::u32::MAX as u64; 3];
weights.push(1);
let shuffle = weighted_shuffle(weights.iter().cloned(), [0x5a; 32]);
shuffle.into_iter().for_each(|x| {
if x == weights.len() - 1 {
assert_eq!(weights[x], 1);
} else {
assert_eq!(weights[x], std::u32::MAX as u64);
}
});
}
#[test]
fn test_weighted_best() {
let weights_and_indexes: Vec<_> = vec![100u64, 1000, 10_000, 10]
.into_iter()
.enumerate()
.map(|(i, weight)| (weight, i))
.collect();
let best_index = weighted_best(&weights_and_indexes, [0x5b; 32]);
assert_eq!(best_index, 2);
}
// Asserts that empty weights will return empty shuffle.
#[test]
fn test_weighted_shuffle_empty_weights() {
let weights = Vec::<u64>::new();
let mut rng = rand::thread_rng();
let shuffle = WeightedShuffle::new(&weights).unwrap();
let shuffle = WeightedShuffle::new("", &weights);
assert!(shuffle.clone().shuffle(&mut rng).next().is_none());
assert!(shuffle.first(&mut rng).is_none());
}
@@ -354,7 +234,7 @@ mod tests {
let weights = vec![0u64; 5];
let seed = [37u8; 32];
let mut rng = ChaChaRng::from_seed(seed);
let shuffle = WeightedShuffle::new(&weights).unwrap();
let shuffle = WeightedShuffle::new("", &weights);
assert_eq!(
shuffle.clone().shuffle(&mut rng).collect::<Vec<_>>(),
[1, 4, 2, 3, 0]
@@ -372,14 +252,14 @@ mod tests {
let weights = [1, 0, 1000, 0, 0, 10, 100, 0];
let mut counts = [0; 8];
for _ in 0..100000 {
let mut shuffle = WeightedShuffle::new(&weights).unwrap().shuffle(&mut rng);
let mut shuffle = WeightedShuffle::new("", &weights).shuffle(&mut rng);
counts[shuffle.next().unwrap()] += 1;
let _ = shuffle.count(); // consume the rest.
}
assert_eq!(counts, [95, 0, 90069, 0, 0, 908, 8928, 0]);
let mut counts = [0; 8];
for _ in 0..100000 {
let mut shuffle = WeightedShuffle::new(&weights).unwrap();
let mut shuffle = WeightedShuffle::new("", &weights);
shuffle.remove_index(5);
shuffle.remove_index(3);
shuffle.remove_index(1);
@@ -390,6 +270,26 @@ mod tests {
assert_eq!(counts, [97, 0, 90862, 0, 0, 0, 9041, 0]);
}
#[test]
fn test_weighted_shuffle_negative_overflow() {
const SEED: [u8; 32] = [48u8; 32];
let weights = [19i64, 23, 7, 0, 0, 23, 3, 0, 5, 0, 19, 29];
let mut rng = ChaChaRng::from_seed(SEED);
let shuffle = WeightedShuffle::new("", &weights);
assert_eq!(
shuffle.shuffle(&mut rng).collect::<Vec<_>>(),
[8, 1, 5, 10, 11, 0, 2, 6, 9, 4, 3, 7]
);
// Negative weights and overflowing ones are treated as zero.
let weights = [19, 23, 7, -57, i64::MAX, 23, 3, i64::MAX, 5, -79, 19, 29];
let mut rng = ChaChaRng::from_seed(SEED);
let shuffle = WeightedShuffle::new("", &weights);
assert_eq!(
shuffle.shuffle(&mut rng).collect::<Vec<_>>(),
[8, 1, 5, 10, 11, 0, 2, 6, 9, 4, 3, 7]
);
}
#[test]
fn test_weighted_shuffle_hard_coded() {
let weights = [
@@ -397,7 +297,7 @@ mod tests {
];
let seed = [48u8; 32];
let mut rng = ChaChaRng::from_seed(seed);
let mut shuffle = WeightedShuffle::new(&weights).unwrap();
let mut shuffle = WeightedShuffle::new("", &weights);
assert_eq!(
shuffle.clone().shuffle(&mut rng).collect::<Vec<_>>(),
[2, 12, 18, 0, 14, 15, 17, 10, 1, 9, 7, 6, 13, 20, 4, 19, 3, 8, 11, 16, 5]
@@ -417,7 +317,7 @@ mod tests {
assert_eq!(shuffle.first(&mut rng), Some(4));
let seed = [37u8; 32];
let mut rng = ChaChaRng::from_seed(seed);
let mut shuffle = WeightedShuffle::new(&weights).unwrap();
let mut shuffle = WeightedShuffle::new("", &weights);
assert_eq!(
shuffle.clone().shuffle(&mut rng).collect::<Vec<_>>(),
[19, 3, 15, 14, 6, 10, 17, 18, 9, 2, 4, 1, 0, 7, 8, 20, 12, 13, 16, 5, 11]
@@ -447,13 +347,13 @@ mod tests {
let mut seed = [0u8; 32];
rng.fill(&mut seed[..]);
let mut rng = ChaChaRng::from_seed(seed);
let shuffle = WeightedShuffle::new(&weights).unwrap();
let shuffle = WeightedShuffle::new("", &weights);
let shuffle: Vec<_> = shuffle.shuffle(&mut rng).collect();
let mut rng = ChaChaRng::from_seed(seed);
let shuffle_slow = weighted_shuffle_slow(&mut rng, weights.clone());
assert_eq!(shuffle, shuffle_slow);
let mut rng = ChaChaRng::from_seed(seed);
let shuffle = WeightedShuffle::new(&weights).unwrap();
let shuffle = WeightedShuffle::new("", &weights);
assert_eq!(shuffle.first(&mut rng), Some(shuffle_slow[0]));
}
}

View File

@@ -1,12 +1,15 @@
#![allow(clippy::integer_arithmetic)]
use {
crossbeam_channel::{unbounded, Receiver, Sender, TryRecvError},
itertools::Itertools,
rand::SeedableRng,
rand_chacha::ChaChaRng,
rayon::{iter::ParallelIterator, prelude::*},
serial_test::serial,
solana_gossip::{
cluster_info::{compute_retransmit_peers, ClusterInfo},
contact_info::ContactInfo,
deprecated::{shuffle_peers_and_index, sorted_retransmit_peers_and_stakes},
weighted_shuffle::WeightedShuffle,
},
solana_sdk::{pubkey::Pubkey, signer::keypair::Keypair},
solana_streamer::socket::SocketAddrSpace,
@@ -32,6 +35,77 @@ fn find_insert_shred(id: &Pubkey, shred: i32, batches: &mut [Nodes]) {
});
}
fn sorted_retransmit_peers_and_stakes(
cluster_info: &ClusterInfo,
stakes: Option<&HashMap<Pubkey, u64>>,
) -> (Vec<ContactInfo>, Vec<(u64, usize)>) {
let mut peers = cluster_info.tvu_peers();
// insert "self" into this list for the layer and neighborhood computation
peers.push(cluster_info.my_contact_info());
let stakes_and_index = sorted_stakes_with_index(&peers, stakes);
(peers, stakes_and_index)
}
fn sorted_stakes_with_index(
peers: &[ContactInfo],
stakes: Option<&HashMap<Pubkey, u64>>,
) -> Vec<(u64, usize)> {
let stakes_and_index: Vec<_> = peers
.iter()
.enumerate()
.map(|(i, c)| {
// For stake weighted shuffle a valid weight is atleast 1. Weight 0 is
// assumed to be missing entry. So let's make sure stake weights are atleast 1
let stake = 1.max(
stakes
.as_ref()
.map_or(1, |stakes| *stakes.get(&c.id).unwrap_or(&1)),
);
(stake, i)
})
.sorted_by(|(l_stake, l_info), (r_stake, r_info)| {
if r_stake == l_stake {
peers[*r_info].id.cmp(&peers[*l_info].id)
} else {
r_stake.cmp(l_stake)
}
})
.collect();
stakes_and_index
}
fn shuffle_peers_and_index(
id: &Pubkey,
peers: &[ContactInfo],
stakes_and_index: &[(u64, usize)],
seed: [u8; 32],
) -> (usize, Vec<(u64, usize)>) {
let shuffled_stakes_and_index = stake_weighted_shuffle(stakes_and_index, seed);
let self_index = shuffled_stakes_and_index
.iter()
.enumerate()
.find_map(|(i, (_stake, index))| {
if peers[*index].id == *id {
Some(i)
} else {
None
}
})
.unwrap();
(self_index, shuffled_stakes_and_index)
}
fn stake_weighted_shuffle(stakes_and_index: &[(u64, usize)], seed: [u8; 32]) -> Vec<(u64, usize)> {
let mut rng = ChaChaRng::from_seed(seed);
let stake_weights: Vec<_> = stakes_and_index.iter().map(|(w, _)| *w).collect();
let shuffle = WeightedShuffle::new("stake_weighted_shuffle", &stake_weights);
shuffle
.shuffle(&mut rng)
.map(|i| stakes_and_index[i])
.collect()
}
fn retransmit(
mut shuffled_nodes: Vec<ContactInfo>,
senders: &HashMap<Pubkey, Sender<(i32, bool)>>,

View File

@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-install"
description = "The solana cluster software installer"
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -26,12 +26,12 @@ reqwest = { version = "0.11.10", default-features = false, features = ["blocking
semver = "1.0.6"
serde = { version = "1.0.136", features = ["derive"] }
serde_yaml = "0.8.23"
solana-clap-utils = { path = "../clap-utils", version = "=1.10.4" }
solana-client = { path = "../client", version = "=1.10.4" }
solana-config-program = { path = "../programs/config", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.9" }
solana-client = { path = "../client", version = "=1.10.9" }
solana-config-program = { path = "../programs/config", version = "=1.10.9" }
solana-logger = { path = "../logger", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
tar = "0.4.38"
tempfile = "3.3.0"
url = "2.2.2"

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-keygen"
version = "1.10.4"
version = "1.10.9"
description = "Solana key generation utility"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -14,11 +14,11 @@ bs58 = "0.4.0"
clap = "2.33"
dirs-next = "2.0.0"
num_cpus = "1.13.1"
solana-clap-utils = { path = "../clap-utils", version = "=1.10.4" }
solana-cli-config = { path = "../cli-config", version = "=1.10.4" }
solana-remote-wallet = { path = "../remote-wallet", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.9" }
solana-cli-config = { path = "../cli-config", version = "=1.10.9" }
solana-remote-wallet = { path = "../remote-wallet", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
tiny-bip39 = "0.8.2"
[[bin]]

View File

@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
edition = "2021"
name = "solana-ledger-tool"
description = "Blockchain, Rebuilt for Scale"
version = "1.10.4"
version = "1.10.9"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -21,20 +21,20 @@ log = { version = "0.4.14" }
regex = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.79"
solana-clap-utils = { path = "../clap-utils", version = "=1.10.4" }
solana-cli-output = { path = "../cli-output", version = "=1.10.4" }
solana-core = { path = "../core", version = "=1.10.4" }
solana-entry = { path = "../entry", version = "=1.10.4" }
solana-ledger = { path = "../ledger", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-measure = { path = "../measure", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-stake-program = { path = "../programs/stake", version = "=1.10.4" }
solana-storage-bigtable = { path = "../storage-bigtable", version = "=1.10.4" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.4" }
solana-version = { path = "../version", version = "=1.10.4" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.4" }
solana-clap-utils = { path = "../clap-utils", version = "=1.10.9" }
solana-cli-output = { path = "../cli-output", version = "=1.10.9" }
solana-core = { path = "../core", version = "=1.10.9" }
solana-entry = { path = "../entry", version = "=1.10.9" }
solana-ledger = { path = "../ledger", version = "=1.10.9" }
solana-logger = { path = "../logger", version = "=1.10.9" }
solana-measure = { path = "../measure", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-stake-program = { path = "../programs/stake", version = "=1.10.9" }
solana-storage-bigtable = { path = "../storage-bigtable", version = "=1.10.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.9" }
solana-version = { path = "../version", version = "=1.10.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.9" }
tokio = { version = "1", features = ["full"] }
[target.'cfg(not(target_env = "msvc"))'.dependencies]

View File

@@ -16,6 +16,7 @@ use {
},
solana_ledger::{blockstore::Blockstore, blockstore_db::AccessType},
solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature},
solana_storage_bigtable::CredentialType,
solana_transaction_status::{
BlockEncodingOptions, ConfirmedBlock, EncodeError, TransactionDetails,
UiTransactionEncoding,
@@ -34,8 +35,9 @@ async fn upload(
starting_slot: Slot,
ending_slot: Option<Slot>,
force_reupload: bool,
config: solana_storage_bigtable::LedgerStorageConfig,
) -> Result<(), Box<dyn std::error::Error>> {
let bigtable = solana_storage_bigtable::LedgerStorage::new(false, None, None)
let bigtable = solana_storage_bigtable::LedgerStorage::new_with_config(config)
.await
.map_err(|err| format!("Failed to connect to storage: {:?}", err))?;
@@ -50,17 +52,22 @@ async fn upload(
.await
}
async fn delete_slots(slots: Vec<Slot>, dry_run: bool) -> Result<(), Box<dyn std::error::Error>> {
let read_only = dry_run;
let bigtable = solana_storage_bigtable::LedgerStorage::new(read_only, None, None)
async fn delete_slots(
slots: Vec<Slot>,
config: solana_storage_bigtable::LedgerStorageConfig,
) -> Result<(), Box<dyn std::error::Error>> {
let dry_run = config.read_only;
let bigtable = solana_storage_bigtable::LedgerStorage::new_with_config(config)
.await
.map_err(|err| format!("Failed to connect to storage: {:?}", err))?;
solana_ledger::bigtable_delete::delete_confirmed_blocks(bigtable, slots, dry_run).await
}
async fn first_available_block() -> Result<(), Box<dyn std::error::Error>> {
let bigtable = solana_storage_bigtable::LedgerStorage::new(true, None, None).await?;
async fn first_available_block(
config: solana_storage_bigtable::LedgerStorageConfig,
) -> Result<(), Box<dyn std::error::Error>> {
let bigtable = solana_storage_bigtable::LedgerStorage::new_with_config(config).await?;
match bigtable.get_first_available_block().await? {
Some(block) => println!("{}", block),
None => println!("No blocks available"),
@@ -69,8 +76,12 @@ async fn first_available_block() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
async fn block(slot: Slot, output_format: OutputFormat) -> Result<(), Box<dyn std::error::Error>> {
let bigtable = solana_storage_bigtable::LedgerStorage::new(false, None, None)
async fn block(
slot: Slot,
output_format: OutputFormat,
config: solana_storage_bigtable::LedgerStorageConfig,
) -> Result<(), Box<dyn std::error::Error>> {
let bigtable = solana_storage_bigtable::LedgerStorage::new_with_config(config)
.await
.map_err(|err| format!("Failed to connect to storage: {:?}", err))?;
@@ -101,8 +112,12 @@ async fn block(slot: Slot, output_format: OutputFormat) -> Result<(), Box<dyn st
Ok(())
}
async fn blocks(starting_slot: Slot, limit: usize) -> Result<(), Box<dyn std::error::Error>> {
let bigtable = solana_storage_bigtable::LedgerStorage::new(false, None, None)
async fn blocks(
starting_slot: Slot,
limit: usize,
config: solana_storage_bigtable::LedgerStorageConfig,
) -> Result<(), Box<dyn std::error::Error>> {
let bigtable = solana_storage_bigtable::LedgerStorage::new_with_config(config)
.await
.map_err(|err| format!("Failed to connect to storage: {:?}", err))?;
@@ -116,11 +131,10 @@ async fn blocks(starting_slot: Slot, limit: usize) -> Result<(), Box<dyn std::er
async fn compare_blocks(
starting_slot: Slot,
limit: usize,
credential_path: String,
config: solana_storage_bigtable::LedgerStorageConfig,
ref_config: solana_storage_bigtable::LedgerStorageConfig,
) -> Result<(), Box<dyn std::error::Error>> {
assert!(!credential_path.is_empty());
let owned_bigtable = solana_storage_bigtable::LedgerStorage::new(false, None, None)
let owned_bigtable = solana_storage_bigtable::LedgerStorage::new_with_config(config)
.await
.map_err(|err| format!("failed to connect to owned bigtable: {:?}", err))?;
let owned_bigtable_slots = owned_bigtable
@@ -130,10 +144,9 @@ async fn compare_blocks(
"owned bigtable {} blocks found ",
owned_bigtable_slots.len()
);
let reference_bigtable =
solana_storage_bigtable::LedgerStorage::new(false, None, Some(credential_path))
.await
.map_err(|err| format!("failed to connect to reference bigtable: {:?}", err))?;
let reference_bigtable = solana_storage_bigtable::LedgerStorage::new_with_config(ref_config)
.await
.map_err(|err| format!("failed to connect to reference bigtable: {:?}", err))?;
let reference_bigtable_slots = reference_bigtable
.get_confirmed_blocks(starting_slot, limit)
@@ -160,8 +173,9 @@ async fn confirm(
signature: &Signature,
verbose: bool,
output_format: OutputFormat,
config: solana_storage_bigtable::LedgerStorageConfig,
) -> Result<(), Box<dyn std::error::Error>> {
let bigtable = solana_storage_bigtable::LedgerStorage::new(false, None, None)
let bigtable = solana_storage_bigtable::LedgerStorage::new_with_config(config)
.await
.map_err(|err| format!("Failed to connect to storage: {:?}", err))?;
@@ -211,8 +225,9 @@ pub async fn transaction_history(
verbose: bool,
show_transactions: bool,
query_chunk_size: usize,
config: solana_storage_bigtable::LedgerStorageConfig,
) -> Result<(), Box<dyn std::error::Error>> {
let bigtable = solana_storage_bigtable::LedgerStorage::new(true, None, None).await?;
let bigtable = solana_storage_bigtable::LedgerStorage::new_with_config(config).await?;
let mut loaded_block: Option<(Slot, ConfirmedBlock)> = None;
while limit > 0 {
@@ -308,6 +323,15 @@ impl BigTableSubCommand for App<'_, '_> {
.about("Ledger data on a BigTable instance")
.setting(AppSettings::InferSubcommands)
.setting(AppSettings::SubcommandRequiredElseHelp)
.arg(
Arg::with_name("rpc_bigtable_instance_name")
.global(true)
.long("rpc-bigtable-instance-name")
.takes_value(true)
.value_name("INSTANCE_NAME")
.default_value(solana_storage_bigtable::DEFAULT_INSTANCE_NAME)
.help("Name of the target Bigtable instance")
)
.subcommand(
SubCommand::with_name("upload")
.about("Upload the ledger to BigTable")
@@ -417,7 +441,8 @@ impl BigTableSubCommand for App<'_, '_> {
.required(true)
.default_value("1000")
.help("Maximum number of slots to check"),
).arg(
)
.arg(
Arg::with_name("reference_credential")
.long("reference-credential")
.short("c")
@@ -425,6 +450,14 @@ impl BigTableSubCommand for App<'_, '_> {
.takes_value(true)
.required(true)
.help("File path for a credential to a reference bigtable"),
)
.arg(
Arg::with_name("reference_instance_name")
.long("reference-instance-name")
.takes_value(true)
.value_name("INSTANCE_NAME")
.default_value(solana_storage_bigtable::DEFAULT_INSTANCE_NAME)
.help("Name of the reference Bigtable instance to compare to")
),
)
.subcommand(
@@ -521,7 +554,28 @@ pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
let verbose = matches.is_present("verbose");
let output_format = OutputFormat::from_matches(matches, "output_format", verbose);
let future = match matches.subcommand() {
// this is kinda stupid, but there seems to be a bug in clap when a subcommand
// arg is marked both `global(true)` and `default_value("default_value")`.
// despite the "global", when the arg is specified on the subcommand, its value
// is not propagated down to the (sub)subcommand args, resulting in the default
// value when queried there. similarly, if the arg is specified on the
// (sub)subcommand, the value is not propagated back up to the subcommand args,
// again resulting in the default value. the arg having declared a
// `default_value()` obviates `is_present(...)` tests since they will always
// return true. so we consede and compare against the expected default. :/
let (subcommand, sub_matches) = matches.subcommand();
let on_command = matches
.value_of("rpc_bigtable_instance_name")
.map(|v| v != solana_storage_bigtable::DEFAULT_INSTANCE_NAME)
.unwrap_or(false);
let instance_name = if on_command {
value_t_or_exit!(matches, "rpc_bigtable_instance_name", String)
} else {
let sub_matches = sub_matches.as_ref().unwrap();
value_t_or_exit!(sub_matches, "rpc_bigtable_instance_name", String)
};
let future = match (subcommand, sub_matches) {
("upload", Some(arg_matches)) => {
let starting_slot = value_t!(arg_matches, "starting_slot", Slot).unwrap_or(0);
let ending_slot = value_t!(arg_matches, "ending_slot", Slot).ok();
@@ -531,41 +585,81 @@ pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
AccessType::TryPrimaryThenSecondary,
None,
);
let config = solana_storage_bigtable::LedgerStorageConfig {
read_only: false,
instance_name,
..solana_storage_bigtable::LedgerStorageConfig::default()
};
runtime.block_on(upload(
blockstore,
starting_slot,
ending_slot,
force_reupload,
config,
))
}
("delete-slots", Some(arg_matches)) => {
let slots = values_t_or_exit!(arg_matches, "slots", Slot);
let dry_run = !arg_matches.is_present("force");
runtime.block_on(delete_slots(slots, dry_run))
let config = solana_storage_bigtable::LedgerStorageConfig {
read_only: !arg_matches.is_present("force"),
instance_name,
..solana_storage_bigtable::LedgerStorageConfig::default()
};
runtime.block_on(delete_slots(slots, config))
}
("first-available-block", Some(_arg_matches)) => {
let config = solana_storage_bigtable::LedgerStorageConfig {
read_only: true,
instance_name,
..solana_storage_bigtable::LedgerStorageConfig::default()
};
runtime.block_on(first_available_block(config))
}
("first-available-block", Some(_arg_matches)) => runtime.block_on(first_available_block()),
("block", Some(arg_matches)) => {
let slot = value_t_or_exit!(arg_matches, "slot", Slot);
runtime.block_on(block(slot, output_format))
let config = solana_storage_bigtable::LedgerStorageConfig {
read_only: false,
instance_name,
..solana_storage_bigtable::LedgerStorageConfig::default()
};
runtime.block_on(block(slot, output_format, config))
}
("blocks", Some(arg_matches)) => {
let starting_slot = value_t_or_exit!(arg_matches, "starting_slot", Slot);
let limit = value_t_or_exit!(arg_matches, "limit", usize);
let config = solana_storage_bigtable::LedgerStorageConfig {
read_only: false,
instance_name,
..solana_storage_bigtable::LedgerStorageConfig::default()
};
runtime.block_on(blocks(starting_slot, limit))
runtime.block_on(blocks(starting_slot, limit, config))
}
("compare-blocks", Some(arg_matches)) => {
let starting_slot = value_t_or_exit!(arg_matches, "starting_slot", Slot);
let limit = value_t_or_exit!(arg_matches, "limit", usize);
let reference_credential_filepath =
value_t_or_exit!(arg_matches, "reference_credential", String);
let config = solana_storage_bigtable::LedgerStorageConfig {
read_only: false,
instance_name,
..solana_storage_bigtable::LedgerStorageConfig::default()
};
runtime.block_on(compare_blocks(
starting_slot,
limit,
reference_credential_filepath,
))
let credential_path = Some(value_t_or_exit!(
arg_matches,
"reference_credential",
String
));
let ref_instance_name =
value_t_or_exit!(arg_matches, "reference_instance_name", String);
let ref_config = solana_storage_bigtable::LedgerStorageConfig {
read_only: false,
credential_type: CredentialType::Filepath(credential_path),
instance_name: ref_instance_name,
..solana_storage_bigtable::LedgerStorageConfig::default()
};
runtime.block_on(compare_blocks(starting_slot, limit, config, ref_config))
}
("confirm", Some(arg_matches)) => {
let signature = arg_matches
@@ -573,8 +667,13 @@ pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
.unwrap()
.parse()
.expect("Invalid signature");
let config = solana_storage_bigtable::LedgerStorageConfig {
read_only: false,
instance_name,
..solana_storage_bigtable::LedgerStorageConfig::default()
};
runtime.block_on(confirm(&signature, verbose, output_format))
runtime.block_on(confirm(&signature, verbose, output_format, config))
}
("transaction-history", Some(arg_matches)) => {
let address = pubkey_of(arg_matches, "address").unwrap();
@@ -587,6 +686,11 @@ pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
.value_of("until")
.map(|signature| signature.parse().expect("Invalid signature"));
let show_transactions = arg_matches.is_present("show_transactions");
let config = solana_storage_bigtable::LedgerStorageConfig {
read_only: true,
instance_name,
..solana_storage_bigtable::LedgerStorageConfig::default()
};
runtime.block_on(transaction_history(
&address,
@@ -596,6 +700,7 @@ pub fn bigtable_process_command(ledger_path: &Path, matches: &ArgMatches<'_>) {
verbose,
show_transactions,
query_chunk_size,
config,
))
}
_ => unreachable!(),

View File

@@ -803,7 +803,7 @@ fn compute_slot_cost(blockstore: &Blockstore, slot: Slot) -> Result<(), String>
num_programs += transaction.message().instructions().len();
let tx_cost = cost_model.calculate_cost(&transaction);
let result = cost_tracker.try_add(&transaction, &tx_cost);
let result = cost_tracker.try_add(&tx_cost);
if result.is_err() {
println!(
"Slot: {}, CostModel rejected transaction {:?}, reason {:?}",

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-ledger"
version = "1.10.4"
version = "1.10.9"
description = "Solana ledger"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
@@ -11,6 +11,7 @@ edition = "2021"
[dependencies]
bincode = "1.3.3"
bitflags = "1.3.1"
byteorder = "1.4.3"
chrono = { version = "0.4.11", features = ["serde"] }
chrono-humanize = "0.2.1"
@@ -21,10 +22,11 @@ itertools = "0.10.3"
lazy_static = "1.4.0"
libc = "0.2.120"
log = { version = "0.4.14" }
lru = "0.7.5"
num-derive = "0.3"
num-traits = "0.2"
num_cpus = "1.13.1"
prost = "0.9.0"
prost = "0.10.0"
rand = "0.7.0"
rand_chacha = "0.2.2"
rayon = "1.5.1"
@@ -32,21 +34,21 @@ reed-solomon-erasure = { version = "5.0.1", features = ["simd-accel"] }
serde = "1.0.136"
serde_bytes = "0.11.5"
sha2 = "0.10.2"
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.10.4" }
solana-entry = { path = "../entry", version = "=1.10.4" }
solana-frozen-abi = { path = "../frozen-abi", version = "=1.10.4" }
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.10.4" }
solana-measure = { path = "../measure", version = "=1.10.4" }
solana-metrics = { path = "../metrics", version = "=1.10.4" }
solana-perf = { path = "../perf", version = "=1.10.4" }
solana-program-runtime = { path = "../program-runtime", version = "=1.10.4" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.10.4" }
solana-runtime = { path = "../runtime", version = "=1.10.4" }
solana-sdk = { path = "../sdk", version = "=1.10.4" }
solana-storage-bigtable = { path = "../storage-bigtable", version = "=1.10.4" }
solana-storage-proto = { path = "../storage-proto", version = "=1.10.4" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.4" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.4" }
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.10.9" }
solana-entry = { path = "../entry", version = "=1.10.9" }
solana-frozen-abi = { path = "../frozen-abi", version = "=1.10.9" }
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.10.9" }
solana-measure = { path = "../measure", version = "=1.10.9" }
solana-metrics = { path = "../metrics", version = "=1.10.9" }
solana-perf = { path = "../perf", version = "=1.10.9" }
solana-program-runtime = { path = "../program-runtime", version = "=1.10.9" }
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.10.9" }
solana-runtime = { path = "../runtime", version = "=1.10.9" }
solana-sdk = { path = "../sdk", version = "=1.10.9" }
solana-storage-bigtable = { path = "../storage-bigtable", version = "=1.10.9" }
solana-storage-proto = { path = "../storage-proto", version = "=1.10.9" }
solana-transaction-status = { path = "../transaction-status", version = "=1.10.9" }
solana-vote-program = { path = "../programs/vote", version = "=1.10.9" }
tempfile = "3.3.0"
thiserror = "1.0"
tokio = { version = "1", features = ["full"] }
@@ -63,8 +65,8 @@ features = ["lz4"]
[dev-dependencies]
assert_matches = "1.5.0"
matches = "0.1.9"
solana-account-decoder = { path = "../account-decoder", version = "=1.10.4" }
solana-logger = { path = "../logger", version = "=1.10.4" }
solana-account-decoder = { path = "../account-decoder", version = "=1.10.9" }
solana-logger = { path = "../logger", version = "=1.10.9" }
[build-dependencies]
rustc_version = "0.4"

View File

@@ -7,8 +7,10 @@ use {
},
leader_schedule_cache::LeaderScheduleCache,
},
crossbeam_channel::unbounded,
log::*,
solana_runtime::{
accounts_background_service::DroppedSlotsReceiver,
accounts_update_notifier_interface::AccountsUpdateNotifier,
bank_forks::BankForks,
snapshot_archive_info::SnapshotArchiveInfoGetter,
@@ -47,16 +49,17 @@ pub fn load(
accounts_package_sender: AccountsPackageSender,
accounts_update_notifier: Option<AccountsUpdateNotifier>,
) -> LoadResult {
let (mut bank_forks, leader_schedule_cache, starting_snapshot_hashes) = load_bank_forks(
genesis_config,
blockstore,
account_paths,
shrink_paths,
snapshot_config,
&process_options,
cache_block_meta_sender,
accounts_update_notifier,
);
let (mut bank_forks, leader_schedule_cache, starting_snapshot_hashes, pruned_banks_receiver) =
load_bank_forks(
genesis_config,
blockstore,
account_paths,
shrink_paths,
snapshot_config,
&process_options,
cache_block_meta_sender,
accounts_update_notifier,
);
blockstore_processor::process_blockstore_from_root(
blockstore,
@@ -67,6 +70,7 @@ pub fn load(
cache_block_meta_sender,
snapshot_config,
accounts_package_sender,
pruned_banks_receiver,
)
.map(|_| (bank_forks, leader_schedule_cache, starting_snapshot_hashes))
}
@@ -85,6 +89,7 @@ pub fn load_bank_forks(
BankForks,
LeaderScheduleCache,
Option<StartingSnapshotHashes>,
DroppedSlotsReceiver,
) {
let snapshot_present = if let Some(snapshot_config) = snapshot_config {
info!(
@@ -144,12 +149,30 @@ pub fn load_bank_forks(
)
};
let mut leader_schedule_cache = LeaderScheduleCache::new_from_bank(&bank_forks.root_bank());
// Before replay starts, set the callbacks in each of the banks in BankForks so that
// all dropped banks come through the `pruned_banks_receiver` channel. This way all bank
// drop behavior can be safely synchronized with any other ongoing accounts activity like
// cache flush, clean, shrink, as long as the same thread performing those activities also
// is processing the dropped banks from the `pruned_banks_receiver` channel.
// There should only be one bank, the root bank in BankForks. Thus all banks added to
// BankForks from now on will be descended from the root bank and thus will inherit
// the bank drop callback.
assert_eq!(bank_forks.banks().len(), 1);
let (pruned_banks_sender, pruned_banks_receiver) = unbounded();
let root_bank = bank_forks.root_bank();
let callback = root_bank
.rc
.accounts
.accounts_db
.create_drop_bank_callback(pruned_banks_sender);
root_bank.set_callback(Some(Box::new(callback)));
let mut leader_schedule_cache = LeaderScheduleCache::new_from_bank(&root_bank);
if process_options.full_leader_cache {
leader_schedule_cache.set_max_schedules(std::usize::MAX);
}
assert_eq!(bank_forks.banks().len(), 1);
if let Some(ref new_hard_forks) = process_options.new_hard_forks {
let root_bank = bank_forks.root_bank();
let hard_forks = root_bank.hard_forks();
@@ -166,7 +189,12 @@ pub fn load_bank_forks(
}
}
(bank_forks, leader_schedule_cache, starting_snapshot_hashes)
(
bank_forks,
leader_schedule_cache,
starting_snapshot_hashes,
pruned_banks_receiver,
)
}
#[allow(clippy::too_many_arguments)]

View File

@@ -16,6 +16,7 @@ use {
max_ticks_per_n_shreds, ErasureSetId, Result as ShredResult, Shred, ShredId, ShredType,
Shredder, SHRED_PAYLOAD_SIZE,
},
slot_stats::{ShredSource, SlotsStats},
},
bincode::deserialize,
crossbeam_channel::{bounded, Receiver, Sender, TrySendError},
@@ -50,7 +51,7 @@ use {
borrow::Cow,
cell::RefCell,
cmp,
collections::{hash_map::Entry as HashMapEntry, BTreeMap, BTreeSet, HashMap, HashSet},
collections::{hash_map::Entry as HashMapEntry, BTreeSet, HashMap, HashSet},
convert::TryInto,
fs,
io::{Error as IoError, ErrorKind},
@@ -60,7 +61,6 @@ use {
atomic::{AtomicBool, Ordering},
Arc, Mutex, RwLock, RwLockWriteGuard,
},
time::Instant,
},
tempfile::{Builder, TempDir},
thiserror::Error,
@@ -181,26 +181,6 @@ pub struct Blockstore {
column_options: LedgerColumnOptions,
}
struct SlotsStats {
last_cleanup_ts: Instant,
stats: BTreeMap<Slot, SlotStats>,
}
impl Default for SlotsStats {
fn default() -> Self {
SlotsStats {
last_cleanup_ts: Instant::now(),
stats: BTreeMap::new(),
}
}
}
#[derive(Default)]
struct SlotStats {
num_repaired: usize,
num_recovered: usize,
}
pub struct IndexMetaWorkingSetEntry {
index: Index,
// true only if at least one shred for this Index was inserted since the time this
@@ -223,13 +203,6 @@ pub struct SlotMetaWorkingSetEntry {
did_insert_occur: bool,
}
#[derive(PartialEq, Debug, Clone)]
enum ShredSource {
Turbine,
Repaired,
Recovered,
}
#[derive(Default)]
pub struct BlockstoreInsertionMetrics {
pub num_shreds: usize,
@@ -1250,13 +1223,13 @@ impl Blockstore {
let mut newly_completed_data_sets: Vec<CompletedDataSetInfo> = vec![];
let mut inserted_indices = Vec::new();
for (i, (shred, is_repaired)) in shreds.into_iter().zip(is_repaired).enumerate() {
let shred_source = if is_repaired {
ShredSource::Repaired
} else {
ShredSource::Turbine
};
match shred.shred_type() {
ShredType::Data => {
let shred_source = if is_repaired {
ShredSource::Repaired
} else {
ShredSource::Turbine
};
match self.check_insert_data_shred(
shred,
&mut erasure_metas,
@@ -1295,7 +1268,7 @@ impl Blockstore {
&mut index_meta_time,
handle_duplicate,
is_trusted,
is_repaired,
shred_source,
metrics,
);
}
@@ -1464,10 +1437,9 @@ impl Blockstore {
}
fn erasure_mismatch(shred1: &Shred, shred2: &Shred) -> bool {
// TODO should also compare first-coding-index once position field is
// populated across cluster.
shred1.coding_header.num_coding_shreds != shred2.coding_header.num_coding_shreds
|| shred1.coding_header.num_data_shreds != shred2.coding_header.num_data_shreds
|| shred1.first_coding_index() != shred2.first_coding_index()
}
#[allow(clippy::too_many_arguments)]
@@ -1481,7 +1453,7 @@ impl Blockstore {
index_meta_time: &mut u64,
handle_duplicate: &F,
is_trusted: bool,
is_repaired: bool,
shred_source: ShredSource,
metrics: &mut BlockstoreInsertionMetrics,
) -> bool
where
@@ -1548,13 +1520,10 @@ impl Blockstore {
return false;
}
if is_repaired {
let mut slots_stats = self.slots_stats.lock().unwrap();
let mut e = slots_stats.stats.entry(slot).or_default();
e.num_repaired += 1;
}
self.slots_stats
.lock()
.unwrap()
.add_shred(slot, shred_source);
// insert coding shred into rocks
let result = self
.insert_coding_shred(index_meta, &shred, write_batch)
@@ -1700,7 +1669,7 @@ impl Blockstore {
just_inserted_shreds,
&self.last_root,
leader_schedule,
shred_source.clone(),
shred_source,
) {
return Err(InsertDataShredError::InvalidShred);
}
@@ -1972,49 +1941,12 @@ impl Blockstore {
end_index,
})
.collect();
if shred_source == ShredSource::Repaired || shred_source == ShredSource::Recovered {
{
let mut slots_stats = self.slots_stats.lock().unwrap();
let mut e = slots_stats.stats.entry(slot_meta.slot).or_default();
if shred_source == ShredSource::Repaired {
e.num_repaired += 1;
slots_stats.add_shred(slot_meta.slot, shred_source);
if slot_meta.is_full() {
slots_stats.set_full(slot_meta);
}
if shred_source == ShredSource::Recovered {
e.num_recovered += 1;
}
}
if slot_meta.is_full() {
let (num_repaired, num_recovered) = {
let mut slots_stats = self.slots_stats.lock().unwrap();
if let Some(e) = slots_stats.stats.remove(&slot_meta.slot) {
if slots_stats.last_cleanup_ts.elapsed().as_secs() > 30 {
let root = self.last_root();
slots_stats.stats = slots_stats.stats.split_off(&root);
slots_stats.last_cleanup_ts = Instant::now();
}
(e.num_repaired, e.num_recovered)
} else {
(0, 0)
}
};
datapoint_info!(
"shred_insert_is_full",
(
"total_time_ms",
solana_sdk::timing::timestamp() - slot_meta.first_shred_timestamp,
i64
),
("slot", slot_meta.slot, i64),
(
"last_index",
slot_meta
.last_index
.and_then(|ix| i64::try_from(ix).ok())
.unwrap_or(-1),
i64
),
("num_repaired", num_repaired, i64),
("num_recovered", num_recovered, i64),
);
}
trace!("inserted shred into slot {:?} and index {:?}", slot, index);
Ok(newly_completed_data_sets)
@@ -6355,7 +6287,7 @@ pub mod tests {
panic!("no dupes");
},
false,
false,
ShredSource::Turbine,
&mut BlockstoreInsertionMetrics::default(),
));
@@ -6373,7 +6305,7 @@ pub mod tests {
counter.fetch_add(1, Ordering::Relaxed);
},
false,
false,
ShredSource::Turbine,
&mut BlockstoreInsertionMetrics::default(),
));
assert_eq!(counter.load(Ordering::Relaxed), 1);
@@ -6487,7 +6419,7 @@ pub mod tests {
);
coding_shred.common_header.fec_set_index = std::u32::MAX - 1;
coding_shred.coding_header.num_data_shreds = 2;
coding_shred.coding_header.num_coding_shreds = 3;
coding_shred.coding_header.num_coding_shreds = 4;
coding_shred.coding_header.position = 1;
coding_shred.common_header.index = std::u32::MAX - 1;
assert!(!Blockstore::should_insert_coding_shred(

View File

@@ -257,10 +257,6 @@ impl ErasureMeta {
None => return false,
};
other.__unused_size = self.__unused_size;
// Ignore first_coding_index field for now to be backward compatible.
// TODO remove this once cluster is upgraded to always populate
// first_coding_index field.
other.first_coding_index = self.first_coding_index;
self == &other
}
@@ -275,16 +271,7 @@ impl ErasureMeta {
pub(crate) fn coding_shreds_indices(&self) -> Range<u64> {
let num_coding = self.config.num_coding() as u64;
// first_coding_index == 0 may imply that the field is not populated.
// self.set_index to be backward compatible.
// TODO remove this once cluster is upgraded to always populate
// first_coding_index field.
let first_coding_index = if self.first_coding_index == 0 {
self.set_index
} else {
self.first_coding_index
};
first_coding_index..first_coding_index + num_coding
self.first_coding_index..self.first_coding_index + num_coding
}
pub(crate) fn status(&self, index: &Index) -> ErasureMetaStatus {

View File

@@ -17,6 +17,7 @@ use {
solana_program_runtime::timings::{ExecuteTimingType, ExecuteTimings},
solana_rayon_threadlimit::get_thread_count,
solana_runtime::{
accounts_background_service::DroppedSlotsReceiver,
accounts_db::{AccountShrinkThreshold, AccountsDbConfig},
accounts_index::AccountSecondaryIndexes,
accounts_update_notifier_interface::AccountsUpdateNotifier,
@@ -566,16 +567,17 @@ pub fn test_process_blockstore(
blockstore: &Blockstore,
opts: ProcessOptions,
) -> (BankForks, LeaderScheduleCache) {
let (mut bank_forks, leader_schedule_cache, ..) = crate::bank_forks_utils::load_bank_forks(
genesis_config,
blockstore,
Vec::new(),
None,
None,
&opts,
None,
None,
);
let (mut bank_forks, leader_schedule_cache, .., pruned_banks_receiver) =
crate::bank_forks_utils::load_bank_forks(
genesis_config,
blockstore,
Vec::new(),
None,
None,
&opts,
None,
None,
);
let (accounts_package_sender, _) = unbounded();
process_blockstore_from_root(
blockstore,
@@ -586,6 +588,7 @@ pub fn test_process_blockstore(
None,
None,
accounts_package_sender,
pruned_banks_receiver,
)
.unwrap();
(bank_forks, leader_schedule_cache)
@@ -636,6 +639,7 @@ pub fn process_blockstore_from_root(
cache_block_meta_sender: Option<&CacheBlockMetaSender>,
snapshot_config: Option<&SnapshotConfig>,
accounts_package_sender: AccountsPackageSender,
pruned_banks_receiver: DroppedSlotsReceiver,
) -> result::Result<Option<Slot>, BlockstoreProcessorError> {
if let Some(num_threads) = opts.override_num_threads {
PAR_THREAD_POOL.with(|pool| {
@@ -695,6 +699,7 @@ pub fn process_blockstore_from_root(
accounts_package_sender,
&mut timing,
&mut last_full_snapshot_slot,
pruned_banks_receiver,
)?;
} else {
// If there's no meta for the input `start_slot`, then we started from a snapshot
@@ -1116,6 +1121,7 @@ fn load_frozen_forks(
accounts_package_sender: AccountsPackageSender,
timing: &mut ExecuteTimings,
last_full_snapshot_slot: &mut Option<Slot>,
pruned_banks_receiver: DroppedSlotsReceiver,
) -> result::Result<(), BlockstoreProcessorError> {
let recyclers = VerifyRecyclers::default();
let mut all_banks = HashMap::new();
@@ -1284,6 +1290,17 @@ fn load_frozen_forks(
}
if last_free.elapsed() > Duration::from_secs(10) {
// Purge account state for all dropped banks
for (pruned_slot, pruned_bank_id) in pruned_banks_receiver.try_iter() {
// Simulate this purge being from the AccountsBackgroundService
let is_from_abs = true;
new_root_bank.rc.accounts.purge_slot(
pruned_slot,
pruned_bank_id,
is_from_abs,
);
}
// Must be called after `squash()`, so that AccountsDb knows what
// the roots are for the cache flushing in exhaustively_free_unused_resource().
// This could take few secs; so update last_free later
@@ -3143,6 +3160,7 @@ pub mod tests {
// Test process_blockstore_from_root() from slot 1 onwards
let (accounts_package_sender, _) = unbounded();
let (_pruned_banks_sender, pruned_banks_receiver) = unbounded();
process_blockstore_from_root(
&blockstore,
&mut bank_forks,
@@ -3152,6 +3170,7 @@ pub mod tests {
None,
None,
accounts_package_sender,
pruned_banks_receiver,
)
.unwrap();
@@ -3252,6 +3271,7 @@ pub mod tests {
let (accounts_package_sender, accounts_package_receiver) = unbounded();
let leader_schedule_cache = LeaderScheduleCache::new_from_bank(&bank);
let (_pruned_banks_sender, pruned_banks_receiver) = unbounded();
process_blockstore_from_root(
&blockstore,
&mut bank_forks,
@@ -3261,6 +3281,7 @@ pub mod tests {
None,
Some(&snapshot_config),
accounts_package_sender.clone(),
pruned_banks_receiver,
)
.unwrap();

View File

@@ -8,6 +8,7 @@ pub mod bigtable_delete;
pub mod bigtable_upload;
pub mod bigtable_upload_service;
pub mod block_error;
mod slot_stats;
#[macro_use]
pub mod blockstore;
pub mod ancestor_iterator;

View File

@@ -60,16 +60,14 @@ use {
solana_measure::measure::Measure,
solana_perf::packet::{limited_deserialize, Packet},
solana_rayon_threadlimit::get_thread_count,
solana_runtime::bank::Bank,
solana_sdk::{
clock::Slot,
feature_set,
hash::{hashv, Hash},
packet::PACKET_DATA_SIZE,
pubkey::Pubkey,
signature::{Keypair, Signature, Signer},
},
std::{cell::RefCell, convert::TryInto, mem::size_of},
std::{cell::RefCell, mem::size_of},
thiserror::Error,
};
@@ -504,9 +502,10 @@ impl Shred {
pub(crate) fn first_coding_index(&self) -> Option<u32> {
match self.shred_type() {
ShredType::Data => None,
// TODO should be: self.index() - self.coding_header.position
// once position field is populated.
ShredType::Code => Some(self.fec_set_index()),
ShredType::Code => {
let position = u32::from(self.coding_header.position);
self.index().checked_sub(position)
}
}
}
@@ -536,25 +535,25 @@ impl Shred {
// Returns the block index within the erasure coding set.
fn erasure_block_index(&self) -> Option<usize> {
let index = self.index().checked_sub(self.fec_set_index())?;
let index = usize::try_from(index).ok()?;
match self.shred_type() {
ShredType::Data => Some(index),
ShredType::Data => {
let index = self.index().checked_sub(self.fec_set_index())?;
usize::try_from(index).ok()
}
ShredType::Code => {
// TODO should use first_coding_index once position field is
// populated.
// Assert that the last shred index in the erasure set does not
// overshoot u32.
self.fec_set_index().checked_add(u32::from(
self.coding_header
.num_data_shreds
.max(self.coding_header.num_coding_shreds)
.checked_sub(1)?,
self.coding_header.num_data_shreds.checked_sub(1)?,
))?;
self.first_coding_index()?.checked_add(u32::from(
self.coding_header.num_coding_shreds.checked_sub(1)?,
))?;
let num_data_shreds = usize::from(self.coding_header.num_data_shreds);
let num_coding_shreds = usize::from(self.coding_header.num_coding_shreds);
let position = usize::from(self.coding_header.position);
let fec_set_size = num_data_shreds.checked_add(num_coding_shreds)?;
let index = index.checked_add(num_data_shreds)?;
let index = position.checked_add(num_data_shreds)?;
(index < fec_set_size).then(|| index)
}
}
@@ -608,19 +607,13 @@ impl Shred {
self.common_header.signature
}
pub fn seed(&self, leader_pubkey: Pubkey, root_bank: &Bank) -> [u8; 32] {
if enable_deterministic_seed(self.slot(), root_bank) {
hashv(&[
&self.slot().to_le_bytes(),
&self.index().to_le_bytes(),
&leader_pubkey.to_bytes(),
])
.to_bytes()
} else {
let signature = self.common_header.signature.as_ref();
let offset = signature.len().checked_sub(32).unwrap();
signature[offset..].try_into().unwrap()
}
pub fn seed(&self, leader_pubkey: Pubkey) -> [u8; 32] {
hashv(&[
&self.slot().to_le_bytes(),
&self.index().to_le_bytes(),
&leader_pubkey.to_bytes(),
])
.to_bytes()
}
#[inline]
@@ -710,21 +703,6 @@ impl Shred {
}
}
fn enable_deterministic_seed(shred_slot: Slot, bank: &Bank) -> bool {
let feature_slot = bank
.feature_set
.activated_slot(&feature_set::deterministic_shred_seed_enabled::id());
match feature_slot {
None => false,
Some(feature_slot) => {
let epoch_schedule = bank.epoch_schedule();
let feature_epoch = epoch_schedule.get_epoch(feature_slot);
let shred_epoch = epoch_schedule.get_epoch(shred_slot);
feature_epoch < shred_epoch
}
}
}
#[derive(Debug)]
pub struct Shredder {
pub slot: Slot,

90
ledger/src/slot_stats.rs Normal file
View File

@@ -0,0 +1,90 @@
use {
crate::blockstore_meta::SlotMeta, bitflags::bitflags, lru::LruCache, solana_sdk::clock::Slot,
};
const SLOTS_STATS_CACHE_CAPACITY: usize = 300;
macro_rules! get_mut_entry (
($cache:expr, $key:expr) => (
match $cache.get_mut(&$key) {
Some(entry) => entry,
None => {
$cache.put($key, SlotStats::default());
$cache.get_mut(&$key).unwrap()
}
}
);
);
#[derive(Copy, Clone, Debug)]
pub(crate) enum ShredSource {
Turbine,
Repaired,
Recovered,
}
bitflags! {
#[derive(Default)]
struct SlotFlags: u8 {
const DEAD = 0b00000001;
const FULL = 0b00000010;
const ROOTED = 0b00000100;
}
}
#[derive(Default)]
struct SlotStats {
flags: SlotFlags,
num_repaired: usize,
num_recovered: usize,
}
pub(crate) struct SlotsStats(LruCache<Slot, SlotStats>);
impl Default for SlotsStats {
fn default() -> Self {
// LruCache::unbounded because capacity is enforced manually.
Self(LruCache::unbounded())
}
}
impl SlotsStats {
pub(crate) fn add_shred(&mut self, slot: Slot, source: ShredSource) {
let entry = get_mut_entry!(self.0, slot);
match source {
ShredSource::Turbine => (),
ShredSource::Repaired => entry.num_repaired += 1,
ShredSource::Recovered => entry.num_recovered += 1,
}
self.maybe_evict_cache();
}
pub(crate) fn set_full(&mut self, slot_meta: &SlotMeta) {
let total_time_ms =
solana_sdk::timing::timestamp().saturating_sub(slot_meta.first_shred_timestamp);
let last_index = slot_meta
.last_index
.and_then(|ix| i64::try_from(ix).ok())
.unwrap_or(-1);
let entry = get_mut_entry!(self.0, slot_meta.slot);
if !entry.flags.contains(SlotFlags::FULL) {
datapoint_info!(
"shred_insert_is_full",
("total_time_ms", total_time_ms, i64),
("slot", slot_meta.slot, i64),
("last_index", last_index, i64),
("num_repaired", entry.num_repaired, i64),
("num_recovered", entry.num_recovered, i64),
);
}
entry.flags |= SlotFlags::FULL;
self.maybe_evict_cache();
}
fn maybe_evict_cache(&mut self) {
while self.0.len() > SLOTS_STATS_CACHE_CAPACITY {
let (_slot, _entry) = self.0.pop_lru().unwrap();
// TODO: submit metrics for (slot, entry).
}
}
}

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