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 set -x
rsync -a --delete --link-dest="$PWD" target "$d" rsync -a --delete --link-dest="$PWD" target "$d"
du -hs "$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 set -x
d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL" d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL"
MAX_CACHE_SIZE=18 # gigabytes
if [[ -d $d ]]; then if [[ -d $d ]]; then
du -hs "$d" du -hs "$d"
read -r cacheSizeInGB _ < <(du -s --block-size=1000000000 "$d") read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d")
if [[ $cacheSizeInGB -gt 10 ]]; then echo "--- ${cacheSizeInGB}GB: $d"
echo "$d has gotten too large, removing it" if [[ $cacheSizeInGB -gt $MAX_CACHE_SIZE ]]; then
echo "--- $d is too large, removing it"
rm -rf "$d" rm -rf "$d"
fi fi
else
echo "--- $d not present"
fi fi
mkdir -p "$d"/target mkdir -p "$d"/target

4
.gitignore vendored
View File

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

1132
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -61,7 +61,7 @@ There are three release channels that map to branches as follows:
## Release Steps ## Release Steps
### Changing channels ### Advance the Channels
#### Create the new branch #### Create the new branch
1. Pick your branch point for release on master. 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 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". "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. 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`. release should be `<branchname>.X-rc.0`.
1. Verify release automation: 1. Verify release automation:
1. [Crates.io](https://crates.io/crates/solana) should have an updated Solana version. 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 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 semantic version (e.g. 0.9.0 -> 0.9.1) by running
`./scripts/increment-cargo-version.sh patch`, then rebuild with `cargo `./scripts/increment-cargo-version.sh patch`, then rebuild with `cargo
build` to cause a refresh of `Cargo.lock`. build` to cause a refresh of `Cargo.lock`.
1. Push your Cargo.toml change and the autogenerated Cargo.lock changes to the 1. Push your Cargo.toml change and the autogenerated Cargo.lock changes to the
release branch. 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>"] authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018" edition = "2018"
name = "solana-bench-exchange" name = "solana-bench-exchange"
version = "0.14.0" version = "0.15.0"
repository = "https://github.com/solana-labs/solana" repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0" license = "Apache-2.0"
homepage = "https://solana.com/" homepage = "https://solana.com/"
@ -10,28 +10,29 @@ homepage = "https://solana.com/"
[dependencies] [dependencies]
bs58 = "0.2.0" bs58 = "0.2.0"
clap = "2.32.0" clap = "2.32.0"
bincode = "1.1.2" bincode = "1.1.4"
env_logger = "0.6.0" env_logger = "0.6.0"
itertools = "0.8.0" itertools = "0.8.0"
log = "0.4.6" log = "0.4.6"
num-traits = "0.2" num-traits = "0.2"
num-derive = "0.2" num-derive = "0.2"
rand = "0.6.5"
rayon = "1.0.3" rayon = "1.0.3"
serde = "1.0.87" serde = "1.0.91"
serde_derive = "1.0.87" serde_derive = "1.0.91"
serde_json = "1.0.38" serde_json = "1.0.38"
# solana-runtime = { path = "../solana/runtime"} # solana-runtime = { path = "../solana/runtime"}
solana = { path = "../core", version = "0.14.0" } solana = { path = "../core", version = "0.15.0" }
solana-client = { path = "../client", version = "0.14.0" } solana-client = { path = "../client", version = "0.15.0" }
solana-drone = { path = "../drone", version = "0.14.0" } solana-drone = { path = "../drone", version = "0.15.0" }
solana-exchange-api = { path = "../programs/exchange_api", version = "0.14.0" } solana-exchange-api = { path = "../programs/exchange_api", version = "0.15.0" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.14.0" } solana-exchange-program = { path = "../programs/exchange_program", version = "0.15.0" }
solana-logger = { path = "../logger", version = "0.14.0" } solana-logger = { path = "../logger", version = "0.15.0" }
solana-metrics = { path = "../metrics", version = "0.14.0" } solana-metrics = { path = "../metrics", version = "0.15.0" }
solana-netutil = { path = "../netutil", version = "0.14.0" } solana-netutil = { path = "../netutil", version = "0.15.0" }
solana-runtime = { path = "../runtime", version = "0.14.0" } solana-runtime = { path = "../runtime", version = "0.15.0" }
solana-sdk = { path = "../sdk", version = "0.14.0" } solana-sdk = { path = "../sdk", version = "0.15.0" }
ws = "0.8.0" ws = "0.8.1"
untrusted = "0.6.2" untrusted = "0.6.2"
[features] [features]

View File

@ -23,7 +23,7 @@ demo demonstrates one way to host an exchange on the Solana blockchain by
emulating a currency exchange. emulating a currency exchange.
The assets are virtual tokens held by investors who may post trade requests to 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. matching trade orders. All the transactions can execute concurrently.
## Premise ## Premise
@ -75,7 +75,7 @@ matching trade orders. All the transactions can execute concurrently.
contain the same information as the trade request. contain the same information as the trade request.
- Price spread - Price spread
- The difference between the two matching trade orders. The spread is the - 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 - Swap requirements
- Policies that result in a successful trade swap. - Policies that result in a successful trade swap.
- Swap request - 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 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 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 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. account. All trade swaps are recorded in a new account for posterity.
- Investor - Investor
- Individual investors who hold a number of tokens and wish to trade them on - 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 accounts containing tokens and/or trade requests. Investors post
transactions to the exchange in order to request tokens and post or cancel transactions to the exchange in order to request tokens and post or cancel
trade requests. trade requests.
- Broker - Swapper
- An agent who facilitates trading between investors. Brokers operate as - An agent who facilitates trading between investors. Swappers operate as
Solana thin clients who monitor all the trade orders looking for a trade 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. match. Once found, the Swapper issues a swap request to the exchange.
Brokers are the engine of the exchange and are rewarded for their efforts by Swappers are the engine of the exchange and are rewarded for their efforts by
accumulating the price spreads of the swaps they initiate. Brokers also accumulating the price spreads of the swaps they initiate. Swappers also
provide current bid/ask price and OHLCV (Open, High, Low, Close, Volume) provide current bid/ask price and OHLCV (Open, High, Low, Close, Volume)
information on demand via a public network port. information on demand via a public network port.
- Transaction fees - Transaction fees
- Solana transaction fees are paid for by the transaction submitters who are - Solana transaction fees are paid for by the transaction submitters who are
the Investors and Brokers. the Investors and Swappers.
## Exchange startup ## Exchange startup
The exchange is up and running when it reaches a state where it can take 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: following must occur in order:
- Start the Solana blockchain - Start the Solana blockchain
- Start the broker thin-client - Start the Swapper thin-client
- The broker subscribes to change notifications for all the accounts owned by - 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 the exchange program id. The subscription is managed via Solana's JSON RPC
interface. 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 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, 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 and the Swapper 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 this is for the Swapper to read the current state of all accounts looking for all
open trade orders.--> open trade orders.-->
Investors will initially query the exchange to discover their current balance 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 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. request accounts to hold the tokens they earn by initiating trade swaps.
```rust ```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 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. would provide another way to exchange a 3rd party asset into tokens.
@ -269,10 +269,10 @@ pub enum ExchangeInstruction {
## Trade swaps ## 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 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 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 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 in either account will keep the trade order valid for further swap requests in
the future. the future.
@ -310,14 +310,14 @@ whole for clarity.
| 5 | 1 T AB 2 10 | 2 F AB 1 5 | | 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 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. Swapper'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. 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, To: Investor 1 trades 2 A tokens to 8 B tokens
- Row 1, From: Investor 2 trades 2 A tokens from 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: 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 | | 3 | 1 T AB 2 8 | 2 F AB 3 6 |
| 4 | 1 T AB 2 10 | 2 F AB 1 5 | | 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, To: Investor 1 trades 1 A token to 4 B tokens
- Row 1, From: Investor 2 trades 1 A token from 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: 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 | | 3 | 1 T AB 2 10 | 2 F AB 3 6 |
| 4 | | 2 F AB 1 5 | | 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, To: Investor 1 trades 1 A token to 6 B tokens
- Row 1, From: Investor 2 trades 1 A token from 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: 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 | | 2 | 1 T AB 2 8 | 2 F AB 3 5 |
| 3 | 1 T AB 2 10 | 2 F AB 1 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, To: Investor 1 trades 2 A token to 12 B tokens
- Row 1, From: Investor 2 trades 2 A token from 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: Table becomes:
@ -383,7 +383,7 @@ pub enum ExchangeInstruction {
/// key 3 - `From` trade order /// key 3 - `From` trade order
/// key 4 - Token account associated with the To Trade /// key 4 - Token account associated with the To Trade
/// key 5 - Token account associated with From 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, SwapRequest,
} }
@ -442,14 +442,14 @@ pub enum ExchangeInstruction {
/// key 3 - `From` trade order /// key 3 - `From` trade order
/// key 4 - Token account associated with the To Trade /// key 4 - Token account associated with the To Trade
/// key 5 - Token account associated with From 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, SwapRequest,
} }
``` ```
## Quotes and OHLCV ## 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 also provide OHLCV based on some time window. The details of how the bid/ask
price quotes are calculated are yet to be decided. price quotes are calculated are yet to be decided.

View File

@ -3,23 +3,21 @@
use crate::order_book::*; use crate::order_book::*;
use itertools::izip; use itertools::izip;
use log::*; use log::*;
use rand::{thread_rng, Rng};
use rayon::prelude::*; use rayon::prelude::*;
use solana::cluster_info::FULLNODE_PORT_RANGE;
use solana::contact_info::ContactInfo;
use solana::gen_keys::GenKeys; use solana::gen_keys::GenKeys;
use solana_client::thin_client::create_client; use solana_client::perf_utils::{sample_txs, SampleStats};
use solana_client::thin_client::ThinClient;
use solana_drone::drone::request_airdrop_transaction; use solana_drone::drone::request_airdrop_transaction;
use solana_exchange_api::exchange_instruction; use solana_exchange_api::exchange_instruction;
use solana_exchange_api::exchange_state::*; use solana_exchange_api::exchange_state::*;
use solana_exchange_api::id; use solana_exchange_api::id;
use solana_metrics::influxdb; use solana_metrics::datapoint_info;
use solana_sdk::client::Client; use solana_sdk::client::Client;
use solana_sdk::client::SyncClient; use solana_sdk::client::SyncClient;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_instruction; 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 solana_sdk::transaction::Transaction;
use std::cmp; use std::cmp;
use std::collections::VecDeque; 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) pub fn do_bench_exchange<T>(clients: Vec<T>, config: Config)
where where
T: 'static + Client + Send + Sync, T: 'static + Client + Send + Sync,
@ -91,6 +79,18 @@ where
chunk_size, chunk_size,
account_groups, account_groups,
} = config; } = 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 accounts_in_groups = batch_size * account_groups;
let exit_signal = Arc::new(AtomicBool::new(false)); let exit_signal = Arc::new(AtomicBool::new(false));
let clients: Vec<_> = clients.into_iter().map(Arc::new).collect(); let clients: Vec<_> = clients.into_iter().map(Arc::new).collect();
@ -168,6 +168,7 @@ where
&shared_txs, &shared_txs,
&swapper_signers, &swapper_signers,
&profit_pubkeys, &profit_pubkeys,
transfer_delay,
batch_size, batch_size,
chunk_size, chunk_size,
account_groups, 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>( fn do_tx_transfers<T>(
exit_signal: &Arc<AtomicBool>, exit_signal: &Arc<AtomicBool>,
shared_txs: &SharedTransactions, shared_txs: &SharedTransactions,
@ -301,7 +246,6 @@ fn do_tx_transfers<T>(
) where ) where
T: Client, T: Client,
{ {
let mut stats = Stats::default();
loop { loop {
let txs; let txs;
{ {
@ -318,48 +262,18 @@ fn do_tx_transfers<T>(
let duration = now.elapsed(); let duration = now.elapsed();
total_txs_sent_count.fetch_add(n, Ordering::Relaxed); total_txs_sent_count.fetch_add(n, Ordering::Relaxed);
stats.total += n as u64; datapoint_info!(
stats.sent_ns += duration_as_ns(&duration); "bench-exchange-do_tx_transfers",
let rate = n as f32 / duration_as_s(&duration); ("duration", duration_as_ms(&duration), i64),
if rate > stats.sent_peak_rate { ("count", n, i64)
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(),
); );
} }
if exit_signal.load(Ordering::Relaxed) { 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; 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 { struct TradeInfo {
trade_account: Pubkey, trade_account: Pubkey,
order_info: TradeOrderInfo, order_info: TradeOrderInfo,
@ -371,6 +285,7 @@ fn swapper<T>(
shared_txs: &SharedTransactions, shared_txs: &SharedTransactions,
signers: &[Arc<Keypair>], signers: &[Arc<Keypair>],
profit_pubkeys: &[Pubkey], profit_pubkeys: &[Pubkey],
transfer_delay: u64,
batch_size: usize, batch_size: usize,
chunk_size: usize, chunk_size: usize,
account_groups: usize, account_groups: usize,
@ -378,28 +293,57 @@ fn swapper<T>(
) where ) where
T: Client, T: Client,
{ {
let mut stats = Stats::default();
let mut order_book = OrderBook::default(); let mut order_book = OrderBook::default();
let mut account_group: usize = 0; 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 { 'outer: loop {
if let Ok(trade_infos) = receiver.try_recv() { if let Ok(trade_infos) = receiver.try_recv() {
let mut tries = 0; let mut tries = 0;
let mut trade_index = 0;
while client while client
.get_balance(&trade_infos[0].trade_account) .get_balance(&trade_infos[trade_index].trade_account)
.unwrap_or(0) .unwrap_or(0)
== 0 == 0
{ {
tries += 1; tries += 1;
if tries > 300 { if tries >= max_tries {
if exit_signal.load(Ordering::Relaxed) { if exit_signal.load(Ordering::Relaxed) {
break 'outer; 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; continue 'outer;
} }
debug!("{} waiting for trades batch to clear", tries); 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| { trade_infos.iter().for_each(|info| {
order_book order_book
@ -414,9 +358,6 @@ fn swapper<T>(
} }
} }
let swaps_size = swaps.len(); let swaps_size = swaps.len();
stats.total += swaps_size as u64;
let now = Instant::now();
let mut to_swap = vec![]; let mut to_swap = vec![];
let start = account_group * swaps_size as usize; let start = account_group * swaps_size as usize;
@ -429,17 +370,8 @@ fn swapper<T>(
to_swap.push((signer, swap, profit)); to_swap.push((signer, swap, profit));
} }
account_group = (account_group + 1) % account_groups as usize; 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, _fee_calculator) = client
let blockhash = client
.get_recent_blockhash() .get_recent_blockhash()
.expect("Failed to get blockhash"); .expect("Failed to get blockhash");
let to_swap_txs: Vec<_> = to_swap let to_swap_txs: Vec<_> = to_swap
@ -459,21 +391,25 @@ fn swapper<T>(
) )
}) })
.collect(); .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( txs += to_swap_txs.len() as u64;
influxdb::Point::new("bench-exchange") total_txs += to_swap_txs.len() as u64;
.add_tag("op", influxdb::Value::String("swaps".to_string())) total_elapsed = start_time.elapsed();
.add_field("count", influxdb::Value::Integer(to_swap_txs.len() as i64)) let duration = now.elapsed();
.to_owned(), 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(); 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()); 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) { if exit_signal.load(Ordering::Relaxed) {
@ -489,18 +427,9 @@ fn swapper<T>(
} }
} }
info!( info!(
"{} Swaps, batch size {}, chunk size {}", "Swapper sent {} at {:9.2} TPS",
stats.total, batch_size, chunk_size total_txs,
); total_txs as f32 / duration_as_s(&total_elapsed)
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
); );
assert_eq!( assert_eq!(
order_book.get_num_outstanding().0 + order_book.get_num_outstanding().1, order_book.get_num_outstanding().0 + order_book.get_num_outstanding().1,
@ -515,7 +444,7 @@ fn trader<T>(
shared_txs: &SharedTransactions, shared_txs: &SharedTransactions,
signers: &[Arc<Keypair>], signers: &[Arc<Keypair>],
srcs: &[Pubkey], srcs: &[Pubkey],
delay: u64, transfer_delay: u64,
batch_size: usize, batch_size: usize,
chunk_size: usize, chunk_size: usize,
account_groups: usize, account_groups: usize,
@ -523,16 +452,19 @@ fn trader<T>(
) where ) where
T: Client, T: Client,
{ {
let mut stats = Stats::default();
// TODO Hard coded for now // TODO Hard coded for now
let pair = TokenPair::AB; let pair = TokenPair::AB;
let tokens = 1; let tokens = 1;
let price = 1000; let price = 1000;
let mut account_group: usize = 0; 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 { loop {
let now = Instant::now();
let trade_keys = generate_keypairs(batch_size as u64); let trade_keys = generate_keypairs(batch_size as u64);
let mut trades = vec![]; let mut trades = vec![];
@ -566,20 +498,12 @@ fn trader<T>(
trades.push((signer, trade.pubkey(), direction, src)); trades.push((signer, trade.pubkey(), direction, src));
} }
account_group = (account_group + 1) % account_groups as usize; 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() .get_recent_blockhash()
.expect("Failed to get blockhash"); .expect("Failed to get blockhash");
trades.chunks(chunk_size).for_each(|chunk| { trades.chunks(chunk_size).for_each(|chunk| {
let now = Instant::now();
let trades_txs: Vec<_> = chunk let trades_txs: Vec<_> = chunk
.par_iter() .par_iter()
.map(|(signer, trade, direction, src)| { .map(|(signer, trade, direction, src)| {
@ -598,55 +522,52 @@ fn trader<T>(
) )
}) })
.collect(); .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") txs += chunk_size as u64;
.add_tag("op", influxdb::Value::String("trades".to_string())) total_txs += chunk_size as u64;
.add_field("count", influxdb::Value::Integer(trades_txs.len() as i64)) total_elapsed = start_time.elapsed();
.to_owned(), 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 let mut shared_txs_wl = shared_txs
.write() .write()
.expect("Failed to send tx to transfer threads"); .expect("Failed to send tx to transfer threads");
stats.total += chunk_size as u64;
shared_txs_wl.push_back(trades_txs); 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 sender
.send(trade_infos) .send(trade_infos)
.expect("Failed to send trades to swapper"); .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(), 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)| { to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
tx.sign(&[*k], blockhash); 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 let mut to_create_txs: Vec<_> = chunk
.par_iter() .par_iter()
.map(|(signer, new)| { .map(|(signer, new)| {
let owner_id = &signer.pubkey(); let owner_pubkey = &signer.pubkey();
let space = mem::size_of::<ExchangeState>() as u64; let space = mem::size_of::<ExchangeState>() as u64;
let create_ix = let create_ix =
system_instruction::create_account(owner_id, new, 1, space, &id()); system_instruction::create_account(owner_pubkey, new, 1, space, &id());
let request_ix = exchange_instruction::account_request(owner_id, new); let request_ix = exchange_instruction::account_request(owner_pubkey, new);
( (
signer, signer,
Transaction::new_unsigned_instructions(vec![create_ix, request_ix]), 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; let mut retries = 0;
while !to_create_txs.is_empty() { while !to_create_txs.is_empty() {
let blockhash = client let (blockhash, _fee_calculator) = client
.get_recent_blockhash() .get_recent_blockhash()
.expect("Failed to get blockhash"); .expect("Failed to get blockhash");
to_create_txs.par_iter_mut().for_each(|(k, tx)| { 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_txs = 0;
let mut max_elapsed = Duration::new(0, 0); let mut max_elapsed = Duration::new(0, 0);
info!("| Max TPS | Total Transactions"); info!("| Max TPS | Total Transactions");
info!("+---------------+--------------------"); info!("+---------------+--------------------");
for stats in maxes.read().unwrap().iter() { for (_sock, stats) in maxes.read().unwrap().iter() {
let maybe_flag = match stats.txs { let maybe_flag = match stats.txs {
0 => "!!!!!", 0 => "!!!!!",
_ => "", _ => "",
@ -933,7 +855,7 @@ pub fn airdrop_lamports(client: &Client, drone_addr: &SocketAddr, id: &Keypair,
let mut tries = 0; let mut tries = 0;
loop { loop {
let blockhash = client let (blockhash, _fee_calculator) = client
.get_recent_blockhash() .get_recent_blockhash()
.expect("Failed to get blockhash"); .expect("Failed to get blockhash");
match request_airdrop_transaction(&drone_addr, &id.pubkey(), amount_to_drop, 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use solana::fullnode::FullnodeConfig; use solana::gossip_service::{discover_cluster, get_clients};
use solana::gossip_service::discover_nodes;
use solana::local_cluster::{ClusterConfig, LocalCluster}; use solana::local_cluster::{ClusterConfig, LocalCluster};
use solana::validator::ValidatorConfig;
use solana_drone::drone::run_local_drone; use solana_drone::drone::run_local_drone;
use solana_exchange_api::exchange_processor::process_instruction; use solana_exchange_api::exchange_processor::process_instruction;
use solana_runtime::bank::Bank; use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient; 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; use std::sync::mpsc::channel;
#[test] #[test]
@ -1003,16 +907,16 @@ mod tests {
solana_logger::setup(); solana_logger::setup();
const NUM_NODES: usize = 1; const NUM_NODES: usize = 1;
let fullnode_config = FullnodeConfig::default(); let validator_config = ValidatorConfig::default();
let mut config = Config::default(); let mut config = Config::default();
config.identity = Keypair::new(); config.identity = Keypair::new();
config.threads = 1;
config.duration = Duration::from_secs(1); config.duration = Duration::from_secs(1);
config.fund_amount = 100_000; config.fund_amount = 100_000;
config.transfer_delay = 20; config.threads = 1;
config.transfer_delay = 20; // 15
config.batch_size = 100; // 1000; config.batch_size = 100; // 1000;
config.chunk_size = 10; // 250; config.chunk_size = 10; // 200;
config.account_groups = 1; // 10; config.account_groups = 1; // 10;
let Config { let Config {
fund_amount, fund_amount,
@ -1025,8 +929,8 @@ mod tests {
let cluster = LocalCluster::new(&ClusterConfig { let cluster = LocalCluster::new(&ClusterConfig {
node_stakes: vec![100_000; NUM_NODES], node_stakes: vec![100_000; NUM_NODES],
cluster_lamports: 100_000_000_000_000, cluster_lamports: 100_000_000_000_000,
fullnode_config, validator_config,
native_instruction_processors: [("solana_exchange_program".to_string(), id())].to_vec(), native_instruction_processors: [solana_exchange_program!()].to_vec(),
..ClusterConfig::default() ..ClusterConfig::default()
}); });
@ -1042,8 +946,8 @@ mod tests {
let drone_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap(); let drone_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
info!("Connecting to the cluster"); info!("Connecting to the cluster");
let nodes = let (nodes, _) = discover_cluster(&cluster.entry_point_info.gossip, NUM_NODES)
discover_nodes(&cluster.entry_point_info.gossip, NUM_NODES).unwrap_or_else(|err| { .unwrap_or_else(|err| {
error!("Failed to discover {} nodes: {:?}", NUM_NODES, err); error!("Failed to discover {} nodes: {:?}", NUM_NODES, err);
exit(1); exit(1);
}); });
@ -1072,16 +976,16 @@ mod tests {
#[test] #[test]
fn test_exchange_bank_client() { fn test_exchange_bank_client() {
solana_logger::setup(); 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); let mut bank = Bank::new(&genesis_block);
bank.add_instruction_processor(id(), process_instruction); bank.add_instruction_processor(id(), process_instruction);
let clients = vec![BankClient::new(bank)]; let clients = vec![BankClient::new(bank)];
let mut config = Config::default(); let mut config = Config::default();
config.identity = identity; config.identity = identity;
config.threads = 1;
config.duration = Duration::from_secs(1); config.duration = Duration::from_secs(1);
config.fund_amount = 100_000; config.fund_amount = 100_000;
config.threads = 1;
config.transfer_delay = 20; // 0; config.transfer_delay = 20; // 0;
config.batch_size = 100; // 1500; config.batch_size = 100; // 1500;
config.chunk_size = 10; // 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 clap::{crate_description, crate_name, crate_version, value_t, App, Arg, ArgMatches};
use solana::gen_keys::GenKeys; use solana::gen_keys::GenKeys;
use solana_drone::drone::DRONE_PORT; use solana_drone::drone::DRONE_PORT;
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil}; use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
use std::net::SocketAddr;
use std::process::exit;
use std::time::Duration;
pub struct Config { pub struct Config {
pub network_addr: SocketAddr, pub entrypoint_addr: SocketAddr,
pub drone_addr: SocketAddr, pub drone_addr: SocketAddr,
pub identity: Keypair, pub identity: Keypair,
pub threads: usize, pub threads: usize,
@ -23,7 +23,7 @@ pub struct Config {
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
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)), drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
identity: Keypair::new(), identity: Keypair::new(),
num_nodes: 1, num_nodes: 1,
@ -43,14 +43,14 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.about(crate_description!()) .about(crate_description!())
.version(crate_version!()) .version(crate_version!())
.arg( .arg(
Arg::with_name("network") Arg::with_name("entrypoint")
.short("n") .short("n")
.long("network") .long("entrypoint")
.value_name("HOST:PORT") .value_name("HOST:PORT")
.takes_value(true) .takes_value(true)
.required(false) .required(false)
.default_value("127.0.0.1:8001") .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(
Arg::with_name("drone") 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 { pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
let mut args = Config::default(); let mut args = Config::default();
args.network_addr = matches args.entrypoint_addr = solana_netutil::parse_host_port(matches.value_of("entrypoint").unwrap())
.value_of("network") .unwrap_or_else(|e| {
.unwrap() eprintln!("failed to parse entrypoint address: {}", e);
.parse() exit(1)
.expect("Failed to parse network"); });
args.drone_addr = matches
.value_of("drone") args.drone_addr = solana_netutil::parse_host_port(matches.value_of("drone").unwrap())
.unwrap() .unwrap_or_else(|e| {
.parse() eprintln!("failed to parse drone address: {}", e);
.expect("Failed to parse drone address"); exit(1)
});
if matches.is_present("identity") { if matches.is_present("identity") {
args.identity = read_keypair(matches.value_of("identity").unwrap()) args.identity = read_keypair(matches.value_of("identity").unwrap())

View File

@ -2,9 +2,13 @@ pub mod bench;
mod cli; mod cli;
pub mod order_book; 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 log::*;
use solana::gossip_service::discover_nodes; use solana::gossip_service::{discover_cluster, get_clients};
use solana_sdk::signature::KeypairUtil; use solana_sdk::signature::KeypairUtil;
fn main() { fn main() {
@ -15,7 +19,7 @@ fn main() {
let cli_config = cli::extract_args(&matches); let cli_config = cli::extract_args(&matches);
let cli::Config { let cli::Config {
network_addr, entrypoint_addr,
drone_addr, drone_addr,
identity, identity,
threads, threads,
@ -30,7 +34,8 @@ fn main() {
} = cli_config; } = cli_config;
info!("Connecting to the cluster"); info!("Connecting to the cluster");
let nodes = discover_nodes(&network_addr, num_nodes).unwrap_or_else(|_| { let (nodes, _replicators) =
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
panic!("Failed to discover nodes"); panic!("Failed to discover 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>"] authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018" edition = "2018"
name = "solana-bench-streamer" name = "solana-bench-streamer"
version = "0.14.0" version = "0.15.0"
repository = "https://github.com/solana-labs/solana" repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0" license = "Apache-2.0"
homepage = "https://solana.com/" homepage = "https://solana.com/"
[dependencies] [dependencies]
clap = "2.33.0" clap = "2.33.0"
solana = { path = "../core", version = "0.14.0" } solana = { path = "../core", version = "0.15.0" }
solana-logger = { path = "../logger", version = "0.14.0" } solana-logger = { path = "../logger", version = "0.15.0" }
solana-netutil = { path = "../netutil", version = "0.14.0" } solana-netutil = { path = "../netutil", version = "0.15.0" }
[features] [features]
cuda = ["solana/cuda"] 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.size = PACKET_DATA_SIZE;
w.meta.set_addr(&addr); w.meta.set_addr(&addr);
} }
let msgs_ = msgs.clone(); let msgs = Arc::new(msgs);
spawn(move || loop { spawn(move || loop {
if exit.load(Ordering::Relaxed) { if exit.load(Ordering::Relaxed) {
return; return;
} }
let mut num = 0; let mut num = 0;
for p in &msgs_.packets { for p in &msgs.packets {
let a = p.meta.addr(); let a = p.meta.addr();
assert!(p.meta.size < BLOB_SIZE); assert!(p.meta.size < BLOB_SIZE);
send.send_to(&p.data[..p.meta.size], &a).unwrap(); send.send_to(&p.data[..p.meta.size], &a).unwrap();

View File

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

View File

@ -1,10 +1,13 @@
use solana_metrics; use solana_metrics;
use log::*;
use rayon::prelude::*; use rayon::prelude::*;
use solana::gen_keys::GenKeys; use solana::gen_keys::GenKeys;
use solana_client::perf_utils::{sample_txs, SampleStats};
use solana_drone::drone::request_airdrop_transaction; use solana_drone::drone::request_airdrop_transaction;
use solana_metrics::influxdb; use solana_metrics::datapoint_info;
use solana_sdk::client::Client; use solana_sdk::client::Client;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_instruction; use solana_sdk::system_instruction;
use solana_sdk::system_transaction; use solana_sdk::system_transaction;
@ -22,13 +25,6 @@ use std::thread::Builder;
use std::time::Duration; use std::time::Duration;
use std::time::Instant; 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 MAX_SPENDS_PER_TX: usize = 4;
pub const NUM_LAMPORTS_PER_ACCOUNT: u64 = 20; pub const NUM_LAMPORTS_PER_ACCOUNT: u64 = 20;
@ -61,7 +57,8 @@ pub fn do_bench_tps<T>(
config: Config, config: Config,
gen_keypairs: Vec<Keypair>, gen_keypairs: Vec<Keypair>,
keypair0_balance: u64, keypair0_balance: u64,
) where ) -> u64
where
T: 'static + Client + Send + Sync, T: 'static + Client + Send + Sync,
{ {
let Config { let Config {
@ -98,7 +95,7 @@ pub fn do_bench_tps<T>(
Builder::new() Builder::new()
.name("solana-client-sample".to_string()) .name("solana-client-sample".to_string())
.spawn(move || { .spawn(move || {
sample_tx_count(&exit_signal, &maxes, first_tx_count, sample_period, &client); sample_txs(&exit_signal, &maxes, sample_period, &client);
}) })
.unwrap() .unwrap()
}) })
@ -136,28 +133,39 @@ pub fn do_bench_tps<T>(
let start = Instant::now(); let start = Instant::now();
let mut reclaim_lamports_back_to_source_account = false; let mut reclaim_lamports_back_to_source_account = false;
let mut i = keypair0_balance; let mut i = keypair0_balance;
let mut blockhash = Hash::default();
let mut blockhash_time = Instant::now();
while start.elapsed() < duration { 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 // ping-pong between source and destination accounts for each loop iteration
// this seems to be faster than trying to determine the balance of individual // this seems to be faster than trying to determine the balance of individual
// accounts // accounts
let len = tx_count as usize; 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( generate_txs(
&shared_txs, &shared_txs,
&blockhash,
&keypairs[..len], &keypairs[..len],
&keypairs[len..], &keypairs[len..],
threads, threads,
reclaim_lamports_back_to_source_account, reclaim_lamports_back_to_source_account,
&client,
); );
// In sustained mode overlap the transfers with generation // In sustained mode overlap the transfers with generation
// this has higher average performance but lower peak performance // this has higher average performance but lower peak performance
// in tested environments. // in tested environments.
if !sustained { if !sustained {
while shared_tx_active_thread_count.load(Ordering::Relaxed) > 0 { 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(), &start.elapsed(),
total_tx_sent_count.load(Ordering::Relaxed), 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) { fn metrics_submit_lamport_balance(lamport_balance: u64) {
println!("Token balance: {}", lamport_balance); println!("Token balance: {}", lamport_balance);
solana_metrics::submit( datapoint_info!(
influxdb::Point::new("bench-tps") "bench-tps-lamport_balance",
.add_tag("op", influxdb::Value::String("lamport_balance".to_string())) ("balance", lamport_balance, i64)
.add_field("balance", influxdb::Value::Integer(lamport_balance as i64))
.to_owned(),
); );
} }
fn sample_tx_count<T: Client>( fn generate_txs(
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>(
shared_txs: &SharedTransactions, shared_txs: &SharedTransactions,
blockhash: &Hash,
source: &[Keypair], source: &[Keypair],
dest: &[Keypair], dest: &[Keypair],
threads: usize, threads: usize,
reclaim: bool, reclaim: bool,
client: &Arc<T>,
) { ) {
let blockhash = client.get_recent_blockhash().unwrap();
let tx_count = source.len(); let tx_count = source.len();
println!("Signing transactions... {} (reclaim={})", tx_count, reclaim); println!("Signing transactions... {} (reclaim={})", tx_count, reclaim);
let signing_start = Instant::now(); let signing_start = Instant::now();
@ -287,7 +236,7 @@ fn generate_txs<T: Client>(
.par_iter() .par_iter()
.map(|(id, keypair)| { .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(), timestamp(),
) )
}) })
@ -304,14 +253,9 @@ fn generate_txs<T: Client>(
duration_as_ms(&duration), duration_as_ms(&duration),
blockhash, blockhash,
); );
solana_metrics::submit( datapoint_info!(
influxdb::Point::new("bench-tps") "bench-tps-generate_txs",
.add_tag("op", influxdb::Value::String("generate_txs".to_string())) ("duration", duration_as_ms(&duration), i64)
.add_field(
"duration",
influxdb::Value::Integer(duration_as_ms(&duration) as i64),
)
.to_owned(),
); );
let sz = transactions.len() / threads; let sz = transactions.len() / threads;
@ -338,7 +282,7 @@ fn do_tx_transfers<T: Client>(
} }
let txs; 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(); txs = shared_txs_wl.pop_front();
} }
if let Some(txs0) = txs { if let Some(txs0) = txs {
@ -355,7 +299,9 @@ fn do_tx_transfers<T: Client>(
if now > tx.1 && now - tx.1 > 1000 * 30 { if now > tx.1 && now - tx.1 > 1000 * 30 {
continue; 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); shared_tx_thread_count.fetch_add(-1, Ordering::Relaxed);
total_tx_sent_count.fetch_add(tx_len, 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()), duration_as_ms(&transfer_start.elapsed()),
tx_len as f32 / duration_as_s(&transfer_start.elapsed()), tx_len as f32 / duration_as_s(&transfer_start.elapsed()),
); );
solana_metrics::submit( datapoint_info!(
influxdb::Point::new("bench-tps") "bench-tps-do_tx_transfers",
.add_tag("op", influxdb::Value::String("do_tx_transfers".to_string())) ("duration", duration_as_ms(&transfer_start.elapsed()), i64),
.add_field( ("count", tx_len, i64)
"duration",
influxdb::Value::Integer(duration_as_ms(&transfer_start.elapsed()) as i64),
)
.add_field("count", influxdb::Value::Integer(tx_len as i64))
.to_owned(),
); );
} }
if exit_signal.load(Ordering::Relaxed) { 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(), 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 // re-sign retained to_fund_txes with updated blockhash
to_fund_txs.par_iter_mut().for_each(|(k, tx)| { to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
@ -515,7 +456,7 @@ pub fn airdrop_lamports<T: Client>(
id.pubkey(), 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) { match request_airdrop_transaction(&drone_addr, &id.pubkey(), airdrop_amount, blockhash) {
Ok(transaction) => { Ok(transaction) => {
let signature = client.async_send_transaction(transaction).unwrap(); let signature = client.async_send_transaction(transaction).unwrap();
@ -556,7 +497,7 @@ pub fn airdrop_lamports<T: Client>(
} }
fn compute_and_report_stats( fn compute_and_report_stats(
maxes: &Arc<RwLock<Vec<(String, NodeStats)>>>, maxes: &Arc<RwLock<Vec<(String, SampleStats)>>>,
sample_period: u64, sample_period: u64,
tx_send_elapsed: &Duration, tx_send_elapsed: &Duration,
total_tx_send_count: usize, total_tx_send_count: usize,
@ -570,14 +511,14 @@ fn compute_and_report_stats(
println!("---------------------+---------------+--------------------"); println!("---------------------+---------------+--------------------");
for (sock, stats) in maxes.read().unwrap().iter() { for (sock, stats) in maxes.read().unwrap().iter() {
let maybe_flag = match stats.tx { let maybe_flag = match stats.txs {
0 => "!!!!!", 0 => "!!!!!",
_ => "", _ => "",
}; };
println!( println!(
"{:20} | {:13.2} | {} {}", "{:20} | {:13.2} | {} {}",
sock, stats.tps, stats.tx, maybe_flag sock, stats.tps, stats.txs, maybe_flag
); );
if stats.tps == 0.0 { if stats.tps == 0.0 {
@ -588,27 +529,33 @@ fn compute_and_report_stats(
if stats.tps > max_of_maxes { if stats.tps > max_of_maxes {
max_of_maxes = stats.tps; max_of_maxes = stats.tps;
} }
if stats.tx > max_tx_count { if stats.txs > max_tx_count {
max_tx_count = stats.tx; max_tx_count = stats.txs;
} }
} }
if total_maxes > 0.0 { if total_maxes > 0.0 {
let num_nodes_with_tps = maxes.read().unwrap().len() - nodes_with_zero_tps; 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!( println!(
"\nAverage max TPS: {:.2}, {} nodes had 0 TPS", "\nAverage max TPS: {:.2}, {} nodes had 0 TPS",
average_max, nodes_with_zero_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!( println!(
"\nHighest TPS: {:.2} sampling period {}s max transactions: {} clients: {} drop rate: {:.2}", "\nHighest TPS: {:.2} sampling period {}s max transactions: {} clients: {} drop rate: {:.2}",
max_of_maxes, max_of_maxes,
sample_period, sample_period,
max_tx_count, max_tx_count,
maxes.read().unwrap().len(), maxes.read().unwrap().len(),
(total_tx_send_count as u64 - max_tx_count) as f64 / total_tx_send_count as f64, drop_rate,
); );
println!( println!(
"\tAverage TPS: {}", "\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) 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]; 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 rnd = GenKeys::new(seed);
let mut total_keys = 0; let mut total_keys = 0;
let mut target = tx_count * 2; let mut target = count;
while target > 0 { while target > 1 {
total_keys += target; 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) 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use solana::cluster_info::FULLNODE_PORT_RANGE; use solana::cluster_info::FULLNODE_PORT_RANGE;
use solana::fullnode::FullnodeConfig;
use solana::local_cluster::{ClusterConfig, LocalCluster}; use solana::local_cluster::{ClusterConfig, LocalCluster};
use solana::validator::ValidatorConfig;
use solana_client::thin_client::create_client; use solana_client::thin_client::create_client;
use solana_drone::drone::run_local_drone; use solana_drone::drone::run_local_drone;
use solana_runtime::bank::Bank; use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient; 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; use std::sync::mpsc::channel;
#[test] #[test]
@ -666,14 +649,14 @@ mod tests {
} }
#[test] #[test]
#[ignore] fn test_bench_tps_local_cluster() {
fn test_bench_tps() { solana_logger::setup();
let fullnode_config = FullnodeConfig::default(); let validator_config = ValidatorConfig::default();
const NUM_NODES: usize = 1; const NUM_NODES: usize = 1;
let cluster = LocalCluster::new(&ClusterConfig { let cluster = LocalCluster::new(&ClusterConfig {
node_stakes: vec![999_990; NUM_NODES], node_stakes: vec![999_990; NUM_NODES],
cluster_lamports: 2_000_000, cluster_lamports: 2_000_000,
fullnode_config, validator_config,
..ClusterConfig::default() ..ClusterConfig::default()
}); });
@ -688,18 +671,27 @@ mod tests {
config.tx_count = 100; config.tx_count = 100;
config.duration = Duration::from_secs(5); config.duration = Duration::from_secs(5);
let keypairs = generate_keypairs(&config.id, config.tx_count);
let client = create_client( let client = create_client(
(cluster.entry_point_info.gossip, drone_addr), (cluster.entry_point_info.rpc, cluster.entry_point_info.tpu),
FULLNODE_PORT_RANGE, 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] #[test]
fn test_bench_tps_bank_client() { 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 bank = Bank::new(&genesis_block);
let clients = vec![BankClient::new(bank)]; let clients = vec![BankClient::new(bank)];
@ -708,9 +700,26 @@ mod tests {
config.tx_count = 10; config.tx_count = 10;
config.duration = Duration::from_secs(5); config.duration = Duration::from_secs(5);
let keypairs = generate_keypairs(&config.id, config.tx_count); let (keypairs, _keypair_balance) =
fund_keys(&clients[0], &config.id, &keypairs, 20); generate_and_fund_keypairs(&clients[0], None, &config.id, config.tx_count, 20);
do_bench_tps(clients, config, keypairs, 0); 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 /// Holds the configuration for a single run of the benchmark
pub struct Config { pub struct Config {
pub network_addr: SocketAddr, pub entrypoint_addr: SocketAddr,
pub drone_addr: SocketAddr, pub drone_addr: SocketAddr,
pub id: Keypair, pub id: Keypair,
pub threads: usize, pub threads: usize,
@ -22,7 +22,7 @@ pub struct Config {
impl Default for Config { impl Default for Config {
fn default() -> Config { fn default() -> Config {
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)), drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
id: Keypair::new(), id: Keypair::new(),
threads: 4, threads: 4,
@ -40,12 +40,12 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
App::new(crate_name!()).about(crate_description!()) App::new(crate_name!()).about(crate_description!())
.version(crate_version!()) .version(crate_version!())
.arg( .arg(
Arg::with_name("network") Arg::with_name("entrypoint")
.short("n") .short("n")
.long("network") .long("entrypoint")
.value_name("HOST:PORT") .value_name("HOST:PORT")
.takes_value(true) .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(
Arg::with_name("drone") Arg::with_name("drone")
@ -53,7 +53,7 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.long("drone") .long("drone")
.value_name("HOST:PORT") .value_name("HOST:PORT")
.takes_value(true) .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(
Arg::with_name("identity") 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 { pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
let mut args = Config::default(); let mut args = Config::default();
if let Some(addr) = matches.value_of("network") { if let Some(addr) = matches.value_of("entrypoint") {
args.network_addr = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| { args.entrypoint_addr = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| {
eprintln!("failed to parse network address: {}", e); eprintln!("failed to parse entrypoint address: {}", e);
exit(1) exit(1)
}); });
} }

View File

@ -1,15 +1,8 @@
mod bench; mod bench;
mod cli; mod cli;
use crate::bench::{ use crate::bench::{do_bench_tps, generate_and_fund_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT};
airdrop_lamports, do_bench_tps, fund_keys, generate_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT, use solana::gossip_service::{discover_cluster, get_clients};
};
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 std::process::exit; use std::process::exit;
fn main() { fn main() {
@ -20,7 +13,7 @@ fn main() {
let cli_config = cli::extract_args(&matches); let cli_config = cli::extract_args(&matches);
let cli::Config { let cli::Config {
network_addr, entrypoint_addr,
drone_addr, drone_addr,
id, id,
threads, threads,
@ -32,7 +25,8 @@ fn main() {
} = cli_config; } = cli_config;
println!("Connecting to the cluster"); println!("Connecting to the cluster");
let nodes = discover_nodes(&network_addr, num_nodes).unwrap_or_else(|err| { let (nodes, _replicators) =
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|err| {
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err); eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
exit(1); exit(1);
}); });
@ -43,40 +37,16 @@ fn main() {
); );
exit(1); 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 clients = get_clients(&nodes);
let keypairs = generate_keypairs(&id, tx_count);
println!("Get lamports..."); let (keypairs, keypair_balance) = generate_and_fund_keypairs(
&clients[0],
// Sample the first keypair, see if it has lamports, if so then resume. Some(drone_addr),
// This logic is to prevent lamport loss on repeated solana-bench-tps executions &id,
let keypair0_balance = clients[0] tx_count,
.get_balance(&keypairs.last().unwrap().pubkey()) NUM_LAMPORTS_PER_ACCOUNT,
.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 config = Config { let config = Config {
id, id,
@ -87,5 +57,5 @@ fn main() {
sustained, 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 +------------+ +--------+ Neighborhood 0 +----------+
| | | | | | | |
| +--------------+ | | +--------------------+ |
v v v v
+--------+--------+ +--------+--------+ +---------+----------+ +----------+---------+
| +--------------------->+ | | | | |
+-----------------+ Validator 1 | | Validator 2 +-------------+ | Neighborhood 1 | | Neighborhood 2 |
| | +<---------------------+ | | | | | |
| +------+-+-+------+ +---+-+-+---------+ | +---+-----+----------+ +----------+-----+---+
| | | |
v v v v
+------------------+-+ +-+------------------+ +------------------+-+ +-+------------------+
| | | | | | | | | | | | | | | |
| | | | | | | | | Neighborhood 3 | | Neighborhood 4 | | Neighborhood 5 | | Neighborhood 6 |
| +---------------------------------------------+ | | |
| | | | | | | |
| | | | | +----------------------+ | |
| | | | | | | |
| | | | +--------------------------------------------+ |
| | | | | | | |
| | | +----------------------+ | | |
| | | | | | | |
v v v v v v v v
+--------------------+ +--------------------+ +--------------------+ +--------------------+
| | | | | | | |
| Neighborhood 1 | | Neighborhood 2 | | Neighborhood 3 | | Neighborhood 4 |
| | | | | | | | | | | | | | | |
+--------------------+ +--------------------+ +--------------------+ +--------------------+ +--------------------+ +--------------------+ +--------------------+ +--------------------+

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")" 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)" make -j"$(nproc)"

View File

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

View File

@ -19,9 +19,10 @@
- [Data Plane Fanout](data-plane-fanout.md) - [Data Plane Fanout](data-plane-fanout.md)
- [Ledger Replication](ledger-replication.md) - [Ledger Replication](ledger-replication.md)
- [Secure Vote Signing](vote-signing.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) - [TPU](tpu.md)
- [TVU](tvu.md) - [TVU](tvu.md)
- [Blocktree](blocktree.md) - [Blocktree](blocktree.md)
@ -38,9 +39,6 @@
- [Ledger Replication](ledger-replication-to-implement.md) - [Ledger Replication](ledger-replication-to-implement.md)
- [Secure Vote Signing](vote-signing-to-implement.md) - [Secure Vote Signing](vote-signing-to-implement.md)
- [Staking Rewards](staking-rewards.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) - [Cluster Economics](ed_overview.md)
- [Validation-client Economics](ed_validation_client_economics.md) - [Validation-client Economics](ed_validation_client_economics.md)
- [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md) - [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md)
@ -55,13 +53,17 @@
- [Economic Design MVP](ed_mvp.md) - [Economic Design MVP](ed_mvp.md)
- [References](ed_references.md) - [References](ed_references.md)
- [Cluster Test Framework](cluster-test-framework.md) - [Cluster Test Framework](cluster-test-framework.md)
- [Testing Programs](testing-programs.md)
- [Credit-only Accounts](credit-only-credit-debit-accounts.md) - [Credit-only Accounts](credit-only-credit-debit-accounts.md)
- [Cluster Software Installation and Updates](installer.md)
- [Deterministic Transaction Fees](transaction-fees.md) - [Deterministic Transaction Fees](transaction-fees.md)
- [Validator](validator-proposal.md)
- [Implemented Design Proposals](implemented-proposals.md) - [Implemented Design Proposals](implemented-proposals.md)
- [Fork Selection](fork-selection.md) - [Fork Selection](fork-selection.md)
- [Leader-to-Leader Transition](leader-leader-transition.md) - [Leader-to-Leader Transition](leader-leader-transition.md)
- [Leader-to-Validator Transition](leader-validator-transition.md) - [Leader-to-Validator Transition](leader-validator-transition.md)
- [Testnet Participation](testnet-participation.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: `blockstream` socket location:
```bash ```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: 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` Tests are provided an entry point, which is a `contact_info::ContactInfo`
structure, and a keypair that has already been funded. 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 time. At boot time this configuration specifies any extra cluster configuration
required for the test. The cluster should boot with the configuration when it required for the test. The cluster should boot with the configuration when it
is run in-process or in a data center. 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 To enable specific scenarios, the cluster needs to be booted with special
configurations. These configurations can be captured in configurations. These configurations can be captured in
`fullnode::FullnodeConfig`. `fullnode::ValidatorConfig`.
For example: For example:
```rust,ignore ```rust,ignore
let mut fullnode_config = FullnodeConfig::default(); let mut validator_config = ValidatorConfig::default();
fullnode_config.rpc_config.enable_fullnode_exit = true; validator_config.rpc_config.enable_fullnode_exit = true;
let local = LocalCluster::new_with_config( let local = LocalCluster::new_with_config(
num_nodes, num_nodes,
10_000, 10_000,
100, 100,
&fullnode_config &validator_config
); );
``` ```
@ -86,9 +86,9 @@ advertised gossip nodes.
Configure the RPC service: Configure the RPC service:
```rust,ignore ```rust,ignore
let mut fullnode_config = FullnodeConfig::default(); let mut validator_config = ValidatorConfig::default();
fullnode_config.rpc_config.enable_rpc_gossip_push = true; validator_config.rpc_config.enable_rpc_gossip_push = true;
fullnode_config.rpc_config.enable_rpc_gossip_refresh_active_set = true; validator_config.rpc_config.enable_rpc_gossip_refresh_active_set = true;
``` ```
Wire the RPCs and write a new test: Wire the RPCs and write a new test:

View File

@ -28,7 +28,7 @@ its copy.
## Joining a Cluster ## 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* its *control plane*. The control plane is implemented using a *gossip*
protocol, meaning that a node may register with any existing node, and expect 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 its registration to propagate to all nodes in the cluster. The time it takes

View File

@ -6,15 +6,14 @@ In order to establish the fanout, the cluster divides itself into small
collections of nodes, called *neighborhoods*. Each node is responsible for collections of nodes, called *neighborhoods*. Each node is responsible for
sharing any data it receives with the other nodes in its neighborhood, as well 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 During its slot, the leader node distributes blobs between the validator nodes
in one neighborhood (layer 1). Each validator shares its data within its in the first neighborhood (layer 0). Each validator shares its data within its
neighborhood, but also retransmits the blobs to one node in each of multiple neighborhood, but also retransmits the blobs to one node in some neighborhoods
neighborhoods in the next layer (layer 2). The layer-2 nodes each share their in the next layer (layer 1). The layer-1 nodes each share their data with their
data with their neighborhood peers, and retransmit to nodes in the next layer, neighborhood peers, and retransmit to nodes in the next layer, etc, until all
etc, until all nodes in the cluster have received all the blobs. nodes in the cluster have received all the blobs.
<img alt="Two layer cluster" src="img/data-plane.svg" class="center"/>
## Neighborhood Assignment - Weighted Selection ## 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 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 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 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 make up layer 0. These will automatically be the highest stake holders, allowing
the heaviest votes to come back to the leader first. Layer-1 and lower-layer 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 lower layer peers. nodes use the same logic to find their neighbors and next layer peers.
## Layer and Neighborhood Structure ## Layer and Neighborhood Structure
The current leader makes its initial broadcasts to at most `DATA_PLANE_FANOUT` 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 the data plane fanout mechanism adds layers below. Subsequent layers follow
these constraints to determine layer-capacity: Each neighborhood contains these constraints to determine layer-capacity: Each neighborhood contains
`NEIGHBORHOOD_SIZE` nodes and each layer may have up to `DATA_PLANE_FANOUT/2` `DATA_PLANE_FANOUT` nodes. Layer-0 starts with 1 neighborhood with fanout nodes.
neighborhoods. 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 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 neighbors and to exactly 1 node in some next-layer neighborhoods,
every TVU peer in the cluster. In the default mode, each layer contains instead of to every TVU peer in the cluster. A good way to think about this is,
`DATA_PLANE_FANOUT/2` neighborhoods. The retransmit mechanism also supports a layer-0 starts with 1 neighborhood with fanout nodes, layer-1 adds "fanout"
second, `grow`, mode of operation that squares the number of neighborhoods neighborhoods, each with fanout nodes and layer-2 will have
allowed each layer. This dramatically reduces the number of layers needed to `fanout * number of nodes in layer-1` and so on.
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 This way each node only has to communicate with a maximum of `2 * DATA_PLANE_FANOUT - 1` nodes.
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 The following diagram shows how the Leader sends blobs with a Fanout of 2 to
capacities` remain constant, so all layers past layer-2 will have the same Neighborhood 0 in Layer 0 and how the nodes in Neighborhood 0 share their data
number of nodes until the whole cluster is covered. When `grow` is enabled, this with each other.
becomes a traditional fanout where layer-3 will have the square of the number of
nodes in layer-2 and so on. <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 #### Configuration Values
`DATA_PLANE_FANOUT` - Determines the size of layer 1. Subsequent `DATA_PLANE_FANOUT` - Determines the size of layer 0. Subsequent
layers have `DATA_PLANE_FANOUT/2` neighborhoods when `grow` is inactive. layers grow by a factor of `DATA_PLANE_FANOUT`.
The number of nodes in a neighborhood is equal to the fanout value.
`NEIGHBORHOOD_SIZE` - The number of nodes allowed in a neighborhood.
Neighborhoods will fill to capacity before new ones are added, i.e if a Neighborhoods will fill to capacity before new ones are added, i.e if a
neighborhood isn't full, it _must_ be the last one. 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, 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 these parameters may be hosted on-chain, allowing modification on the fly as the
cluster sizes change. cluster sizes change.
@ -72,13 +73,10 @@ cluster sizes change.
## Neighborhoods ## Neighborhoods
The following diagram shows how two neighborhoods in different layers interact. The following diagram shows how two neighborhoods in different layers interact.
What this diagram doesn't capture is that each neighbor actually receives To cripple a neighborhood, enough nodes (erasure codes +1) from the neighborhood
blobs from one validator per neighborhood above it. This means that, to above need to fail. Since each neighborhood receives blobs from multiple nodes
cripple a neighborhood, enough nodes (erasure codes +1 per neighborhood) from in a neighborhood in the upper layer, we'd need a big network failure in the upper
the layer above need to fail. Since multiple neighborhoods exist in the upper layers to end up with incomplete data.
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.
<img alt="Inner workings of a neighborhood" <img alt="Inner workings of a neighborhood"
src="img/data-plane-neighborhood.svg" class="center"/> src="img/data-plane-neighborhood.svg" class="center"/>

View File

@ -47,8 +47,8 @@ nodes are started
$ cargo build --all $ cargo build --all
``` ```
The network is initialized with a genesis ledger and fullnode configuration files. The network is initialized with a genesis ledger generated by running the
These files can be generated by running the following script. following script.
```bash ```bash
$ ./multinode-demo/setup.sh $ ./multinode-demo/setup.sh
@ -69,7 +69,7 @@ $ ./multinode-demo/drone.sh
### Singlenode Testnet ### 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 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. 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 ### Multinode Testnet
To run a multinode testnet, after starting a leader node, spin up some 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 ```bash
$ ./multinode-demo/fullnode-x.sh $ ./multinode-demo/validator-x.sh
``` ```
To run a performance-enhanced full node on Linux, To run a performance-enhanced full node on Linux,
@ -99,7 +99,7 @@ your system:
```bash ```bash
$ ./fetch-perf-libs.sh $ ./fetch-perf-libs.sh
$ SOLANA_CUDA=1 ./multinode-demo/bootstrap-leader.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 ### 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. messages and `info` for performance-related logging.
You can also attach to a running process with GDB. The leader's process is named You can also attach to a running process with GDB. The leader's process is named
_solana-fullnode_: _solana-validator_:
```bash ```bash
$ sudo gdb $ 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`. 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 ```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) 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 # 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. use the service to ensure information is available to all other nodes in a cluster.
The service broadcasts information using a gossip protocol. 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 ## Notable differences from PlumTree
The active push protocol described here is based on (Plum The active push protocol described here is based on [Plum
Tree)[https://haslab.uminho.pt/jop/files/lpr07a.pdf]. The main differences are: 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 * 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 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 $ solana-install init --pubkey 92DMonmBYXwEMHJ99c9ceRSpAmk9v6i3RdvDdXaVcrfj # <-- pubkey is obtained from whoever is deploying the updates
$ export PATH=~/.local/share/solana-install/bin:$PATH $ export PATH=~/.local/share/solana-install/bin:$PATH
$ solana-keygen ... # <-- runs the latest solana-keygen $ 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 ### On-chain Update Manifest

View File

@ -30,6 +30,7 @@ Methods
* [getSlotLeader](#getslotleader) * [getSlotLeader](#getslotleader)
* [getNumBlocksSinceSignatureConfirmation](#getnumblockssincesignatureconfirmation) * [getNumBlocksSinceSignatureConfirmation](#getnumblockssincesignatureconfirmation)
* [getTransactionCount](#gettransactioncount) * [getTransactionCount](#gettransactioncount)
* [getEpochVoteAccounts](#getepochvoteaccounts)
* [requestAirdrop](#requestairdrop) * [requestAirdrop](#requestairdrop)
* [sendTransaction](#sendtransaction) * [sendTransaction](#sendtransaction)
* [startSubscriptionChannel](#startsubscriptionchannel) * [startSubscriptionChannel](#startsubscriptionchannel)
@ -167,13 +168,16 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
--- ---
### getRecentBlockhash ### 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: ##### Parameters:
None None
##### Results: ##### Results:
An array consisting of
* `string` - a Hash as base-58 encoded string * `string` - a Hash as base-58 encoded string
* `FeeCalculator object` - the fee schedule for this block hash
##### Example: ##### Example:
```bash ```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 curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899
// Result // 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 ### requestAirdrop
Requests an airdrop of lamports to a Pubkey 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>/`: After connect to the RPC PubSub websocket at `ws://<ADDRESS>/`:
- Submit subscription requests to the websocket using the methods below - Submit subscription requests to the websocket using the methods below
- Multiple subscriptions may be active at once - 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: ##### Parameters:
* `string` - account Pubkey, as base-58 encoded string * `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: ##### Results:
* `integer` - Subscription id (needed to unsubscribe) * `integer` - Subscription id (needed to unsubscribe)
@ -334,6 +381,8 @@ for a given account public key changes
// Request // Request
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]} {"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", 15]}
// Result // Result
{"jsonrpc": "2.0","result": 0,"id": 1} {"jsonrpc": "2.0","result": 0,"id": 1}
``` ```
@ -371,6 +420,8 @@ for a given account owned by the program changes
##### Parameters: ##### Parameters:
* `string` - program_id Pubkey, as base-58 encoded string * `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: ##### Results:
* `integer` - Subscription id (needed to unsubscribe) * `integer` - Subscription id (needed to unsubscribe)
@ -380,6 +431,8 @@ for a given account owned by the program changes
// Request // Request
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV"]} {"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV"]}
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV", 15]}
// Result // Result
{"jsonrpc": "2.0","result": 0,"id": 1} {"jsonrpc": "2.0","result": 0,"id": 1}
``` ```
@ -419,6 +472,8 @@ On `signatureNotification`, the subscription is automatically cancelled
##### Parameters: ##### Parameters:
* `string` - Transaction Signature, as base-58 encoded string * `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: ##### Results:
* `integer` - subscription id (needed to unsubscribe) * `integer` - subscription id (needed to unsubscribe)
@ -428,6 +483,8 @@ On `signatureNotification`, the subscription is automatically cancelled
// Request // Request
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]} {"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b", 15]}
// Result // Result
{"jsonrpc": "2.0","result": 0,"id": 1} {"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. cluster to advertise higher TPS capacity.
## Fullnode Loop ## Validator Loop
The PoH Recorder manages the transition between modes. Once a ledger is The PoH Recorder manages the transition between modes. Once a ledger is
replayed, the validator can run until the recorder indicates it should be replayed, the validator can run until the recorder indicates it should be

View File

@ -2,6 +2,12 @@
Replication behavior yet to be implemented. 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 ### Validator behavior
3. Every NUM\_KEY\_ROTATION\_TICKS it also validates samples received from 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 The replicator is rewarded and the validator's staking balance is slashed or
frozen. 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. 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 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, 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 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 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 ### 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. unit of storage for a replicator.
2. NUM\_KEY\_ROTATION\_TICKS: Number of ticks to save a PoH value and cause a 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 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. 3. Validator generates a storage proof confirmation transaction.
4. The storage proof confirmation transaction is integrated into the ledger. 4. The storage proof confirmation transaction is integrated into the ledger.
6. Validator responds to RPC interfaces for what the last storage epoch PoH 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 ### Replicator behavior
@ -95,10 +95,10 @@ is:
- (d) replicator can subscribe to an abbreviated transaction stream to - (d) replicator can subscribe to an abbreviated transaction stream to
generate the information itself generate the information itself
2. A replicator obtains the PoH hash corresponding to the last key rotation 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 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 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. replicate.
4. The replicator retrives the ledger by asking peer validators and 4. The replicator retrives the ledger by asking peer validators and
replicators. See 6.5. 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 ### Finding who has a given block of ledger
1. Validators monitor the transaction stream for storage mining proofs, and 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 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 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 corresponds to and sends a repair request to retrieve the necessary blocks of
ledger. ledger.

View File

@ -85,7 +85,7 @@ contains the following state information:
* Account::lamports - The staked lamports. * 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. delegated to.
* `credits_observed` - The total credits claimed over the lifetime of the * `credits_observed` - The total credits claimed over the lifetime of the
@ -109,7 +109,7 @@ program.
* `account[0]` - RW - The StakeState::Delegate instance. * `account[0]` - RW - The StakeState::Delegate instance.
`StakeState::Delegate::credits_observed` is initialized to `VoteState::credits`. `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. * `account[1]` - R - The VoteState instance.
@ -127,7 +127,7 @@ reward.
* `account[1]` - RW - The StakeState::Delegate instance that is redeeming votes * `account[1]` - RW - The StakeState::Delegate instance that is redeeming votes
credits. credits.
* `account[2]` - R - The VoteState instance, must be the same as * `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 Reward is payed out for the difference between `VoteState::credits` to
`StakeState::Delgate.credits_observed`, and `credits_observed` is updated 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 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 all the StakeState programs that have the VoteState pubkey as the
`StakeState::Delegate::voter_id`. `StakeState::Delegate::voter_pubkey`.
## Example Callflow ## 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 way to implement this would be for the StakeState to delegate to a pool of
validators instead of a single one. 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. program, the program can be initialized with a vector of tuples.
```rust,ignore ```rust,ignore
Voter { Voter {
voter_id: Pubkey, voter_pubkey: Pubkey,
credits_observed: u64, credits_observed: u64,
weight: u8, 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 # Stake Delegation and Rewards
Stakers are rewarded for helping validate the ledger. They do it by delegating Stakers are rewarded for helping to validate the ledger. They do this by
their stake to fullnodes. Those fullnodes do the legwork and send votes to the delegating their stake to validator nodes. Those validators do the legwork of
stakers' staking accounts. The rest of the cluster uses those stake-weighted replaying the ledger and send votes to a per-node vote account to which stakers
votes to select a block when forks arise. Both the fullnode and staker need can delegate their stakes. The rest of the cluster uses those stake-weighted
some economic incentive to play their part. The fullnode needs to be votes to select a block when forks arise. Both the validator and staker need
compensated for its hardware and the staker needs to be compensated for risking some economic incentive to play their part. The validator needs to be
getting its stake slashed. The economics are covered in [staking 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 rewards](staking-rewards.md). This chapter, on the other hand, describes the
underlying mechanics of its implementation. underlying mechanics of its implementation.
## Vote and Rewards accounts ## Basic Besign
The rewards process is split into two on-chain programs. The Vote program The general idea is that the validator owns a Vote account. The Vote account
solves the problem of making stakes slashable. The Rewards account acts as tracks validator votes, counts validator generated credits, and provides any
custodian of the rewards pool. It is responsible for paying out each staker additional validator specific state. The Vote account is not aware of any
once the staker proves to the Rewards program that it participated in stakes delegated to it and has no staking weight.
validating the ledger.
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 Any number of Stake accounts can delegate to a single
account's stake. It is typically the identity of a fullnode, but may be any Vote account without an interactive action from the identity controlling
identity involved in stake-weighted computations. 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 The rewards process is split into two on-chain programs. The Vote program solves
for rewards. 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 ### VoteState
Vote account to the program. Claiming a reward requires a transaction that
includes the following instructions:
1. `RewardsInstruction::RedeemVoteCredits` VoteState is the current state of all the votes the validator has submitted to
2. `VoteInstruction::ClearCredits` the network. VoteState contains the following state information:
The Rewards program transfers lamports from the Rewards account to the Vote * votes - The submitted votes data structure.
account's public key. The Rewards program also ensures that the `ClearCredits`
instruction follows the `RedeemVoteCredits` instruction, such that a staker may * credits - The total number of rewards this vote program has generated over its
not claim rewards for the same work more than once. 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 `VoteInstruction::AuthorizeVoter` allows a staker to choose a signing service
for its votes. That service is responsible for ensuring the vote won't cause for its votes. That service is responsible for ensuring the vote won't cause
the staker to be slashed. the staker to be slashed.
## Limitations ## Benefits of the design
Many stakers may delegate their stakes to the same fullnode. The fullnode must * Single vote for all the stakers.
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 * Clearing of the credit variable is not necessary for claiming rewards.
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. * 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 # Staking Rewards
Initial Proof of Stake (PoS) (i.e. using in-protocol asset, SOL, to provide A 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 secure consensus) design is outlined here. Solana implements a proof of
stake reward/security scheme for node validators in the cluster. The purpose is stake reward/security scheme for validator nodes in the cluster. The purpose is
threefold: threefold:
- Align validator incentives with that of the greater cluster through - Align validator incentives with that of the greater cluster through

View File

@ -15,39 +15,43 @@ reasons:
* The cluster rolled back the ledger * The cluster rolled back the ledger
* A validator responded to queries maliciously * 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 To troubleshoot, the application should retarget a lower-level component, where
fewer errors are possible. Retargeting can be done with different 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 ```rust,ignore
trait Transact { trait AsyncClient {
async fn send_transactions(txs: &[Transaction]) -> Vec<Result<(), TransactionError>>; 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 The highest level implementation, ThinClient, targets a Solana cluster, which
deployed testnet or a local cluster running on a development machine. 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 The next level is the TPU implementation, which is not yet implemented. At the
application sends transactions over Rust channels, where there can be no TPU level, the application sends transactions over Rust channels, where there
surprises from network queues or dropped packets. The TPU implements all can be no surprises from network queues or dropped packets. The TPU implements
"normal" transaction errors. It does signature verification, may report all "normal" transaction errors. It does signature verification, may report
account-in-use errors, and otherwise results in the ledger, complete with proof account-in-use errors, and otherwise results in the ledger, complete with proof
of history hashes. of history hashes.
### Low-level testing ### 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 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 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 Please note some of the information and instructions described here may change
in future releases. in future releases.
### Beta Testnet Overview ### Overview
The testnet features a validator running at testnet.solana.com, which The testnet features a validator running at testnet.solana.com, which
serves as the entrypoint to the cluster for your validator. 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. should the hourly automated cluster sanity test fail.
There is a **#validator-support** Discord channel available to reach other 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 ### Machine Requirements
Since the testnet is not intended for stress testing of max transaction 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). Prebuilt binaries are available for Linux x86_64 (Ubuntu 18.04 recommended).
MacOS or WSL users may build from source. 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 #### Confirm The Testnet Is Reachable
Before attaching a validator node, sanity check that the cluster is accessible 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, 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. software on Linux x86_64 systems.
```bash ```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 $ 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 Sanity check that you are able to interact with the cluster by receiving a small
airdrop of lamports from the testnet drone: airdrop of lamports from the testnet drone:
```bash ```bash
$ solana-wallet -n testnet.solana.com airdrop 123 $ solana-wallet airdrop 123
$ solana-wallet -n testnet.solana.com balance $ solana-wallet balance
``` ```
Also try running following command to join the gossip network and view all the other nodes in the cluster: Also try running following command to join the gossip network and view all the other nodes in the cluster:
```bash ```bash
$ solana-gossip --network testnet.solana.com:8001 spy $ solana-gossip --entrypoint testnet.solana.com:8001 spy
# Press ^C to exit # 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: If this is a `solana-install`-installation:
```bash ```bash
$ clear-fullnode-config.sh $ clear-config.sh
$ fullnode.sh --public-address --poll-for-new-genesis-block testnet.solana.com $ 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 Alternatively, the `solana-install run` command can be used to run the validator
node while periodically checking for and applying software updates: node while periodically checking for and applying software updates:
```bash ```bash
$ clear-fullnode-config.sh $ clear-config.sh
$ solana-install run fullnode.sh -- --public-address --poll-for-new-genesis-block testnet.solana.com $ solana-install run validator.sh -- --identity validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
``` ```
If you built from source: If you built from source:
```bash ```bash
$ USE_INSTALL=1 ./multinode-demo/clear-fullnode-config.sh $ USE_INSTALL=1 ./multinode-demo/clear-config.sh
$ USE_INSTALL=1 ./multinode-demo/fullnode.sh --public-address --poll-for-new-genesis-block testnet.solana.com $ USE_INSTALL=1 ./multinode-demo/validator.sh --identity validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
``` ```
#### Controlling local network port allocation #### Controlling local network port allocation
By default the validator will dynamically select available network ports in the By default the validator will dynamically select available network ports in the
8000-10000 range, and may be overridden with `--dynamic-port-range`. For 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 to ports 11000-11011.
### Validator Monitoring ### Validator Monitoring
From another console, confirm the IP address of your validator is visible in the When `validator.sh` starts, it will output a validator configuration that looks
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
similar to: similar to:
```bash ```bash
======================[ Fullnode configuration ]====================== ======================[ validator configuration ]======================
node id: 4ceWXsL3UJvn7NYZiRkw7NsryMpviaKBDYr8GK7J61Dm identity pubkey: 4ceWXsL3UJvn7NYZiRkw7NsryMpviaKBDYr8GK7J61Dm
vote id: 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G vote pubkey: 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
ledger: ... ledger: ...
accounts: ... 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: the recent voting activity from your validator:
```bash ```bash
$ solana-wallet -n testnet.solana.com show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G $ 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 ```bash
# If this is a `solana-install`-installation run: # 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: # 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 ### Sharing Metrics From Your Validator
If you'd like to share metrics perform the following steps before starting the If you have obtained a metrics username/password from the Solana maintainers to
validator node: help us monitor the health of the testnet, please perform the following steps
before starting the validator to activate metrics reporting:
```bash ```bash
export u="username obtained from the Solana maintainers" export u="username obtained from the Solana maintainers"
export p="password 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 ## 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 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 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 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 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. 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 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 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 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 exit 1
fi fi
(
set -x set -x
git clone git@github.com:solana-labs/solana-perf-libs.git target/perf-libs git clone git@github.com:solana-labs/solana-perf-libs.git target/perf-libs
cd target/perf-libs cd target/perf-libs
make -j"$(nproc)" make -j"$(nproc)"
make DESTDIR=. install 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" - "queue=cuda"
- command: "ci/test-bench.sh" - command: "ci/test-bench.sh"
name: "bench" 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" - command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-stable.sh"
name: "stable" name: "stable"
timeout_in_minutes: 30 timeout_in_minutes: 40
artifact_paths: "log-*.txt" artifact_paths: "log-*.txt"
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh" - command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
name: "coverage" name: "coverage"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,19 +13,24 @@ declare prints=(
'println!' 'println!'
'eprint!' 'eprint!'
'eprintln!' 'eprintln!'
'dbg!'
) )
# Parts of the tree that are expected to be print free # Parts of the tree that are expected to be print free
declare print_free_tree=( declare print_free_tree=(
'core/src' 'core/src'
'drone' 'drone/src'
'metrics' 'metrics/src'
'netutil' 'netutil/src'
'runtime' 'runtime/src'
'sdk' '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 exit 1
fi fi
@ -34,7 +39,21 @@ fi
# Default::default() # Default::default()
# #
# Ref: https://github.com/solana-labs/solana/issues/2630 # 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 exit 1
fi 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 runtime
vote-signer vote-signer
core core
fullnode validator
genesis genesis
gossip gossip
ledger-tool ledger-tool

View File

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

View File

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

View File

@ -40,7 +40,10 @@ fi
BENCH_FILE=bench_output.log BENCH_FILE=bench_output.log
BENCH_ARTIFACT=current_bench_results.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" rm -f "$BENCH_FILE"
# Run sdk benches # 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 \ _ 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" -- -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 \ _ 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" upload-ci-artifact "$BENCH_ARTIFACT"

View File

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

View File

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

View File

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

View File

@ -52,14 +52,14 @@ launchTestnet() {
declare q_mean_tps=' declare q_mean_tps='
SELECT round(mean("sum_count")) AS "mean_tps" FROM ( SELECT round(mean("sum_count")) AS "mean_tps" FROM (
SELECT sum("count") AS "sum_count" 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) WHERE time > now() - 300s GROUP BY time(1s)
)' )'
declare q_max_tps=' declare q_max_tps='
SELECT round(max("sum_count")) AS "max_tps" FROM ( SELECT round(max("sum_count")) AS "max_tps" FROM (
SELECT sum("count") AS "sum_count" 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) WHERE time > now() - 300s GROUP BY time(1s)
)' )'

View File

@ -11,15 +11,19 @@ clientNodeCount=0
additionalFullNodeCount=10 additionalFullNodeCount=10
publicNetwork=false publicNetwork=false
stopNetwork=false stopNetwork=false
skipSetup=false reuseLedger=false
skipCreate=false
skipStart=false skipStart=false
externalNode=false externalNode=false
failOnValidatorBootupFailure=true
tarChannelOrTag=edge tarChannelOrTag=edge
delete=false delete=false
enableGpu=false enableGpu=false
bootDiskType="" bootDiskType=""
leaderRotation=true
blockstreamer=false blockstreamer=false
deployUpdateManifest=true
fetchLogs=true
maybeHashesPerTick=
usage() { usage() {
exitcode=0 exitcode=0
@ -46,17 +50,23 @@ Deploys a CD testnet
-c [number] - Number of client bencher nodes (default: $clientNodeCount) -c [number] - Number of client bencher nodes (default: $clientNodeCount)
-u - Include a Blockstreamer (default: $blockstreamer) -u - Include a Blockstreamer (default: $blockstreamer)
-P - Use public network IP addresses (default: $publicNetwork) -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) -g - Enable GPU (default: $enableGpu)
-b - Disable leader rotation
-a [address] - Set the bootstrap fullnode's external IP address to this GCE address -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 [disk-type] - Specify a boot disk type (default None) Use pd-ssd to get ssd on GCE.
-D - Delete the network -D - Delete the network
-r - Reuse existing node/ledger configuration from a -r - Reuse existing node/ledger configuration from a
previous |start| (ie, don't run ./multinode-demo/setup.sh). previous |start| (ie, don't run ./multinode-demo/setup.sh).
-x - External node. Default: false -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 - Skip start. Nodes will still be created or configured, but network software will not be started.
-S - Stop network software without tearing down nodes. -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 Note: the SOLANA_METRICS_CONFIG environment variable is used to configure
metrics metrics
@ -66,7 +76,22 @@ EOF
zone=() 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 case $opt in
h | \?) h | \?)
usage usage
@ -99,9 +124,6 @@ while getopts "h?p:Pn:c:t:gG:a:Dbd:rusxz:p:C:S" opt; do
;; ;;
esac esac
;; ;;
b)
leaderRotation=false
;;
g) g)
enableGpu=true enableGpu=true
;; ;;
@ -119,7 +141,10 @@ while getopts "h?p:Pn:c:t:gG:a:Dbd:rusxz:p:C:S" opt; do
delete=true delete=true
;; ;;
r) r)
skipSetup=true reuseLedger=true
;;
e)
skipCreate=true
;; ;;
s) s)
skipStart=true skipStart=true
@ -127,14 +152,21 @@ while getopts "h?p:Pn:c:t:gG:a:Dbd:rusxz:p:C:S" opt; do
x) x)
externalNode=true externalNode=true
;; ;;
f)
failOnValidatorBootupFailure=false
;;
u) u)
blockstreamer=true blockstreamer=true
;; ;;
S) S)
stopNetwork=true stopNetwork=true
;; ;;
w)
fetchLogs=false
deployUpdateManifest=false
;;
*) *)
usage "Error: unhandled option: $opt" usage "Unknown option: $opt"
;; ;;
esac esac
done done
@ -170,15 +202,15 @@ for val in "${zone[@]}"; do
done done
if $stopNetwork; then if $stopNetwork; then
skipSetup=true skipCreate=true
fi fi
if $delete; then if $delete; then
skipSetup=false skipCreate=false
fi fi
# Create the network # Create the network
if ! $skipSetup; then if ! $skipCreate; then
echo "--- $cloudProvider.sh delete" echo "--- $cloudProvider.sh delete"
# shellcheck disable=SC2068 # shellcheck disable=SC2068
time net/"$cloudProvider".sh delete ${zone_args[@]} -p "$netName" ${externalNode:+-x} time net/"$cloudProvider".sh delete ${zone_args[@]} -p "$netName" ${externalNode:+-x}
@ -212,10 +244,6 @@ if ! $skipSetup; then
fi fi
fi fi
if ! $leaderRotation; then
create_args+=(-b)
fi
if $publicNetwork; then if $publicNetwork; then
create_args+=(-P) create_args+=(-P)
fi fi
@ -224,6 +252,10 @@ if ! $skipSetup; then
create_args+=(-x) create_args+=(-x)
fi fi
if ! $failOnValidatorBootupFailure; then
create_args+=(-f)
fi
time net/"$cloudProvider".sh create "${create_args[@]}" time net/"$cloudProvider".sh create "${create_args[@]}"
else else
echo "--- $cloudProvider.sh config" echo "--- $cloudProvider.sh config"
@ -236,6 +268,14 @@ else
config_args+=(-P) config_args+=(-P)
fi fi
if $externalNode; then
config_args+=(-x)
fi
if ! $failOnValidatorBootupFailure; then
config_args+=(-f)
fi
time net/"$cloudProvider".sh config "${config_args[@]}" time net/"$cloudProvider".sh config "${config_args[@]}"
fi fi
net/init-metrics.sh -e net/init-metrics.sh -e
@ -249,53 +289,54 @@ if $stopNetwork; then
exit 0 exit 0
fi 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 ok=true
if ! $skipStart; then if ! $skipStart; then
( (
if $skipSetup; then if $skipCreate; then
# TODO: Enable rolling updates # TODO: Enable rolling updates
#op=update #op=update
op=restart op=restart
else else
op=start op=start
fi 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/ # 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 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 fi
# shellcheck disable=SC2086 # Don't want to double quote the $maybeXYZ variables # shellcheck disable=SC2086 # Don't want to double quote the $maybeXYZ variables
time net/net.sh $op -t "$tarChannelOrTag" \ time net/net.sh "${args[@]}"
$maybeUpdateManifestKeypairFile \
$maybeSkipSetup \
$maybeRejectExtraNodes \
$maybeNoValidatorSanity \
$maybeNoLedgerVerify
) || ok=false ) || ok=false
if $fetchLogs; then
net/net.sh logs net/net.sh logs
fi fi
fi
$ok $ok

View File

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

1
client/.gitignore vendored Normal file
View File

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

View File

@ -1,6 +1,6 @@
[package] [package]
name = "solana-client" name = "solana-client"
version = "0.14.0" version = "0.15.0"
description = "Solana Client" description = "Solana Client"
authors = ["Solana Maintainers <maintainers@solana.com>"] authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana" repository = "https://github.com/solana-labs/solana"
@ -9,18 +9,18 @@ license = "Apache-2.0"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
bincode = "1.1.2" bincode = "1.1.4"
bs58 = "0.2.0" bs58 = "0.2.0"
log = "0.4.2" log = "0.4.2"
jsonrpc-core = "10.1.0" jsonrpc-core = "10.1.0"
reqwest = "0.9.11" reqwest = "0.9.17"
serde = "1.0.89" serde = "1.0.89"
serde_derive = "1.0.88" serde_derive = "1.0.91"
serde_json = "1.0.39" serde_json = "1.0.39"
solana-netutil = { path = "../netutil", version = "0.14.0" } solana-netutil = { path = "../netutil", version = "0.15.0" }
solana-sdk = { path = "../sdk", version = "0.14.0" } solana-sdk = { path = "../sdk", version = "0.15.0" }
[dev-dependencies] [dev-dependencies]
jsonrpc-core = "10.1.0" jsonrpc-core = "10.1.0"
jsonrpc-http-server = "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; pub mod client_error;
mod generic_rpc_client_request; mod generic_rpc_client_request;
pub mod mock_rpc_client_request; pub mod mock_rpc_client_request;
pub mod perf_utils;
pub mod rpc_client; pub mod rpc_client;
pub mod rpc_client_request; pub mod rpc_client_request;
pub mod rpc_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::generic_rpc_client_request::GenericRpcClientRequest;
use crate::rpc_request::RpcRequest; use crate::rpc_request::RpcRequest;
use serde_json::{Number, Value}; use serde_json::{Number, Value};
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::transaction::{self, TransactionError}; use solana_sdk::transaction::{self, TransactionError};
pub const PUBKEY: &str = "7RoSF9fUmdphVCpabEoefH81WwrW7orsWonXWqTXkKV8"; pub const PUBKEY: &str = "7RoSF9fUmdphVCpabEoefH81WwrW7orsWonXWqTXkKV8";
@ -44,7 +45,10 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
let n = if self.url == "airdrop" { 0 } else { 50 }; let n = if self.url == "airdrop" { 0 } else { 50 };
Value::Number(Number::from(n)) 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 => { RpcRequest::GetSignatureStatus => {
let response: Option<transaction::Result<()>> = if self.url == "account_in_use" { let response: Option<transaction::Result<()>> = if self.url == "account_in_use" {
Some(Err(TransactionError::AccountInUse)) 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_client_request::RpcClientRequest;
use crate::rpc_request::RpcRequest; use crate::rpc_request::RpcRequest;
use bincode::serialize; use bincode::serialize;
use bs58;
use log::*; use log::*;
use serde_json::{json, Value}; use serde_json::{json, Value};
use solana_sdk::account::Account; use solana_sdk::account::Account;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::hash::Hash; use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil, Signature}; use solana_sdk::signature::{KeypairUtil, Signature};
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 solana_sdk::transaction::{self, Transaction, TransactionError}; use solana_sdk::transaction::{self, Transaction, TransactionError};
use std::error; use std::error;
use std::io; use std::io;
@ -78,7 +78,7 @@ impl RpcClient {
pub fn send_and_confirm_transaction<T: KeypairUtil>( pub fn send_and_confirm_transaction<T: KeypairUtil>(
&self, &self,
transaction: &mut Transaction, transaction: &mut Transaction,
signer: &T, signer_keys: &[&T],
) -> Result<String, ClientError> { ) -> Result<String, ClientError> {
let mut send_retries = 5; let mut send_retries = 5;
loop { loop {
@ -97,7 +97,7 @@ impl RpcClient {
if cfg!(not(test)) { if cfg!(not(test)) {
// Retry ~twice during a slot // Retry ~twice during a slot
sleep(Duration::from_millis( 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), Ok(_) => return Ok(signature_str),
Err(TransactionError::AccountInUse) => { Err(TransactionError::AccountInUse) => {
// Fetch a new blockhash and re-sign the transaction before sending it again // 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 send_retries - 1
} }
Err(_) => 0, Err(_) => 0,
@ -127,10 +127,10 @@ impl RpcClient {
} }
} }
pub fn send_and_confirm_transactions( pub fn send_and_confirm_transactions<T: KeypairUtil>(
&self, &self,
mut transactions: Vec<Transaction>, mut transactions: Vec<Transaction>,
signer: &Keypair, signer_keys: &[&T],
) -> Result<(), Box<dyn error::Error>> { ) -> Result<(), Box<dyn error::Error>> {
let mut send_retries = 5; let mut send_retries = 5;
loop { loop {
@ -143,7 +143,7 @@ impl RpcClient {
// Delay ~1 tick between write transactions in an attempt to reduce AccountInUse errors // 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 // when all the write transactions modify the same program account (eg, deploying a
// new program) // 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(); let signature = self.send_transaction(&transaction).ok();
@ -157,7 +157,7 @@ impl RpcClient {
if cfg!(not(test)) { if cfg!(not(test)) {
// Retry ~twice during a slot // Retry ~twice during a slot
sleep(Duration::from_millis( 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; send_retries -= 1;
// Re-sign any failed transactions with a new blockhash and retry // 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)?; self.get_new_blockhash(&transactions_signatures[0].0.message().recent_blockhash)?;
transactions = transactions_signatures transactions = transactions_signatures
.into_iter() .into_iter()
.map(|(mut transaction, _)| { .map(|(mut transaction, _)| {
transaction.sign(&[signer], blockhash); transaction.sign(signer_keys, blockhash);
transaction transaction
}) })
.collect(); .collect();
@ -202,10 +202,11 @@ impl RpcClient {
pub fn resign_transaction<T: KeypairUtil>( pub fn resign_transaction<T: KeypairUtil>(
&self, &self,
tx: &mut Transaction, tx: &mut Transaction,
signer_key: &T, signer_keys: &[&T],
) -> Result<(), ClientError> { ) -> Result<(), ClientError> {
let blockhash = self.get_new_blockhash(&tx.message().recent_blockhash)?; let (blockhash, _fee_calculator) =
tx.sign(&[signer_key], blockhash); self.get_new_blockhash(&tx.message().recent_blockhash)?;
tx.sign(signer_keys, blockhash);
Ok(()) Ok(())
} }
@ -222,31 +223,7 @@ impl RpcClient {
Ok(res) Ok(res)
} }
pub fn get_account_data(&self, pubkey: &Pubkey) -> io::Result<Vec<u8>> { pub fn get_account(&self, pubkey: &Pubkey) -> io::Result<Account> {
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> {
let params = json!([format!("{}", pubkey)]); let params = json!([format!("{}", pubkey)]);
let response = self let response = self
.client .client
@ -257,80 +234,89 @@ impl RpcClient {
let account: Account = let account: Account =
serde_json::from_value(account_json).expect("deserialize account"); serde_json::from_value(account_json).expect("deserialize account");
trace!("Response account {:?} {:?}", pubkey, account); trace!("Response account {:?} {:?}", pubkey, account);
trace!("get_balance {:?}", account.lamports); Ok(account)
Ok(account.lamports)
}) })
.map_err(|error| { .map_err(|err| {
debug!("Response account {}: None (error: {:?})", pubkey, error); io::Error::new(
io::Error::new(io::ErrorKind::Other, "AccountNotFound") 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, /// Request the transaction count. If the response packet is dropped by the network,
/// this method will try again 5 times. /// this method will try again 5 times.
pub fn get_transaction_count(&self) -> io::Result<u64> { pub fn get_transaction_count(&self) -> io::Result<u64> {
debug!("get_transaction_count"); let response = self
.client
let mut num_retries = 5; .send(&RpcRequest::GetTransactionCount, None, 0)
while num_retries > 0 { .map_err(|err| {
let response = self.client.send(&RpcRequest::GetTransactionCount, None, 0); io::Error::new(
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, io::ErrorKind::Other,
"Unable to get transaction count, too many retries", format!("GetTransactionCount request failure: {:?}", err),
))? )
} })?;
pub fn get_recent_blockhash(&self) -> io::Result<Hash> { serde_json::from_value(response).map_err(|err| {
let mut num_retries = 5; io::Error::new(
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, io::ErrorKind::Other,
"Unable to get recent blockhash, too many retries", format!("GetTransactionCount parse failure: {}", err),
)) )
})
} }
pub fn get_new_blockhash(&self, blockhash: &Hash) -> io::Result<Hash> { 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, FeeCalculator)> {
let mut num_retries = 10; let mut num_retries = 10;
while num_retries > 0 { 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 { if new_blockhash != *blockhash {
return Ok(new_blockhash); return Ok((new_blockhash, fee_calculator));
} }
} }
debug!("Got same blockhash ({:?}), will retry...", blockhash); debug!("Got same blockhash ({:?}), will retry...", blockhash);
// Retry ~twice during a slot // Retry ~twice during a slot
sleep(Duration::from_millis( 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; num_retries -= 1;
} }
@ -482,24 +468,22 @@ impl RpcClient {
Some(params.clone()), Some(params.clone()),
1, 1,
) )
.map_err(|error| { .map_err(|err| {
debug!(
"Response get_num_blocks_since_signature_confirmation: {:?}",
error
);
io::Error::new( io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
"GetNumBlocksSinceSignatureConfirmation request failure", format!(
"GetNumBlocksSinceSignatureConfirmation request failure: {}",
err
),
) )
})?; })?;
serde_json::from_value(response).map_err(|error| { serde_json::from_value(response).map_err(|err| {
debug!(
"ParseError: get_num_blocks_since_signature_confirmation: {}",
error
);
io::Error::new( io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
"GetNumBlocksSinceSignatureConfirmation parse failure", format!(
"GetNumBlocksSinceSignatureConfirmation parse failure: {}",
err
),
) )
}) })
} }
@ -606,7 +590,7 @@ mod tests {
// Send erroneous parameter // Send erroneous parameter
let blockhash = rpc_client.retry_make_rpc_request( let blockhash = rpc_client.retry_make_rpc_request(
&RpcRequest::GetRecentBlockhash, &RpcRequest::GetRecentBlockhash,
Some(json!("paramter")), Some(json!("parameter")),
0, 0,
); );
assert_eq!(blockhash.is_err(), true); assert_eq!(blockhash.is_err(), true);
@ -657,7 +641,7 @@ mod tests {
let key = Keypair::new(); let key = Keypair::new();
let to = Pubkey::new_rand(); let to = Pubkey::new_rand();
let blockhash = Hash::default(); 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); let signature = rpc_client.send_transaction(&tx);
assert_eq!(signature.unwrap(), SIGNATURE.to_string()); assert_eq!(signature.unwrap(), SIGNATURE.to_string());
@ -671,16 +655,14 @@ mod tests {
fn test_get_recent_blockhash() { fn test_get_recent_blockhash() {
let rpc_client = RpcClient::new_mock("succeeds".to_string()); let rpc_client = RpcClient::new_mock("succeeds".to_string());
let vec = bs58::decode(PUBKEY).into_vec().unwrap(); let expected_blockhash: Hash = PUBKEY.parse().unwrap();
let expected_blockhash = Hash::new(&vec);
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); assert_eq!(blockhash, expected_blockhash);
let rpc_client = RpcClient::new_mock("fails".to_string()); let rpc_client = RpcClient::new_mock("fails".to_string());
let blockhash = dbg!(rpc_client.get_recent_blockhash()); assert!(rpc_client.get_recent_blockhash().is_err());
assert!(blockhash.is_err());
} }
#[test] #[test]
@ -708,17 +690,17 @@ mod tests {
let key = Keypair::new(); let key = Keypair::new();
let to = Pubkey::new_rand(); let to = Pubkey::new_rand();
let blockhash = Hash::default(); 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(); result.unwrap();
let rpc_client = RpcClient::new_mock("account_in_use".to_string()); 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()); assert!(result.is_err());
let rpc_client = RpcClient::new_mock("fails".to_string()); 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()); assert!(result.is_err());
} }
@ -728,14 +710,13 @@ mod tests {
let key = Keypair::new(); let key = Keypair::new();
let to = Pubkey::new_rand(); let to = Pubkey::new_rand();
let vec = bs58::decode("HUu3LwEzGRsUkuJS121jzkPJW39Kq62pXCTmTa1F9jDL") let blockhash: Hash = "HUu3LwEzGRsUkuJS121jzkPJW39Kq62pXCTmTa1F9jDL"
.into_vec() .parse()
.unwrap(); .unwrap();
let blockhash = Hash::new(&vec); let prev_tx = system_transaction::create_user_account(&key, &to, 50, blockhash);
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);
let mut tx = system_transaction::create_user_account(&key, &to, 50, blockhash, 0);
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, tx);
assert_ne!(prev_tx.signatures, tx.signatures); assert_ne!(prev_tx.signatures, tx.signatures);

View File

@ -4,7 +4,7 @@ use crate::rpc_request::{RpcError, RpcRequest};
use log::*; use log::*;
use reqwest; use reqwest;
use reqwest::header::CONTENT_TYPE; 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::thread::sleep;
use std::time::Duration; use std::time::Duration;
@ -73,7 +73,7 @@ impl GenericRpcClientRequest for RpcClientRequest {
// Sleep for approximately half a slot // Sleep for approximately half a slot
sleep(Duration::from_millis( 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, GetRecentBlockhash,
GetSignatureStatus, GetSignatureStatus,
GetSlotLeader, GetSlotLeader,
GetEpochVoteAccounts,
GetStorageBlockhash, GetStorageBlockhash,
GetStorageEntryHeight, GetStorageSlot,
GetStoragePubkeysForEntryHeight, GetStoragePubkeysForSlot,
GetTransactionCount, GetTransactionCount,
RegisterNode, RegisterNode,
RequestAirdrop, RequestAirdrop,
@ -39,9 +40,10 @@ impl RpcRequest {
RpcRequest::GetRecentBlockhash => "getRecentBlockhash", RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
RpcRequest::GetSignatureStatus => "getSignatureStatus", RpcRequest::GetSignatureStatus => "getSignatureStatus",
RpcRequest::GetSlotLeader => "getSlotLeader", RpcRequest::GetSlotLeader => "getSlotLeader",
RpcRequest::GetEpochVoteAccounts => "getEpochVoteAccounts",
RpcRequest::GetStorageBlockhash => "getStorageBlockhash", RpcRequest::GetStorageBlockhash => "getStorageBlockhash",
RpcRequest::GetStorageEntryHeight => "getStorageEntryHeight", RpcRequest::GetStorageSlot => "getStorageSlot",
RpcRequest::GetStoragePubkeysForEntryHeight => "getStoragePubkeysForEntryHeight", RpcRequest::GetStoragePubkeysForSlot => "getStoragePubkeysForSlot",
RpcRequest::GetTransactionCount => "getTransactionCount", RpcRequest::GetTransactionCount => "getTransactionCount",
RpcRequest::RegisterNode => "registerNode", RpcRequest::RegisterNode => "registerNode",
RpcRequest::RequestAirdrop => "requestAirdrop", RpcRequest::RequestAirdrop => "requestAirdrop",

View File

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

1
core/.gitignore vendored Normal file
View File

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

View File

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

View File

@ -4,31 +4,34 @@ extern crate test;
#[macro_use] #[macro_use]
extern crate solana; extern crate solana;
use log::*;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use rayon::prelude::*; use rayon::prelude::*;
use solana::banking_stage::{create_test_recorder, BankingStage}; use solana::banking_stage::{create_test_recorder, BankingStage};
use solana::blocktree::{get_tmp_ledger_path, Blocktree}; use solana::blocktree::{get_tmp_ledger_path, Blocktree};
use solana::cluster_info::ClusterInfo; use solana::cluster_info::ClusterInfo;
use solana::cluster_info::Node; 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::packet::to_packets_chunked;
use solana::poh_recorder::WorkingBankEntries; use solana::poh_recorder::WorkingBankEntries;
use solana::service::Service; use solana::service::Service;
use solana::test_tx::test_tx;
use solana_runtime::bank::Bank; use solana_runtime::bank::Bank;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::hash; use solana_sdk::hash::hash;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{KeypairUtil, Signature}; use solana_sdk::signature::Signature;
use solana_sdk::system_transaction; 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::iter;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::mpsc::{channel, Receiver}; use std::sync::mpsc::{channel, Receiver};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::time::Duration; use std::time::{Duration, Instant};
use test::Bencher; 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; let mut total = 0;
loop { loop {
let entries = receiver.recv_timeout(Duration::new(1, 0)); let entries = receiver.recv_timeout(Duration::new(1, 0));
@ -47,25 +50,63 @@ fn check_txs(receiver: &Receiver<WorkingBankEntries>, ref_tx_count: usize) {
} }
#[bench] #[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) { fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
solana_logger::setup();
let num_threads = BankingStage::num_threads() as usize; let num_threads = BankingStage::num_threads() as usize;
// a multiple of packet chunk 2X duplicates to avoid races // 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 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 (verified_sender, verified_receiver) = channel();
let (vote_sender, vote_receiver) = channel(); let (vote_sender, vote_receiver) = channel();
let bank = Arc::new(Bank::new(&genesis_block)); let bank = Arc::new(Bank::new(&genesis_block));
let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank)); let to_pubkey = Pubkey::new_rand();
let dummy = system_transaction::transfer( let dummy = system_transaction::transfer(&mint_keypair, &to_pubkey, 1, genesis_block.hash());
&mint_keypair, trace!("txs: {}", txes);
&mint_keypair.pubkey(),
1,
genesis_block.hash(),
0,
);
let transactions: Vec<_> = (0..txes) let transactions: Vec<_> = (0..txes)
.into_par_iter() .into_par_iter()
.map(|_| { .map(|_| {
@ -86,7 +127,6 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
&tx.message.account_keys[0], &tx.message.account_keys[0],
mint_total / txes as u64, mint_total / txes as u64,
genesis_block.hash(), genesis_block.hash(),
0,
); );
let x = bank.process_transaction(&fund); let x = bank.process_transaction(&fund);
x.unwrap(); x.unwrap();
@ -124,25 +164,29 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
&poh_recorder, &poh_recorder,
verified_receiver, verified_receiver,
vote_receiver, vote_receiver,
&leader_schedule_cache,
); );
poh_recorder.lock().unwrap().set_bank(&bank); 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 half_len = verified.len() / 2;
let mut start = 0; 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 || { bencher.iter(move || {
// make sure the transactions are still valid let now = Instant::now();
bank.register_tick(&genesis_block.hash());
for v in verified[start..start + half_len].chunks(verified.len() / num_threads) { 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(); 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(); bank.clear_signatures();
start += half_len; start += half_len;
start %= verified.len(); start %= verified.len();
@ -151,7 +195,7 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
exit.store(true, Ordering::Relaxed); exit.store(true, Ordering::Relaxed);
poh_service.join().unwrap(); poh_service.join().unwrap();
} }
Blocktree::destroy(&ledger_path).unwrap(); let _unused = Blocktree::destroy(&ledger_path);
} }
#[bench] #[bench]
@ -162,19 +206,17 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
// a multiple of packet chunk 2X duplicates to avoid races // a multiple of packet chunk 2X duplicates to avoid races
let txes = 96 * 100 * num_threads * 2; let txes = 96 * 100 * num_threads * 2;
let mint_total = 1_000_000_000_000; 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 (verified_sender, verified_receiver) = channel();
let (vote_sender, vote_receiver) = channel(); let (vote_sender, vote_receiver) = channel();
let bank = Arc::new(Bank::new(&genesis_block)); let bank = Arc::new(Bank::new(&genesis_block));
let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank)); let to_pubkey = Pubkey::new_rand();
let dummy = system_transaction::transfer( let dummy = system_transaction::transfer(&mint_keypair, &to_pubkey, 1, genesis_block.hash());
&mint_keypair,
&mint_keypair.pubkey(),
1,
genesis_block.hash(),
0,
);
let transactions: Vec<_> = (0..txes) let transactions: Vec<_> = (0..txes)
.into_par_iter() .into_par_iter()
.map(|_| { .map(|_| {
@ -211,7 +253,6 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
&tx.message.account_keys[0], &tx.message.account_keys[0],
mint_total / txes as u64, mint_total / txes as u64,
genesis_block.hash(), genesis_block.hash(),
0,
); );
bank.process_transaction(&fund).unwrap(); bank.process_transaction(&fund).unwrap();
}); });
@ -249,7 +290,6 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
&poh_recorder, &poh_recorder,
verified_receiver, verified_receiver,
vote_receiver, vote_receiver,
&leader_schedule_cache,
); );
poh_recorder.lock().unwrap().set_bank(&bank); 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 half_len = verified.len() / 2;
let mut start = 0; let mut start = 0;
let signal_receiver = Arc::new(signal_receiver);
let signal_receiver2 = signal_receiver.clone();
bencher.iter(move || { bencher.iter(move || {
// make sure the transactions are still valid // make sure the transactions are still valid
bank.register_tick(&genesis_block.hash()); bank.register_tick(&genesis_block.hash());
for v in verified[start..start + half_len].chunks(verified.len() / num_threads) { for v in verified[start..start + half_len].chunks(verified.len() / num_threads) {
verified_sender.send(v.to_vec()).unwrap(); verified_sender.send(v.to_vec()).unwrap();
} }
check_txs(&signal_receiver, txes / 2); check_txs(&signal_receiver2, txes / 2);
bank.clear_signatures(); bank.clear_signatures();
start += half_len; start += half_len;
start %= verified.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 zero = Hash::default();
let one = hash(&zero.as_ref()); let one = hash(&zero.as_ref());
let keypair = Keypair::new(); 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 transactions = vec![tx0; 10];
let entries = next_entries(&zero, 1, transactions); 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 //! The `bank_forks` module implments BankForks a DAG of checkpointed Banks
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use solana_metrics::counter::Counter; use solana_metrics::inc_new_counter_info;
use solana_runtime::bank::Bank; use solana_runtime::bank::Bank;
use solana_sdk::timing; use solana_sdk::timing;
use std::ops::Index; use std::ops::Index;
@ -11,6 +11,7 @@ use std::time::Instant;
pub struct BankForks { pub struct BankForks {
banks: HashMap<u64, Arc<Bank>>, banks: HashMap<u64, Arc<Bank>>,
working_bank: Arc<Bank>, working_bank: Arc<Bank>,
root: u64,
} }
impl Index<u64> for BankForks { impl Index<u64> for BankForks {
@ -28,6 +29,7 @@ impl BankForks {
Self { Self {
banks, banks,
working_bank, working_bank,
root: 0,
} }
} }
@ -35,7 +37,8 @@ impl BankForks {
pub fn ancestors(&self) -> HashMap<u64, HashSet<u64>> { pub fn ancestors(&self) -> HashMap<u64, HashSet<u64>> {
let mut ancestors = HashMap::new(); let mut ancestors = HashMap::new();
for bank in self.banks.values() { 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.insert(bank.slot(), set);
} }
ancestors ancestors
@ -46,9 +49,11 @@ impl BankForks {
let mut descendants = HashMap::new(); let mut descendants = HashMap::new();
for bank in self.banks.values() { for bank in self.banks.values() {
let _ = descendants.entry(bank.slot()).or_insert(HashSet::new()); 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 descendants
.entry(parent.slot()) .entry(parent)
.or_insert(HashSet::new()) .or_insert(HashSet::new())
.insert(bank.slot()); .insert(bank.slot());
} }
@ -76,13 +81,14 @@ impl BankForks {
self.banks.get(&bank_slot) 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 mut banks = HashMap::new();
let working_bank = initial_banks[0].clone(); let working_bank = initial_banks[0].clone();
for bank in initial_banks { for bank in initial_banks {
banks.insert(bank.slot(), bank.clone()); banks.insert(bank.slot(), bank.clone());
} }
Self { Self {
root,
banks, banks,
working_bank, working_bank,
} }
@ -102,35 +108,52 @@ impl BankForks {
} }
pub fn set_root(&mut self, root: u64) { pub fn set_root(&mut self, root: u64) {
self.root = root;
let set_root_start = Instant::now(); let set_root_start = Instant::now();
let root_bank = self let root_bank = self
.banks .banks
.get(&root) .get(&root)
.expect("root bank didn't exist in bank_forks"); .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(); root_bank.squash();
let new_tx_count = root_bank.transaction_count();
self.prune_non_root(root); self.prune_non_root(root);
inc_new_counter_info!( inc_new_counter_info!(
"bank-forks_set_root_ms", "bank-forks_set_root_ms",
timing::duration_as_ms(&set_root_start.elapsed()) as usize 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) { fn prune_non_root(&mut self, root: u64) {
let descendants = self.descendants();
self.banks self.banks
.retain(|slot, bank| *slot >= root || bank.is_in_subtree_of(root)) .retain(|slot, _| descendants[&root].contains(slot))
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use solana_sdk::genesis_block::GenesisBlock; use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use solana_sdk::hash::Hash; use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
#[test] #[test]
fn test_bank_forks() { 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 bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank); let mut bank_forks = BankForks::new(0, bank);
let child_bank = Bank::new_from_parent(&bank_forks[0u64], &Pubkey::default(), 1); let child_bank = Bank::new_from_parent(&bank_forks[0u64], &Pubkey::default(), 1);
@ -142,7 +165,7 @@ mod tests {
#[test] #[test]
fn test_bank_forks_descendants() { 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 bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank); let mut bank_forks = BankForks::new(0, bank);
let bank0 = bank_forks[0].clone(); let bank0 = bank_forks[0].clone();
@ -159,7 +182,7 @@ mod tests {
#[test] #[test]
fn test_bank_forks_ancestors() { 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 bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank); let mut bank_forks = BankForks::new(0, bank);
let bank0 = bank_forks[0].clone(); let bank0 = bank_forks[0].clone();
@ -177,7 +200,7 @@ mod tests {
#[test] #[test]
fn test_bank_forks_frozen_banks() { 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 bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank); let mut bank_forks = BankForks::new(0, bank);
let child_bank = Bank::new_from_parent(&bank_forks[0u64], &Pubkey::default(), 1); let child_bank = Bank::new_from_parent(&bank_forks[0u64], &Pubkey::default(), 1);
@ -188,7 +211,7 @@ mod tests {
#[test] #[test]
fn test_bank_forks_active_banks() { 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 bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank); let mut bank_forks = BankForks::new(0, bank);
let child_bank = Bank::new_from_parent(&bank_forks[0u64], &Pubkey::default(), 1); 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, &self,
slot: u64, slot: u64,
tick_height: u64, tick_height: u64,
leader_id: &Pubkey, leader_pubkey: &Pubkey,
entries: &Entry, entries: &Entry,
) -> Result<()>; ) -> Result<()>;
fn emit_block_event( fn emit_block_event(
&self, &self,
slot: u64, slot: u64,
tick_height: u64, tick_height: u64,
leader_id: &Pubkey, leader_pubkey: &Pubkey,
blockhash: Hash, blockhash: Hash,
) -> Result<()>; ) -> Result<()>;
} }
@ -90,7 +90,7 @@ where
&self, &self,
slot: u64, slot: u64,
tick_height: u64, tick_height: u64,
leader_id: &Pubkey, leader_pubkey: &Pubkey,
entry: &Entry, entry: &Entry,
) -> Result<()> { ) -> Result<()> {
let transactions: Vec<Vec<u8>> = serialize_transactions(entry); let transactions: Vec<Vec<u8>> = serialize_transactions(entry);
@ -105,7 +105,7 @@ where
Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true), Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true),
slot, slot,
tick_height, tick_height,
leader_id, leader_pubkey,
json_entry, json_entry,
); );
self.output.write(payload)?; self.output.write(payload)?;
@ -116,7 +116,7 @@ where
&self, &self,
slot: u64, slot: u64,
tick_height: u64, tick_height: u64,
leader_id: &Pubkey, leader_pubkey: &Pubkey,
blockhash: Hash, blockhash: Hash,
) -> Result<()> { ) -> Result<()> {
let payload = format!( let payload = format!(
@ -124,7 +124,7 @@ where
Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true), Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true),
slot, slot,
tick_height, tick_height,
leader_id, leader_pubkey,
blockhash, blockhash,
); );
self.output.write(payload)?; self.output.write(payload)?;
@ -183,10 +183,8 @@ mod test {
let keypair0 = Keypair::new(); let keypair0 = Keypair::new();
let keypair1 = Keypair::new(); let keypair1 = Keypair::new();
let tx0 = let tx0 = system_transaction::transfer(&keypair0, &keypair1.pubkey(), 1, Hash::default());
system_transaction::transfer(&keypair0, &keypair1.pubkey(), 1, Hash::default(), 0); let tx1 = system_transaction::transfer(&keypair1, &keypair0.pubkey(), 2, Hash::default());
let tx1 =
system_transaction::transfer(&keypair1, &keypair0.pubkey(), 2, Hash::default(), 0);
let serialized_tx0 = serialize(&tx0).unwrap(); let serialized_tx0 = serialize(&tx0).unwrap();
let serialized_tx1 = serialize(&tx1).unwrap(); let serialized_tx1 = serialize(&tx1).unwrap();
let entry = Entry::new(&Hash::default(), 1, vec![tx0, tx1]); let entry = Entry::new(&Hash::default(), 1, vec![tx0, tx1]);
@ -208,19 +206,19 @@ mod test {
let tick_height_initial = 0; let tick_height_initial = 0;
let tick_height_final = tick_height_initial + ticks_per_slot + 2; let tick_height_final = tick_height_initial + ticks_per_slot + 2;
let mut curr_slot = 0; 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 { for tick_height in tick_height_initial..=tick_height_final {
if tick_height == 5 { if tick_height == 5 {
blockstream blockstream
.emit_block_event(curr_slot, tick_height - 1, &leader_id, blockhash) .emit_block_event(curr_slot, tick_height - 1, &leader_pubkey, blockhash)
.unwrap(); .unwrap();
curr_slot += 1; curr_slot += 1;
} }
let entry = Entry::new(&mut blockhash, 1, vec![]); // just ticks let entry = Entry::new(&mut blockhash, 1, vec![]); // just ticks
blockhash = entry.hash; blockhash = entry.hash;
blockstream blockstream
.emit_entry_event(curr_slot, tick_height, &leader_id, &entry) .emit_entry_event(curr_slot, tick_height, &leader_pubkey, &entry)
.unwrap(); .unwrap();
expected_entries.push(entry.clone()); expected_entries.push(entry.clone());
entries.push(entry); entries.push(entry);

View File

@ -109,10 +109,10 @@ mod test {
use super::*; use super::*;
use crate::blocktree::create_new_tmp_ledger; use crate::blocktree::create_new_tmp_ledger;
use crate::entry::{create_ticks, Entry}; use crate::entry::{create_ticks, Entry};
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use bincode::{deserialize, serialize}; use bincode::{deserialize, serialize};
use chrono::{DateTime, FixedOffset}; use chrono::{DateTime, FixedOffset};
use serde_json::Value; use serde_json::Value;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::Hash; use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction; use solana_sdk::system_transaction;
@ -121,10 +121,12 @@ mod test {
#[test] #[test]
fn test_blockstream_service_process_entries() { fn test_blockstream_service_process_entries() {
let ticks_per_slot = 5; let ticks_per_slot = 5;
let leader_id = Pubkey::new_rand(); let leader_pubkey = Pubkey::new_rand();
// Set up genesis block and blocktree // 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; genesis_block.ticks_per_slot = ticks_per_slot;
let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_block); let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_block);
@ -146,7 +148,6 @@ mod test {
&keypair.pubkey(), &keypair.pubkey(),
1, 1,
Hash::default(), Hash::default(),
0,
); );
let entry = Entry::new(&mut blockhash, 1, vec![tx]); let entry = Entry::new(&mut blockhash, 1, vec![tx]);
blockhash = entry.hash; blockhash = entry.hash;
@ -161,7 +162,7 @@ mod test {
.write_entries(1, 0, 0, ticks_per_slot, &entries) .write_entries(1, 0, 0, ticks_per_slot, &entries)
.unwrap(); .unwrap();
slot_full_sender.send((1, leader_id)).unwrap(); slot_full_sender.send((1, leader_pubkey)).unwrap();
BlockstreamService::process_entries( BlockstreamService::process_entries(
&slot_full_receiver, &slot_full_receiver,
&Arc::new(blocktree), &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::collections::HashMap;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::path::Path; use std::path::Path;
use std::sync::Arc;
pub mod columns { pub mod columns {
#[derive(Debug)] #[derive(Debug)]
@ -30,6 +31,10 @@ pub mod columns {
#[derive(Debug)] #[derive(Debug)]
/// The erasure meta column /// The erasure meta column
pub struct ErasureMeta; pub struct ErasureMeta;
#[derive(Debug)]
/// The root column
pub struct Root;
} }
pub trait Backend: Sized + Send + Sync { 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 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>; fn raw_iterator_cf(&self, cf: Self::ColumnFamily) -> Result<Self::Cursor>;
@ -112,7 +117,15 @@ pub struct Database<B>
where where
B: Backend, B: Backend,
{ {
backend: B, backend: Arc<B>,
}
#[derive(Debug, Clone)]
pub struct BatchProcessor<B>
where
B: Backend,
{
backend: Arc<B>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -132,7 +145,7 @@ where
B: Backend, B: Backend,
C: Column<B>, C: Column<B>,
{ {
backend: PhantomData<B>, backend: Arc<B>,
column: PhantomData<C>, column: PhantomData<C>,
} }
@ -151,7 +164,7 @@ where
B: Backend, B: Backend,
{ {
pub fn open(path: &Path) -> Result<Self> { pub fn open(path: &Path) -> Result<Self> {
let backend = B::open(path)?; let backend = Arc::new(B::open(path)?);
Ok(Database { backend }) Ok(Database { backend })
} }
@ -170,7 +183,7 @@ where
.get_cf(self.cf_handle::<C>(), C::key(key).borrow()) .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 where
C: Column<B>, C: Column<B>,
{ {
@ -178,7 +191,7 @@ where
.put_cf(self.cf_handle::<C>(), C::key(key).borrow(), data) .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 where
C: Column<B>, 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 where
C: TypedColumn<B>, 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 where
C: Column<B>, C: Column<B>,
{ {
let iter = self let iter = {
.backend if let Some(index) = start_from {
.iterator_cf(self.cf_handle::<C>())? let key = C::key(index);
.map(|(key, value)| (C::index(&key), value.into())); 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>> { pub fn batch(&mut self) -> Result<WriteBatch<B>> {
let db_write_batch = self.backend.batch()?; let db_write_batch = self.backend.batch()?;
let map = self let map = self
@ -259,24 +314,6 @@ where
pub fn write(&mut self, batch: WriteBatch<B>) -> Result<()> { pub fn write(&mut self, batch: WriteBatch<B>) -> Result<()> {
self.backend.write(batch.write_batch) 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> impl<B, C> Cursor<B, C>
@ -333,41 +370,55 @@ where
B: Backend, B: Backend,
C: Column<B>, C: Column<B>,
{ {
pub fn get_bytes(&self, db: &Database<B>, key: C::Index) -> Result<Option<Vec<u8>>> { pub fn get_bytes(&self, key: C::Index) -> Result<Option<Vec<u8>>> {
db.backend.get_cf(self.handle(db), C::key(key).borrow()) self.backend.get_cf(self.handle(), C::key(key).borrow())
} }
pub fn cursor(&self, db: &Database<B>) -> Result<Cursor<B, C>> { pub fn cursor(&self) -> Result<Cursor<B, C>> {
db.cursor() 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>)>> { pub fn iter(
db.iter::<C>() &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 { #[inline]
db.cf_handle::<C>() pub fn handle(&self) -> B::ColumnFamily {
self.backend.cf_handle(C::NAME).clone()
} }
pub fn is_empty(&self, db: &Database<B>) -> Result<bool> { pub fn is_empty(&self) -> Result<bool> {
let mut cursor = self.cursor(db)?; let mut cursor = self.cursor()?;
cursor.seek_to_first(); cursor.seek_to_first();
Ok(!cursor.valid()) Ok(!cursor.valid())
} }
pub fn put_bytes(&self, key: C::Index, value: &[u8]) -> Result<()> {
self.backend
.put_cf(self.handle(), C::key(key).borrow(), value)
} }
impl<B, C> LedgerColumn<B, C> pub fn delete(&self, key: C::Index) -> Result<()> {
where self.backend.delete_cf(self.handle(), C::key(key).borrow())
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 delete(&self, db: &mut Database<B>, key: C::Index) -> Result<()> {
db.backend.delete_cf(self.handle(db), C::key(key).borrow())
} }
} }
@ -376,18 +427,21 @@ where
B: Backend, B: Backend,
C: TypedColumn<B>, C: TypedColumn<B>,
{ {
pub fn get(&self, db: &Database<B>, key: C::Index) -> Result<Option<C::Type>> { pub fn get(&self, key: C::Index) -> Result<Option<C::Type>> {
db.get::<C>(key) if let Some(serialized_value) = self.backend.get_cf(self.handle(), C::key(key).borrow())? {
let value = deserialize(&serialized_value)?;
Ok(Some(value))
} else {
Ok(None)
} }
} }
impl<B, C> LedgerColumn<B, C> pub fn put(&self, key: C::Index, value: &C::Type) -> Result<()> {
where let serialized_value = serialize(value)?;
B: Backend,
C: TypedColumn<B>, self.backend
{ .put_cf(self.handle(), C::key(key).borrow(), &serialized_value)
pub fn put(&self, db: &mut Database<B>, key: C::Index, value: &C::Type) -> Result<()> {
db.put::<C>(key, value)
} }
} }

View File

@ -119,6 +119,40 @@ impl TypedColumn<Kvs> for cf::Orphans {
type Type = bool; 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 { impl Column<Kvs> for cf::SlotMeta {
const NAME: &'static str = super::META_CF; const NAME: &'static str = super::META_CF;
type Index = u64; type Index = u64;

View File

@ -1,4 +1,6 @@
use crate::erasure::{NUM_CODING, NUM_DATA}; use crate::erasure::{NUM_CODING, NUM_DATA};
use solana_metrics::datapoint;
use std::borrow::Borrow;
#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
// The Meta column family // The Meta column family
@ -23,8 +25,6 @@ pub struct SlotMeta {
// True if this slot is full (consumed == last_index + 1) and if every // True if this slot is full (consumed == last_index + 1) and if every
// slot that is a parent of this slot is also connected. // slot that is a parent of this slot is also connected.
pub is_connected: bool, pub is_connected: bool,
// True if this slot is a root
pub is_root: bool,
} }
impl SlotMeta { impl SlotMeta {
@ -38,17 +38,17 @@ impl SlotMeta {
// Should never happen // Should never happen
if self.consumed > self.last_index + 1 { if self.consumed > self.last_index + 1 {
solana_metrics::submit( datapoint!(
solana_metrics::influxdb::Point::new("blocktree_error") "blocktree_error",
.add_field( (
"error", "error",
solana_metrics::influxdb::Value::String(format!( format!(
"Observed a slot meta with consumed: {} > meta.last_index + 1: {}", "Observed a slot meta with consumed: {} > meta.last_index + 1: {}",
self.consumed, self.consumed,
self.last_index + 1 self.last_index + 1
)), ),
String
) )
.to_owned(),
); );
} }
@ -67,7 +67,6 @@ impl SlotMeta {
parent_slot, parent_slot,
next_slots: vec![], next_slots: vec![],
is_connected: slot == 0, is_connected: slot == 0,
is_root: false,
last_index: std::u64::MAX, last_index: std::u64::MAX,
} }
} }
@ -81,9 +80,9 @@ pub struct ErasureMeta {
/// Size of shards in this erasure set /// Size of shards in this erasure set
pub size: usize, pub size: usize,
/// Bitfield representing presence/absence of data blobs /// Bitfield representing presence/absence of data blobs
pub data: u64, data: u64,
/// Bitfield representing presence/absence of coding blobs /// Bitfield representing presence/absence of coding blobs
pub coding: u64, coding: u64,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -104,10 +103,8 @@ impl ErasureMeta {
} }
pub fn status(&self) -> ErasureMetaStatus { pub fn status(&self) -> ErasureMetaStatus {
let (data_missing, coding_missing) = ( let (data_missing, coding_missing) =
NUM_DATA - self.data.count_ones() as usize, (NUM_DATA - self.num_data(), NUM_CODING - self.num_coding());
NUM_CODING - self.coding.count_ones() as usize,
);
if data_missing > 0 && data_missing + coding_missing <= NUM_CODING { if data_missing > 0 && data_missing + coding_missing <= NUM_CODING {
assert!(self.size != 0); assert!(self.size != 0);
ErasureMetaStatus::CanRecover 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 { pub fn is_coding_present(&self, index: u64) -> bool {
if let Some(position) = self.data_index_in_set(index) { if let Some(position) = self.data_index_in_set(index) {
self.coding & (1 << position) != 0 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 { pub fn set_index_for(index: u64) -> u64 {
index / NUM_DATA as u64 index / NUM_DATA as u64
} }
@ -201,7 +226,7 @@ fn test_meta_indexes() {
for _ in 0..100 { for _ in 0..100 {
let set_index = rng.gen_range(0, 1_000); 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)); assert_eq!(set_index, ErasureMeta::set_index_for(blob_index));
let e_meta = ErasureMeta::new(set_index); let e_meta = ErasureMeta::new(set_index);
@ -236,8 +261,8 @@ fn test_meta_indexes() {
fn test_meta_coding_present() { fn test_meta_coding_present() {
let mut e_meta = ErasureMeta::default(); 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 { for i in 0..NUM_CODING as u64 {
e_meta.set_coding_present(i, true);
assert_eq!(e_meta.is_coding_present(i), true); assert_eq!(e_meta.is_coding_present(i), true);
} }
for i in NUM_CODING as u64..NUM_DATA as u64 { 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); 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); e_meta.set_coding_present(i, true);
assert_eq!(e_meta.is_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); assert_eq!(e_meta.is_coding_present(i), false);
} }
} }
#[test] #[test]
fn test_erasure_meta_status() { 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 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)); assert_eq!(e_meta.status(), ErasureMetaStatus::StillNeed(NUM_DATA));
e_meta.data = 0b1111_1111_1111_1111; e_meta.set_data_multi(0..N_DATA, true);
e_meta.coding = 0x00;
assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull); assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull);
e_meta.coding = 0x0e;
e_meta.size = 1; e_meta.size = 1;
e_meta.set_coding_multi(0..N_CODING, true);
assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull); assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull);
e_meta.data = 0b0111_1111_1111_1111; for &idx in data_indexes.choose_multiple(&mut rng, NUM_CODING) {
e_meta.set_data_present(idx, false);
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover); assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
}
e_meta.data = 0b0111_1111_1111_1110; e_meta.set_data_multi(0..N_DATA, true);
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
e_meta.data = 0b0111_1111_1011_1110; for &idx in coding_indexes.choose_multiple(&mut rng, NUM_CODING) {
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover); e_meta.set_coding_present(idx, false);
e_meta.data = 0b0111_1011_1011_1110; assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull);
assert_eq!(e_meta.status(), ErasureMetaStatus::StillNeed(1)); }
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);
} }
#[test] #[test]
fn test_meta_data_present() { fn test_meta_data_present() {
let mut e_meta = ErasureMeta::default(); 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 { for i in 0..NUM_DATA as u64 {
e_meta.set_data_present(i, true);
assert_eq!(e_meta.is_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 { 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); 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 { for i in start_idx..start_idx + NUM_DATA as u64 {
e_meta.set_data_present(i, true);
assert_eq!(e_meta.is_data_present(i), true); 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); 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 byteorder::{BigEndian, ByteOrder};
use rocksdb::{ use rocksdb::{
self, ColumnFamily, ColumnFamilyDescriptor, DBIterator, DBRawIterator, IteratorMode, Options, self, ColumnFamily, ColumnFamilyDescriptor, DBIterator, DBRawIterator, Direction, IteratorMode,
WriteBatch as RWriteBatch, DB, Options, WriteBatch as RWriteBatch, DB,
}; };
use std::fs; use std::fs;
@ -30,7 +30,7 @@ impl Backend for Rocks {
type Error = rocksdb::Error; type Error = rocksdb::Error;
fn open(path: &Path) -> Result<Rocks> { 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)?; fs::create_dir_all(&path)?;
@ -44,6 +44,7 @@ impl Backend for Rocks {
let erasure_meta_cf_descriptor = let erasure_meta_cf_descriptor =
ColumnFamilyDescriptor::new(ErasureMeta::NAME, get_cf_options()); ColumnFamilyDescriptor::new(ErasureMeta::NAME, get_cf_options());
let orphans_cf_descriptor = ColumnFamilyDescriptor::new(Orphans::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![ let cfs = vec![
meta_cf_descriptor, meta_cf_descriptor,
@ -51,6 +52,7 @@ impl Backend for Rocks {
erasure_cf_descriptor, erasure_cf_descriptor,
erasure_meta_cf_descriptor, erasure_meta_cf_descriptor,
orphans_cf_descriptor, orphans_cf_descriptor,
root_cf_descriptor,
]; ];
// Open the database // Open the database
@ -60,13 +62,14 @@ impl Backend for Rocks {
} }
fn columns(&self) -> Vec<&'static str> { 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![ vec![
Coding::NAME, Coding::NAME,
ErasureMeta::NAME, ErasureMeta::NAME,
Data::NAME, Data::NAME,
Orphans::NAME, Orphans::NAME,
Root::NAME,
SlotMeta::NAME, SlotMeta::NAME,
] ]
} }
@ -98,10 +101,17 @@ impl Backend for Rocks {
Ok(()) Ok(())
} }
fn iterator_cf(&self, cf: ColumnFamily) -> Result<DBIterator> { fn iterator_cf(&self, cf: ColumnFamily, start_from: Option<&[u8]>) -> Result<DBIterator> {
let raw_iter = self.0.iterator_cf(cf, IteratorMode::Start)?; 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> { fn raw_iterator_cf(&self, cf: ColumnFamily) -> Result<DBRawIterator> {
@ -170,6 +180,25 @@ impl TypedColumn<Rocks> for cf::Orphans {
type Type = bool; 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 { impl Column<Rocks> for cf::SlotMeta {
const NAME: &'static str = super::META_CF; const NAME: &'static str = super::META_CF;
type Index = u64; 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::entry::{Entry, EntrySlice};
use crate::leader_schedule_cache::LeaderScheduleCache; use crate::leader_schedule_cache::LeaderScheduleCache;
use rayon::prelude::*; 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::bank::Bank;
use solana_runtime::locked_accounts_results::LockedAccountsResults; use solana_runtime::locked_accounts_results::LockedAccountsResults;
use solana_sdk::genesis_block::GenesisBlock; use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::timing::duration_as_ms; use solana_sdk::timing::duration_as_ms;
use solana_sdk::timing::MAX_RECENT_BLOCKHASHES; use solana_sdk::timing::MAX_RECENT_BLOCKHASHES;
use solana_sdk::transaction::Result; use solana_sdk::transaction::Result;
use solana_sdk::transaction::Transaction;
use std::result; use std::result;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
fn first_err(results: &[Result<()>]) -> Result<()> { fn first_err(results: &[Result<()>]) -> Result<()> {
for r in results { for r in results {
r.clone()?; if r.is_err() {
return r.clone();
}
} }
Ok(()) Ok(())
} }
fn par_execute_entries(bank: &Bank, entries: &[(&Entry, LockedAccountsResults)]) -> Result<()> { fn par_execute_entries(
inc_new_counter_info!("bank-par_execute_entries-count", entries.len()); bank: &Bank,
entries: &[(&Entry, LockedAccountsResults<Transaction>)],
) -> Result<()> {
inc_new_counter_debug!("bank-par_execute_entries-count", entries.len());
let results: Vec<Result<()>> = entries let results: Vec<Result<()>> = entries
.into_par_iter() .into_par_iter()
.map(|(e, locked_accounts)| { .map(|(e, locked_accounts)| {
@ -32,21 +38,17 @@ fn par_execute_entries(bank: &Bank, entries: &[(&Entry, LockedAccountsResults)])
MAX_RECENT_BLOCKHASHES, MAX_RECENT_BLOCKHASHES,
); );
let mut first_err = None; 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 let Err(ref e) = r {
if first_err.is_none() { if first_err.is_none() {
first_err = Some(r.clone()); first_err = Some(r.clone());
} }
if !Bank::can_commit(&r) { if !Bank::can_commit(&r) {
warn!("Unexpected validator error: {:?}", e); warn!("Unexpected validator error: {:?}, tx: {:?}", e, tx);
solana_metrics::submit( datapoint_error!(
solana_metrics::influxdb::Point::new("validator_process_entry_error") "validator_process_entry_error",
.add_field( ("error", format!("error: {:?}, tx: {:?}", e, tx), String)
"error", );
solana_metrics::influxdb::Value::String(format!("{:?}", e)),
)
.to_owned(),
)
} }
} }
} }
@ -69,25 +71,47 @@ pub fn process_entries(bank: &Bank, entries: &[Entry]) -> Result<()> {
if entry.is_tick() { if entry.is_tick() {
// if its a tick, execute the group and register the tick // if its a tick, execute the group and register the tick
par_execute_entries(bank, &mt_group)?; par_execute_entries(bank, &mt_group)?;
bank.register_tick(&entry.hash);
mt_group = vec![]; mt_group = vec![];
bank.register_tick(&entry.hash);
continue; continue;
} }
// else loop on processing the entry
loop {
// try to lock the accounts // try to lock the accounts
let lock_results = bank.lock_accounts(&entry.transactions); let lock_results = bank.lock_accounts(&entry.transactions);
// if any of the locks error out
// execute the current group let first_lock_err = first_err(lock_results.locked_accounts_results());
if first_err(lock_results.locked_accounts_results()).is_err() {
par_execute_entries(bank, &mt_group)?; // if locking worked
// Drop all the locks on accounts by clearing the LockedAccountsFinalizer's in the if first_lock_err.is_ok() {
// mt_group
mt_group = vec![];
drop(lock_results);
let lock_results = bank.lock_accounts(&entry.transactions);
mt_group.push((entry, lock_results));
} else {
// push the entry to the mt_group // push the entry to the mt_group
mt_group.push((entry, lock_results)); 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)?; par_execute_entries(bank, &mt_group)?;
@ -131,10 +155,13 @@ pub fn process_blocktree(
vec![(slot, meta, bank, entry_height, last_entry_hash)] 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 fork_info = vec![];
let mut last_status_report = Instant::now(); let mut last_status_report = Instant::now();
let mut root = 0;
while !pending_slots.is_empty() { while !pending_slots.is_empty() {
let (slot, meta, bank, mut entry_height, mut last_entry_hash) = let (slot, meta, bank, mut entry_height, mut last_entry_hash) =
pending_slots.pop().unwrap(); 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 bank.freeze(); // all banks handled by this routine are created from complete slots
if blocktree.is_root(slot) { if blocktree.is_root(slot) {
root = slot;
leader_schedule_cache.set_root(slot);
bank.squash(); bank.squash();
pending_slots.clear();
fork_info.clear();
} }
if meta.next_slots.is_empty() { if meta.next_slots.is_empty() {
@ -217,7 +248,7 @@ pub fn process_blocktree(
let next_bank = Arc::new(Bank::new_from_parent( let next_bank = Arc::new(Bank::new_from_parent(
&bank, &bank,
&leader_schedule_cache &leader_schedule_cache
.slot_leader_at_else_compute(next_slot, &bank) .slot_leader_at(next_slot, Some(&bank))
.unwrap(), .unwrap(),
next_slot, next_slot,
)); ));
@ -245,7 +276,7 @@ pub fn process_blocktree(
} }
let (banks, bank_forks_info): (Vec<_>, Vec<_>) = fork_info.into_iter().unzip(); 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!( info!(
"processing ledger...complete in {}ms, forks={}...", "processing ledger...complete in {}ms, forks={}...",
duration_as_ms(&now.elapsed()), duration_as_ms(&now.elapsed()),
@ -256,12 +287,15 @@ pub fn process_blocktree(
} }
#[cfg(test)] #[cfg(test)]
mod tests { pub mod tests {
use super::*; use super::*;
use crate::blocktree::create_new_tmp_ledger; use crate::blocktree::create_new_tmp_ledger;
use crate::blocktree::tests::entries_to_blobs; use crate::blocktree::tests::entries_to_blobs;
use crate::entry::{create_ticks, next_entry, Entry}; use crate::entry::{create_ticks, next_entry, next_entry_mut, Entry};
use solana_sdk::genesis_block::GenesisBlock; 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::hash::Hash;
use solana_sdk::instruction::InstructionError; use solana_sdk::instruction::InstructionError;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
@ -269,7 +303,7 @@ mod tests {
use solana_sdk::system_transaction; use solana_sdk::system_transaction;
use solana_sdk::transaction::TransactionError; use solana_sdk::transaction::TransactionError;
fn fill_blocktree_slot_with_ticks( pub fn fill_blocktree_slot_with_ticks(
blocktree: &Blocktree, blocktree: &Blocktree,
ticks_per_slot: u64, ticks_per_slot: u64,
slot: u64, slot: u64,
@ -289,7 +323,7 @@ mod tests {
fn test_process_blocktree_with_incomplete_slot() { fn test_process_blocktree_with_incomplete_slot() {
solana_logger::setup(); 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; 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] #[test]
fn test_process_blocktree_with_two_forks() { fn test_process_blocktree_with_two_forks() {
solana_logger::setup(); 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; let ticks_per_slot = genesis_block.ticks_per_slot;
// Create a new ledger with slot 0 full of ticks // 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_fork1_entry.hash: {:?}", last_fork1_entry_hash);
info!("last_fork2_entry.hash: {:?}", last_fork2_entry_hash); info!("last_fork2_entry.hash: {:?}", last_fork2_entry_hash);
blocktree.set_root(0).unwrap(); blocktree.set_root(0, 0).unwrap();
blocktree.set_root(1).unwrap(); blocktree.set_root(1, 0).unwrap();
let (bank_forks, bank_forks_info, _) = let (bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None).unwrap(); process_blocktree(&genesis_block, &blocktree, None).unwrap();
@ -424,6 +532,8 @@ mod tests {
&[1] &[1]
); );
assert_eq!(bank_forks.root(), 1);
// Ensure bank_forks holds the right banks // Ensure bank_forks holds the right banks
for info in bank_forks_info { for info in bank_forks_info {
assert_eq!(bank_forks[info.bank_slot].slot(), info.bank_slot); 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] #[test]
fn test_first_err() { fn test_first_err() {
assert_eq!(first_err(&[Ok(())]), Ok(())); assert_eq!(first_err(&[Ok(())]), Ok(()));
@ -468,7 +635,11 @@ mod tests {
fn test_process_empty_entry_is_registered() { fn test_process_empty_entry_is_registered() {
solana_logger::setup(); 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 bank = Bank::new(&genesis_block);
let keypair = Keypair::new(); let keypair = Keypair::new();
let slot_entries = create_ticks(genesis_block.ticks_per_slot - 1, genesis_block.hash()); let slot_entries = create_ticks(genesis_block.ticks_per_slot - 1, genesis_block.hash());
@ -477,7 +648,6 @@ mod tests {
&keypair.pubkey(), &keypair.pubkey(),
1, 1,
slot_entries.last().unwrap().hash, slot_entries.last().unwrap().hash,
0,
); );
// First, ensure the TX is rejected because of the unregistered last ID // First, ensure the TX is rejected because of the unregistered last ID
@ -495,13 +665,19 @@ mod tests {
fn test_process_ledger_simple() { fn test_process_ledger_simple() {
solana_logger::setup(); solana_logger::setup();
let leader_pubkey = Pubkey::new_rand(); 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); let (ledger_path, mut last_entry_hash) = create_new_tmp_ledger!(&genesis_block);
debug!("ledger_path: {:?}", ledger_path); debug!("ledger_path: {:?}", ledger_path);
let deducted_from_mint = 3;
let mut entries = vec![]; let mut entries = vec![];
let blockhash = genesis_block.hash(); 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 // Transfer one token from the mint to a random account
let keypair = Keypair::new(); let keypair = Keypair::new();
let tx = system_transaction::create_user_account( let tx = system_transaction::create_user_account(
@ -509,7 +685,6 @@ mod tests {
&keypair.pubkey(), &keypair.pubkey(),
1, 1,
blockhash, blockhash,
0,
); );
let entry = Entry::new(&last_entry_hash, 1, vec![tx]); let entry = Entry::new(&last_entry_hash, 1, vec![tx]);
last_entry_hash = entry.hash; last_entry_hash = entry.hash;
@ -523,7 +698,6 @@ mod tests {
&keypair2.pubkey(), &keypair2.pubkey(),
42, 42,
blockhash, blockhash,
0,
); );
let entry = Entry::new(&last_entry_hash, 1, vec![tx]); let entry = Entry::new(&last_entry_hash, 1, vec![tx]);
last_entry_hash = entry.hash; last_entry_hash = entry.hash;
@ -543,6 +717,7 @@ mod tests {
process_blocktree(&genesis_block, &blocktree, None).unwrap(); process_blocktree(&genesis_block, &blocktree, None).unwrap();
assert_eq!(bank_forks_info.len(), 1); assert_eq!(bank_forks_info.len(), 1);
assert_eq!(bank_forks.root(), 0);
assert_eq!( assert_eq!(
bank_forks_info[0], bank_forks_info[0],
BankForksInfo { BankForksInfo {
@ -552,14 +727,19 @@ mod tests {
); );
let bank = bank_forks[1].clone(); 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.tick_height(), 2 * genesis_block.ticks_per_slot - 1);
assert_eq!(bank.last_blockhash(), entries.last().unwrap().hash); assert_eq!(bank.last_blockhash(), entries.last().unwrap().hash);
} }
#[test] #[test]
fn test_process_ledger_with_one_tick_per_slot() { 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; genesis_block.ticks_per_slot = 1;
let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_block); let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_block);
@ -581,7 +761,7 @@ mod tests {
#[test] #[test]
fn test_process_entries_tick() { 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); let bank = Bank::new(&genesis_block);
// ensure bank can process a tick // ensure bank can process a tick
@ -593,7 +773,11 @@ mod tests {
#[test] #[test]
fn test_process_entries_2_entries_collision() { 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 bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new(); let keypair1 = Keypair::new();
let keypair2 = Keypair::new(); let keypair2 = Keypair::new();
@ -606,7 +790,6 @@ mod tests {
&keypair1.pubkey(), &keypair1.pubkey(),
2, 2,
bank.last_blockhash(), bank.last_blockhash(),
0,
); );
let entry_1 = next_entry(&blockhash, 1, vec![tx]); let entry_1 = next_entry(&blockhash, 1, vec![tx]);
let tx = system_transaction::create_user_account( let tx = system_transaction::create_user_account(
@ -614,7 +797,6 @@ mod tests {
&keypair2.pubkey(), &keypair2.pubkey(),
2, 2,
bank.last_blockhash(), bank.last_blockhash(),
0,
); );
let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]); let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]);
assert_eq!(process_entries(&bank, &[entry_1, entry_2]), Ok(())); assert_eq!(process_entries(&bank, &[entry_1, entry_2]), Ok(()));
@ -625,7 +807,11 @@ mod tests {
#[test] #[test]
fn test_process_entries_2_txes_collision() { 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 bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new(); let keypair1 = Keypair::new();
let keypair2 = Keypair::new(); let keypair2 = Keypair::new();
@ -644,7 +830,6 @@ mod tests {
&mint_keypair.pubkey(), &mint_keypair.pubkey(),
1, 1,
bank.last_blockhash(), bank.last_blockhash(),
0,
)], )],
); );
@ -657,14 +842,12 @@ mod tests {
&keypair3.pubkey(), &keypair3.pubkey(),
2, 2,
bank.last_blockhash(), bank.last_blockhash(),
0,
), // should be fine ), // should be fine
system_transaction::create_user_account( system_transaction::create_user_account(
&keypair1, &keypair1,
&mint_keypair.pubkey(), &mint_keypair.pubkey(),
2, 2,
bank.last_blockhash(), bank.last_blockhash(),
0,
), // will collide ), // will collide
], ],
); );
@ -681,7 +864,11 @@ mod tests {
#[test] #[test]
fn test_process_entries_2_txes_collision_and_error() { 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 bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new(); let keypair1 = Keypair::new();
let keypair2 = Keypair::new(); let keypair2 = Keypair::new();
@ -703,14 +890,12 @@ mod tests {
&mint_keypair.pubkey(), &mint_keypair.pubkey(),
1, 1,
bank.last_blockhash(), bank.last_blockhash(),
0,
), ),
system_transaction::transfer( system_transaction::transfer(
&keypair4, &keypair4,
&keypair4.pubkey(), &keypair4.pubkey(),
1, 1,
Hash::default(), // Should cause a transaction failure with BlockhashNotFound Hash::default(), // Should cause a transaction failure with BlockhashNotFound
0,
), ),
], ],
); );
@ -724,14 +909,12 @@ mod tests {
&keypair3.pubkey(), &keypair3.pubkey(),
2, 2,
bank.last_blockhash(), bank.last_blockhash(),
0,
), // should be fine ), // should be fine
system_transaction::create_user_account( system_transaction::create_user_account(
&keypair1, &keypair1,
&mint_keypair.pubkey(), &mint_keypair.pubkey(),
2, 2,
bank.last_blockhash(), bank.last_blockhash(),
0,
), // will collide ), // 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] #[test]
fn test_process_entries_2_entries_par() { 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 bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new(); let keypair1 = Keypair::new();
let keypair2 = Keypair::new(); let keypair2 = Keypair::new();
@ -776,7 +1059,6 @@ mod tests {
&keypair1.pubkey(), &keypair1.pubkey(),
1, 1,
bank.last_blockhash(), bank.last_blockhash(),
0,
); );
assert_eq!(bank.process_transaction(&tx), Ok(())); assert_eq!(bank.process_transaction(&tx), Ok(()));
let tx = system_transaction::create_user_account( let tx = system_transaction::create_user_account(
@ -784,7 +1066,6 @@ mod tests {
&keypair2.pubkey(), &keypair2.pubkey(),
1, 1,
bank.last_blockhash(), bank.last_blockhash(),
0,
); );
assert_eq!(bank.process_transaction(&tx), Ok(())); assert_eq!(bank.process_transaction(&tx), Ok(()));
@ -795,7 +1076,6 @@ mod tests {
&keypair3.pubkey(), &keypair3.pubkey(),
1, 1,
bank.last_blockhash(), bank.last_blockhash(),
0,
); );
let entry_1 = next_entry(&blockhash, 1, vec![tx]); let entry_1 = next_entry(&blockhash, 1, vec![tx]);
let tx = system_transaction::create_user_account( let tx = system_transaction::create_user_account(
@ -803,7 +1083,6 @@ mod tests {
&keypair4.pubkey(), &keypair4.pubkey(),
1, 1,
bank.last_blockhash(), bank.last_blockhash(),
0,
); );
let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]); let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]);
assert_eq!(process_entries(&bank, &[entry_1, entry_2]), Ok(())); assert_eq!(process_entries(&bank, &[entry_1, entry_2]), Ok(()));
@ -814,7 +1093,11 @@ mod tests {
#[test] #[test]
fn test_process_entries_2_entries_tick() { 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 bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new(); let keypair1 = Keypair::new();
let keypair2 = Keypair::new(); let keypair2 = Keypair::new();
@ -827,7 +1110,6 @@ mod tests {
&keypair1.pubkey(), &keypair1.pubkey(),
1, 1,
bank.last_blockhash(), bank.last_blockhash(),
0,
); );
assert_eq!(bank.process_transaction(&tx), Ok(())); assert_eq!(bank.process_transaction(&tx), Ok(()));
let tx = system_transaction::create_user_account( let tx = system_transaction::create_user_account(
@ -835,7 +1117,6 @@ mod tests {
&keypair2.pubkey(), &keypair2.pubkey(),
1, 1,
bank.last_blockhash(), bank.last_blockhash(),
0,
); );
assert_eq!(bank.process_transaction(&tx), Ok(())); 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 // ensure bank can process 2 entries that do not have a common account and tick is registered
let tx = 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 entry_1 = next_entry(&blockhash, 1, vec![tx]);
let tick = next_entry(&entry_1.hash, 1, vec![]); let tick = next_entry(&entry_1.hash, 1, vec![]);
let tx = system_transaction::create_user_account( let tx = system_transaction::create_user_account(
@ -854,7 +1135,6 @@ mod tests {
&keypair4.pubkey(), &keypair4.pubkey(),
1, 1,
bank.last_blockhash(), bank.last_blockhash(),
0,
); );
let entry_2 = next_entry(&tick.hash, 1, vec![tx]); let entry_2 = next_entry(&tick.hash, 1, vec![tx]);
assert_eq!( assert_eq!(
@ -870,7 +1150,6 @@ mod tests {
&keypair3.pubkey(), &keypair3.pubkey(),
1, 1,
bank.last_blockhash(), bank.last_blockhash(),
0,
); );
let entry_3 = next_entry(&entry_2.hash, 1, vec![tx]); let entry_3 = next_entry(&entry_2.hash, 1, vec![tx]);
assert_eq!( assert_eq!(
@ -882,7 +1161,11 @@ mod tests {
#[test] #[test]
fn test_update_transaction_statuses() { fn test_update_transaction_statuses() {
// Make sure instruction errors still update the signature cache // 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 bank = Bank::new(&genesis_block);
let pubkey = Pubkey::new_rand(); let pubkey = Pubkey::new_rand();
bank.transfer(1_000, &mint_keypair, &pubkey).unwrap(); bank.transfer(1_000, &mint_keypair, &pubkey).unwrap();
@ -901,13 +1184,8 @@ mod tests {
); );
// Make sure other errors don't update the signature cache // Make sure other errors don't update the signature cache
let tx = system_transaction::create_user_account( let tx =
&mint_keypair, system_transaction::create_user_account(&mint_keypair, &pubkey, 1000, Hash::default());
&pubkey,
1000,
Hash::default(),
0,
);
let signature = tx.signatures[0]; let signature = tx.signatures[0];
// Should fail with blockhash not found // Should fail with blockhash not found
@ -925,7 +1203,11 @@ mod tests {
#[test] #[test]
fn test_update_transaction_statuses_fail() { 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 bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new(); let keypair1 = Keypair::new();
let keypair2 = Keypair::new(); let keypair2 = Keypair::new();
@ -934,14 +1216,12 @@ mod tests {
&keypair1.pubkey(), &keypair1.pubkey(),
1, 1,
bank.last_blockhash(), bank.last_blockhash(),
0,
); );
let fail_tx = system_transaction::create_user_account( let fail_tx = system_transaction::create_user_account(
&mint_keypair, &mint_keypair,
&keypair2.pubkey(), &keypair2.pubkey(),
2, 2,
bank.last_blockhash(), bank.last_blockhash(),
0,
); );
let entry_1_to_mint = next_entry( let entry_1_to_mint = next_entry(
@ -961,4 +1241,87 @@ mod tests {
// Should not see duplicate signature error // Should not see duplicate signature error
assert_eq!(bank.process_transaction(&fail_tx), Ok(())); 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 //! A stage to broadcast data from a leader node to validators
//! //!
use crate::blocktree::Blocktree; use crate::blocktree::Blocktree;
use crate::cluster_info::{ClusterInfo, ClusterInfoError, NEIGHBORHOOD_SIZE}; use crate::cluster_info::{ClusterInfo, ClusterInfoError, DATA_PLANE_FANOUT};
use crate::entry::{EntrySender, EntrySlice}; use crate::entry::EntrySlice;
use crate::erasure::CodingGenerator; use crate::erasure::CodingGenerator;
use crate::packet::index_blobs_with_genesis; use crate::packet::index_blobs_with_genesis;
use crate::poh_recorder::WorkingBankEntries; use crate::poh_recorder::WorkingBankEntries;
@ -10,8 +10,10 @@ use crate::result::{Error, Result};
use crate::service::Service; use crate::service::Service;
use crate::staking_utils; use crate::staking_utils;
use rayon::prelude::*; use rayon::prelude::*;
use solana_metrics::counter::Counter; use solana_metrics::{
use solana_metrics::{influxdb, submit}; 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::hash::Hash;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_sdk::timing::duration_as_ms; use solana_sdk::timing::duration_as_ms;
@ -27,9 +29,17 @@ pub enum BroadcastStageReturnType {
ChannelDisconnected, ChannelDisconnected,
} }
#[derive(Default)]
struct BroadcastStats {
num_entries: Vec<usize>,
run_elapsed: Vec<u64>,
to_blobs_elapsed: Vec<u64>,
}
struct Broadcast { struct Broadcast {
id: Pubkey, id: Pubkey,
coding_generator: CodingGenerator, coding_generator: CodingGenerator,
stats: BroadcastStats,
} }
impl Broadcast { impl Broadcast {
@ -39,14 +49,13 @@ impl Broadcast {
receiver: &Receiver<WorkingBankEntries>, receiver: &Receiver<WorkingBankEntries>,
sock: &UdpSocket, sock: &UdpSocket,
blocktree: &Arc<Blocktree>, blocktree: &Arc<Blocktree>,
storage_entry_sender: &EntrySender,
genesis_blockhash: &Hash, genesis_blockhash: &Hash,
) -> Result<()> { ) -> Result<()> {
let timer = Duration::new(1, 0); let timer = Duration::new(1, 0);
let (mut bank, entries) = receiver.recv_timeout(timer)?; let (mut bank, entries) = receiver.recv_timeout(timer)?;
let mut max_tick_height = bank.max_tick_height(); 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 num_entries = entries.len();
let mut ventries = Vec::new(); let mut ventries = Vec::new();
let mut last_tick = entries.last().map(|v| v.1).unwrap_or(0); 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 bank_epoch = bank.get_stakers_epoch(bank.slot());
let mut broadcast_table = cluster_info.read().unwrap().sorted_tvu_peers( let mut broadcast_table = cluster_info
&staking_utils::delegated_stakes_at_epoch(&bank, bank_epoch).unwrap(), .read()
); .unwrap()
inc_new_counter_info!("broadcast_service-num_peers", broadcast_table.len() + 1); .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. // 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); inc_new_counter_info!("broadcast_service-entries_received", num_entries);
@ -87,11 +98,9 @@ impl Broadcast {
let blobs: Vec<_> = ventries let blobs: Vec<_> = ventries
.into_par_iter() .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 entries: Vec<_> = p.into_iter().map(|e| e.0).collect();
let blobs = entries.to_shared_blobs(); entries.to_shared_blobs()
let _ignored = s.send(entries);
blobs
}) })
.flatten() .flatten()
.collect(); .collect();
@ -128,33 +137,47 @@ impl Broadcast {
// Send out data // Send out data
ClusterInfo::broadcast(&self.id, contains_last_tick, &broadcast_table, sock, &blobs)?; 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 // send out erasures
ClusterInfo::broadcast(&self.id, false, &broadcast_table, sock, &coding)?; ClusterInfo::broadcast(&self.id, false, &broadcast_table, sock, &coding)?;
let broadcast_elapsed = duration_as_ms(&broadcast_start.elapsed()); self.update_broadcast_stats(
duration_as_ms(&broadcast_start.elapsed()),
inc_new_counter_info!( duration_as_ms(&run_start.elapsed()),
"broadcast_service-time_ms", num_entries,
duration_as_ms(&now.elapsed()) as usize to_blobs_elapsed,
); blob_index,
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(),
); );
Ok(()) 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 // Implement a destructor for the BroadcastStage thread to signal it exited
@ -186,7 +209,6 @@ impl BroadcastStage {
cluster_info: &Arc<RwLock<ClusterInfo>>, cluster_info: &Arc<RwLock<ClusterInfo>>,
receiver: &Receiver<WorkingBankEntries>, receiver: &Receiver<WorkingBankEntries>,
blocktree: &Arc<Blocktree>, blocktree: &Arc<Blocktree>,
storage_entry_sender: EntrySender,
genesis_blockhash: &Hash, genesis_blockhash: &Hash,
) -> BroadcastStageReturnType { ) -> BroadcastStageReturnType {
let me = cluster_info.read().unwrap().my_data().clone(); let me = cluster_info.read().unwrap().my_data().clone();
@ -195,17 +217,13 @@ impl BroadcastStage {
let mut broadcast = Broadcast { let mut broadcast = Broadcast {
id: me.id, id: me.id,
coding_generator, coding_generator,
stats: BroadcastStats::default(),
}; };
loop { loop {
if let Err(e) = broadcast.run( if let Err(e) =
&cluster_info, broadcast.run(&cluster_info, receiver, sock, blocktree, genesis_blockhash)
receiver, {
sock,
blocktree,
&storage_entry_sender,
genesis_blockhash,
) {
match e { match e {
Error::RecvTimeoutError(RecvTimeoutError::Disconnected) | Error::SendError => { Error::RecvTimeoutError(RecvTimeoutError::Disconnected) | Error::SendError => {
return BroadcastStageReturnType::ChannelDisconnected; return BroadcastStageReturnType::ChannelDisconnected;
@ -213,7 +231,7 @@ impl BroadcastStage {
Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (), Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (),
Error::ClusterInfoError(ClusterInfoError::NoPeers) => (), // TODO: Why are the unit-tests throwing hundreds of these? 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); error!("broadcaster error: {:?}", e);
} }
} }
@ -243,7 +261,6 @@ impl BroadcastStage {
receiver: Receiver<WorkingBankEntries>, receiver: Receiver<WorkingBankEntries>,
exit_sender: &Arc<AtomicBool>, exit_sender: &Arc<AtomicBool>,
blocktree: &Arc<Blocktree>, blocktree: &Arc<Blocktree>,
storage_entry_sender: EntrySender,
genesis_blockhash: &Hash, genesis_blockhash: &Hash,
) -> Self { ) -> Self {
let blocktree = blocktree.clone(); let blocktree = blocktree.clone();
@ -258,7 +275,6 @@ impl BroadcastStage {
&cluster_info, &cluster_info,
&receiver, &receiver,
&blocktree, &blocktree,
storage_entry_sender,
&genesis_blockhash, &genesis_blockhash,
) )
}) })
@ -282,9 +298,9 @@ mod test {
use crate::blocktree::{get_tmp_ledger_path, Blocktree}; use crate::blocktree::{get_tmp_ledger_path, Blocktree};
use crate::cluster_info::{ClusterInfo, Node}; use crate::cluster_info::{ClusterInfo, Node};
use crate::entry::create_ticks; use crate::entry::create_ticks;
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use crate::service::Service; use crate::service::Service;
use solana_runtime::bank::Bank; use solana_runtime::bank::Bank;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::Hash; use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil}; use solana_sdk::signature::{Keypair, KeypairUtil};
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
@ -320,9 +336,8 @@ mod test {
let cluster_info = Arc::new(RwLock::new(cluster_info)); let cluster_info = Arc::new(RwLock::new(cluster_info));
let exit_sender = Arc::new(AtomicBool::new(false)); 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)); let bank = Arc::new(Bank::new(&genesis_block));
// Start up the broadcast stage // Start up the broadcast stage
@ -332,7 +347,6 @@ mod test {
entry_receiver, entry_receiver,
&exit_sender, &exit_sender,
&blocktree, &blocktree,
storage_sender,
&Hash::default(), &Hash::default(),
); );

View File

@ -1,12 +1,11 @@
use crate::blocktree::Blocktree; use crate::blocktree::Blocktree;
use solana_storage_api::SLOTS_PER_SEGMENT;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::{BufWriter, Write}; use std::io::{BufWriter, Write};
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use crate::storage_stage::ENTRIES_PER_SEGMENT;
pub const CHACHA_BLOCK_SIZE: usize = 64; pub const CHACHA_BLOCK_SIZE: usize = 64;
pub const CHACHA_KEY_SIZE: usize = 32; pub const CHACHA_KEY_SIZE: usize = 32;
@ -50,8 +49,7 @@ pub fn chacha_cbc_encrypt_ledger(
let mut entry = slice; let mut entry = slice;
loop { 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)) => { Ok((num_entries, entry_len)) => {
debug!( debug!(
"chacha: encrypting slice: {} num_entries: {} entry_len: {}", "chacha: encrypting slice: {} num_entries: {} entry_len: {}",
@ -124,7 +122,6 @@ mod tests {
&keypair.pubkey(), &keypair.pubkey(),
1, 1,
one, one,
0,
)], )],
) )
}) })
@ -156,13 +153,11 @@ mod tests {
let mut hasher = Hasher::default(); let mut hasher = Hasher::default();
hasher.hash(&buf[..size]); hasher.hash(&buf[..size]);
use bs58;
// golden needs to be updated if blob stuff changes.... // golden needs to be updated if blob stuff changes....
let golden = Hash::new( let golden: Hash = "9xb2Asf7UK5G8WqPwsvzo5xwLi4dixBSDiYKCtYRikA"
&bs58::decode("5Pz5KQyNht2nqkJhVd8F9zTFxzoDvbQSzaxQbtCPiyCo") .parse()
.into_vec() .unwrap();
.unwrap(),
);
assert_eq!(hasher.result(), golden); assert_eq!(hasher.result(), golden);
remove_file(out_path).unwrap(); 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, chacha_cbc_encrypt_many_sample, chacha_end_sha_state, chacha_init_sha_state,
}; };
use solana_sdk::hash::Hash; use solana_sdk::hash::Hash;
use solana_storage_api::SLOTS_PER_SEGMENT;
use std::io; use std::io;
use std::mem::size_of; use std::mem::size_of;
use std::sync::Arc; use std::sync::Arc;
use crate::storage_stage::ENTRIES_PER_SEGMENT;
// Encrypt a file with multiple starting IV states, determined by ivecs.len() // 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 // 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); chacha_init_sha_state(int_sha_states.as_mut_ptr(), num_keys as u32);
} }
loop { 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)) => { Ok((num_entries, entry_len)) => {
debug!( debug!(
"chacha_cuda: encrypting segment: {} num_entries: {} entry_len: {}", "chacha_cuda: encrypting segment: {} num_entries: {} entry_len: {}",
@ -78,9 +76,9 @@ pub fn chacha_cbc_encrypt_file_many_keys(
entry += num_entries; entry += num_entries;
debug!( debug!(
"total entries: {} entry: {} segment: {} entries_per_segment: {}", "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; break;
} }
} }

View File

@ -1,6 +1,6 @@
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
pub trait Cluster { pub trait Cluster {
fn get_node_ids(&self) -> Vec<Pubkey>; fn get_node_pubkeys(&self) -> Vec<Pubkey>;
fn restart_node(&mut self, pubkey: 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::service::Service;
use crate::sigverify_stage::VerifiedPackets; use crate::sigverify_stage::VerifiedPackets;
use crate::{packet, sigverify}; 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::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
@ -56,7 +56,7 @@ impl ClusterInfoVoteListener {
let (votes, new_ts) = cluster_info.read().unwrap().get_votes(last_ts); let (votes, new_ts) = cluster_info.read().unwrap().get_votes(last_ts);
if poh_recorder.lock().unwrap().bank().is_some() { if poh_recorder.lock().unwrap().bank().is_some() {
last_ts = new_ts; 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); let msgs = packet::to_packets(&votes);
if !msgs.is_empty() { if !msgs.is_empty() {
let r = if sigverify_disabled { let r = if sigverify_disabled {
@ -82,3 +82,41 @@ impl Service for ClusterInfoVoteListener {
Ok(()) 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::cluster_info::FULLNODE_PORT_RANGE;
use crate::contact_info::ContactInfo; use crate::contact_info::ContactInfo;
use crate::entry::{Entry, EntrySlice}; 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::locktower::VOTE_THRESHOLD_DEPTH;
use crate::poh_service::PohServiceConfig;
use solana_client::thin_client::create_client; use solana_client::thin_client::create_client;
use solana_runtime::epoch_schedule::MINIMUM_SLOT_LENGTH;
use solana_sdk::client::SyncClient; use solana_sdk::client::SyncClient;
use solana_sdk::hash::Hash; use solana_sdk::hash::Hash;
use solana_sdk::poh_config::PohConfig;
use solana_sdk::signature::{Keypair, KeypairUtil, Signature}; use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
use solana_sdk::system_transaction; use solana_sdk::system_transaction;
use solana_sdk::timing::{ 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 solana_sdk::transport::TransportError;
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; 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 /// Spend and verify from every node in the network
pub fn spend_and_verify_all_nodes( pub fn spend_and_verify_all_nodes(
@ -29,7 +31,7 @@ pub fn spend_and_verify_all_nodes(
funding_keypair: &Keypair, funding_keypair: &Keypair,
nodes: usize, 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); assert!(cluster_nodes.len() >= nodes);
for ingress_node in &cluster_nodes { for ingress_node in &cluster_nodes {
let random_keypair = Keypair::new(); let random_keypair = Keypair::new();
@ -38,13 +40,9 @@ pub fn spend_and_verify_all_nodes(
.poll_get_balance(&funding_keypair.pubkey()) .poll_get_balance(&funding_keypair.pubkey())
.expect("balance in source"); .expect("balance in source");
assert!(bal > 0); assert!(bal > 0);
let mut transaction = system_transaction::transfer( let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
&funding_keypair, let mut transaction =
&random_keypair.pubkey(), system_transaction::transfer(&funding_keypair, &random_keypair.pubkey(), 1, blockhash);
1,
client.get_recent_blockhash().unwrap(),
0,
);
let confs = VOTE_THRESHOLD_DEPTH + 1; let confs = VOTE_THRESHOLD_DEPTH + 1;
let sig = client let sig = client
.retry_transfer_until_confirmed(&funding_keypair, &mut transaction, 5, confs) .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()) .poll_get_balance(&funding_keypair.pubkey())
.expect("balance in source"); .expect("balance in source");
assert!(bal > 0); assert!(bal > 0);
let mut transaction = system_transaction::transfer( let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
&funding_keypair, let mut transaction =
&random_keypair.pubkey(), system_transaction::transfer(&funding_keypair, &random_keypair.pubkey(), 1, blockhash);
1,
client.get_recent_blockhash().unwrap(),
0,
);
client client
.retry_transfer(&funding_keypair, &mut transaction, 5) .retry_transfer(&funding_keypair, &mut transaction, 5)
.unwrap(); .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) { 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); assert!(cluster_nodes.len() >= nodes);
for node in &cluster_nodes { for node in &cluster_nodes {
let client = create_client(node.client_facing_addr(), FULLNODE_PORT_RANGE); let client = create_client(node.client_facing_addr(), FULLNODE_PORT_RANGE);
assert!(client.fullnode_exit().unwrap()); assert!(client.fullnode_exit().unwrap());
} }
sleep(Duration::from_millis(SLOT_MILLIS)); sleep(Duration::from_millis(DEFAULT_SLOT_MILLIS));
for node in &cluster_nodes { for node in &cluster_nodes {
let client = create_client(node.client_facing_addr(), FULLNODE_PORT_RANGE); let client = create_client(node.client_facing_addr(), FULLNODE_PORT_RANGE);
assert!(client.fullnode_exit().is_err()); 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( pub fn sleep_n_epochs(
num_epochs: f64, num_epochs: f64,
config: &PohServiceConfig, config: &PohConfig,
ticks_per_slot: u64, ticks_per_slot: u64,
slots_per_epoch: u64, slots_per_epoch: u64,
) { ) {
let num_ticks_per_second = { let num_ticks_per_second = (1000 / duration_as_ms(&config.target_tick_duration)) as f64;
match config {
PohServiceConfig::Sleep(d) => (1000 / duration_as_ms(d)) as f64,
_ => panic!("Unsuppported tick config for testing"),
}
};
let num_ticks_to_sleep = num_epochs * ticks_per_slot as f64 * slots_per_epoch as f64; let num_ticks_to_sleep = num_epochs * ticks_per_slot as f64 * slots_per_epoch as f64;
sleep(Duration::from_secs( let secs = ((num_ticks_to_sleep + num_ticks_per_second - 1.0) / num_ticks_per_second) as u64;
((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( pub fn kill_entry_and_spend_and_verify_rest(
entry_point_info: &ContactInfo, entry_point_info: &ContactInfo,
funding_keypair: &Keypair, funding_keypair: &Keypair,
nodes: usize, nodes: usize,
slot_millis: u64,
) { ) {
solana_logger::setup(); 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); assert!(cluster_nodes.len() >= nodes);
let client = create_client(entry_point_info.client_facing_addr(), FULLNODE_PORT_RANGE); 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"); info!("sleeping for 2 leader fortnights");
sleep(Duration::from_millis( 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!("done sleeping for first 2 warmup epochs");
info!("killing entry point"); info!("killing entry point: {}", entry_point_info.id);
assert!(client.fullnode_exit().unwrap()); assert!(client.fullnode_exit().unwrap());
info!("sleeping for 2 leader fortnights"); info!("sleeping for some time");
sleep(Duration::from_millis( sleep(Duration::from_millis(
SLOT_MILLIS * NUM_CONSECUTIVE_LEADER_SLOTS, slot_millis * NUM_CONSECUTIVE_LEADER_SLOTS,
)); ));
info!("done sleeping for 2 fortnights"); info!("done sleeping for 2 fortnights");
for ingress_node in &cluster_nodes { 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 client = create_client(ingress_node.client_facing_addr(), FULLNODE_PORT_RANGE);
let bal = client let balance = client
.poll_get_balance(&funding_keypair.pubkey()) .poll_get_balance(&funding_keypair.pubkey())
.expect("balance in source"); .expect("balance in source");
assert!(bal > 0); assert_ne!(balance, 0);
let mut result = Ok(()); let mut result = Ok(());
let mut retries = 0; let mut retries = 0;
@ -184,12 +181,12 @@ pub fn kill_entry_and_spend_and_verify_rest(
} }
let random_keypair = Keypair::new(); let random_keypair = Keypair::new();
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
let mut transaction = system_transaction::transfer( let mut transaction = system_transaction::transfer(
&funding_keypair, &funding_keypair,
&random_keypair.pubkey(), &random_keypair.pubkey(),
1, 1,
client.get_recent_blockhash().unwrap(), blockhash,
0,
); );
let confs = VOTE_THRESHOLD_DEPTH + 1; let confs = VOTE_THRESHOLD_DEPTH + 1;

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