Compare commits

...

329 Commits

Author SHA1 Message Date
b4adb1c266 Cherry pick fix for freeze (#4459)
* check freeze before updating slot_hashes (#4448)

* check freeze before updating slot_hashes

* fixup

* add more information to dropped vote warning (#4449)

* add more information to dropped vote warning

* fixup
2019-05-28 20:35:54 -06:00
b9b541441b update book with passive staking (#4451) (#4455)
* update book with passive staking (#4451)

* undelete votestate etc
2019-05-28 16:01:53 -07:00
e510d4e272 Drop influxcloud (#4461) 2019-05-28 15:34:36 -07:00
9341e64ec7 Lock down blockexplorer version (#4462) 2019-05-28 15:26:53 -07:00
d934f94e05 Add commented correct future test lines 2019-05-28 09:59:07 -06:00
59dc123fa8 Add test indicating need for credit-only account handling 2019-05-28 09:58:57 -06:00
0faea87c84 Revert --retry-on-http-error usage, Travis CI's wget doesn't recognize it 2019-05-27 19:34:46 -07:00
19137ce3f4 Bump logging level of validator procsesing errors (#4444)
automerge
2019-05-27 15:36:35 -07:00
8bdeb2d1ed Use nohup and sleep a little to improve stability when launching a node 2019-05-27 14:00:24 -07:00
d29a45266b data_dir -> data-dir 2019-05-27 07:32:29 -07:00
2adb98a4a0 Ignore flaky test_repairman_catchup (#4439)
automerge
2019-05-26 12:24:20 -07:00
471465a5f4 net/: Add solana-install test to sanity (#4438)
* Add instance creation date to motd

* Setup localtime

* Add solana-install test
2019-05-26 11:17:07 -07:00
942785b626 sdk/bpf/scripts/install.sh: Retry downloads on transient 403 S3 failures seen in CI (#4436)
* Avoid caching perf-libs in CI

* Retry downloads on transient 403 S3 failures seen in CI
2019-05-25 14:41:09 -07:00
aa3c00231a Fix should_update check to update EpochSlots in gossip (#4435)
automerge
2019-05-25 06:44:40 -07:00
d772a27936 Plumb ClusterInfoRepairListener (#4428)
automerge
2019-05-24 19:20:09 -07:00
0302f13b97 add datapoint for corrupt vote_account (#4424) 2019-05-24 18:34:56 -07:00
16b25d0874 Clone with https for Travis/repo with submodules (#4431) 2019-05-24 21:18:31 -04:00
c2dcbee6af cd within the subshell 2019-05-24 18:10:25 -07:00
1f71d05299 remove copying of forwarded packets (#4425)
automerge
2019-05-24 17:35:09 -07:00
bfa1c025fd Add rust bpf allocator (#4426) 2019-05-24 16:21:42 -07:00
8611b40074 Add argument to net/net to specify number of nodes (#4429)
Allows for testing different node counts without recreating the network.
2019-05-24 16:20:14 -07:00
916844d399 Fix replicator account setup in fullnode.sh (#4430) 2019-05-24 15:40:49 -07:00
4c9b7c9d2b Submit all incoming proofs as valid (#4377) 2019-05-24 14:49:10 -07:00
9843c3a5cb Restrict transaction fee payers to system accounts (#4198)
automerge
2019-05-24 13:06:55 -07:00
f56955a17c Use absolute path to env.sh 2019-05-24 12:27:12 -07:00
9784bbf154 Pay for storage transactions with a system account (#4423)
automerge
2019-05-24 11:04:05 -07:00
45642c4da1 Add path to env.sh 2019-05-24 09:56:07 -07:00
8eac199e8b Include perf-libs in release tarball (#4422) 2019-05-24 09:28:52 -07:00
2e251ccc5c De-fullnode variable names (#4420) 2019-05-24 04:31:39 -07:00
cf4bb70d80 Rename id to pubkey in cluster_info_repair_listener (#4421) 2019-05-24 04:31:32 -07:00
57f8a15b96 Fix issues in ClusterInfoRepairListener (#4418)
* Sort repairmen before shuffling so order is the same across all validators

* Reduce repair redundancy to 1 for now

* Fix local cache of roots so that 1) Timestamps are only updated to acknowledge a repair was sent 2) Roots are updated even when timestamps aren't updated to keep in sync with network

* Refactor code, add test
2019-05-24 00:47:51 -07:00
cfe5afd34c _id => _pubkey variable renaming (#4419)
* wallet: rename *_account_id to *_account_pubkey

* s/from_id/from_pubkey/g

* s/node_id/node_pubkey/g

* s/stake_id/stake_pubkey/g

* s/voter_id/voter_pubkey/g

* s/vote_id/vote_pubkey/g

* s/delegate_id/delegate_pubkey/g

* s/account_id/account_pubkey/g

* s/to_id/to_pubkey/g

* s/my_id/my_pubkey/g

* cargo fmt

* s/staker_id/staker_pubkey/g

* s/mining_pool_id/mining_pool_pubkey/g

* s/leader_id/leader_pubkey/g

* cargo fmt

* s/funding_id/funding_pubkey/g
2019-05-23 23:20:04 -07:00
94beb4b8c2 More fullnode -> validator renaming (#4414)
* s/fullnode_config/validator_config/g

* s/FullnodeConfig/ValidatorConfig/g

* mv core/lib/fullnode.rs core/lib/validator.rs

* s/Fullnode/Validator/g

* Add replicator-x.sh

* Rename fullnode.md to validator.md

* cargo fmt
2019-05-23 22:05:16 -07:00
50207a30ef Rename solana-fullnode to solana-validator redux (#4417) 2019-05-23 21:28:18 -07:00
35e8f966e3 add freeze_lock() and fix par_process_entries() failure to detect self conflict (#4415)
* add freeze_lock and fix par_process_entries failure to detect self conflict

* fixup

* fixup
2019-05-23 17:35:15 -07:00
943cd0a24a Add credit-only info to AccountMetadata (#4405)
* Add credit-only flag to AccountMeta, default to false

* Sort keys by is_credit_only within signed/unsigned groupings

* Process and de-dupe program keys along with other account keys

* Add message helper functions

* Fix test

* Improve comment

* s/is_credit_only/is_debitable

* Add InstructionKeys helper struct, and simplify program_position method
2019-05-23 18:19:53 -04:00
0b892b2579 Reduce 100ms to 1ms. (#4412)
automerge
2019-05-23 15:15:26 -07:00
fb2eac20bb Rename solana-fullnode to solana-validator (#4411) 2019-05-23 15:06:01 -07:00
b37d2fde3d Add storage mining pool (#4364)
* Add storage mining pool

* Set gossip port

* Add create-storage-mining-pool-account wallet command

* Add claim-storage-reward wallet command

* Create storage account upfront

* Add storage program to genesis

* Use STORAGE_ACCOUNT_SPACE

* Fix tests

* Add wallet commands to create validator/replicator storage accounts

* Add create_validator_storage_account()

* Storage stage no longer implicitly creates a storage account
2019-05-23 14:50:23 -07:00
6b35e16676 Turn on real PoH in perf testnets (#4407)
* Turn on real PoH in perf testnets

* enable real PoH for all testnets except "testnet"
2019-05-23 13:22:52 -07:00
6a9e0bc593 Change EpochSlots to use BtreeSet so that serialization/deserialization returns the same order (#4404)
automerge
2019-05-23 03:50:41 -07:00
591fd72e0b Implement listener for serving repairs through Repairman protocol (#4306)
* Make listener for serving repairs through Repairman protocol
2019-05-23 03:10:16 -07:00
2ed77b040a create_genesis_block() now returns a struct (#4403) 2019-05-22 20:39:00 -07:00
7ada8510c4 add slot_hashes to bank, remove phony slot_hashes_from_vote_instruction (#4401) 2019-05-22 19:07:56 -07:00
b8f6c17dee Don't filter transactions if we are buffering it locally (#4395)
automerge
2019-05-22 17:54:28 -07:00
2f976ae460 Dashboard update for real PoH performance (#4397) 2019-05-22 16:18:57 -07:00
36019cb1e3 Tweaks to real PoH based on perf testing (#4396)
* Some counters for real poh perf analysis

* more metrics

* Comment on CPU affinity change, and reduce hash batch size based on TPS perf

* review comments
2019-05-22 15:54:24 -07:00
99d2428041 Transaction format changes toward Credit-Only accounts (#4386)
* Add num_readonly_accounts slice

* Impl programs in account_keys

* Emulate current account-loading functionality using program-account_keys (breaks exchange_program_api tests)

* Fix test

* Add temporary exchange faucet id

* Update chacha golden

* Split num_credit_only_accounts into separate fields

* Improve readability

* Move message field constants into Message

* Add MessageHeader struct and fixup comments
2019-05-22 18:23:16 -04:00
c121498b5b Check that Rust project exists (#4393) 2019-05-22 15:09:59 -07:00
eef2bdf690 Add CPU affinity for PoH service thread (#4394)
automerge
2019-05-22 14:21:43 -07:00
190656967d Bump nix from 0.13.0 to 0.14.0 (#4382)
Bumps [nix](https://github.com/nix-rust/nix) from 0.13.0 to 0.14.0.
- [Release notes](https://github.com/nix-rust/nix/releases)
- [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/compare/v0.13.0...v0.14.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-22 14:07:35 -07:00
90e73515ed Remove ls which is innacurate since we never clean up the logs (#4392)
automerge
2019-05-22 13:11:20 -07:00
1d7a758c97 Bump perf libs version to fix out buffer sizing (#4385) 2019-05-22 13:00:03 -07:00
e5b7aead12 Cargo watch ignores local metrics (#4384) 2019-05-22 00:08:18 -07:00
578c2ad3ea add bank hash to votes (#4381) 2019-05-21 21:45:38 -07:00
de6838da78 change unwrap to expect where WSL sometimes aborts (#4375)
* change unwrap to expect where WSL sometimes aborts

* clippy
2019-05-21 21:34:51 -07:00
604071c5d8 Bump num-traits from 0.2.7 to 0.2.8 (#4379)
Bumps [num-traits](https://github.com/rust-num/num-traits) from 0.2.7 to 0.2.8.
- [Release notes](https://github.com/rust-num/num-traits/releases)
- [Changelog](https://github.com/rust-num/num-traits/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-traits/commits)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-21 21:27:00 -07:00
41a377013f fix spelling (#4378)
automerge
2019-05-21 17:13:21 -07:00
52d453d06f Reduce broadcast prints (#4368) 2019-05-21 17:02:19 -07:00
58295b825d introduce syscalls (#4373) 2019-05-21 15:19:41 -07:00
f6c7812fcc Rename Broker to Swapper (#4371) 2019-05-21 14:21:41 -07:00
2f7561e4ee Split SDK's BPF Rust-utils (#4369) 2019-05-21 13:39:27 -07:00
1cbd2372fc Revert 4271 workaround (#4367)
* Revert "#4271 not reproducable, remove workaround (#4363)"

This reverts commit ef0580bd3d.

* Remove old comment
2019-05-21 11:53:53 -07:00
28f948aa7f Multi rust projects (#4362) 2019-05-21 11:22:33 -07:00
c9ba9e4eb7 Add storage space constant (#4366)
automerge
2019-05-21 11:07:13 -07:00
f877fb8c8f Don't print leader update message unless leader actually updates (#4365) 2019-05-21 11:06:56 -07:00
772ba41ede Cargo.lock 2019-05-21 08:08:07 -07:00
6374e69a69 Add mining pool wallet commands (#4360)
automerge
2019-05-21 07:32:38 -07:00
ef0580bd3d #4271 not reproducable, remove workaround (#4363) 2019-05-20 23:45:09 -07:00
1a77486f8e Make RootedSlotsIterator for traversing slots on the root fork (#4361) 2019-05-20 23:09:00 -07:00
ead15d294e add get_epoch_vote_accounts rpc (#4317)
* add get_epoch_vote_accounts rpc

* fixups

* documentation and type updates
2019-05-20 22:21:13 -07:00
1acfcf3acf Fix storage-keypair 2019-05-20 19:54:37 -07:00
d15e248cdb Add bootstrap storage account to genesis (#4359)
* Add bootstrap storage account to genesis

* Add storage account genesis command to run.sh

* Update airdrop for all validators

* Remove unhelpful Short for arg

* Set the correct program owner
2019-05-20 19:46:15 -07:00
f1e5edee14 Modify Roots Column To Support Multiple Roots (#4321)
* Fix 1) Roots column family to handle storing multiple slots, 2) Store all slots on the rooted path in the roots column family
2019-05-20 19:04:18 -07:00
7153abd483 Revert "Performance tweaks (#4340)" (#4350)
* Revert "Performance tweaks (#4340)"

This reverts commit 55cee5742f.

* Revert Rc change
2019-05-20 17:48:42 -07:00
90fb5d074d Bump num-traits from 0.2.6 to 0.2.7 (#4355)
Bumps [num-traits](https://github.com/rust-num/num-traits) from 0.2.6 to 0.2.7.
- [Release notes](https://github.com/rust-num/num-traits/releases)
- [Changelog](https://github.com/rust-num/num-traits/blob/master/RELEASES.md)
- [Commits](https://github.com/rust-num/num-traits/compare/num-traits-0.2.6...num-traits-0.2.7)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-20 16:35:14 -06:00
af82b0dce9 Fix sending root slots instead of current slot (#4354)
automerge
2019-05-20 15:01:55 -07:00
d4da2fbacd fix bench warnings (#4356) 2019-05-20 14:32:23 -07:00
77efe95730 Order "install" correctly 2019-05-20 13:54:15 -07:00
86e03a6d1b support issuing vote instructions from system account (#4338)
* issue vote instructions from system account

* fixup

* bring back KeypairUtil
2019-05-20 13:32:32 -07:00
114e2989fa Improve PoH unit test asserts (#4351)
automerge
2019-05-20 13:02:44 -07:00
7024c73e9b Revert banking threads increase which seems to be slower in testing. (#4349) 2019-05-20 11:42:37 -07:00
6d418aa3f1 Use rust_stable 2019-05-20 10:48:48 -07:00
f079a78c5e Remove fee arg from system_transaction::* (#4346)
automerge
2019-05-20 10:03:19 -07:00
6365c4c061 Use cleanup (#4347) 2019-05-20 09:58:27 -07:00
55cee5742f Performance tweaks (#4340)
* Use Rc to prevent clone of Packets

* Fix min => max in banking_stage threads.

Coalesce packet buffers better since a larger batch will
be faster through banking and sigverify.

Deconstruct batches into banking_stage from sigverify since
sigverify likes to accumulate batches but then a single banking_stage
thread will be stuck with a large batch. Maximize parallelism by
creating more chunks of work for banking_stage.
2019-05-20 09:15:00 -07:00
034eda4546 Fix a couple replicator nits (#4345)
automerge
2019-05-20 08:55:45 -07:00
44ff25d044 Update readme
* rustfmt no longer in preview
* since virtual manifest, cargo commands no longer require `--all`
2019-05-19 19:41:15 -06:00
a7e160e5c4 Add datapoint metrics to dashboard (#4343)
automerge
2019-05-19 15:07:03 -07:00
6283cc916d Add SOLANA_METRICS_MAX_POINTS_PER_SECOND env var (#4342) 2019-05-19 13:56:52 -07:00
4b6aca6120 Bump tempfile from 3.0.7 to 3.0.8 (#4341)
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.0.7 to 3.0.8.
- [Release notes](https://github.com/Stebalien/tempfile/releases)
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/NEWS)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.0.7...v3.0.8)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-18 19:56:51 -07:00
20b2be6e0b Enable real PoH on beta testnet as well 2019-05-18 19:45:00 -07:00
cbebc7a80c Expand shortArgs correctly 2019-05-18 19:00:48 -07:00
06eb2364f2 Handle missed slots in storage stage (#4337)
* Handle missed slots in storage stage

* Fix test compile

* Make test use the new missed slot support
2019-05-18 15:24:50 -07:00
167890ca63 Set rust_version 2019-05-18 14:48:53 -07:00
392a39dd54 Poh subsystem cleanup, genesis plumbing, enable real PoH on edge testnet (#4292)
* Remove unused PohServiceConfig::Step

* Clarify variable name

* Poh::hash() now takes an iteration counter

* man -> max

* Inline functions with single call site

* Move PohServiceConfig into GenesisBlock

* Add plumbing to enable real PoH on testnets

* Batch hashes to improve PoH hash rate

* Ensure a constant hashes_per_tick

* Remove PohEntry mixin field

* Poh/PohEntry no longer maintains tick_height

* Ensure a constant hashes_per_tick

* ci/localnet-sanity.sh: Use real PoH

* Rework Poh/PohService to keep PohRecorder unlocked as much as possible while hashing
2019-05-18 14:01:36 -07:00
7e1a7862db test_process_store_ok() now pays with a system account (#4339)
automerge
2019-05-17 20:17:50 -07:00
458ae3fdac Switch to instances with AVX-512 if possible for better interop with dev machines (#4328)
automerge
2019-05-17 20:06:07 -07:00
431cc82032 add Transaction::partial_sign() (#4333)
* add partial sign

* nits
2019-05-17 18:55:57 -07:00
18c6729d6c Bump tar from 0.4.25 to 0.4.26 (#4330)
Bumps [tar](https://github.com/alexcrichton/tar-rs) from 0.4.25 to 0.4.26.
- [Release notes](https://github.com/alexcrichton/tar-rs/releases)
- [Commits](https://github.com/alexcrichton/tar-rs/compare/0.4.25...0.4.26)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-17 17:46:12 -07:00
9476fe5ce3 Use log levels for datapoint metrics (#4335)
* Use log levels for datapoint metrics

* address review comments

* fix cyclomatic complexity
2019-05-17 17:34:05 -07:00
788290ad82 Rework Storage Program to accept multiple proofs per segment (#4319)
automerge
2019-05-17 14:52:54 -07:00
6b5bcfaa58 Bump libc from 0.2.54 to 0.2.55 (#4324)
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.54 to 0.2.55.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.54...0.2.55)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-17 13:24:12 -07:00
4ed0cded9c Rm metrics docker even if not running (#4329) 2019-05-17 13:13:38 -07:00
035a364122 Add expect to get better errors on crash (#4327) 2019-05-17 12:49:41 -07:00
b114bc3674 Add benchmark for sigverify stage (#4320) 2019-05-17 11:09:42 -07:00
bc74ee7117 Common Rust-BPF utilities and types (#4325) 2019-05-17 11:04:29 -07:00
b2ce5dc9f5 Adjust log level for counter metrics (#4323) 2019-05-17 07:00:06 -07:00
e920191de0 Rate limit metrics per log level (#4313)
* Rate limit metrics per log level

* fix tests
2019-05-16 22:27:05 -07:00
39e85a3e53 kill some bs58 (#4316)
* kill some bs58

* fixup
2019-05-16 21:43:18 -07:00
41156da4ca Sync run.sh with fd3f2cb (#4322)
automerge
2019-05-16 21:32:23 -07:00
9271ba0039 Cleanup Rust BPF program building (#4318) 2019-05-16 17:35:42 -07:00
b3e45fd6b7 Add erroring tx to unexpected validator error logging (#4314)
* Add tx logging to error

* Add tx logging to unexpected validator errors
2019-05-16 14:59:22 -07:00
7bfb60f82e add impl FromStr for Signature (#4315)
automerge
2019-05-16 14:54:31 -07:00
359c50f1b3 cp -a includes -r (#4312) 2019-05-16 12:24:04 -07:00
fff1631a8b Return a better error when a program account isn't found (#4310) 2019-05-16 11:32:27 -06:00
7d42ae30d9 Update Rust-BPF to 2018 Edition (#4307) 2019-05-16 09:12:33 -07:00
87414de3e2 switch over to passive stakes (#4295)
* add failing test

* switch over to passive stakes

* test multiple stakers
2019-05-16 08:23:31 -07:00
a0ffbf50a5 Correctly remove replicator from data plane after its done repairing (#4301)
* Correctly remove replicator from data plane after its done repairing

* Update discover to report nodes and replicators separately

* Fix print and condition to be spy
2019-05-16 07:14:58 -07:00
d40b66ff7b Bump solana_rbpf from 0.1.10 to 0.1.11 (#4304)
Bumps [solana_rbpf](https://github.com/solana-labs/rbpf) from 0.1.10 to 0.1.11.
- [Release notes](https://github.com/solana-labs/rbpf/releases)
- [Commits](https://github.com/solana-labs/rbpf/commits/v0.1.11)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-16 01:07:10 -07:00
abd7f6b090 Change slot_meta_iterator() to return an iterator not a cursor (#4303)
automerge
2019-05-15 18:28:23 -07:00
d8735df1de Update replicator to use cluster_info instead of cached client (#4302) 2019-05-15 18:14:04 -07:00
481853e1b1 Bump reqwest from 0.9.16 to 0.9.17 (#4296)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.9.16 to 0.9.17.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.9.16...v0.9.17)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-15 16:02:17 -07:00
778bcbce50 Reduce log level for frequent log message and frequency for metrics datapoint (#4300)
automerge
2019-05-15 16:01:17 -07:00
fd3f2cb910 Add Storage accounts for all nodes (#4298)
* Setup storage keypairs for all nodes

* Clean up naming

* clippy

* Update arg value_names
2019-05-15 15:19:29 -07:00
915956b94b Remove disable leader rotation option (#4299)
automerge
2019-05-15 15:16:45 -07:00
4576250342 Fix bug in storage processor and remove duplicate Constant (#4294)
* Fix bug in storage processor and remove duplicate Constant

* Add test

* Bump replicator timeout
2019-05-15 13:28:56 -07:00
2bef1b0433 Use rust-bpf-sysroot release branches (#4293) 2019-05-15 12:45:48 -07:00
628128b376 add passive staking to local_cluster (#4285)
* add passive staking to local_cluster

* add stake_program to genesis

* use equal stakes in local_cluster tests
2019-05-15 12:15:31 -07:00
916017ca2c Fix repair for a range of slots (#4286)
* Fix repair for a range of slots

* Delete RepairInfo
2019-05-15 11:37:20 -07:00
3204a00e73 Update rust-bpf-sysroot to latest (#4291) 2019-05-15 09:53:44 -07:00
1d327a5167 Bump bincode from 1.1.3 to 1.1.4 (#4290)
Bumps [bincode](https://github.com/TyOverby/bincode) from 1.1.3 to 1.1.4.
- [Release notes](https://github.com/TyOverby/bincode/releases)
- [Commits](https://github.com/TyOverby/bincode/compare/v1.1.3...v1.1.4)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-15 09:47:55 -06:00
6e4f9cedf2 Fix Plum Tree link 2019-05-15 08:15:22 -07:00
a79fbbafc9 SlotMeta is incorrectly updated on insertion of erasure blobs (#4289)
* Fix put_coding_blob_bytes to properly update slotmetas and chaining
2019-05-15 00:28:31 -07:00
1d54d29076 Fix reading ledger for chacha encrypt (#4288) 2019-05-14 16:59:17 -07:00
10b9a4806b Fix incorrect genesis blockhashes on restart (#4287) 2019-05-14 16:32:44 -07:00
0c1191c3ee rework staking_utils (#4283) 2019-05-14 16:15:51 -07:00
18b386cd10 remove unused make_active_set_entries (#4284) 2019-05-14 15:08:49 -07:00
714b8c7fc8 Bump tokio from 0.1.18 to 0.1.20 (#4280)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 0.1.18 to 0.1.20.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-0.1.18...tokio-0.1.20)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-14 15:30:56 -06:00
216e9a61a0 expose stake directly from bank (#4281) 2019-05-14 13:35:14 -07:00
0f498e6265 remove unused filter_zero_balance (#4279) 2019-05-14 10:44:29 -07:00
e8ad822111 get program names from programs (#4273)
* get program names from programs

* fixup
2019-05-14 10:44:16 -07:00
65a82ebf50 Fix broken tip (#4278)
automerge
2019-05-14 02:35:32 -07:00
727802684c Use 20 bytes signature slice for cache purposes (#4260) 2019-05-13 22:53:10 -07:00
e20a8329d3 Add API to iterate over slot's blobs (#4276) 2019-05-13 22:04:54 -07:00
88c2d0fad4 Add genesis blockhash to replicators and blob filter for window (#4275)
* Add genesis blockhash to replicators and blob filter for window

* Fixes to mining submission and ledger download

* Add todo over sleep

* Update log
2019-05-13 21:19:51 -07:00
3bd921264a Move EpochSchedule into own module (#4272) 2019-05-13 16:24:32 -07:00
7501ed65e5 Initialize and Update EpochSlots in RepairService (#4255)
* Initialize EpochSlots in RepairService

* Fix flaky test
2019-05-13 15:37:50 -07:00
2eaa64c4e8 valhashators -> validators 2019-05-13 15:29:23 -07:00
c9b86018c6 Filter out all unprocessed transactions before forwarding them (#4266)
* Filter out all unprocessed transactions before forwarding them

* fix clippy
2019-05-13 14:40:05 -07:00
a4fb01b42b Add +x 2019-05-13 14:30:44 -07:00
0d2574f8f0 get DLL names from programs that made the DLL (#4269)
* get program names from programs

* fixup
2019-05-13 14:17:44 -07:00
796000e96f Improve erasure metrics (#4268)
* Improve erasure metrics

* Simplify metrics submission
2019-05-13 16:04:43 -05:00
e2f00dc205 Cargo.toml implied (#4270)
automerge
2019-05-13 13:51:42 -07:00
5e91f8f59d Update reported tx count to exclude errors (#4201) 2019-05-13 13:23:52 -07:00
e2830f5b0e Add rate limit to metrics datapoint submission (#4237)
Cleanup

Raise limit on submission threshold

Pick nits and add metrics point

fmt

Fixup compiler warning

Cleanup if-else

Append new point to vec rather than submit
2019-05-13 14:17:25 -06:00
a2e3a92b01 Extend GetBlockHash RPC API to include the fee scehdule for using the returned blockhash (#4222) 2019-05-13 12:49:37 -07:00
23c696706b add stake_accounts to banks' caches (#4267) 2019-05-13 12:33:23 -07:00
1393d26f63 Remove obsolete internal multinode-demo/ logging (#4265) 2019-05-13 10:51:18 -07:00
1b68da7572 Use solana-ed25519-dalek v0.2.0 (#4264)
automerge
2019-05-13 09:51:59 -07:00
8542006259 Config instructions now only require one key (#4258) 2019-05-12 22:47:12 -07:00
426d06b89b Improve target/ cache logging 2019-05-12 22:16:23 -07:00
06378d6db6 Refine killNode logging 2019-05-12 21:21:31 -07:00
dccfe31e8c Increase target cache size for coverage build 2019-05-12 21:21:31 -07:00
1dce5976cf Disable node restart in localnet-sanity.sh 2019-05-12 21:21:31 -07:00
340d01665c Avoid generating default keypair 2019-05-12 21:21:31 -07:00
50f79e495e net/ improvements (#4257)
automerge
2019-05-11 22:54:50 -07:00
dd12db2f06 Correctly handle more zones than additional nodes 2019-05-11 14:47:27 -07:00
1afccb7351 Add more regions to the testnet 2019-05-11 14:12:13 -07:00
bfc65e829e Use zone[0] for any left over nodes 2019-05-11 14:07:36 -07:00
eb4515525d Bump ws from 0.8.0 to 0.8.1 (#4251)
Bumps [ws](https://github.com/housleyjk/ws-rs) from 0.8.0 to 0.8.1.
- [Release notes](https://github.com/housleyjk/ws-rs/releases)
- [Changelog](https://github.com/housleyjk/ws-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/housleyjk/ws-rs/compare/v0.8.0...v0.8.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-10 21:06:33 -07:00
55f5f6a033 Make links clickable 2019-05-10 19:49:45 -07:00
7ae421eaf6 Update release version in book (#4254) 2019-05-10 18:43:08 -06:00
e7da2c0931 Add validator registration link (#4229) 2019-05-10 15:14:03 -07:00
133be2df51 Check for transaction forwarding delay to detect an expired transaction before forwarding it (#4249)
Also refactored code for forwarding packets, and added test for it
2019-05-10 14:28:38 -07:00
06a93dcb43 Move to solana-ed25519-dalek (#4248) 2019-05-10 15:26:46 -06:00
ad7f04a245 Add genesis_blockhash to erasure blobs so they don't get filtered out by window_service (#4247) 2019-05-10 14:06:31 -07:00
0da6e1af14 Repair Design Proposal (#3402)
* Update information about existing repair protocol + new repairman proposal
2019-05-10 13:50:23 -07:00
576524f13b Updates to storage proposal with more storage contract details (#3373) 2019-05-10 09:19:06 -07:00
f567877d1d Cleanup metrics (#4230) 2019-05-10 08:33:58 -07:00
9881820444 RepairService saves db_iterator instead of reconstructing on every search (#4242) 2019-05-09 19:57:51 -07:00
ba8f49366d passive staking 4 (#4240)
* support passive staking with wallet, use it

* fixups

* clippy

* cleanup app generation in wallet, finish fullnode.sh staking

* _id and _keypair => pubkey
use keygen, not wallet to get pubkey

* found 'em
2019-05-09 19:31:42 -07:00
81fa69d347 Revert "Create bank snapshots (#3671)" (#4243)
This reverts commit abf2b300da.
2019-05-09 19:27:27 -07:00
abf2b300da Create bank snapshots (#3671)
* Be able to create bank snapshots

* fix clippy

* load snapshot on start

* regenerate account index from the storage

* Remove rc feature dependency

* cleanup

* save snapshot for slot 0
2019-05-09 19:27:06 -07:00
a8254fd258 Clear stale ledger on fullnode startup if necessary (#4238) 2019-05-09 17:09:36 -07:00
b15848de3b Bump tar from 0.4.24 to 0.4.25 (#4239)
Bumps [tar](https://github.com/alexcrichton/tar-rs) from 0.4.24 to 0.4.25.
- [Release notes](https://github.com/alexcrichton/tar-rs/releases)
- [Commits](https://github.com/alexcrichton/tar-rs/compare/0.4.24...0.4.25)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-09 17:07:31 -07:00
ab3c988146 API for getting first and last slots in epoch (#4241)
automerge
2019-05-09 16:06:57 -07:00
575a0e318b Add newly completed slots signal to Blocktree (#4225)
* Add channel to blocktree for communicating when slots are completed

* Refactor RepairService options into a RepairStrategy
2019-05-09 14:10:04 -07:00
a031b09190 Add replicator support to multinode demo (#4221)
automerge
2019-05-09 13:43:39 -07:00
df43e721e3 Exit 1 on ledger verification failure 2019-05-09 12:05:51 -07:00
5f72650c7f add derive Debug to Keypair (#4236) 2019-05-09 11:41:11 -07:00
5d0d467287 fix banking_stage benches (#4231) 2019-05-09 11:20:26 -07:00
994515d0f2 add impl PartialEq for Keypair (#4233)
* add-impl-PartialEq-for-Keypair

* clippy

* do the TODO, improve wrapper comments
2019-05-09 11:03:14 -07:00
1e949caa7f Move airdrop retries fully out of bash (#4234)
automerge
2019-05-09 09:48:27 -07:00
f2b727b534 Update mint keypair filename 2019-05-09 07:27:13 -07:00
f7680752e7 make gen_keypair_file take &str (#4232)
automerge
2019-05-08 23:00:48 -07:00
da4c37beec multinode-demo/ grooming (#4226)
* Rename leader to entrypoint

* The fullnode identity keypair can now be provided

* Rename _id to _keypair
2019-05-08 19:59:22 -07:00
d486d2b8ce Consolidate default arg parsing (#4224)
automerge
2019-05-08 19:12:43 -07:00
bba94c43b9 Add BankForks to RepairService (#4223)
automerge
2019-05-08 18:51:43 -07:00
9cdffc7d64 Don't push empty vecs into the unprocessed buffers (#4214) 2019-05-08 17:58:07 -07:00
5a86f2506d Remove unnecessary retrying (#4219) 2019-05-08 16:20:37 -07:00
518227eac0 add-rpc_client.get_account (#4218) 2019-05-08 15:50:23 -07:00
b8fd51e97d Add new gossip structure for supporting repairs (#4205)
* Add Epoch Slots to gossip

* Add new gossip structure to support Repair

* remove unnecessary clones

* Setup dummy fast repair in repair_service

* PR comments
2019-05-08 13:50:32 -07:00
965c1e0000 staking plumbing part 3, 3.5 (#4216) 2019-05-08 12:56:11 -07:00
a80176496d add /target/ to .gitignore files for all crates (#4217)
* add /target/ to .gitignore files for all crates

* shellcheck
2019-05-08 12:15:05 -07:00
5719b8f251 Change remote node's ssh config to allow more login retries (#4215)
automerge
2019-05-08 11:20:06 -07:00
1a2b131ceb Don't forward transactions that are expired or failed signature check (#4199) 2019-05-08 10:32:25 -07:00
349306ddf7 Bump proc-macro2 from 0.4.27 to 0.4.29 (#4180)
Bumps [proc-macro2](https://github.com/alexcrichton/proc-macro2) from 0.4.27 to 0.4.29.
- [Release notes](https://github.com/alexcrichton/proc-macro2/releases)
- [Commits](https://github.com/alexcrichton/proc-macro2/compare/0.4.27...0.4.29)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-08 09:03:30 -07:00
791ee411a5 Add root to bank forks (#4206) 2019-05-07 23:34:10 -07:00
f690c64375 Disable solana-upload-perf until performance can be debugged (#4210) 2019-05-07 23:02:09 -07:00
427963f554 Core depends on vote and budget program directly (#4204) 2019-05-07 22:41:47 -07:00
b0f2220ef6 Fix solana-upload-perf log folding. Upload bench output too (#4208) 2019-05-07 22:30:42 -07:00
908b48bf0e Increase test-stable build timeout 2019-05-07 22:23:43 -07:00
b49f8c0984 reduce replicode, introduce passive staking support (#4207) 2019-05-07 22:22:43 -07:00
7609a007c6 Add FeeCalculator to the genesis block (#4196) 2019-05-07 20:28:41 -07:00
674a49f8d7 Bump serde_derive from 1.0.90 to 1.0.91 (#4172)
Bumps [serde_derive](https://github.com/serde-rs/serde) from 1.0.90 to 1.0.91.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.90...v1.0.91)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-07 20:05:41 -07:00
d10bde656a Pass payer key into get_keys() (#4203) 2019-05-07 18:48:31 -07:00
401764ddb1 add create_delegate_stake_account (#4197) 2019-05-07 17:08:49 -07:00
69eeb7cf08 Fix parent record locks usage in child banks (#4159)
* Introduce record locks on txs that will be recorded

* Add tests for LockedAccountsResults

* Fix broken bench

* Exit process_entries on detecting conflicting locks within same entry
2019-05-07 15:51:35 -07:00
55e3b7d380 Storage transactions are now paid for by a system account (#4193)
* Pay program loading fees from a system account

* Pay transaction fees from a system account
2019-05-07 15:01:10 -07:00
d9e18a71ec Pay program loading fees from a system account (#4190) 2019-05-07 15:00:54 -07:00
2107e15bd3 Reduce Avalanche redundancy and implement traditional fanout (#4174)
* Reduce Avalanche redundancy and implement traditional fanout

* Revert tiny fanout

* Update diagrams and docs based on review comments
2019-05-07 13:24:58 -07:00
4f3b22d04e Minor code restyling, no functional changes 2019-05-07 12:35:29 -07:00
2c78a93001 GenesisBlock::new(X) => create_genesis_block(X) 2019-05-07 12:34:17 -07:00
2621aeee82 Set default wallet rpc port correctly 2019-05-07 11:37:51 -07:00
8e400fc4bd rework genesis (passive staking groundwork) (#4187)
* rework genesis

* fixup
2019-05-07 11:16:22 -07:00
29c2a63c8b Retry transactions that failed due to account lock (#4184)
* added test
2019-05-07 10:23:02 -07:00
736ada4e21 Bump dtoa from 0.4.3 to 0.4.4 (#4178)
Bumps [dtoa](https://github.com/dtolnay/dtoa) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/dtolnay/dtoa/releases)
- [Commits](https://github.com/dtolnay/dtoa/compare/0.4.3...0.4.4)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-07 07:31:57 -07:00
3df9b44d4c Bump nom from 4.2.2 to 4.2.3 (#4182)
Bumps [nom](https://github.com/Geal/nom) from 4.2.2 to 4.2.3.
- [Release notes](https://github.com/Geal/nom/releases)
- [Changelog](https://github.com/Geal/nom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Geal/nom/compare/4.2.2...4.2.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-07 07:31:48 -07:00
7225b89142 Bump globset from 0.4.2 to 0.4.3 (#4176)
Bumps [globset](https://github.com/BurntSushi/ripgrep) from 0.4.2 to 0.4.3.
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.2...globset-0.4.3)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-07 07:15:34 -07:00
0cc0d3ab7a Bump socket2 from 0.3.8 to 0.3.9 (#4186)
Bumps [socket2](https://github.com/alexcrichton/socket2-rs) from 0.3.8 to 0.3.9.
- [Release notes](https://github.com/alexcrichton/socket2-rs/releases)
- [Commits](https://github.com/alexcrichton/socket2-rs/compare/0.3.8...0.3.9)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-07 07:15:04 -07:00
88d9618788 Bump futures from 0.1.25 to 0.1.26 (#4179)
Bumps [futures](https://github.com/rust-lang-nursery/futures-rs) from 0.1.25 to 0.1.26.
- [Release notes](https://github.com/rust-lang-nursery/futures-rs/releases)
- [Changelog](https://github.com/rust-lang-nursery/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang-nursery/futures-rs/compare/0.1.25...0.1.26)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-07 07:14:48 -07:00
57038529e0 Bump hex-literal from 0.1.4 to 0.2.0 (#4185)
Bumps [hex-literal](https://github.com/RustCrypto/utils) from 0.1.4 to 0.2.0.
- [Release notes](https://github.com/RustCrypto/utils/releases)
- [Commits](https://github.com/RustCrypto/utils/compare/hex-literal-v0.1.4...hex-literal-v0.2.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-07 07:14:16 -07:00
5c25eae631 Bump tokio-sync from 0.1.4 to 0.1.5 (#4177)
Bumps [tokio-sync](https://github.com/tokio-rs/tokio) from 0.1.4 to 0.1.5.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-sync-0.1.4...tokio-sync-0.1.5)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-06 22:06:09 -07:00
b8b4d33f72 Bump h2 from 0.1.17 to 0.1.18 (#4175)
Bumps [h2](https://github.com/carllerche/h2) from 0.1.17 to 0.1.18.
- [Release notes](https://github.com/carllerche/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/carllerche/h2/compare/v0.1.17...v0.1.18)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-06 21:42:22 -07:00
673a9417ef Bump serde from 1.0.90 to 1.0.91 (#4171)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.90 to 1.0.91.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.90...v1.0.91)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-06 21:41:30 -07:00
3fd9aada8b Add missing modules to workspace (#4183) 2019-05-06 22:40:54 -06:00
453fdb9e28 Add support for local metric collection/viewing (#4170) 2019-05-06 16:44:06 -07:00
3f6a79b078 Add a validator node proposal (#3035)
automerge
2019-05-06 13:41:28 -07:00
e9f80e5542 Don't use default ticks per slot in calculating next slot leader (#4169) 2019-05-06 11:23:06 -07:00
694d28acf8 remove some boilerplate (#4143) 2019-05-06 10:11:18 -07:00
88fdba5aca Temp: bump test-bench timeout 2019-05-06 10:00:00 -07:00
a19df7a36c Add type annotations for external crates (#4125) 2019-05-06 10:11:50 -06:00
9b50583641 wallet: add --url, remove --host/--rpc-host/--rpc-port/-rpc-tls (#4153)
Also by default the wallet now talks to testnet.solana.com instead of
localhost
2019-05-06 07:38:26 -07:00
71f9b44687 Add Confirmations parameter to RPC Subscriptions (#4154)
* Add optional depth parameter to pubsub, and store in subscriptions

* Pass bank_forks into rpc_subscription; add method to check depth before notify and impl for account subscriptions

* Impl check-depth for signature subscriptions

* Impl check-depth for program subscriptions

* Plumb fork id through accounts

* Use fork id and root to prevent repeated account notifications; also s/Depth/Confirmations

* Write tests in terms of bank_forks

* Fixup accounts tests

* Add pubsub-confirmations tests

* Update pubsub documentation
2019-05-06 08:31:50 -06:00
0139e5db21 Correct blockstreamer node args 2019-05-04 08:22:36 -07:00
586fb15c2c fullnode positional arguments may now be mixed with optional arguments (#4151) 2019-05-03 20:49:24 -07:00
297328ff9a Fix improper tick sleeping time in test (#4155)
automerge
2019-05-03 20:15:10 -07:00
6b3384c205 Bump serde_yaml from 0.8.8 to 0.8.9 (#4127)
Bumps [serde_yaml](https://github.com/dtolnay/serde-yaml) from 0.8.8 to 0.8.9.
- [Release notes](https://github.com/dtolnay/serde-yaml/releases)
- [Commits](https://github.com/dtolnay/serde-yaml/compare/0.8.8...0.8.9)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-03 19:45:09 -07:00
3ef961fe37 Record poh ticks even when the node is not in leader schedule (#4148)
* remove obsolete test
2019-05-03 16:41:19 -07:00
a7b695c27a Change replicators to slot-based (#4118) 2019-05-03 16:27:53 -07:00
5bb75a5894 Fix roots never being purged (#4134) 2019-05-03 15:17:37 -07:00
f3f416b7ba Rename --network argument to --entrypoint (#4149) 2019-05-03 15:00:19 -07:00
31b74bdf0b Display release date in the local timezone (#4144) 2019-05-03 14:54:25 -07:00
ed48d8323c Reduce locking in Blocktree (#4075)
* Reduce lock contention in blocktree

* Store root slot in separate column
2019-05-03 16:46:02 -05:00
f91627a230 Remove extra-fullnode-args.sh (#4142) 2019-05-03 13:32:59 -07:00
f9c093022c multinode-demo/: Merge bootstrap-leader.sh into fullnode.sh (#4139) 2019-05-03 12:33:48 -07:00
7fe3c75c6b Add a node-specific ip echo service to remove dependency on ifconfig.co (#4137) 2019-05-03 11:01:35 -07:00
c8ed41167a Factor tune_system() out of multinode-demo/ (#4138)
* Remove x bit from ulimit-n.sh

* Factor tune_system() out of multinode-demo/
2019-05-03 10:40:02 -07:00
5b2a82a951 Fix validator confirmation graph y axis scale (#4136) 2019-05-02 19:51:56 -07:00
441e76ebeb Index buffered transactions at the correct offset (#4126)
* tests
2019-05-02 19:05:53 -07:00
c2dfb9900e Revert "Change forwarded metrics to be in line with fetch stage metrics (#4068)" (#4135)
automerge
2019-05-02 17:36:19 -07:00
916458e132 Change erasure set size to 8:8 (#4129)
* Change erasure set size to 8:8

* Change tests to be agnostic to exact set size and ratio

* Add convenience methods for setting presence
2019-05-02 19:04:40 -05:00
ffb15578ce remove cargo install cargo-audit from CI (#4123) 2019-05-02 15:35:47 -07:00
abcbbb925f push latest, too (#4131) 2019-05-02 15:33:08 -07:00
059755fe59 install mdbook and svgbob_cli (#4128) 2019-05-02 15:32:31 -07:00
ae12dc2c75 Add specs to testnet participation guide (#4078) 2019-05-02 15:21:05 -07:00
37b5c6afaa install cargo audit (#4122) 2019-05-02 13:16:21 -07:00
92ed7b36a2 Bump libc from 0.2.53 to 0.2.54 (#4124)
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.53 to 0.2.54.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.53...0.2.54)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-02 13:11:29 -06:00
379d2e6d95 add support for single-crate coverage to help iterate, update to latest grcov (#4085)
* add support for single-crate coverage to help iterate, update to latest grcov

* shellcheck

* fixup

* remove unused

* install grcov before setting RUSTFLAGS ;)

* rely on nightly having grcov installed
2019-05-01 23:33:28 -07:00
7f75cc8906 update nightly to 2019-05-01 (#4111)
* update nightly to 2019-05-01

* cargo fmt

* cargo fmt

* increase bench timeout
2019-05-01 20:08:42 -07:00
1ab5098576 Move get_clients into gossip_service (#4109) 2019-05-01 17:14:01 -07:00
598f765960 Fix net.sh for running on macos (#4107)
automerge
2019-05-01 16:03:35 -07:00
aac626c2c2 Add sample_txs function to perf_utils shared crate (#4104)
Shared code between bench-tps and bench-exchange
2019-05-01 15:58:35 -07:00
3eec3cfac2 Cleanup banking stage in lieu of recent transaction forwarding changes (#4101) 2019-05-01 15:13:10 -07:00
5eee9e62e5 Add swapper back-off (#4088)
* Add swapper back-off

* Reset back-off if bench-exchange suspects back-log

* nudge

* nudge
2019-05-01 14:29:57 -07:00
a7d18125d3 install grcov (#4097) 2019-05-01 14:27:17 -07:00
8202310073 Minor update to gossip spy command (#4103) 2019-05-01 14:25:26 -07:00
1e2ba110eb Bump reqwest from 0.9.15 to 0.9.16 (#4089)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.9.15 to 0.9.16.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.9.15...v0.9.16)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-05-01 14:24:59 -06:00
62c9b7d850 Fix and un-ignore bench-tps local_cluster test (#4019)
* un-ignore bench-tps local_cluster test

And add bench_tps_fund_keys test.

* Unify generate_and_airdrop logic for tests
2019-05-01 13:21:45 -07:00
4f18fc836f Forward transactions to the next slot leader (#4092)
- this ensures that transactions will reach in time for the next node to process them
2019-05-01 11:37:29 -07:00
950d8494ba earlyoom: Stop using unsupported -k option (#4096)
automerge
2019-05-01 11:29:02 -07:00
cb528af4e2 fix accounts_db storage.reset() (#4094)
* fix accounts_db storage.reset()

* fix compilation errors, remove unused, fix test_accounts_grow() failure
2019-05-01 09:27:13 -07:00
ad27c30623 Cleanup bench-exchange messages (#4093) 2019-04-30 23:09:33 -07:00
9add8d0afc Add alternative to Spy Nodes that can fully participate in Gossip (#4087)
automerge
2019-04-30 16:42:56 -07:00
af2e7ea285 Add 1 decimal to validator confirmation (#4084) 2019-04-30 16:37:03 -07:00
675a78aaa1 get vote_instruction off bank for tests (#4086)
* get vote_instruction off bank for tests

* clippy
2019-04-30 15:11:08 -07:00
408bdbce7a Add non_default_stream parameter to cuda_verify (#4079) 2019-04-30 13:34:46 -07:00
1a259d4a3f Prevent Requests/Caching of leader schedules for epochs beyond confirmed roots (#4033)
automerge
2019-04-30 13:23:21 -07:00
c5f8b4960c Stop nodes in parallel 2019-04-30 10:42:59 -07:00
21f845ed39 Use more -w 2019-04-30 09:57:14 -07:00
7a369df9a7 Add flag to skip slow extras when deploying a large testnet 2019-04-30 09:26:50 -07:00
f02ec31c68 Flip if/else 2019-04-30 08:56:53 -07:00
d21fa4a177 v0.14: various net/ fixes for large clusters (#4080)
* net.sh: Add -F to discard validator nodes that didn't bootup successfully

* Relax sanity node count when validator bootup failure is permitted

* Less sanity for testnet-demo

* net.sh: Add -F to discard validator nodes that didn't bootup successfully
2019-04-29 21:38:32 -07:00
bd0871cbe7 Update release doc to include testnet update instuctions (#4066)
* Update release doc to include testnet update instuctions

* Fixup headers and pick nits

* Remove outdated testnet behavior
2019-04-29 19:40:18 -06:00
2604f8ac0a Move implemented functionality into the Implemented Proposals section (#4057) 2019-04-29 17:29:41 -06:00
a7574f8657 Cleanup metrics dashboard (#4072) 2019-04-29 15:52:04 -07:00
73f250f03a Make minimum warmup period 32 slots long (#4031)
* Make minimum warmup period 32 slots long

* PR fixes
2019-04-29 15:26:52 -07:00
bae0aadafa Remove Bench Exchange Contract Execution graph 2019-04-29 14:29:54 -07:00
5524146ddf push down noop's messages (#4069)
automerge
2019-04-29 14:10:36 -07:00
3b2adbc9df Change forwarded metrics to be in line with fetch stage metrics (#4068)
automerge
2019-04-29 13:50:14 -07:00
4e41c81bc7 Fix the output from Gossip Discovery (#4067)
automerge
2019-04-29 13:19:24 -07:00
c545e812d0 Expand bank benches to include async/sync and native_loader (#4026) 2019-04-29 13:09:11 -07:00
c2193a37ce cleanup unused function (#4064) 2019-04-29 12:45:14 -07:00
fabba82173 ignore non-descendants of roots in blocktree (#4032) 2019-04-29 12:29:14 -07:00
c3ec5ad846 testnet-demo: use more low quota nodes 2019-04-29 12:18:39 -07:00
c4945cc04a Bump tar from 0.4.23 to 0.4.24 (#4060)
Bumps [tar](https://github.com/alexcrichton/tar-rs) from 0.4.23 to 0.4.24.
- [Release notes](https://github.com/alexcrichton/tar-rs/releases)
- [Commits](https://github.com/alexcrichton/tar-rs/compare/0.4.23...0.4.24)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-29 10:34:30 -06:00
e2e55f81d0 Increase testnet-demo node count a little 2019-04-29 09:09:55 -07:00
d862565b16 Move Transact proposal to implemented (#4055)
And update names to reflect what was implemented.
2019-04-29 09:13:39 -06:00
0cc3956693 testnet-demo now runs across more GCE zones (#4053)
* testnet-demo now runs across more GCE zones

* Save zone info to config file

* Add geoip whitelist for common data centers

* Skip more of start

* Include -x for config

* Fetch private key from first validator node if necessary

* Correct -r propagation
2019-04-28 19:50:52 -07:00
4e5677f116 Sample random trade_infos for success (#4043)
Just looking at a single trade_info which may or may not succeed
can fail often.
2019-04-28 11:00:16 -07:00
acba1d6f9e Roots are added out of order to the accounts index (#4051)
* fix root race

* assert root order

* fixup! assert root order

* last root test

* update

* fix tests
2019-04-28 10:27:37 -07:00
3e14af5033 bast bank ancestor check (#4050) 2019-04-28 10:27:09 -07:00
6f56501034 Correctly terminate instances across multiple zones 2019-04-28 09:09:02 -07:00
0b7269b64e Switch testnet-demo to influxcloud 2019-04-27 22:12:50 -07:00
457a2d948b Correct us-central1-b zone name 2019-04-27 21:43:18 -07:00
528bdf34fd testnet-demo: skip over validator nodes that fail to boot 2019-04-27 21:34:23 -07:00
697cd17b59 Use GPU nodes for blockstreamer as well if rest of testnet has GPUs (#4046)
- The blockstreamer crashes otherwise, as sigverify() looks for CUDA libs
2019-04-27 20:45:38 -07:00
13fcfcb964 Blockstreamer annotation fix for non buildkite deployments (#4045) 2019-04-27 20:37:36 -07:00
9c1fd55768 testnet-demo: add more GCE zones, remove client 2019-04-27 16:52:09 -07:00
7f9a476660 Performance metrics computation methodology (#4041) 2019-04-27 16:37:51 -07:00
b07290df81 Add usage to net.sh when it encounters an invalid argument (#4042)
automerge
2019-04-27 16:12:13 -07:00
4b599a95b3 Bump libc from 0.2.51 to 0.2.53 (#4009)
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.51 to 0.2.53.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.51...0.2.53)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-04-27 14:04:23 -06:00
64222cfff7 update lock file for 0.15 (#4039)
automerge
2019-04-27 11:43:12 -07:00
e81d434903 Add DNS resolution for network/drone arguments (#4038)
automerge
2019-04-27 10:06:58 -07:00
bf0dd158de Avoid inaccurate PATH nagging (#4034)
automerge
2019-04-27 09:11:02 -07:00
18e398131d Fix issues with bench-tps (#4005) 2019-04-27 08:39:29 -07:00
4a5837a286 Add " 2019-04-27 07:41:21 -07:00
656e2649a7 v0.15.0 2019-04-27 07:38:46 -07:00
387 changed files with 29350 additions and 12288 deletions

View File

@ -10,6 +10,8 @@
set -x
rsync -a --delete --link-dest="$PWD" target "$d"
du -hs "$d"
read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d")
echo "--- ${cacheSizeInGB}GB: $d"
)
#

View File

@ -14,14 +14,18 @@ export PS4="++"
(
set -x
d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL"
MAX_CACHE_SIZE=18 # gigabytes
if [[ -d $d ]]; then
du -hs "$d"
read -r cacheSizeInGB _ < <(du -s --block-size=1000000000 "$d")
if [[ $cacheSizeInGB -gt 10 ]]; then
echo "$d has gotten too large, removing it"
read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d")
echo "--- ${cacheSizeInGB}GB: $d"
if [[ $cacheSizeInGB -gt $MAX_CACHE_SIZE ]]; then
echo "--- $d is too large, removing it"
rm -rf "$d"
fi
else
echo "--- $d not present"
fi
mkdir -p "$d"/target

4
.gitignore vendored
View File

@ -1,13 +1,11 @@
/book/html/
/book/src/img/
/book/src/tests.ok
/core/target/
/farf/
/ledger-tool/target/
/metrics/scripts/lib/
/solana-release/
solana-release.tar.bz2
/target/
/wallet/target/
**/*.rs.bk
.cargo

1132
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,12 @@
[workspace]
members = [
".",
"bench-exchange",
"bench-streamer",
"bench-tps",
"client",
"core",
"drone",
"fullnode",
"validator",
"genesis",
"gossip",
"install",
@ -14,6 +15,7 @@ members = [
"ledger-tool",
"logger",
"metrics",
"netutil",
"programs/bpf",
"programs/bpf_loader",
"programs/budget_api",
@ -22,17 +24,18 @@ members = [
"programs/config_program",
"programs/exchange_api",
"programs/exchange_program",
"programs/token_api",
"programs/token_program",
"programs/failure_program",
"programs/noop_program",
"programs/stake_api",
"programs/stake_program",
"programs/storage_api",
"programs/storage_program",
"programs/token_api",
"programs/token_program",
"programs/vote_api",
"programs/vote_program",
"replicator",
"runtime",
"sdk",
"upload-perf",
"vote-signer",

View File

@ -41,7 +41,7 @@ Install rustc, cargo and rustfmt:
```bash
$ curl https://sh.rustup.rs -sSf | sh
$ source $HOME/.cargo/env
$ rustup component add rustfmt-preview
$ rustup component add rustfmt
```
If your rustc version is lower than 1.34.0, please update it:
@ -66,7 +66,7 @@ $ cd solana
Build
```bash
$ cargo build --all
$ cargo build
```
Then to run a minimal local cluster
@ -80,7 +80,7 @@ Testing
Run the test suite:
```bash
$ cargo test --all
$ cargo test
```
Local Testnet

View File

@ -61,7 +61,7 @@ There are three release channels that map to branches as follows:
## Release Steps
### Changing channels
### Advance the Channels
#### Create the new branch
1. Pick your branch point for release on master.
@ -84,7 +84,7 @@ There are three release channels that map to branches as follows:
At this point, `ci/channel-info.sh` should show your freshly cut release branch as
"BETA_CHANNEL" and the previous release branch as "STABLE_CHANNEL".
### Updating channels (i.e. "making a release")
### Make the Release
We use [github's Releases UI](https://github.com/solana-labs/solana/releases) for tagging a release.
@ -99,13 +99,59 @@ We use [github's Releases UI](https://github.com/solana-labs/solana/releases) fo
release should be `<branchname>.X-rc.0`.
1. Verify release automation:
1. [Crates.io](https://crates.io/crates/solana) should have an updated Solana version.
1. ...
1. After testnet deployment, verify that testnets are running correct software.
http://metrics.solana.com should show testnet running on a hash from your
newly created branch.
1. Once the release has been made, update Cargo.toml on the release branch to the next
semantic version (e.g. 0.9.0 -> 0.9.1) by running
`./scripts/increment-cargo-version.sh patch`, then rebuild with `cargo
build` to cause a refresh of `Cargo.lock`.
1. Push your Cargo.toml change and the autogenerated Cargo.lock changes to the
release branch.
### Update software on testnet.solana.com
The testnet running on testnet.solana.com is set to use a fixed release tag
which is set in the Buildkite testnet-management pipeline.
This tag needs to be updated and the testnet restarted after a new release
tag is created.
#### Update testnet schedules
Go to https://buildkite.com/solana-labs and click through: Pipelines ->
testnet-management -> Pipeline Settings -> Schedules
Or just click here:
https://buildkite.com/solana-labs/testnet-management/settings/schedules
There are two scheduled jobs for testnet: a daily restart and an hourly sanity-or-restart. \
https://buildkite.com/solana-labs/testnet-management/settings/schedules/0efd7856-7143-4713-8817-47e6bdb05387
https://buildkite.com/solana-labs/testnet-management/settings/schedules/2a926646-d972-42b5-aeb9-bb6759592a53
On each schedule:
1. Set TESTNET_TAG environment variable to the desired release tag.
1. Example, TESTNET_TAG=v0.13.2
1. Set the Build Branch to the branch that TESTNET_TAG is from.
1. Example: v0.13
#### Restart the testnet
Trigger a TESTNET_OP=create-and-start to refresh the cluster with the new version
1. Go to https://buildkite.com/solana-labs/testnet-management
2. Click "New Build" and use the following settings, then click "Create Build"
1. Commit: HEAD
1. Branch: [channel branch as set in the schedules]
1. Environment Variables:
```
TESTNET=testnet
TESTNET_TAG=[same value as used in TESTNET_TAG in the schedules]
TESTNET_OP=create-and-start
```
#### Update documentation
Document the new recommended version by updating
```export SOLANA_RELEASE=[new scheduled TESTNET_TAG value]```
in book/src/testnet-participation.md for both edge and beta channel branches.
### Alert the community
Notify Discord users on #validator-support that a new release for
testnet.solana.com is available

View File

@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-exchange"
version = "0.14.0"
version = "0.15.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -10,28 +10,29 @@ homepage = "https://solana.com/"
[dependencies]
bs58 = "0.2.0"
clap = "2.32.0"
bincode = "1.1.2"
bincode = "1.1.4"
env_logger = "0.6.0"
itertools = "0.8.0"
log = "0.4.6"
num-traits = "0.2"
num-derive = "0.2"
rand = "0.6.5"
rayon = "1.0.3"
serde = "1.0.87"
serde_derive = "1.0.87"
serde = "1.0.91"
serde_derive = "1.0.91"
serde_json = "1.0.38"
# solana-runtime = { path = "../solana/runtime"}
solana = { path = "../core", version = "0.14.0" }
solana-client = { path = "../client", version = "0.14.0" }
solana-drone = { path = "../drone", version = "0.14.0" }
solana-exchange-api = { path = "../programs/exchange_api", version = "0.14.0" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.14.0" }
solana-logger = { path = "../logger", version = "0.14.0" }
solana-metrics = { path = "../metrics", version = "0.14.0" }
solana-netutil = { path = "../netutil", version = "0.14.0" }
solana-runtime = { path = "../runtime", version = "0.14.0" }
solana-sdk = { path = "../sdk", version = "0.14.0" }
ws = "0.8.0"
solana = { path = "../core", version = "0.15.0" }
solana-client = { path = "../client", version = "0.15.0" }
solana-drone = { path = "../drone", version = "0.15.0" }
solana-exchange-api = { path = "../programs/exchange_api", version = "0.15.0" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.15.0" }
solana-logger = { path = "../logger", version = "0.15.0" }
solana-metrics = { path = "../metrics", version = "0.15.0" }
solana-netutil = { path = "../netutil", version = "0.15.0" }
solana-runtime = { path = "../runtime", version = "0.15.0" }
solana-sdk = { path = "../sdk", version = "0.15.0" }
ws = "0.8.1"
untrusted = "0.6.2"
[features]

View File

@ -23,7 +23,7 @@ demo demonstrates one way to host an exchange on the Solana blockchain by
emulating a currency exchange.
The assets are virtual tokens held by investors who may post trade requests to
the exchange. A broker monitors the exchange and posts swap requests for
the exchange. A Swapper monitors the exchange and posts swap requests for
matching trade orders. All the transactions can execute concurrently.
## Premise
@ -75,7 +75,7 @@ matching trade orders. All the transactions can execute concurrently.
contain the same information as the trade request.
- Price spread
- The difference between the two matching trade orders. The spread is the
profit of the broker initiating the swap request.
profit of the Swapper initiating the swap request.
- Swap requirements
- Policies that result in a successful trade swap.
- Swap request
@ -85,7 +85,7 @@ matching trade orders. All the transactions can execute concurrently.
swap requirements. A trade swap may not wholly satisfy one or both of the
trade orders in which case the trade orders are adjusted appropriately. As
long as the swap requirements are met there will be an exchange of tokens
between accounts. Any price spread is deposited into the broker's profit
between accounts. Any price spread is deposited into the Swapper's profit
account. All trade swaps are recorded in a new account for posterity.
- Investor
- Individual investors who hold a number of tokens and wish to trade them on
@ -93,41 +93,41 @@ matching trade orders. All the transactions can execute concurrently.
accounts containing tokens and/or trade requests. Investors post
transactions to the exchange in order to request tokens and post or cancel
trade requests.
- Broker
- An agent who facilitates trading between investors. Brokers operate as
- Swapper
- An agent who facilitates trading between investors. Swappers operate as
Solana thin clients who monitor all the trade orders looking for a trade
match. Once found, the broker issues a swap request to the exchange.
Brokers are the engine of the exchange and are rewarded for their efforts by
accumulating the price spreads of the swaps they initiate. Brokers also
match. Once found, the Swapper issues a swap request to the exchange.
Swappers are the engine of the exchange and are rewarded for their efforts by
accumulating the price spreads of the swaps they initiate. Swappers also
provide current bid/ask price and OHLCV (Open, High, Low, Close, Volume)
information on demand via a public network port.
- Transaction fees
- Solana transaction fees are paid for by the transaction submitters who are
the Investors and Brokers.
the Investors and Swappers.
## Exchange startup
The exchange is up and running when it reaches a state where it can take
investor's trades and broker's swap requests. To achieve this state the
investor's trades and Swapper's swap requests. To achieve this state the
following must occur in order:
- Start the Solana blockchain
- Start the broker thin-client
- The broker subscribes to change notifications for all the accounts owned by
- Start the Swapper thin-client
- The Swapper subscribes to change notifications for all the accounts owned by
the exchange program id. The subscription is managed via Solana's JSON RPC
interface.
- The broker starts responding to queries for bid/ask price and OHLCV
- The Swapper starts responding to queries for bid/ask price and OHLCV
The broker responding successfully to price and OHLCV requests is the signal to
The Swapper responding successfully to price and OHLCV requests is the signal to
the investors that trades submitted after that point will be analyzed. <!--This
is not ideal, and instead investors should be able to submit trades at any time,
and the broker could come and go without missing a trade. One way to achieve
this is for the broker to read the current state of all accounts looking for all
and the Swapper could come and go without missing a trade. One way to achieve
this is for the Swapper to read the current state of all accounts looking for all
open trade orders.-->
Investors will initially query the exchange to discover their current balance
for each type of token. If the investor does not already have an account for
each type of token, they will submit account requests. Brokers as well will
each type of token, they will submit account requests. Swappers as well will
request accounts to hold the tokens they earn by initiating trade swaps.
```rust
@ -165,7 +165,7 @@ pub struct TokenAccountInfo {
}
```
For this demo investors or brokers can request more tokens from the exchange at
For this demo investors or Swappers can request more tokens from the exchange at
any time by submitting token requests. In non-demos, an exchange of this type
would provide another way to exchange a 3rd party asset into tokens.
@ -269,10 +269,10 @@ pub enum ExchangeInstruction {
## Trade swaps
The broker is monitoring the accounts assigned to the exchange program and
The Swapper is monitoring the accounts assigned to the exchange program and
building a trade-order table. The trade order table is used to identify
matching trade orders which could be fulfilled. When a match is found the
broker should issue a swap request. Swap requests may not satisfy the entirety
Swapper should issue a swap request. Swap requests may not satisfy the entirety
of either order, but the exchange will greedily fulfill it. Any leftover tokens
in either account will keep the trade order valid for further swap requests in
the future.
@ -310,14 +310,14 @@ whole for clarity.
| 5 | 1 T AB 2 10 | 2 F AB 1 5 |
As part of a successful swap request, the exchange will credit tokens to the
broker's account equal to the difference in the price ratios or the two orders.
These tokens are considered the broker's profit for initiating the trade.
Swapper's account equal to the difference in the price ratios or the two orders.
These tokens are considered the Swapper's profit for initiating the trade.
The broker would initiate the following swap on the order table above:
The Swapper would initiate the following swap on the order table above:
- Row 1, To: Investor 1 trades 2 A tokens to 8 B tokens
- Row 1, From: Investor 2 trades 2 A tokens from 8 B tokens
- Broker takes 8 B tokens as profit
- Swapper takes 8 B tokens as profit
Both row 1 trades are fully realized, table becomes:
@ -328,11 +328,11 @@ Both row 1 trades are fully realized, table becomes:
| 3 | 1 T AB 2 8 | 2 F AB 3 6 |
| 4 | 1 T AB 2 10 | 2 F AB 1 5 |
The broker would initiate the following swap:
The Swapper would initiate the following swap:
- Row 1, To: Investor 1 trades 1 A token to 4 B tokens
- Row 1, From: Investor 2 trades 1 A token from 4 B tokens
- Broker takes 4 B tokens as profit
- Swapper takes 4 B tokens as profit
Row 1 From is not fully realized, table becomes:
@ -343,11 +343,11 @@ Row 1 From is not fully realized, table becomes:
| 3 | 1 T AB 2 10 | 2 F AB 3 6 |
| 4 | | 2 F AB 1 5 |
The broker would initiate the following swap:
The Swapper would initiate the following swap:
- Row 1, To: Investor 1 trades 1 A token to 6 B tokens
- Row 1, From: Investor 2 trades 1 A token from 6 B tokens
- Broker takes 2 B tokens as profit
- Swapper takes 2 B tokens as profit
Row 1 To is now fully realized, table becomes:
@ -357,11 +357,11 @@ Row 1 To is now fully realized, table becomes:
| 2 | 1 T AB 2 8 | 2 F AB 3 5 |
| 3 | 1 T AB 2 10 | 2 F AB 1 5 |
The broker would initiate the following last swap:
The Swapper would initiate the following last swap:
- Row 1, To: Investor 1 trades 2 A token to 12 B tokens
- Row 1, From: Investor 2 trades 2 A token from 12 B tokens
- Broker takes 4 B tokens as profit
- Swapper takes 4 B tokens as profit
Table becomes:
@ -383,7 +383,7 @@ pub enum ExchangeInstruction {
/// key 3 - `From` trade order
/// key 4 - Token account associated with the To Trade
/// key 5 - Token account associated with From trade
/// key 6 - Token account in which to deposit the brokers profit from the swap.
/// key 6 - Token account in which to deposit the Swappers profit from the swap.
SwapRequest,
}
@ -442,14 +442,14 @@ pub enum ExchangeInstruction {
/// key 3 - `From` trade order
/// key 4 - Token account associated with the To Trade
/// key 5 - Token account associated with From trade
/// key 6 - Token account in which to deposit the brokers profit from the swap.
/// key 6 - Token account in which to deposit the Swappers profit from the swap.
SwapRequest,
}
```
## Quotes and OHLCV
The broker will provide current bid/ask price quotes based on trade actively and
The Swapper will provide current bid/ask price quotes based on trade actively and
also provide OHLCV based on some time window. The details of how the bid/ask
price quotes are calculated are yet to be decided.

View File

@ -3,23 +3,21 @@
use crate::order_book::*;
use itertools::izip;
use log::*;
use rand::{thread_rng, Rng};
use rayon::prelude::*;
use solana::cluster_info::FULLNODE_PORT_RANGE;
use solana::contact_info::ContactInfo;
use solana::gen_keys::GenKeys;
use solana_client::thin_client::create_client;
use solana_client::thin_client::ThinClient;
use solana_client::perf_utils::{sample_txs, SampleStats};
use solana_drone::drone::request_airdrop_transaction;
use solana_exchange_api::exchange_instruction;
use solana_exchange_api::exchange_state::*;
use solana_exchange_api::id;
use solana_metrics::influxdb;
use solana_metrics::datapoint_info;
use solana_sdk::client::Client;
use solana_sdk::client::SyncClient;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_instruction;
use solana_sdk::timing::{duration_as_ms, duration_as_ns, duration_as_s};
use solana_sdk::timing::{duration_as_ms, duration_as_s};
use solana_sdk::transaction::Transaction;
use std::cmp;
use std::collections::VecDeque;
@ -67,16 +65,6 @@ impl Default for Config {
}
}
#[derive(Default)]
pub struct SampleStats {
/// Maximum TPS reported by this node
pub tps: f32,
/// Total time taken for those txs
pub elapsed: Duration,
/// Total transactions reported by this node
pub txs: u64,
}
pub fn do_bench_exchange<T>(clients: Vec<T>, config: Config)
where
T: 'static + Client + Send + Sync,
@ -91,6 +79,18 @@ where
chunk_size,
account_groups,
} = config;
info!(
"Exchange client: threads {} duration {} fund_amount {}",
threads,
duration_as_s(&duration),
fund_amount
);
info!(
"Exchange client: transfer delay {} batch size {} chunk size {}",
transfer_delay, batch_size, chunk_size
);
let accounts_in_groups = batch_size * account_groups;
let exit_signal = Arc::new(AtomicBool::new(false));
let clients: Vec<_> = clients.into_iter().map(Arc::new).collect();
@ -168,6 +168,7 @@ where
&shared_txs,
&swapper_signers,
&profit_pubkeys,
transfer_delay,
batch_size,
chunk_size,
account_groups,
@ -237,62 +238,6 @@ where
);
}
fn sample_txs<T>(
exit_signal: &Arc<AtomicBool>,
sample_stats: &Arc<RwLock<Vec<SampleStats>>>,
sample_period: u64,
client: &Arc<T>,
) where
T: Client,
{
let mut max_tps = 0.0;
let mut total_elapsed;
let mut total_txs;
let mut now = Instant::now();
let start_time = now;
let initial_txs = client.get_transaction_count().expect("transaction count");
let mut last_txs = initial_txs;
loop {
total_elapsed = start_time.elapsed();
let elapsed = now.elapsed();
now = Instant::now();
let mut txs = client.get_transaction_count().expect("transaction count");
if txs < last_txs {
error!("expected txs({}) >= last_txs({})", txs, last_txs);
txs = last_txs;
}
total_txs = txs - initial_txs;
let sample_txs = txs - last_txs;
last_txs = txs;
let tps = sample_txs as f32 / duration_as_s(&elapsed);
if tps > max_tps {
max_tps = tps;
}
info!(
"Sampler {:9.2} TPS, Transactions: {:6}, Total transactions: {} over {} s",
tps,
sample_txs,
total_txs,
total_elapsed.as_secs(),
);
if exit_signal.load(Ordering::Relaxed) {
let stats = SampleStats {
tps: max_tps,
elapsed: total_elapsed,
txs: total_txs,
};
sample_stats.write().unwrap().push(stats);
return;
}
sleep(Duration::from_secs(sample_period));
}
}
fn do_tx_transfers<T>(
exit_signal: &Arc<AtomicBool>,
shared_txs: &SharedTransactions,
@ -301,7 +246,6 @@ fn do_tx_transfers<T>(
) where
T: Client,
{
let mut stats = Stats::default();
loop {
let txs;
{
@ -318,48 +262,18 @@ fn do_tx_transfers<T>(
let duration = now.elapsed();
total_txs_sent_count.fetch_add(n, Ordering::Relaxed);
stats.total += n as u64;
stats.sent_ns += duration_as_ns(&duration);
let rate = n as f32 / duration_as_s(&duration);
if rate > stats.sent_peak_rate {
stats.sent_peak_rate = rate;
}
trace!(" tx {:?} sent {:.2}/s", n, rate);
solana_metrics::submit(
influxdb::Point::new("bench-exchange")
.add_tag("op", influxdb::Value::String("do_tx_transfers".to_string()))
.add_field(
"duration",
influxdb::Value::Integer(duration_as_ms(&duration) as i64),
)
.add_field("count", influxdb::Value::Integer(n as i64))
.to_owned(),
datapoint_info!(
"bench-exchange-do_tx_transfers",
("duration", duration_as_ms(&duration), i64),
("count", n, i64)
);
}
if exit_signal.load(Ordering::Relaxed) {
info!(
" Thread Transferred {} Txs, avg {:.2}/s peak {:.2}/s",
stats.total,
(stats.total as f64 / stats.sent_ns as f64) * 1_000_000_000_f64,
stats.sent_peak_rate,
);
return;
}
}
}
#[derive(Default)]
struct Stats {
total: u64,
keygen_ns: u64,
keygen_peak_rate: f32,
sign_ns: u64,
sign_peak_rate: f32,
sent_ns: u64,
sent_peak_rate: f32,
}
struct TradeInfo {
trade_account: Pubkey,
order_info: TradeOrderInfo,
@ -371,6 +285,7 @@ fn swapper<T>(
shared_txs: &SharedTransactions,
signers: &[Arc<Keypair>],
profit_pubkeys: &[Pubkey],
transfer_delay: u64,
batch_size: usize,
chunk_size: usize,
account_groups: usize,
@ -378,28 +293,57 @@ fn swapper<T>(
) where
T: Client,
{
let mut stats = Stats::default();
let mut order_book = OrderBook::default();
let mut account_group: usize = 0;
let mut txs = 0;
let mut total_txs = 0;
let mut now = Instant::now();
let start_time = now;
let mut total_elapsed = start_time.elapsed();
// Chunks may have been dropped and we don't want to wait a long time
// for each time, Back-off each time we fail to confirm a chunk
const CHECK_TX_TIMEOUT_MAX_MS: u64 = 15000;
const CHECK_TX_DELAY_MS: u64 = 100;
let mut max_tries = CHECK_TX_TIMEOUT_MAX_MS / CHECK_TX_DELAY_MS;
// If we dump too many chunks maybe we are just waiting on a back-log
// rather than a series of dropped packets, reset to max waits
const MAX_DUMPS: u64 = 50;
let mut dumps = 0;
'outer: loop {
if let Ok(trade_infos) = receiver.try_recv() {
let mut tries = 0;
let mut trade_index = 0;
while client
.get_balance(&trade_infos[0].trade_account)
.get_balance(&trade_infos[trade_index].trade_account)
.unwrap_or(0)
== 0
{
tries += 1;
if tries > 300 {
if tries >= max_tries {
if exit_signal.load(Ordering::Relaxed) {
break 'outer;
}
error!("Give up waiting, dump batch");
error!("Give up and dump batch");
if dumps >= MAX_DUMPS {
error!("Max batches dumped, reset wait back-off");
max_tries = CHECK_TX_TIMEOUT_MAX_MS / CHECK_TX_DELAY_MS;
dumps = 0;
} else {
dumps += 1;
max_tries /= 2;
}
continue 'outer;
}
debug!("{} waiting for trades batch to clear", tries);
sleep(Duration::from_millis(100));
sleep(Duration::from_millis(CHECK_TX_DELAY_MS));
trade_index = thread_rng().gen_range(0, trade_infos.len());
}
max_tries = CHECK_TX_TIMEOUT_MAX_MS / CHECK_TX_DELAY_MS;
dumps = 0;
trade_infos.iter().for_each(|info| {
order_book
@ -414,9 +358,6 @@ fn swapper<T>(
}
}
let swaps_size = swaps.len();
stats.total += swaps_size as u64;
let now = Instant::now();
let mut to_swap = vec![];
let start = account_group * swaps_size as usize;
@ -429,17 +370,8 @@ fn swapper<T>(
to_swap.push((signer, swap, profit));
}
account_group = (account_group + 1) % account_groups as usize;
let duration = now.elapsed();
let rate = swaps_size as f32 / duration_as_s(&duration);
stats.keygen_ns += duration_as_ns(&duration);
if rate > stats.keygen_peak_rate {
stats.keygen_peak_rate = rate;
}
trace!("sw {:?} keypairs {:.2} /s", swaps_size, rate);
let now = Instant::now();
let blockhash = client
let (blockhash, _fee_calculator) = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
let to_swap_txs: Vec<_> = to_swap
@ -459,21 +391,25 @@ fn swapper<T>(
)
})
.collect();
let duration = now.elapsed();
let n = to_swap_txs.len();
let rate = n as f32 / duration_as_s(&duration);
stats.sign_ns += duration_as_ns(&duration);
if rate > stats.sign_peak_rate {
stats.sign_peak_rate = rate;
}
trace!(" sw {:?} signed {:.2} /s ", n, rate);
solana_metrics::submit(
influxdb::Point::new("bench-exchange")
.add_tag("op", influxdb::Value::String("swaps".to_string()))
.add_field("count", influxdb::Value::Integer(to_swap_txs.len() as i64))
.to_owned(),
);
txs += to_swap_txs.len() as u64;
total_txs += to_swap_txs.len() as u64;
total_elapsed = start_time.elapsed();
let duration = now.elapsed();
if duration_as_s(&duration) >= 1_f32 {
now = Instant::now();
let tps = txs as f32 / duration_as_s(&duration);
info!(
"Swapper {:9.2} TPS, Transactions: {:6}, Total transactions: {} over {} s",
tps,
txs,
total_txs,
total_elapsed.as_secs(),
);
txs = 0;
}
datapoint_info!("bench-exchange-swaps", ("count", to_swap_txs.len(), i64));
let chunks: Vec<_> = to_swap_txs.chunks(chunk_size).collect();
{
@ -482,6 +418,8 @@ fn swapper<T>(
shared_txs_wl.push_back(chunk.to_vec());
}
}
// Throttle the swapper so it doesn't try to catchup unbridled
sleep(Duration::from_millis(transfer_delay / 2));
}
if exit_signal.load(Ordering::Relaxed) {
@ -489,18 +427,9 @@ fn swapper<T>(
}
}
info!(
"{} Swaps, batch size {}, chunk size {}",
stats.total, batch_size, chunk_size
);
info!(
" Keygen avg {:.2}/s peak {:.2}/s",
(stats.total as f64 / stats.keygen_ns as f64) * 1_000_000_000_f64,
stats.keygen_peak_rate
);
info!(
" Signed avg {:.2}/s peak {:.2}/s",
(stats.total as f64 / stats.sign_ns as f64) * 1_000_000_000_f64,
stats.sign_peak_rate
"Swapper sent {} at {:9.2} TPS",
total_txs,
total_txs as f32 / duration_as_s(&total_elapsed)
);
assert_eq!(
order_book.get_num_outstanding().0 + order_book.get_num_outstanding().1,
@ -515,7 +444,7 @@ fn trader<T>(
shared_txs: &SharedTransactions,
signers: &[Arc<Keypair>],
srcs: &[Pubkey],
delay: u64,
transfer_delay: u64,
batch_size: usize,
chunk_size: usize,
account_groups: usize,
@ -523,16 +452,19 @@ fn trader<T>(
) where
T: Client,
{
let mut stats = Stats::default();
// TODO Hard coded for now
let pair = TokenPair::AB;
let tokens = 1;
let price = 1000;
let mut account_group: usize = 0;
let mut txs = 0;
let mut total_txs = 0;
let mut now = Instant::now();
let start_time = now;
let mut total_elapsed = start_time.elapsed();
loop {
let now = Instant::now();
let trade_keys = generate_keypairs(batch_size as u64);
let mut trades = vec![];
@ -566,20 +498,12 @@ fn trader<T>(
trades.push((signer, trade.pubkey(), direction, src));
}
account_group = (account_group + 1) % account_groups as usize;
let duration = now.elapsed();
let rate = batch_size as f32 / duration_as_s(&duration);
stats.keygen_ns += duration_as_ns(&duration);
if rate > stats.keygen_peak_rate {
stats.keygen_peak_rate = rate;
}
trace!("sw {:?} keypairs {:.2} /s", batch_size, rate);
let blockhash = client
let (blockhash, _fee_calculator) = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
trades.chunks(chunk_size).for_each(|chunk| {
let now = Instant::now();
let trades_txs: Vec<_> = chunk
.par_iter()
.map(|(signer, trade, direction, src)| {
@ -598,55 +522,52 @@ fn trader<T>(
)
})
.collect();
let duration = now.elapsed();
let n = trades_txs.len();
let rate = n as f32 / duration_as_s(&duration);
stats.sign_ns += duration_as_ns(&duration);
if rate > stats.sign_peak_rate {
stats.sign_peak_rate = rate;
}
trace!(" sw {:?} signed {:.2} /s ", n, rate);
solana_metrics::submit(
influxdb::Point::new("bench-exchange")
.add_tag("op", influxdb::Value::String("trades".to_string()))
.add_field("count", influxdb::Value::Integer(trades_txs.len() as i64))
.to_owned(),
);
{
let mut shared_txs_wl = shared_txs
.write()
.expect("Failed to send tx to transfer threads");
stats.total += chunk_size as u64;
shared_txs_wl.push_back(trades_txs);
txs += chunk_size as u64;
total_txs += chunk_size as u64;
total_elapsed = start_time.elapsed();
let duration = now.elapsed();
if duration_as_s(&duration) >= 1_f32 {
now = Instant::now();
let tps = txs as f32 / duration_as_s(&duration);
info!(
"Trader {:9.2} TPS, Transactions: {:6}, Total transactions: {} over {} s",
tps,
txs,
total_txs,
total_elapsed.as_secs(),
);
txs = 0;
}
datapoint_info!("bench-exchange-trades", ("count", trades_txs.len(), i64));
{
let mut shared_txs_wl = shared_txs
.write()
.expect("Failed to send tx to transfer threads");
shared_txs_wl.push_back(trades_txs);
}
}
if delay > 0 {
sleep(Duration::from_millis(delay));
if transfer_delay > 0 {
sleep(Duration::from_millis(transfer_delay));
}
});
if exit_signal.load(Ordering::Relaxed) {
info!(
"Trader sent {} at {:9.2} TPS",
total_txs,
total_txs as f32 / duration_as_s(&total_elapsed)
);
return;
}
// TODO chunk the trade infos and send them when the batch is sent
sender
.send(trade_infos)
.expect("Failed to send trades to swapper");
if exit_signal.load(Ordering::Relaxed) {
info!(
"{} Trades with batch size {} chunk size {}",
stats.total, batch_size, chunk_size
);
info!(
" Keygen avg {:.2}/s peak {:.2}/s",
(stats.total as f64 / stats.keygen_ns as f64) * 1_000_000_000_f64,
stats.keygen_peak_rate
);
info!(
" Signed avg {:.2}/s peak {:.2}/s",
(stats.total as f64 / stats.sign_ns as f64) * 1_000_000_000_f64,
stats.sign_peak_rate
);
break;
}
}
}
@ -742,7 +663,8 @@ pub fn fund_keys(client: &Client, source: &Keypair, dests: &[Arc<Keypair>], lamp
to_fund_txs.len(),
);
let blockhash = client.get_recent_blockhash().expect("blockhash");
let (blockhash, _fee_calculator) =
client.get_recent_blockhash().expect("blockhash");
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
tx.sign(&[*k], blockhash);
});
@ -793,11 +715,11 @@ pub fn create_token_accounts(client: &Client, signers: &[Arc<Keypair>], accounts
let mut to_create_txs: Vec<_> = chunk
.par_iter()
.map(|(signer, new)| {
let owner_id = &signer.pubkey();
let owner_pubkey = &signer.pubkey();
let space = mem::size_of::<ExchangeState>() as u64;
let create_ix =
system_instruction::create_account(owner_id, new, 1, space, &id());
let request_ix = exchange_instruction::account_request(owner_id, new);
system_instruction::create_account(owner_pubkey, new, 1, space, &id());
let request_ix = exchange_instruction::account_request(owner_pubkey, new);
(
signer,
Transaction::new_unsigned_instructions(vec![create_ix, request_ix]),
@ -817,7 +739,7 @@ pub fn create_token_accounts(client: &Client, signers: &[Arc<Keypair>], accounts
let mut retries = 0;
while !to_create_txs.is_empty() {
let blockhash = client
let (blockhash, _fee_calculator) = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
to_create_txs.par_iter_mut().for_each(|(k, tx)| {
@ -868,13 +790,13 @@ pub fn create_token_accounts(client: &Client, signers: &[Arc<Keypair>], accounts
}
}
fn compute_and_report_stats(maxes: &Arc<RwLock<Vec<(SampleStats)>>>, total_txs_sent: u64) {
fn compute_and_report_stats(maxes: &Arc<RwLock<Vec<(String, SampleStats)>>>, total_txs_sent: u64) {
let mut max_txs = 0;
let mut max_elapsed = Duration::new(0, 0);
info!("| Max TPS | Total Transactions");
info!("+---------------+--------------------");
for stats in maxes.read().unwrap().iter() {
for (_sock, stats) in maxes.read().unwrap().iter() {
let maybe_flag = match stats.txs {
0 => "!!!!!",
_ => "",
@ -933,7 +855,7 @@ pub fn airdrop_lamports(client: &Client, drone_addr: &SocketAddr, id: &Keypair,
let mut tries = 0;
loop {
let blockhash = client
let (blockhash, _fee_calculator) = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
match request_airdrop_transaction(&drone_addr, &id.pubkey(), amount_to_drop, blockhash) {
@ -967,35 +889,17 @@ pub fn airdrop_lamports(client: &Client, drone_addr: &SocketAddr, id: &Keypair,
}
}
pub fn get_clients(nodes: &[ContactInfo]) -> Vec<ThinClient> {
nodes
.iter()
.filter_map(|node| {
let cluster_entrypoint = node;
let cluster_addrs = cluster_entrypoint.client_facing_addr();
if ContactInfo::is_valid_address(&cluster_addrs.0)
&& ContactInfo::is_valid_address(&cluster_addrs.1)
{
let client = create_client(cluster_addrs, FULLNODE_PORT_RANGE);
Some(client)
} else {
None
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use solana::fullnode::FullnodeConfig;
use solana::gossip_service::discover_nodes;
use solana::gossip_service::{discover_cluster, get_clients};
use solana::local_cluster::{ClusterConfig, LocalCluster};
use solana::validator::ValidatorConfig;
use solana_drone::drone::run_local_drone;
use solana_exchange_api::exchange_processor::process_instruction;
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::genesis_block::create_genesis_block;
use std::sync::mpsc::channel;
#[test]
@ -1003,16 +907,16 @@ mod tests {
solana_logger::setup();
const NUM_NODES: usize = 1;
let fullnode_config = FullnodeConfig::default();
let validator_config = ValidatorConfig::default();
let mut config = Config::default();
config.identity = Keypair::new();
config.threads = 1;
config.duration = Duration::from_secs(1);
config.fund_amount = 100_000;
config.transfer_delay = 20;
config.threads = 1;
config.transfer_delay = 20; // 15
config.batch_size = 100; // 1000;
config.chunk_size = 10; // 250;
config.chunk_size = 10; // 200;
config.account_groups = 1; // 10;
let Config {
fund_amount,
@ -1025,8 +929,8 @@ mod tests {
let cluster = LocalCluster::new(&ClusterConfig {
node_stakes: vec![100_000; NUM_NODES],
cluster_lamports: 100_000_000_000_000,
fullnode_config,
native_instruction_processors: [("solana_exchange_program".to_string(), id())].to_vec(),
validator_config,
native_instruction_processors: [solana_exchange_program!()].to_vec(),
..ClusterConfig::default()
});
@ -1042,8 +946,8 @@ mod tests {
let drone_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
info!("Connecting to the cluster");
let nodes =
discover_nodes(&cluster.entry_point_info.gossip, NUM_NODES).unwrap_or_else(|err| {
let (nodes, _) = discover_cluster(&cluster.entry_point_info.gossip, NUM_NODES)
.unwrap_or_else(|err| {
error!("Failed to discover {} nodes: {:?}", NUM_NODES, err);
exit(1);
});
@ -1072,16 +976,16 @@ mod tests {
#[test]
fn test_exchange_bank_client() {
solana_logger::setup();
let (genesis_block, identity) = GenesisBlock::new(100_000_000_000_000);
let (genesis_block, identity) = create_genesis_block(100_000_000_000_000);
let mut bank = Bank::new(&genesis_block);
bank.add_instruction_processor(id(), process_instruction);
let clients = vec![BankClient::new(bank)];
let mut config = Config::default();
config.identity = identity;
config.threads = 1;
config.duration = Duration::from_secs(1);
config.fund_amount = 100_000;
config.threads = 1;
config.transfer_delay = 20; // 0;
config.batch_size = 100; // 1500;
config.chunk_size = 10; // 1500;

View File

@ -1,13 +1,13 @@
use std::net::SocketAddr;
use std::time::Duration;
use clap::{crate_description, crate_name, crate_version, value_t, App, Arg, ArgMatches};
use solana::gen_keys::GenKeys;
use solana_drone::drone::DRONE_PORT;
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
use std::net::SocketAddr;
use std::process::exit;
use std::time::Duration;
pub struct Config {
pub network_addr: SocketAddr,
pub entrypoint_addr: SocketAddr,
pub drone_addr: SocketAddr,
pub identity: Keypair,
pub threads: usize,
@ -23,7 +23,7 @@ pub struct Config {
impl Default for Config {
fn default() -> Self {
Self {
network_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
identity: Keypair::new(),
num_nodes: 1,
@ -43,14 +43,14 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.about(crate_description!())
.version(crate_version!())
.arg(
Arg::with_name("network")
Arg::with_name("entrypoint")
.short("n")
.long("network")
.long("entrypoint")
.value_name("HOST:PORT")
.takes_value(true)
.required(false)
.default_value("127.0.0.1:8001")
.help("Network's gossip entry point; defaults to 127.0.0.1:8001"),
.help("Cluster entry point; defaults to 127.0.0.1:8001"),
)
.arg(
Arg::with_name("drone")
@ -146,16 +146,17 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
let mut args = Config::default();
args.network_addr = matches
.value_of("network")
.unwrap()
.parse()
.expect("Failed to parse network");
args.drone_addr = matches
.value_of("drone")
.unwrap()
.parse()
.expect("Failed to parse drone address");
args.entrypoint_addr = solana_netutil::parse_host_port(matches.value_of("entrypoint").unwrap())
.unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e);
exit(1)
});
args.drone_addr = solana_netutil::parse_host_port(matches.value_of("drone").unwrap())
.unwrap_or_else(|e| {
eprintln!("failed to parse drone address: {}", e);
exit(1)
});
if matches.is_present("identity") {
args.identity = read_keypair(matches.value_of("identity").unwrap())

View File

@ -2,9 +2,13 @@ pub mod bench;
mod cli;
pub mod order_book;
use crate::bench::{airdrop_lamports, do_bench_exchange, get_clients, Config};
#[cfg(test)]
#[macro_use]
extern crate solana_exchange_program;
use crate::bench::{airdrop_lamports, do_bench_exchange, Config};
use log::*;
use solana::gossip_service::discover_nodes;
use solana::gossip_service::{discover_cluster, get_clients};
use solana_sdk::signature::KeypairUtil;
fn main() {
@ -15,7 +19,7 @@ fn main() {
let cli_config = cli::extract_args(&matches);
let cli::Config {
network_addr,
entrypoint_addr,
drone_addr,
identity,
threads,
@ -30,9 +34,10 @@ fn main() {
} = cli_config;
info!("Connecting to the cluster");
let nodes = discover_nodes(&network_addr, num_nodes).unwrap_or_else(|_| {
panic!("Failed to discover nodes");
});
let (nodes, _replicators) =
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
panic!("Failed to discover nodes");
});
let clients = get_clients(&nodes);

1
bench-streamer/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target/

View File

@ -2,16 +2,16 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-streamer"
version = "0.14.0"
version = "0.15.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
clap = "2.33.0"
solana = { path = "../core", version = "0.14.0" }
solana-logger = { path = "../logger", version = "0.14.0" }
solana-netutil = { path = "../netutil", version = "0.14.0" }
solana = { path = "../core", version = "0.15.0" }
solana-logger = { path = "../logger", version = "0.15.0" }
solana-netutil = { path = "../netutil", version = "0.15.0" }
[features]
cuda = ["solana/cuda"]

View File

@ -20,13 +20,13 @@ fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
w.meta.size = PACKET_DATA_SIZE;
w.meta.set_addr(&addr);
}
let msgs_ = msgs.clone();
let msgs = Arc::new(msgs);
spawn(move || loop {
if exit.load(Ordering::Relaxed) {
return;
}
let mut num = 0;
for p in &msgs_.packets {
for p in &msgs.packets {
let a = p.meta.addr();
assert!(p.meta.size < BLOB_SIZE);
send.send_to(&p.data[..p.meta.size], &a).unwrap();

View File

@ -2,23 +2,24 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-tps"
version = "0.14.0"
version = "0.15.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
clap = "2.33.0"
log = "0.4.6"
rayon = "1.0.3"
serde_json = "1.0.39"
solana = { path = "../core", version = "0.14.0" }
solana-client = { path = "../client", version = "0.14.0" }
solana-drone = { path = "../drone", version = "0.14.0" }
solana-logger = { path = "../logger", version = "0.14.0" }
solana-metrics = { path = "../metrics", version = "0.14.0" }
solana-netutil = { path = "../netutil", version = "0.14.0" }
solana-runtime = { path = "../runtime", version = "0.14.0" }
solana-sdk = { path = "../sdk", version = "0.14.0" }
solana = { path = "../core", version = "0.15.0" }
solana-client = { path = "../client", version = "0.15.0" }
solana-drone = { path = "../drone", version = "0.15.0" }
solana-logger = { path = "../logger", version = "0.15.0" }
solana-metrics = { path = "../metrics", version = "0.15.0" }
solana-netutil = { path = "../netutil", version = "0.15.0" }
solana-runtime = { path = "../runtime", version = "0.15.0" }
solana-sdk = { path = "../sdk", version = "0.15.0" }
[features]
cuda = ["solana/cuda"]

View File

@ -1,10 +1,13 @@
use solana_metrics;
use log::*;
use rayon::prelude::*;
use solana::gen_keys::GenKeys;
use solana_client::perf_utils::{sample_txs, SampleStats};
use solana_drone::drone::request_airdrop_transaction;
use solana_metrics::influxdb;
use solana_metrics::datapoint_info;
use solana_sdk::client::Client;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_instruction;
use solana_sdk::system_transaction;
@ -22,13 +25,6 @@ use std::thread::Builder;
use std::time::Duration;
use std::time::Instant;
pub struct NodeStats {
/// Maximum TPS reported by this node
pub tps: f64,
/// Total transactions reported by this node
pub tx: u64,
}
pub const MAX_SPENDS_PER_TX: usize = 4;
pub const NUM_LAMPORTS_PER_ACCOUNT: u64 = 20;
@ -61,7 +57,8 @@ pub fn do_bench_tps<T>(
config: Config,
gen_keypairs: Vec<Keypair>,
keypair0_balance: u64,
) where
) -> u64
where
T: 'static + Client + Send + Sync,
{
let Config {
@ -98,7 +95,7 @@ pub fn do_bench_tps<T>(
Builder::new()
.name("solana-client-sample".to_string())
.spawn(move || {
sample_tx_count(&exit_signal, &maxes, first_tx_count, sample_period, &client);
sample_txs(&exit_signal, &maxes, sample_period, &client);
})
.unwrap()
})
@ -136,28 +133,39 @@ pub fn do_bench_tps<T>(
let start = Instant::now();
let mut reclaim_lamports_back_to_source_account = false;
let mut i = keypair0_balance;
let mut blockhash = Hash::default();
let mut blockhash_time = Instant::now();
while start.elapsed() < duration {
let balance = client.get_balance(&id.pubkey()).unwrap_or(0);
metrics_submit_lamport_balance(balance);
// ping-pong between source and destination accounts for each loop iteration
// this seems to be faster than trying to determine the balance of individual
// accounts
let len = tx_count as usize;
if let Ok((new_blockhash, _fee_calculator)) = client.get_new_blockhash(&blockhash) {
blockhash = new_blockhash;
} else {
if blockhash_time.elapsed().as_secs() > 30 {
panic!("Blockhash is not updating");
}
sleep(Duration::from_millis(100));
continue;
}
blockhash_time = Instant::now();
let balance = client.get_balance(&id.pubkey()).unwrap_or(0);
metrics_submit_lamport_balance(balance);
generate_txs(
&shared_txs,
&blockhash,
&keypairs[..len],
&keypairs[len..],
threads,
reclaim_lamports_back_to_source_account,
&client,
);
// In sustained mode overlap the transfers with generation
// this has higher average performance but lower peak performance
// in tested environments.
if !sustained {
while shared_tx_active_thread_count.load(Ordering::Relaxed) > 0 {
sleep(Duration::from_millis(100));
sleep(Duration::from_millis(1));
}
}
@ -194,86 +202,27 @@ pub fn do_bench_tps<T>(
&start.elapsed(),
total_tx_sent_count.load(Ordering::Relaxed),
);
let r_maxes = maxes.read().unwrap();
r_maxes.first().unwrap().1.txs
}
fn metrics_submit_lamport_balance(lamport_balance: u64) {
println!("Token balance: {}", lamport_balance);
solana_metrics::submit(
influxdb::Point::new("bench-tps")
.add_tag("op", influxdb::Value::String("lamport_balance".to_string()))
.add_field("balance", influxdb::Value::Integer(lamport_balance as i64))
.to_owned(),
datapoint_info!(
"bench-tps-lamport_balance",
("balance", lamport_balance, i64)
);
}
fn sample_tx_count<T: Client>(
exit_signal: &Arc<AtomicBool>,
maxes: &Arc<RwLock<Vec<(String, NodeStats)>>>,
first_tx_count: u64,
sample_period: u64,
client: &Arc<T>,
) {
let mut now = Instant::now();
let mut initial_tx_count = client.get_transaction_count().expect("transaction count");
let mut max_tps = 0.0;
let mut total;
let log_prefix = format!("{:21}:", client.transactions_addr());
loop {
let mut tx_count = client.get_transaction_count().expect("transaction count");
if tx_count < initial_tx_count {
println!(
"expected tx_count({}) >= initial_tx_count({})",
tx_count, initial_tx_count
);
tx_count = initial_tx_count;
}
let duration = now.elapsed();
now = Instant::now();
let sample = tx_count - initial_tx_count;
initial_tx_count = tx_count;
let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos());
let tps = (sample * 1_000_000_000) as f64 / ns as f64;
if tps > max_tps {
max_tps = tps;
}
if tx_count > first_tx_count {
total = tx_count - first_tx_count;
} else {
total = 0;
}
println!(
"{} {:9.2} TPS, Transactions: {:6}, Total transactions: {}",
log_prefix, tps, sample, total
);
sleep(Duration::new(sample_period, 0));
if exit_signal.load(Ordering::Relaxed) {
println!("{} Exiting validator thread", log_prefix);
let stats = NodeStats {
tps: max_tps,
tx: total,
};
maxes
.write()
.unwrap()
.push((client.transactions_addr(), stats));
break;
}
}
}
fn generate_txs<T: Client>(
fn generate_txs(
shared_txs: &SharedTransactions,
blockhash: &Hash,
source: &[Keypair],
dest: &[Keypair],
threads: usize,
reclaim: bool,
client: &Arc<T>,
) {
let blockhash = client.get_recent_blockhash().unwrap();
let tx_count = source.len();
println!("Signing transactions... {} (reclaim={})", tx_count, reclaim);
let signing_start = Instant::now();
@ -287,7 +236,7 @@ fn generate_txs<T: Client>(
.par_iter()
.map(|(id, keypair)| {
(
system_transaction::create_user_account(id, &keypair.pubkey(), 1, blockhash, 0),
system_transaction::create_user_account(id, &keypair.pubkey(), 1, *blockhash),
timestamp(),
)
})
@ -304,14 +253,9 @@ fn generate_txs<T: Client>(
duration_as_ms(&duration),
blockhash,
);
solana_metrics::submit(
influxdb::Point::new("bench-tps")
.add_tag("op", influxdb::Value::String("generate_txs".to_string()))
.add_field(
"duration",
influxdb::Value::Integer(duration_as_ms(&duration) as i64),
)
.to_owned(),
datapoint_info!(
"bench-tps-generate_txs",
("duration", duration_as_ms(&duration), i64)
);
let sz = transactions.len() / threads;
@ -338,7 +282,7 @@ fn do_tx_transfers<T: Client>(
}
let txs;
{
let mut shared_txs_wl = shared_txs.write().unwrap();
let mut shared_txs_wl = shared_txs.write().expect("write lock in do_tx_transfers");
txs = shared_txs_wl.pop_front();
}
if let Some(txs0) = txs {
@ -355,7 +299,9 @@ fn do_tx_transfers<T: Client>(
if now > tx.1 && now - tx.1 > 1000 * 30 {
continue;
}
client.async_send_transaction(tx.0).unwrap();
client
.async_send_transaction(tx.0)
.expect("async_send_transaction in do_tx_transfers");
}
shared_tx_thread_count.fetch_add(-1, Ordering::Relaxed);
total_tx_sent_count.fetch_add(tx_len, Ordering::Relaxed);
@ -364,15 +310,10 @@ fn do_tx_transfers<T: Client>(
duration_as_ms(&transfer_start.elapsed()),
tx_len as f32 / duration_as_s(&transfer_start.elapsed()),
);
solana_metrics::submit(
influxdb::Point::new("bench-tps")
.add_tag("op", influxdb::Value::String("do_tx_transfers".to_string()))
.add_field(
"duration",
influxdb::Value::Integer(duration_as_ms(&transfer_start.elapsed()) as i64),
)
.add_field("count", influxdb::Value::Integer(tx_len as i64))
.to_owned(),
datapoint_info!(
"bench-tps-do_tx_transfers",
("duration", duration_as_ms(&transfer_start.elapsed()), i64),
("count", tx_len, i64)
);
}
if exit_signal.load(Ordering::Relaxed) {
@ -465,7 +406,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Keypair], lam
to_fund_txs.len(),
);
let blockhash = client.get_recent_blockhash().unwrap();
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
// re-sign retained to_fund_txes with updated blockhash
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
@ -515,7 +456,7 @@ pub fn airdrop_lamports<T: Client>(
id.pubkey(),
);
let blockhash = client.get_recent_blockhash().unwrap();
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
match request_airdrop_transaction(&drone_addr, &id.pubkey(), airdrop_amount, blockhash) {
Ok(transaction) => {
let signature = client.async_send_transaction(transaction).unwrap();
@ -556,7 +497,7 @@ pub fn airdrop_lamports<T: Client>(
}
fn compute_and_report_stats(
maxes: &Arc<RwLock<Vec<(String, NodeStats)>>>,
maxes: &Arc<RwLock<Vec<(String, SampleStats)>>>,
sample_period: u64,
tx_send_elapsed: &Duration,
total_tx_send_count: usize,
@ -570,14 +511,14 @@ fn compute_and_report_stats(
println!("---------------------+---------------+--------------------");
for (sock, stats) in maxes.read().unwrap().iter() {
let maybe_flag = match stats.tx {
let maybe_flag = match stats.txs {
0 => "!!!!!",
_ => "",
};
println!(
"{:20} | {:13.2} | {} {}",
sock, stats.tps, stats.tx, maybe_flag
sock, stats.tps, stats.txs, maybe_flag
);
if stats.tps == 0.0 {
@ -588,27 +529,33 @@ fn compute_and_report_stats(
if stats.tps > max_of_maxes {
max_of_maxes = stats.tps;
}
if stats.tx > max_tx_count {
max_tx_count = stats.tx;
if stats.txs > max_tx_count {
max_tx_count = stats.txs;
}
}
if total_maxes > 0.0 {
let num_nodes_with_tps = maxes.read().unwrap().len() - nodes_with_zero_tps;
let average_max = total_maxes / num_nodes_with_tps as f64;
let average_max = total_maxes / num_nodes_with_tps as f32;
println!(
"\nAverage max TPS: {:.2}, {} nodes had 0 TPS",
average_max, nodes_with_zero_tps
);
}
let total_tx_send_count = total_tx_send_count as u64;
let drop_rate = if total_tx_send_count > max_tx_count {
(total_tx_send_count - max_tx_count) as f64 / total_tx_send_count as f64
} else {
0.0
};
println!(
"\nHighest TPS: {:.2} sampling period {}s max transactions: {} clients: {} drop rate: {:.2}",
max_of_maxes,
sample_period,
max_tx_count,
maxes.read().unwrap().len(),
(total_tx_send_count as u64 - max_tx_count) as f64 / total_tx_send_count as f64,
drop_rate,
);
println!(
"\tAverage TPS: {}",
@ -623,31 +570,67 @@ fn should_switch_directions(num_lamports_per_account: u64, i: u64) -> bool {
i % (num_lamports_per_account / 4) == 0 && (i >= (3 * num_lamports_per_account) / 4)
}
pub fn generate_keypairs(id: &Keypair, tx_count: usize) -> Vec<Keypair> {
pub fn generate_keypairs(seed_keypair: &Keypair, count: usize) -> Vec<Keypair> {
let mut seed = [0u8; 32];
seed.copy_from_slice(&id.to_bytes()[..32]);
seed.copy_from_slice(&seed_keypair.to_bytes()[..32]);
let mut rnd = GenKeys::new(seed);
let mut total_keys = 0;
let mut target = tx_count * 2;
while target > 0 {
let mut target = count;
while target > 1 {
total_keys += target;
target /= MAX_SPENDS_PER_TX;
// Use the upper bound for this division otherwise it may not generate enough keys
target = (target + MAX_SPENDS_PER_TX - 1) / MAX_SPENDS_PER_TX;
}
rnd.gen_n_keypairs(total_keys as u64)
}
pub fn generate_and_fund_keypairs<T: Client>(
client: &T,
drone_addr: Option<SocketAddr>,
funding_pubkey: &Keypair,
tx_count: usize,
lamports_per_account: u64,
) -> (Vec<Keypair>, u64) {
info!("Creating {} keypairs...", tx_count * 2);
let mut keypairs = generate_keypairs(funding_pubkey, tx_count * 2);
info!("Get lamports...");
// Sample the first keypair, see if it has lamports, if so then resume.
// This logic is to prevent lamport loss on repeated solana-bench-tps executions
let last_keypair_balance = client
.get_balance(&keypairs[tx_count * 2 - 1].pubkey())
.unwrap_or(0);
if lamports_per_account > last_keypair_balance {
let extra = lamports_per_account - last_keypair_balance;
let total = extra * (keypairs.len() as u64);
if client.get_balance(&funding_pubkey.pubkey()).unwrap_or(0) < total {
airdrop_lamports(client, &drone_addr.unwrap(), funding_pubkey, total);
}
info!("adding more lamports {}", extra);
fund_keys(client, funding_pubkey, &keypairs, extra);
}
// 'generate_keypairs' generates extra keys to be able to have size-aligned funding batches for fund_keys.
keypairs.truncate(2 * tx_count);
(keypairs, last_keypair_balance)
}
#[cfg(test)]
mod tests {
use super::*;
use solana::cluster_info::FULLNODE_PORT_RANGE;
use solana::fullnode::FullnodeConfig;
use solana::local_cluster::{ClusterConfig, LocalCluster};
use solana::validator::ValidatorConfig;
use solana_client::thin_client::create_client;
use solana_drone::drone::run_local_drone;
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::client::SyncClient;
use solana_sdk::genesis_block::create_genesis_block;
use std::sync::mpsc::channel;
#[test]
@ -666,14 +649,14 @@ mod tests {
}
#[test]
#[ignore]
fn test_bench_tps() {
let fullnode_config = FullnodeConfig::default();
fn test_bench_tps_local_cluster() {
solana_logger::setup();
let validator_config = ValidatorConfig::default();
const NUM_NODES: usize = 1;
let cluster = LocalCluster::new(&ClusterConfig {
node_stakes: vec![999_990; NUM_NODES],
cluster_lamports: 2_000_000,
fullnode_config,
validator_config,
..ClusterConfig::default()
});
@ -688,18 +671,27 @@ mod tests {
config.tx_count = 100;
config.duration = Duration::from_secs(5);
let keypairs = generate_keypairs(&config.id, config.tx_count);
let client = create_client(
(cluster.entry_point_info.gossip, drone_addr),
(cluster.entry_point_info.rpc, cluster.entry_point_info.tpu),
FULLNODE_PORT_RANGE,
);
do_bench_tps(vec![client], config, keypairs, 0);
let lamports_per_account = 100;
let (keypairs, _keypair_balance) = generate_and_fund_keypairs(
&client,
Some(drone_addr),
&config.id,
config.tx_count,
lamports_per_account,
);
let total = do_bench_tps(vec![client], config, keypairs, 0);
assert!(total > 100);
}
#[test]
fn test_bench_tps_bank_client() {
let (genesis_block, id) = GenesisBlock::new(10_000);
let (genesis_block, id) = create_genesis_block(10_000);
let bank = Bank::new(&genesis_block);
let clients = vec![BankClient::new(bank)];
@ -708,9 +700,26 @@ mod tests {
config.tx_count = 10;
config.duration = Duration::from_secs(5);
let keypairs = generate_keypairs(&config.id, config.tx_count);
fund_keys(&clients[0], &config.id, &keypairs, 20);
let (keypairs, _keypair_balance) =
generate_and_fund_keypairs(&clients[0], None, &config.id, config.tx_count, 20);
do_bench_tps(clients, config, keypairs, 0);
}
#[test]
fn test_bench_tps_fund_keys() {
let (genesis_block, id) = create_genesis_block(10_000);
let bank = Bank::new(&genesis_block);
let client = BankClient::new(bank);
let tx_count = 10;
let lamports = 20;
let (keypairs, _keypair_balance) =
generate_and_fund_keypairs(&client, None, &id, tx_count, lamports);
for kp in &keypairs {
// TODO: This should be >= lamports, but fails at the moment
assert_ne!(client.get_balance(&kp.pubkey()).unwrap(), 0);
}
}
}

View File

@ -8,7 +8,7 @@ use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
/// Holds the configuration for a single run of the benchmark
pub struct Config {
pub network_addr: SocketAddr,
pub entrypoint_addr: SocketAddr,
pub drone_addr: SocketAddr,
pub id: Keypair,
pub threads: usize,
@ -22,7 +22,7 @@ pub struct Config {
impl Default for Config {
fn default() -> Config {
Config {
network_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
id: Keypair::new(),
threads: 4,
@ -40,12 +40,12 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
App::new(crate_name!()).about(crate_description!())
.version(crate_version!())
.arg(
Arg::with_name("network")
Arg::with_name("entrypoint")
.short("n")
.long("network")
.long("entrypoint")
.value_name("HOST:PORT")
.takes_value(true)
.help("Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001"),
.help("Rendezvous with the cluster at this entry point; defaults to 127.0.0.1:8001"),
)
.arg(
Arg::with_name("drone")
@ -53,7 +53,7 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.long("drone")
.value_name("HOST:PORT")
.takes_value(true)
.help("Location of the drone; defaults to network:DRONE_PORT"),
.help("Location of the drone; defaults to entrypoint:DRONE_PORT"),
)
.arg(
Arg::with_name("identity")
@ -116,9 +116,9 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
let mut args = Config::default();
if let Some(addr) = matches.value_of("network") {
args.network_addr = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| {
eprintln!("failed to parse network address: {}", e);
if let Some(addr) = matches.value_of("entrypoint") {
args.entrypoint_addr = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e);
exit(1)
});
}

View File

@ -1,15 +1,8 @@
mod bench;
mod cli;
use crate::bench::{
airdrop_lamports, do_bench_tps, fund_keys, generate_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT,
};
use solana::cluster_info::FULLNODE_PORT_RANGE;
use solana::contact_info::ContactInfo;
use solana::gossip_service::discover_nodes;
use solana_client::thin_client::create_client;
use solana_sdk::client::SyncClient;
use solana_sdk::signature::KeypairUtil;
use crate::bench::{do_bench_tps, generate_and_fund_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT};
use solana::gossip_service::{discover_cluster, get_clients};
use std::process::exit;
fn main() {
@ -20,7 +13,7 @@ fn main() {
let cli_config = cli::extract_args(&matches);
let cli::Config {
network_addr,
entrypoint_addr,
drone_addr,
id,
threads,
@ -32,10 +25,11 @@ fn main() {
} = cli_config;
println!("Connecting to the cluster");
let nodes = discover_nodes(&network_addr, num_nodes).unwrap_or_else(|err| {
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
exit(1);
});
let (nodes, _replicators) =
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|err| {
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
exit(1);
});
if nodes.len() < num_nodes {
eprintln!(
"Error: Insufficient nodes discovered. Expecting {} or more",
@ -43,40 +37,16 @@ fn main() {
);
exit(1);
}
let clients: Vec<_> = nodes
.iter()
.filter_map(|node| {
let cluster_entrypoint = node.clone();
let cluster_addrs = cluster_entrypoint.client_facing_addr();
if ContactInfo::is_valid_address(&cluster_addrs.0)
&& ContactInfo::is_valid_address(&cluster_addrs.1)
{
let client = create_client(cluster_addrs, FULLNODE_PORT_RANGE);
Some(client)
} else {
None
}
})
.collect();
println!("Creating {} keypairs...", tx_count * 2);
let keypairs = generate_keypairs(&id, tx_count);
let clients = get_clients(&nodes);
println!("Get lamports...");
// Sample the first keypair, see if it has lamports, if so then resume.
// This logic is to prevent lamport loss on repeated solana-bench-tps executions
let keypair0_balance = clients[0]
.get_balance(&keypairs.last().unwrap().pubkey())
.unwrap_or(0);
if NUM_LAMPORTS_PER_ACCOUNT > keypair0_balance {
let extra = NUM_LAMPORTS_PER_ACCOUNT - keypair0_balance;
let total = extra * (keypairs.len() as u64);
airdrop_lamports(&clients[0], &drone_addr, &id, total);
println!("adding more lamports {}", extra);
fund_keys(&clients[0], &id, &keypairs, extra);
}
let (keypairs, keypair_balance) = generate_and_fund_keypairs(
&clients[0],
Some(drone_addr),
&id,
tx_count,
NUM_LAMPORTS_PER_ACCOUNT,
);
let config = Config {
id,
@ -87,5 +57,5 @@ fn main() {
sustained,
};
do_bench_tps(clients, config, keypairs, keypair0_balance);
do_bench_tps(clients, config, keypairs, keypair_balance);
}

View File

@ -0,0 +1,19 @@
+------------------------------------------------------------------+
| |
| +-----------------+ Neighborhood 0 +-----------------+ |
| | +--------------------->+ | |
| | Validator 1 | | Validator 2 | |
| | +<---------------------+ | |
| +--------+-+------+ +------+-+--------+ |
| | | | | |
| | +-----------------------------+ | | |
| | +------------------------+------+ | |
| | | | | |
+------------------------------------------------------------------+
| | | |
v v v v
+---------+------+---+ +-+--------+---------+
| | | |
| Neighborhood 1 | | Neighborhood 2 |
| | | |
+--------------------+ +--------------------+

View File

@ -0,0 +1,15 @@
+--------------+
| |
+------------+ Leader +------------+
| | | |
| +--------------+ |
v v
+------------+----------------------------------------+------------+
| |
| +-----------------+ Neighborhood 0 +-----------------+ |
| | +--------------------->+ | |
| | Validator 1 | | Validator 2 | |
| | +<---------------------+ | |
| +-----------------+ +-----------------+ |
| |
+------------------------------------------------------------------+

View File

@ -1,28 +1,18 @@
+--------------+
| |
+------------+ Leader +------------+
| | | |
| +--------------+ |
v v
+--------+--------+ +--------+--------+
| +--------------------->+ |
+-----------------+ Validator 1 | | Validator 2 +-------------+
| | +<---------------------+ | |
| +------+-+-+------+ +---+-+-+---------+ |
| | | | | | | |
| | | | | | | |
| +---------------------------------------------+ | | |
| | | | | | | |
| | | | | +----------------------+ | |
| | | | | | | |
| | | | +--------------------------------------------+ |
| | | | | | | |
| | | +----------------------+ | | |
| | | | | | | |
v v v v v v v v
+--------------------+ +--------------------+ +--------------------+ +--------------------+
| | | | | | | |
| Neighborhood 1 | | Neighborhood 2 | | Neighborhood 3 | | Neighborhood 4 |
| | | | | | | |
+--------------------+ +--------------------+ +--------------------+ +--------------------+
+--------------------+
| |
+--------+ Neighborhood 0 +----------+
| | | |
| +--------------------+ |
v v
+---------+----------+ +----------+---------+
| | | |
| Neighborhood 1 | | Neighborhood 2 |
| | | |
+---+-----+----------+ +----------+-----+---+
| | | |
v v v v
+------------------+-+ +-+------------------+ +------------------+-+ +-+------------------+
| | | | | | | |
| Neighborhood 3 | | Neighborhood 4 | | Neighborhood 5 | | Neighborhood 6 |
| | | | | | | |
+--------------------+ +--------------------+ +--------------------+ +--------------------+

View File

@ -0,0 +1,60 @@
.------------.
| Upstream |
| Validators |
`----+-------`
|
|
.-----------------------------------.
| Validator | |
| v |
| .-----------. .------------. |
.--------. | | Fetch | | Repair | |
| Client +---->| Stage | | Stage | |
`--------` | `---+-------` `----+-------` |
| | | |
| v v |
| .-----------. .------------. |
| | TPU |<-->| Blockstore | |
| | | | | |
| `-----------` `----+-------` |
| | |
| v |
| .------------. |
| | Multicast | |
| | Stage | |
| `----+-------` |
| | |
`-----------------------------------`
|
v
.------------.
| Downstream |
| Validators |
`------------`
.------------.
| PoH |
| Service |
`-------+----`
^ |
| |
.-----------------------------------.
| TPU | | |
| | v |
.-------. | .-----------. .---+--------. | .------------.
| Fetch +---->| SigVerify +--->| Banking |<--->| Blockstore |
| Stage | | | Stage | | Stage | | | |
`-------` | `-----------` `-----+------` | `------------`
| | |
| | |
`-----------------------------------`
|
v
.------------.
| Banktree |
| |
`------------`

View File

@ -1,5 +1,5 @@
.--------------------------------------.
| Fullnode |
| Validator |
| |
.--------. | .-------------------. |
| |---->| | |

View File

@ -3,16 +3,4 @@ set -e
cd "$(dirname "$0")"
cargo_install_unless() {
declare crate=$1
shift
"$@" > /dev/null 2>&1 || \
cargo install "$crate"
}
export PATH=$CARGO_HOME/bin:$PATH
cargo_install_unless mdbook mdbook --help
cargo_install_unless svgbob_cli svgbob --help
make -j"$(nproc)"

View File

@ -1,7 +1,8 @@
BOB_SRCS=$(wildcard art/*.bob)
MSC_SRCS=$(wildcard art/*.msc)
MD_SRCS=$(wildcard src/*.md)
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/img/%.svg)
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/img/%.svg) $(MSC_SRCS:art/%.msc=src/img/%.svg)
all: html/index.html
@ -17,6 +18,10 @@ src/img/%.svg: art/%.bob
@mkdir -p $(@D)
svgbob < $< > $@
src/img/%.svg: art/%.msc
@mkdir -p $(@D)
mscgen -T svg -i $< -o $@
src/%.md: %.md
@mkdir -p $(@D)
@cp $< $@

View File

@ -19,9 +19,10 @@
- [Data Plane Fanout](data-plane-fanout.md)
- [Ledger Replication](ledger-replication.md)
- [Secure Vote Signing](vote-signing.md)
- [Staking Delegation and Rewards](stake-delegation-and-rewards.md)
- [Stake Delegation and Rewards](stake-delegation-and-rewards.md)
- [Performance Metrics](performance-metrics.md)
- [Anatomy of a Fullnode](fullnode.md)
- [Anatomy of a Validator](validator.md)
- [TPU](tpu.md)
- [TVU](tvu.md)
- [Blocktree](blocktree.md)
@ -38,9 +39,6 @@
- [Ledger Replication](ledger-replication-to-implement.md)
- [Secure Vote Signing](vote-signing-to-implement.md)
- [Staking Rewards](staking-rewards.md)
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.md)
- [Reliable Vote Transmission](reliable-vote-transmission.md)
- [Persistent Account Storage](persistent-account-storage.md)
- [Cluster Economics](ed_overview.md)
- [Validation-client Economics](ed_validation_client_economics.md)
- [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md)
@ -55,13 +53,17 @@
- [Economic Design MVP](ed_mvp.md)
- [References](ed_references.md)
- [Cluster Test Framework](cluster-test-framework.md)
- [Testing Programs](testing-programs.md)
- [Credit-only Accounts](credit-only-credit-debit-accounts.md)
- [Cluster Software Installation and Updates](installer.md)
- [Deterministic Transaction Fees](transaction-fees.md)
- [Validator](validator-proposal.md)
- [Implemented Design Proposals](implemented-proposals.md)
- [Fork Selection](fork-selection.md)
- [Leader-to-Leader Transition](leader-leader-transition.md)
- [Leader-to-Validator Transition](leader-validator-transition.md)
- [Testnet Participation](testnet-participation.md)
- [Testing Programs](testing-programs.md)
- [Reliable Vote Transmission](reliable-vote-transmission.md)
- [Persistent Account Storage](persistent-account-storage.md)
- [Cluster Software Installation and Updates](installer.md)
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.md)

View File

@ -12,7 +12,7 @@ To run a blockstreamer, include the argument `no-signer` and (optional)
`blockstream` socket location:
```bash
$ ./multinode-demo/fullnode-x.sh --no-signer --blockstream <SOCKET>
$ ./multinode-demo/validator-x.sh --no-signer --blockstream <SOCKET>
```
The stream will output a series of JSON objects:

View File

@ -20,7 +20,7 @@ least amount of internal plumbing exposed to the test.
Tests are provided an entry point, which is a `contact_info::ContactInfo`
structure, and a keypair that has already been funded.
Each node in the cluster is configured with a `fullnode::FullnodeConfig` at boot
Each node in the cluster is configured with a `fullnode::ValidatorConfig` at boot
time. At boot time this configuration specifies any extra cluster configuration
required for the test. The cluster should boot with the configuration when it
is run in-process or in a data center.
@ -61,18 +61,18 @@ let cluster_nodes = discover_nodes(&entry_point_info, num_nodes);
To enable specific scenarios, the cluster needs to be booted with special
configurations. These configurations can be captured in
`fullnode::FullnodeConfig`.
`fullnode::ValidatorConfig`.
For example:
```rust,ignore
let mut fullnode_config = FullnodeConfig::default();
fullnode_config.rpc_config.enable_fullnode_exit = true;
let mut validator_config = ValidatorConfig::default();
validator_config.rpc_config.enable_fullnode_exit = true;
let local = LocalCluster::new_with_config(
num_nodes,
10_000,
100,
&fullnode_config
&validator_config
);
```
@ -86,9 +86,9 @@ advertised gossip nodes.
Configure the RPC service:
```rust,ignore
let mut fullnode_config = FullnodeConfig::default();
fullnode_config.rpc_config.enable_rpc_gossip_push = true;
fullnode_config.rpc_config.enable_rpc_gossip_refresh_active_set = true;
let mut validator_config = ValidatorConfig::default();
validator_config.rpc_config.enable_rpc_gossip_push = true;
validator_config.rpc_config.enable_rpc_gossip_refresh_active_set = true;
```
Wire the RPCs and write a new test:

View File

@ -28,7 +28,7 @@ its copy.
## Joining a Cluster
Fullnodes and replicators enter the cluster via registration messages sent to
Validators and replicators enter the cluster via registration messages sent to
its *control plane*. The control plane is implemented using a *gossip*
protocol, meaning that a node may register with any existing node, and expect
its registration to propagate to all nodes in the cluster. The time it takes

View File

@ -5,16 +5,15 @@ broadcast transaction blobs to all nodes in a very quick and efficient manner.
In order to establish the fanout, the cluster divides itself into small
collections of nodes, called *neighborhoods*. Each node is responsible for
sharing any data it receives with the other nodes in its neighborhood, as well
as propagating the data on to a small set of nodes in other neighborhoods.
as propagating the data on to a small set of nodes in other neighborhoods.
This way each node only has to communicate with a small number of nodes.
During its slot, the leader node distributes blobs between the validator nodes
in one neighborhood (layer 1). Each validator shares its data within its
neighborhood, but also retransmits the blobs to one node in each of multiple
neighborhoods in the next layer (layer 2). The layer-2 nodes each share their
data with their neighborhood peers, and retransmit to nodes in the next layer,
etc, until all nodes in the cluster have received all the blobs.
<img alt="Two layer cluster" src="img/data-plane.svg" class="center"/>
in the first neighborhood (layer 0). Each validator shares its data within its
neighborhood, but also retransmits the blobs to one node in some neighborhoods
in the next layer (layer 1). The layer-1 nodes each share their data with their
neighborhood peers, and retransmit to nodes in the next layer, etc, until all
nodes in the cluster have received all the blobs.
## Neighborhood Assignment - Weighted Selection
@ -23,48 +22,50 @@ cluster is divided into neighborhoods. To achieve this, all the recognized
validator nodes (the TVU peers) are sorted by stake and stored in a list. This
list is then indexed in different ways to figure out neighborhood boundaries and
retransmit peers. For example, the leader will simply select the first nodes to
make up layer 1. These will automatically be the highest stake holders, allowing
the heaviest votes to come back to the leader first. Layer-1 and lower-layer
nodes use the same logic to find their neighbors and lower layer peers.
make up layer 0. These will automatically be the highest stake holders, allowing
the heaviest votes to come back to the leader first. Layer-0 and lower-layer
nodes use the same logic to find their neighbors and next layer peers.
## Layer and Neighborhood Structure
The current leader makes its initial broadcasts to at most `DATA_PLANE_FANOUT`
nodes. If this layer 1 is smaller than the number of nodes in the cluster, then
nodes. If this layer 0 is smaller than the number of nodes in the cluster, then
the data plane fanout mechanism adds layers below. Subsequent layers follow
these constraints to determine layer-capacity: Each neighborhood contains
`NEIGHBORHOOD_SIZE` nodes and each layer may have up to `DATA_PLANE_FANOUT/2`
neighborhoods.
`DATA_PLANE_FANOUT` nodes. Layer-0 starts with 1 neighborhood with fanout nodes.
The number of nodes in each additional layer grows by a factor of fanout.
As mentioned above, each node in a layer only has to broadcast its blobs to its
neighbors and to exactly 1 node in each next-layer neighborhood, instead of to
every TVU peer in the cluster. In the default mode, each layer contains
`DATA_PLANE_FANOUT/2` neighborhoods. The retransmit mechanism also supports a
second, `grow`, mode of operation that squares the number of neighborhoods
allowed each layer. This dramatically reduces the number of layers needed to
support a large cluster, but can also have a negative impact on the network
pressure on each node in the lower layers. A good way to think of the default
mode (when `grow` is disabled) is to imagine it as chain of layers, where the
leader sends blobs to layer-1 and then layer-1 to layer-2 and so on, the `layer
capacities` remain constant, so all layers past layer-2 will have the same
number of nodes until the whole cluster is covered. When `grow` is enabled, this
becomes a traditional fanout where layer-3 will have the square of the number of
nodes in layer-2 and so on.
neighbors and to exactly 1 node in some next-layer neighborhoods,
instead of to every TVU peer in the cluster. A good way to think about this is,
layer-0 starts with 1 neighborhood with fanout nodes, layer-1 adds "fanout"
neighborhoods, each with fanout nodes and layer-2 will have
`fanout * number of nodes in layer-1` and so on.
This way each node only has to communicate with a maximum of `2 * DATA_PLANE_FANOUT - 1` nodes.
The following diagram shows how the Leader sends blobs with a Fanout of 2 to
Neighborhood 0 in Layer 0 and how the nodes in Neighborhood 0 share their data
with each other.
<img alt="Leader sends blobs to Neighborhood 0 in Layer 0" src="img/data-plane-seeding.svg" class="center"/>
The following diagram shows how Neighborhood 0 fans out to Neighborhoods 1 and 2.
<img alt="Neighborhood 0 Fanout to Neighborhood 1 and 2" src="img/data-plane-fanout.svg" class="center"/>
Finally, the following diagram shows a two layer cluster with a Fanout of 2.
<img alt="Two layer cluster with a Fanout of 2" src="img/data-plane.svg" class="center"/>
#### Configuration Values
`DATA_PLANE_FANOUT` - Determines the size of layer 1. Subsequent
layers have `DATA_PLANE_FANOUT/2` neighborhoods when `grow` is inactive.
`NEIGHBORHOOD_SIZE` - The number of nodes allowed in a neighborhood.
`DATA_PLANE_FANOUT` - Determines the size of layer 0. Subsequent
layers grow by a factor of `DATA_PLANE_FANOUT`.
The number of nodes in a neighborhood is equal to the fanout value.
Neighborhoods will fill to capacity before new ones are added, i.e if a
neighborhood isn't full, it _must_ be the last one.
`GROW_LAYER_CAPACITY` - Whether or not retransmit should be behave like a
_traditional fanout_, i.e if each additional layer should have growing
capacities. When this mode is disabled (default), all layers after layer 1 have
the same capacity, keeping the network pressure on all nodes equal.
Currently, configuration is set when the cluster is launched. In the future,
these parameters may be hosted on-chain, allowing modification on the fly as the
cluster sizes change.
@ -72,13 +73,10 @@ cluster sizes change.
## Neighborhoods
The following diagram shows how two neighborhoods in different layers interact.
What this diagram doesn't capture is that each neighbor actually receives
blobs from one validator per neighborhood above it. This means that, to
cripple a neighborhood, enough nodes (erasure codes +1 per neighborhood) from
the layer above need to fail. Since multiple neighborhoods exist in the upper
layer and a node will receive blobs from a node in each of those neighborhoods,
we'd need a big network failure in the upper layers to end up with incomplete
data.
To cripple a neighborhood, enough nodes (erasure codes +1) from the neighborhood
above need to fail. Since each neighborhood receives blobs from multiple nodes
in a neighborhood in the upper layer, we'd need a big network failure in the upper
layers to end up with incomplete data.
<img alt="Inner workings of a neighborhood"
src="img/data-plane-neighborhood.svg" class="center"/>

View File

@ -47,8 +47,8 @@ nodes are started
$ cargo build --all
```
The network is initialized with a genesis ledger and fullnode configuration files.
These files can be generated by running the following script.
The network is initialized with a genesis ledger generated by running the
following script.
```bash
$ ./multinode-demo/setup.sh
@ -69,7 +69,7 @@ $ ./multinode-demo/drone.sh
### Singlenode Testnet
Before you start a fullnode, make sure you know the IP address of the machine you
Before you start a validator, make sure you know the IP address of the machine you
want to be the bootstrap leader for the demo, and make sure that udp ports 8000-10000 are
open on all the machines you want to test with.
@ -86,10 +86,10 @@ The drone does not need to be running for subsequent leader starts.
### Multinode Testnet
To run a multinode testnet, after starting a leader node, spin up some
additional full nodes in separate shells:
additional validators in separate shells:
```bash
$ ./multinode-demo/fullnode-x.sh
$ ./multinode-demo/validator-x.sh
```
To run a performance-enhanced full node on Linux,
@ -99,7 +99,7 @@ your system:
```bash
$ ./fetch-perf-libs.sh
$ SOLANA_CUDA=1 ./multinode-demo/bootstrap-leader.sh
$ SOLANA_CUDA=1 ./multinode-demo/fullnode-x.sh
$ SOLANA_CUDA=1 ./multinode-demo/validator.sh
```
### Testnet Client Demo
@ -145,7 +145,7 @@ Generally we are using `debug` for infrequent debug messages, `trace` for potent
messages and `info` for performance-related logging.
You can also attach to a running process with GDB. The leader's process is named
_solana-fullnode_:
_solana-validator_:
```bash
$ sudo gdb
@ -161,7 +161,7 @@ This will dump all the threads stack traces into gdb.txt
In this example the client connects to our public testnet. To run validators on the testnet you would need to open udp ports `8000-10000`.
```bash
$ ./multinode-demo/client.sh --network testnet.solana.com:8001 --duration 60
$ ./multinode-demo/client.sh --entrypoint testnet.solana.com:8001 --duration 60
```
You can observe the effects of your client's transactions on our [dashboard](https://metrics.solana.com:3000/d/testnet/testnet-hud?orgId=2&from=now-30m&to=now&refresh=5s&var-testnet=testnet)

View File

@ -1,6 +1,6 @@
# Gossip Service
The Gossip Service acts as a gateway to nodes in the control plane. Fullnodes
The Gossip Service acts as a gateway to nodes in the control plane. Validators
use the service to ensure information is available to all other nodes in a cluster.
The service broadcasts information using a gossip protocol.
@ -116,8 +116,8 @@ Just like *pull message*, nodes are selected into the active set based on weight
## Notable differences from PlumTree
The active push protocol described here is based on (Plum
Tree)[https://haslab.uminho.pt/jop/files/lpr07a.pdf]. The main differences are:
The active push protocol described here is based on [Plum
Tree](https://haslab.uminho.pt/jop/files/lpr07a.pdf). The main differences are:
* Push messages have a wallclock that is signed by the originator. Once the
wallclock expires the message is dropped. A hop limit is difficult to implement

View File

@ -58,7 +58,7 @@ $ solana-install deploy http://example.com/path/to/solana-release.tar.bz2 update
$ solana-install init --pubkey 92DMonmBYXwEMHJ99c9ceRSpAmk9v6i3RdvDdXaVcrfj # <-- pubkey is obtained from whoever is deploying the updates
$ export PATH=~/.local/share/solana-install/bin:$PATH
$ solana-keygen ... # <-- runs the latest solana-keygen
$ solana-install run solana-fullnode ... # <-- runs a fullnode, restarting it as necesary when an update is applied
$ solana-install run solana-validator ... # <-- runs a validator, restarting it as necesary when an update is applied
```
### On-chain Update Manifest

View File

@ -30,6 +30,7 @@ Methods
* [getSlotLeader](#getslotleader)
* [getNumBlocksSinceSignatureConfirmation](#getnumblockssincesignatureconfirmation)
* [getTransactionCount](#gettransactioncount)
* [getEpochVoteAccounts](#getepochvoteaccounts)
* [requestAirdrop](#requestairdrop)
* [sendTransaction](#sendtransaction)
* [startSubscriptionChannel](#startsubscriptionchannel)
@ -167,13 +168,16 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
---
### getRecentBlockhash
Returns a recent block hash from the ledger
Returns a recent block hash from the ledger, and a fee schedule that can be used
to compute the cost of submitting a transaction using it.
##### Parameters:
None
##### Results:
An array consisting of
* `string` - a Hash as base-58 encoded string
* `FeeCalculator object` - the fee schedule for this block hash
##### Example:
```bash
@ -181,7 +185,7 @@ None
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC",{"lamportsPerSignature": 0}],"id":1}
```
---
@ -271,6 +275,39 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
---
### getEpochVoteAccounts
Returns the account info and associated stake for all the voting accounts in the current epoch.
##### Parameters:
None
##### Results:
An array consisting of vote accounts:
* `string` - the vote account's Pubkey as base-58 encoded string
* `integer` - the stake, in lamports, delegated to this vote account
* `VoteState` - the vote account's state
Each VoteState will be a JSON object with the following sub fields:
* `votes`, array of most recent vote lockouts
* `node_pubkey`, the pubkey of the node that votes using this account
* `authorized_voter_pubkey`, the pubkey of the authorized vote signer for this account
* `commission`, a 32-bit integer used as a fraction (commission/MAX_U32) for rewards payout
* `root_slot`, the most recent slot this account has achieved maximum lockout
* `credits`, credits accrued by this account for reaching lockouts
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochVoteAccounts"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[[[84,115,89,23,41,83,221,72,58,23,53,245,195,188,140,161,242,189,200,164,139,214,12,180,84,161,28,151,24,243,159,125],10000000,{"authorized_voter_pubkey":[84,115,89,23,41,83,221,72,58,23,53,245,195,188,140,161,242,189,200,164,139,214,12,180,84,161,28,151,24,243,159,125],"commission":0,"credits":0,"node_pubkey":[49,139,227,211,47,39,69,86,131,244,160,144,228,169,84,143,142,253,83,81,212,110,254,12,242,71,219,135,30,60,157,213],"root_slot":null,"votes":[{"confirmation_count":1,"slot":0}]}]],"id":1}
```
---
### requestAirdrop
Requests an airdrop of lamports to a Pubkey
@ -316,6 +353,14 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
After connect to the RPC PubSub websocket at `ws://<ADDRESS>/`:
- Submit subscription requests to the websocket using the methods below
- Multiple subscriptions may be active at once
- All subscriptions take an optional `confirmations` parameter, which defines
how many confirmed blocks the node should wait before sending a notification.
The greater the number, the more likely the notification is to represent
consensus across the cluster, and the less likely it is to be affected by
forking or rollbacks. If unspecified, the default value is 0; the node will
send a notification as soon as it witnesses the event. The maximum
`confirmations` wait length is the cluster's `MAX_LOCKOUT_HISTORY`, which
represents the economic finality of the chain.
---
@ -325,6 +370,8 @@ for a given account public key changes
##### Parameters:
* `string` - account Pubkey, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
##### Results:
* `integer` - Subscription id (needed to unsubscribe)
@ -334,6 +381,8 @@ for a given account public key changes
// Request
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
@ -371,6 +420,8 @@ for a given account owned by the program changes
##### Parameters:
* `string` - program_id Pubkey, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
##### Results:
* `integer` - Subscription id (needed to unsubscribe)
@ -380,6 +431,8 @@ for a given account owned by the program changes
// Request
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV"]}
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
@ -419,6 +472,8 @@ On `signatureNotification`, the subscription is automatically cancelled
##### Parameters:
* `string` - Transaction Signature, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
##### Results:
* `integer` - subscription id (needed to unsubscribe)
@ -428,6 +483,8 @@ On `signatureNotification`, the subscription is automatically cancelled
// Request
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```

View File

@ -57,7 +57,7 @@ Forwarding is preferred, as it would minimize network congestion, allowing the
cluster to advertise higher TPS capacity.
## Fullnode Loop
## Validator Loop
The PoH Recorder manages the transition between modes. Once a ledger is
replayed, the validator can run until the recorder indicates it should be

View File

@ -2,6 +2,12 @@
Replication behavior yet to be implemented.
### Storage epoch
The storage epoch should be the number of slots which results in around 100GB-1TB of
ledger to be generated for replicators to store. Replicators will start storing ledger
when a given fork has a high probability of not being rolled back.
### Validator behavior
3. Every NUM\_KEY\_ROTATION\_TICKS it also validates samples received from
@ -37,3 +43,100 @@ transacation proves the validator incorrectly validated a fake storage proof.
The replicator is rewarded and the validator's staking balance is slashed or
frozen.
### Storage proof contract logic
Each replicator and validator will have their own storage account. The validator's
account would be separate from their gossip id similiar to their vote account.
These should be implemented as two programs one which handles the validator as the keysigner
and one for the replicator. In that way when the programs reference other accounts, they
can check the program id to ensure it is a validator or replicator account they are
referencing.
#### SubmitMiningProof
```rust,ignore
SubmitMiningProof {
slot: u64,
sha_state: Hash,
signature: Signature,
};
keys = [replicator_keypair]
```
Replicators create these after mining their stored ledger data for a certain hash value.
The slot is the end slot of the segment of ledger they are storing, the sha\_state
the result of the replicator using the hash function to sample their encrypted ledger segment.
The signature is the signature that was created when they signed a PoH value for the
current storage epoch. The list of proofs from the current storage epoch should be saved
in the account state, and then transfered to a list of proofs for the previous epoch when
the epoch passes. In a given storage epoch a given replicator should only submit proofs
for one segment.
The program should have a list of slots which are valid storage mining slots.
This list should be maintained by keeping track of slots which are rooted slots in which a significant
portion of the network has voted on with a high lockout value, maybe 32-votes old. Every SLOTS\_PER\_SEGMENT
number of slots would be added to this set. The program should check that the slot is in this set. The set can
be maintained by receiving a AdvertiseStorageRecentBlockHash and checking with its bank/locktower state.
The program should do a signature verify check on the signature, public key from the transaction submitter and the message of
the previous storage epoch PoH value.
#### ProofValidation
```rust,ignore
ProofValidation {
proof_mask: Vec<ProofStatus>,
}
keys = [validator_keypair, replicator_keypair(s) (unsigned)]
```
A validator will submit this transaction to indicate that a set of proofs for a given
segment are valid/not-valid or skipped where the validator did not look at it. The
keypairs for the replicators that it looked at should be referenced in the keys so the program
logic can go to those accounts and see that the proofs are generated in the previous epoch. The
sampling of the storage proofs should be verified ensuring that the correct proofs are skipped by
the validator according to the logic outlined in the validator behavior of sampling.
The included replicator keys will indicate the the storage samples which are being referenced; the
length of the proof\_mask should be verified against the set of storage proofs in the referenced
replicator account(s), and should match with the number of proofs submitted in the previous storage
epoch in the state of said replicator account.
#### ClaimStorageReward
```rust,ignore
ClaimStorageReward {
}
keys = [validator_keypair or replicator_keypair, validator/replicator_keypairs (unsigned)]
```
Replicators and validators will use this transaction to get paid tokens from a program state
where SubmitStorageProof, ProofValidation and ChallengeProofValidations are in a state where
proofs have been submitted and validated and there are no ChallengeProofValidations referencing
those proofs. For a validator, it should reference the replicator keypairs to which it has validated
proofs in the relevant epoch. And for a replicator it should reference validator keypairs for which it
has validated and wants to be rewarded.
#### ChallengeProofValidation
```rust,ignore
ChallengeProofValidation {
proof_index: u64,
hash_seed_value: Vec<u8>,
}
keys = [replicator_keypair, validator_keypair]
```
This transaction is for catching lazy validators who are not doing the work to validate proofs.
A replicator will submit this transaction when it sees a validator has approved a fake SubmitMiningProof
transaction. Since the replicator is a light client not looking at the full chain, it will have to ask
a validator or some set of validators for this information maybe via RPC call to obtain all ProofValidations for
a certain segment in the previous storage epoch. The program will look in the validator account
state see that a ProofValidation is submitted in the previous storage epoch and hash the hash\_seed\_value and
see that the hash matches the SubmitMiningProof transaction and that the validator marked it as valid. If so,
then it will save the challenge to the list of challenges that it has in its state.
#### AdvertiseStorageRecentBlockhash
```rust,ignore
AdvertiseStorageRecentBlockhash {
hash: Hash,
slot: u64,
}
```
Validators and replicators will submit this to indicate that a new storage epoch has passed and that the
storage proofs which are current proofs should now be for the previous epoch. Other transactions should
check to see that the epoch that they are referencing is accurate according to current chain state.

View File

@ -31,7 +31,7 @@ core. The total space required for verification is `1_ledger_segment +
Validators for PoRep are the same validators that are verifying transactions.
They have some stake that they have put up as collateral that ensures that
their work is honest. If you can prove that a validator verified a fake PoRep,
then the validators stake can be slashed.
then the validator will not receive a reward for that storage epoch.
Replicators are specialized *light clients*. They download a part of the ledger
and store it, and provide PoReps of storing the ledger. For each verified PoRep
@ -53,7 +53,7 @@ changes to determine what rate it can validate storage proofs.
### Constants
1. NUM\_STORAGE\_ENTRIES: Number of entries in a segment of ledger data. The
1. SLOTS\_PER\_SEGMENT: Number of slots in a segment of ledger data. The
unit of storage for a replicator.
2. NUM\_KEY\_ROTATION\_TICKS: Number of ticks to save a PoH value and cause a
key generation for the section of ledger just generated and the rotation of
@ -77,7 +77,7 @@ height.
3. Validator generates a storage proof confirmation transaction.
4. The storage proof confirmation transaction is integrated into the ledger.
6. Validator responds to RPC interfaces for what the last storage epoch PoH
value is and its entry\_height.
value is and its slot.
### Replicator behavior
@ -95,10 +95,10 @@ is:
- (d) replicator can subscribe to an abbreviated transaction stream to
generate the information itself
2. A replicator obtains the PoH hash corresponding to the last key rotation
along with its entry\_height.
along with its slot.
3. The replicator signs the PoH hash with its keypair. That signature is the
seed used to pick the segment to replicate and also the encryption key. The
replicator mods the signature with the entry\_height to get which segment to
replicator mods the signature with the slot to get which segment to
replicate.
4. The replicator retrives the ledger by asking peer validators and
replicators. See 6.5.
@ -118,9 +118,9 @@ current leader and it is put onto the ledger.
### Finding who has a given block of ledger
1. Validators monitor the transaction stream for storage mining proofs, and
keep a mapping of ledger segments by entry\_height to public keys. When it sees
keep a mapping of ledger segments by slot to public keys. When it sees
a storage mining proof it updates this mapping and provides an RPC interface
which takes an entry\_height and hands back a list of public keys. The client
which takes a slot and hands back a list of public keys. The client
then looks up in their cluster\_info table to see which network address that
corresponds to and sends a repair request to retrieve the necessary blocks of
ledger.

View File

@ -85,7 +85,7 @@ contains the following state information:
* Account::lamports - The staked lamports.
* `voter_id` - The pubkey of the VoteState instance the lamports are
* `voter_pubkey` - The pubkey of the VoteState instance the lamports are
delegated to.
* `credits_observed` - The total credits claimed over the lifetime of the
@ -109,7 +109,7 @@ program.
* `account[0]` - RW - The StakeState::Delegate instance.
`StakeState::Delegate::credits_observed` is initialized to `VoteState::credits`.
`StakeState::Delegate::voter_id` is initialized to `account[1]`
`StakeState::Delegate::voter_pubkey` is initialized to `account[1]`
* `account[1]` - R - The VoteState instance.
@ -127,7 +127,7 @@ reward.
* `account[1]` - RW - The StakeState::Delegate instance that is redeeming votes
credits.
* `account[2]` - R - The VoteState instance, must be the same as
`StakeState::voter_id`
`StakeState::voter_pubkey`
Reward is payed out for the difference between `VoteState::credits` to
`StakeState::Delgate.credits_observed`, and `credits_observed` is updated to
@ -181,7 +181,7 @@ the VoteState program or submitting votes to the program.
The total stake allocated to a VoteState program can be calculated by the sum of
all the StakeState programs that have the VoteState pubkey as the
`StakeState::Delegate::voter_id`.
`StakeState::Delegate::voter_pubkey`.
## Example Callflow
@ -194,12 +194,12 @@ nodes since stake is used as weight in the network control and data planes. One
way to implement this would be for the StakeState to delegate to a pool of
validators instead of a single one.
Instead of a single `vote_id` and `credits_observed` entry in the StakeState
Instead of a single `vote_pubkey` and `credits_observed` entry in the StakeState
program, the program can be initialized with a vector of tuples.
```rust,ignore
Voter {
voter_id: Pubkey,
voter_pubkey: Pubkey,
credits_observed: u64,
weight: u8,
}

View File

@ -0,0 +1,29 @@
# Performance Metrics
Solana cluster performance is measured as average number of transactions per second
that the network can sustain (TPS). And, how long it takes for a transaction to be
confirmed by super majority of the cluster (Confirmation Time).
Each cluster node maintains various counters that are incremented on certain events.
These counters are periodically uploaded to a cloud based database. Solana's metrics
dashboard fetches these counters, and computes the performance metrics and displays
it on the dashboard.
## TPS
The leader node's banking stage maintains a count of transactions that it recorded.
The dashboard displays the count averaged over 2 second period in the TPS time series
graph. The dashboard also shows per second mean, maximum and total TPS as a running
counter.
## Confirmation Time
Each validator node maintains a list of active ledger forks that are visible to the node.
A fork is considered to be frozen when the node has received and processed all entries
corresponding to the fork. A fork is considered to be confirmed when it receives cumulative
super majority vote, and when one of its children forks is frozen.
The node assigns a timestamp to every new fork, and computes the time it took to confirm
the fork. This time is reflected as validator confirmation time in performance metrics.
The performance dashboard displays the average of each validator node's confirmation time
as a time series graph.

View File

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

View File

@ -1,68 +1,195 @@
# Stake Delegation and Rewards
Stakers are rewarded for helping validate the ledger. They do it by delegating
their stake to fullnodes. Those fullnodes do the legwork and send votes to the
stakers' staking accounts. The rest of the cluster uses those stake-weighted
votes to select a block when forks arise. Both the fullnode and staker need
some economic incentive to play their part. The fullnode needs to be
compensated for its hardware and the staker needs to be compensated for risking
getting its stake slashed. The economics are covered in [staking
Stakers are rewarded for helping to validate the ledger. They do this by
delegating their stake to validator nodes. Those validators do the legwork of
replaying the ledger and send votes to a per-node vote account to which stakers
can delegate their stakes. The rest of the cluster uses those stake-weighted
votes to select a block when forks arise. Both the validator and staker need
some economic incentive to play their part. The validator needs to be
compensated for its hardware and the staker needs to be compensated for the risk
of getting its stake slashed. The economics are covered in [staking
rewards](staking-rewards.md). This chapter, on the other hand, describes the
underlying mechanics of its implementation.
## Vote and Rewards accounts
## Basic Besign
The rewards process is split into two on-chain programs. The Vote program
solves the problem of making stakes slashable. The Rewards account acts as
custodian of the rewards pool. It is responsible for paying out each staker
once the staker proves to the Rewards program that it participated in
validating the ledger.
The general idea is that the validator owns a Vote account. The Vote account
tracks validator votes, counts validator generated credits, and provides any
additional validator specific state. The Vote account is not aware of any
stakes delegated to it and has no staking weight.
The Vote account contains the following state information:
A separate Stake account (created by a staker) names a Vote account to which the
stake is delegated. Rewards generated are proportional to the amount of
lamports staked. The Stake account is owned by the staker only. Lamports
stored in this account are the stake.
* votes - The submitted votes.
## Passive Delegation
* `delegate_id` - An identity that may operate with the weight of this
account's stake. It is typically the identity of a fullnode, but may be any
identity involved in stake-weighted computations.
Any number of Stake accounts can delegate to a single
Vote account without an interactive action from the identity controlling
the Vote account or submitting votes to the account.
* `authorized_voter_id` - Only this identity is authorized to submit votes.
The total stake allocated to a Vote account can be calculated by the sum of
all the Stake accounts that have the Vote account pubkey as the
`StakeState::Delegate::voter_pubkey`.
* `credits` - The amount of unclaimed rewards.
## Vote and Stake accounts
* `root_slot` - The last slot to reach the full lockout commitment necessary
for rewards.
The rewards process is split into two on-chain programs. The Vote program solves
the problem of making stakes slashable. The Stake account acts as custodian of
the rewards pool, and provides passive delegation. The Stake program is
responsible for paying out each staker once the staker proves to the Stake
program that its delegate has participated in validating the ledger.
The Rewards program is stateless and pays out reward when a staker submits its
Vote account to the program. Claiming a reward requires a transaction that
includes the following instructions:
### VoteState
1. `RewardsInstruction::RedeemVoteCredits`
2. `VoteInstruction::ClearCredits`
VoteState is the current state of all the votes the validator has submitted to
the network. VoteState contains the following state information:
The Rewards program transfers lamports from the Rewards account to the Vote
account's public key. The Rewards program also ensures that the `ClearCredits`
instruction follows the `RedeemVoteCredits` instruction, such that a staker may
not claim rewards for the same work more than once.
* votes - The submitted votes data structure.
* credits - The total number of rewards this vote program has generated over its
lifetime.
* root\_slot - The last slot to reach the full lockout commitment necessary for
rewards.
* commission - The commission taken by this VoteState for any rewards claimed by
staker's Stake accounts. This is the percentage ceiling of the reward.
* Account::lamports - The accumulated lamports from the commission. These do not
count as stakes.
* `authorized_vote_signer` - Only this identity is authorized to submit votes. This field can only modified by this identity.
### VoteInstruction::Initialize
* `account[0]` - RW - The VoteState
`VoteState::authorized_vote_signer` is initialized to `account[0]`
other VoteState members defaulted
### VoteInstruction::AuthorizeVoteSigner(Pubkey)
* `account[0]` - RW - The VoteState
`VoteState::authorized_vote_signer` is set to to `Pubkey`, instruction must by
signed by Pubkey
### VoteInstruction::Vote(Vec<Vote>)
* `account[0]` - RW - The VoteState
`VoteState::lockouts` and `VoteState::credits` are updated according to voting lockout rules see [Fork Selection](fork-selection.md)
### Delegating Stake
* `account[1]` - RO - A list of some N most recent slots and their hashes for the vote to be verified against.
`VoteInstruction::DelegateStake` allows the staker to choose a fullnode to
validate the ledger on its behalf. By being a delegate, the fullnode is
entitled to collect transaction fees when its is leader. The larger the stake,
the more often the fullnode will be able to collect those fees.
### Authorizing a Vote Signer
### StakeState
A StakeState takes one of two forms, StakeState::Delegate and StakeState::MiningPool.
### StakeState::Delegate
StakeState is the current delegation preference of the **staker**. StakeState
contains the following state information:
* Account::lamports - The staked lamports.
* `voter_pubkey` - The pubkey of the VoteState instance the lamports are
delegated to.
* `credits_observed` - The total credits claimed over the lifetime of the
program.
### StakeState::MiningPool
There are two approaches to the mining pool. The bank could allow the
StakeState program to bypass the token balance check, or a program representing
the mining pool could run on the network. To avoid a single network wide lock,
the pool can be split into several mining pools. This design focuses on using
StakeState::MiningPool instances as the cluster wide mining pools.
* 256 StakeState::MiningPool are initialized, each with 1/256 number of mining pool
tokens stored as `Account::lamports`.
The stakes and the MiningPool are accounts that are owned by the same `Stake`
program.
### StakeInstruction::Initialize
* `account[0]` - RW - The StakeState::Delegate instance.
`StakeState::Delegate::credits_observed` is initialized to `VoteState::credits`.
`StakeState::Delegate::voter_pubkey` is initialized to `account[1]`
* `account[1]` - R - The VoteState instance.
### StakeInstruction::RedeemVoteCredits
The Staker or the owner of the Stake account sends a transaction with this
instruction to claim rewards.
The Vote account and the Stake account pair maintain a lifetime counter
of total rewards generated and claimed. When claiming rewards, the total lamports
deposited into the Stake account and as validator commission is proportional to
`VoteState::credits - StakeState::credits_observed`.
* `account[0]` - RW - The StakeState::MiningPool instance that will fulfill the
reward.
* `account[1]` - RW - The StakeState::Delegate instance that is redeeming votes
credits.
* `account[2]` - R - The VoteState instance, must be the same as
`StakeState::voter_pubkey`
Reward is paid out for the difference between `VoteState::credits` to
`StakeState::Delgate.credits_observed`, and `credits_observed` is updated to
`VoteState::credits`. The commission is deposited into the Vote account token
balance, and the reward is deposited to the Stake account token balance.
The total lamports paid is a percentage-rate of the lamports staked muiltplied by
the ratio of rewards being redeemed to rewards that could have been generated
during the rate period.
Any random MiningPool can be used to redeem the credits.
```rust,ignore
let credits_to_claim = vote_state.credits - stake_state.credits_observed;
stake_state.credits_observed = vote_state.credits;
```
`credits_to_claim` is used to compute the reward and commission, and
`StakeState::Delegate::credits_observed` is updated to the latest
`VoteState::credits` value.
## Collecting network fees into the MiningPool
At the end of the block, before the bank is frozen, but after it processed all
the transactions for the block, a virtual instruction is executed to collect
the transaction fees.
* A portion of the fees are deposited into the leader's account.
* A portion of the fees are deposited into the smallest StakeState::MiningPool
account.
## Authorizing a Vote Signer
`VoteInstruction::AuthorizeVoter` allows a staker to choose a signing service
for its votes. That service is responsible for ensuring the vote won't cause
the staker to be slashed.
## Limitations
## Benefits of the design
Many stakers may delegate their stakes to the same fullnode. The fullnode must
send a separate vote to each staking account. If there are far more stakers
than fullnodes, that's a lot of network traffic. An alternative design might
have fullnodes submit each vote to just one account and then have each staker
submit that account along with their own to collect its reward.
* Single vote for all the stakers.
* Clearing of the credit variable is not necessary for claiming rewards.
* Each delegated stake can claim its rewards independently.
* Commission for the work is deposited when a reward is claimed by the delegated
stake.
This proposal would benefit from the `read-only` accounts proposal to allow for
many rewards to be claimed concurrently.
## Example Callflow
<img alt="Passive Staking Callflow" src="img/passive-staking-callflow.svg" class="center"/>

View File

@ -1,8 +1,8 @@
# Staking Rewards
Initial Proof of Stake (PoS) (i.e. using in-protocol asset, SOL, to provide
secure consensus) design ideas outlined here. Solana will implement a proof of
stake reward/security scheme for node validators in the cluster. The purpose is
A Proof of Stake (PoS), (i.e. using in-protocol asset, SOL, to provide
secure consensus) design is outlined here. Solana implements a proof of
stake reward/security scheme for validator nodes in the cluster. The purpose is
threefold:
- Align validator incentives with that of the greater cluster through
@ -64,7 +64,7 @@ capital-at-risk to prevent a logical/optimal strategy of multiple chain voting.
We intend to implement slashing rules which, if broken, result some amount of
the offending validator's deposited stake to be removed from circulation. Given
the ordering properties of the PoH data structure, we believe we can simplify
our slashing rules to the level of a voting lockout time assigned per vote.
our slashing rules to the level of a voting lockout time assigned per vote.
I.e. Each vote has an associated lockout time (PoH duration) that represents a
duration by any additional vote from that validator must be in a PoH that
@ -110,7 +110,7 @@ in a slashable amount as a function of either:
1. the fraction of validators, out of the total validator pool, that were also
slashed during the same time period (ala Casper)
2. the amount of time since the vote was cast (e.g. a linearly increasing % of
total deposited as slashable amount over time), or both.
total deposited as slashable amount over time), or both.
This is an area currently under exploration

View File

@ -15,39 +15,43 @@ reasons:
* The cluster rolled back the ledger
* A validator responded to queries maliciously
### The Transact Trait
### The AsyncClient and SyncClient Traits
To troubleshoot, the application should retarget a lower-level component, where
fewer errors are possible. Retargeting can be done with different
implementations of the Transact trait.
implementations of the AsyncClient and SyncClient traits.
When Futures 0.3.0 is released, the Transact trait may look like this:
Components implement the following primary methods:
```rust,ignore
trait Transact {
async fn send_transactions(txs: &[Transaction]) -> Vec<Result<(), TransactionError>>;
trait AsyncClient {
fn async_send_transaction(&self, transaction: Transaction) -> io::Result<Signature>;
}
trait SyncClient {
fn get_signature_status(&self, signature: &Signature) -> Result<Option<transaction::Result<()>>>;
}
```
Users send transactions and asynchrounously await their results.
Users send transactions and asynchrounously and synchrounously await results.
#### Transact with Clusters
#### ThinClient for Clusters
The highest level implementation targets a Solana cluster, which may be a
deployed testnet or a local cluster running on a development machine.
The highest level implementation, ThinClient, targets a Solana cluster, which
may be a deployed testnet or a local cluster running on a development machine.
#### Transact with the TPU
#### TpuClient for the TPU
The next level is the TPU implementation of Transact. At the TPU level, the
application sends transactions over Rust channels, where there can be no
surprises from network queues or dropped packets. The TPU implements all
"normal" transaction errors. It does signature verification, may report
The next level is the TPU implementation, which is not yet implemented. At the
TPU level, the application sends transactions over Rust channels, where there
can be no surprises from network queues or dropped packets. The TPU implements
all "normal" transaction errors. It does signature verification, may report
account-in-use errors, and otherwise results in the ledger, complete with proof
of history hashes.
### Low-level testing
### Testing with the Bank
#### BankClient for the Bank
Below the TPU level is the Bank. The Bank doesn't do signature verification or
generate a ledger. The Bank is a convenient layer at which to test new on-chain

View File

@ -5,7 +5,7 @@ validator node.
Please note some of the information and instructions described here may change
in future releases.
### Beta Testnet Overview
### Overview
The testnet features a validator running at testnet.solana.com, which
serves as the entrypoint to the cluster for your validator.
@ -16,7 +16,10 @@ The testnet is configured to reset the ledger daily, or sooner
should the hourly automated cluster sanity test fail.
There is a **#validator-support** Discord channel available to reach other
testnet participants, https://discord.gg/pquxPsq.
testnet participants, [https://discord.gg/pquxPsq](https://discord.gg/pquxPsq).
Also we'd love it if you choose to register your validator node with us at
[https://forms.gle/LfFscZqJELbuUP139](https://forms.gle/LfFscZqJELbuUP139).
### Machine Requirements
Since the testnet is not intended for stress testing of max transaction
@ -29,6 +32,16 @@ traversal issues. A cloud-hosted machine works best. **Ensure that IP ports
Prebuilt binaries are available for Linux x86_64 (Ubuntu 18.04 recommended).
MacOS or WSL users may build from source.
For a performance testnet with many transactions we have some preliminary recomended setups:
| | Low end | Medium end | High end | Notes |
| --- | ---------|------------|----------| -- |
| CPU | AMD Threadripper 1900x | AMD Threadripper 2920x | AMD Threadripper 2950x | Consider a 10Gb-capable motherboard with as many PCIe lanes and m.2 slots as possible. |
| RAM | 16GB | 32GB | 64GB | |
| OS Drive | Samsung 860 Evo 2TB | Samsung 860 Evo 4TB | Samsung 860 Evo 4TB | Or equivalent SSD |
| Accounts Drive(s) | None | Samsung 970 Pro 1TB | 2x Samsung 970 Pro 1TB | |
| GPU | 4x Nvidia 1070 or 2x Nvidia 1080 Ti or 2x Nvidia 2070 | 2x Nvidia 2080 Ti | 4x Nvidia 2080 Ti | Any number of cuda-capable GPUs are supported on Linux platforms. |
#### Confirm The Testnet Is Reachable
Before attaching a validator node, sanity check that the cluster is accessible
to your machine by running some simple commands. If any of the commands fail,
@ -54,7 +67,7 @@ The `solana-install` tool can be used to easily install and upgrade the cluster
software on Linux x86_64 systems.
```bash
$ export SOLANA_RELEASE=v0.14.0 # skip this line to install the latest release
$ export SOLANA_RELEASE=v0.14.2 # skip this line to install the latest release
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.14.0/install/solana-install-init.sh | sh -s
```
@ -94,78 +107,90 @@ $ export PATH=$PWD/bin:$PATH
Sanity check that you are able to interact with the cluster by receiving a small
airdrop of lamports from the testnet drone:
```bash
$ solana-wallet -n testnet.solana.com airdrop 123
$ solana-wallet -n testnet.solana.com balance
$ solana-wallet airdrop 123
$ solana-wallet balance
```
Also try running following command to join the gossip network and view all the other nodes in the cluster:
```bash
$ solana-gossip --network testnet.solana.com:8001 spy
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
# Press ^C to exit
```
Then the following command will start a new validator node.
Now configure a key pair for your validator by running:
```bash
$ solana-keygen -o validator-keypair.json
```
Then use one of the following commands, depending on your installation
choice, to start the node:
If this is a `solana-install`-installation:
```bash
$ clear-fullnode-config.sh
$ fullnode.sh --public-address --poll-for-new-genesis-block testnet.solana.com
$ clear-config.sh
$ validator.sh --identity validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
```
Alternatively, the `solana-install run` command can be used to run the validator
node while periodically checking for and applying software updates:
```bash
$ clear-fullnode-config.sh
$ solana-install run fullnode.sh -- --public-address --poll-for-new-genesis-block testnet.solana.com
$ clear-config.sh
$ solana-install run validator.sh -- --identity validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
```
If you built from source:
```bash
$ USE_INSTALL=1 ./multinode-demo/clear-fullnode-config.sh
$ USE_INSTALL=1 ./multinode-demo/fullnode.sh --public-address --poll-for-new-genesis-block testnet.solana.com
$ USE_INSTALL=1 ./multinode-demo/clear-config.sh
$ USE_INSTALL=1 ./multinode-demo/validator.sh --identity validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
```
#### Controlling local network port allocation
By default the validator will dynamically select available network ports in the
8000-10000 range, and may be overridden with `--dynamic-port-range`. For
example, `fullnode.sh --dynamic-port-range 11000-11010 ...` will restrict the
example, `validator.sh --dynamic-port-range 11000-11010 ...` will restrict the
validator to ports 11000-11011.
### Validator Monitoring
From another console, confirm the IP address of your validator is visible in the
gossip network by running:
```bash
$ solana-gossip --network edge.testnet.solana.com:8001 spy
```
When `fullnode.sh` starts, it will output a fullnode configuration that looks
When `validator.sh` starts, it will output a validator configuration that looks
similar to:
```bash
======================[ Fullnode configuration ]======================
node id: 4ceWXsL3UJvn7NYZiRkw7NsryMpviaKBDYr8GK7J61Dm
vote id: 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
======================[ validator configuration ]======================
identity pubkey: 4ceWXsL3UJvn7NYZiRkw7NsryMpviaKBDYr8GK7J61Dm
vote pubkey: 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
ledger: ...
accounts: ...
======================================================================
```
Provide the **vote id** pubkey to the `solana-wallet show-vote-account` command to view
The **identity pubkey** for your validator can also be found by running:
```bash
$ solana-keygen pubkey validator-keypair.json
```
From another console, confirm the IP address and **identity pubkey** of your validator is visible in the
gossip network by running:
```bash
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
```
Provide the **vote pubkey** to the `solana-wallet show-vote-account` command to view
the recent voting activity from your validator:
```bash
$ solana-wallet -n testnet.solana.com show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
```
The vote id for the validator can also be found by running:
The vote pubkey for the validator can also be found by running:
```bash
# If this is a `solana-install`-installation run:
$ solana-keygen pubkey ~/.local/share/solana/install/active_release/config-local/fullnode-vote-id.json
$ solana-keygen pubkey ~/.local/share/solana/install/active_release/config-local/validator-vote-keypair.json
# Otherwise run:
$ solana-keygen pubkey ./config-local/fullnode-vote-id.json
$ solana-keygen pubkey ./config-local/validator-vote-keypair.json
```
### Sharing Metrics From Your Validator
If you'd like to share metrics perform the following steps before starting the
validator node:
If you have obtained a metrics username/password from the Solana maintainers to
help us monitor the health of the testnet, please perform the following steps
before starting the validator to activate metrics reporting:
```bash
export u="username obtained from the Solana maintainers"
export p="password obtained from the Solana maintainers"

View File

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

View File

@ -1,10 +1,10 @@
# Anatomy of a Fullnode
# Anatomy of a Validator
<img alt="Fullnode block diagrams" src="img/fullnode.svg" class="center"/>
<img alt="Validator block diagrams" src="img/validator.svg" class="center"/>
## Pipelining
The fullnodes make extensive use of an optimization common in CPU design,
The validators make extensive use of an optimization common in CPU design,
called *pipelining*. Pipelining is the right tool for the job when there's a
stream of input data that needs to be processed by a sequence of steps, and
there's different hardware responsible for each. The quintessential example is
@ -19,9 +19,9 @@ dryer and the first is being folded. In this way, one can make progress on
three loads of laundry simultaneously. Given infinite loads, the pipeline will
consistently complete a load at the rate of the slowest stage in the pipeline.
## Pipelining in the Fullnode
## Pipelining in the Validator
The fullnode contains two pipelined processes, one used in leader mode called
The validator contains two pipelined processes, one used in leader mode called
the TPU and one used in validator mode called the TVU. In both cases, the
hardware being pipelined is the same, the network input, the GPU cards, the CPU
cores, writes to disk, and the network output. What it does with that hardware

View File

@ -12,8 +12,12 @@ if [[ -d target/perf-libs ]]; then
exit 1
fi
set -x
git clone git@github.com:solana-labs/solana-perf-libs.git target/perf-libs
cd target/perf-libs
make -j"$(nproc)"
make DESTDIR=. install
(
set -x
git clone git@github.com:solana-labs/solana-perf-libs.git target/perf-libs
cd target/perf-libs
make -j"$(nproc)"
make DESTDIR=. install
)
./fetch-perf-libs.sh

View File

@ -1,20 +0,0 @@
#!/usr/bin/env bash
#
# Audits project dependencies for security vulnerabilities
#
set -e
cd "$(dirname "$0")/.."
source ci/_
cargo_install_unless() {
declare crate=$1
shift
"$@" > /dev/null 2>&1 || \
_ cargo install "$crate"
}
cargo_install_unless cargo-audit cargo audit --version
_ cargo audit

View File

@ -14,10 +14,10 @@ steps:
- "queue=cuda"
- command: "ci/test-bench.sh"
name: "bench"
timeout_in_minutes: 20
timeout_in_minutes: 60
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-stable.sh"
name: "stable"
timeout_in_minutes: 30
timeout_in_minutes: 40
artifact_paths: "log-*.txt"
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
name: "coverage"

View File

@ -4,9 +4,8 @@ ARG date
RUN set -x \
&& rustup install nightly-$date \
&& rustup show \
&& rustup show \
&& rustc --version \
&& cargo --version \
&& cargo install grcov \
&& rustc +nightly-$date --version \
&& cargo +nightly-$date --version

View File

@ -21,9 +21,11 @@ RUN set -x \
rsync \
sudo \
\
&& rm -rf /var/lib/apt/lists/* \
&& rustup component add rustfmt \
&& rustup component add clippy \
&& rm -rf /var/lib/apt/lists/* \
&& cargo install cargo-audit \
&& cargo install svgbob_cli \
&& cargo install mdbook \
&& rustc --version \
&& cargo --version

View File

@ -9,3 +9,4 @@ read -r rustc version _ < <(docker run solanalabs/rust rustc --version)
[[ $rustc = rustc ]]
docker tag solanalabs/rust:latest solanalabs/rust:"$version"
docker push solanalabs/rust:"$version"
docker push solanalabs/rust:latest

View File

@ -25,8 +25,7 @@ fi
build() {
$genPipeline && return
source ci/rust-version.sh stable
_ scripts/ulimit-n.sh
source scripts/ulimit-n.sh
_ cargo +$rust_stable build --all
}
@ -52,11 +51,11 @@ runTest() {
build
runTest "Leader rotation on" \
runTest "basic" \
"ci/localnet-sanity.sh -i 128"
runTest "Leader rotation on, restart" \
runTest "restart" \
"ci/localnet-sanity.sh -i 128 -k 16"
runTest "Leader rotation on, incremental restart, extra node" \
runTest "incremental restart, extra node" \
"ci/localnet-sanity.sh -i 128 -k 16 -R -x"

View File

@ -7,7 +7,7 @@ restartInterval=never
rollingRestart=false
maybeNoLeaderRotation=
extraNodes=0
walletRpcEndpoint=
walletRpcPort=:8899
usage() {
exitcode=0
@ -27,7 +27,7 @@ Start a local cluster and run sanity on it
nodes (at the cadence specified by -k). When disabled all
nodes will be first killed then restarted (default: $rollingRestart)
-b - Disable leader rotation
-x - Add an extra fullnode (may be supplied multiple times)
-x - Add an extra validator (may be supplied multiple times)
-r - Select the RPC endpoint hosted by a node that starts as
a validator node. If unspecified the RPC endpoint hosted by
the bootstrap leader will be used.
@ -61,7 +61,7 @@ while getopts "ch?i:k:brxR" opt; do
extraNodes=$((extraNodes + 1))
;;
r)
walletRpcEndpoint="--rpc-port 18899"
walletRpcPort=":18899"
;;
R)
rollingRestart=true
@ -79,17 +79,20 @@ nodes=(
"multinode-demo/drone.sh"
"multinode-demo/bootstrap-leader.sh \
--enable-rpc-exit \
--no-restart \
--init-complete-file init-complete-node1.log"
"multinode-demo/fullnode.sh \
"multinode-demo/validator.sh \
$maybeNoLeaderRotation \
--enable-rpc-exit \
--no-restart \
--init-complete-file init-complete-node2.log \
--rpc-port 18899"
)
for i in $(seq 1 $extraNodes); do
nodes+=(
"multinode-demo/fullnode.sh \
"multinode-demo/validator.sh \
--no-restart \
--label dyn$i \
--init-complete-file init-complete-node$((2 + i)).log \
$maybeNoLeaderRotation"
@ -166,13 +169,11 @@ startNodes() {
killNode() {
declare pid=$1
echo "kill $pid"
set +e
if kill "$pid"; then
echo "Waiting for $pid to exit..."
wait "$pid"
else
echo "^^^ +++"
echo "Warning: unable to kill $pid"
echo "$pid exited with $?"
fi
set -e
}
@ -196,10 +197,11 @@ killNodes() {
# Give the nodes a splash of time to cleanly exit before killing them
sleep 2
echo "--- Killing nodes"
echo "--- Killing nodes: ${pids[*]}"
for pid in "${pids[@]}"; do
killNode "$pid"
done
echo "done killing nodes"
pids=()
}
@ -254,7 +256,7 @@ rollingNodeRestart() {
}
verifyLedger() {
for ledger in bootstrap-leader fullnode; do
for ledger in bootstrap-leader validator; do
echo "--- $ledger ledger verification"
(
source multinode-demo/common.sh
@ -292,7 +294,7 @@ flag_error() {
}
if ! $skipSetup; then
multinode-demo/setup.sh
multinode-demo/setup.sh --hashes-per-tick auto
else
verifyLedger
fi
@ -304,10 +306,10 @@ while [[ $iteration -le $iterations ]]; do
(
source multinode-demo/common.sh
set -x
client_id=/tmp/client-id.json-$$
$solana_keygen -o $client_id || exit $?
client_keypair=/tmp/client-id.json-$$
$solana_keygen -o $client_keypair || exit $?
$solana_gossip spy --num-nodes-exactly $numNodes || exit $?
rm -rf $client_id
rm -rf $client_keypair
) || flag_error
echo "--- RPC API: bootstrap-leader getTransactionCount ($iteration)"
@ -321,7 +323,7 @@ while [[ $iteration -le $iterations ]]; do
cat log-transactionCount.txt
) || flag_error
echo "--- RPC API: fullnode getTransactionCount ($iteration)"
echo "--- RPC API: validator getTransactionCount ($iteration)"
(
set -x
curl --retry 5 --retry-delay 2 --retry-connrefused \
@ -362,8 +364,7 @@ while [[ $iteration -le $iterations ]]; do
}
(
set -x
# shellcheck disable=SC2086 # Don't want to double quote $walletRpcEndpoint
timeout 60s scripts/wallet-sanity.sh $walletRpcEndpoint
timeout 60s scripts/wallet-sanity.sh --url http://127.0.0.1"$walletRpcPort"
) || flag_error_if_no_leader_rotation
iteration=$((iteration + 1))

View File

@ -13,19 +13,24 @@ declare prints=(
'println!'
'eprint!'
'eprintln!'
'dbg!'
)
# Parts of the tree that are expected to be print free
declare print_free_tree=(
'core/src'
'drone'
'metrics'
'netutil'
'runtime'
'sdk'
'drone/src'
'metrics/src'
'netutil/src'
'runtime/src'
'sdk/src'
'programs/vote_api/src'
'programs/vote_program/src'
'programs/stake_api/src'
'programs/stake_program/src'
)
if _ git grep "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then
if _ git --no-pager grep -n --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then
exit 1
fi
@ -34,7 +39,21 @@ fi
# Default::default()
#
# Ref: https://github.com/solana-labs/solana/issues/2630
if _ git grep 'Default::default()' -- '*.rs'; then
if _ git --no-pager grep -n 'Default::default()' -- '*.rs'; then
exit 1
fi
# Let's keep a .gitignore for every crate, ensure it's got
# /target/ in it
declare gitignores_ok=true
for i in $(git --no-pager ls-files \*/Cargo.toml ); do
dir=$(dirname "$i")
if [[ ! -f $dir/.gitignore ]]; then
echo 'error: nits.sh .gitnore missing for crate '"$dir" >&2
gitignores_ok=false
elif ! grep -q -e '^/target/$' "$dir"/.gitignore; then
echo 'error: nits.sh "/target/" apparently missing from '"$dir"'/.gitignore' >&2
gitignores_ok=false
fi
done
"$gitignores_ok"

View File

@ -25,7 +25,7 @@ CRATES=(
runtime
vote-signer
core
fullnode
validator
genesis
gossip
ledger-tool

View File

@ -48,7 +48,7 @@ echo --- Creating tarball
COMMIT="$(git rev-parse HEAD)"
(
echo "channel: $CHANNEL"
echo "channel: $CHANNEL_OR_TAG"
echo "commit: $COMMIT"
echo "target: $TARGET"
) > solana-release/version.yml
@ -56,37 +56,41 @@ echo --- Creating tarball
source ci/rust-version.sh stable
scripts/cargo-install-all.sh +"$rust_stable" solana-release
rm -rf target/perf-libs
./fetch-perf-libs.sh
mkdir solana-release/target
cp -a target/perf-libs solana-release/target/
# shellcheck source=/dev/null
source ./target/perf-libs/env.sh
(
cd fullnode
cargo install --path . --features=cuda --root ../solana-release-cuda
cd validator
cargo +"$rust_stable" install --path . --features=cuda --root ../solana-release-cuda
)
cp solana-release-cuda/bin/solana-fullnode solana-release/bin/solana-fullnode-cuda
cp solana-release-cuda/bin/solana-validator solana-release/bin/solana-validator-cuda
cp -a scripts multinode-demo solana-release/
# Add a wrapper script for fullnode.sh
# Add a wrapper script for validator.sh
# TODO: Remove multinode/... from tarball
cat > solana-release/bin/fullnode.sh <<'EOF'
cat > solana-release/bin/validator.sh <<'EOF'
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"/..
export USE_INSTALL=1
exec multinode-demo/fullnode.sh "$@"
exec multinode-demo/validator.sh "$@"
EOF
chmod +x solana-release/bin/fullnode.sh
chmod +x solana-release/bin/validator.sh
# Add a wrapper script for clear-fullnode-config.sh
# Add a wrapper script for clear-config.sh
# TODO: Remove multinode/... from tarball
cat > solana-release/bin/clear-fullnode-config.sh <<'EOF'
cat > solana-release/bin/clear-config.sh <<'EOF'
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"/..
export USE_INSTALL=1
exec multinode-demo/clear-fullnode-config.sh "$@"
exec multinode-demo/clear-validator-config.sh "$@"
EOF
chmod +x solana-release/bin/clear-fullnode-config.sh
chmod +x solana-release/bin/clear-config.sh
tar jvcf solana-release-$TARGET.tar.bz2 solana-release/
cp solana-release/bin/solana-install solana-install-$TARGET

View File

@ -16,8 +16,8 @@
export rust_stable=1.34.0
export rust_stable_docker_image=solanalabs/rust:1.34.0
export rust_nightly=nightly-2019-03-14
export rust_nightly_docker_image=solanalabs/rust-nightly:2019-03-14
export rust_nightly=nightly-2019-05-01
export rust_nightly_docker_image=solanalabs/rust-nightly:2019-05-01
[[ -z $1 ]] || (

View File

@ -40,7 +40,10 @@ fi
BENCH_FILE=bench_output.log
BENCH_ARTIFACT=current_bench_results.log
# First remove "BENCH_FILE", if it exists so that the following commands can append
# Ensure all dependencies are built
_ cargo +$rust_nightly build --all --release
# Remove "BENCH_FILE", if it exists so that the following commands can append
rm -f "$BENCH_FILE"
# Run sdk benches
@ -59,8 +62,11 @@ _ cargo +$rust_nightly bench --manifest-path core/Cargo.toml ${V:+--verbose} \
_ cargo +$rust_nightly bench --manifest-path programs/bpf/Cargo.toml ${V:+--verbose} --features=bpf_c \
-- -Z unstable-options --format=json --nocapture | tee -a "$BENCH_FILE"
# TODO: debug why solana-upload-perf takes over 30 minutes to complete.
exit 0
_ cargo +$rust_nightly run --release --package solana-upload-perf \
-- "$BENCH_FILE" "$TARGET_BRANCH" "$UPLOAD_METRICS" > "$BENCH_ARTIFACT"
-- "$BENCH_FILE" "$TARGET_BRANCH" "$UPLOAD_METRICS" | tee "$BENCH_ARTIFACT"
upload-ci-artifact "$BENCH_FILE"
upload-ci-artifact "$BENCH_ARTIFACT"

View File

@ -12,7 +12,7 @@ export RUSTFLAGS="-D warnings"
_ cargo +"$rust_stable" fmt --all -- --check
_ cargo +"$rust_stable" clippy --all -- --version
_ cargo +"$rust_stable" clippy --all -- --deny=warnings
_ ci/audit.sh
_ cargo +"$rust_stable" audit
_ ci/nits.sh
_ book/build.sh

View File

@ -8,6 +8,7 @@ source ci/rust-version.sh stable
export RUST_BACKTRACE=1
rm -rf target/perf-libs
./fetch-perf-libs.sh
export LD_LIBRARY_PATH=$PWD/target/perf-libs:$LD_LIBRARY_PATH

View File

@ -47,7 +47,6 @@ test-stable-perf)
# BPF program tests
_ make -C programs/bpf/c tests
_ programs/bpf/rust/noop/build.sh # Must be built out of band
_ cargo +"$rust_stable" test \
--manifest-path programs/bpf/Cargo.toml \
--no-default-features --features=bpf_c,bpf_rust
@ -62,6 +61,7 @@ test-stable-perf)
# is not yet loaded.
sudo --non-interactive ./net/scripts/enable-nvidia-persistence-mode.sh
rm -rf target/perf-libs
./fetch-perf-libs.sh
# shellcheck source=/dev/null
source ./target/perf-libs/env.sh

View File

@ -52,14 +52,14 @@ launchTestnet() {
declare q_mean_tps='
SELECT round(mean("sum_count")) AS "mean_tps" FROM (
SELECT sum("count") AS "sum_count"
FROM "testnet-automation"."autogen"."counter-banking_stage-process_transactions"
FROM "testnet-automation"."autogen"."banking_stage-record_transactions"
WHERE time > now() - 300s GROUP BY time(1s)
)'
declare q_max_tps='
SELECT round(max("sum_count")) AS "max_tps" FROM (
SELECT sum("count") AS "sum_count"
FROM "testnet-automation"."autogen"."counter-banking_stage-process_transactions"
FROM "testnet-automation"."autogen"."banking_stage-record_transactions"
WHERE time > now() - 300s GROUP BY time(1s)
)'

View File

@ -11,15 +11,19 @@ clientNodeCount=0
additionalFullNodeCount=10
publicNetwork=false
stopNetwork=false
skipSetup=false
reuseLedger=false
skipCreate=false
skipStart=false
externalNode=false
failOnValidatorBootupFailure=true
tarChannelOrTag=edge
delete=false
enableGpu=false
bootDiskType=""
leaderRotation=true
blockstreamer=false
deployUpdateManifest=true
fetchLogs=true
maybeHashesPerTick=
usage() {
exitcode=0
@ -46,17 +50,23 @@ Deploys a CD testnet
-c [number] - Number of client bencher nodes (default: $clientNodeCount)
-u - Include a Blockstreamer (default: $blockstreamer)
-P - Use public network IP addresses (default: $publicNetwork)
-G - Enable GPU, and set count/type of GPUs to use (e.g n1-standard-16 --accelerator count=4,type=nvidia-tesla-k80)
-G - Enable GPU, and set count/type of GPUs to use (e.g n1-standard-16 --accelerator count=2,type=nvidia-tesla-v100)
-g - Enable GPU (default: $enableGpu)
-b - Disable leader rotation
-a [address] - Set the bootstrap fullnode's external IP address to this GCE address
-d [disk-type] - Specify a boot disk type (default None) Use pd-ssd to get ssd on GCE.
-D - Delete the network
-r - Reuse existing node/ledger configuration from a
previous |start| (ie, don't run ./multinode-demo/setup.sh).
-x - External node. Default: false
-e - Skip create. Assume the nodes have already been created
-s - Skip start. Nodes will still be created or configured, but network software will not be started.
-S - Stop network software without tearing down nodes.
-f - Discard validator nodes that didn't bootup successfully
-w - Skip time-consuming "bells and whistles" that are
unnecessary for a high-node count demo testnet
--hashes-per-tick NUM_HASHES|sleep|auto
- Override the default --hashes-per-tick for the cluster
Note: the SOLANA_METRICS_CONFIG environment variable is used to configure
metrics
@ -66,7 +76,22 @@ EOF
zone=()
while getopts "h?p:Pn:c:t:gG:a:Dbd:rusxz:p:C:S" opt; do
shortArgs=()
while [[ -n $1 ]]; do
if [[ ${1:0:2} = -- ]]; then
if [[ $1 = --hashes-per-tick ]]; then
maybeHashesPerTick="$1 $2"
shift 2
else
usage "Unknown long option: $1"
fi
else
shortArgs+=("$1")
shift
fi
done
while getopts "h?p:Pn:c:t:gG:a:Dd:rusxz:p:C:Sfew" opt "${shortArgs[@]}"; do
case $opt in
h | \?)
usage
@ -99,9 +124,6 @@ while getopts "h?p:Pn:c:t:gG:a:Dbd:rusxz:p:C:S" opt; do
;;
esac
;;
b)
leaderRotation=false
;;
g)
enableGpu=true
;;
@ -119,7 +141,10 @@ while getopts "h?p:Pn:c:t:gG:a:Dbd:rusxz:p:C:S" opt; do
delete=true
;;
r)
skipSetup=true
reuseLedger=true
;;
e)
skipCreate=true
;;
s)
skipStart=true
@ -127,14 +152,21 @@ while getopts "h?p:Pn:c:t:gG:a:Dbd:rusxz:p:C:S" opt; do
x)
externalNode=true
;;
f)
failOnValidatorBootupFailure=false
;;
u)
blockstreamer=true
;;
S)
stopNetwork=true
;;
w)
fetchLogs=false
deployUpdateManifest=false
;;
*)
usage "Error: unhandled option: $opt"
usage "Unknown option: $opt"
;;
esac
done
@ -170,15 +202,15 @@ for val in "${zone[@]}"; do
done
if $stopNetwork; then
skipSetup=true
skipCreate=true
fi
if $delete; then
skipSetup=false
skipCreate=false
fi
# Create the network
if ! $skipSetup; then
if ! $skipCreate; then
echo "--- $cloudProvider.sh delete"
# shellcheck disable=SC2068
time net/"$cloudProvider".sh delete ${zone_args[@]} -p "$netName" ${externalNode:+-x}
@ -212,10 +244,6 @@ if ! $skipSetup; then
fi
fi
if ! $leaderRotation; then
create_args+=(-b)
fi
if $publicNetwork; then
create_args+=(-P)
fi
@ -224,6 +252,10 @@ if ! $skipSetup; then
create_args+=(-x)
fi
if ! $failOnValidatorBootupFailure; then
create_args+=(-f)
fi
time net/"$cloudProvider".sh create "${create_args[@]}"
else
echo "--- $cloudProvider.sh config"
@ -236,6 +268,14 @@ else
config_args+=(-P)
fi
if $externalNode; then
config_args+=(-x)
fi
if ! $failOnValidatorBootupFailure; then
config_args+=(-f)
fi
time net/"$cloudProvider".sh config "${config_args[@]}"
fi
net/init-metrics.sh -e
@ -249,53 +289,54 @@ if $stopNetwork; then
exit 0
fi
echo --- net.sh start
maybeRejectExtraNodes=
if ! $publicNetwork; then
maybeRejectExtraNodes="-o rejectExtraNodes"
fi
maybeNoValidatorSanity=
if [[ -n $NO_VALIDATOR_SANITY ]]; then
maybeNoValidatorSanity="-o noValidatorSanity"
fi
maybeNoLedgerVerify=
if [[ -n $NO_LEDGER_VERIFY ]]; then
maybeNoLedgerVerify="-o noLedgerVerify"
fi
maybeSkipSetup=
if $skipSetup; then
maybeSkipSetup="-r"
fi
ok=true
if ! $skipStart; then
(
if $skipSetup; then
if $skipCreate; then
# TODO: Enable rolling updates
#op=update
op=restart
else
op=start
fi
echo "--- net.sh $op"
args=("$op" -t "$tarChannelOrTag")
if ! $publicNetwork; then
args+=(-o rejectExtraNodes)
fi
if [[ -n $NO_VALIDATOR_SANITY ]]; then
args+=(-o noValidatorSanity)
fi
if [[ -n $NO_LEDGER_VERIFY ]]; then
args+=(-o noLedgerVerify)
fi
if [[ -n $maybeHashesPerTick ]]; then
# shellcheck disable=SC2206 # Do not want to quote $maybeHashesPerTick
args+=($maybeHashesPerTick)
fi
if $reuseLedger; then
args+=(-r)
fi
if ! $failOnValidatorBootupFailure; then
args+=(-F)
fi
maybeUpdateManifestKeypairFile=
# shellcheck disable=SC2154 # SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu comes from .buildkite/env/
if [[ -n $SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu ]]; then
if $deployUpdateManifest && [[ -n $SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu ]]; then
echo "$SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu" > update_manifest_keypair.json
maybeUpdateManifestKeypairFile="-i update_manifest_keypair.json"
args+=(-i update_manifest_keypair.json)
fi
# shellcheck disable=SC2086 # Don't want to double quote the $maybeXYZ variables
time net/net.sh $op -t "$tarChannelOrTag" \
$maybeUpdateManifestKeypairFile \
$maybeSkipSetup \
$maybeRejectExtraNodes \
$maybeNoValidatorSanity \
$maybeNoLedgerVerify
time net/net.sh "${args[@]}"
) || ok=false
net/net.sh logs
if $fetchLogs; then
net/net.sh logs
fi
fi
$ok

View File

@ -52,7 +52,7 @@ steps:
value: "create-and-start"
- label: "Create testnet, but do not start software. If the testnet already exists it will be deleted and re-created"
value: "create"
- label: "Start network software on an existing testnet. If software is already running it will be restarted."
- label: "Start network software on an existing testnet. If software is already running it will be restarted"
value: "start"
- label: "Stop network software without deleting testnet nodes"
value: "stop"
@ -62,11 +62,11 @@ steps:
value: "sanity-or-restart"
- label: "Sanity check only"
value: "sanity"
- label: "Delete the testnet.
- label: "Delete the testnet"
value: "delete"
- label: "Enable/unlock the testnet."
- label: "Enable/unlock the testnet"
value: "enable"
- label: "Delete and then lock the testnet from further operation until it is re-enabled."
- label: "Delete and then lock the testnet from further operation until it is re-enabled"
value: "disable"
- command: "ci/$(basename "$0")"
agents:
@ -80,25 +80,68 @@ ci/channel-info.sh
eval "$(ci/channel-info.sh)"
EC2_ZONES=(us-west-1a sa-east-1a ap-northeast-2a eu-central-1a ca-central-1a)
GCE_ZONES=(us-west1-b asia-east2-a europe-west4-a southamerica-east1-b us-east4-c)
EC2_ZONES=(
us-west-1a
us-west-2a
us-east-1a
us-east-2a
sa-east-1a
eu-west-1a
eu-west-2a
eu-central-1a
ap-northeast-2a
ap-southeast-2a
ap-south-1a
ca-central-1a
)
# GCE zones with _lots_ of quota
GCE_ZONES=(
us-west1-a
us-central1-a
us-east1-b
europe-west4-a
us-west1-b
us-central1-b
us-east1-c
europe-west4-b
us-west1-c
us-east1-d
europe-west4-c
)
# GCE zones with enough quota for one CPU-only fullnode
GCE_LOW_QUOTA_ZONES=(
asia-east2-a
asia-northeast1-b
asia-northeast2-b
asia-south1-c
asia-southeast1-b
australia-southeast1-b
europe-north1-a
europe-west2-b
europe-west3-c
europe-west6-a
northamerica-northeast1-a
southamerica-east1-b
)
case $TESTNET in
testnet-edge|testnet-edge-perf)
CHANNEL_OR_TAG=edge
CHANNEL_BRANCH=$EDGE_CHANNEL
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
testnet-beta|testnet-beta-perf)
CHANNEL_OR_TAG=beta
CHANNEL_BRANCH=$BETA_CHANNEL
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
testnet)
CHANNEL_OR_TAG=$STABLE_CHANNEL_LATEST_TAG
CHANNEL_BRANCH=$STABLE_CHANNEL
: "${EC2_NODE_COUNT:=10}"
: "${GCE_NODE_COUNT:=}"
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
testnet-perf)
CHANNEL_OR_TAG=$STABLE_CHANNEL_LATEST_TAG
@ -107,7 +150,8 @@ testnet-perf)
testnet-demo)
CHANNEL_OR_TAG=beta
CHANNEL_BRANCH=$BETA_CHANNEL
: "${GCE_NODE_COUNT:=200}"
: "${GCE_NODE_COUNT:=150}"
: "${GCE_LOW_QUOTA_NODE_COUNT:=70}"
;;
*)
echo "Error: Invalid TESTNET=$TESTNET"
@ -123,6 +167,10 @@ GCE_ZONE_ARGS=()
for val in "${GCE_ZONES[@]}"; do
GCE_ZONE_ARGS+=("-z $val")
done
GCE_LOW_QUOTA_ZONE_ARGS=()
for val in "${GCE_LOW_QUOTA_ZONES[@]}"; do
GCE_LOW_QUOTA_ZONE_ARGS+=("-z $val")
done
if [[ -n $TESTNET_DB_HOST ]]; then
SOLANA_METRICS_PARTIAL_CONFIG="host=$TESTNET_DB_HOST,$SOLANA_METRICS_PARTIAL_CONFIG"
@ -151,6 +199,7 @@ steps:
TESTNET_DB_HOST: "$TESTNET_DB_HOST"
EC2_NODE_COUNT: "$EC2_NODE_COUNT"
GCE_NODE_COUNT: "$GCE_NODE_COUNT"
GCE_LOW_QUOTA_NODE_COUNT: "$GCE_LOW_QUOTA_NODE_COUNT"
EOF
) | buildkite-agent pipeline upload
exit 0
@ -227,7 +276,8 @@ sanity() {
ok=true
if [[ -n $GCE_NODE_COUNT ]]; then
NO_LEDGER_VERIFY=1 \
ci/testnet-sanity.sh demo-testnet-solana-com gce "${GCE_ZONES[0]}" || ok=false
NO_VALIDATOR_SANITY=1 \
ci/testnet-sanity.sh demo-testnet-solana-com gce "${GCE_ZONES[0]}" -f || ok=false
else
echo "Error: no GCE nodes"
ok=false
@ -270,10 +320,11 @@ deploy() {
set -x
ci/testnet-deploy.sh -p edge-testnet-solana-com -C ec2 -z us-west-1a \
-t "$CHANNEL_OR_TAG" -n 3 -c 0 -u -P -a eipalloc-0ccd4f2239886fa94 \
${skipCreate:+-r} \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
${maybeDelete:+-D} \
--hashes-per-tick auto
)
;;
testnet-edge-perf)
@ -284,11 +335,11 @@ deploy() {
RUST_LOG=solana=warn \
ci/testnet-deploy.sh -p edge-perf-testnet-solana-com -C ec2 -z us-west-2b \
-g -t "$CHANNEL_OR_TAG" -c 2 \
-b \
${skipCreate:+-r} \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
${maybeDelete:+-D} \
--hashes-per-tick auto
)
;;
testnet-beta)
@ -297,11 +348,11 @@ deploy() {
NO_VALIDATOR_SANITY=1 \
ci/testnet-deploy.sh -p beta-testnet-solana-com -C ec2 -z us-west-1a \
-t "$CHANNEL_OR_TAG" -n 3 -c 0 -u -P -a eipalloc-0f286cf8a0771ce35 \
-b \
${skipCreate:+-r} \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
${maybeDelete:+-D} \
--hashes-per-tick auto
)
;;
testnet-beta-perf)
@ -312,11 +363,11 @@ deploy() {
RUST_LOG=solana=warn \
ci/testnet-deploy.sh -p beta-perf-testnet-solana-com -C ec2 -z us-west-2b \
-g -t "$CHANNEL_OR_TAG" -c 2 \
-b \
${skipCreate:+-r} \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
${maybeDelete:+-D} \
--hashes-per-tick auto
)
;;
testnet)
@ -329,8 +380,8 @@ deploy() {
# shellcheck disable=SC2068
ci/testnet-deploy.sh -p testnet-solana-com -C ec2 ${EC2_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$EC2_NODE_COUNT" -c 0 -u -P -a eipalloc-0fa502bf95f6f18b2 \
${skipCreate:+-r} \
-t "$CHANNEL_OR_TAG" -n "$EC2_NODE_COUNT" -c 0 -u -P -f -a eipalloc-0fa502bf95f6f18b2 \
${skipCreate:+-e} \
${maybeSkipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
@ -338,12 +389,12 @@ deploy() {
if [[ -n $GCE_NODE_COUNT ]]; then
# shellcheck disable=SC2068
ci/testnet-deploy.sh -p testnet-solana-com -C gce ${GCE_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P \
${skipCreate:+-r} \
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P -f \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
${EC2_NODE_COUNT:+-x}
-x
fi
)
;;
@ -356,26 +407,45 @@ deploy() {
ci/testnet-deploy.sh -p perf-testnet-solana-com -C gce -z us-west1-b \
-G "--machine-type n1-standard-16 --accelerator count=2,type=nvidia-tesla-v100" \
-t "$CHANNEL_OR_TAG" -c 2 \
-b \
-d pd-ssd \
${skipCreate:+-r} \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
${maybeDelete:+-D} \
--hashes-per-tick auto
)
;;
testnet-demo)
(
set -x
if [[ -n $GCE_NODE_COUNT ]]; then
# shellcheck disable=SC2068
ci/testnet-deploy.sh -p testnet-demo -C gce ${GCE_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 1 -P -u \
if [[ -n $GCE_LOW_QUOTA_NODE_COUNT ]] || [[ -n $skipStart ]]; then
maybeSkipStart="skip"
fi
# shellcheck disable=SC2068
NO_LEDGER_VERIFY=1 \
NO_VALIDATOR_SANITY=1 \
ci/testnet-deploy.sh -p demo-testnet-solana-com -C gce ${GCE_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P -u -f -w \
-a demo-testnet-solana-com \
${skipCreate:+-r} \
${skipStart:+-s} \
${skipCreate:+-e} \
${maybeSkipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
${maybeDelete:+-D} \
--hashes-per-tick auto
if [[ -n $GCE_LOW_QUOTA_NODE_COUNT ]]; then
# shellcheck disable=SC2068
NO_LEDGER_VERIFY=1 \
NO_VALIDATOR_SANITY=1 \
ci/testnet-deploy.sh -p demo-testnet-solana-com2 -C gce ${GCE_LOW_QUOTA_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$GCE_LOW_QUOTA_NODE_COUNT" -c 0 -P -f -x -w \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
fi
)
;;

1
client/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target/

View File

@ -1,6 +1,6 @@
[package]
name = "solana-client"
version = "0.14.0"
version = "0.15.0"
description = "Solana Client"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -9,18 +9,18 @@ license = "Apache-2.0"
edition = "2018"
[dependencies]
bincode = "1.1.2"
bincode = "1.1.4"
bs58 = "0.2.0"
log = "0.4.2"
jsonrpc-core = "10.1.0"
reqwest = "0.9.11"
reqwest = "0.9.17"
serde = "1.0.89"
serde_derive = "1.0.88"
serde_derive = "1.0.91"
serde_json = "1.0.39"
solana-netutil = { path = "../netutil", version = "0.14.0" }
solana-sdk = { path = "../sdk", version = "0.14.0" }
solana-netutil = { path = "../netutil", version = "0.15.0" }
solana-sdk = { path = "../sdk", version = "0.15.0" }
[dev-dependencies]
jsonrpc-core = "10.1.0"
jsonrpc-http-server = "10.1.0"
solana-logger = { path = "../logger", version = "0.14.0" }
solana-logger = { path = "../logger", version = "0.15.0" }

View File

@ -1,6 +1,7 @@
pub mod client_error;
mod generic_rpc_client_request;
pub mod mock_rpc_client_request;
pub mod perf_utils;
pub mod rpc_client;
pub mod rpc_client_request;
pub mod rpc_request;

View File

@ -2,6 +2,7 @@ use crate::client_error::ClientError;
use crate::generic_rpc_client_request::GenericRpcClientRequest;
use crate::rpc_request::RpcRequest;
use serde_json::{Number, Value};
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::transaction::{self, TransactionError};
pub const PUBKEY: &str = "7RoSF9fUmdphVCpabEoefH81WwrW7orsWonXWqTXkKV8";
@ -44,7 +45,10 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
let n = if self.url == "airdrop" { 0 } else { 50 };
Value::Number(Number::from(n))
}
RpcRequest::GetRecentBlockhash => Value::String(PUBKEY.to_string()),
RpcRequest::GetRecentBlockhash => Value::Array(vec![
Value::String(PUBKEY.to_string()),
serde_json::to_value(FeeCalculator::default()).unwrap(),
]),
RpcRequest::GetSignatureStatus => {
let response: Option<transaction::Result<()>> = if self.url == "account_in_use" {
Some(Err(TransactionError::AccountInUse))

76
client/src/perf_utils.rs Normal file
View File

@ -0,0 +1,76 @@
use log::*;
use solana_sdk::client::Client;
use solana_sdk::timing::duration_as_s;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::thread::sleep;
use std::time::{Duration, Instant};
#[derive(Default)]
pub struct SampleStats {
/// Maximum TPS reported by this node
pub tps: f32,
/// Total time taken for those txs
pub elapsed: Duration,
/// Total transactions reported by this node
pub txs: u64,
}
pub fn sample_txs<T>(
exit_signal: &Arc<AtomicBool>,
sample_stats: &Arc<RwLock<Vec<(String, SampleStats)>>>,
sample_period: u64,
client: &Arc<T>,
) where
T: Client,
{
let mut max_tps = 0.0;
let mut total_elapsed;
let mut total_txs;
let mut now = Instant::now();
let start_time = now;
let initial_txs = client.get_transaction_count().expect("transaction count");
let mut last_txs = initial_txs;
loop {
total_elapsed = start_time.elapsed();
let elapsed = now.elapsed();
now = Instant::now();
let mut txs = client.get_transaction_count().expect("transaction count");
if txs < last_txs {
info!("Expected txs({}) >= last_txs({})", txs, last_txs);
txs = last_txs;
}
total_txs = txs - initial_txs;
let sample_txs = txs - last_txs;
last_txs = txs;
let tps = sample_txs as f32 / duration_as_s(&elapsed);
if tps > max_tps {
max_tps = tps;
}
info!(
"Sampler {:9.2} TPS, Transactions: {:6}, Total transactions: {} over {} s",
tps,
sample_txs,
total_txs,
total_elapsed.as_secs(),
);
if exit_signal.load(Ordering::Relaxed) {
let stats = SampleStats {
tps: max_tps,
elapsed: total_elapsed,
txs: total_txs,
};
sample_stats
.write()
.unwrap()
.push((client.transactions_addr(), stats));
return;
}
sleep(Duration::from_secs(sample_period));
}
}

View File

@ -4,14 +4,14 @@ use crate::mock_rpc_client_request::MockRpcClientRequest;
use crate::rpc_client_request::RpcClientRequest;
use crate::rpc_request::RpcRequest;
use bincode::serialize;
use bs58;
use log::*;
use serde_json::{json, Value};
use solana_sdk::account::Account;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
use solana_sdk::timing::{DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND};
use solana_sdk::signature::{KeypairUtil, Signature};
use solana_sdk::timing::{DEFAULT_NUM_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT};
use solana_sdk::transaction::{self, Transaction, TransactionError};
use std::error;
use std::io;
@ -78,7 +78,7 @@ impl RpcClient {
pub fn send_and_confirm_transaction<T: KeypairUtil>(
&self,
transaction: &mut Transaction,
signer: &T,
signer_keys: &[&T],
) -> Result<String, ClientError> {
let mut send_retries = 5;
loop {
@ -97,7 +97,7 @@ impl RpcClient {
if cfg!(not(test)) {
// Retry ~twice during a slot
sleep(Duration::from_millis(
500 * DEFAULT_TICKS_PER_SLOT / NUM_TICKS_PER_SECOND,
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_NUM_TICKS_PER_SECOND,
));
}
};
@ -106,7 +106,7 @@ impl RpcClient {
Ok(_) => return Ok(signature_str),
Err(TransactionError::AccountInUse) => {
// Fetch a new blockhash and re-sign the transaction before sending it again
self.resign_transaction(transaction, signer)?;
self.resign_transaction(transaction, signer_keys)?;
send_retries - 1
}
Err(_) => 0,
@ -127,10 +127,10 @@ impl RpcClient {
}
}
pub fn send_and_confirm_transactions(
pub fn send_and_confirm_transactions<T: KeypairUtil>(
&self,
mut transactions: Vec<Transaction>,
signer: &Keypair,
signer_keys: &[&T],
) -> Result<(), Box<dyn error::Error>> {
let mut send_retries = 5;
loop {
@ -143,7 +143,7 @@ impl RpcClient {
// Delay ~1 tick between write transactions in an attempt to reduce AccountInUse errors
// when all the write transactions modify the same program account (eg, deploying a
// new program)
sleep(Duration::from_millis(1000 / NUM_TICKS_PER_SECOND));
sleep(Duration::from_millis(1000 / DEFAULT_NUM_TICKS_PER_SECOND));
}
let signature = self.send_transaction(&transaction).ok();
@ -157,7 +157,7 @@ impl RpcClient {
if cfg!(not(test)) {
// Retry ~twice during a slot
sleep(Duration::from_millis(
500 * DEFAULT_TICKS_PER_SLOT / NUM_TICKS_PER_SECOND,
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_NUM_TICKS_PER_SECOND,
));
}
@ -187,12 +187,12 @@ impl RpcClient {
send_retries -= 1;
// Re-sign any failed transactions with a new blockhash and retry
let blockhash =
let (blockhash, _fee_calculator) =
self.get_new_blockhash(&transactions_signatures[0].0.message().recent_blockhash)?;
transactions = transactions_signatures
.into_iter()
.map(|(mut transaction, _)| {
transaction.sign(&[signer], blockhash);
transaction.sign(signer_keys, blockhash);
transaction
})
.collect();
@ -202,10 +202,11 @@ impl RpcClient {
pub fn resign_transaction<T: KeypairUtil>(
&self,
tx: &mut Transaction,
signer_key: &T,
signer_keys: &[&T],
) -> Result<(), ClientError> {
let blockhash = self.get_new_blockhash(&tx.message().recent_blockhash)?;
tx.sign(&[signer_key], blockhash);
let (blockhash, _fee_calculator) =
self.get_new_blockhash(&tx.message().recent_blockhash)?;
tx.sign(signer_keys, blockhash);
Ok(())
}
@ -222,31 +223,7 @@ impl RpcClient {
Ok(res)
}
pub fn get_account_data(&self, pubkey: &Pubkey) -> io::Result<Vec<u8>> {
let params = json!([format!("{}", pubkey)]);
let response = self
.client
.send(&RpcRequest::GetAccountInfo, Some(params), 0);
match response {
Ok(account_json) => {
let account: Account =
serde_json::from_value(account_json).expect("deserialize account");
Ok(account.data)
}
Err(error) => {
debug!("get_account_data failed: {:?}", error);
Err(io::Error::new(
io::ErrorKind::Other,
"get_account_data failed",
))
}
}
}
/// Request the balance of the user holding `pubkey`. This method blocks
/// until the server sends a response. If the response packet is dropped
/// by the network, this method will hang indefinitely.
pub fn get_balance(&self, pubkey: &Pubkey) -> io::Result<u64> {
pub fn get_account(&self, pubkey: &Pubkey) -> io::Result<Account> {
let params = json!([format!("{}", pubkey)]);
let response = self
.client
@ -257,80 +234,89 @@ impl RpcClient {
let account: Account =
serde_json::from_value(account_json).expect("deserialize account");
trace!("Response account {:?} {:?}", pubkey, account);
trace!("get_balance {:?}", account.lamports);
Ok(account.lamports)
Ok(account)
})
.map_err(|error| {
debug!("Response account {}: None (error: {:?})", pubkey, error);
io::Error::new(io::ErrorKind::Other, "AccountNotFound")
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("AccountNotFound: pubkey={}: {}", pubkey, err),
)
})
}
pub fn get_account_data(&self, pubkey: &Pubkey) -> io::Result<Vec<u8>> {
self.get_account(pubkey).map(|account| account.data)
}
/// Request the balance of the user holding `pubkey`. This method blocks
/// until the server sends a response. If the response packet is dropped
/// by the network, this method will hang indefinitely.
pub fn get_balance(&self, pubkey: &Pubkey) -> io::Result<u64> {
self.get_account(pubkey).map(|account| account.lamports)
}
/// Request the transaction count. If the response packet is dropped by the network,
/// this method will try again 5 times.
pub fn get_transaction_count(&self) -> io::Result<u64> {
debug!("get_transaction_count");
let response = self
.client
.send(&RpcRequest::GetTransactionCount, None, 0)
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetTransactionCount request failure: {:?}", err),
)
})?;
let mut num_retries = 5;
while num_retries > 0 {
let response = self.client.send(&RpcRequest::GetTransactionCount, None, 0);
match response {
Ok(value) => {
debug!("transaction_count response: {:?}", value);
if let Some(transaction_count) = value.as_u64() {
return Ok(transaction_count);
}
}
Err(err) => {
debug!("transaction_count failed: {:?}", err);
}
}
num_retries -= 1;
}
Err(io::Error::new(
io::ErrorKind::Other,
"Unable to get transaction count, too many retries",
))?
serde_json::from_value(response).map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetTransactionCount parse failure: {}", err),
)
})
}
pub fn get_recent_blockhash(&self) -> io::Result<Hash> {
let mut num_retries = 5;
while num_retries > 0 {
match self.client.send(&RpcRequest::GetRecentBlockhash, None, 0) {
Ok(value) => {
if let Some(blockhash_str) = value.as_str() {
let blockhash_vec = bs58::decode(blockhash_str)
.into_vec()
.expect("bs58::decode");
return Ok(Hash::new(&blockhash_vec));
}
}
Err(err) => {
debug!("retry_get_recent_blockhash failed: {:?}", err);
}
}
num_retries -= 1;
}
Err(io::Error::new(
io::ErrorKind::Other,
"Unable to get recent blockhash, too many retries",
))
pub fn get_recent_blockhash(&self) -> io::Result<(Hash, FeeCalculator)> {
let response = self
.client
.send(&RpcRequest::GetRecentBlockhash, None, 0)
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetRecentBlockhash request failure: {:?}", err),
)
})?;
let (blockhash, fee_calculator) =
serde_json::from_value::<(String, FeeCalculator)>(response).map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetRecentBlockhash parse failure: {:?}", err),
)
})?;
let blockhash = blockhash.parse().map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetRecentBlockhash parse failure: {:?}", err),
)
})?;
Ok((blockhash, fee_calculator))
}
pub fn get_new_blockhash(&self, blockhash: &Hash) -> io::Result<Hash> {
pub fn get_new_blockhash(&self, blockhash: &Hash) -> io::Result<(Hash, FeeCalculator)> {
let mut num_retries = 10;
while num_retries > 0 {
if let Ok(new_blockhash) = self.get_recent_blockhash() {
if let Ok((new_blockhash, fee_calculator)) = self.get_recent_blockhash() {
if new_blockhash != *blockhash {
return Ok(new_blockhash);
return Ok((new_blockhash, fee_calculator));
}
}
debug!("Got same blockhash ({:?}), will retry...", blockhash);
// Retry ~twice during a slot
sleep(Duration::from_millis(
500 * DEFAULT_TICKS_PER_SLOT / NUM_TICKS_PER_SECOND,
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_NUM_TICKS_PER_SECOND,
));
num_retries -= 1;
}
@ -482,24 +468,22 @@ impl RpcClient {
Some(params.clone()),
1,
)
.map_err(|error| {
debug!(
"Response get_num_blocks_since_signature_confirmation: {:?}",
error
);
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
"GetNumBlocksSinceSignatureConfirmation request failure",
format!(
"GetNumBlocksSinceSignatureConfirmation request failure: {}",
err
),
)
})?;
serde_json::from_value(response).map_err(|error| {
debug!(
"ParseError: get_num_blocks_since_signature_confirmation: {}",
error
);
serde_json::from_value(response).map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
"GetNumBlocksSinceSignatureConfirmation parse failure",
format!(
"GetNumBlocksSinceSignatureConfirmation parse failure: {}",
err
),
)
})
}
@ -606,7 +590,7 @@ mod tests {
// Send erroneous parameter
let blockhash = rpc_client.retry_make_rpc_request(
&RpcRequest::GetRecentBlockhash,
Some(json!("paramter")),
Some(json!("parameter")),
0,
);
assert_eq!(blockhash.is_err(), true);
@ -657,7 +641,7 @@ mod tests {
let key = Keypair::new();
let to = Pubkey::new_rand();
let blockhash = Hash::default();
let tx = system_transaction::create_user_account(&key, &to, 50, blockhash, 0);
let tx = system_transaction::create_user_account(&key, &to, 50, blockhash);
let signature = rpc_client.send_transaction(&tx);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
@ -671,16 +655,14 @@ mod tests {
fn test_get_recent_blockhash() {
let rpc_client = RpcClient::new_mock("succeeds".to_string());
let vec = bs58::decode(PUBKEY).into_vec().unwrap();
let expected_blockhash = Hash::new(&vec);
let expected_blockhash: Hash = PUBKEY.parse().unwrap();
let blockhash = dbg!(rpc_client.get_recent_blockhash()).expect("blockhash ok");
let (blockhash, _fee_calculator) = rpc_client.get_recent_blockhash().expect("blockhash ok");
assert_eq!(blockhash, expected_blockhash);
let rpc_client = RpcClient::new_mock("fails".to_string());
let blockhash = dbg!(rpc_client.get_recent_blockhash());
assert!(blockhash.is_err());
assert!(rpc_client.get_recent_blockhash().is_err());
}
#[test]
@ -708,17 +690,17 @@ mod tests {
let key = Keypair::new();
let to = Pubkey::new_rand();
let blockhash = Hash::default();
let mut tx = system_transaction::create_user_account(&key, &to, 50, blockhash, 0);
let mut tx = system_transaction::create_user_account(&key, &to, 50, blockhash);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &key);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&key]);
result.unwrap();
let rpc_client = RpcClient::new_mock("account_in_use".to_string());
let result = rpc_client.send_and_confirm_transaction(&mut tx, &key);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&key]);
assert!(result.is_err());
let rpc_client = RpcClient::new_mock("fails".to_string());
let result = rpc_client.send_and_confirm_transaction(&mut tx, &key);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&key]);
assert!(result.is_err());
}
@ -728,14 +710,13 @@ mod tests {
let key = Keypair::new();
let to = Pubkey::new_rand();
let vec = bs58::decode("HUu3LwEzGRsUkuJS121jzkPJW39Kq62pXCTmTa1F9jDL")
.into_vec()
let blockhash: Hash = "HUu3LwEzGRsUkuJS121jzkPJW39Kq62pXCTmTa1F9jDL"
.parse()
.unwrap();
let blockhash = Hash::new(&vec);
let prev_tx = system_transaction::create_user_account(&key, &to, 50, blockhash, 0);
let mut tx = system_transaction::create_user_account(&key, &to, 50, blockhash, 0);
let prev_tx = system_transaction::create_user_account(&key, &to, 50, blockhash);
let mut tx = system_transaction::create_user_account(&key, &to, 50, blockhash);
rpc_client.resign_transaction(&mut tx, &key).unwrap();
rpc_client.resign_transaction(&mut tx, &[&key]).unwrap();
assert_ne!(prev_tx, tx);
assert_ne!(prev_tx.signatures, tx.signatures);

View File

@ -4,7 +4,7 @@ use crate::rpc_request::{RpcError, RpcRequest};
use log::*;
use reqwest;
use reqwest::header::CONTENT_TYPE;
use solana_sdk::timing::{DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND};
use solana_sdk::timing::{DEFAULT_NUM_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT};
use std::thread::sleep;
use std::time::Duration;
@ -73,7 +73,7 @@ impl GenericRpcClientRequest for RpcClientRequest {
// Sleep for approximately half a slot
sleep(Duration::from_millis(
500 * DEFAULT_TICKS_PER_SLOT / NUM_TICKS_PER_SECOND,
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_NUM_TICKS_PER_SECOND,
));
}
}

View File

@ -13,9 +13,10 @@ pub enum RpcRequest {
GetRecentBlockhash,
GetSignatureStatus,
GetSlotLeader,
GetEpochVoteAccounts,
GetStorageBlockhash,
GetStorageEntryHeight,
GetStoragePubkeysForEntryHeight,
GetStorageSlot,
GetStoragePubkeysForSlot,
GetTransactionCount,
RegisterNode,
RequestAirdrop,
@ -39,9 +40,10 @@ impl RpcRequest {
RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
RpcRequest::GetSignatureStatus => "getSignatureStatus",
RpcRequest::GetSlotLeader => "getSlotLeader",
RpcRequest::GetEpochVoteAccounts => "getEpochVoteAccounts",
RpcRequest::GetStorageBlockhash => "getStorageBlockhash",
RpcRequest::GetStorageEntryHeight => "getStorageEntryHeight",
RpcRequest::GetStoragePubkeysForEntryHeight => "getStoragePubkeysForEntryHeight",
RpcRequest::GetStorageSlot => "getStorageSlot",
RpcRequest::GetStoragePubkeysForSlot => "getStoragePubkeysForSlot",
RpcRequest::GetTransactionCount => "getTransactionCount",
RpcRequest::RegisterNode => "registerNode",
RpcRequest::RequestAirdrop => "requestAirdrop",

View File

@ -7,6 +7,7 @@ use crate::rpc_client::RpcClient;
use bincode::{serialize_into, serialized_size};
use log::*;
use solana_sdk::client::{AsyncClient, Client, SyncClient};
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::hash::Hash;
use solana_sdk::instruction::Instruction;
use solana_sdk::message::Message;
@ -107,7 +108,8 @@ impl ThinClient {
return Ok(transaction.signatures[0]);
}
info!("{} tries failed transfer to {}", x, self.transactions_addr);
transaction.sign(keypairs, self.rpc_client.get_recent_blockhash()?);
let (blockhash, _fee_calculator) = self.rpc_client.get_recent_blockhash()?;
transaction.sign(keypairs, blockhash);
}
Err(io::Error::new(
io::ErrorKind::Other,
@ -115,10 +117,6 @@ impl ThinClient {
))
}
pub fn get_new_blockhash(&self, blockhash: &Hash) -> io::Result<Hash> {
self.rpc_client.get_new_blockhash(blockhash)
}
pub fn poll_balance_with_timeout(
&self,
pubkey: &Pubkey,
@ -163,7 +161,7 @@ impl Client for ThinClient {
impl SyncClient for ThinClient {
fn send_message(&self, keypairs: &[&Keypair], message: Message) -> TransportResult<Signature> {
let blockhash = self.get_recent_blockhash()?;
let (blockhash, _fee_calculator) = self.get_recent_blockhash()?;
let mut transaction = Transaction::new(&keypairs, message, blockhash);
let signature = self.send_and_confirm_transaction(keypairs, &mut transaction, 5, 0)?;
Ok(signature)
@ -214,9 +212,8 @@ impl SyncClient for ThinClient {
Ok(status)
}
fn get_recent_blockhash(&self) -> TransportResult<Hash> {
let recent_blockhash = self.rpc_client.get_recent_blockhash()?;
Ok(recent_blockhash)
fn get_recent_blockhash(&self) -> TransportResult<(Hash, FeeCalculator)> {
Ok(self.rpc_client.get_recent_blockhash()?)
}
fn get_transaction_count(&self) -> TransportResult<u64> {
@ -238,6 +235,10 @@ impl SyncClient for ThinClient {
fn poll_for_signature(&self, signature: &Signature) -> TransportResult<()> {
Ok(self.rpc_client.poll_for_signature(signature)?)
}
fn get_new_blockhash(&self, blockhash: &Hash) -> TransportResult<(Hash, FeeCalculator)> {
Ok(self.rpc_client.get_new_blockhash(blockhash)?)
}
}
impl AsyncClient for ThinClient {

1
core/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target/

View File

@ -1,7 +1,7 @@
[package]
name = "solana"
description = "Blockchain, Rebuilt for Scale"
version = "0.14.0"
version = "0.15.0"
documentation = "https://docs.rs/solana"
homepage = "https://solana.com/"
readme = "../README.md"
@ -20,12 +20,12 @@ erasure = []
kvstore = ["solana-kvstore"]
[dependencies]
bincode = "1.1.2"
bincode = "1.1.4"
bs58 = "0.2.0"
byteorder = "1.3.1"
chrono = { version = "0.4.0", features = ["serde"] }
crc = { version = "1.8.1", optional = true }
ed25519-dalek = "1.0.0-pre.0"
core_affinity = "0.5.9"
hashbrown = "0.2.0"
indexmap = "1.0"
itertools = "0.8.0"
@ -34,41 +34,48 @@ jsonrpc-derive = "11.0.0"
jsonrpc-http-server = "11.0.0"
jsonrpc-pubsub = "11.0.0"
jsonrpc-ws-server = "11.0.0"
libc = "0.2.50"
libc = "0.2.55"
log = "0.4.2"
memmap = { version = "0.7.0", optional = true }
nix = "0.13.0"
nix = "0.14.0"
rand = "0.6.5"
rand_chacha = "0.1.1"
rayon = "1.0.0"
reed-solomon-erasure = "3.1.1"
reqwest = "0.9.11"
reqwest = "0.9.17"
rocksdb = "0.11.0"
serde = "1.0.89"
serde_derive = "1.0.88"
serde_derive = "1.0.91"
serde_json = "1.0.39"
solana-budget-api = { path = "../programs/budget_api", version = "0.14.0" }
solana-client = { path = "../client", version = "0.14.0" }
solana-drone = { path = "../drone", version = "0.14.0" }
solana-kvstore = { path = "../kvstore", version = "0.14.0" , optional = true }
solana-logger = { path = "../logger", version = "0.14.0" }
solana-metrics = { path = "../metrics", version = "0.14.0" }
solana-netutil = { path = "../netutil", version = "0.14.0" }
solana-runtime = { path = "../runtime", version = "0.14.0" }
solana-sdk = { path = "../sdk", version = "0.14.0" }
solana-storage-api = { path = "../programs/storage_api", version = "0.14.0" }
solana-vote-api = { path = "../programs/vote_api", version = "0.14.0" }
solana-vote-signer = { path = "../vote-signer", version = "0.14.0" }
solana-budget-api = { path = "../programs/budget_api", version = "0.15.0" }
solana-budget-program = { path = "../programs/budget_program", version = "0.15.0" }
solana-client = { path = "../client", version = "0.15.0" }
solana-drone = { path = "../drone", version = "0.15.0" }
solana-ed25519-dalek = "0.2.0"
solana-kvstore = { path = "../kvstore", version = "0.15.0" , optional = true }
solana-logger = { path = "../logger", version = "0.15.0" }
solana-metrics = { path = "../metrics", version = "0.15.0" }
solana-netutil = { path = "../netutil", version = "0.15.0" }
solana-runtime = { path = "../runtime", version = "0.15.0" }
solana-sdk = { path = "../sdk", version = "0.15.0" }
solana-stake-api = { path = "../programs/stake_api", version = "0.15.0" }
solana-stake-program = { path = "../programs/stake_program", version = "0.15.0" }
solana-storage-api = { path = "../programs/storage_api", version = "0.15.0" }
solana-storage-program = { path = "../programs/storage_program", version = "0.15.0" }
solana-vote-api = { path = "../programs/vote_api", version = "0.15.0" }
solana-vote-program = { path = "../programs/vote_program", version = "0.15.0" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.15.0" }
solana-config-program = { path = "../programs/config_program", version = "0.15.0" }
solana-vote-signer = { path = "../vote-signer", version = "0.15.0" }
sys-info = "0.5.6"
tokio = "0.1"
tokio-codec = "0.1"
untrusted = "0.6.2"
[dev-dependencies]
hex-literal = "0.1.4"
hex-literal = "0.2.0"
matches = "0.1.6"
solana-vote-program = { path = "../programs/vote_program", version = "0.14.0" }
solana-budget-program = { path = "../programs/budget_program", version = "0.14.0" }
[[bench]]
name = "banking_stage"
@ -85,6 +92,12 @@ name = "gen_keys"
[[bench]]
name = "sigverify"
[[bench]]
name = "sigverify_stage"
[[bench]]
name = "poh"
[[bench]]
required-features = ["chacha"]
name = "chacha"

View File

@ -4,31 +4,34 @@ extern crate test;
#[macro_use]
extern crate solana;
use log::*;
use rand::{thread_rng, Rng};
use rayon::prelude::*;
use solana::banking_stage::{create_test_recorder, BankingStage};
use solana::blocktree::{get_tmp_ledger_path, Blocktree};
use solana::cluster_info::ClusterInfo;
use solana::cluster_info::Node;
use solana::leader_schedule_cache::LeaderScheduleCache;
use solana::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use solana::packet::to_packets_chunked;
use solana::poh_recorder::WorkingBankEntries;
use solana::service::Service;
use solana::test_tx::test_tx;
use solana_runtime::bank::Bank;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{KeypairUtil, Signature};
use solana_sdk::signature::Signature;
use solana_sdk::system_transaction;
use solana_sdk::timing::{DEFAULT_TICKS_PER_SLOT, MAX_RECENT_BLOCKHASHES};
use solana_sdk::timing::{
duration_as_ms, timestamp, DEFAULT_TICKS_PER_SLOT, MAX_RECENT_BLOCKHASHES,
};
use std::iter;
use std::sync::atomic::Ordering;
use std::sync::mpsc::{channel, Receiver};
use std::sync::{Arc, RwLock};
use std::time::Duration;
use std::time::{Duration, Instant};
use test::Bencher;
fn check_txs(receiver: &Receiver<WorkingBankEntries>, ref_tx_count: usize) {
fn check_txs(receiver: &Arc<Receiver<WorkingBankEntries>>, ref_tx_count: usize) {
let mut total = 0;
loop {
let entries = receiver.recv_timeout(Duration::new(1, 0));
@ -47,25 +50,63 @@ fn check_txs(receiver: &Receiver<WorkingBankEntries>, ref_tx_count: usize) {
}
#[bench]
#[ignore]
fn bench_consume_buffered(bencher: &mut Bencher) {
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(100_000);
let bank = Arc::new(Bank::new(&genesis_block));
let ledger_path = get_tmp_ledger_path!();
let my_pubkey = Pubkey::new_rand();
{
let blocktree = Arc::new(
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"),
);
let (exit, poh_recorder, poh_service, _signal_receiver) =
create_test_recorder(&bank, &blocktree);
let tx = test_tx();
let len = 4096;
let chunk_size = 1024;
let batches = to_packets_chunked(&vec![tx; len], chunk_size);
let mut packets = vec![];
for batch in batches {
let batch_len = batch.packets.len();
packets.push((batch, vec![0usize; batch_len]));
}
// This tests the performance of buffering packets.
// If the packet buffers are copied, performance will be poor.
bencher.iter(move || {
let _ignored =
BankingStage::consume_buffered_packets(&my_pubkey, &poh_recorder, &mut packets);
});
exit.store(true, Ordering::Relaxed);
poh_service.join().unwrap();
}
let _unused = Blocktree::destroy(&ledger_path);
}
#[bench]
fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
solana_logger::setup();
let num_threads = BankingStage::num_threads() as usize;
// a multiple of packet chunk 2X duplicates to avoid races
let txes = 192 * 50 * num_threads * 2;
let txes = 192 * num_threads * 2;
let mint_total = 1_000_000_000_000;
let (genesis_block, mint_keypair) = GenesisBlock::new(mint_total);
let GenesisBlockInfo {
mut genesis_block,
mint_keypair,
..
} = create_genesis_block(mint_total);
// Set a high ticks_per_slot so we don't run out of ticks
// during the benchmark
genesis_block.ticks_per_slot = 10_000;
let (verified_sender, verified_receiver) = channel();
let (vote_sender, vote_receiver) = channel();
let bank = Arc::new(Bank::new(&genesis_block));
let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank));
let dummy = system_transaction::transfer(
&mint_keypair,
&mint_keypair.pubkey(),
1,
genesis_block.hash(),
0,
);
let to_pubkey = Pubkey::new_rand();
let dummy = system_transaction::transfer(&mint_keypair, &to_pubkey, 1, genesis_block.hash());
trace!("txs: {}", txes);
let transactions: Vec<_> = (0..txes)
.into_par_iter()
.map(|_| {
@ -86,7 +127,6 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
&tx.message.account_keys[0],
mint_total / txes as u64,
genesis_block.hash(),
0,
);
let x = bank.process_transaction(&fund);
x.unwrap();
@ -124,25 +164,29 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
&poh_recorder,
verified_receiver,
vote_receiver,
&leader_schedule_cache,
);
poh_recorder.lock().unwrap().set_bank(&bank);
let mut id = genesis_block.hash();
for _ in 0..(MAX_RECENT_BLOCKHASHES * DEFAULT_TICKS_PER_SLOT as usize) {
id = hash(&id.as_ref());
bank.register_tick(&id);
}
let half_len = verified.len() / 2;
let mut start = 0;
// This is so that the signal_receiver does not go out of scope after the closure.
// If it is dropped before poh_service, then poh_service will error when
// calling send() on the channel.
let signal_receiver = Arc::new(signal_receiver);
let signal_receiver2 = signal_receiver.clone();
bencher.iter(move || {
// make sure the transactions are still valid
bank.register_tick(&genesis_block.hash());
let now = Instant::now();
for v in verified[start..start + half_len].chunks(verified.len() / num_threads) {
trace!("sending... {}..{} {}", start, start + half_len, timestamp());
verified_sender.send(v.to_vec()).unwrap();
}
check_txs(&signal_receiver, txes / 2);
check_txs(&signal_receiver2, txes / 2);
trace!(
"time: {} checked: {}",
duration_as_ms(&now.elapsed()),
txes / 2
);
bank.clear_signatures();
start += half_len;
start %= verified.len();
@ -151,7 +195,7 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
exit.store(true, Ordering::Relaxed);
poh_service.join().unwrap();
}
Blocktree::destroy(&ledger_path).unwrap();
let _unused = Blocktree::destroy(&ledger_path);
}
#[bench]
@ -162,19 +206,17 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
// a multiple of packet chunk 2X duplicates to avoid races
let txes = 96 * 100 * num_threads * 2;
let mint_total = 1_000_000_000_000;
let (genesis_block, mint_keypair) = GenesisBlock::new(mint_total);
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(mint_total);
let (verified_sender, verified_receiver) = channel();
let (vote_sender, vote_receiver) = channel();
let bank = Arc::new(Bank::new(&genesis_block));
let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank));
let dummy = system_transaction::transfer(
&mint_keypair,
&mint_keypair.pubkey(),
1,
genesis_block.hash(),
0,
);
let to_pubkey = Pubkey::new_rand();
let dummy = system_transaction::transfer(&mint_keypair, &to_pubkey, 1, genesis_block.hash());
let transactions: Vec<_> = (0..txes)
.into_par_iter()
.map(|_| {
@ -211,7 +253,6 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
&tx.message.account_keys[0],
mint_total / txes as u64,
genesis_block.hash(),
0,
);
bank.process_transaction(&fund).unwrap();
});
@ -249,7 +290,6 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
&poh_recorder,
verified_receiver,
vote_receiver,
&leader_schedule_cache,
);
poh_recorder.lock().unwrap().set_bank(&bank);
@ -261,13 +301,15 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
let half_len = verified.len() / 2;
let mut start = 0;
let signal_receiver = Arc::new(signal_receiver);
let signal_receiver2 = signal_receiver.clone();
bencher.iter(move || {
// make sure the transactions are still valid
bank.register_tick(&genesis_block.hash());
for v in verified[start..start + half_len].chunks(verified.len() / num_threads) {
verified_sender.send(v.to_vec()).unwrap();
}
check_txs(&signal_receiver, txes / 2);
check_txs(&signal_receiver2, txes / 2);
bank.clear_signatures();
start += half_len;
start %= verified.len();

View File

@ -13,7 +13,7 @@ fn bench_block_to_blobs_to_block(bencher: &mut Bencher) {
let zero = Hash::default();
let one = hash(&zero.as_ref());
let keypair = Keypair::new();
let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, one, 0);
let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, one);
let transactions = vec![tx0; 10];
let entries = next_entries(&zero, 1, transactions);

63
core/benches/poh.rs Normal file
View File

@ -0,0 +1,63 @@
// This bench attempts to justify the value of `solana::poh_service::NUM_HASHES_PER_BATCH`
#![feature(test)]
extern crate test;
use solana::poh::Poh;
use solana::poh_service::NUM_HASHES_PER_BATCH;
use solana_sdk::hash::Hash;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use test::Bencher;
const NUM_HASHES: u64 = 30_000; // Should require ~10ms on a 2017 MacBook Pro
#[bench]
// No locking. Fastest.
fn bench_poh_hash(bencher: &mut Bencher) {
let mut poh = Poh::new(Hash::default(), None);
bencher.iter(|| {
poh.hash(NUM_HASHES);
})
}
#[bench]
// Lock on each iteration. Slowest.
fn bench_arc_mutex_poh_hash(bencher: &mut Bencher) {
let poh = Arc::new(Mutex::new(Poh::new(Hash::default(), None)));
bencher.iter(|| {
for _ in 0..NUM_HASHES {
poh.lock().unwrap().hash(1);
}
})
}
#[bench]
// Acquire lock every NUM_HASHES_PER_BATCH iterations.
// Speed should be close to bench_poh_hash() if NUM_HASHES_PER_BATCH is set well.
fn bench_arc_mutex_poh_batched_hash(bencher: &mut Bencher) {
let poh = Arc::new(Mutex::new(Poh::new(Hash::default(), Some(NUM_HASHES))));
//let exit = Arc::new(AtomicBool::new(false));
let exit = Arc::new(AtomicBool::new(true));
bencher.iter(|| {
// NOTE: This block attempts to look as close as possible to `PohService::tick_producer()`
loop {
if poh.lock().unwrap().hash(NUM_HASHES_PER_BATCH) {
poh.lock().unwrap().tick().unwrap();
if exit.load(Ordering::Relaxed) {
break;
}
}
}
})
}
#[bench]
// Worst case transaction record delay due to batch hashing at NUM_HASHES_PER_BATCH
fn bench_poh_lock_time_per_batch(bencher: &mut Bencher) {
let mut poh = Poh::new(Hash::default(), None);
bencher.iter(|| {
poh.hash(NUM_HASHES_PER_BATCH);
})
}

View File

@ -0,0 +1,83 @@
#![feature(test)]
extern crate solana;
extern crate test;
use log::*;
use rand::{thread_rng, Rng};
use solana::packet::to_packets_chunked;
use solana::service::Service;
use solana::sigverify_stage::SigVerifyStage;
use solana::test_tx::test_tx;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;
use solana_sdk::timing::duration_as_ms;
use std::sync::mpsc::channel;
use std::time::{Duration, Instant};
use test::Bencher;
#[bench]
fn bench_sigverify_stage(bencher: &mut Bencher) {
solana_logger::setup();
let (packet_s, packet_r) = channel();
let (verified_s, verified_r) = channel();
let sigverify_disabled = false;
let stage = SigVerifyStage::new(packet_r, sigverify_disabled, verified_s);
let now = Instant::now();
let len = 4096;
let use_same_tx = true;
let chunk_size = 1024;
let mut batches = if use_same_tx {
let tx = test_tx();
to_packets_chunked(&vec![tx; len], chunk_size)
} else {
let from_keypair = Keypair::new();
let to_keypair = Keypair::new();
let txs: Vec<_> = (0..len)
.into_iter()
.map(|_| {
let amount = thread_rng().gen();
let tx = system_transaction::transfer(
&from_keypair,
&to_keypair.pubkey(),
amount,
Hash::default(),
);
tx
})
.collect();
to_packets_chunked(&txs, chunk_size)
};
trace!(
"starting... generation took: {} ms batches: {}",
duration_as_ms(&now.elapsed()),
batches.len()
);
bencher.iter(move || {
let mut sent_len = 0;
for _ in 0..batches.len() {
if let Some(batch) = batches.pop() {
sent_len += batch.packets.len();
packet_s.send(batch).unwrap();
}
}
let mut received = 0;
trace!("sent: {}", sent_len);
loop {
if let Ok(mut verifieds) = verified_r.recv_timeout(Duration::from_millis(10)) {
while let Some(v) = verifieds.pop() {
received += v.0.packets.len();
batches.push(v.0);
}
if received >= sent_len {
break;
}
}
}
trace!("received: {}", received);
});
stage.join().unwrap();
}

View File

@ -1,7 +1,7 @@
//! The `bank_forks` module implments BankForks a DAG of checkpointed Banks
use hashbrown::{HashMap, HashSet};
use solana_metrics::counter::Counter;
use solana_metrics::inc_new_counter_info;
use solana_runtime::bank::Bank;
use solana_sdk::timing;
use std::ops::Index;
@ -11,6 +11,7 @@ use std::time::Instant;
pub struct BankForks {
banks: HashMap<u64, Arc<Bank>>,
working_bank: Arc<Bank>,
root: u64,
}
impl Index<u64> for BankForks {
@ -28,6 +29,7 @@ impl BankForks {
Self {
banks,
working_bank,
root: 0,
}
}
@ -35,7 +37,8 @@ impl BankForks {
pub fn ancestors(&self) -> HashMap<u64, HashSet<u64>> {
let mut ancestors = HashMap::new();
for bank in self.banks.values() {
let set = bank.parents().into_iter().map(|b| b.slot()).collect();
let mut set: HashSet<u64> = bank.ancestors.keys().cloned().collect();
set.remove(&bank.slot());
ancestors.insert(bank.slot(), set);
}
ancestors
@ -46,9 +49,11 @@ impl BankForks {
let mut descendants = HashMap::new();
for bank in self.banks.values() {
let _ = descendants.entry(bank.slot()).or_insert(HashSet::new());
for parent in bank.parents() {
let mut set: HashSet<u64> = bank.ancestors.keys().cloned().collect();
set.remove(&bank.slot());
for parent in set {
descendants
.entry(parent.slot())
.entry(parent)
.or_insert(HashSet::new())
.insert(bank.slot());
}
@ -76,13 +81,14 @@ impl BankForks {
self.banks.get(&bank_slot)
}
pub fn new_from_banks(initial_banks: &[Arc<Bank>]) -> Self {
pub fn new_from_banks(initial_banks: &[Arc<Bank>], root: u64) -> Self {
let mut banks = HashMap::new();
let working_bank = initial_banks[0].clone();
for bank in initial_banks {
banks.insert(bank.slot(), bank.clone());
}
Self {
root,
banks,
working_bank,
}
@ -102,35 +108,52 @@ impl BankForks {
}
pub fn set_root(&mut self, root: u64) {
self.root = root;
let set_root_start = Instant::now();
let root_bank = self
.banks
.get(&root)
.expect("root bank didn't exist in bank_forks");
let root_tx_count = root_bank
.parents()
.last()
.map(|bank| bank.transaction_count())
.unwrap_or(0);
root_bank.squash();
let new_tx_count = root_bank.transaction_count();
self.prune_non_root(root);
inc_new_counter_info!(
"bank-forks_set_root_ms",
timing::duration_as_ms(&set_root_start.elapsed()) as usize
);
inc_new_counter_info!(
"bank-forks_set_root_tx_count",
(new_tx_count - root_tx_count) as usize
);
}
pub fn root(&self) -> u64 {
self.root
}
fn prune_non_root(&mut self, root: u64) {
let descendants = self.descendants();
self.banks
.retain(|slot, bank| *slot >= root || bank.is_in_subtree_of(root))
.retain(|slot, _| descendants[&root].contains(slot))
}
}
#[cfg(test)]
mod tests {
use super::*;
use solana_sdk::genesis_block::GenesisBlock;
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
#[test]
fn test_bank_forks() {
let (genesis_block, _) = GenesisBlock::new(10_000);
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank);
let child_bank = Bank::new_from_parent(&bank_forks[0u64], &Pubkey::default(), 1);
@ -142,7 +165,7 @@ mod tests {
#[test]
fn test_bank_forks_descendants() {
let (genesis_block, _) = GenesisBlock::new(10_000);
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank);
let bank0 = bank_forks[0].clone();
@ -159,7 +182,7 @@ mod tests {
#[test]
fn test_bank_forks_ancestors() {
let (genesis_block, _) = GenesisBlock::new(10_000);
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank);
let bank0 = bank_forks[0].clone();
@ -177,7 +200,7 @@ mod tests {
#[test]
fn test_bank_forks_frozen_banks() {
let (genesis_block, _) = GenesisBlock::new(10_000);
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank);
let child_bank = Bank::new_from_parent(&bank_forks[0u64], &Pubkey::default(), 1);
@ -188,7 +211,7 @@ mod tests {
#[test]
fn test_bank_forks_active_banks() {
let (genesis_block, _) = GenesisBlock::new(10_000);
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank);
let child_bank = Bank::new_from_parent(&bank_forks[0u64], &Pubkey::default(), 1);

File diff suppressed because it is too large Load Diff

View File

@ -65,14 +65,14 @@ pub trait BlockstreamEvents {
&self,
slot: u64,
tick_height: u64,
leader_id: &Pubkey,
leader_pubkey: &Pubkey,
entries: &Entry,
) -> Result<()>;
fn emit_block_event(
&self,
slot: u64,
tick_height: u64,
leader_id: &Pubkey,
leader_pubkey: &Pubkey,
blockhash: Hash,
) -> Result<()>;
}
@ -90,7 +90,7 @@ where
&self,
slot: u64,
tick_height: u64,
leader_id: &Pubkey,
leader_pubkey: &Pubkey,
entry: &Entry,
) -> Result<()> {
let transactions: Vec<Vec<u8>> = serialize_transactions(entry);
@ -105,7 +105,7 @@ where
Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true),
slot,
tick_height,
leader_id,
leader_pubkey,
json_entry,
);
self.output.write(payload)?;
@ -116,7 +116,7 @@ where
&self,
slot: u64,
tick_height: u64,
leader_id: &Pubkey,
leader_pubkey: &Pubkey,
blockhash: Hash,
) -> Result<()> {
let payload = format!(
@ -124,7 +124,7 @@ where
Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true),
slot,
tick_height,
leader_id,
leader_pubkey,
blockhash,
);
self.output.write(payload)?;
@ -183,10 +183,8 @@ mod test {
let keypair0 = Keypair::new();
let keypair1 = Keypair::new();
let tx0 =
system_transaction::transfer(&keypair0, &keypair1.pubkey(), 1, Hash::default(), 0);
let tx1 =
system_transaction::transfer(&keypair1, &keypair0.pubkey(), 2, Hash::default(), 0);
let tx0 = system_transaction::transfer(&keypair0, &keypair1.pubkey(), 1, Hash::default());
let tx1 = system_transaction::transfer(&keypair1, &keypair0.pubkey(), 2, Hash::default());
let serialized_tx0 = serialize(&tx0).unwrap();
let serialized_tx1 = serialize(&tx1).unwrap();
let entry = Entry::new(&Hash::default(), 1, vec![tx0, tx1]);
@ -208,19 +206,19 @@ mod test {
let tick_height_initial = 0;
let tick_height_final = tick_height_initial + ticks_per_slot + 2;
let mut curr_slot = 0;
let leader_id = Pubkey::new_rand();
let leader_pubkey = Pubkey::new_rand();
for tick_height in tick_height_initial..=tick_height_final {
if tick_height == 5 {
blockstream
.emit_block_event(curr_slot, tick_height - 1, &leader_id, blockhash)
.emit_block_event(curr_slot, tick_height - 1, &leader_pubkey, blockhash)
.unwrap();
curr_slot += 1;
}
let entry = Entry::new(&mut blockhash, 1, vec![]); // just ticks
blockhash = entry.hash;
blockstream
.emit_entry_event(curr_slot, tick_height, &leader_id, &entry)
.emit_entry_event(curr_slot, tick_height, &leader_pubkey, &entry)
.unwrap();
expected_entries.push(entry.clone());
entries.push(entry);

View File

@ -109,10 +109,10 @@ mod test {
use super::*;
use crate::blocktree::create_new_tmp_ledger;
use crate::entry::{create_ticks, Entry};
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use bincode::{deserialize, serialize};
use chrono::{DateTime, FixedOffset};
use serde_json::Value;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;
@ -121,10 +121,12 @@ mod test {
#[test]
fn test_blockstream_service_process_entries() {
let ticks_per_slot = 5;
let leader_id = Pubkey::new_rand();
let leader_pubkey = Pubkey::new_rand();
// Set up genesis block and blocktree
let (mut genesis_block, _mint_keypair) = GenesisBlock::new(1000);
let GenesisBlockInfo {
mut genesis_block, ..
} = create_genesis_block(1000);
genesis_block.ticks_per_slot = ticks_per_slot;
let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_block);
@ -146,7 +148,6 @@ mod test {
&keypair.pubkey(),
1,
Hash::default(),
0,
);
let entry = Entry::new(&mut blockhash, 1, vec![tx]);
blockhash = entry.hash;
@ -161,7 +162,7 @@ mod test {
.write_entries(1, 0, 0, ticks_per_slot, &entries)
.unwrap();
slot_full_sender.send((1, leader_id)).unwrap();
slot_full_sender.send((1, leader_pubkey)).unwrap();
BlockstreamService::process_entries(
&slot_full_receiver,
&Arc::new(blocktree),

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@ use std::borrow::Borrow;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::path::Path;
use std::sync::Arc;
pub mod columns {
#[derive(Debug)]
@ -30,6 +31,10 @@ pub mod columns {
#[derive(Debug)]
/// The erasure meta column
pub struct ErasureMeta;
#[derive(Debug)]
/// The root column
pub struct Root;
}
pub trait Backend: Sized + Send + Sync {
@ -55,7 +60,7 @@ pub trait Backend: Sized + Send + Sync {
fn delete_cf(&self, cf: Self::ColumnFamily, key: &Self::Key) -> Result<()>;
fn iterator_cf(&self, cf: Self::ColumnFamily) -> Result<Self::Iter>;
fn iterator_cf(&self, cf: Self::ColumnFamily, from: Option<&Self::Key>) -> Result<Self::Iter>;
fn raw_iterator_cf(&self, cf: Self::ColumnFamily) -> Result<Self::Cursor>;
@ -112,7 +117,15 @@ pub struct Database<B>
where
B: Backend,
{
backend: B,
backend: Arc<B>,
}
#[derive(Debug, Clone)]
pub struct BatchProcessor<B>
where
B: Backend,
{
backend: Arc<B>,
}
#[derive(Debug, Clone)]
@ -132,7 +145,7 @@ where
B: Backend,
C: Column<B>,
{
backend: PhantomData<B>,
backend: Arc<B>,
column: PhantomData<C>,
}
@ -151,7 +164,7 @@ where
B: Backend,
{
pub fn open(path: &Path) -> Result<Self> {
let backend = B::open(path)?;
let backend = Arc::new(B::open(path)?);
Ok(Database { backend })
}
@ -170,7 +183,7 @@ where
.get_cf(self.cf_handle::<C>(), C::key(key).borrow())
}
pub fn put_bytes<C>(&mut self, key: C::Index, data: &[u8]) -> Result<()>
pub fn put_bytes<C>(&self, key: C::Index, data: &[u8]) -> Result<()>
where
C: Column<B>,
{
@ -178,7 +191,7 @@ where
.put_cf(self.cf_handle::<C>(), C::key(key).borrow(), data)
}
pub fn delete<C>(&mut self, key: C::Index) -> Result<()>
pub fn delete<C>(&self, key: C::Index) -> Result<()>
where
C: Column<B>,
{
@ -202,7 +215,7 @@ where
}
}
pub fn put<C>(&mut self, key: C::Index, value: &C::Type) -> Result<()>
pub fn put<C>(&self, key: C::Index, value: &C::Type) -> Result<()>
where
C: TypedColumn<B>,
{
@ -228,18 +241,60 @@ where
})
}
pub fn iter<C>(&self) -> Result<impl Iterator<Item = (C::Index, Vec<u8>)>>
pub fn iter<C>(
&self,
start_from: Option<C::Index>,
) -> Result<impl Iterator<Item = (C::Index, Box<[u8]>)>>
where
C: Column<B>,
{
let iter = self
.backend
.iterator_cf(self.cf_handle::<C>())?
.map(|(key, value)| (C::index(&key), value.into()));
let iter = {
if let Some(index) = start_from {
let key = C::key(index);
self.backend
.iterator_cf(self.cf_handle::<C>(), Some(key.borrow()))?
} else {
self.backend.iterator_cf(self.cf_handle::<C>(), None)?
}
};
Ok(iter)
Ok(iter.map(|(key, value)| (C::index(&key), value)))
}
#[inline]
pub fn cf_handle<C>(&self) -> B::ColumnFamily
where
C: Column<B>,
{
self.backend.cf_handle(C::NAME).clone()
}
pub fn column<C>(&self) -> LedgerColumn<B, C>
where
C: Column<B>,
{
LedgerColumn {
backend: Arc::clone(&self.backend),
column: PhantomData,
}
}
// Note this returns an object that can be used to directly write to multiple column families.
// This circumvents the synchronization around APIs that in Blocktree that use
// blocktree.batch_processor, so this API should only be used if the caller is sure they
// are writing to data in columns that will not be corrupted by any simultaneous blocktree
// operations.
pub unsafe fn batch_processor(&self) -> BatchProcessor<B> {
BatchProcessor {
backend: Arc::clone(&self.backend),
}
}
}
impl<B> BatchProcessor<B>
where
B: Backend,
{
pub fn batch(&mut self) -> Result<WriteBatch<B>> {
let db_write_batch = self.backend.batch()?;
let map = self
@ -259,24 +314,6 @@ where
pub fn write(&mut self, batch: WriteBatch<B>) -> Result<()> {
self.backend.write(batch.write_batch)
}
#[inline]
pub fn cf_handle<C>(&self) -> B::ColumnFamily
where
C: Column<B>,
{
self.backend.cf_handle(C::NAME).clone()
}
pub fn column<C>(&self) -> LedgerColumn<B, C>
where
C: Column<B>,
{
LedgerColumn {
backend: PhantomData,
column: PhantomData,
}
}
}
impl<B, C> Cursor<B, C>
@ -333,41 +370,55 @@ where
B: Backend,
C: Column<B>,
{
pub fn get_bytes(&self, db: &Database<B>, key: C::Index) -> Result<Option<Vec<u8>>> {
db.backend.get_cf(self.handle(db), C::key(key).borrow())
pub fn get_bytes(&self, key: C::Index) -> Result<Option<Vec<u8>>> {
self.backend.get_cf(self.handle(), C::key(key).borrow())
}
pub fn cursor(&self, db: &Database<B>) -> Result<Cursor<B, C>> {
db.cursor()
pub fn cursor(&self) -> Result<Cursor<B, C>> {
let db_cursor = self.backend.raw_iterator_cf(self.handle())?;
Ok(Cursor {
db_cursor,
column: PhantomData,
backend: PhantomData,
})
}
pub fn iter(&self, db: &Database<B>) -> Result<impl Iterator<Item = (C::Index, Vec<u8>)>> {
db.iter::<C>()
pub fn iter(
&self,
start_from: Option<C::Index>,
) -> Result<impl Iterator<Item = (C::Index, Box<[u8]>)>> {
let iter = {
if let Some(index) = start_from {
let key = C::key(index);
self.backend
.iterator_cf(self.handle(), Some(key.borrow()))?
} else {
self.backend.iterator_cf(self.handle(), None)?
}
};
Ok(iter.map(|(key, value)| (C::index(&key), value)))
}
pub fn handle(&self, db: &Database<B>) -> B::ColumnFamily {
db.cf_handle::<C>()
#[inline]
pub fn handle(&self) -> B::ColumnFamily {
self.backend.cf_handle(C::NAME).clone()
}
pub fn is_empty(&self, db: &Database<B>) -> Result<bool> {
let mut cursor = self.cursor(db)?;
pub fn is_empty(&self) -> Result<bool> {
let mut cursor = self.cursor()?;
cursor.seek_to_first();
Ok(!cursor.valid())
}
}
impl<B, C> LedgerColumn<B, C>
where
B: Backend,
C: Column<B>,
{
pub fn put_bytes(&self, db: &mut Database<B>, key: C::Index, value: &[u8]) -> Result<()> {
db.backend
.put_cf(self.handle(db), C::key(key).borrow(), value)
pub fn put_bytes(&self, key: C::Index, value: &[u8]) -> Result<()> {
self.backend
.put_cf(self.handle(), C::key(key).borrow(), value)
}
pub fn delete(&self, db: &mut Database<B>, key: C::Index) -> Result<()> {
db.backend.delete_cf(self.handle(db), C::key(key).borrow())
pub fn delete(&self, key: C::Index) -> Result<()> {
self.backend.delete_cf(self.handle(), C::key(key).borrow())
}
}
@ -376,18 +427,21 @@ where
B: Backend,
C: TypedColumn<B>,
{
pub fn get(&self, db: &Database<B>, key: C::Index) -> Result<Option<C::Type>> {
db.get::<C>(key)
}
}
pub fn get(&self, key: C::Index) -> Result<Option<C::Type>> {
if let Some(serialized_value) = self.backend.get_cf(self.handle(), C::key(key).borrow())? {
let value = deserialize(&serialized_value)?;
impl<B, C> LedgerColumn<B, C>
where
B: Backend,
C: TypedColumn<B>,
{
pub fn put(&self, db: &mut Database<B>, key: C::Index, value: &C::Type) -> Result<()> {
db.put::<C>(key, value)
Ok(Some(value))
} else {
Ok(None)
}
}
pub fn put(&self, key: C::Index, value: &C::Type) -> Result<()> {
let serialized_value = serialize(value)?;
self.backend
.put_cf(self.handle(), C::key(key).borrow(), &serialized_value)
}
}

View File

@ -119,6 +119,40 @@ impl TypedColumn<Kvs> for cf::Orphans {
type Type = bool;
}
impl Column<Kvs> for cf::Root {
const NAME: &'static str = super::ROOT_CF;
type Index = u64;
fn key(slot: u64) -> Key {
let mut key = Key::default();
BigEndian::write_u64(&mut key.0[8..16], slot);
key
}
fn index(key: &Key) -> u64 {
BigEndian::read_u64(&key.0[8..16])
}
}
impl TypedColumn<Kvs> for cf::Root {
type Type = bool;
}
impl Column<Kvs> for cf::SlotMeta {
const NAME: &'static str = super::META_CF;
type Index = u64;
fn key(slot: u64) -> Key {
let mut key = Key::default();
BigEndian::write_u64(&mut key.0[8..16], slot);
key
}
fn index(key: &Key) -> u64 {
BigEndian::read_u64(&key.0[8..16])
}
}
impl Column<Kvs> for cf::SlotMeta {
const NAME: &'static str = super::META_CF;
type Index = u64;

View File

@ -1,4 +1,6 @@
use crate::erasure::{NUM_CODING, NUM_DATA};
use solana_metrics::datapoint;
use std::borrow::Borrow;
#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
// The Meta column family
@ -23,8 +25,6 @@ pub struct SlotMeta {
// True if this slot is full (consumed == last_index + 1) and if every
// slot that is a parent of this slot is also connected.
pub is_connected: bool,
// True if this slot is a root
pub is_root: bool,
}
impl SlotMeta {
@ -38,17 +38,17 @@ impl SlotMeta {
// Should never happen
if self.consumed > self.last_index + 1 {
solana_metrics::submit(
solana_metrics::influxdb::Point::new("blocktree_error")
.add_field(
"error",
solana_metrics::influxdb::Value::String(format!(
"Observed a slot meta with consumed: {} > meta.last_index + 1: {}",
self.consumed,
self.last_index + 1
)),
)
.to_owned(),
datapoint!(
"blocktree_error",
(
"error",
format!(
"Observed a slot meta with consumed: {} > meta.last_index + 1: {}",
self.consumed,
self.last_index + 1
),
String
)
);
}
@ -67,7 +67,6 @@ impl SlotMeta {
parent_slot,
next_slots: vec![],
is_connected: slot == 0,
is_root: false,
last_index: std::u64::MAX,
}
}
@ -81,9 +80,9 @@ pub struct ErasureMeta {
/// Size of shards in this erasure set
pub size: usize,
/// Bitfield representing presence/absence of data blobs
pub data: u64,
data: u64,
/// Bitfield representing presence/absence of coding blobs
pub coding: u64,
coding: u64,
}
#[derive(Debug, PartialEq)]
@ -104,10 +103,8 @@ impl ErasureMeta {
}
pub fn status(&self) -> ErasureMetaStatus {
let (data_missing, coding_missing) = (
NUM_DATA - self.data.count_ones() as usize,
NUM_CODING - self.coding.count_ones() as usize,
);
let (data_missing, coding_missing) =
(NUM_DATA - self.num_data(), NUM_CODING - self.num_coding());
if data_missing > 0 && data_missing + coding_missing <= NUM_CODING {
assert!(self.size != 0);
ErasureMetaStatus::CanRecover
@ -118,6 +115,14 @@ impl ErasureMeta {
}
}
pub fn num_coding(&self) -> usize {
self.coding.count_ones() as usize
}
pub fn num_data(&self) -> usize {
self.data.count_ones() as usize
}
pub fn is_coding_present(&self, index: u64) -> bool {
if let Some(position) = self.data_index_in_set(index) {
self.coding & (1 << position) != 0
@ -162,6 +167,26 @@ impl ErasureMeta {
}
}
pub fn set_data_multi<I, Idx>(&mut self, indexes: I, present: bool)
where
I: IntoIterator<Item = Idx>,
Idx: Borrow<u64>,
{
for index in indexes.into_iter() {
self.set_data_present(*index.borrow(), present);
}
}
pub fn set_coding_multi<I, Idx>(&mut self, indexes: I, present: bool)
where
I: IntoIterator<Item = Idx>,
Idx: Borrow<u64>,
{
for index in indexes.into_iter() {
self.set_coding_present(*index.borrow(), present);
}
}
pub fn set_index_for(index: u64) -> u64 {
index / NUM_DATA as u64
}
@ -201,7 +226,7 @@ fn test_meta_indexes() {
for _ in 0..100 {
let set_index = rng.gen_range(0, 1_000);
let blob_index = (set_index * NUM_DATA) + rng.gen_range(0, 16);
let blob_index = (set_index * NUM_DATA) + rng.gen_range(0, NUM_DATA);
assert_eq!(set_index, ErasureMeta::set_index_for(blob_index));
let e_meta = ErasureMeta::new(set_index);
@ -236,8 +261,8 @@ fn test_meta_indexes() {
fn test_meta_coding_present() {
let mut e_meta = ErasureMeta::default();
e_meta.set_coding_multi(0..NUM_CODING as u64, true);
for i in 0..NUM_CODING as u64 {
e_meta.set_coding_present(i, true);
assert_eq!(e_meta.is_coding_present(i), true);
}
for i in NUM_CODING as u64..NUM_DATA as u64 {
@ -245,60 +270,62 @@ fn test_meta_coding_present() {
}
e_meta.set_index = ErasureMeta::set_index_for((NUM_DATA * 17) as u64);
let start_idx = e_meta.start_index();
e_meta.set_coding_multi(start_idx..start_idx + NUM_CODING as u64, true);
for i in (NUM_DATA * 17) as u64..((NUM_DATA * 17) + NUM_CODING) as u64 {
for i in start_idx..start_idx + NUM_CODING as u64 {
e_meta.set_coding_present(i, true);
assert_eq!(e_meta.is_coding_present(i), true);
}
for i in (NUM_DATA * 17 + NUM_CODING) as u64..((NUM_DATA * 17) + NUM_DATA) as u64 {
for i in start_idx + NUM_CODING as u64..start_idx + NUM_DATA as u64 {
assert_eq!(e_meta.is_coding_present(i), false);
}
}
#[test]
fn test_erasure_meta_status() {
use rand::{seq::SliceRandom, thread_rng};
// Local constansts just used to avoid repetitive casts
const N_DATA: u64 = crate::erasure::NUM_DATA as u64;
const N_CODING: u64 = crate::erasure::NUM_CODING as u64;
let mut e_meta = ErasureMeta::default();
let mut rng = thread_rng();
let data_indexes: Vec<u64> = (0..N_DATA).collect();
let coding_indexes: Vec<u64> = (0..N_CODING).collect();
assert_eq!(e_meta.status(), ErasureMetaStatus::StillNeed(NUM_DATA));
e_meta.data = 0b1111_1111_1111_1111;
e_meta.coding = 0x00;
e_meta.set_data_multi(0..N_DATA, true);
assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull);
e_meta.coding = 0x0e;
e_meta.size = 1;
e_meta.set_coding_multi(0..N_CODING, true);
assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull);
e_meta.data = 0b0111_1111_1111_1111;
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
for &idx in data_indexes.choose_multiple(&mut rng, NUM_CODING) {
e_meta.set_data_present(idx, false);
e_meta.data = 0b0111_1111_1111_1110;
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
}
e_meta.data = 0b0111_1111_1011_1110;
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
e_meta.set_data_multi(0..N_DATA, true);
e_meta.data = 0b0111_1011_1011_1110;
assert_eq!(e_meta.status(), ErasureMetaStatus::StillNeed(1));
for &idx in coding_indexes.choose_multiple(&mut rng, NUM_CODING) {
e_meta.set_coding_present(idx, false);
e_meta.data = 0b0111_1011_1011_1110;
assert_eq!(e_meta.status(), ErasureMetaStatus::StillNeed(1));
e_meta.coding = 0b0000_1110;
e_meta.data = 0b1111_1111_1111_1100;
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
e_meta.data = 0b1111_1111_1111_1000;
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull);
}
}
#[test]
fn test_meta_data_present() {
let mut e_meta = ErasureMeta::default();
e_meta.set_data_multi(0..NUM_DATA as u64, true);
for i in 0..NUM_DATA as u64 {
e_meta.set_data_present(i, true);
assert_eq!(e_meta.is_data_present(i), true);
}
for i in NUM_DATA as u64..2 * NUM_DATA as u64 {
@ -306,12 +333,13 @@ fn test_meta_data_present() {
}
e_meta.set_index = ErasureMeta::set_index_for((NUM_DATA * 23) as u64);
let start_idx = e_meta.start_index();
e_meta.set_data_multi(start_idx..start_idx + NUM_DATA as u64, true);
for i in (NUM_DATA * 23) as u64..(NUM_DATA * 24) as u64 {
e_meta.set_data_present(i, true);
for i in start_idx..start_idx + NUM_DATA as u64 {
assert_eq!(e_meta.is_data_present(i), true);
}
for i in (NUM_DATA * 22) as u64..(NUM_DATA * 23) as u64 {
for i in start_idx - NUM_DATA as u64..start_idx {
assert_eq!(e_meta.is_data_present(i), false);
}
}

View File

@ -6,8 +6,8 @@ use crate::result::{Error, Result};
use byteorder::{BigEndian, ByteOrder};
use rocksdb::{
self, ColumnFamily, ColumnFamilyDescriptor, DBIterator, DBRawIterator, IteratorMode, Options,
WriteBatch as RWriteBatch, DB,
self, ColumnFamily, ColumnFamilyDescriptor, DBIterator, DBRawIterator, Direction, IteratorMode,
Options, WriteBatch as RWriteBatch, DB,
};
use std::fs;
@ -30,7 +30,7 @@ impl Backend for Rocks {
type Error = rocksdb::Error;
fn open(path: &Path) -> Result<Rocks> {
use crate::blocktree::db::columns::{Coding, Data, ErasureMeta, Orphans, SlotMeta};
use crate::blocktree::db::columns::{Coding, Data, ErasureMeta, Orphans, Root, SlotMeta};
fs::create_dir_all(&path)?;
@ -44,6 +44,7 @@ impl Backend for Rocks {
let erasure_meta_cf_descriptor =
ColumnFamilyDescriptor::new(ErasureMeta::NAME, get_cf_options());
let orphans_cf_descriptor = ColumnFamilyDescriptor::new(Orphans::NAME, get_cf_options());
let root_cf_descriptor = ColumnFamilyDescriptor::new(Root::NAME, get_cf_options());
let cfs = vec![
meta_cf_descriptor,
@ -51,6 +52,7 @@ impl Backend for Rocks {
erasure_cf_descriptor,
erasure_meta_cf_descriptor,
orphans_cf_descriptor,
root_cf_descriptor,
];
// Open the database
@ -60,13 +62,14 @@ impl Backend for Rocks {
}
fn columns(&self) -> Vec<&'static str> {
use crate::blocktree::db::columns::{Coding, Data, ErasureMeta, Orphans, SlotMeta};
use crate::blocktree::db::columns::{Coding, Data, ErasureMeta, Orphans, Root, SlotMeta};
vec![
Coding::NAME,
ErasureMeta::NAME,
Data::NAME,
Orphans::NAME,
Root::NAME,
SlotMeta::NAME,
]
}
@ -98,10 +101,17 @@ impl Backend for Rocks {
Ok(())
}
fn iterator_cf(&self, cf: ColumnFamily) -> Result<DBIterator> {
let raw_iter = self.0.iterator_cf(cf, IteratorMode::Start)?;
fn iterator_cf(&self, cf: ColumnFamily, start_from: Option<&[u8]>) -> Result<DBIterator> {
let iter = {
if let Some(start_from) = start_from {
self.0
.iterator_cf(cf, IteratorMode::From(start_from, Direction::Forward))?
} else {
self.0.iterator_cf(cf, IteratorMode::Start)?
}
};
Ok(raw_iter)
Ok(iter)
}
fn raw_iterator_cf(&self, cf: ColumnFamily) -> Result<DBRawIterator> {
@ -170,6 +180,25 @@ impl TypedColumn<Rocks> for cf::Orphans {
type Type = bool;
}
impl Column<Rocks> for cf::Root {
const NAME: &'static str = super::ROOT_CF;
type Index = u64;
fn key(slot: u64) -> Vec<u8> {
let mut key = vec![0; 8];
BigEndian::write_u64(&mut key[..], slot);
key
}
fn index(key: &[u8]) -> u64 {
BigEndian::read_u64(&key[..8])
}
}
impl TypedColumn<Rocks> for cf::Root {
type Type = bool;
}
impl Column<Rocks> for cf::SlotMeta {
const NAME: &'static str = super::META_CF;
type Index = u64;

View File

@ -0,0 +1,126 @@
use super::*;
pub struct RootedSlotIterator<'a> {
next_slots: Vec<u64>,
blocktree: &'a super::Blocktree,
}
impl<'a> RootedSlotIterator<'a> {
pub fn new(start_slot: u64, blocktree: &'a super::Blocktree) -> Result<Self> {
if blocktree.is_root(start_slot) {
Ok(Self {
next_slots: vec![start_slot],
blocktree,
})
} else {
Err(Error::BlocktreeError(BlocktreeError::SlotNotRooted))
}
}
}
impl<'a> Iterator for RootedSlotIterator<'a> {
type Item = (u64, super::SlotMeta);
fn next(&mut self) -> Option<Self::Item> {
// Clone b/c passing the closure to the map below requires exclusive access to
// `self`, which is borrowed here if we don't clone.
let rooted_slot = self
.next_slots
.iter()
.find(|x| self.blocktree.is_root(**x))
.cloned();
rooted_slot.map(|rooted_slot| {
let slot_meta = self
.blocktree
.meta(rooted_slot)
.expect("Database failure, couldnt fetch SlotMeta")
.expect("SlotMeta in iterator didn't exist");
self.next_slots = slot_meta.next_slots.clone();
(rooted_slot, slot_meta)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::blocktree_processor::tests::fill_blocktree_slot_with_ticks;
#[test]
fn test_rooted_slot_iterator() {
let blocktree_path = get_tmp_ledger_path("test_rooted_slot_iterator");
let blocktree = Blocktree::open(&blocktree_path).unwrap();
blocktree.set_root(0, 0).unwrap();
let ticks_per_slot = 5;
/*
Build a blocktree in the ledger with the following fork structure:
slot 0
|
slot 1 <-- set_root(true)
/ \
slot 2 |
/ |
slot 3 |
|
slot 4
*/
// Fork 1, ending at slot 3
let last_entry_hash = Hash::default();
let fork_point = 1;
let mut fork_hash = Hash::default();
for slot in 0..=3 {
let parent = {
if slot == 0 {
0
} else {
slot - 1
}
};
let last_entry_hash = fill_blocktree_slot_with_ticks(
&blocktree,
ticks_per_slot,
slot,
parent,
last_entry_hash,
);
if slot == fork_point {
fork_hash = last_entry_hash;
}
}
// Fork 2, ending at slot 4
let _ =
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, 4, fork_point, fork_hash);
// Set a root
blocktree.set_root(3, 0).unwrap();
// Trying to get an iterator on a different fork will error
assert!(RootedSlotIterator::new(4, &blocktree).is_err());
// Trying to get an iterator on any slot on the root fork should succeed
let result: Vec<_> = RootedSlotIterator::new(3, &blocktree)
.unwrap()
.into_iter()
.map(|(slot, _)| slot)
.collect();
let expected = vec![3];
assert_eq!(result, expected);
let result: Vec<_> = RootedSlotIterator::new(0, &blocktree)
.unwrap()
.into_iter()
.map(|(slot, _)| slot)
.collect();
let expected = vec![0, 1, 2, 3];
assert_eq!(result, expected);
drop(blocktree);
Blocktree::destroy(&blocktree_path).expect("Expected successful database destruction");
}
}

View File

@ -3,26 +3,32 @@ use crate::blocktree::Blocktree;
use crate::entry::{Entry, EntrySlice};
use crate::leader_schedule_cache::LeaderScheduleCache;
use rayon::prelude::*;
use solana_metrics::counter::Counter;
use solana_metrics::{datapoint, datapoint_error, inc_new_counter_debug};
use solana_runtime::bank::Bank;
use solana_runtime::locked_accounts_results::LockedAccountsResults;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::timing::duration_as_ms;
use solana_sdk::timing::MAX_RECENT_BLOCKHASHES;
use solana_sdk::transaction::Result;
use solana_sdk::transaction::Transaction;
use std::result;
use std::sync::Arc;
use std::time::{Duration, Instant};
fn first_err(results: &[Result<()>]) -> Result<()> {
for r in results {
r.clone()?;
if r.is_err() {
return r.clone();
}
}
Ok(())
}
fn par_execute_entries(bank: &Bank, entries: &[(&Entry, LockedAccountsResults)]) -> Result<()> {
inc_new_counter_info!("bank-par_execute_entries-count", entries.len());
fn par_execute_entries(
bank: &Bank,
entries: &[(&Entry, LockedAccountsResults<Transaction>)],
) -> Result<()> {
inc_new_counter_debug!("bank-par_execute_entries-count", entries.len());
let results: Vec<Result<()>> = entries
.into_par_iter()
.map(|(e, locked_accounts)| {
@ -32,21 +38,17 @@ fn par_execute_entries(bank: &Bank, entries: &[(&Entry, LockedAccountsResults)])
MAX_RECENT_BLOCKHASHES,
);
let mut first_err = None;
for r in results {
for (r, tx) in results.iter().zip(e.transactions.iter()) {
if let Err(ref e) = r {
if first_err.is_none() {
first_err = Some(r.clone());
}
if !Bank::can_commit(&r) {
warn!("Unexpected validator error: {:?}", e);
solana_metrics::submit(
solana_metrics::influxdb::Point::new("validator_process_entry_error")
.add_field(
"error",
solana_metrics::influxdb::Value::String(format!("{:?}", e)),
)
.to_owned(),
)
warn!("Unexpected validator error: {:?}, tx: {:?}", e, tx);
datapoint_error!(
"validator_process_entry_error",
("error", format!("error: {:?}, tx: {:?}", e, tx), String)
);
}
}
}
@ -69,25 +71,47 @@ pub fn process_entries(bank: &Bank, entries: &[Entry]) -> Result<()> {
if entry.is_tick() {
// if its a tick, execute the group and register the tick
par_execute_entries(bank, &mt_group)?;
bank.register_tick(&entry.hash);
mt_group = vec![];
bank.register_tick(&entry.hash);
continue;
}
// try to lock the accounts
let lock_results = bank.lock_accounts(&entry.transactions);
// if any of the locks error out
// execute the current group
if first_err(lock_results.locked_accounts_results()).is_err() {
par_execute_entries(bank, &mt_group)?;
// Drop all the locks on accounts by clearing the LockedAccountsFinalizer's in the
// mt_group
mt_group = vec![];
drop(lock_results);
// else loop on processing the entry
loop {
// try to lock the accounts
let lock_results = bank.lock_accounts(&entry.transactions);
mt_group.push((entry, lock_results));
} else {
// push the entry to the mt_group
mt_group.push((entry, lock_results));
let first_lock_err = first_err(lock_results.locked_accounts_results());
// if locking worked
if first_lock_err.is_ok() {
// push the entry to the mt_group
mt_group.push((entry, lock_results));
// done with this entry
break;
}
// else we failed to lock, 2 possible reasons
if mt_group.is_empty() {
// An entry has account lock conflicts with *itself*, which should not happen
// if generated by a properly functioning leader
datapoint!(
"validator_process_entry_error",
(
"error",
format!(
"Lock accounts error, entry conflicts with itself, txs: {:?}",
entry.transactions
),
String
)
);
// bail
first_lock_err?;
} else {
// else we have an entry that conflicts with a prior entry
// execute the current queue and try to process this entry again
par_execute_entries(bank, &mt_group)?;
mt_group = vec![];
}
}
}
par_execute_entries(bank, &mt_group)?;
@ -131,10 +155,13 @@ pub fn process_blocktree(
vec![(slot, meta, bank, entry_height, last_entry_hash)]
};
let leader_schedule_cache = LeaderScheduleCache::new(*pending_slots[0].2.epoch_schedule());
blocktree.set_root(0, 0).expect("Couldn't set first root");
let leader_schedule_cache = LeaderScheduleCache::new(*pending_slots[0].2.epoch_schedule(), 0);
let mut fork_info = vec![];
let mut last_status_report = Instant::now();
let mut root = 0;
while !pending_slots.is_empty() {
let (slot, meta, bank, mut entry_height, mut last_entry_hash) =
pending_slots.pop().unwrap();
@ -188,7 +215,11 @@ pub fn process_blocktree(
bank.freeze(); // all banks handled by this routine are created from complete slots
if blocktree.is_root(slot) {
root = slot;
leader_schedule_cache.set_root(slot);
bank.squash();
pending_slots.clear();
fork_info.clear();
}
if meta.next_slots.is_empty() {
@ -212,12 +243,12 @@ pub fn process_blocktree(
.unwrap();
// only process full slots in blocktree_processor, replay_stage
// handles any partials
// handles any partials
if next_meta.is_full() {
let next_bank = Arc::new(Bank::new_from_parent(
&bank,
&leader_schedule_cache
.slot_leader_at_else_compute(next_slot, &bank)
.slot_leader_at(next_slot, Some(&bank))
.unwrap(),
next_slot,
));
@ -245,7 +276,7 @@ pub fn process_blocktree(
}
let (banks, bank_forks_info): (Vec<_>, Vec<_>) = fork_info.into_iter().unzip();
let bank_forks = BankForks::new_from_banks(&banks);
let bank_forks = BankForks::new_from_banks(&banks, root);
info!(
"processing ledger...complete in {}ms, forks={}...",
duration_as_ms(&now.elapsed()),
@ -256,12 +287,15 @@ pub fn process_blocktree(
}
#[cfg(test)]
mod tests {
pub mod tests {
use super::*;
use crate::blocktree::create_new_tmp_ledger;
use crate::blocktree::tests::entries_to_blobs;
use crate::entry::{create_ticks, next_entry, Entry};
use solana_sdk::genesis_block::GenesisBlock;
use crate::entry::{create_ticks, next_entry, next_entry_mut, Entry};
use crate::genesis_utils::{
create_genesis_block, create_genesis_block_with_leader, GenesisBlockInfo,
};
use solana_runtime::epoch_schedule::EpochSchedule;
use solana_sdk::hash::Hash;
use solana_sdk::instruction::InstructionError;
use solana_sdk::pubkey::Pubkey;
@ -269,7 +303,7 @@ mod tests {
use solana_sdk::system_transaction;
use solana_sdk::transaction::TransactionError;
fn fill_blocktree_slot_with_ticks(
pub fn fill_blocktree_slot_with_ticks(
blocktree: &Blocktree,
ticks_per_slot: u64,
slot: u64,
@ -289,7 +323,7 @@ mod tests {
fn test_process_blocktree_with_incomplete_slot() {
solana_logger::setup();
let (genesis_block, _mint_keypair) = GenesisBlock::new(10_000);
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let ticks_per_slot = genesis_block.ticks_per_slot;
/*
@ -342,11 +376,85 @@ mod tests {
);
}
#[test]
fn test_process_blocktree_with_two_forks_and_squash() {
solana_logger::setup();
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let ticks_per_slot = genesis_block.ticks_per_slot;
// Create a new ledger with slot 0 full of ticks
let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_block);
debug!("ledger_path: {:?}", ledger_path);
let mut last_entry_hash = blockhash;
/*
Build a blocktree in the ledger with the following fork structure:
slot 0
|
slot 1
/ \
slot 2 |
/ |
slot 3 |
|
slot 4 <-- set_root(true)
*/
let blocktree =
Blocktree::open(&ledger_path).expect("Expected to successfully open database ledger");
// Fork 1, ending at slot 3
let last_slot1_entry_hash =
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, 1, 0, last_entry_hash);
last_entry_hash =
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, 2, 1, last_slot1_entry_hash);
let last_fork1_entry_hash =
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, 3, 2, last_entry_hash);
// Fork 2, ending at slot 4
let last_fork2_entry_hash =
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, 4, 1, last_slot1_entry_hash);
info!("last_fork1_entry.hash: {:?}", last_fork1_entry_hash);
info!("last_fork2_entry.hash: {:?}", last_fork2_entry_hash);
blocktree.set_root(4, 0).unwrap();
let (bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None).unwrap();
assert_eq!(bank_forks_info.len(), 1); // One fork, other one is ignored b/c not a descendant of the root
assert_eq!(
bank_forks_info[0],
BankForksInfo {
bank_slot: 4, // Fork 2's head is slot 4
entry_height: ticks_per_slot * 3,
}
);
assert!(&bank_forks[4]
.parents()
.iter()
.map(|bank| bank.slot())
.collect::<Vec<_>>()
.is_empty());
// Ensure bank_forks holds the right banks
for info in bank_forks_info {
assert_eq!(bank_forks[info.bank_slot].slot(), info.bank_slot);
assert!(bank_forks[info.bank_slot].is_frozen());
}
assert_eq!(bank_forks.root(), 4);
}
#[test]
fn test_process_blocktree_with_two_forks() {
solana_logger::setup();
let (genesis_block, _mint_keypair) = GenesisBlock::new(10_000);
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let ticks_per_slot = genesis_block.ticks_per_slot;
// Create a new ledger with slot 0 full of ticks
@ -386,8 +494,8 @@ mod tests {
info!("last_fork1_entry.hash: {:?}", last_fork1_entry_hash);
info!("last_fork2_entry.hash: {:?}", last_fork2_entry_hash);
blocktree.set_root(0).unwrap();
blocktree.set_root(1).unwrap();
blocktree.set_root(0, 0).unwrap();
blocktree.set_root(1, 0).unwrap();
let (bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None).unwrap();
@ -424,6 +532,8 @@ mod tests {
&[1]
);
assert_eq!(bank_forks.root(), 1);
// Ensure bank_forks holds the right banks
for info in bank_forks_info {
assert_eq!(bank_forks[info.bank_slot].slot(), info.bank_slot);
@ -431,6 +541,63 @@ mod tests {
}
}
#[test]
fn test_process_blocktree_epoch_boundary_root() {
solana_logger::setup();
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let ticks_per_slot = genesis_block.ticks_per_slot;
// Create a new ledger with slot 0 full of ticks
let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_block);
let mut last_entry_hash = blockhash;
let blocktree =
Blocktree::open(&ledger_path).expect("Expected to successfully open database ledger");
// Let last_slot be the number of slots in the first two epochs
let epoch_schedule = get_epoch_schedule(&genesis_block, None);
let last_slot = epoch_schedule.get_last_slot_in_epoch(1);
// Create a single chain of slots with all indexes in the range [0, last_slot + 1]
for i in 1..=last_slot + 1 {
last_entry_hash = fill_blocktree_slot_with_ticks(
&blocktree,
ticks_per_slot,
i,
i - 1,
last_entry_hash,
);
}
// Set a root on the last slot of the last confirmed epoch
blocktree.set_root(last_slot, 0).unwrap();
// Set a root on the next slot of the confrimed epoch
blocktree.set_root(last_slot + 1, last_slot).unwrap();
// Check that we can properly restart the ledger / leader scheduler doesn't fail
let (bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None).unwrap();
assert_eq!(bank_forks_info.len(), 1); // There is one fork
assert_eq!(
bank_forks_info[0],
BankForksInfo {
bank_slot: last_slot + 1, // Head is last_slot + 1
entry_height: ticks_per_slot * (last_slot + 2),
}
);
// The latest root should have purged all its parents
assert!(&bank_forks[last_slot + 1]
.parents()
.iter()
.map(|bank| bank.slot())
.collect::<Vec<_>>()
.is_empty());
}
#[test]
fn test_first_err() {
assert_eq!(first_err(&[Ok(())]), Ok(()));
@ -468,7 +635,11 @@ mod tests {
fn test_process_empty_entry_is_registered() {
solana_logger::setup();
let (genesis_block, mint_keypair) = GenesisBlock::new(2);
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(2);
let bank = Bank::new(&genesis_block);
let keypair = Keypair::new();
let slot_entries = create_ticks(genesis_block.ticks_per_slot - 1, genesis_block.hash());
@ -477,7 +648,6 @@ mod tests {
&keypair.pubkey(),
1,
slot_entries.last().unwrap().hash,
0,
);
// First, ensure the TX is rejected because of the unregistered last ID
@ -495,13 +665,19 @@ mod tests {
fn test_process_ledger_simple() {
solana_logger::setup();
let leader_pubkey = Pubkey::new_rand();
let (genesis_block, mint_keypair) = GenesisBlock::new_with_leader(100, &leader_pubkey, 50);
let mint = 100;
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block_with_leader(mint, &leader_pubkey, 50);
let (ledger_path, mut last_entry_hash) = create_new_tmp_ledger!(&genesis_block);
debug!("ledger_path: {:?}", ledger_path);
let deducted_from_mint = 3;
let mut entries = vec![];
let blockhash = genesis_block.hash();
for _ in 0..3 {
for _ in 0..deducted_from_mint {
// Transfer one token from the mint to a random account
let keypair = Keypair::new();
let tx = system_transaction::create_user_account(
@ -509,7 +685,6 @@ mod tests {
&keypair.pubkey(),
1,
blockhash,
0,
);
let entry = Entry::new(&last_entry_hash, 1, vec![tx]);
last_entry_hash = entry.hash;
@ -523,7 +698,6 @@ mod tests {
&keypair2.pubkey(),
42,
blockhash,
0,
);
let entry = Entry::new(&last_entry_hash, 1, vec![tx]);
last_entry_hash = entry.hash;
@ -543,6 +717,7 @@ mod tests {
process_blocktree(&genesis_block, &blocktree, None).unwrap();
assert_eq!(bank_forks_info.len(), 1);
assert_eq!(bank_forks.root(), 0);
assert_eq!(
bank_forks_info[0],
BankForksInfo {
@ -552,14 +727,19 @@ mod tests {
);
let bank = bank_forks[1].clone();
assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 50 - 3);
assert_eq!(
bank.get_balance(&mint_keypair.pubkey()),
mint - deducted_from_mint
);
assert_eq!(bank.tick_height(), 2 * genesis_block.ticks_per_slot - 1);
assert_eq!(bank.last_blockhash(), entries.last().unwrap().hash);
}
#[test]
fn test_process_ledger_with_one_tick_per_slot() {
let (mut genesis_block, _mint_keypair) = GenesisBlock::new(123);
let GenesisBlockInfo {
mut genesis_block, ..
} = create_genesis_block(123);
genesis_block.ticks_per_slot = 1;
let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_block);
@ -581,7 +761,7 @@ mod tests {
#[test]
fn test_process_entries_tick() {
let (genesis_block, _mint_keypair) = GenesisBlock::new(1000);
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(1000);
let bank = Bank::new(&genesis_block);
// ensure bank can process a tick
@ -593,7 +773,11 @@ mod tests {
#[test]
fn test_process_entries_2_entries_collision() {
let (genesis_block, mint_keypair) = GenesisBlock::new(1000);
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
@ -606,7 +790,6 @@ mod tests {
&keypair1.pubkey(),
2,
bank.last_blockhash(),
0,
);
let entry_1 = next_entry(&blockhash, 1, vec![tx]);
let tx = system_transaction::create_user_account(
@ -614,7 +797,6 @@ mod tests {
&keypair2.pubkey(),
2,
bank.last_blockhash(),
0,
);
let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]);
assert_eq!(process_entries(&bank, &[entry_1, entry_2]), Ok(()));
@ -625,7 +807,11 @@ mod tests {
#[test]
fn test_process_entries_2_txes_collision() {
let (genesis_block, mint_keypair) = GenesisBlock::new(1000);
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
@ -644,7 +830,6 @@ mod tests {
&mint_keypair.pubkey(),
1,
bank.last_blockhash(),
0,
)],
);
@ -657,14 +842,12 @@ mod tests {
&keypair3.pubkey(),
2,
bank.last_blockhash(),
0,
), // should be fine
system_transaction::create_user_account(
&keypair1,
&mint_keypair.pubkey(),
2,
bank.last_blockhash(),
0,
), // will collide
],
);
@ -681,7 +864,11 @@ mod tests {
#[test]
fn test_process_entries_2_txes_collision_and_error() {
let (genesis_block, mint_keypair) = GenesisBlock::new(1000);
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
@ -703,14 +890,12 @@ mod tests {
&mint_keypair.pubkey(),
1,
bank.last_blockhash(),
0,
),
system_transaction::transfer(
&keypair4,
&keypair4.pubkey(),
1,
Hash::default(), // Should cause a transaction failure with BlockhashNotFound
0,
),
],
);
@ -724,14 +909,12 @@ mod tests {
&keypair3.pubkey(),
2,
bank.last_blockhash(),
0,
), // should be fine
system_transaction::create_user_account(
&keypair1,
&mint_keypair.pubkey(),
2,
bank.last_blockhash(),
0,
), // will collide
],
);
@ -761,9 +944,109 @@ mod tests {
}
}
#[test]
fn test_process_entries_2nd_entry_collision_with_self_and_error() {
solana_logger::setup();
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
let keypair3 = Keypair::new();
// fund: put some money in each of 1 and 2
assert_matches!(bank.transfer(5, &mint_keypair, &keypair1.pubkey()), Ok(_));
assert_matches!(bank.transfer(4, &mint_keypair, &keypair2.pubkey()), Ok(_));
// 3 entries: first has a transfer, 2nd has a conflict with 1st, 3rd has a conflict with itself
let entry_1_to_mint = next_entry(
&bank.last_blockhash(),
1,
vec![system_transaction::transfer(
&keypair1,
&mint_keypair.pubkey(),
1,
bank.last_blockhash(),
)],
);
// should now be:
// keypair1=4
// keypair2=4
// keypair3=0
let entry_2_to_3_and_1_to_mint = next_entry(
&entry_1_to_mint.hash,
1,
vec![
system_transaction::create_user_account(
&keypair2,
&keypair3.pubkey(),
2,
bank.last_blockhash(),
), // should be fine
system_transaction::transfer(
&keypair1,
&mint_keypair.pubkey(),
2,
bank.last_blockhash(),
), // will collide with predecessor
],
);
// should now be:
// keypair1=2
// keypair2=2
// keypair3=2
let entry_conflict_itself = next_entry(
&entry_2_to_3_and_1_to_mint.hash,
1,
vec![
system_transaction::transfer(
&keypair1,
&keypair3.pubkey(),
1,
bank.last_blockhash(),
),
system_transaction::transfer(
&keypair1,
&keypair2.pubkey(),
1,
bank.last_blockhash(),
), // should be fine
],
);
// would now be:
// keypair1=0
// keypair2=3
// keypair3=3
assert!(process_entries(
&bank,
&[
entry_1_to_mint.clone(),
entry_2_to_3_and_1_to_mint.clone(),
entry_conflict_itself.clone()
]
)
.is_err());
// last entry should have been aborted before par_execute_entries
assert_eq!(bank.get_balance(&keypair1.pubkey()), 2);
assert_eq!(bank.get_balance(&keypair2.pubkey()), 2);
assert_eq!(bank.get_balance(&keypair3.pubkey()), 2);
}
#[test]
fn test_process_entries_2_entries_par() {
let (genesis_block, mint_keypair) = GenesisBlock::new(1000);
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
@ -776,7 +1059,6 @@ mod tests {
&keypair1.pubkey(),
1,
bank.last_blockhash(),
0,
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
let tx = system_transaction::create_user_account(
@ -784,7 +1066,6 @@ mod tests {
&keypair2.pubkey(),
1,
bank.last_blockhash(),
0,
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
@ -795,7 +1076,6 @@ mod tests {
&keypair3.pubkey(),
1,
bank.last_blockhash(),
0,
);
let entry_1 = next_entry(&blockhash, 1, vec![tx]);
let tx = system_transaction::create_user_account(
@ -803,7 +1083,6 @@ mod tests {
&keypair4.pubkey(),
1,
bank.last_blockhash(),
0,
);
let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]);
assert_eq!(process_entries(&bank, &[entry_1, entry_2]), Ok(()));
@ -814,7 +1093,11 @@ mod tests {
#[test]
fn test_process_entries_2_entries_tick() {
let (genesis_block, mint_keypair) = GenesisBlock::new(1000);
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
@ -827,7 +1110,6 @@ mod tests {
&keypair1.pubkey(),
1,
bank.last_blockhash(),
0,
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
let tx = system_transaction::create_user_account(
@ -835,7 +1117,6 @@ mod tests {
&keypair2.pubkey(),
1,
bank.last_blockhash(),
0,
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
@ -846,7 +1127,7 @@ mod tests {
// ensure bank can process 2 entries that do not have a common account and tick is registered
let tx =
system_transaction::create_user_account(&keypair2, &keypair3.pubkey(), 1, blockhash, 0);
system_transaction::create_user_account(&keypair2, &keypair3.pubkey(), 1, blockhash);
let entry_1 = next_entry(&blockhash, 1, vec![tx]);
let tick = next_entry(&entry_1.hash, 1, vec![]);
let tx = system_transaction::create_user_account(
@ -854,7 +1135,6 @@ mod tests {
&keypair4.pubkey(),
1,
bank.last_blockhash(),
0,
);
let entry_2 = next_entry(&tick.hash, 1, vec![tx]);
assert_eq!(
@ -870,7 +1150,6 @@ mod tests {
&keypair3.pubkey(),
1,
bank.last_blockhash(),
0,
);
let entry_3 = next_entry(&entry_2.hash, 1, vec![tx]);
assert_eq!(
@ -882,7 +1161,11 @@ mod tests {
#[test]
fn test_update_transaction_statuses() {
// Make sure instruction errors still update the signature cache
let (genesis_block, mint_keypair) = GenesisBlock::new(11_000);
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(11_000);
let bank = Bank::new(&genesis_block);
let pubkey = Pubkey::new_rand();
bank.transfer(1_000, &mint_keypair, &pubkey).unwrap();
@ -901,13 +1184,8 @@ mod tests {
);
// Make sure other errors don't update the signature cache
let tx = system_transaction::create_user_account(
&mint_keypair,
&pubkey,
1000,
Hash::default(),
0,
);
let tx =
system_transaction::create_user_account(&mint_keypair, &pubkey, 1000, Hash::default());
let signature = tx.signatures[0];
// Should fail with blockhash not found
@ -925,7 +1203,11 @@ mod tests {
#[test]
fn test_update_transaction_statuses_fail() {
let (genesis_block, mint_keypair) = GenesisBlock::new(11_000);
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(11_000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
@ -934,14 +1216,12 @@ mod tests {
&keypair1.pubkey(),
1,
bank.last_blockhash(),
0,
);
let fail_tx = system_transaction::create_user_account(
&mint_keypair,
&keypair2.pubkey(),
2,
bank.last_blockhash(),
0,
);
let entry_1_to_mint = next_entry(
@ -961,4 +1241,87 @@ mod tests {
// Should not see duplicate signature error
assert_eq!(bank.process_transaction(&fail_tx), Ok(()));
}
#[test]
#[ignore]
fn test_process_entries_stress() {
// this test throws lots of rayon threads at process_entries()
// finds bugs in very low-layer stuff
solana_logger::setup();
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1_000_000_000);
let mut bank = Bank::new(&genesis_block);
const NUM_TRANSFERS: usize = 100;
let keypairs: Vec<_> = (0..NUM_TRANSFERS * 2).map(|_| Keypair::new()).collect();
// give everybody one lamport
for keypair in &keypairs {
bank.transfer(1, &mint_keypair, &keypair.pubkey())
.expect("funding failed");
}
let mut i = 0;
let mut hash = bank.last_blockhash();
loop {
let entries: Vec<_> = (0..NUM_TRANSFERS)
.map(|i| {
next_entry_mut(
&mut hash,
0,
vec![system_transaction::transfer(
&keypairs[i],
&keypairs[i + NUM_TRANSFERS].pubkey(),
1,
bank.last_blockhash(),
)],
)
})
.collect();
info!("paying iteration {}", i);
process_entries(&bank, &entries).expect("paying failed");
let entries: Vec<_> = (0..NUM_TRANSFERS)
.map(|i| {
next_entry_mut(
&mut hash,
0,
vec![system_transaction::transfer(
&keypairs[i + NUM_TRANSFERS],
&keypairs[i].pubkey(),
1,
bank.last_blockhash(),
)],
)
})
.collect();
info!("refunding iteration {}", i);
process_entries(&bank, &entries).expect("refunding failed");
// advance to next block
process_entries(
&bank,
&(0..bank.ticks_per_slot())
.map(|_| next_entry_mut(&mut hash, 1, vec![]))
.collect::<Vec<_>>(),
)
.expect("process ticks failed");
i += 1;
bank = Bank::new_from_parent(&Arc::new(bank), &Pubkey::default(), i as u64);
bank.squash();
}
}
fn get_epoch_schedule(
genesis_block: &GenesisBlock,
account_paths: Option<String>,
) -> EpochSchedule {
let bank = Bank::new_with_paths(&genesis_block, account_paths);
bank.epoch_schedule().clone()
}
}

View File

@ -1,8 +1,8 @@
//! A stage to broadcast data from a leader node to validators
//!
use crate::blocktree::Blocktree;
use crate::cluster_info::{ClusterInfo, ClusterInfoError, NEIGHBORHOOD_SIZE};
use crate::entry::{EntrySender, EntrySlice};
use crate::cluster_info::{ClusterInfo, ClusterInfoError, DATA_PLANE_FANOUT};
use crate::entry::EntrySlice;
use crate::erasure::CodingGenerator;
use crate::packet::index_blobs_with_genesis;
use crate::poh_recorder::WorkingBankEntries;
@ -10,8 +10,10 @@ use crate::result::{Error, Result};
use crate::service::Service;
use crate::staking_utils;
use rayon::prelude::*;
use solana_metrics::counter::Counter;
use solana_metrics::{influxdb, submit};
use solana_metrics::{
datapoint, inc_new_counter_debug, inc_new_counter_error, inc_new_counter_info,
inc_new_counter_warn,
};
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::timing::duration_as_ms;
@ -27,9 +29,17 @@ pub enum BroadcastStageReturnType {
ChannelDisconnected,
}
#[derive(Default)]
struct BroadcastStats {
num_entries: Vec<usize>,
run_elapsed: Vec<u64>,
to_blobs_elapsed: Vec<u64>,
}
struct Broadcast {
id: Pubkey,
coding_generator: CodingGenerator,
stats: BroadcastStats,
}
impl Broadcast {
@ -39,14 +49,13 @@ impl Broadcast {
receiver: &Receiver<WorkingBankEntries>,
sock: &UdpSocket,
blocktree: &Arc<Blocktree>,
storage_entry_sender: &EntrySender,
genesis_blockhash: &Hash,
) -> Result<()> {
let timer = Duration::new(1, 0);
let (mut bank, entries) = receiver.recv_timeout(timer)?;
let mut max_tick_height = bank.max_tick_height();
let now = Instant::now();
let run_start = Instant::now();
let mut num_entries = entries.len();
let mut ventries = Vec::new();
let mut last_tick = entries.last().map(|v| v.1).unwrap_or(0);
@ -74,12 +83,14 @@ impl Broadcast {
}
let bank_epoch = bank.get_stakers_epoch(bank.slot());
let mut broadcast_table = cluster_info.read().unwrap().sorted_tvu_peers(
&staking_utils::delegated_stakes_at_epoch(&bank, bank_epoch).unwrap(),
);
inc_new_counter_info!("broadcast_service-num_peers", broadcast_table.len() + 1);
let mut broadcast_table = cluster_info
.read()
.unwrap()
.sorted_tvu_peers(staking_utils::staked_nodes_at_epoch(&bank, bank_epoch).as_ref());
inc_new_counter_warn!("broadcast_service-num_peers", broadcast_table.len() + 1);
// Layer 1, leader nodes are limited to the fanout size.
broadcast_table.truncate(NEIGHBORHOOD_SIZE);
broadcast_table.truncate(DATA_PLANE_FANOUT);
inc_new_counter_info!("broadcast_service-entries_received", num_entries);
@ -87,11 +98,9 @@ impl Broadcast {
let blobs: Vec<_> = ventries
.into_par_iter()
.map_with(storage_entry_sender.clone(), |s, p| {
.map(|p| {
let entries: Vec<_> = p.into_iter().map(|e| e.0).collect();
let blobs = entries.to_shared_blobs();
let _ignored = s.send(entries);
blobs
entries.to_shared_blobs()
})
.flatten()
.collect();
@ -128,33 +137,47 @@ impl Broadcast {
// Send out data
ClusterInfo::broadcast(&self.id, contains_last_tick, &broadcast_table, sock, &blobs)?;
inc_new_counter_info!("streamer-broadcast-sent", blobs.len());
inc_new_counter_debug!("streamer-broadcast-sent", blobs.len());
// send out erasures
ClusterInfo::broadcast(&self.id, false, &broadcast_table, sock, &coding)?;
let broadcast_elapsed = duration_as_ms(&broadcast_start.elapsed());
inc_new_counter_info!(
"broadcast_service-time_ms",
duration_as_ms(&now.elapsed()) as usize
);
info!(
"broadcast: {} entries, blob time {} broadcast time {}",
num_entries, to_blobs_elapsed, broadcast_elapsed
);
submit(
influxdb::Point::new("broadcast-service")
.add_field(
"transmit-index",
influxdb::Value::Integer(blob_index as i64),
)
.to_owned(),
self.update_broadcast_stats(
duration_as_ms(&broadcast_start.elapsed()),
duration_as_ms(&run_start.elapsed()),
num_entries,
to_blobs_elapsed,
blob_index,
);
Ok(())
}
fn update_broadcast_stats(
&mut self,
broadcast_elapsed: u64,
run_elapsed: u64,
num_entries: usize,
to_blobs_elapsed: u64,
blob_index: u64,
) {
inc_new_counter_info!("broadcast_service-time_ms", broadcast_elapsed as usize);
self.stats.num_entries.push(num_entries);
self.stats.to_blobs_elapsed.push(to_blobs_elapsed);
self.stats.run_elapsed.push(run_elapsed);
if self.stats.num_entries.len() >= 16 {
info!(
"broadcast: entries: {:?} blob times ms: {:?} broadcast times ms: {:?}",
self.stats.num_entries, self.stats.to_blobs_elapsed, self.stats.run_elapsed
);
self.stats.num_entries.clear();
self.stats.to_blobs_elapsed.clear();
self.stats.run_elapsed.clear();
}
datapoint!("broadcast-service", ("transmit-index", blob_index, i64));
}
}
// Implement a destructor for the BroadcastStage thread to signal it exited
@ -186,7 +209,6 @@ impl BroadcastStage {
cluster_info: &Arc<RwLock<ClusterInfo>>,
receiver: &Receiver<WorkingBankEntries>,
blocktree: &Arc<Blocktree>,
storage_entry_sender: EntrySender,
genesis_blockhash: &Hash,
) -> BroadcastStageReturnType {
let me = cluster_info.read().unwrap().my_data().clone();
@ -195,17 +217,13 @@ impl BroadcastStage {
let mut broadcast = Broadcast {
id: me.id,
coding_generator,
stats: BroadcastStats::default(),
};
loop {
if let Err(e) = broadcast.run(
&cluster_info,
receiver,
sock,
blocktree,
&storage_entry_sender,
genesis_blockhash,
) {
if let Err(e) =
broadcast.run(&cluster_info, receiver, sock, blocktree, genesis_blockhash)
{
match e {
Error::RecvTimeoutError(RecvTimeoutError::Disconnected) | Error::SendError => {
return BroadcastStageReturnType::ChannelDisconnected;
@ -213,7 +231,7 @@ impl BroadcastStage {
Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (),
Error::ClusterInfoError(ClusterInfoError::NoPeers) => (), // TODO: Why are the unit-tests throwing hundreds of these?
_ => {
inc_new_counter_info!("streamer-broadcaster-error", 1, 1);
inc_new_counter_error!("streamer-broadcaster-error", 1, 1);
error!("broadcaster error: {:?}", e);
}
}
@ -243,7 +261,6 @@ impl BroadcastStage {
receiver: Receiver<WorkingBankEntries>,
exit_sender: &Arc<AtomicBool>,
blocktree: &Arc<Blocktree>,
storage_entry_sender: EntrySender,
genesis_blockhash: &Hash,
) -> Self {
let blocktree = blocktree.clone();
@ -258,7 +275,6 @@ impl BroadcastStage {
&cluster_info,
&receiver,
&blocktree,
storage_entry_sender,
&genesis_blockhash,
)
})
@ -282,9 +298,9 @@ mod test {
use crate::blocktree::{get_tmp_ledger_path, Blocktree};
use crate::cluster_info::{ClusterInfo, Node};
use crate::entry::create_ticks;
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use crate::service::Service;
use solana_runtime::bank::Bank;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use std::sync::atomic::AtomicBool;
@ -320,9 +336,8 @@ mod test {
let cluster_info = Arc::new(RwLock::new(cluster_info));
let exit_sender = Arc::new(AtomicBool::new(false));
let (storage_sender, _receiver) = channel();
let (genesis_block, _) = GenesisBlock::new(10_000);
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let bank = Arc::new(Bank::new(&genesis_block));
// Start up the broadcast stage
@ -332,7 +347,6 @@ mod test {
entry_receiver,
&exit_sender,
&blocktree,
storage_sender,
&Hash::default(),
);

View File

@ -1,12 +1,11 @@
use crate::blocktree::Blocktree;
use solana_storage_api::SLOTS_PER_SEGMENT;
use std::fs::File;
use std::io;
use std::io::{BufWriter, Write};
use std::path::Path;
use std::sync::Arc;
use crate::storage_stage::ENTRIES_PER_SEGMENT;
pub const CHACHA_BLOCK_SIZE: usize = 64;
pub const CHACHA_KEY_SIZE: usize = 32;
@ -50,8 +49,7 @@ pub fn chacha_cbc_encrypt_ledger(
let mut entry = slice;
loop {
match blocktree.read_blobs_bytes(entry, ENTRIES_PER_SEGMENT - total_entries, &mut buffer, 0)
{
match blocktree.read_blobs_bytes(0, SLOTS_PER_SEGMENT - total_entries, &mut buffer, entry) {
Ok((num_entries, entry_len)) => {
debug!(
"chacha: encrypting slice: {} num_entries: {} entry_len: {}",
@ -124,7 +122,6 @@ mod tests {
&keypair.pubkey(),
1,
one,
0,
)],
)
})
@ -156,13 +153,11 @@ mod tests {
let mut hasher = Hasher::default();
hasher.hash(&buf[..size]);
use bs58;
// golden needs to be updated if blob stuff changes....
let golden = Hash::new(
&bs58::decode("5Pz5KQyNht2nqkJhVd8F9zTFxzoDvbQSzaxQbtCPiyCo")
.into_vec()
.unwrap(),
);
let golden: Hash = "9xb2Asf7UK5G8WqPwsvzo5xwLi4dixBSDiYKCtYRikA"
.parse()
.unwrap();
assert_eq!(hasher.result(), golden);
remove_file(out_path).unwrap();
}

View File

@ -7,12 +7,11 @@ use crate::sigverify::{
chacha_cbc_encrypt_many_sample, chacha_end_sha_state, chacha_init_sha_state,
};
use solana_sdk::hash::Hash;
use solana_storage_api::SLOTS_PER_SEGMENT;
use std::io;
use std::mem::size_of;
use std::sync::Arc;
use crate::storage_stage::ENTRIES_PER_SEGMENT;
// Encrypt a file with multiple starting IV states, determined by ivecs.len()
//
// Then sample each block at the offsets provided by samples argument with sha256
@ -47,8 +46,7 @@ pub fn chacha_cbc_encrypt_file_many_keys(
chacha_init_sha_state(int_sha_states.as_mut_ptr(), num_keys as u32);
}
loop {
match blocktree.read_blobs_bytes(entry, ENTRIES_PER_SEGMENT - total_entries, &mut buffer, 0)
{
match blocktree.read_blobs_bytes(entry, SLOTS_PER_SEGMENT - total_entries, &mut buffer, 0) {
Ok((num_entries, entry_len)) => {
debug!(
"chacha_cuda: encrypting segment: {} num_entries: {} entry_len: {}",
@ -78,9 +76,9 @@ pub fn chacha_cbc_encrypt_file_many_keys(
entry += num_entries;
debug!(
"total entries: {} entry: {} segment: {} entries_per_segment: {}",
total_entries, entry, segment, ENTRIES_PER_SEGMENT
total_entries, entry, segment, SLOTS_PER_SEGMENT
);
if (entry - segment) >= ENTRIES_PER_SEGMENT {
if (entry - segment) >= SLOTS_PER_SEGMENT {
break;
}
}

View File

@ -1,6 +1,6 @@
use solana_sdk::pubkey::Pubkey;
pub trait Cluster {
fn get_node_ids(&self) -> Vec<Pubkey>;
fn get_node_pubkeys(&self) -> Vec<Pubkey>;
fn restart_node(&mut self, pubkey: Pubkey);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,968 @@
use crate::blocktree::Blocktree;
use crate::cluster_info::ClusterInfo;
use crate::crds_value::EpochSlots;
use crate::result::Result;
use crate::service::Service;
use byteorder::{ByteOrder, LittleEndian};
use rand::seq::SliceRandom;
use rand::SeedableRng;
use rand_chacha::ChaChaRng;
use solana_metrics::datapoint;
use solana_runtime::epoch_schedule::EpochSchedule;
use solana_sdk::pubkey::Pubkey;
use std::cmp;
use std::collections::HashMap;
use std::mem;
use std::net::SocketAddr;
use std::net::UdpSocket;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::thread::{self, sleep, Builder, JoinHandle};
use std::time::Duration;
pub const REPAIRMEN_SLEEP_MILLIS: usize = 100;
pub const REPAIR_REDUNDANCY: usize = 1;
pub const NUM_BUFFER_SLOTS: usize = 50;
pub const GOSSIP_DELAY_SLOTS: usize = 2;
pub const NUM_SLOTS_PER_UPDATE: usize = 2;
// Represents the blobs that a repairman is responsible for repairing in specific slot. More
// specifically, a repairman is responsible for every blob in this slot with index
// `(start_index + step_size * i) % num_blobs_in_slot`, for all `0 <= i <= num_blobs_to_send - 1`
// in this slot.
struct BlobIndexesToRepairIterator {
start_index: usize,
num_blobs_to_send: usize,
step_size: usize,
num_blobs_in_slot: usize,
blobs_sent: usize,
}
impl BlobIndexesToRepairIterator {
fn new(
start_index: usize,
num_blobs_to_send: usize,
step_size: usize,
num_blobs_in_slot: usize,
) -> Self {
Self {
start_index,
num_blobs_to_send,
step_size,
num_blobs_in_slot,
blobs_sent: 0,
}
}
}
impl Iterator for BlobIndexesToRepairIterator {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.blobs_sent == self.num_blobs_to_send {
None
} else {
let blob_index = Some(
(self.start_index + self.step_size * self.blobs_sent) % self.num_blobs_in_slot,
);
self.blobs_sent += 1;
blob_index
}
}
}
pub struct ClusterInfoRepairListener {
thread_hdls: Vec<JoinHandle<()>>,
}
impl ClusterInfoRepairListener {
pub fn new(
blocktree: &Arc<Blocktree>,
exit: &Arc<AtomicBool>,
cluster_info: Arc<RwLock<ClusterInfo>>,
epoch_schedule: EpochSchedule,
) -> Self {
let exit = exit.clone();
let blocktree = blocktree.clone();
let thread = Builder::new()
.name("solana-cluster_info_repair_listener".to_string())
.spawn(move || {
// Maps a peer to
// 1) The latest timestamp of the EpochSlots gossip message at which a repair was
// sent to this peer
// 2) The latest root the peer gossiped
let mut peer_roots: HashMap<Pubkey, (u64, u64)> = HashMap::new();
let _ = Self::recv_loop(
&blocktree,
&mut peer_roots,
&exit,
&cluster_info,
&epoch_schedule,
);
})
.unwrap();
Self {
thread_hdls: vec![thread],
}
}
fn recv_loop(
blocktree: &Blocktree,
peer_roots: &mut HashMap<Pubkey, (u64, u64)>,
exit: &Arc<AtomicBool>,
cluster_info: &Arc<RwLock<ClusterInfo>>,
epoch_schedule: &EpochSchedule,
) -> Result<()> {
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
let my_pubkey = cluster_info.read().unwrap().id();
let mut my_gossiped_root = 0;
loop {
if exit.load(Ordering::Relaxed) {
return Ok(());
}
let peers = cluster_info.read().unwrap().gossip_peers();
let mut peers_needing_repairs: HashMap<Pubkey, EpochSlots> = HashMap::new();
// Iterate through all the known nodes in the network, looking for ones that
// need repairs
for peer in peers {
if let Some(repairee_epoch_slots) = Self::process_potential_repairee(
&my_pubkey,
&peer.id,
cluster_info,
peer_roots,
&mut my_gossiped_root,
) {
peers_needing_repairs.insert(peer.id, repairee_epoch_slots);
}
}
// After updating all the peers, send out repairs to those that need it
let _ = Self::serve_repairs(
&my_pubkey,
blocktree,
peer_roots,
&peers_needing_repairs,
&socket,
cluster_info,
&mut my_gossiped_root,
epoch_schedule,
);
sleep(Duration::from_millis(REPAIRMEN_SLEEP_MILLIS as u64));
}
}
fn process_potential_repairee(
my_pubkey: &Pubkey,
peer_pubkey: &Pubkey,
cluster_info: &Arc<RwLock<ClusterInfo>>,
peer_roots: &mut HashMap<Pubkey, (u64, u64)>,
my_gossiped_root: &mut u64,
) -> Option<EpochSlots> {
let last_cached_repair_ts = Self::get_last_ts(peer_pubkey, peer_roots);
let my_root = Self::read_my_gossiped_root(&my_pubkey, cluster_info, my_gossiped_root);
{
let r_cluster_info = cluster_info.read().unwrap();
// Update our local map with the updated peers' information
if let Some((peer_epoch_slots, updated_ts)) =
r_cluster_info.get_epoch_state_for_node(&peer_pubkey, last_cached_repair_ts)
{
let peer_entry = peer_roots.entry(*peer_pubkey).or_default();
let peer_root = cmp::max(peer_epoch_slots.root, peer_entry.1);
let mut result = None;
let last_repair_ts = {
// Following logic needs to be fast because it holds the lock
// preventing updates on gossip
if Self::should_repair_peer(my_root, peer_epoch_slots.root, NUM_BUFFER_SLOTS) {
// Clone out EpochSlots structure to avoid holding lock on gossip
result = Some(peer_epoch_slots.clone());
updated_ts
} else {
// No repairs were sent, don't need to update the timestamp
peer_entry.0
}
};
*peer_entry = (last_repair_ts, peer_root);
result
} else {
None
}
}
}
fn serve_repairs(
my_pubkey: &Pubkey,
blocktree: &Blocktree,
peer_roots: &HashMap<Pubkey, (u64, u64)>,
repairees: &HashMap<Pubkey, EpochSlots>,
socket: &UdpSocket,
cluster_info: &Arc<RwLock<ClusterInfo>>,
my_gossiped_root: &mut u64,
epoch_schedule: &EpochSchedule,
) -> Result<()> {
for (repairee_pubkey, repairee_epoch_slots) in repairees {
let repairee_root = repairee_epoch_slots.root;
let repairee_tvu = {
let r_cluster_info = cluster_info.read().unwrap();
let contact_info = r_cluster_info.get_contact_info_for_node(repairee_pubkey);
contact_info.map(|c| c.tvu)
};
if let Some(repairee_tvu) = repairee_tvu {
// For every repairee, get the set of repairmen who are responsible for
let mut eligible_repairmen = Self::find_eligible_repairmen(
my_pubkey,
repairee_root,
peer_roots,
NUM_BUFFER_SLOTS,
);
Self::shuffle_repairmen(
&mut eligible_repairmen,
repairee_pubkey,
repairee_epoch_slots.root,
);
let my_root =
Self::read_my_gossiped_root(my_pubkey, cluster_info, my_gossiped_root);
let _ = Self::serve_repairs_to_repairee(
my_pubkey,
my_root,
blocktree,
&repairee_epoch_slots,
&eligible_repairmen,
socket,
&repairee_tvu,
NUM_SLOTS_PER_UPDATE,
epoch_schedule,
);
}
}
Ok(())
}
fn serve_repairs_to_repairee(
my_pubkey: &Pubkey,
my_root: u64,
blocktree: &Blocktree,
repairee_epoch_slots: &EpochSlots,
eligible_repairmen: &[&Pubkey],
socket: &UdpSocket,
repairee_tvu: &SocketAddr,
num_slots_to_repair: usize,
epoch_schedule: &EpochSchedule,
) -> Result<()> {
let slot_iter = blocktree.rooted_slot_iterator(repairee_epoch_slots.root + 1);
if slot_iter.is_err() {
warn!("Root for repairee is on different fork OR replay_stage hasn't marked this slot as root yet");
return Ok(());
}
let slot_iter = slot_iter?;
let mut total_data_blobs_sent = 0;
let mut total_coding_blobs_sent = 0;
let mut num_slots_repaired = 0;
let max_confirmed_repairee_epoch =
epoch_schedule.get_stakers_epoch(repairee_epoch_slots.root);
let max_confirmed_repairee_slot =
epoch_schedule.get_last_slot_in_epoch(max_confirmed_repairee_epoch);
for (slot, slot_meta) in slot_iter {
if slot > my_root
|| num_slots_repaired >= num_slots_to_repair
|| slot > max_confirmed_repairee_slot
{
break;
}
if !repairee_epoch_slots.slots.contains(&slot) {
// Calculate the blob indexes this node is responsible for repairing. Note that
// because we are only repairing slots that are before our root, the slot.received
// should be equal to the actual total number of blobs in the slot. Optimistically
// this means that most repairmen should observe the same "total" number of blobs
// for a particular slot, and thus the calculation in
// calculate_my_repairman_index_for_slot() will divide responsibility evenly across
// the cluster
let num_blobs_in_slot = slot_meta.received as usize;
if let Some(my_repair_indexes) = Self::calculate_my_repairman_index_for_slot(
my_pubkey,
&eligible_repairmen,
num_blobs_in_slot,
REPAIR_REDUNDANCY,
) {
// Repairee is missing this slot, send them the blobs for this slot
for blob_index in my_repair_indexes {
// Loop over the sblob indexes and query the database for these blob that
// this node is reponsible for repairing. This should be faster than using
// a database iterator over the slots because by the time this node is
// sending the blobs in this slot for repair, we expect these slots
// to be full.
if let Some(blob_data) = blocktree
.get_data_blob_bytes(slot, blob_index as u64)
.expect("Failed to read data blob from blocktree")
{
socket.send_to(&blob_data[..], repairee_tvu)?;
total_data_blobs_sent += 1;
}
if let Some(coding_bytes) = blocktree
.get_coding_blob_bytes(slot, blob_index as u64)
.expect("Failed to read coding blob from blocktree")
{
socket.send_to(&coding_bytes[..], repairee_tvu)?;
total_coding_blobs_sent += 1;
}
}
num_slots_repaired += 1;
}
}
}
Self::report_repair_metrics(total_data_blobs_sent, total_coding_blobs_sent);
Ok(())
}
fn report_repair_metrics(total_data_blobs_sent: u64, total_coding_blobs_sent: u64) {
if total_data_blobs_sent > 0 || total_coding_blobs_sent > 0 {
datapoint!(
"repairman_activity",
("data_sent", total_data_blobs_sent, i64),
("coding_sent", total_coding_blobs_sent, i64)
);
}
}
fn shuffle_repairmen(
eligible_repairmen: &mut Vec<&Pubkey>,
repairee_pubkey: &Pubkey,
repairee_root: u64,
) {
// Make a seed from pubkey + repairee root
let mut seed = [0u8; mem::size_of::<Pubkey>()];
let repairee_pubkey_bytes = repairee_pubkey.as_ref();
seed[..repairee_pubkey_bytes.len()].copy_from_slice(repairee_pubkey_bytes);
LittleEndian::write_u64(&mut seed[0..], repairee_root);
// Deterministically shuffle the eligible repairmen based on the seed
let mut rng = ChaChaRng::from_seed(seed);
eligible_repairmen.shuffle(&mut rng);
}
// The calculation should partition the blobs in the slot across the repairmen in the cluster
// such that each blob in the slot is the responsibility of `repair_redundancy` or
// `repair_redundancy + 1` number of repairmen in the cluster.
fn calculate_my_repairman_index_for_slot(
my_pubkey: &Pubkey,
eligible_repairmen: &[&Pubkey],
num_blobs_in_slot: usize,
repair_redundancy: usize,
) -> Option<BlobIndexesToRepairIterator> {
let total_blobs = num_blobs_in_slot * repair_redundancy;
let total_repairmen_for_slot = cmp::min(total_blobs, eligible_repairmen.len());
let blobs_per_repairman = cmp::min(
(total_blobs + total_repairmen_for_slot - 1) / total_repairmen_for_slot,
num_blobs_in_slot,
);
// Calculate the indexes this node is responsible for
if let Some(my_position) = eligible_repairmen[..total_repairmen_for_slot]
.iter()
.position(|id| *id == my_pubkey)
{
let start_index = my_position % num_blobs_in_slot;
Some(BlobIndexesToRepairIterator::new(
start_index,
blobs_per_repairman,
total_repairmen_for_slot,
num_blobs_in_slot,
))
} else {
// If there are more repairmen than `total_blobs`, then some repairmen
// will not have any responsibility to repair this slot
None
}
}
fn find_eligible_repairmen<'a>(
my_pubkey: &'a Pubkey,
repairee_root: u64,
repairman_roots: &'a HashMap<Pubkey, (u64, u64)>,
num_buffer_slots: usize,
) -> Vec<&'a Pubkey> {
let mut repairmen: Vec<_> = repairman_roots
.iter()
.filter_map(|(repairman_pubkey, (_, repairman_root))| {
if Self::should_repair_peer(
*repairman_root,
repairee_root,
num_buffer_slots - GOSSIP_DELAY_SLOTS,
) {
Some(repairman_pubkey)
} else {
None
}
})
.collect();
repairmen.push(my_pubkey);
repairmen.sort();
repairmen
}
// Read my root out of gossip, and update the cached `old_root`
fn read_my_gossiped_root(
my_pubkey: &Pubkey,
cluster_info: &Arc<RwLock<ClusterInfo>>,
old_root: &mut u64,
) -> u64 {
let new_root = cluster_info
.read()
.unwrap()
.get_gossiped_root_for_node(&my_pubkey, None);
if let Some(new_root) = new_root {
*old_root = new_root;
new_root
} else {
*old_root
}
}
// Decide if a repairman with root == `repairman_root` should send repairs to a
// potential repairee with root == `repairee_root`
fn should_repair_peer(
repairman_root: u64,
repairee_root: u64,
num_buffer_slots: usize,
) -> bool {
// Check if this potential repairman's root is greater than the repairee root +
// num_buffer_slots
repairman_root > repairee_root + num_buffer_slots as u64
}
fn get_last_ts(pubkey: &Pubkey, peer_roots: &mut HashMap<Pubkey, (u64, u64)>) -> Option<u64> {
peer_roots.get(pubkey).map(|(last_ts, _)| *last_ts)
}
}
impl Service for ClusterInfoRepairListener {
type JoinReturnType = ();
fn join(self) -> thread::Result<()> {
for thread_hdl in self.thread_hdls {
thread_hdl.join()?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::blocktree::get_tmp_ledger_path;
use crate::blocktree::tests::make_many_slot_entries;
use crate::cluster_info::Node;
use crate::packet::{Blob, SharedBlob};
use crate::streamer;
use std::collections::BTreeSet;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::channel;
use std::sync::mpsc::Receiver;
use std::sync::Arc;
use std::thread::sleep;
use std::time::Duration;
struct MockRepairee {
id: Pubkey,
receiver: Receiver<Vec<SharedBlob>>,
tvu_address: SocketAddr,
repairee_exit: Arc<AtomicBool>,
repairee_receiver_thread_hdl: JoinHandle<()>,
}
impl MockRepairee {
pub fn new(
id: Pubkey,
receiver: Receiver<Vec<SharedBlob>>,
tvu_address: SocketAddr,
repairee_exit: Arc<AtomicBool>,
repairee_receiver_thread_hdl: JoinHandle<()>,
) -> Self {
Self {
id,
receiver,
tvu_address,
repairee_exit,
repairee_receiver_thread_hdl,
}
}
pub fn make_mock_repairee() -> Self {
let id = Pubkey::new_rand();
let (repairee_sender, repairee_receiver) = channel();
let repairee_socket = Arc::new(UdpSocket::bind("0.0.0.0:0").unwrap());
let repairee_tvu_addr = repairee_socket.local_addr().unwrap();
let repairee_exit = Arc::new(AtomicBool::new(false));
let repairee_receiver_thread_hdl =
streamer::blob_receiver(repairee_socket, &repairee_exit, repairee_sender);
Self::new(
id,
repairee_receiver,
repairee_tvu_addr,
repairee_exit,
repairee_receiver_thread_hdl,
)
}
pub fn close(self) -> Result<()> {
self.repairee_exit.store(true, Ordering::Relaxed);
self.repairee_receiver_thread_hdl.join()?;
Ok(())
}
}
#[test]
fn test_process_potential_repairee() {
// Set up node ids
let my_pubkey = Pubkey::new_rand();
let peer_pubkey = Pubkey::new_rand();
// Set up cluster_info
let cluster_info = Arc::new(RwLock::new(ClusterInfo::new_with_invalid_keypair(
Node::new_localhost().info,
)));
// Push a repairee's epoch slots into cluster info
let repairee_root = 0;
let repairee_slots = BTreeSet::new();
cluster_info.write().unwrap().push_epoch_slots(
peer_pubkey,
repairee_root,
repairee_slots.clone(),
);
// Set up locally cached information
let mut peer_roots = HashMap::new();
let mut my_gossiped_root = repairee_root;
// Root is not sufficiently far ahead, we shouldn't repair
assert!(ClusterInfoRepairListener::process_potential_repairee(
&my_pubkey,
&peer_pubkey,
&cluster_info,
&mut peer_roots,
&mut my_gossiped_root,
)
.is_none());
// Update the root to be sufficiently far ahead. A repair should now occur even if the
// object in gossip is not updated
my_gossiped_root = repairee_root + NUM_BUFFER_SLOTS as u64 + 1;
assert!(ClusterInfoRepairListener::process_potential_repairee(
&my_pubkey,
&peer_pubkey,
&cluster_info,
&mut peer_roots,
&mut my_gossiped_root,
)
.is_some());
// An repair was already sent, so if gossip is not updated, no repair should be sent again,
// even if our root moves forward
my_gossiped_root += 4;
assert!(ClusterInfoRepairListener::process_potential_repairee(
&my_pubkey,
&peer_pubkey,
&cluster_info,
&mut peer_roots,
&mut my_gossiped_root,
)
.is_none());
// Sleep to make sure the timestamp is updated in gossip. Update the gossiped EpochSlots.
// Now a repair should be sent again
sleep(Duration::from_millis(10));
cluster_info
.write()
.unwrap()
.push_epoch_slots(peer_pubkey, repairee_root, repairee_slots);
assert!(ClusterInfoRepairListener::process_potential_repairee(
&my_pubkey,
&peer_pubkey,
&cluster_info,
&mut peer_roots,
&mut my_gossiped_root,
)
.is_some());
}
#[test]
fn test_serve_repairs_to_repairee() {
let blocktree_path = get_tmp_ledger_path!();
let blocktree = Blocktree::open(&blocktree_path).unwrap();
let blobs_per_slot = 5;
let num_slots = 10;
assert_eq!(num_slots % 2, 0);
let (blobs, _) = make_many_slot_entries(0, num_slots, blobs_per_slot);
// Write slots in the range [0, num_slots] to blocktree
blocktree.insert_data_blobs(&blobs).unwrap();
// Write roots so that these slots will qualify to be sent by the repairman
blocktree.set_root(0, 0).unwrap();
blocktree.set_root(num_slots - 1, 0).unwrap();
// Set up my information
let my_pubkey = Pubkey::new_rand();
let my_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
// Set up a mock repairee with a socket listening for incoming repairs
let mock_repairee = MockRepairee::make_mock_repairee();
// Set up the repairee's EpochSlots, such that they are missing every odd indexed slot
// in the range (repairee_root, num_slots]
let repairee_root = 0;
let repairee_slots: BTreeSet<_> = (0..=num_slots).step_by(2).collect();
let repairee_epoch_slots =
EpochSlots::new(mock_repairee.id, repairee_root, repairee_slots, 1);
// Mock out some other repairmen such that each repairman is responsible for 1 blob in a slot
let num_repairmen = blobs_per_slot - 1;
let mut eligible_repairmen: Vec<_> =
(0..num_repairmen).map(|_| Pubkey::new_rand()).collect();
eligible_repairmen.push(my_pubkey);
let eligible_repairmen_refs: Vec<_> = eligible_repairmen.iter().collect();
// Have all the repairman send the repairs
let epoch_schedule = EpochSchedule::new(32, 16, false);
let num_missing_slots = num_slots / 2;
for repairman_pubkey in &eligible_repairmen {
ClusterInfoRepairListener::serve_repairs_to_repairee(
&repairman_pubkey,
num_slots - 1,
&blocktree,
&repairee_epoch_slots,
&eligible_repairmen_refs,
&my_socket,
&mock_repairee.tvu_address,
num_missing_slots as usize,
&epoch_schedule,
)
.unwrap();
}
let mut received_blobs: Vec<Arc<RwLock<Blob>>> = vec![];
// This repairee was missing exactly `num_slots / 2` slots, so we expect to get
// `(num_slots / 2) * blobs_per_slot * REPAIR_REDUNDANCY` blobs.
let num_expected_blobs = (num_slots / 2) * blobs_per_slot * REPAIR_REDUNDANCY as u64;
while (received_blobs.len() as u64) < num_expected_blobs {
received_blobs.extend(mock_repairee.receiver.recv().unwrap());
}
// Make sure no extra blobs get sent
sleep(Duration::from_millis(1000));
assert!(mock_repairee.receiver.try_recv().is_err());
assert_eq!(received_blobs.len() as u64, num_expected_blobs);
// Shutdown
mock_repairee.close().unwrap();
drop(blocktree);
Blocktree::destroy(&blocktree_path).expect("Expected successful database destruction");
}
#[test]
fn test_no_repair_past_confirmed_epoch() {
let blocktree_path = get_tmp_ledger_path!();
let blocktree = Blocktree::open(&blocktree_path).unwrap();
let stakers_slot_offset = 16;
let slots_per_epoch = stakers_slot_offset * 2;
let epoch_schedule = EpochSchedule::new(slots_per_epoch, stakers_slot_offset, false);
// Create blobs for first two epochs and write them to blocktree
let total_slots = slots_per_epoch * 2;
let (blobs, _) = make_many_slot_entries(0, total_slots, 1);
blocktree.insert_data_blobs(&blobs).unwrap();
// Write roots so that these slots will qualify to be sent by the repairman
blocktree.set_root(0, 0).unwrap();
blocktree.set_root(slots_per_epoch * 2 - 1, 0).unwrap();
// Set up my information
let my_pubkey = Pubkey::new_rand();
let my_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
// Set up a mock repairee with a socket listening for incoming repairs
let mock_repairee = MockRepairee::make_mock_repairee();
// Set up the repairee's EpochSlots, such that:
// 1) They are missing all of the second epoch, but have all of the first epoch.
// 2) The root only confirms epoch 1, so the leader for epoch 2 is unconfirmed.
//
// Thus, no repairmen should send any blobs to this repairee b/c this repairee
// already has all the slots for which they have a confirmed leader schedule
let repairee_root = 0;
let repairee_slots: BTreeSet<_> = (0..=slots_per_epoch).collect();
let repairee_epoch_slots =
EpochSlots::new(mock_repairee.id, repairee_root, repairee_slots.clone(), 1);
ClusterInfoRepairListener::serve_repairs_to_repairee(
&my_pubkey,
total_slots - 1,
&blocktree,
&repairee_epoch_slots,
&vec![&my_pubkey],
&my_socket,
&mock_repairee.tvu_address,
1 as usize,
&epoch_schedule,
)
.unwrap();
// Make sure no blobs get sent
sleep(Duration::from_millis(1000));
assert!(mock_repairee.receiver.try_recv().is_err());
// Set the root to stakers_slot_offset, now epoch 2 should be confirmed, so the repairee
// is now eligible to get slots from epoch 2:
let repairee_epoch_slots =
EpochSlots::new(mock_repairee.id, stakers_slot_offset, repairee_slots, 1);
ClusterInfoRepairListener::serve_repairs_to_repairee(
&my_pubkey,
total_slots - 1,
&blocktree,
&repairee_epoch_slots,
&vec![&my_pubkey],
&my_socket,
&mock_repairee.tvu_address,
1 as usize,
&epoch_schedule,
)
.unwrap();
// Make sure some blobs get sent this time
sleep(Duration::from_millis(1000));
assert!(mock_repairee.receiver.try_recv().is_ok());
// Shutdown
mock_repairee.close().unwrap();
drop(blocktree);
Blocktree::destroy(&blocktree_path).expect("Expected successful database destruction");
}
#[test]
fn test_shuffle_repairmen() {
let num_repairmen = 10;
let eligible_repairmen: Vec<_> = (0..num_repairmen).map(|_| Pubkey::new_rand()).collect();
let unshuffled_refs: Vec<_> = eligible_repairmen.iter().collect();
let mut expected_order = unshuffled_refs.clone();
// Find the expected shuffled order based on a fixed seed
ClusterInfoRepairListener::shuffle_repairmen(&mut expected_order, unshuffled_refs[0], 0);
for _ in 0..10 {
let mut copied = unshuffled_refs.clone();
ClusterInfoRepairListener::shuffle_repairmen(&mut copied, unshuffled_refs[0], 0);
// Make sure shuffling repairmen is deterministic every time
assert_eq!(copied, expected_order);
// Make sure shuffling actually changes the order of the keys
assert_ne!(copied, unshuffled_refs);
}
}
#[test]
fn test_calculate_my_repairman_index_for_slot() {
// Test when the number of blobs in the slot > number of repairmen
let num_repairmen = 10;
let num_blobs_in_slot = 42;
let repair_redundancy = 3;
run_calculate_my_repairman_index_for_slot(
num_repairmen,
num_blobs_in_slot,
repair_redundancy,
);
// Test when num_blobs_in_slot is a multiple of num_repairmen
let num_repairmen = 12;
let num_blobs_in_slot = 48;
let repair_redundancy = 3;
run_calculate_my_repairman_index_for_slot(
num_repairmen,
num_blobs_in_slot,
repair_redundancy,
);
// Test when num_repairmen and num_blobs_in_slot are relatively prime
let num_repairmen = 12;
let num_blobs_in_slot = 47;
let repair_redundancy = 12;
run_calculate_my_repairman_index_for_slot(
num_repairmen,
num_blobs_in_slot,
repair_redundancy,
);
// Test 1 repairman
let num_repairmen = 1;
let num_blobs_in_slot = 30;
let repair_redundancy = 3;
run_calculate_my_repairman_index_for_slot(
num_repairmen,
num_blobs_in_slot,
repair_redundancy,
);
// Test when repair_redundancy is 1, and num_blobs_in_slot does not evenly
// divide num_repairmen
let num_repairmen = 12;
let num_blobs_in_slot = 47;
let repair_redundancy = 1;
run_calculate_my_repairman_index_for_slot(
num_repairmen,
num_blobs_in_slot,
repair_redundancy,
);
// Test when the number of blobs in the slot <= number of repairmen
let num_repairmen = 10;
let num_blobs_in_slot = 10;
let repair_redundancy = 3;
run_calculate_my_repairman_index_for_slot(
num_repairmen,
num_blobs_in_slot,
repair_redundancy,
);
// Test when there are more repairmen than repair_redundancy * num_blobs_in_slot
let num_repairmen = 42;
let num_blobs_in_slot = 10;
let repair_redundancy = 3;
run_calculate_my_repairman_index_for_slot(
num_repairmen,
num_blobs_in_slot,
repair_redundancy,
);
}
#[test]
fn test_should_repair_peer() {
// If repairee is ahead of us, we don't repair
let repairman_root = 0;
let repairee_root = 5;
assert!(!ClusterInfoRepairListener::should_repair_peer(
repairman_root,
repairee_root,
0,
));
// If repairee is at the same place as us, we don't repair
let repairman_root = 5;
let repairee_root = 5;
assert!(!ClusterInfoRepairListener::should_repair_peer(
repairman_root,
repairee_root,
0,
));
// If repairee is behind with no buffer, we repair
let repairman_root = 15;
let repairee_root = 5;
assert!(ClusterInfoRepairListener::should_repair_peer(
repairman_root,
repairee_root,
0,
));
// If repairee is behind, but within the buffer, we don't repair
let repairman_root = 16;
let repairee_root = 5;
assert!(!ClusterInfoRepairListener::should_repair_peer(
repairman_root,
repairee_root,
11,
));
// If repairee is behind, but outside the buffer, we repair
let repairman_root = 16;
let repairee_root = 5;
assert!(ClusterInfoRepairListener::should_repair_peer(
repairman_root,
repairee_root,
10,
));
}
fn run_calculate_my_repairman_index_for_slot(
num_repairmen: usize,
num_blobs_in_slot: usize,
repair_redundancy: usize,
) {
let eligible_repairmen: Vec<_> = (0..num_repairmen).map(|_| Pubkey::new_rand()).collect();
let eligible_repairmen_ref: Vec<_> = eligible_repairmen.iter().collect();
let mut results = HashMap::new();
let mut none_results = 0;
for pk in &eligible_repairmen {
if let Some(my_repair_indexes) =
ClusterInfoRepairListener::calculate_my_repairman_index_for_slot(
pk,
&eligible_repairmen_ref[..],
num_blobs_in_slot,
repair_redundancy,
)
{
for blob_index in my_repair_indexes {
results
.entry(blob_index)
.and_modify(|e| *e += 1)
.or_insert(1);
}
} else {
// This repairman isn't responsible for repairing this slot
none_results += 1;
}
}
// Analyze the results:
// 1) If there are a sufficient number of repairmen, then each blob should be sent
// `repair_redundancy` OR `repair_redundancy + 1` times.
let num_expected_redundancy = cmp::min(num_repairmen, repair_redundancy);
for b in results.keys() {
assert!(
results[b] == num_expected_redundancy || results[b] == num_expected_redundancy + 1
);
}
// 2) The number of times each blob is sent should be evenly distributed
let max_times_blob_sent = results.values().min_by(|x, y| x.cmp(y)).unwrap();
let min_times_blob_sent = results.values().max_by(|x, y| x.cmp(y)).unwrap();
assert!(*max_times_blob_sent <= *min_times_blob_sent + 1);
// 3) There should only be repairmen who are not responsible for repairing this slot
// if we have more repairman than `num_blobs_in_slot * repair_redundancy`. In this case the
// first `num_blobs_in_slot * repair_redundancy` repairmen would send one blob, and the rest
// would not be responsible for sending any repairs
assert_eq!(
none_results,
num_repairmen.saturating_sub(num_blobs_in_slot * repair_redundancy)
);
}
}

View File

@ -4,7 +4,7 @@ use crate::result::Result;
use crate::service::Service;
use crate::sigverify_stage::VerifiedPackets;
use crate::{packet, sigverify};
use solana_metrics::counter::Counter;
use solana_metrics::inc_new_counter_debug;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex, RwLock};
@ -56,7 +56,7 @@ impl ClusterInfoVoteListener {
let (votes, new_ts) = cluster_info.read().unwrap().get_votes(last_ts);
if poh_recorder.lock().unwrap().bank().is_some() {
last_ts = new_ts;
inc_new_counter_info!("cluster_info_vote_listener-recv_count", votes.len());
inc_new_counter_debug!("cluster_info_vote_listener-recv_count", votes.len());
let msgs = packet::to_packets(&votes);
if !msgs.is_empty() {
let r = if sigverify_disabled {
@ -82,3 +82,41 @@ impl Service for ClusterInfoVoteListener {
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::locktower::MAX_RECENT_VOTES;
use crate::packet;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::transaction::Transaction;
use solana_vote_api::vote_instruction;
use solana_vote_api::vote_state::Vote;
#[test]
fn test_max_vote_tx_fits() {
solana_logger::setup();
let node_keypair = Keypair::new();
let vote_keypair = Keypair::new();
let votes = (0..MAX_RECENT_VOTES)
.map(|i| Vote::new(i as u64, Hash::default()))
.collect::<Vec<_>>();
let vote_ix = vote_instruction::vote(
&node_keypair.pubkey(),
&vote_keypair.pubkey(),
&vote_keypair.pubkey(),
votes,
);
let mut vote_tx = Transaction::new_unsigned_instructions(vec![vote_ix]);
vote_tx.partial_sign(&[&node_keypair], Hash::default());
vote_tx.partial_sign(&[&vote_keypair], Hash::default());
use bincode::serialized_size;
info!("max vote size {}", serialized_size(&vote_tx).unwrap());
let msgs = packet::to_packets(&[vote_tx]); // panics if won't fit
assert_eq!(msgs.len(), 1);
}
}

View File

@ -6,22 +6,24 @@ use crate::blocktree::Blocktree;
use crate::cluster_info::FULLNODE_PORT_RANGE;
use crate::contact_info::ContactInfo;
use crate::entry::{Entry, EntrySlice};
use crate::gossip_service::discover_nodes;
use crate::gossip_service::discover_cluster;
use crate::locktower::VOTE_THRESHOLD_DEPTH;
use crate::poh_service::PohServiceConfig;
use solana_client::thin_client::create_client;
use solana_runtime::epoch_schedule::MINIMUM_SLOT_LENGTH;
use solana_sdk::client::SyncClient;
use solana_sdk::hash::Hash;
use solana_sdk::poh_config::PohConfig;
use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
use solana_sdk::system_transaction;
use solana_sdk::timing::{
duration_as_ms, DEFAULT_TICKS_PER_SLOT, NUM_CONSECUTIVE_LEADER_SLOTS, NUM_TICKS_PER_SECOND,
duration_as_ms, DEFAULT_NUM_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT,
NUM_CONSECUTIVE_LEADER_SLOTS,
};
use solana_sdk::transport::TransportError;
use std::thread::sleep;
use std::time::Duration;
const SLOT_MILLIS: u64 = (DEFAULT_TICKS_PER_SLOT * 1000) / NUM_TICKS_PER_SECOND;
const DEFAULT_SLOT_MILLIS: u64 = (DEFAULT_TICKS_PER_SLOT * 1000) / DEFAULT_NUM_TICKS_PER_SECOND;
/// Spend and verify from every node in the network
pub fn spend_and_verify_all_nodes(
@ -29,7 +31,7 @@ pub fn spend_and_verify_all_nodes(
funding_keypair: &Keypair,
nodes: usize,
) {
let cluster_nodes = discover_nodes(&entry_point_info.gossip, nodes).unwrap();
let (cluster_nodes, _) = discover_cluster(&entry_point_info.gossip, nodes).unwrap();
assert!(cluster_nodes.len() >= nodes);
for ingress_node in &cluster_nodes {
let random_keypair = Keypair::new();
@ -38,13 +40,9 @@ pub fn spend_and_verify_all_nodes(
.poll_get_balance(&funding_keypair.pubkey())
.expect("balance in source");
assert!(bal > 0);
let mut transaction = system_transaction::transfer(
&funding_keypair,
&random_keypair.pubkey(),
1,
client.get_recent_blockhash().unwrap(),
0,
);
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
let mut transaction =
system_transaction::transfer(&funding_keypair, &random_keypair.pubkey(), 1, blockhash);
let confs = VOTE_THRESHOLD_DEPTH + 1;
let sig = client
.retry_transfer_until_confirmed(&funding_keypair, &mut transaction, 5, confs)
@ -64,13 +62,9 @@ pub fn send_many_transactions(node: &ContactInfo, funding_keypair: &Keypair, num
.poll_get_balance(&funding_keypair.pubkey())
.expect("balance in source");
assert!(bal > 0);
let mut transaction = system_transaction::transfer(
&funding_keypair,
&random_keypair.pubkey(),
1,
client.get_recent_blockhash().unwrap(),
0,
);
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
let mut transaction =
system_transaction::transfer(&funding_keypair, &random_keypair.pubkey(), 1, blockhash);
client
.retry_transfer(&funding_keypair, &mut transaction, 5)
.unwrap();
@ -78,13 +72,13 @@ pub fn send_many_transactions(node: &ContactInfo, funding_keypair: &Keypair, num
}
pub fn fullnode_exit(entry_point_info: &ContactInfo, nodes: usize) {
let cluster_nodes = discover_nodes(&entry_point_info.gossip, nodes).unwrap();
let (cluster_nodes, _) = discover_cluster(&entry_point_info.gossip, nodes).unwrap();
assert!(cluster_nodes.len() >= nodes);
for node in &cluster_nodes {
let client = create_client(node.client_facing_addr(), FULLNODE_PORT_RANGE);
assert!(client.fullnode_exit().unwrap());
}
sleep(Duration::from_millis(SLOT_MILLIS));
sleep(Duration::from_millis(DEFAULT_SLOT_MILLIS));
for node in &cluster_nodes {
let client = create_client(node.client_facing_addr(), FULLNODE_PORT_RANGE);
assert!(client.fullnode_exit().is_err());
@ -126,42 +120,45 @@ pub fn verify_ledger_ticks(ledger_path: &str, ticks_per_slot: usize) {
pub fn sleep_n_epochs(
num_epochs: f64,
config: &PohServiceConfig,
config: &PohConfig,
ticks_per_slot: u64,
slots_per_epoch: u64,
) {
let num_ticks_per_second = {
match config {
PohServiceConfig::Sleep(d) => (1000 / duration_as_ms(d)) as f64,
_ => panic!("Unsuppported tick config for testing"),
}
};
let num_ticks_per_second = (1000 / duration_as_ms(&config.target_tick_duration)) as f64;
let num_ticks_to_sleep = num_epochs * ticks_per_slot as f64 * slots_per_epoch as f64;
sleep(Duration::from_secs(
((num_ticks_to_sleep + num_ticks_per_second - 1.0) / num_ticks_per_second) as u64,
));
let secs = ((num_ticks_to_sleep + num_ticks_per_second - 1.0) / num_ticks_per_second) as u64;
warn!("sleep_n_epochs: {} seconds", secs);
sleep(Duration::from_secs(secs));
}
pub fn kill_entry_and_spend_and_verify_rest(
entry_point_info: &ContactInfo,
funding_keypair: &Keypair,
nodes: usize,
slot_millis: u64,
) {
solana_logger::setup();
let cluster_nodes = discover_nodes(&entry_point_info.gossip, nodes).unwrap();
let (cluster_nodes, _) = discover_cluster(&entry_point_info.gossip, nodes).unwrap();
assert!(cluster_nodes.len() >= nodes);
let client = create_client(entry_point_info.client_facing_addr(), FULLNODE_PORT_RANGE);
let first_two_epoch_slots = MINIMUM_SLOT_LENGTH * 3;
for ingress_node in &cluster_nodes {
client
.poll_get_balance(&ingress_node.id)
.unwrap_or_else(|err| panic!("Node {} has no balance: {}", ingress_node.id, err));
}
info!("sleeping for 2 leader fortnights");
sleep(Duration::from_millis(
SLOT_MILLIS * NUM_CONSECUTIVE_LEADER_SLOTS * 2,
slot_millis * first_two_epoch_slots as u64,
));
info!("done sleeping for 2 fortnights");
info!("killing entry point");
info!("done sleeping for first 2 warmup epochs");
info!("killing entry point: {}", entry_point_info.id);
assert!(client.fullnode_exit().unwrap());
info!("sleeping for 2 leader fortnights");
info!("sleeping for some time");
sleep(Duration::from_millis(
SLOT_MILLIS * NUM_CONSECUTIVE_LEADER_SLOTS,
slot_millis * NUM_CONSECUTIVE_LEADER_SLOTS,
));
info!("done sleeping for 2 fortnights");
for ingress_node in &cluster_nodes {
@ -170,10 +167,10 @@ pub fn kill_entry_and_spend_and_verify_rest(
}
let client = create_client(ingress_node.client_facing_addr(), FULLNODE_PORT_RANGE);
let bal = client
let balance = client
.poll_get_balance(&funding_keypair.pubkey())
.expect("balance in source");
assert!(bal > 0);
assert_ne!(balance, 0);
let mut result = Ok(());
let mut retries = 0;
@ -184,12 +181,12 @@ pub fn kill_entry_and_spend_and_verify_rest(
}
let random_keypair = Keypair::new();
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
let mut transaction = system_transaction::transfer(
&funding_keypair,
&random_keypair.pubkey(),
1,
client.get_recent_blockhash().unwrap(),
0,
blockhash,
);
let confs = VOTE_THRESHOLD_DEPTH + 1;

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