Compare commits

...

301 Commits

Author SHA1 Message Date
c53f163ef5 Add RPC api to return program accounts (#4876) (#4912)
automerge
2019-07-02 11:48:00 -07:00
ca35854417 Add .exe extension before checking for a program file on windows (#4902) (#4903)
automerge
2019-07-02 08:48:35 -07:00
ab1fda2a54 Add curl retries 2019-07-02 08:38:18 -07:00
a6ec77c230 Update Cargo.toml 2019-07-01 23:18:15 -07:00
1d7894f1be Avoid signal-hook crate on windows (#4901) 2019-07-01 22:52:49 -07:00
4866a1fc39 run command now kills child process on SIGTERM to cleanly exit (#4896) (#4899)
automerge
2019-07-01 19:28:12 -07:00
60c5e59a5e Always send pull responses to the origin addr (#4894) (#4897)
automerge
2019-07-01 17:34:51 -07:00
fd93bdadf6 Try to gracefully terminal child process before using SIGKILL (#4890) (#4892)
automerge
2019-07-01 14:43:15 -07:00
6089db2a07 Rework fullnode.sh to recover better from genesis block resets (#4884) (#4888)
automerge
2019-07-01 12:31:53 -07:00
462d0cfc6c Disable Enter prompt when stdin is not a tty (#4874) (#4877)
(cherry picked from commit 41bda18046)
2019-06-28 18:18:39 -07:00
e6d6fc4391 Don't prompt the user to update their PATH if --no-modify-path was supplied (#4872) (#4875)
automerge
2019-06-28 17:39:21 -07:00
092556ae5e Lower warn to info, fetch from validator root instead of root + 1 (#4870) (#4873)
automerge
2019-06-28 16:55:46 -07:00
ad9fa54a47 Ensure validator process is killed when fullnode.sh is killed (#4869) (#4871)
automerge
2019-06-28 15:03:46 -07:00
2d68170747 Update cargo.toml files to version 0.16.2 (#4854) 2019-06-27 11:08:02 -06:00
20f3d18458 Save snapshots followed by accounts to avoid stale account data (#4847) (#4849)
automerge
2019-06-26 23:53:14 -07:00
be79efe9b7 rsync of ledger/ and state.tgz now works on both macOS and Linux (#4845) (#4846)
automerge
2019-06-26 22:45:44 -07:00
5db377f743 Use default pubkey for solana-install sanity check 2019-06-26 21:51:10 -07:00
9c2f45a1e0 Fix possible subtract with overflow if mining pool is not setup (#4836)
automerge
2019-06-26 15:28:37 -07:00
8646918d00 Upload all artifacts 2019-06-26 14:38:55 -07:00
7c44fc3561 Set CI_REPO_SLUG correctly for the solana-secondary pipeline 2019-06-26 14:38:55 -07:00
686403eb1d Setup reward pools in genesis (#4831) (#4834)
automerge
2019-06-26 14:29:27 -07:00
6cf9b60a9c Airdrop more token in wallet sanity due to fee (#4830) (#4832)
automerge
2019-06-26 14:05:17 -07:00
aca142df16 Update cargo.toml files to 0.16.1 (#4829) 2019-06-26 13:25:13 -06:00
b2582196db Create snapshots sparsely (#4815) (#4816)
(cherry picked from commit c5e6ebb496)
2019-06-25 12:13:05 -07:00
85a77bec5f Set proper count value for account stores (#4797) (#4813)
* set count values for store accounts

* Use AppendVecId type

(cherry picked from commit 9e7f618cff)
2019-06-25 07:57:40 -07:00
e781cbf4ba Ignore flaky test_two_unbalanced_stakes (#4794) (#4796)
automerge
2019-06-23 21:30:02 -07:00
59956e4543 Remove --storage-mining-pool-lamports (#4792) (#4793)
automerge
2019-06-23 20:53:31 -07:00
303417f981 Lock blockexplorer version 2019-06-22 22:23:16 -07:00
fea03fdf33 Add storage reward pools (#4779) (#4789)
automerge
2019-06-22 21:26:11 -07:00
e8160efc46 Prevent Travis/Appveyor from trying to build mergify branches (#4786) (#4787)
(cherry picked from commit 23b6b85bf0)
2019-06-22 08:58:13 -07:00
e0ba0d581c Update authorized public key (#4783) 2019-06-22 08:44:00 -07:00
36eda29fc9 Add instructions and processor for stake deactivation (#4781)
automerge
2019-06-22 08:43:17 -07:00
2ec73db6bd Program instruction to withdraw un-staked lamports from stake account (#4780) (#4782) 2019-06-22 00:11:15 -07:00
ef6ce2765e Remove storage-mining-pool-keypair arg 2019-06-21 21:38:43 -07:00
8acbb4ab2f Bank cap rpc (#4774)
* core/rpc: Name magic number for minted lamports in tests genesis block

* core/rpc: Expose bank::capitalization() via getSolTotalSupply RPC method

* book: Add entry for getTotalSupply RPC method
2019-06-21 21:00:26 -07:00
a49f5378e2 rewrite vote credits redemption to eat from rewards_pools on an epoch-sensitive basis (#4775)
* move redemption to rewards pools

* rewrite redemption, touch a few other things

* re-establish test coverage
2019-06-21 20:43:24 -07:00
f39e74f0d7 serde the full FeeCalculator (#4778)
automerge
2019-06-21 17:23:26 -07:00
22b767308a Add insturctions to run a replicator on testnet (#4733) 2019-06-21 16:32:23 -07:00
36aa876833 Avoid linking with CUDA directly 2019-06-21 15:26:22 -07:00
06ba0b7279 Remove holding cluster_info lock while forwarding packets (#4773) 2019-06-21 15:21:49 -07:00
a38e1a81ef Call do.sh from anywhere (#4771) 2019-06-21 12:26:17 -07:00
da925142d1 Update replicator ports and silence socket timeout on windows (#4770)
automerge
2019-06-21 11:28:52 -07:00
5feeb257bb Seperate out BPF Loader helpers (#4768) 2019-06-21 11:08:50 -07:00
06c547094a Add Merkle Tree implementation (#4749)
automerge
2019-06-21 10:22:21 -07:00
a40c5cf185 Update storage contract to use a deterministic BTreeMap (#4763) 2019-06-21 09:51:05 -07:00
deb83cdef6 Bump rayon from 1.0.3 to 1.1.0 (#4729)
automerge
2019-06-21 09:32:41 -07:00
20db335aed Bump reqwest from 0.9.17 to 0.9.18
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.9.17 to 0.9.18.
- [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.17...v0.9.18)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-21 08:50:49 -07:00
407db65336 Add 128bit Rust BPF test (#4766)
automerge
2019-06-21 02:43:50 -07:00
9c5a3cd277 Update Rust BPF to v0.1.2 (#4767) 2019-06-21 02:15:42 -07:00
138a49e820 Fix paths (#4764) 2019-06-21 00:08:02 -07:00
36c9e22e3d Revert "Dynamic erasure (#4653)"
This reverts commit ada4d16c4c.
2019-06-20 20:53:03 -07:00
aa0f8538ed Fix client script arguments in the book (#4760) 2019-06-20 19:59:20 -07:00
4177c56c51 Use real panic that reports file/line (#4758) 2019-06-20 19:10:03 -07:00
425ac8d520 Remove need to use null when passing Rust strings (#4756) 2019-06-20 19:09:50 -07:00
ada4d16c4c Dynamic erasure (#4653)
Remove erasure-related constants

Remove unneeded `Iterator::collect` call

Document erasure module

Randomize coding blobs used for repair
2019-06-20 20:27:41 -05:00
4069ef2e02 Install xargo once (#4753) 2019-06-20 16:49:33 -07:00
ace98bba08 Upgrade BPF sysroot to v0.4 (#4754) 2019-06-20 16:41:49 -07:00
e59b53dfa8 BPF rust language updates (#4752) 2019-06-20 16:07:12 -07:00
aacb38864c Mark dead forks in replay stage (#4715)
* Add DeadSlots column family

* Filter dead forks from get_slots_since

* Mark erroring slots as dead in replay stage, add test

* Mark dead forks in progress instead of removing them

* Fix logging process_entries failures in replay_stage

* Unignore test_fail_entry_verification_leader
2019-06-20 15:50:41 -07:00
33d13a3aea Update inflation.rs 2019-06-20 12:37:24 -07:00
1f0f947ed2 add validator rewards pools (#4742)
* add validator rewards pools

* populate rewards syscall
2019-06-20 12:22:29 -07:00
6854c64a09 Bump coverage build timeout 2019-06-20 09:24:11 -07:00
4a32bc48d2 ignore unstable test_repairman_catchup 2019-06-20 09:24:11 -07:00
b430762a23 check rust programs (#4688) 2019-06-20 07:43:31 -07:00
f8523db51d Revert "remove build --all (#4737)" (#4745)
This reverts commit 63503ad589.
2019-06-19 23:21:10 -07:00
48b11d1841 Initialize paths for non existent accounts (#4744) 2019-06-19 23:15:22 -07:00
3600a926df protect against corruption (#4741) 2019-06-19 21:29:36 -07:00
c228792967 Add blocktree and repair_service to SUMMARY.md (#4738) 2019-06-19 20:10:04 -07:00
7ea522e851 add rewards syscall, groom some others (#4740) 2019-06-19 19:46:47 -07:00
63503ad589 remove build --all (#4737)
* remove build  all

* Update test-stable.sh
2019-06-19 17:36:25 -07:00
9800e09431 Thread pool for par_iter in EntrySlice::verify (#4732)
* Use thread pool for entry verify par iter

* some performance metrics

* check batch size and use CPU for smaller batches
2019-06-19 16:31:32 -07:00
2e2b1881f5 move genesis_block to builder pattern (#4736) 2019-06-19 15:40:39 -07:00
61483c18ca Change seed for retransmit to use blob signature (#4727)
* Switch seed for retransmit to use blob signature

* Use seed_len

* Use last bytes of signature as seed instead of first bytes
2019-06-19 15:36:06 -07:00
a5279bb835 Don't ship bench-streamer 2019-06-19 12:37:35 -07:00
357554b209 Cache target/ 2019-06-19 12:37:35 -07:00
41fbdc6e08 use stake (#4721) 2019-06-19 11:54:52 -07:00
8bd1c57448 Defer CUDA selection to env.sh, also always create env.sh 2019-06-19 08:47:27 -07:00
2562e48b9d Check for non zero count value 2019-06-19 08:47:12 -07:00
46bb79df29 Support for custom BroadcastStage in local cluster tests (#4716)
* Refactor BroadcastStage to support custom implementations, add FailEntryVerificationBroadcastRun implementation

* Plumb switch on broadcast type through validator

* Add test for validator generating non-verifiable entries to local_cluster

* Fix bad initializers

* Refactor broadcast run code into utils
2019-06-19 00:13:19 -07:00
6bc0d2a0cb exit with success even if no CUDA version detected 2019-06-18 21:18:13 -07:00
465cd45833 Various Snapshot generation improvements
* Only a single snapshot is maintained to avoid unbounded disk growth
* Snapshot is stored as a compressed tar archive for faster rsyncing
* Any validator node may now generate snapshots
* Updated testnet scripts to generate snapshots on the blockstreamer node
2019-06-18 20:11:09 -07:00
b4484b89c3 ' 2019-06-18 19:13:44 -07:00
c029f069f0 Cache .cargo for faster builds 2019-06-18 19:11:36 -07:00
fdb57bc5db Add Rust BPF Tick Height test (#4718) 2019-06-18 15:56:24 -07:00
e43a634944 Calculate bench client lamports based on signature fee (#4713)
* use fee calculator to compute max fee

* review comments

* shellcheck
2019-06-18 14:44:53 -07:00
2da7c7fbd3 Bump nix from 0.14.0 to 0.14.1 (#4642)
automerge
2019-06-18 11:36:26 -07:00
5683282c94 Update to solana-perf-libs v0.14.0, with support for both CUDA 10.0 and 10.1 2019-06-18 10:41:03 -07:00
44967abd1c update storage len 2019-06-17 22:48:27 -07:00
8b41a5d725 periodically save config in separate folders 2019-06-17 22:48:27 -07:00
07c183bb84 Fix test 2019-06-17 22:48:27 -07:00
7fd879b417 Restart validator nodes from snapshots 2019-06-17 22:48:27 -07:00
dc5c6e7cf8 validator restart 2019-06-17 22:48:27 -07:00
bd633d2b81 Add CI_REPO_SLUG (#4714)
automerge
2019-06-17 20:42:09 -07:00
feeaad619a Avoid panic if no rpc peers exist 2019-06-17 19:47:45 -07:00
b44d8c394e Only add --mining-pool arg when a mining pool keypair exists 2019-06-17 19:47:45 -07:00
0ff9c4cd8e add stake warmup and cool down (#4711) 2019-06-17 19:34:21 -07:00
9cafd1f85e Change check_txs to ignore recv errors and re-enable test (#4593)
Use more chunks to avoid duplicate signature failures:
Duplicate signatures can occur because bank.clear_signatures()
can occur before the bank has actually committed the signatures
to the status cache and then error out on the next set of transactions.
2019-06-17 19:04:21 -07:00
7fe10ba060 Don't start drone if primordial accounts are created for nodes (#4704)
* disable wallet sanity if no airdrops
2019-06-17 18:15:22 -07:00
cc48773b03 Add "download from replicator" utility (#4709)
automerge
2019-06-17 18:12:13 -07:00
8fbf0e2d9f Update replicators to use the storage blockhash to generate offsets (#4712) 2019-06-17 16:39:26 -07:00
d86358eedc add Account::new_data (#4701)
* add account_new_data

* fixup

* fixup
2019-06-17 15:58:05 -07:00
fe04fb4cd3 Refetch perf-libs when the release version is changed (#4706)
automerge
2019-06-17 14:31:41 -07:00
de3f7e9634 Update Rust program build script paths (#4707) 2019-06-17 14:24:00 -07:00
5e8fcdbe1d Set {min,max}_lamports_per_signature correctly when fees don't adjust (#4705)
automerge
2019-06-17 13:18:12 -07:00
3ee7256c0c Add cuda docs 2019-06-17 12:42:59 -07:00
2a7a9fdf03 Re-org SDK dir (#4690) 2019-06-17 11:04:38 -07:00
5bf87de136 Add obvious log message indicating CUDA feature state 2019-06-17 11:01:55 -07:00
97a136ea20 Set rustc-cfg=cuda explicitly, also code cleanup 2019-06-17 11:01:55 -07:00
735dfab02e decomma 2019-06-17 11:01:55 -07:00
b5f65ce49c Link cuda feature validator/ to core/ 2019-06-17 11:01:55 -07:00
a283863694 Add storage-mining-pool genesis params 2019-06-14 20:25:39 -07:00
25908feef9 Fund accounts with the worst-case fee 2019-06-14 19:52:44 -07:00
b91ad6fd96 Clear C dependency files from cache (#4692) 2019-06-14 19:11:16 -07:00
02abf422df Serialize genesis block using bincode (#4687)
* use mmap to read the genesis block, and deserialize
2019-06-14 14:22:52 -07:00
3fe5f886d7 change store to store_account (#4689) 2019-06-14 13:34:15 -07:00
4c6a6d63bf add MiningPools, fund validator MiningPools from inflation (#4676)
* add MiningPool fund validator MinigPools from inflation

* fixup

* finish rename of MINIMUM_SLOT_LENGTH to MINIMUM_SLOTS_PER_EPOCH

* deterministic miningpool location

* point_value, not credit_value... use f64
2019-06-14 11:38:37 -07:00
589a9d3a72 Create aligned number of keypairs so they all get funded (#4685) 2019-06-14 11:11:52 -07:00
bd884a56bf Install libssl1.1 better 2019-06-14 08:01:22 -07:00
119467df59 Add storage mining pool to genesis and implement automatic reward redeeming (#4683)
* Add storage mining pool to genesis and implement automatic reward collection

* Address review comments
2019-06-13 22:30:51 -07:00
ee68b9800e Wait for nodes to boot up before launching other nodes and client (#4682)
* Wait for nodes to bootup in testnet

* increase timeout (as with multiple clients it takes even longer)
2019-06-13 19:37:36 -07:00
c6b4a3a706 Witness account data in Budget (#4650)
* Add support for contracts based on account data to Budget

* Add program_id to account constraints

* No longer require a signature for the account data witness

* Rename bank::store to store_account

* fmt

* Add a doc

* clippy
2019-06-13 18:20:28 -07:00
b1ac8f933b Fix storage program space issues and limit storage transaction data (#4677) 2019-06-13 17:53:54 -07:00
9e3758983d Find max root and purge roots below it. (#4645)
* Test for forking accounts

* Find max root and purge roots below it.
2019-06-13 17:35:16 -07:00
34c0537e9b update book with staking changes (#4679) 2019-06-13 16:24:03 -07:00
8628f33d0b Fix HostId field in the testnet dashboard 2019-06-13 16:09:09 -07:00
ed05aeaef8 Permit datapoints with no fields 2019-06-13 16:09:09 -07:00
e1444a9b00 Add curl retries 2019-06-13 15:05:07 -07:00
9514169bf6 Ensure volume mountpoints exist 2019-06-13 15:05:07 -07:00
fa8394f526 Initial documentation for validator metrics 2019-06-13 15:05:07 -07:00
1cd8c1865e Generate random passwords and keep them out of the environment/program args 2019-06-13 12:37:39 -07:00
e3f895d7d4 Create bench exchange accounts in genesis block (#4655)
* fix script

* review comments
2019-06-13 11:51:35 -07:00
8abf22f34b Temporarily revert: Convert System Transfer accounts to credit-only (#4670) 2019-06-13 11:01:09 -06:00
a016bc2736 Add infra to publish metrics tarball 2019-06-13 10:00:24 -07:00
470debef16 Inline metrics/scripts dependencies 2019-06-13 10:00:24 -07:00
c147dc3028 Update README 2019-06-13 10:00:24 -07:00
bdd95b2286 Generate local dashboard to avoid duplication 2019-06-13 10:00:24 -07:00
efe676bc94 Minor script refactoring/refinement 2019-06-13 10:00:24 -07:00
fc34687687 Create write-only user, default to custom grafana 2019-06-13 10:00:24 -07:00
6042ccf496 Streamline grafana.ini 2019-06-13 10:00:24 -07:00
f1197e1b1f Adjust datasource name 2019-06-13 10:00:24 -07:00
8c1b9a0b67 Data plane verification (#4639)
* Add signature to blob

* Change Signable trait to support returning references to signable data

* Add signing to broadcast

* Verify signatures in window_service

* Add testing for signatures to erasure

* Add RPC for getting current slot, consume RPC call in test_repairman_catchup for more deterministic results
2019-06-12 16:43:05 -07:00
0da9ac1a47 Remove unnecessary parameter element (#4666)
* Stop passing pubkey ref unnecessarily

* Cargo.lock
2019-06-12 16:18:27 -06:00
c1f316721a Clean up some error handling (#4667)
Shouldn't call exit from a library function.
2019-06-12 15:01:59 -07:00
8e86014311 Update stakers_slot_offset if slots_per_epoch is adjusted (#4660) 2019-06-12 14:12:17 -07:00
d807217be7 Simplify and camelCase getEpochVoteAccounts RPC API (#4658)
* Simplify and camelCase getEpochVoteAccounts RPC API

* Set a commission for testing
2019-06-12 14:12:08 -07:00
bc44516eb4 Add test to exercise more args then registers (#4661) 2019-06-12 13:04:53 -07:00
b78a13d42c Nits (#4662) 2019-06-12 13:04:24 -07:00
0dcdc37fec Split BPF loader to match the rest of the programs (#4636) 2019-06-12 08:49:59 -07:00
dd1c3514a8 Use auto hashes-per-tick config for testnet testnet 2019-06-12 08:40:56 -07:00
767efab941 add inflation to genesis (#4652)
* add inflation to genesis

* avoid having to write new()
2019-06-11 21:42:31 -07:00
288a3bdcd9 Provision bench client accounts in genesis block (#4648)
* fixes to script

* shellcheck

* address review comments
2019-06-11 18:47:35 -07:00
8019bff391 Fixes for storage program and rework storage stage (#4654)
automerge
2019-06-11 18:27:47 -07:00
575a897ffc track market cap (#4643)
* track market cap

* fixup, rebase

* prettier
2019-06-11 17:04:13 -07:00
697228a484 rpc vote_accounts by ecurrent pocch, not stakers epoch (#4651) 2019-06-11 16:57:47 -07:00
ca907f37c3 fix cuda testnet compilation errors (#4649) 2019-06-11 15:30:39 -07:00
439e7cc26a Add dependent crate test (#4647)
automerge
2019-06-11 11:45:12 -07:00
3217a1d70c use highest staked node as bootstrap leader, remove bootstrap_leader from genesis_block (#4635)
* use highest staked node as bootstrap leader, remove bootstrap_leader from genesis_block

* clippy

* fixup

* fixup
2019-06-11 11:44:58 -07:00
6dbba86cc6 Cleanup rust-utils (#4646)
automerge
2019-06-11 11:42:30 -07:00
8cc863ea6c Bump libloading from 0.5.0 to 0.5.1 (#4640)
Bumps [libloading](https://github.com/nagisa/rust_libloading) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/nagisa/rust_libloading/releases)
- [Commits](https://github.com/nagisa/rust_libloading/compare/0.5.0...0.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-11 11:21:45 -07:00
1d957b6b80 Bump byteorder from 1.3.1 to 1.3.2 (#4641)
Bumps [byteorder](https://github.com/BurntSushi/byteorder) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/BurntSushi/byteorder/releases)
- [Changelog](https://github.com/BurntSushi/byteorder/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/byteorder/compare/1.3.1...1.3.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-11 11:21:26 -07:00
e56430c9fb make runtime depend on bpf_loader (#4601)
* make runtime depend on bpf_loader

* remove vote redundancy, move bpf_loader to genesis, export program\! from bpf_loader crate

* move bpf_loader specification into genesis

* bpf tests to use genesis with bpf

* need to avoid depending on programs, except for macros
2019-06-11 10:27:22 -07:00
e4d8ea11ac Make lamports_per_signature dynamic based on cluster load (#4562)
* Make lamports_per_signature dynamic based on cluster load

* Move transaction-fees.md to implemented
2019-06-10 22:18:32 -07:00
a4035a3c65 Remove record locks and parent locks from accounts (#4633)
* Revert "Fix parent record locks usage in child banks (#4159)"

This reverts commit 69eeb7cf08.

* Revert "Fix DuplicateSignatures caused by races on frozen banks (#3819)"

This reverts commit 083090817a.

* Remove unused imports
2019-06-10 22:05:46 -07:00
807c69d97c Slimmer implementation of credit-only accounts (#4592)
* Add credit-only debit/data check to verify_instruction

* Store credits and pass to accounts_db

* Add InstructionErrors and tests

* Relax account locks for credit-only accounts

* Collect credit-only account credits before passing to accounts_db to store properly

* Convert System Transfer  accounts to credit-only, and fixup test

* Functionalize collect_accounts to unit test

* Review comments

* Rebase
2019-06-10 20:50:02 -06:00
9259d342ac Facility to provision primordial accounts for fullnodes in genesis block (#4631)
* updated usage

* shellcheck

* support replicators

* disable airdrops if primordial accounts are used

* review comments
2019-06-10 19:42:49 -07:00
b4d4edb645 Restore cargo install to work around --features= 'feature' (#4627) 2019-06-10 18:49:08 -07:00
966b6999d1 Accounts index opt (#4621)
* Add accounts_index bench

* Don't take the accounts index lock unless needed

* Accounts_index remove insert return vec and add capacity stats

* Use hashbrown hashmap for accounts_index
2019-06-10 18:15:39 -07:00
73491e3ca1 bump libssl (#4634) 2019-06-10 18:03:13 -07:00
d1d53c3fb6 calculate stake from activated amount (#4630) 2019-06-10 16:17:29 -07:00
a77e576cd9 void key 2019-06-10 15:54:32 -07:00
9e14cde461 Revert "Fix roots never being purged (#4134)" (#4628)
automerge
2019-06-10 14:08:09 -07:00
a2a7c86c0d Move Testnet Participation under Getting Started 2019-06-10 13:53:31 -07:00
38aeed02fc Ignore dependabot branches 2019-06-10 12:50:48 -07:00
64d63966c7 Bump jsonrpc crates to 12.0.0 (#4553)
* Bump jsonrpc-pubsub from 11.0.0 to 12.0.0

Bumps [jsonrpc-pubsub](https://github.com/paritytech/jsonrpc) from 11.0.0 to 12.0.0.
- [Release notes](https://github.com/paritytech/jsonrpc/releases)
- [Commits](https://github.com/paritytech/jsonrpc/commits/v12.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* Update all jsonrpc crates
2019-06-10 13:22:57 -06:00
38ae54b720 Bump walkdir from 2.2.7 to 2.2.8 (#4615)
Bumps [walkdir](https://github.com/BurntSushi/walkdir) from 2.2.7 to 2.2.8.
- [Release notes](https://github.com/BurntSushi/walkdir/releases)
- [Commits](https://github.com/BurntSushi/walkdir/compare/2.2.7...2.2.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-06-10 12:19:45 -07:00
a18c0e34f4 add activate_stake to stake_api (#4600) 2019-06-10 12:17:29 -07:00
be3a0b6b10 Build/clean all (#4626)
automerge
2019-06-10 11:15:28 -07:00
9f6496d38a Panic filename (#4625)
automerge
2019-06-10 11:00:15 -07:00
1fa31c9410 .iter fixed, drop enumerate where not needed (#4624)
automerge
2019-06-10 10:19:58 -07:00
2b5e757d57 Adjust slack notification 2019-06-10 07:44:31 -07:00
0dbe5ee559 Add chacha-sys crate (#4620)
* af9ff9c7f9/src/cpu-crypt

* Add chacha-sys crate

* Remove chacha feature

* Remove erasure feature

* Add .gitignore
2019-06-10 07:14:02 -07:00
6926e89e86 Minor doc update 2019-06-08 19:36:37 -07:00
ec0007217d Modify HKEY_CURRENT_USER\Environment\PATH on Windows (#4614)
Thanks for the Windows registry editing code rustup.rs!
2019-06-08 19:25:02 -07:00
91b23f8316 Switch from solana-install to solana-install-init 2019-06-08 19:24:36 -07:00
2fd8d57504 Print path normally to avoid forward slash escaping 2019-06-08 19:22:17 -07:00
0595109f98 Add solana-install-init binary (#4613)
* Add solana-install-init binary

* Add Enter prompt on solana-install-init exit for Windows users
2019-06-08 19:01:22 -07:00
9f46b2a6ce Avoid weird paths on Windows (#4612)
automerge
2019-06-08 17:23:34 -07:00
a357d08524 Avoid unnecessary re-downloading if |solana-install init| is invoked repeatedly (#4611) 2019-06-08 17:15:16 -07:00
177c9cc026 -f 2019-06-08 16:33:22 -07:00
0c4cb76acf Add GPU based PoH verification (#4524)
* Add GPU poh verify

* Switch to single PoH verify function

* Add EntrySlice verify tests with hashes and txs

* Add poh-verify benchmarks
2019-06-08 10:21:43 -06:00
8676b5d40c Use more -f 2019-06-07 22:18:55 -07:00
efab896c9e Ignore unencrypted file 2019-06-07 21:38:30 -07:00
97b9d57b62 shellcheck 2019-06-07 21:35:51 -07:00
487826a539 Deploy windows updates 2019-06-07 20:46:58 -07:00
4acb764589 Pick up .exe extension 2019-06-07 20:13:47 -07:00
9de4c1dcd9 Add slack notification 2019-06-07 19:58:52 -07:00
e8c4302d6d Add Appveyor CI for Windows release artifacts 2019-06-07 19:47:26 -07:00
a9f73ea321 solana-keygen no longer blindly overwrites a keypair, or assumes "new" (#4599)
automerge
2019-06-07 17:54:54 -07:00
66c41b3e8c Enable iter test (#4542)
automerge
2019-06-07 16:44:14 -07:00
8435fbfa0b Travis window support is too unstable, disable for now 2019-06-07 15:17:27 -07:00
9a4c449135 Builtins (#4594)
automerge
2019-06-07 14:38:49 -07:00
ac6dbf8f04 Broadcast blobs even if the peers have no stake (#4597) 2019-06-07 14:12:27 -07:00
b55927370b Restore OS -> TARGET map 2019-06-07 13:22:40 -07:00
002fbc4d53 Add |wallet fees| subcommand for easy viewing of the current cluster fees (#4596) 2019-06-07 13:11:56 -07:00
53deb7919c Update book for Turbine (#4583) 2019-06-07 13:03:05 -07:00
8e46c44f3e Deploy mac OS solana-install update package to testnets (#4595) 2019-06-07 12:59:58 -07:00
37c2fa1d8d add current to bank syscalls (#4581) 2019-06-07 11:41:34 -07:00
fdaa939892 Bring in Travis CI to build Windows and mac OS release binaries (#4591)
* Bring in Travis CI to build Windows and mac OS release binaries

* Update .travis.yml
2019-06-07 11:32:47 -07:00
c9d63204eb Replace unneeded seqcst with relaxed on atomic operations (#4587) 2019-06-06 23:53:21 -07:00
cfab54511b Ignore bench_banking_stage_multi_accounts (#4590)
automerge
2019-06-06 21:47:07 -07:00
492cc93850 Limit short_vec length to u16, usize is overkill for our usage (#4588) 2019-06-06 20:18:41 -07:00
fd9fd43e83 add solana_name_id, reassociate names with modules, modularize id tests (#4580) 2019-06-06 19:27:49 -07:00
191483f4ee Facility to add accounts with specific balance to genesis block (#4585)
* Facility to add accounts with specific balance to genesis block

* address review comments
2019-06-06 19:24:09 -07:00
688f8a669a Add a storage accounts cache to Bank (#4578) 2019-06-06 17:40:01 -07:00
46eea85022 Improve error message 2019-06-06 17:06:56 -07:00
1c765124e7 Clean up .configured flag handling to work with an external identity keypair (#4579)
automerge
2019-06-06 14:51:48 -07:00
194491ae96 Removed some dead code (#4563)
* Removed some dead code

* remove dead code from Replicator
2019-06-06 14:26:12 -07:00
2ae595294c fullnode.rs: restart the node correctly on non-zero exit 2019-06-06 13:46:46 -07:00
ead947e710 Change default setting for real PoH in testnet scripts (#4573) 2019-06-06 12:49:46 -07:00
82df267ec9 s/avalanche/turbine (#4561)
* s/avalanche/turbine/g
2019-06-06 12:48:40 -07:00
53275cc678 Introduce normalized CI environment vars: ci/env.sh (#4571) 2019-06-06 12:20:47 -07:00
44835a91db Update PoRep entry in the book (#4560)
* Rework PoRep design doc

* Define the stages of the PoRep game

* Add that the stages are really transactions

* Update turn count

* Review comment + clarification

* More clarification

* Rephrase for clarity
2019-06-06 12:16:54 -07:00
ee42040e6b Give coverage build more time (#4572)
automerge
2019-06-06 11:07:32 -07:00
2b98a16ec6 Upgrade to rust stable 1.35.0 (#4568) 2019-06-06 09:24:38 -07:00
aa4a7b0c73 Disable |solana-install| check for edge/beta testnets (#4564)
The release tarball URL changes for these testnets, which causes the
normal |solana-install| check to fail and the testnet is unnecessarily
rebooted.
2019-06-05 15:31:29 -07:00
8f50c3dd2e Be explicit about return status 2019-06-05 14:12:06 -07:00
9c47ce30a7 shift 2019-06-05 12:06:54 -07:00
3433b08b8c remove unnecessary wrapper (#4559) 2019-06-05 11:43:41 -07:00
d26fd27bf9 Avoid sudo in tune-system.sh unless requested by the user (#4556) 2019-06-05 09:10:23 -07:00
5c98c1d306 Sanity check that runs on the blockstreamer node now checks that node instead of the bootstrap leader (#4551)
automerge
2019-06-04 22:46:48 -07:00
51aacfe3ca Use pure-rust reed-solomon-erasure for windows (#4548) 2019-06-04 21:49:27 -07:00
82bd2df986 Use Library::new() for windows (#4544) 2019-06-04 21:49:05 -07:00
aa88c40a9e multi_bind_in_range(): limit to 1 socket in windows (#4549) 2019-06-04 20:55:02 -07:00
8ec5a47027 Add EntryWriter::write() stub for windows (#4546) 2019-06-04 20:15:37 -07:00
5bd3eb4557 Up number of threads (#4541) 2019-06-04 18:01:28 -07:00
e9cb4a12dc Bump serde_derive from 1.0.91 to 1.0.92 (#4505)
automerge
2019-06-04 15:48:23 -07:00
de5cad9211 Add account owner to Storage Accounts (#4537)
* Add account owner to Storage Accounts

* Fix tests
2019-06-04 14:52:52 -07:00
e3365529de Enable transaction fees for multinode-demo/ and net/ (#4527)
* Collect fees at the end of a slot

* Enable transaction fees for multinode-demo/ and net/
2019-06-04 14:51:52 -07:00
ce2ce76958 Bump serde from 1.0.91 to 1.0.92 (#4504)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.91 to 1.0.92.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.91...v1.0.92)
2019-06-04 14:32:09 -07:00
16f2fb5c09 Bump tokio from 0.1.20 to 0.1.21 (#4489)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 0.1.20 to 0.1.21.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-0.1.20...tokio-0.1.21)
2019-06-04 14:31:29 -07:00
d77c98530f Bump libc from 0.2.55 to 0.2.58 (#4514)
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.55 to 0.2.58.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.55...0.2.58)
2019-06-04 14:31:06 -07:00
fe40b75ac6 Bench TPS tweaks for transaction fees (#4538)
* use lamports_per_signature instead of hard coding it in bench client
2019-06-04 13:56:11 -07:00
e7129757c9 store_accounts to use try_available (#4523)
* store_accounts to use try_available

* tighter

* clippy
2019-06-04 11:21:12 -07:00
3635a68129 solana-install now compiles for Windows (#4531)
automerge
2019-06-04 08:51:20 -07:00
70a16e91a5 Randomize avalanche broadcast peer table for each blob (#4529)
* fix clippy warnings
2019-06-03 20:38:05 -07:00
41daf1ef0c Wait for crate to be locatable on crates.io after uploading (#4526)
* Wait for crate to be locatable on crates.io after uploading

* Fix nits and shellcheck

* shellchecker
2019-06-03 19:54:41 -06:00
ff77789718 Collect fees at the end of a slot (#4525) 2019-06-03 18:40:20 -07:00
a77775cb58 Move validation submissions into its own fn (#4528)
automerge
2019-06-03 18:27:06 -07:00
167e15a5ae Update replicator sampling and proof generation (#4522)
* Update replicator sampling and proof generation

* Clippy
2019-06-03 17:27:28 -07:00
dea663d509 Storage arranged by fork (#4518) 2019-06-03 15:34:32 -07:00
9754e551cb Fund vote accounts with 1 lamport only (#4512) 2019-06-03 14:48:01 -07:00
40a4ac15f1 Remove per transaction fee (#4521)
automerge
2019-06-03 13:00:08 -07:00
c56052ff16 remove from_account from stake_instruction (#4502) 2019-06-03 09:04:51 -07:00
482ef51502 register_tick() on the correct bank (#4506)
* skip syscall_id in hash and delta

* add more tests, skip syscalls
2019-06-03 09:04:26 -07:00
e4ca3900ae Reduce default validators from 5 to 2 2019-06-02 22:58:59 -07:00
3574469052 Add random distribution for avalanche peers (#4493)
* Add random distribution for avalanche peers

* fix clippy warnings

* bug fixes

* nits
2019-06-01 07:55:43 -07:00
e15246746d Enable non-zero fees for all testnets (#4513)
automerge
2019-05-31 22:33:55 -07:00
ec5cca41bc Separate bootstrap leader's stake lamports from its identity lamports (#4510)
* Revert "Prevent run.sh from running beyond the first epoch under normal use (#4498)"

This reverts commit d343c409e6.

* Separate bootstrap leader's stake lamports from its identity lamports
2019-05-31 19:58:52 -07:00
bc1368ba3e Make run.sh compatible with multinode-demo/validator.sh (#4507)
automerge
2019-05-31 16:51:09 -07:00
c0a161afe8 Enable fees in ci/localnet-sanity.sh (#4508)
automerge
2019-05-31 16:50:39 -07:00
d343c409e6 Prevent run.sh from running beyond the first epoch under normal use (#4498)
The local cluster that run.sh starts will typically only have a single
node, the bootstrap leader.  With epoch warmup enabled, run.sh will fail
after ~90 seconds once the warmup period has been exceeded due to lack
of votes from other validators.

As a workaround, disable epoch warmup and set slots-per-epoch to 1
million to keep run.sh alive for more than a fortnight.
2019-05-31 15:42:32 -07:00
64e8a21d73 Add tick height syscall (#4497)
* Remove tick_height from entrypoint signature

* Impl tick_height syscall and use in storage program

* Properly remove tick height from bpf handling
2019-05-31 16:29:21 -06:00
ce04d2bfc2 Add replicator support to net/ (#4494) 2019-05-31 15:27:31 -07:00
1c1d83bd56 skip syscall_id in hash and delta (#4500)
* skip syscall_id in hash and delta

* add more tests, skip syscalls
2019-05-31 12:26:45 -07:00
028e111fbc remove payer from vote instructions (#4475) 2019-05-31 11:45:17 -07:00
9670788bf5 Bump dirs from 1.0.5 to 2.0.1 (#4490)
Bumps [dirs](https://github.com/soc/dirs-rs) from 1.0.5 to 2.0.1.
- [Release notes](https://github.com/soc/dirs-rs/releases)
- [Commits](https://github.com/soc/dirs-rs/commits)
2019-05-31 08:57:35 -07:00
d2f9625878 minor update 2019-05-31 07:36:59 -07:00
182096dc1a Create bank snapshots (#4244)
* Revert "Revert "Create bank snapshots (#3671)" (#4243)"

This reverts commit 81fa69d347.

* keep saved and unsaved copies of status cache

* fix format check

* bench for status cache serialize

* misc cleanup

* remove appendvec storage on purge

* fix accounts restore

* cleanup

* Pass snapshot path as args

* Fix clippy
2019-05-30 21:31:35 -07:00
2d284ba6db Fix clear-config.sh 2019-05-30 15:31:41 -07:00
1de805e7cd Add fees syscall to expose cluster fees into programs (#4472) 2019-05-30 15:18:48 -07:00
d642125f68 publish-crate fixups 2019-05-30 15:15:58 -07:00
b8aff218e2 Shutdown all services before bailing replicator init (#4487)
automerge
2019-05-30 14:36:47 -07:00
045d4d5294 Unignore test test_repairman_catchup (#4484) 2019-05-30 13:21:12 -07:00
d67dd8ce1f Fix stable metrics dashboard for current channel use (#4483) 2019-05-30 13:16:26 -06:00
4d6679906b Clean up crates.io publishing (#4478)
* Clean up crates.io publishing

* Cargo.lock
2019-05-30 11:53:41 -07:00
4537f54532 Break noop_program -> runtime dependency (#4481) 2019-05-30 11:20:49 -07:00
39b40dfff8 Remove runtime dependency on storage (#4480) 2019-05-30 10:54:28 -07:00
c82f4a1b6d Unignore test_repairman_catchup 2019-05-29 21:59:41 -07:00
7a021dff05 Beautify Cargo.tomls with |cargo tomlfmt| (#4477) 2019-05-29 18:30:49 -07:00
348c2263ba Remove genesis blockhash (#4471)
* Remove genesis blockhash

* Remove genesis blockhash from tests

* Fix golden
2019-05-29 17:29:02 -07:00
b5324063f1 Use thread pools for rayon par_iter (#4473)
* Use thread pools for rayon par_iter

* address review comments

* cleanup
2019-05-29 17:16:36 -07:00
6ed071c4dd Fix storage stage operating on empty slots (#4474)
* Fix storage stage operating on empty slots

* Reduce fn argument count

* Fix tests
2019-05-29 15:01:20 -07:00
4404634b14 Coalesce packets better (#4456) 2019-05-29 12:17:50 -07:00
6a1de33138 tighten up packets_to_blobs (#4464)
* tighten up packets_to_blobs

* missed a test
2019-05-29 10:08:35 -07:00
c05c3e69ca add tests and groom naming (#4467) 2019-05-29 10:08:03 -07:00
534244b322 Fix set_roots to use cached bank parents instead of searching blocktree (#4466) 2019-05-29 09:43:22 -07:00
335dfdc4d5 Fix Gossip skipping push for some values (#4463)
* Make gossip skip over values from Pruned nodes

* Add test and init blooms to contain the origin
2019-05-28 18:39:40 -07:00
a7ef409c2b Drop influxcloud (#4460)
automerge
2019-05-28 16:26:59 -07:00
14594217db undelete votestate etc (#4457) 2019-05-28 16:01:27 -07:00
c8a03c7b3d Save RNG for generating random storage sampling offsets (#4450)
* Save RNG for generating random storage sampling offsets

* fix clippy

* fix stable-perf

* fix chacha
2019-05-28 14:14:46 -07:00
9fcd162412 update book with passive staking (#4451) 2019-05-28 14:02:04 -07:00
441fed7a5b check freeze before updating slot_hashes (#4448)
* check freeze before updating slot_hashes

* fixup
2019-05-28 12:25:55 -07:00
ff31ffbd54 add more information to dropped vote warning (#4449)
* add more information to dropped vote warning

* fixup
2019-05-28 12:25:34 -07:00
0e26ee854b Add test indicating need for credit-only account handling (#4441)
* Add test indicating need for credit-only account handling

* Add commented correct future test lines
2019-05-28 11:57:22 -04:00
5340800cea Add some optimizing to ThinClient (#4112)
Can create a multi-socketed ThinClient which will use request time
from get_recent_blockhash to tune for the best node to talk to.
2019-05-27 20:54:44 -07:00
13c2e50b38 Bump sys-info from 0.5.6 to 0.5.7 (#4445)
automerge
2019-05-27 20:31:12 -07:00
dd39b2b056 Revert --retry-on-http-error usage, Travis CI's wget doesn't recognize it 2019-05-27 19:35:04 -07:00
65f89d6729 Bump logging level of validator procsesing errors (#4442) 2019-05-27 16:19:38 -07:00
1eceb4831d Use nohup and sleep a little to improve stability when launching a node 2019-05-27 13:57:40 -07:00
50303c9ede data_dir -> data-dir 2019-05-27 07:31:50 -07:00
ed6a438c51 v0.16.0 2019-05-26 19:42:15 -07:00
393 changed files with 16449 additions and 15235 deletions

41
.appveyor.yml Normal file
View File

@ -0,0 +1,41 @@
os: Visual Studio 2017
version: '{build}'
branches:
only:
- master
- /^v[0-9.]+/
cache:
- '%USERPROFILE%\.cargo'
- '%APPVEYOR_BUILD_FOLDER%\target'
build_script:
- bash ci/publish-tarball.sh
notifications:
- provider: Slack
incoming_webhook:
secure: 6HTXVh+FBz29LGJb+taFOo9dqoADfo9xyAszeyXZF5Ub9t5NERytKAR35B2wb+uIOOCBF8+JhmH4437Cgf/ti4IqvURzW1QReXK7eQhn1EI=
channel: ci-status
on_build_success: false
on_build_failure: true
on_build_status_changed: true
deploy:
- provider: S3
access_key_id:
secure: ptvqM/yvgeTeA12XOzybH1KYNh95AdfEvqoH9mvP2ic=
secret_access_key:
secure: IkrgBlz5hdxvwcJdMXyyHUrpWhKa6fXLOD/8rm/rjKqYCdrba9B8V1nLZVrzXGGy
bucket: release.solana.com
region: us-west-1
set_public: true
- provider: GitHub
auth_token:
secure: vQ3jMl5LQrit6+TQONA3ZgQjZ/Ej62BN2ReVb2NSOwjITHMu1131hjc3dOrMEZL6
draft: false
prerelease: false
on:
appveyor_repo_tag: true

1
.buildkite/env/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/secrets_unencrypted.ejson

View File

@ -1,12 +1,14 @@
{
"_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f",
"environment": {
"CODECOV_TOKEN": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:jA0WqO70coUtF0iokRdgtCR/lF/lETAI:d/Wl8Tdl6xVh/B39cTf1DaQkomR7I/2vMhvxd1msJ++BjI2l3p2dFoGsXqWT+/os8VgiPg==]",
"CRATES_IO_TOKEN": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:2FaZ6k4RGH8luyNRaN6yeZUQDNAu2KwC:XeYe0tCAivYE0F9HEWM79mAI6kNbfYaqP7k7yY+SBDvs0341U9BdGZp7SErbHleS]",
"GITHUB_TOKEN": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:9kh4DGPiGDcUU7ejSFWg3gTW8nrOM09Q:b+GE07Wu6/bEnkDZcUtf48vTKAFphrCSt3tNNER9h6A+wZ80k499edw4pbDdl9kEvxB30fFwrLQ=]",
"INFLUX_DATABASE": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:rCHsYi0rc7dmvr1V3wEgNoaNIyr+9ClM:omjVcOqM7vwt44kJ+As4BjJL]",
"INFLUX_PASSWORD": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:bP5Gw1Vy66viKFKO41o2Gho998XajH/5:khkCYz2LFvkJkk7R4xY1Hfz1yU3/NENjauiUkPhXA+dmg1qOIToxEagCgIkRwyeCiYaoCR6CZyw=]",
"INFLUX_USERNAME": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:ZamCvza2W9/bZRGSkqDu55xNN04XKKhp:5jlmCOdFbpL7EFez41zCbLfk3ZZlfmhI]",
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:Oi2nsRxnvWnnBYsB6KwEDzLPcYgpYojU:ELbvjXkXKlgFCMES45R+fxG7Ex43WHWErjMbxZoqasxyr7GSH66hQzUWqiQSJyT4ukYrRhRC9YrsKKGkjACLU57X4EGIy9TuLgTnyBYhPnxLYStC3y/7o/MB5FCTt5wHJw3/A9p+me5+T4UmyZ7OeP21NhDUCGQcb0040VwYWS78klW2aQESJJ6wTI1xboE8/zC0vtnB/u50+LydbKEyb21r6y3OH9FYNEpSwIspWKcgpruJdQSCnDoKxP9YR1yzvk2rabss13LJNdV1Y6mQNIdP4OIFQhCs6dXT253RTl5qdZ0MruHwlp8wX4btOuYDcCoM5exr]"
"CODECOV_TOKEN": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:ks2/ElgxwgxqgmFcxTHANNLmj23YH74h:U4uzRONRfiQyqy6HrPQ/e7OnBUY4HkW37R0iekkF3KJ9UGnHqT1UvwgVbDqLahtDIJ4rWw==]",
"CRATES_IO_TOKEN": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:lKMh3aLW+jyRrfS/c7yvkpB+TaPhXqLq:j0v27EbaPgwRdHZAbsM0FlAnt3r9ScQrFbWJYOAZtM3qestEiByTlKpZ0eyF/823]",
"GITHUB_TOKEN": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:Ll78c3jGpYqnTwR7HJq3mNNUC7pOv9Lu:GrInO2r8MjmP5c54szkyygdsrW5KQYkDgJQUVyFEPyG8SWfchyM9Gur8RV0a+cdwuxNkHLi4U2M=]",
"INFLUX_DATABASE": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:IlH/ZLTXv3SwlY3TVyAPCX2KzLRY6iG3:gGmUGSU/kCfR/mTwKONaUC/X]",
"INFLUX_PASSWORD": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:o2qm95GU4VrrcC4OU06jjPvCwKZy/CZF:OW2ga3kLOQJvaDEdGRJ+gn3L2ckFm8AJZtv9wj/GeUIKDH2A4uBPTHsAH9PMe6zujpuHGk3qbeg=]",
"INFLUX_USERNAME": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:yDWW/uIHsJqOTDYskZoSx3pzoB1vztWY:2z31oTA3g0Xs9fCczGNJRcx8xf/hFCed]",
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:RqRaHlYUvGPNFJa6gmciaYM3tRJTURUH:q78/3GTHCN3Uqx9z4nOBjPZcO1lOazNoB/mdhGRDFsnAqVd2hU8zbKkqLrZfLlGqyD8WQOFuw5oTJR9qWg6L9LcOyj3pGL8jWF2yjgZxdtNMXnkbSrCWLooWBBLT61jYQnEwg73gT8ld3Q8EVv3T+MeSMu6FnPz+0+bqQCAGgfqksP4hsUAJGzgZu+i0tNOdlT7fxnh5KJK/yFM/CKgN2sRwEjukA9hXsffyB61g2zqzTDJxCUDLbCVrCkA/bfUk7Of/t0W5t0nK1H3oyGZEc/lRMauCknDBka3Gz11dVss2QT19WQNh0u7bHVaT/U4lepX1j9Zv]",
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_apple_darwin": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:wFDl3INEnA3EQDHRX40avqGe1OMoJxyy:6ncCRVRTIRuYI5o/gayeuWCudWvmKNYr8KEHAWeTq34a5bdcKInBdKhjmjX+wLHqsEwQ5gcyhcxy4Ri2mbuN6AHazfZOZlubQkGlyUOAIYO5D5jkbyIh40DAtjVzo1MD/0HsW9zdGOzqUKp5xJJeDsbR4F153jbxa7fvwF90Q4UQjYFTKAtExEmHtDGSJG48ToVwTabTV/OnISMIggDZBviIv2QWHvXgK07b2mUj34rHJywEDGN1nj5rITTDdUeRcB1x4BAMOe94kTFPSTaj/OszvYlGECt8rkKFqbm092qL+XLfiBaImqe/WJHRCnAj6Don]",
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_pc_windows_msvc": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:wAh+dBuZopv6vruVOYegUcq/aBnbksT1:qIJfCfDvDWiqicMOkmbJs/0n7UJLKNmgMQaKzeQ8J7Q60YpXbtWzKVW3tS6lzlgf64m3MrPXyo1C+mWh6jkjsb18T/OfggZy1ZHM4AcsOC6/ldUkV5YtuxUQuAmd5jCuV/R7iuYY8Z66AcfAevlb+bnLpgIifdA8fh/IktOo58nZUQwZDdppAacmftsLc6Frn5Er6A6+EXpxK1nmnlmLJ4AJztqlh6X0r+JvE2O7qeoZUXrIegnkxo7Aay7I/dd8zdYpp7ICSiTEtfVN/xNIu/5QmTRU7gWoz7cPl9epq4aiEALzPOzb6KVOiRcsOg+TlFvLQ71Ik5o=]"
}
}

View File

@ -1,6 +1,8 @@
CI_BUILD_START=$(date +%s)
export CI_BUILD_START
source ci/env.sh
#
# Kill any running docker containers, which are potentially left over from the
# previous CI job

5
.gitignore vendored
View File

@ -2,9 +2,10 @@
/book/src/img/
/book/src/tests.ok
/farf/
/metrics/scripts/lib/
/solana-release/
solana-release.tar.bz2
/solana-release.tar.bz2
/solana-metrics/
/solana-metrics.tar.bz2
/target/
**/*.rs.bk

44
.travis.yml Normal file
View File

@ -0,0 +1,44 @@
os:
- osx
language: rust
cache: cargo
rust:
- 1.35.0
install:
- source ci/rust-version.sh
- test $rust_stable = $TRAVIS_RUST_VERSION # Update .travis.yml rust version above when this fails
script:
- source ci/env.sh
- ci/publish-tarball.sh
branches:
only:
- master
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
notifications:
slack:
on_success: change
secure: F4IjOE05MyaMOdPRL+r8qhs7jBvv4yDM3RmFKE1zNXnfUOqV4X38oQM1EI+YVsgpMQLj/pxnEB7wcTE4Bf86N6moLssEULCpvAuMVoXj4QbWdomLX+01WbFa6fLVeNQIg45NHrz2XzVBhoKOrMNnl+QI5mbR2AlS5oqsudHsXDnyLzZtd4Y5SDMdYG1zVWM01+oNNjgNfjcCGmOE/K0CnOMl6GPi3X9C34tJ19P2XT7MTDsz1/IfEF7fro2Q8DHEYL9dchJMoisXSkem5z7IDQkGzXsWdWT4NnndUvmd1MlTCE9qgoXDqRf95Qh8sB1Dz08HtvgfaosP2XjtNTfDI9BBYS15Ibw9y7PchAJE1luteNjF35EOy6OgmCLw/YpnweqfuNViBZz+yOPWXVC0kxnPIXKZ1wyH9ibeH6E4hr7a8o9SV/6SiWIlbYF+IR9jPXyTCLP/cc3sYljPWxDnhWFwFdRVIi3PbVAhVu7uWtVUO17Oc9gtGPgs/GrhOMkJfwQPXaudRJDpVZowxTX4x9kefNotlMAMRgq+Drbmgt4eEBiCNp0ITWgh17BiE1U09WS3myuduhoct85+FoVeaUkp1sxzHVtGsNQH0hcz7WcpZyOM+AwistJA/qzeEDQao5zi1eKWPbO2xAhi2rV1bDH6bPf/4lDBwLRqSiwvlWU=
deploy:
- provider: s3
access_key_id: $AWS_ACCESS_KEY_ID
secret_access_key: $AWS_SECRET_ACCESS_KEY
bucket: release.solana.com
region: us-west-1
skip_cleanup: true
acl: public_read
local_dir: travis-s3-upload
on:
all_branches: true
- provider: releases
api_key: $GITHUB_TOKEN
skip_cleanup: true
file_glob: true
file: travis-release-upload/*
on:
tags: true

1174
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ members = [
"bench-exchange",
"bench-streamer",
"bench-tps",
"chacha-sys",
"client",
"core",
"drone",
@ -14,10 +15,12 @@ members = [
"kvstore",
"ledger-tool",
"logger",
"merkle-tree",
"metrics",
"netutil",
"programs/bpf",
"programs/bpf_loader",
"programs/bpf_loader_api",
"programs/bpf_loader_program",
"programs/budget_api",
"programs/budget_program",
"programs/config_api",

View File

@ -30,6 +30,40 @@ Before you jump into the code, review the online book [Solana: Blockchain Rebuil
(The _latest_ development version of the online book is also [available here](https://solana-labs.github.io/book-edge/).)
Release Binaries
===
Official release binaries are available at [Github Releases](https://github.com/solana-labs/solana/releases).
Additionally we provide pre-release binaries for the latest code on the edge and
beta channels. Note that these pre-release binaries may be less stable than an
official release.
### Edge channel
#### Linux (x86_64-unknown-linux-gnu)
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
#### mac OS (x86_64-apple-darwin)
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-apple-darwin.tar.bz2)
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
#### Windows (x86_64-pc-windows-msvc)
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-pc-windows-msvc.tar.bz2)
* [solana-install-init.exe](http://release.solana.com/edge/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
#### All platforms
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/edge/solana-metrics.tar.bz2)
### Beta channel
#### Linux (x86_64-unknown-linux-gnu)
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
#### mac OS (x86_64-apple-darwin)
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-apple-darwin.tar.bz2)
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
#### Windows (x86_64-pc-windows-msvc)
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-pc-windows-msvc.tar.bz2)
* [solana-install-init.exe](http://release.solana.com/beta/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
#### All platforms
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/beta/solana-metrics.tar.bz2)
Developing
===

View File

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

View File

@ -20,9 +20,12 @@ use solana_sdk::system_instruction;
use solana_sdk::timing::{duration_as_ms, duration_as_s};
use solana_sdk::transaction::Transaction;
use std::cmp;
use std::collections::VecDeque;
use std::collections::{HashMap, VecDeque};
use std::fs::File;
use std::io::prelude::*;
use std::mem;
use std::net::SocketAddr;
use std::path::Path;
use std::process::exit;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
@ -48,6 +51,8 @@ pub struct Config {
pub batch_size: usize,
pub chunk_size: usize,
pub account_groups: usize,
pub client_ids_and_stake_file: String,
pub read_from_client_file: bool,
}
impl Default for Config {
@ -61,10 +66,38 @@ impl Default for Config {
batch_size: 10,
chunk_size: 10,
account_groups: 100,
client_ids_and_stake_file: String::new(),
read_from_client_file: false,
}
}
}
pub fn create_client_accounts_file(
client_ids_and_stake_file: &str,
batch_size: usize,
account_groups: usize,
fund_amount: u64,
) {
let accounts_in_groups = batch_size * account_groups;
const NUM_KEYPAIR_GROUPS: u64 = 2;
let total_keys = accounts_in_groups as u64 * NUM_KEYPAIR_GROUPS;
let keypairs = generate_keypairs(total_keys);
let mut accounts = HashMap::new();
keypairs.iter().for_each(|keypair| {
accounts.insert(
serde_json::to_string(&keypair.to_bytes().to_vec()).unwrap(),
fund_amount,
);
});
let serialized = serde_yaml::to_string(&accounts).unwrap();
let path = Path::new(&client_ids_and_stake_file);
let mut file = File::create(path).unwrap();
file.write_all(&serialized.into_bytes()).unwrap();
}
pub fn do_bench_exchange<T>(clients: Vec<T>, config: Config)
where
T: 'static + Client + Send + Sync,
@ -78,6 +111,8 @@ where
batch_size,
chunk_size,
account_groups,
client_ids_and_stake_file,
read_from_client_file,
} = config;
info!(
@ -92,35 +127,55 @@ where
);
let accounts_in_groups = batch_size * account_groups;
let exit_signal = Arc::new(AtomicBool::new(false));
const NUM_KEYPAIR_GROUPS: u64 = 2;
let total_keys = accounts_in_groups as u64 * NUM_KEYPAIR_GROUPS;
let mut signer_keypairs = if read_from_client_file {
let path = Path::new(&client_ids_and_stake_file);
let file = File::open(path).unwrap();
let accounts: HashMap<String, u64> = serde_yaml::from_reader(file).unwrap();
accounts
.into_iter()
.map(|(keypair, _)| {
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
Keypair::from_bytes(&bytes).unwrap()
})
.collect()
} else {
info!("Generating {:?} signer keys", total_keys);
generate_keypairs(total_keys)
};
let trader_signers: Vec<_> = signer_keypairs
.drain(0..accounts_in_groups)
.map(Arc::new)
.collect();
let swapper_signers: Vec<_> = signer_keypairs
.drain(0..accounts_in_groups)
.map(Arc::new)
.collect();
let clients: Vec<_> = clients.into_iter().map(Arc::new).collect();
let client = clients[0].as_ref();
const NUM_KEYPAIR_GROUPS: u64 = 4;
let total_keys = accounts_in_groups as u64 * NUM_KEYPAIR_GROUPS;
info!("Generating {:?} keys", total_keys);
let mut keypairs = generate_keypairs(total_keys);
let trader_signers: Vec<_> = keypairs
.drain(0..accounts_in_groups)
.map(Arc::new)
.collect();
let swapper_signers: Vec<_> = keypairs
.drain(0..accounts_in_groups)
.map(Arc::new)
.collect();
let src_pubkeys: Vec<_> = keypairs
.drain(0..accounts_in_groups)
.map(|keypair| keypair.pubkey())
.collect();
let profit_pubkeys: Vec<_> = keypairs
.drain(0..accounts_in_groups)
.map(|keypair| keypair.pubkey())
.collect();
if !read_from_client_file {
info!("Fund trader accounts");
fund_keys(client, &identity, &trader_signers, fund_amount);
info!("Fund swapper accounts");
fund_keys(client, &identity, &swapper_signers, fund_amount);
}
info!("Fund trader accounts");
fund_keys(client, &identity, &trader_signers, fund_amount);
info!("Fund swapper accounts");
fund_keys(client, &identity, &swapper_signers, fund_amount);
info!("Generating {:?} account keys", total_keys);
let mut account_keypairs = generate_keypairs(total_keys);
let src_pubkeys: Vec<_> = account_keypairs
.drain(0..accounts_in_groups)
.map(|keypair| keypair.pubkey())
.collect();
let profit_pubkeys: Vec<_> = account_keypairs
.drain(0..accounts_in_groups)
.map(|keypair| keypair.pubkey())
.collect();
info!("Create {:?} source token accounts", src_pubkeys.len());
create_token_accounts(client, &trader_signers, &src_pubkeys);
@ -136,6 +191,7 @@ where
transfer_delay
);
let exit_signal = Arc::new(AtomicBool::new(false));
let shared_txs: SharedTransactions = Arc::new(RwLock::new(VecDeque::new()));
let total_txs_sent_count = Arc::new(AtomicUsize::new(0));
let s_threads: Vec<_> = (0..threads)
@ -892,7 +948,7 @@ pub fn airdrop_lamports(client: &Client, drone_addr: &SocketAddr, id: &Keypair,
#[cfg(test)]
mod tests {
use super::*;
use solana::gossip_service::{discover_cluster, get_clients};
use solana::gossip_service::{discover_cluster, get_multi_client};
use solana::local_cluster::{ClusterConfig, LocalCluster};
use solana::validator::ValidatorConfig;
use solana_drone::drone::run_local_drone;
@ -907,7 +963,6 @@ mod tests {
solana_logger::setup();
const NUM_NODES: usize = 1;
let validator_config = ValidatorConfig::default();
let mut config = Config::default();
config.identity = Keypair::new();
@ -929,7 +984,7 @@ mod tests {
let cluster = LocalCluster::new(&ClusterConfig {
node_stakes: vec![100_000; NUM_NODES],
cluster_lamports: 100_000_000_000_000,
validator_config,
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
native_instruction_processors: [solana_exchange_program!()].to_vec(),
..ClusterConfig::default()
});
@ -952,25 +1007,20 @@ mod tests {
exit(1);
});
let clients = get_clients(&nodes);
let (client, num_clients) = get_multi_client(&nodes);
if clients.len() < NUM_NODES {
error!(
"Error: Insufficient nodes discovered. Expecting {} or more",
NUM_NODES
);
exit(1);
}
info!("clients: {}", num_clients);
assert!(num_clients >= NUM_NODES);
const NUM_SIGNERS: u64 = 2;
airdrop_lamports(
&clients[0],
&client,
&drone_addr,
&config.identity,
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
);
do_bench_exchange(clients, config);
do_bench_exchange(vec![client], config);
}
#[test]

View File

@ -18,6 +18,9 @@ pub struct Config {
pub batch_size: usize,
pub chunk_size: usize,
pub account_groups: usize,
pub client_ids_and_stake_file: String,
pub write_to_client_file: bool,
pub read_from_client_file: bool,
}
impl Default for Config {
@ -34,6 +37,9 @@ impl Default for Config {
batch_size: 100,
chunk_size: 100,
account_groups: 100,
client_ids_and_stake_file: String::new(),
write_to_client_file: false,
read_from_client_file: false,
}
}
}
@ -141,6 +147,20 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.default_value("10")
.help("Number of account groups to cycle for each batch"),
)
.arg(
Arg::with_name("write-client-keys")
.long("write-client-keys")
.value_name("FILENAME")
.takes_value(true)
.help("Generate client keys and stakes and write the list to YAML file"),
)
.arg(
Arg::with_name("read-client-keys")
.long("read-client-keys")
.value_name("FILENAME")
.takes_value(true)
.help("Read client keys and stakes from the YAML file"),
)
}
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
@ -184,5 +204,15 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
args.account_groups = value_t!(matches.value_of("account-groups"), usize)
.expect("Failed to parse account-groups");
if let Some(s) = matches.value_of("write-client-keys") {
args.write_to_client_file = true;
args.client_ids_and_stake_file = s.to_string();
}
if let Some(s) = matches.value_of("read-client-keys") {
assert!(!args.write_to_client_file);
args.read_from_client_file = true;
args.client_ids_and_stake_file = s.to_string();
}
args
}

View File

@ -6,9 +6,9 @@ pub mod order_book;
#[macro_use]
extern crate solana_exchange_program;
use crate::bench::{airdrop_lamports, do_bench_exchange, Config};
use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_exchange, Config};
use log::*;
use solana::gossip_service::{discover_cluster, get_clients};
use solana::gossip_service::{discover_cluster, get_multi_client};
use solana_sdk::signature::KeypairUtil;
fn main() {
@ -30,33 +30,12 @@ fn main() {
batch_size,
chunk_size,
account_groups,
client_ids_and_stake_file,
write_to_client_file,
read_from_client_file,
..
} = cli_config;
info!("Connecting to the cluster");
let (nodes, _replicators) =
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
panic!("Failed to discover nodes");
});
let clients = get_clients(&nodes);
info!("{} nodes found", clients.len());
if clients.len() < num_nodes {
panic!("Error: Insufficient nodes discovered");
}
info!("Funding keypair: {}", identity.pubkey());
let accounts_in_groups = batch_size * account_groups;
const NUM_SIGNERS: u64 = 2;
airdrop_lamports(
&clients[0],
&drone_addr,
&identity,
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
);
let config = Config {
identity,
threads,
@ -66,7 +45,43 @@ fn main() {
batch_size,
chunk_size,
account_groups,
client_ids_and_stake_file,
read_from_client_file,
};
do_bench_exchange(clients, config);
if write_to_client_file {
create_client_accounts_file(
&config.client_ids_and_stake_file,
config.batch_size,
config.account_groups,
config.fund_amount,
);
} else {
info!("Connecting to the cluster");
let (nodes, _replicators) =
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
panic!("Failed to discover nodes");
});
let (client, num_clients) = get_multi_client(&nodes);
info!("{} nodes found", num_clients);
if num_clients < num_nodes {
panic!("Error: Insufficient nodes discovered");
}
if !read_from_client_file {
info!("Funding keypair: {}", config.identity.pubkey());
let accounts_in_groups = batch_size * account_groups;
const NUM_SIGNERS: u64 = 2;
airdrop_lamports(
&client,
&drone_addr,
&config.identity,
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
);
}
do_bench_exchange(vec![client], config);
}
}

View File

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

View File

@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-tps"
version = "0.15.0"
version = "0.16.2"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@ -10,17 +10,20 @@ homepage = "https://solana.com/"
[dependencies]
clap = "2.33.0"
log = "0.4.6"
rayon = "1.0.3"
rayon = "1.1.0"
serde = "1.0.92"
serde_derive = "1.0.92"
serde_json = "1.0.39"
solana = { path = "../core", version = "0.15.0" }
solana-client = { path = "../client", version = "0.15.0" }
solana-drone = { path = "../drone", version = "0.15.0" }
solana-logger = { path = "../logger", version = "0.15.0" }
solana-metrics = { path = "../metrics", version = "0.15.0" }
solana-netutil = { path = "../netutil", version = "0.15.0" }
solana-runtime = { path = "../runtime", version = "0.15.0" }
solana-sdk = { path = "../sdk", version = "0.15.0" }
serde_yaml = "0.8.9"
solana = { path = "../core", version = "0.16.2" }
solana-client = { path = "../client", version = "0.16.2" }
solana-drone = { path = "../drone", version = "0.16.2" }
solana-logger = { path = "../logger", version = "0.16.2" }
solana-metrics = { path = "../metrics", version = "0.16.2" }
solana-netutil = { path = "../netutil", version = "0.16.2" }
solana-runtime = { path = "../runtime", version = "0.16.2" }
solana-sdk = { path = "../sdk", version = "0.16.2" }
[features]
cuda = ["solana/cuda"]
erasure = []

View File

@ -17,7 +17,6 @@ use solana_sdk::transaction::Transaction;
use std::cmp;
use std::collections::VecDeque;
use std::net::SocketAddr;
use std::process::exit;
use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
use std::thread::sleep;
@ -25,8 +24,15 @@ use std::thread::Builder;
use std::time::Duration;
use std::time::Instant;
pub const MAX_SPENDS_PER_TX: usize = 4;
pub const NUM_LAMPORTS_PER_ACCOUNT: u64 = 20;
pub const MAX_SPENDS_PER_TX: u64 = 4;
pub const NUM_LAMPORTS_PER_ACCOUNT: u64 = 128;
#[derive(Debug)]
pub enum BenchTpsError {
AirdropFailure,
}
pub type Result<T> = std::result::Result<T, BenchTpsError>;
pub type SharedTransactions = Arc<RwLock<VecDeque<Vec<(Transaction, u64)>>>>;
@ -335,8 +341,13 @@ fn verify_funding_transfer<T: Client>(client: &T, tx: &Transaction, amount: u64)
/// fund the dests keys by spending all of the source keys into MAX_SPENDS_PER_TX
/// on every iteration. This allows us to replay the transfers because the source is either empty,
/// or full
pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Keypair], lamports: u64) {
let total = lamports * dests.len() as u64;
pub fn fund_keys<T: Client>(
client: &T,
source: &Keypair,
dests: &[Keypair],
total: u64,
lamports_per_signature: u64,
) {
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
let mut notfunded: Vec<&Keypair> = dests.iter().collect();
@ -346,12 +357,12 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Keypair], lam
let mut to_fund = vec![];
println!("creating from... {}", funded.len());
for f in &mut funded {
let max_units = cmp::min(notfunded.len(), MAX_SPENDS_PER_TX);
let max_units = cmp::min(notfunded.len() as u64, MAX_SPENDS_PER_TX);
if max_units == 0 {
break;
}
let start = notfunded.len() - max_units;
let per_unit = f.1 / (max_units as u64);
let start = notfunded.len() - max_units as usize;
let per_unit = (f.1 - max_units * lamports_per_signature) / max_units;
let moves: Vec<_> = notfunded[start..]
.iter()
.map(|k| (k.pubkey(), per_unit))
@ -442,7 +453,7 @@ pub fn airdrop_lamports<T: Client>(
drone_addr: &SocketAddr,
id: &Keypair,
tx_count: u64,
) {
) -> Result<()> {
let starting_balance = client.get_balance(&id.pubkey()).unwrap_or(0);
metrics_submit_lamport_balance(starting_balance);
println!("starting balance {}", starting_balance);
@ -491,9 +502,10 @@ pub fn airdrop_lamports<T: Client>(
current_balance,
starting_balance
);
exit(1);
return Err(BenchTpsError::AirdropFailure);
}
}
Ok(())
}
fn compute_and_report_stats(
@ -570,19 +582,16 @@ fn should_switch_directions(num_lamports_per_account: u64, i: u64) -> bool {
i % (num_lamports_per_account / 4) == 0 && (i >= (3 * num_lamports_per_account) / 4)
}
pub fn generate_keypairs(seed_keypair: &Keypair, count: usize) -> Vec<Keypair> {
pub fn generate_keypairs(seed_keypair: &Keypair, count: u64) -> Vec<Keypair> {
let mut seed = [0u8; 32];
seed.copy_from_slice(&seed_keypair.to_bytes()[..32]);
let mut rnd = GenKeys::new(seed);
let mut total_keys = 0;
let mut target = count;
while target > 1 {
total_keys += target;
// 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;
let mut total_keys = 1;
while total_keys < count {
total_keys *= MAX_SPENDS_PER_TX;
}
rnd.gen_n_keypairs(total_keys as u64)
rnd.gen_n_keypairs(total_keys)
}
pub fn generate_and_fund_keypairs<T: Client>(
@ -591,9 +600,9 @@ pub fn generate_and_fund_keypairs<T: Client>(
funding_pubkey: &Keypair,
tx_count: usize,
lamports_per_account: u64,
) -> (Vec<Keypair>, u64) {
) -> Result<(Vec<Keypair>, u64)> {
info!("Creating {} keypairs...", tx_count * 2);
let mut keypairs = generate_keypairs(funding_pubkey, tx_count * 2);
let mut keypairs = generate_keypairs(funding_pubkey, tx_count as u64 * 2);
info!("Get lamports...");
@ -604,19 +613,27 @@ pub fn generate_and_fund_keypairs<T: Client>(
.unwrap_or(0);
if lamports_per_account > last_keypair_balance {
let extra = lamports_per_account - last_keypair_balance;
let (_, fee_calculator) = client.get_recent_blockhash().unwrap();
let extra =
lamports_per_account - last_keypair_balance + fee_calculator.max_lamports_per_signature;
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);
airdrop_lamports(client, &drone_addr.unwrap(), funding_pubkey, total)?;
}
info!("adding more lamports {}", extra);
fund_keys(client, funding_pubkey, &keypairs, extra);
fund_keys(
client,
funding_pubkey,
&keypairs,
total,
fee_calculator.max_lamports_per_signature,
);
}
// '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)
Ok((keypairs, last_keypair_balance))
}
#[cfg(test)]
@ -651,12 +668,11 @@ mod tests {
#[test]
fn test_bench_tps_local_cluster() {
solana_logger::setup();
let validator_config = ValidatorConfig::default();
const NUM_NODES: usize = 1;
let cluster = LocalCluster::new(&ClusterConfig {
node_stakes: vec![999_990; NUM_NODES],
cluster_lamports: 2_000_000,
validator_config,
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
..ClusterConfig::default()
});
@ -683,7 +699,8 @@ mod tests {
&config.id,
config.tx_count,
lamports_per_account,
);
)
.unwrap();
let total = do_bench_tps(vec![client], config, keypairs, 0);
assert!(total > 100);
@ -701,7 +718,7 @@ mod tests {
config.duration = Duration::from_secs(5);
let (keypairs, _keypair_balance) =
generate_and_fund_keypairs(&clients[0], None, &config.id, config.tx_count, 20);
generate_and_fund_keypairs(&clients[0], None, &config.id, config.tx_count, 20).unwrap();
do_bench_tps(clients, config, keypairs, 0);
}
@ -715,11 +732,10 @@ mod tests {
let lamports = 20;
let (keypairs, _keypair_balance) =
generate_and_fund_keypairs(&client, None, &id, tx_count, lamports);
generate_and_fund_keypairs(&client, None, &id, tx_count, lamports).unwrap();
for kp in &keypairs {
// TODO: This should be >= lamports, but fails at the moment
assert_ne!(client.get_balance(&kp.pubkey()).unwrap(), 0);
assert!(client.get_balance(&kp.pubkey()).unwrap() >= lamports);
}
}
}

View File

@ -4,6 +4,7 @@ use std::time::Duration;
use clap::{crate_description, crate_name, crate_version, App, Arg, ArgMatches};
use solana_drone::drone::DRONE_PORT;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
/// Holds the configuration for a single run of the benchmark
@ -17,6 +18,10 @@ pub struct Config {
pub tx_count: usize,
pub thread_batch_sleep_ms: usize,
pub sustained: bool,
pub client_ids_and_stake_file: String,
pub write_to_client_file: bool,
pub read_from_client_file: bool,
pub target_lamports_per_signature: u64,
}
impl Default for Config {
@ -31,6 +36,10 @@ impl Default for Config {
tx_count: 500_000,
thread_batch_sleep_ms: 0,
sustained: false,
client_ids_and_stake_file: String::new(),
write_to_client_file: false,
read_from_client_file: false,
target_lamports_per_signature: FeeCalculator::default().target_lamports_per_signature,
}
}
}
@ -106,6 +115,30 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.takes_value(true)
.help("Per-thread-per-iteration sleep in ms"),
)
.arg(
Arg::with_name("write-client-keys")
.long("write-client-keys")
.value_name("FILENAME")
.takes_value(true)
.help("Generate client keys and stakes and write the list to YAML file"),
)
.arg(
Arg::with_name("read-client-keys")
.long("read-client-keys")
.value_name("FILENAME")
.takes_value(true)
.help("Read client keys and stakes from the YAML file"),
)
.arg(
Arg::with_name("target_lamports_per_signature")
.long("target-lamports-per-signature")
.value_name("LAMPORTS")
.takes_value(true)
.help(
"The cost in lamports that the cluster will charge for signature \
verification when the cluster is operating at target-signatures-per-slot",
),
)
}
/// Parses a clap `ArgMatches` structure into a `Config`
@ -163,5 +196,20 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
args.sustained = matches.is_present("sustained");
if let Some(s) = matches.value_of("write-client-keys") {
args.write_to_client_file = true;
args.client_ids_and_stake_file = s.to_string();
}
if let Some(s) = matches.value_of("read-client-keys") {
assert!(!args.write_to_client_file);
args.read_from_client_file = true;
args.client_ids_and_stake_file = s.to_string();
}
if let Some(v) = matches.value_of("target_lamports_per_signature") {
args.target_lamports_per_signature = v.to_string().parse().expect("can't parse lamports");
}
args
}

View File

@ -1,10 +1,21 @@
mod bench;
mod cli;
use crate::bench::{do_bench_tps, generate_and_fund_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT};
use solana::gossip_service::{discover_cluster, get_clients};
use crate::bench::{
do_bench_tps, generate_and_fund_keypairs, generate_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT,
};
use solana::gossip_service::{discover_cluster, get_multi_client};
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::signature::Keypair;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::process::exit;
/// Number of signatures for all transactions in ~1 week at ~100K TPS
pub const NUM_SIGNATURES_FOR_TXS: u64 = 100_000 * 60 * 60 * 24 * 7;
fn main() {
solana_logger::setup();
solana_metrics::set_panic_hook("bench-tps");
@ -22,15 +33,44 @@ fn main() {
tx_count,
thread_batch_sleep_ms,
sustained,
client_ids_and_stake_file,
write_to_client_file,
read_from_client_file,
target_lamports_per_signature,
} = cli_config;
if write_to_client_file {
let keypairs = generate_keypairs(&id, tx_count as u64 * 2);
let num_accounts = keypairs.len() as u64;
let max_fee = FeeCalculator::new(target_lamports_per_signature).max_lamports_per_signature;
let num_lamports_per_account = (num_accounts - 1 + NUM_SIGNATURES_FOR_TXS * max_fee)
/ num_accounts
+ NUM_LAMPORTS_PER_ACCOUNT;
let mut accounts = HashMap::new();
keypairs.iter().for_each(|keypair| {
accounts.insert(
serde_json::to_string(&keypair.to_bytes().to_vec()).unwrap(),
num_lamports_per_account,
);
});
let serialized = serde_yaml::to_string(&accounts).unwrap();
let path = Path::new(&client_ids_and_stake_file);
let mut file = File::create(path).unwrap();
file.write_all(&serialized.into_bytes()).unwrap();
return;
}
println!("Connecting to the cluster");
let (nodes, _replicators) =
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|err| {
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
exit(1);
});
if nodes.len() < num_nodes {
let (client, num_clients) = get_multi_client(&nodes);
if nodes.len() < num_clients {
eprintln!(
"Error: Insufficient nodes discovered. Expecting {} or more",
num_nodes
@ -38,15 +78,33 @@ fn main() {
exit(1);
}
let clients = get_clients(&nodes);
let (keypairs, keypair_balance) = if read_from_client_file {
let path = Path::new(&client_ids_and_stake_file);
let file = File::open(path).unwrap();
let (keypairs, keypair_balance) = generate_and_fund_keypairs(
&clients[0],
Some(drone_addr),
&id,
tx_count,
NUM_LAMPORTS_PER_ACCOUNT,
);
let accounts: HashMap<String, u64> = serde_yaml::from_reader(file).unwrap();
let mut keypairs = vec![];
let mut last_balance = 0;
accounts.into_iter().for_each(|(keypair, balance)| {
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
keypairs.push(Keypair::from_bytes(&bytes).unwrap());
last_balance = balance;
});
(keypairs, last_balance)
} else {
generate_and_fund_keypairs(
&client,
Some(drone_addr),
&id,
tx_count,
NUM_LAMPORTS_PER_ACCOUNT,
)
.unwrap_or_else(|e| {
eprintln!("Error could not fund keys: {:?}", e);
exit(1);
})
};
let config = Config {
id,
@ -57,5 +115,5 @@ fn main() {
sustained,
};
do_bench_tps(clients, config, keypairs, keypair_balance);
do_bench_tps(vec![client], config, keypairs, keypair_balance);
}

View File

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

View File

@ -5,6 +5,8 @@
- [Terminology](terminology.md)
- [Getting Started](getting-started.md)
- [Testnet Participation](testnet-participation.md)
- [Testnet Replicator](testnet-replicator.md)
- [Example: Web Wallet](webwallet.md)
- [Programming Model](programs.md)
@ -16,10 +18,10 @@
- [Leader Rotation](leader-rotation.md)
- [Fork Generation](fork-generation.md)
- [Managing Forks](managing-forks.md)
- [Data Plane Fanout](data-plane-fanout.md)
- [Turbine Block Propagation](turbine-block-propagation.md)
- [Ledger Replication](ledger-replication.md)
- [Secure Vote Signing](vote-signing.md)
- [Staking Delegation and Rewards](stake-delegation-and-rewards.md)
- [Stake Delegation and Rewards](stake-delegation-and-rewards.md)
- [Performance Metrics](performance-metrics.md)
- [Anatomy of a Validator](validator.md)
@ -39,7 +41,6 @@
- [Ledger Replication](ledger-replication-to-implement.md)
- [Secure Vote Signing](vote-signing-to-implement.md)
- [Staking Rewards](staking-rewards.md)
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.md)
- [Cluster Economics](ed_overview.md)
- [Validation-client Economics](ed_validation_client_economics.md)
- [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md)
@ -55,15 +56,17 @@
- [References](ed_references.md)
- [Cluster Test Framework](cluster-test-framework.md)
- [Credit-only Accounts](credit-only-credit-debit-accounts.md)
- [Deterministic Transaction Fees](transaction-fees.md)
- [Validator](validator-proposal.md)
- [Implemented Design Proposals](implemented-proposals.md)
- [Blocktree](blocktree.md)
- [Cluster Software Installation and Updates](installer.md)
- [Deterministic Transaction Fees](transaction-fees.md)
- [Fork Selection](fork-selection.md)
- [Leader-to-Leader Transition](leader-leader-transition.md)
- [Leader-to-Validator Transition](leader-validator-transition.md)
- [Testnet Participation](testnet-participation.md)
- [Testing Programs](testing-programs.md)
- [Reliable Vote Transmission](reliable-vote-transmission.md)
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.md)
- [Persistent Account Storage](persistent-account-storage.md)
- [Cluster Software Installation and Updates](installer.md)
- [Reliable Vote Transmission](reliable-vote-transmission.md)
- [Repair Service](repair-service.md)
- [Testing Programs](testing-programs.md)

View File

@ -161,7 +161,7 @@ This will dump all the threads stack traces into gdb.txt
In this example the client connects to our public testnet. To run validators on the testnet you would need to open udp ports `8000-10000`.
```bash
$ ./multinode-demo/client.sh --entrypoint testnet.solana.com:8001 --duration 60
$ ./multinode-demo/client.sh --entrypoint testnet.solana.com:8001 --drone testnet.solana.com:9900 --duration 60 --tx_count 50
```
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

@ -12,18 +12,18 @@ updates is managed using an on-chain update manifest program.
#### Fetch and run a pre-built installer using a bootstrap curl/shell script
The easiest install method for supported platforms:
```bash
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.13.0/install/solana-install-init.sh | sh
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.0/install/solana-install-init.sh | sh
```
This script will check github for the latest tagged release and download and run the
`solana-install` binary from there.
`solana-install-init` binary from there.
If additional arguments need to be specified during the installation, the
following shell syntax is used:
```bash
$ init_args=.... # arguments for `solana-installer init ...`
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.13.0/install/solana-install-init.sh | sh -s - ${init_args}
$ init_args=.... # arguments for `solana-install-init ...`
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.0/install/solana-install-init.sh | sh -s - ${init_args}
```
#### Fetch and run a pre-built installer from a Github release
@ -31,9 +31,9 @@ With a well-known release URL, a pre-built binary can be obtained for supported
platforms:
```bash
$ curl -o solana-install https://github.com/solana-labs/solana/releases/download/v0.13.0/solana-install-x86_64-apple-darwin
$ chmod +x ./solana-install
$ ./solana-install --help
$ curl -o solana-install-init https://github.com/solana-labs/solana/releases/download/v0.16.0/solana-install-init-x86_64-apple-darwin
$ chmod +x ./solana-install-init
$ ./solana-install-init --help
```
#### Build and run the installer from source
@ -49,7 +49,7 @@ $ cargo run -- --help
Given a solana release tarball (as created by `ci/publish-tarball.sh`) that has already been uploaded to a publicly accessible URL,
the following commands will deploy the update:
```bash
$ solana-keygen -o update-manifest.json # <-- only generated once, the public key is shared with users
$ solana-keygen new -o update-manifest.json # <-- only generated once, the public key is shared with users
$ solana-install deploy http://example.com/path/to/solana-release.tar.bz2 update-manifest.json
```
@ -119,7 +119,7 @@ It manages the following files and directories in the user's home directory:
#### Command-line Interface
```manpage
solana-install 0.13.0
solana-install 0.16.0
The solana cluster software installer
USAGE:

View File

@ -25,11 +25,13 @@ Methods
* [getAccountInfo](#getaccountinfo)
* [getBalance](#getbalance)
* [getClusterNodes](#getclusternodes)
* [getProgramAccounts](#getprogramaccounts)
* [getRecentBlockhash](#getrecentblockhash)
* [getSignatureStatus](#getsignaturestatus)
* [getSlotLeader](#getslotleader)
* [getNumBlocksSinceSignatureConfirmation](#getnumblockssincesignatureconfirmation)
* [getTransactionCount](#gettransactioncount)
* [getTotalSupply](#gettotalsupply)
* [getEpochVoteAccounts](#getepochvoteaccounts)
* [requestAirdrop](#requestairdrop)
* [sendTransaction](#sendtransaction)
@ -95,6 +97,32 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
{"jsonrpc":"2.0","result":true,"id":1}
```
---
### getAccountInfo
Returns all information associated with the account of provided Pubkey
##### Parameters:
* `string` - Pubkey of account to query, as base-58 encoded string
##### Results:
The result field will be a JSON object with the following sub fields:
* `lamports`, number of lamports assigned to this account, as a signed 64-bit integer
* `owner`, array of 32 bytes representing the program this account has been assigned to
* `data`, array of bytes representing any data associated with the account
* `executable`, boolean indicating if the account contains a program (and is strictly read-only)
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"id":1}
```
---
### getBalance
@ -125,7 +153,7 @@ None
##### Results:
The result field will be an array of JSON objects, each with the following sub fields:
* `id` - Node identifier, as base-58 encoded string
* `pubkey` - Node public key, as base-58 encoded string
* `gossip` - Gossip network address for the node
* `tpu` - TPU network address for the node
* `rpc` - JSON RPC network address for the node, or `null` if the JSON RPC service is not enabled
@ -136,33 +164,34 @@ The result field will be an array of JSON objects, each with the following sub f
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getClusterNodes"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[{"gossip":"10.239.6.48:8001","id":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"}],"id":1}
{"jsonrpc":"2.0","result":[{"gossip":"10.239.6.48:8001","pubkey":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"}],"id":1}
```
---
### getAccountInfo
Returns all information associated with the account of provided Pubkey
### getProgramAccounts
Returns all accounts owned by the provided program Pubkey
##### Parameters:
* `string` - Pubkey of account to query, as base-58 encoded string
* `string` - Pubkey of program, as base-58 encoded string
##### Results:
The result field will be a JSON object with the following sub fields:
The result field will be an array of arrays. Each sub array will contain:
* `string` - a the account Pubkey as base-58 encoded string
and a JSON object, with the following sub fields:
* `lamports`, number of lamports assigned to this account, as a signed 64-bit integer
* `owner`, array of 32 bytes representing the program this account has been assigned to
* `data`, array of bytes representing any data associated with the account
* `executable`, boolean indicating if the account contains a program (and is strictly read-only)
* `loader`, array of 32 bytes representing the loader for this program (if `executable`), otherwise all
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getProgramAccounts", "params":["8nQwAgzN2yyUzrukXsCa3JELBYqDQrqJ3UyHiWazWxHR"]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"id":1}
{"jsonrpc":"2.0","result":[["BqGKYtAKu69ZdWEBtZHh4xgJY1BYa2YBiBReQE3pe383", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":1,"data":[]], ["4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":10,"data":[]]]},"id":1}
```
---
@ -275,6 +304,26 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
---
### getTotalSupply
Returns the current total supply in Lamports
##### Parameters:
None
##### Results:
* `integer` - Total supply, as unsigned 64-bit integer
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTotalSupply"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":10126,"id":1}
```
---
### getEpochVoteAccounts
Returns the account info and associated stake for all the voting accounts in the current epoch.
@ -282,19 +331,11 @@ Returns the account info and associated stake for all the voting accounts in the
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
The result field will be an array of JSON objects, each with the following sub fields:
* `votePubkey` - Vote account public key, as base-58 encoded string
* `nodePubkey` - Node public key, as base-58 encoded string
* `stake` - the stake, in lamports, delegated to this vote 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
@ -302,7 +343,7 @@ Each VoteState will be a JSON object with the following sub fields:
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}
{"jsonrpc":"2.0","result":[{"commission":0,"nodePubkey":"Et2RaZJdJRTzTkodUwiHr4H6sLkVmijBFv8tkd7oSSFY","stake":42,"votePubkey":"B4CdWq3NBSoH2wYsVE1CaZSWPo2ZtopE4SJipQhZ3srF"}],"id":1}
```
---
@ -389,7 +430,7 @@ for a given account public key changes
##### Notification Format:
```bash
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"loader":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
```
---

View File

@ -45,7 +45,7 @@ The upsides compared to guards:
* The timeout is not fixed.
* The timeout is local to the leader, and therefore can be clever. The leader's
heuristic can take into account avalanche performance.
heuristic can take into account turbine performance.
* This design doesn't require a ledger hard fork to update.

View File

@ -1,19 +1,18 @@
# Ledger Replication
At full capacity on a 1gbps network solana will generate 4 petabytes of data
per year. To prevent the network from centralizing around full nodes that have
per year. To prevent the network from centralizing around validators that have
to store the full data set this protocol proposes a way for mining nodes to
provide storage capacity for pieces of the network.
provide storage capacity for pieces of the data.
The basic idea to Proof of Replication is encrypting a dataset with a public
symmetric key using CBC encryption, then hash the encrypted dataset. The main
problem with the naive approach is that a dishonest storage node can stream the
encryption and delete the data as its hashed. The simple solution is to force
the hash to be done on the reverse of the encryption, or perhaps with a random
order. This ensures that all the data is present during the generation of the
proof and it also requires the validator to have the entirety of the encrypted
data present for verification of every proof of every identity. So the space
required to validate is `number_of_proofs * data_size`
encryption and delete the data as it's hashed. The simple solution is to periodically
regenerate the hash based on a signed PoH value. This ensures that all the data is present
during the generation of the proof and it also requires validators to have the
entirety of the encrypted data present for verification of every proof of every identity.
So the space required to validate is `number_of_proofs * data_size`
## Optimization with PoH
@ -29,13 +28,12 @@ core. The total space required for verification is `1_ledger_segment +
## Network
Validators for PoRep are the same validators that are verifying transactions.
They have some stake that they have put up as collateral that ensures that
their work is honest. If you can prove that a validator verified a fake PoRep,
then the validator will not receive a reward for that storage epoch.
If a replicator can prove that a validator verified a fake PoRep, then the
validator will not receive a reward for that storage epoch.
Replicators are specialized *light clients*. They download a part of the ledger
and store it, and provide PoReps of storing the ledger. For each verified PoRep
replicators earn a reward of sol from the mining pool.
Replicators are specialized *light clients*. They download a part of the
ledger (a.k.a Segment) and store it, and provide PoReps of storing the ledger.
For each verified PoRep replicators earn a reward of sol from the mining pool.
## Constraints
@ -55,9 +53,8 @@ changes to determine what rate it can validate storage proofs.
1. SLOTS\_PER\_SEGMENT: Number of slots in a segment of ledger data. The
unit of storage for a replicator.
2. NUM\_KEY\_ROTATION\_TICKS: Number of ticks to save a PoH value and cause a
key generation for the section of ledger just generated and the rotation of
another key in the set.
2. NUM\_KEY\_ROTATION\_SEGMENTS: Number of segments after which replicators
regenerate their encryption keys and select a new dataset to store.
3. NUM\_STORAGE\_PROOFS: Number of storage proofs required for a storage proof
claim to be successfully rewarded.
4. RATIO\_OF\_FAKE\_PROOFS: Ratio of fake proofs to real proofs that a storage
@ -66,36 +63,40 @@ mining proof claim has to contain to be valid for a reward.
proof.
6. NUM\_CHACHA\_ROUNDS: Number of encryption rounds performed to generate
encrypted state.
7. NUM\_SLOTS\_PER\_TURN: Number of slots that define a single storage epoch or
a "turn" of the PoRep game.
### Validator behavior
1. Validator joins the network and submits a storage validation capacity
transaction which tells the network how many proofs it can process in a given
period defined by NUM\_KEY\_ROTATION\_TICKS.
2. Every NUM\_KEY\_ROTATION\_TICKS the validator stores the PoH value at that
height.
3. Validator generates a storage proof confirmation transaction.
4. The storage proof confirmation transaction is integrated into the ledger.
6. Validator responds to RPC interfaces for what the last storage epoch PoH
value is and its slot.
1. Validators join the network and begin looking for replicator accounts at each
storage epoch/turn boundary.
2. Every turn, Validators sign the PoH value at the boundary and use that signature
to randomly pick proofs to verify from each storage account found in the turn boundary.
This signed value is also submitted to the validator's storage account and will be used by
replicators at a later stage to cross-verify.
3. Every `NUM_SLOTS_PER_TURN` slots the validator advertises the PoH value. This is value
is also served to Replicators via RPC interfaces.
4. For a given turn N, all validations get locked out until turn N+3 (a gap of 2 turn/epoch).
At which point all validations during that turn are available for reward collection.
5. Any incorrect validations will be marked during the turn in between.
### Replicator behavior
1. Since a replicator is somewhat of a light client and not downloading all the
ledger data, they have to rely on other full nodes (validators) for
information. Any given validator may or may not be malicious and give incorrect
information, although there are not any obvious attack vectors that this could
accomplish besides having the replicator do extra wasted work. For many of the
operations there are a number of options depending on how paranoid a replicator
is:
ledger data, they have to rely on other validators and replicators for information.
Any given validator may or may not be malicious and give incorrect information, although
there are not any obvious attack vectors that this could accomplish besides having the
replicator do extra wasted work. For many of the operations there are a number of options
depending on how paranoid a replicator is:
- (a) replicator can ask a validator
- (b) replicator can ask multiple validators
- (c) replicator can subscribe to the full transaction stream and generate
the information itself
- (d) replicator can subscribe to an abbreviated transaction stream to
generate the information itself
2. A replicator obtains the PoH hash corresponding to the last key rotation
along with its slot.
- (c) replicator can ask other replicators
- (d) replicator can subscribe to the full transaction stream and generate
the information itself (assuming the slot is recent enough)
- (e) replicator can subscribe to an abbreviated transaction stream to
generate the information itself (assuming the slot is recent enough)
2. A replicator obtains the PoH hash corresponding to the last turn with its slot.
3. The replicator signs the PoH hash with its keypair. That signature is the
seed used to pick the segment to replicate and also the encryption key. The
replicator mods the signature with the slot to get which segment to
@ -103,38 +104,67 @@ replicate.
4. The replicator retrives the ledger by asking peer validators and
replicators. See 6.5.
5. The replicator then encrypts that segment with the key with chacha algorithm
in CBC mode with NUM\_CHACHA\_ROUNDS of encryption.
6. The replicator initializes a chacha rng with the signature from step 2 as
in CBC mode with `NUM_CHACHA_ROUNDS` of encryption.
6. The replicator initializes a chacha rng with the a signed recent PoH value as
the seed.
7. The replicator generates NUM\_STORAGE\_SAMPLES samples in the range of the
7. The replicator generates `NUM_STORAGE_SAMPLES` samples in the range of the
entry size and samples the encrypted segment with sha256 for 32-bytes at each
offset value. Sampling the state should be faster than generating the encrypted
segment.
8. The replicator sends a PoRep proof transaction which contains its sha state
at the end of the sampling operation, its seed and the samples it used to the
current leader and it is put onto the ledger.
9. During a given turn the replicator should submit many proofs for the same segment
and based on the `RATIO_OF_FAKE_PROOFS` some of those proofs must be fake.
10. As the PoRep game enters the next turn, the replicator must submit a
transaction with the mask of which proofs were fake during the last turn. This
transaction will define the rewards for both replicators and validators.
11. Finally for a turn N, as the PoRep game enters turn N + 3, replicator's proofs for
turn N will be counted towards their rewards.
### The PoRep Game
The Proof of Replication game has 4 primary stages. For each "turn" multiple PoRep
games can be in progress but each in a different stage.
The 4 stages of the PoRep Game are as follows:
1. Proof submission stage
- Replicators: submit as many proofs as possible during this stage
- Validators: No-op
2. Proof verification stage
- Replicators: No-op
- Validators: Select replicators and verify their proofs from the previous turn
3. Proof challenge stage
- Replicators: Submit the proof mask with justifications (for fake proofs submitted 2 turns ago)
- Validators: No-op
4. Reward collection stage
- Replicators: Collect rewards for 3 turns ago
- Validators: Collect rewards for 3 turns ago
For each turn of the PoRep game, both Validators and Replicators evaluate each
stage. The stages are run as separate transactions on the storage program.
### Finding who has a given block of ledger
1. Validators monitor the transaction stream for storage mining proofs, and
keep a mapping of ledger segments by slot to public keys. When it sees
a storage mining proof it updates this mapping and provides an RPC interface
which takes a slot and hands back a list of public keys. The client
then looks up in their cluster\_info table to see which network address that
corresponds to and sends a repair request to retrieve the necessary blocks of
ledger.
2. Validators would need to prune this list which it could do by periodically
looking at the oldest entries in its mappings and doing a network query to see
if the storage host is still serving the first entry.
1. Validators monitor the turns in the PoRep game and look at the rooted bank
at turn boundaries for any proofs.
2. Validators maintain a map of ledger segments and corresponding replicator public keys.
The map is updated when a Validator processes a replicator's proofs for a segment.
The validator provides an RPC interface to access the this map. Using this API, clients
can map a segment to a replicator's network address (correlating it via cluster_info table).
The clients can then send repair requests to the replicator to retrieve segments.
3. Validators would need to invalidate this list every N turns.
## Sybil attacks
For any random seed, we force everyone to use a signature that is derived from
a PoH hash. Everyone must use the same count, so the same PoH hash is signed by
every participant. The signatures are then each cryptographically tied to the
keypair, which prevents a leader from grinding on the resulting value for more
than 1 identity.
a PoH hash at the turn boundary. Everyone uses the same count, so the same PoH
hash is signed by every participant. The signatures are then each cryptographically
tied to the keypair, which prevents a leader from grinding on the resulting
value for more than 1 identity.
Since there are many more client identities then encryption identities, we need
to split the reward for multiple clients, and prevent Sybil attacks from
@ -155,8 +185,7 @@ the network can reward long lived client identities more than new ones.
showing the initial state for the hash.
- If a validator marks real proofs as fake, no on-chain computation can be done
to distinguish who is correct. Rewards would have to rely on the results from
multiple validators in a stake-weighted fashion to catch bad actors and
replicators from being locked out of the network.
multiple validators to catch bad actors and replicators from being denied rewards.
- Validator stealing mining proof results for itself. The proofs are derived
from a signature from a replicator, since the validator does not know the
private key used to generate the encryption key, it cannot be the generator of

View File

@ -76,21 +76,24 @@ this field can only modified by this entity
### StakeState
A StakeState takes one of two forms, StakeState::Delegate and StakeState::MiningPool.
A StakeState takes one of two forms, StakeState::Stake and StakeState::MiningPool.
### StakeState::Delegate
### StakeState::Stake
StakeState is the current delegation preference of the **staker**. StakeState
Stake is the current delegation preference of the **staker**. Stake
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.
* `stake` - The actual activated stake.
* Account::lamports - Lamports available for staking, including any earned as rewards.
### StakeState::MiningPool
There are two approaches to the mining pool. The bank could allow the
@ -105,11 +108,12 @@ tokens stored as `Account::lamports`.
The stakes and the MiningPool are accounts that are owned by the same `Stake`
program.
### StakeInstruction::Initialize
### StakeInstruction::DelegateStake(stake)
* `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[0]` - RW - The StakeState::Stake instance.
`StakeState::Stake::credits_observed` is initialized to `VoteState::credits`.
`StakeState::Stake::voter_pubkey` is initialized to `account[1]`
`StakeState::Stake::stake` is initialized to `stake`, as long as it's less than account[0].lamports
* `account[1]` - R - The VoteState instance.
@ -124,7 +128,7 @@ deposited into the StakeState and as validator commission is proportional to
* `account[0]` - RW - The StakeState::MiningPool instance that will fulfill the
reward.
* `account[1]` - RW - The StakeState::Delegate instance that is redeeming votes
* `account[1]` - RW - The StakeState::Stake instance that is redeeming votes
credits.
* `account[2]` - R - The VoteState instance, must be the same as
`StakeState::voter_pubkey`
@ -132,7 +136,7 @@ credits.
Reward is payed 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 `VoteState` token
balance, and the reward is deposited to the `StakeState::Delegate` token balance. The
balance, and the reward is deposited to the `StakeState::Stake` token balance. The
reward and the commission is weighted by the `StakeState::lamports` divided by total lamports staked.
The Staker or the owner of the Stake program sends a transaction with this
@ -146,7 +150,7 @@ 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
`StakeState::Stake::credits_observed` is updated to the latest
`VoteState::credits` value.
### Collecting network fees into the MiningPool
@ -175,13 +179,13 @@ many rewards to be claimed concurrently.
## Passive Delegation
Any number of instances of StakeState::Delegate programs can delegate to a single
Any number of instances of StakeState::Stake programs can delegate to a single
VoteState program without an interactive action from the identity controlling
the VoteState program or submitting votes to the program.
The total stake allocated to a VoteState program can be calculated by the sum of
all the StakeState programs that have the VoteState pubkey as the
`StakeState::Delegate::voter_pubkey`.
`StakeState::Stake::voter_pubkey`.
## Example Callflow

View File

@ -26,4 +26,4 @@ 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.
as a time series graph.

View File

@ -34,10 +34,10 @@ The different protocol strategies to address the above challenges:
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).
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.
@ -45,7 +45,7 @@ The different protocol strategies to address the above challenges:
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.
Gossip messages are updated every time a validator receives a complete slot within the epoch. Completed slots are detected by blocktree and sent over a channel to RepairService. It is important to note that we know that by the time a slot X is complete, the epoch schedule must exist for the epoch that contains slot X because WindowService will reject blobs for unconfirmed epochs. When a newly completed slot is detected, we also update the current root if it has changed since the last update. The root is made available to RepairService through Blocktree, which holds the latest root.

View File

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

View File

@ -1,8 +1,8 @@
# Staking Rewards
Initial Proof of Stake (PoS) (i.e. using in-protocol asset, SOL, to provide
secure consensus) design ideas outlined here. Solana will implement a proof of
stake reward/security scheme for node validators in the cluster. The purpose is
A Proof of Stake (PoS), (i.e. using in-protocol asset, SOL, to provide
secure consensus) design is outlined here. Solana implements a proof of
stake reward/security scheme for validator nodes in the cluster. The purpose is
threefold:
- Align validator incentives with that of the greater cluster through
@ -48,7 +48,7 @@ specific parameters will be necessary:
Solana's trustless sense of time and ordering provided by its PoH data
structure, along with its
[avalanche](https://www.youtube.com/watch?v=qt_gDRXHrHQ&t=1s) data broadcast
[turbine](https://www.youtube.com/watch?v=qt_gDRXHrHQ&t=1s) data broadcast
and transmission design, should provide sub-second transaction confirmation times that scale
with the log of the number of nodes in the cluster. This means we shouldn't
have to restrict the number of validating nodes with a prohibitive 'minimum
@ -64,7 +64,7 @@ capital-at-risk to prevent a logical/optimal strategy of multiple chain voting.
We intend to implement slashing rules which, if broken, result some amount of
the offending validator's deposited stake to be removed from circulation. Given
the ordering properties of the PoH data structure, we believe we can simplify
our slashing rules to the level of a voting lockout time assigned per vote.
our slashing rules to the level of a voting lockout time assigned per vote.
I.e. Each vote has an associated lockout time (PoH duration) that represents a
duration by any additional vote from that validator must be in a PoH that
@ -110,7 +110,7 @@ in a slashable amount as a function of either:
1. the fraction of validators, out of the total validator pool, that were also
slashed during the same time period (ala Casper)
2. the amount of time since the vote was cast (e.g. a linearly increasing % of
total deposited as slashable amount over time), or both.
total deposited as slashable amount over time), or both.
This is an area currently under exploration

View File

@ -32,7 +32,7 @@ traversal issues. A cloud-hosted machine works best. **Ensure that IP ports
Prebuilt binaries are available for Linux x86_64 (Ubuntu 18.04 recommended).
MacOS or WSL users may build from source.
For a performance testnet with many transactions we have some preliminary recomended setups:
For a performance testnet with many transactions we have some preliminary recommended setups:
| | Low end | Medium end | High end | Notes |
| --- | ---------|------------|----------| -- |
@ -42,6 +42,13 @@ For a performance testnet with many transactions we have some preliminary recome
| 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. |
#### GPU Requirements
CUDA is required to make use of the GPU on your system. The provided Solana
release binaries are built on Ubuntu 18.04 with <a
href="https://developer.nvidia.com/cuda-toolkit-archive">CUDA Toolkit 10.1
update 1"</a>. If your machine is using a different CUDA version then you will
need to rebuild from source.
#### Confirm The Testnet Is Reachable
Before attaching a validator node, sanity check that the cluster is accessible
to your machine by running some simple commands. If any of the commands fail,
@ -64,11 +71,11 @@ for more detail on cluster activity.
##### Bootstrap with `solana-install`
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 and mac OS systems.
```bash
$ 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
$ export SOLANA_RELEASE=v0.16.0 # skip this line to install the latest release
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.0/install/solana-install-init.sh | sh -s
```
Alternatively build the `solana-install` program from source and run the
@ -78,11 +85,12 @@ $ solana-install init
```
After a successful install, `solana-install update` may be used to easily update the cluster
software to a newer version.
software to a newer version at any time.
##### Download Prebuilt Binaries
Binaries are available for Linux x86_64 systems.
If you would rather not use `solana-install` to manage the install, you can manually download and install the binaries.
###### Linux
Download the binaries by navigating to
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
download **solana-release-x86_64-unknown-linux-gnu.tar.bz2**, then extract the
@ -92,6 +100,17 @@ $ tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
###### mac OS
Download the binaries by navigating to
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
download **solana-release-x86_64-apple-darwin.tar.bz2**, then extract the
archive:
```bash
$ tar jxf solana-release-x86_64-apple-darwin.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
##### Build From Source
If you are unable to use the prebuilt binaries or prefer to build it yourself
from source, navigate to
@ -103,6 +122,12 @@ $ ./scripts/cargo-install-all.sh .
$ export PATH=$PWD/bin:$PATH
```
If building for CUDA, include the `cuda` feature flag as well:
```bash
$ ./scripts/cargo-install-all.sh . cuda
$ export PATH=$PWD/bin:$PATH
```
### Starting The Validator
Sanity check that you are able to interact with the cluster by receiving a small
airdrop of lamports from the testnet drone:
@ -119,7 +144,7 @@ $ solana-gossip --entrypoint testnet.solana.com:8001 spy
Now configure a key pair for your validator by running:
```bash
$ solana-keygen -o validator-keypair.json
$ solana-keygen new -o ~/validator-keypair.json
```
Then use one of the following commands, depending on your installation
@ -128,22 +153,33 @@ choice, to start the node:
If this is a `solana-install`-installation:
```bash
$ clear-config.sh
$ validator.sh --identity validator-keypair.json --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
node while periodically checking for and applying software updates:
```bash
$ clear-config.sh
$ solana-install run validator.sh -- --identity validator-keypair.json --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:
```bash
$ USE_INSTALL=1 ./multinode-demo/clear-config.sh
$ USE_INSTALL=1 ./multinode-demo/validator.sh --identity validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
$ USE_INSTALL=1 ./multinode-demo/validator.sh --identity ~/validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
```
#### Enabling CUDA
By default CUDA is disabled. If your machine has a GPU with CUDA installed,
define the SOLANA_CUDA flag in your environment *before* running any of the
previusly mentioned commands
```bash
$ export SOLANA_CUDA=1
```
When your validator is started look for the following log message to indicate that CUDA is enabled:
`"[<timestamp> solana::validator] CUDA is enabled"`
#### Controlling local network port allocation
By default the validator will dynamically select available network ports in the
8000-10000 range, and may be overridden with `--dynamic-port-range`. For
@ -164,7 +200,7 @@ accounts: ...
The **identity pubkey** for your validator can also be found by running:
```bash
$ solana-keygen pubkey validator-keypair.json
$ solana-keygen pubkey ~/validator-keypair.json
```
From another console, confirm the IP address and **identity pubkey** of your validator is visible in the
@ -176,7 +212,7 @@ $ solana-gossip --entrypoint testnet.solana.com:8001 spy
Provide the **vote pubkey** to the `solana-wallet show-vote-account` command to view
the recent voting activity from your validator:
```bash
$ solana-wallet -n testnet.solana.com show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
$ solana-wallet show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
```
The vote pubkey for the validator can also be found by running:
@ -187,13 +223,20 @@ $ solana-keygen pubkey ~/.local/share/solana/install/active_release/config-local
$ solana-keygen pubkey ./config-local/validator-vote-keypair.json
```
### Sharing Metrics From Your Validator
If you have obtained a metrics username/password from the Solana maintainers to
help us monitor the health of the testnet, please perform the following steps
before starting the validator to activate metrics reporting:
#### Validator Metrics
Metrics are available for local monitoring of your validator.
Docker must be installed and the current user added to the docker group. Then
download `solana-metrics.tar.bz2` from the Github Release and run
```bash
export u="username obtained from the Solana maintainers"
export p="password obtained from the Solana maintainers"
export SOLANA_METRICS_CONFIG="db=testnet,u=${u:?},p=${p:?}"
source scripts/configure-metrics.sh
$ tar jxf solana-metrics.tar.bz2
$ cd solana-metrics/
$ ./start.sh
```
A local InfluxDB and Grafana instance is now running on your machine. Define
`SOLANA_METRICS_CONFIG` in your environment as described at the end of the
`start.sh` output and restart your validator.
Metrics should now be streaming and visible from your local Grafana dashboard.

View File

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

View File

@ -8,17 +8,14 @@ client won't know how much was collected until the transaction is confirmed by
the cluster and the remaining balance is checked. It smells of exactly what we
dislike about Ethereum's "gas", non-determinism.
## Implementation Status
This design is not yet implemented, but is written as though it has been. Once
implemented, delete this comment.
### Congestion-driven fees
Each validator uses *signatures per slot* (SPS) to estimate network congestion
and *SPS target* to estimate the desired processing capacity of the cluster.
The validator learns the SPS target from the genesis block, whereas it
calculates SPS from the ledger data in the previous epoch.
calculates SPS from recently processed transactions. The genesis block also
defines a target `lamports_per_signature`, which is the fee to charge per
signature when the cluster is operating at *SPS target*.
### Calculating fees
@ -37,8 +34,11 @@ lamports as returned by the fee calculator.
In the first implementation of this design, the only fee parameter is
`lamports_per_signature`. The more signatures the cluster needs to verify, the
higher the fee. The exact number of lamports is determined by the ratio of SPS
to the SPS target. The cluster lowers `lamports_per_signature` when SPS is
below the target and raises it when at or above the target.
to the SPS target. At the end of each slot, the cluster lowers
`lamports_per_signature` when SPS is below the target and raises it when above
the target. The minimum value for `lamports_per_signature` is 50% of the target
`lamports_per_signature` and the maximum value is 10x the target
`lamports_per_signature'
Future parameters might include:

View File

@ -1,12 +1,12 @@
# Data Plane Fanout
# Turbine Block Propagation
A Solana cluster uses a multi-layer mechanism called *data plane fanout* to
broadcast transaction blobs to all nodes in a very quick and efficient manner.
In order to establish the fanout, the cluster divides itself into small
collections of nodes, called *neighborhoods*. Each node is responsible for
sharing any data it receives with the other nodes in its neighborhood, as well
as propagating the data on to a small set of nodes in other neighborhoods.
This way each node only has to communicate with a small number of nodes.
A Solana cluster uses a multi-layer block propagation mechanism called *Turbine*
to broadcast transaction blobs to all nodes with minimal amount of duplicate
messages. The cluster divides itself into small collections of nodes, called
*neighborhoods*. Each node is responsible for sharing any data it receives with
the other nodes in its neighborhood, as well as propagating the data on to a
small set of nodes in other neighborhoods. This way each node only has to
communicate with a small number of nodes.
During its slot, the leader node distributes blobs between the validator nodes
in the first neighborhood (layer 0). Each validator shares its data within its
@ -26,6 +26,14 @@ make up layer 0. These will automatically be the highest stake holders, allowing
the heaviest votes to come back to the leader first. Layer-0 and lower-layer
nodes use the same logic to find their neighbors and next layer peers.
To reduce the possibility of attack vectors, each blob is transmitted over a
random tree of neighborhoods. Each node uses the same set of nodes representing
the cluster. A random tree is generated from the set for each blob using
randomness derived from the blob itself. Since the random seed is not known in
advance, attacks that try to eclipse neighborhoods from certain leaders or
blocks become very difficult, and should require almost complete control of the
stake in the cluster.
## Layer and Neighborhood Structure
The current leader makes its initial broadcasts to at most `DATA_PLANE_FANOUT`

View File

@ -284,6 +284,18 @@ ARGS:
<PATH> /path/to/program.o
```
```manpage
solana-wallet-fees
Display current cluster fees
USAGE:
solana-wallet fees
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
```
```manpage
solana-wallet-get-transaction-count
Get current transaction count

View File

@ -521,4 +521,4 @@ ul#searchresults span.teaser em {
}
.content pre {
padding: 0 28px;
}
}

View File

@ -152,4 +152,4 @@ blockquote {
*:active,
*:hover {
outline: none;
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,23 +0,0 @@
#!/usr/bin/env bash
#
# Builds perf-libs from the upstream source and installs them into the correct
# location in the tree
#
set -e
cd "$(dirname "$0")"
if [[ -d target/perf-libs ]]; then
echo "target/perf-libs/ already exists, to continue run:"
echo "$ rm -rf target/perf-libs"
exit 1
fi
(
set -x
git clone git@github.com:solana-labs/solana-perf-libs.git target/perf-libs
cd target/perf-libs
make -j"$(nproc)"
make DESTDIR=. install
)
./fetch-perf-libs.sh

12
chacha-sys/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "solana-chacha-sys"
version = "0.16.2"
description = "Solana chacha-sys"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
homepage = "https://solana.com/"
license = "Apache-2.0"
edition = "2018"
[build-dependencies]
cc = "1.0.37"

8
chacha-sys/build.rs Normal file
View File

@ -0,0 +1,8 @@
extern crate cc;
fn main() {
cc::Build::new()
.file("cpu-crypt/chacha20_core.c")
.file("cpu-crypt/chacha_cbc.c")
.compile("libcpu-crypt");
}

1
chacha-sys/cpu-crypt/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
release/

View File

@ -0,0 +1,25 @@
V:=debug
LIB:=cpu-crypt
CFLAGS_common:=-Wall -Werror -pedantic -fPIC
CFLAGS_release:=-march=native -O3 $(CFLAGS_common)
CFLAGS_debug:=-g $(CFLAGS_common)
CFLAGS:=$(CFLAGS_$V)
all: $V/lib$(LIB).a
$V/chacha20_core.o: chacha20_core.c chacha.h
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@
$V/chacha_cbc.o: chacha_cbc.c chacha.h
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@
$V/lib$(LIB).a: $V/chacha20_core.o $V/chacha_cbc.o
$(AR) rcs $@ $^
.PHONY:clean
clean:
rm -rf $V

View File

@ -0,0 +1,35 @@
#ifndef HEADER_CHACHA_H
# define HEADER_CHACHA_H
#include <string.h>
#include <inttypes.h>
# include <stddef.h>
# ifdef __cplusplus
extern "C" {
# endif
typedef unsigned int u32;
#define CHACHA_KEY_SIZE 32
#define CHACHA_NONCE_SIZE 12
#define CHACHA_BLOCK_SIZE 64
#define CHACHA_ROUNDS 500
void chacha20_encrypt(const u32 input[16],
unsigned char output[64],
int num_rounds);
void chacha20_encrypt_ctr(const uint8_t *in, uint8_t *out, size_t in_len,
const uint8_t key[CHACHA_KEY_SIZE], const uint8_t nonce[CHACHA_NONCE_SIZE],
uint32_t counter);
void chacha20_cbc128_encrypt(const unsigned char* in, unsigned char* out,
uint32_t len, const uint8_t* key,
unsigned char* ivec);
# ifdef __cplusplus
}
# endif
#endif

View File

@ -0,0 +1,102 @@
#include "chacha.h"
#define ROTL32(v, n) (((v) << (n)) | ((v) >> (32 - (n))))
#define ROTATE(v, c) ROTL32((v), (c))
#define XOR(v, w) ((v) ^ (w))
#define PLUS(x, y) ((x) + (y))
#define U32TO8_LITTLE(p, v) \
{ (p)[0] = ((v) ) & 0xff; (p)[1] = ((v) >> 8) & 0xff; \
(p)[2] = ((v) >> 16) & 0xff; (p)[3] = ((v) >> 24) & 0xff; }
#define U8TO32_LITTLE(p) \
(((u32)((p)[0]) ) | ((u32)((p)[1]) << 8) | \
((u32)((p)[2]) << 16) | ((u32)((p)[3]) << 24) )
#define QUARTERROUND(a,b,c,d) \
x[a] = PLUS(x[a],x[b]); x[d] = ROTATE(XOR(x[d],x[a]),16); \
x[c] = PLUS(x[c],x[d]); x[b] = ROTATE(XOR(x[b],x[c]),12); \
x[a] = PLUS(x[a],x[b]); x[d] = ROTATE(XOR(x[d],x[a]), 8); \
x[c] = PLUS(x[c],x[d]); x[b] = ROTATE(XOR(x[b],x[c]), 7);
// sigma contains the ChaCha constants, which happen to be an ASCII string.
static const uint8_t sigma[16] = { 'e', 'x', 'p', 'a', 'n', 'd', ' ', '3',
'2', '-', 'b', 'y', 't', 'e', ' ', 'k' };
void chacha20_encrypt(const u32 input[16],
unsigned char output[64],
int num_rounds)
{
u32 x[16];
int i;
memcpy(x, input, sizeof(u32) * 16);
for (i = num_rounds; i > 0; i -= 2) {
QUARTERROUND( 0, 4, 8,12)
QUARTERROUND( 1, 5, 9,13)
QUARTERROUND( 2, 6,10,14)
QUARTERROUND( 3, 7,11,15)
QUARTERROUND( 0, 5,10,15)
QUARTERROUND( 1, 6,11,12)
QUARTERROUND( 2, 7, 8,13)
QUARTERROUND( 3, 4, 9,14)
}
for (i = 0; i < 16; ++i) {
x[i] = PLUS(x[i], input[i]);
}
for (i = 0; i < 16; ++i) {
U32TO8_LITTLE(output + 4 * i, x[i]);
}
}
void chacha20_encrypt_ctr(const uint8_t *in, uint8_t *out, size_t in_len,
const uint8_t key[CHACHA_KEY_SIZE],
const uint8_t nonce[CHACHA_NONCE_SIZE],
uint32_t counter)
{
uint32_t input[16];
uint8_t buf[64];
size_t todo, i;
input[0] = U8TO32_LITTLE(sigma + 0);
input[1] = U8TO32_LITTLE(sigma + 4);
input[2] = U8TO32_LITTLE(sigma + 8);
input[3] = U8TO32_LITTLE(sigma + 12);
input[4] = U8TO32_LITTLE(key + 0);
input[5] = U8TO32_LITTLE(key + 4);
input[6] = U8TO32_LITTLE(key + 8);
input[7] = U8TO32_LITTLE(key + 12);
input[8] = U8TO32_LITTLE(key + 16);
input[9] = U8TO32_LITTLE(key + 20);
input[10] = U8TO32_LITTLE(key + 24);
input[11] = U8TO32_LITTLE(key + 28);
input[12] = counter;
input[13] = U8TO32_LITTLE(nonce + 0);
input[14] = U8TO32_LITTLE(nonce + 4);
input[15] = U8TO32_LITTLE(nonce + 8);
while (in_len > 0) {
todo = sizeof(buf);
if (in_len < todo) {
todo = in_len;
}
chacha20_encrypt(input, buf, 20);
for (i = 0; i < todo; i++) {
out[i] = in[i] ^ buf[i];
}
out += todo;
in += todo;
in_len -= todo;
input[12]++;
}
}

View File

@ -0,0 +1,72 @@
#include "chacha.h"
#if !defined(STRICT_ALIGNMENT) && !defined(PEDANTIC)
# define STRICT_ALIGNMENT 0
#endif
void chacha20_cbc128_encrypt(const unsigned char* in, unsigned char* out,
uint32_t len, const uint8_t* key,
unsigned char* ivec)
{
size_t n;
unsigned char *iv = ivec;
(void)key;
if (len == 0) {
return;
}
#if !defined(OPENSSL_SMALL_FOOTPRINT)
if (STRICT_ALIGNMENT &&
((size_t)in | (size_t)out | (size_t)ivec) % sizeof(size_t) != 0) {
while (len >= CHACHA_BLOCK_SIZE) {
for (n = 0; n < CHACHA_BLOCK_SIZE; ++n) {
out[n] = in[n] ^ iv[n];
//printf("%x ", out[n]);
}
chacha20_encrypt((const u32*)out, out, CHACHA_ROUNDS);
iv = out;
len -= CHACHA_BLOCK_SIZE;
in += CHACHA_BLOCK_SIZE;
out += CHACHA_BLOCK_SIZE;
}
} else {
while (len >= CHACHA_BLOCK_SIZE) {
for (n = 0; n < CHACHA_BLOCK_SIZE; n += sizeof(size_t)) {
*(size_t *)(out + n) =
*(size_t *)(in + n) ^ *(size_t *)(iv + n);
//printf("%zu ", *(size_t *)(iv + n));
}
chacha20_encrypt((const u32*)out, out, CHACHA_ROUNDS);
iv = out;
len -= CHACHA_BLOCK_SIZE;
in += CHACHA_BLOCK_SIZE;
out += CHACHA_BLOCK_SIZE;
}
}
#endif
while (len) {
for (n = 0; n < CHACHA_BLOCK_SIZE && n < len; ++n) {
out[n] = in[n] ^ iv[n];
}
for (; n < CHACHA_BLOCK_SIZE; ++n) {
out[n] = iv[n];
}
chacha20_encrypt((const u32*)out, out, CHACHA_ROUNDS);
iv = out;
if (len <= CHACHA_BLOCK_SIZE) {
break;
}
len -= CHACHA_BLOCK_SIZE;
in += CHACHA_BLOCK_SIZE;
out += CHACHA_BLOCK_SIZE;
}
memcpy(ivec, iv, CHACHA_BLOCK_SIZE);
}
void chacha20_cbc_encrypt(const uint8_t *in, uint8_t *out, size_t in_len,
const uint8_t key[CHACHA_KEY_SIZE], uint8_t* ivec)
{
chacha20_cbc128_encrypt(in, out, in_len, key, ivec);
}

21
chacha-sys/src/lib.rs Normal file
View File

@ -0,0 +1,21 @@
extern "C" {
fn chacha20_cbc_encrypt(
input: *const u8,
output: *mut u8,
in_len: usize,
key: *const u8,
ivec: *mut u8,
);
}
pub fn chacha_cbc_encrypt(input: &[u8], output: &mut [u8], key: &[u8], ivec: &mut [u8]) {
unsafe {
chacha20_cbc_encrypt(
input.as_ptr(),
output.as_mut_ptr(),
input.len(),
key.as_ptr(),
ivec.as_mut_ptr(),
);
}
}

View File

@ -12,7 +12,7 @@
set -e
cd "$(dirname "$0")"/..
if ci/is-pr.sh; then
if [[ -n $CI_PULL_REQUEST ]]; then
affectedFiles="$(buildkite-agent meta-data get affected_files)"
echo "Affected files in this PR: $affectedFiles"

View File

@ -2,13 +2,13 @@ steps:
- command: "ci/shellcheck.sh"
name: "shellcheck"
timeout_in_minutes: 5
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-checks.sh"
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-checks.sh"
name: "checks"
timeout_in_minutes: 15
- wait
- command: "ci/test-stable-perf.sh"
name: "stable-perf"
timeout_in_minutes: 20
timeout_in_minutes: 30
artifact_paths: "log-*.txt"
agents:
- "queue=cuda"
@ -21,7 +21,7 @@ steps:
artifact_paths: "log-*.txt"
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
name: "coverage"
timeout_in_minutes: 20
timeout_in_minutes: 40
# TODO: Fix and re-enable test-large-network.sh
# - command: "ci/test-large-network.sh || true"
# name: "large-network [ignored]"

View File

@ -89,11 +89,11 @@ BETA_CHANNEL_LATEST_TAG=${beta_tag:+v$beta_tag}
STABLE_CHANNEL_LATEST_TAG=${stable_tag:+v$stable_tag}
if [[ $BUILDKITE_BRANCH = "$STABLE_CHANNEL" ]]; then
if [[ $CI_BRANCH = "$STABLE_CHANNEL" ]]; then
CHANNEL=stable
elif [[ $BUILDKITE_BRANCH = "$EDGE_CHANNEL" ]]; then
elif [[ $CI_BRANCH = "$EDGE_CHANNEL" ]]; then
CHANNEL=edge
elif [[ $BUILDKITE_BRANCH = "$BETA_CHANNEL" ]]; then
elif [[ $CI_BRANCH = "$BETA_CHANNEL" ]]; then
CHANNEL=beta
fi

View File

@ -64,11 +64,14 @@ fi
ARGS+=(
--env BUILDKITE
--env BUILDKITE_AGENT_ACCESS_TOKEN
--env BUILDKITE_BRANCH
--env BUILDKITE_COMMIT
--env BUILDKITE_JOB_ID
--env BUILDKITE_TAG
--env CI
--env CI_BRANCH
--env CI_BUILD_ID
--env CI_COMMIT
--env CI_JOB_ID
--env CI_PULL_REQUEST
--env CI_REPO_SLUG
--env CODECOV_TOKEN
--env CRATES_IO_TOKEN
)

View File

@ -3,6 +3,7 @@ ARG date
RUN set -x \
&& rustup install nightly-$date \
&& rustup component add clippy --toolchain=nightly-$date \
&& rustup show \
&& rustc --version \
&& cargo --version \

View File

@ -15,12 +15,12 @@ To update the pinned version:
1. Run `ci/docker-rust-nightly/build.sh` to rebuild the nightly image locally,
or potentially `ci/docker-rust-nightly/build.sh YYYY-MM-DD` if there's a
specific YYYY-MM-DD that is desired (default is today's build).
1. Update `ci/rust-version.sh` to reflect the new nightly `YYY-MM-DD`
1. Run `SOLANA_DOCKER_RUN_NOSETUID=1 ci/docker-run.sh --nopull solanalabs/rust-nightly:YYYY-MM-DD ci/test-coverage.sh`
to confirm the new nightly image builds. Fix any issues as needed
1. Run `docker login` to enable pushing images to Docker Hub, if you're authorized.
1. Run `CI=true ci/docker-rust-nightly/build.sh YYYY-MM-DD` to push the new nightly image to dockerhub.com.
1. Modify the `solanalabs/rust-nightly:YYYY-MM-DD` reference in `ci/rust-version.sh` from the previous to
new *YYYY-MM-DD* value, send a PR with this change and any codebase adjustments needed.
1. Send a PR with the `ci/rust-version.sh` change and any codebase adjustments needed.
## Troubleshooting

View File

@ -1,6 +1,6 @@
# Note: when the rust version is changed also modify
# ci/buildkite.yml to pick up the new image tag
FROM rust:1.34.0
# ci/rust-version.sh to pick up the new image tag
FROM rust:1.35.0
RUN set -x \
&& apt update \

View File

@ -1,6 +1,7 @@
Docker image containing rust and some preinstalled packages used in CI.
This image may be manually updated by running `./build.sh` if you are a member
of the [Solana Labs](https://hub.docker.com/u/solanalabs/) Docker Hub
organization, but it is also automatically updated periodically by
[this automation](https://buildkite.com/solana-labs/solana-ci-docker-rust).
This image manually maintained:
1. Edit `Dockerfile` to match the desired rust version
2. Run `./build.sh` to publish the new image, if you are a member of the [Solana
Labs](https://hub.docker.com/u/solanalabs/) Docker Hub organization.

89
ci/env.sh Normal file
View File

@ -0,0 +1,89 @@
#
# Normalized CI environment variables
#
# |source| me
#
if [[ -n $CI ]]; then
export CI=1
if [[ -n $TRAVIS ]]; then
export CI_BRANCH=$TRAVIS_BRANCH
export CI_BUILD_ID=$TRAVIS_BUILD_ID
export CI_COMMIT=$TRAVIS_COMMIT
export CI_JOB_ID=$TRAVIS_JOB_ID
if $TRAVIS_PULL_REQUEST; then
export CI_PULL_REQUEST=true
else
export CI_PULL_REQUEST=
fi
export CI_OS_NAME=$TRAVIS_OS_NAME
export CI_REPO_SLUG=$TRAVIS_REPO_SLUG
export CI_TAG=$TRAVIS_TAG
elif [[ -n $BUILDKITE ]]; then
export CI_BRANCH=$BUILDKITE_BRANCH
export CI_BUILD_ID=$BUILDKITE_BUILD_ID
export CI_COMMIT=$BUILDKITE_COMMIT
export CI_JOB_ID=$BUILDKITE_JOB_ID
# The standard BUILDKITE_PULL_REQUEST environment variable is always "false" due
# to how solana-ci-gate is used to trigger PR builds rather than using the
# standard Buildkite PR trigger.
if [[ $CI_BRANCH =~ pull/* ]]; then
export CI_PULL_REQUEST=true
else
export CI_PULL_REQUEST=
fi
export CI_OS_NAME=linux
if [[ -n $BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG ]]; then
# The solana-secondary pipeline should use the slug of the pipeline that
# triggered it
export CI_REPO_SLUG=$BUILDKITE_ORGANIZATION_SLUG/$BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG
else
export CI_REPO_SLUG=$BUILDKITE_ORGANIZATION_SLUG/$BUILDKITE_PIPELINE_SLUG
fi
# TRIGGERED_BUILDKITE_TAG is a workaround to propagate BUILDKITE_TAG into
# the solana-secondary pipeline
if [[ -n $TRIGGERED_BUILDKITE_TAG ]]; then
export CI_TAG=$TRIGGERED_BUILDKITE_TAG
else
export CI_TAG=$BUILDKITE_TAG
fi
elif [[ -n $APPVEYOR ]]; then
export CI_BRANCH=$APPVEYOR_REPO_BRANCH
export CI_BUILD_ID=$APPVEYOR_BUILD_ID
export CI_COMMIT=$APPVEYOR_REPO_COMMIT
export CI_JOB_ID=$APPVEYOR_JOB_ID
if [[ -n $APPVEYOR_PULL_REQUEST_NUMBER ]]; then
export CI_PULL_REQUEST=true
else
export CI_PULL_REQUEST=
fi
if [[ $CI_LINUX = True ]]; then
export CI_OS_NAME=linux
elif [[ $CI_WINDOWS = True ]]; then
export CI_OS_NAME=windows
fi
export CI_REPO_SLUG=$APPVEYOR_REPO_NAME
export CI_TAG=$APPVEYOR_REPO_TAG_NAME
fi
else
export CI=
export CI_BRANCH=
export CI_BUILD_ID=
export CI_COMMIT=
export CI_JOB_ID=
export CI_OS_NAME=
export CI_PULL_REQUEST=
export CI_REPO_SLUG=
export CI_TAG=
fi
cat <<EOF
CI=$CI
CI_BRANCH=$CI_BRANCH
CI_BUILD_ID=$CI_BUILD_ID
CI_COMMIT=$CI_COMMIT
CI_JOB_ID=$CI_JOB_ID
CI_OS_NAME=$CI_OS_NAME
CI_PULL_REQUEST=$CI_PULL_REQUEST
CI_TAG=$CI_TAG
EOF

View File

@ -1,9 +0,0 @@
#!/usr/bin/env bash
set -e
#
# The standard BUILDKITE_PULL_REQUEST environment variable is always "false" due
# to how solana-ci-gate is used to trigger PR builds rather than using the
# standard Buildkite PR trigger.
#
[[ $BUILDKITE_BRANCH =~ pull/* ]]

View File

@ -294,7 +294,7 @@ flag_error() {
}
if ! $skipSetup; then
multinode-demo/setup.sh --hashes-per-tick auto
multinode-demo/setup.sh
else
verifyLedger
fi
@ -307,7 +307,7 @@ while [[ $iteration -le $iterations ]]; do
source multinode-demo/common.sh
set -x
client_keypair=/tmp/client-id.json-$$
$solana_keygen -o $client_keypair || exit $?
$solana_keygen new -f -o $client_keypair || exit $?
$solana_gossip spy --num-nodes-exactly $numNodes || exit $?
rm -rf $client_keypair
) || flag_error

View File

@ -23,11 +23,13 @@ declare print_free_tree=(
'metrics/src'
'netutil/src'
'runtime/src'
'sdk/bpf/rust/rust-utils'
'sdk/src'
'programs/vote_api/src'
'programs/vote_program/src'
'programs/bpf/rust'
'programs/stake_api/src'
'programs/stake_program/src'
'programs/vote_api/src'
'programs/vote_program/src'
)
if _ git --no-pager grep -n --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then

View File

@ -0,0 +1,65 @@
#!/usr/bin/env python
#
# This script figures the order in which workspace crates must be published to
# crates.io. Along the way it also ensures there are no circular dependencies
# that would cause a |cargo publish| to fail.
#
# On success an ordered list of Cargo.toml files is written to stdout
#
import os
import json
import subprocess
import sys;
def load_metadata():
return json.loads(subprocess.Popen(
'cargo metadata --no-deps --format-version=1',
shell=True, stdout=subprocess.PIPE).communicate()[0])
def get_packages():
metadata = load_metadata()
manifest_path = dict()
# Build dictionary of packages and their immediate solana-only dependencies
dependency_graph = dict()
for pkg in metadata['packages']:
manifest_path[pkg['name']] = pkg['manifest_path'];
dependency_graph[pkg['name']] = [x['name'] for x in pkg['dependencies'] if x['name'].startswith('solana')];
# Check for direct circular dependencies
circular_dependencies = set()
for package, dependencies in dependency_graph.items():
for dependency in dependencies:
if dependency in dependency_graph and package in dependency_graph[dependency]:
circular_dependencies.add(' <--> '.join(sorted([package, dependency])))
for dependency in circular_dependencies:
sys.stderr.write('Error: Circular dependency: {}\n'.format(dependency))
if len(circular_dependencies) != 0:
sys.exit(1)
# Order dependencies
sorted_dependency_graph = []
max_iterations = pow(len(dependency_graph),2)
while dependency_graph:
if max_iterations == 0:
# TODO: Be more helpful and find the actual cycle for the user
sys.exit('Error: Circular dependency suspected between these packages: {}\n'.format(' '.join(dependency_graph.keys())))
max_iterations -= 1
for package, dependencies in dependency_graph.items():
for dependency in dependencies:
if dependency in dependency_graph:
break
else:
del dependency_graph[package]
sorted_dependency_graph.append((package, manifest_path[package]))
return sorted_dependency_graph
for package, manifest in get_packages():
print os.path.relpath(manifest)

View File

@ -13,7 +13,7 @@ echo --- create book repo
git config user.email "maintainers@solana.com"
git config user.name "$(basename "$0")"
git add ./* ./.nojekyll
git commit -m "${BUILDKITE_COMMIT:-local}"
git commit -m "${CI_COMMIT:-local}"
)
eval "$(ci/channel-info.sh)"

View File

@ -3,43 +3,21 @@ set -e
cd "$(dirname "$0")/.."
source ci/semver_bash/semver.sh
# List of internal crates to publish
#
# IMPORTANT: the order of the CRATES *is* significant. Crates must be published
# before the crates that depend on them. Note that this information is already
# expressed in the various Cargo.toml files, and ideally would not be duplicated
# here. (TODO: figure the crate ordering dynamically)
#
CRATES=(
kvstore
logger
netutil
sdk
keygen
metrics
client
drone
programs/{budget_api,config_api,stake_api,storage_api,token_api,vote_api,exchange_api}
programs/{vote_program,budget_program,bpf_loader,config_program,exchange_program,failure_program}
programs/{noop_program,stake_program,storage_program,token_program}
runtime
vote-signer
core
validator
genesis
gossip
ledger-tool
wallet
install
)
# shellcheck disable=SC2086
is_crate_version_uploaded() {
name=$1
version=$2
curl https://crates.io/api/v1/crates/${name}/${version} | \
python3 -c "import sys,json; print('version' in json.load(sys.stdin));"
}
# Only package/publish if this is a tagged release
[[ -n $TRIGGERED_BUILDKITE_TAG ]] || {
echo TRIGGERED_BUILDKITE_TAG unset, skipped
[[ -n $CI_TAG ]] || {
echo CI_TAG unset, skipped
exit 0
}
semverParseInto "$TRIGGERED_BUILDKITE_TAG" MAJOR MINOR PATCH SPECIAL
semverParseInto "$CI_TAG" MAJOR MINOR PATCH SPECIAL
expectedCrateVersion="$MAJOR.$MINOR.$PATCH$SPECIAL"
[[ -n "$CRATES_IO_TOKEN" ]] || {
@ -49,25 +27,37 @@ expectedCrateVersion="$MAJOR.$MINOR.$PATCH$SPECIAL"
cargoCommand="cargo publish --token $CRATES_IO_TOKEN"
for crate in "${CRATES[@]}"; do
if [[ ! -r $crate/Cargo.toml ]]; then
echo "Error: $crate/Cargo.toml does not exist"
exit 1
fi
echo "-- $crate"
grep -q "^version = \"$expectedCrateVersion\"$" "$crate"/Cargo.toml || {
echo "Error: $crate/Cargo.toml version is not $expectedCrateVersion"
Cargo_tomls=$(ci/order-crates-for-publishing.py)
for Cargo_toml in $Cargo_tomls; do
echo "-- $Cargo_toml"
grep -q "^version = \"$expectedCrateVersion\"$" "$Cargo_toml" || {
echo "Error: $Cargo_toml version is not $expectedCrateVersion"
exit 1
}
(
set -x
crate=$(dirname "$Cargo_toml")
# TODO: the rocksdb package does not build with the stock rust docker image,
# so use the solana rust docker image until this is resolved upstream
source ci/rust-version.sh
ci/docker-run.sh "$rust_stable_docker_image" bash -exc "cd $crate; $cargoCommand"
#ci/docker-run.sh rust bash -exc "cd $crate; $cargoCommand"
)
) || true # <-- Don't fail. We want to be able to retry the job in cases when a publish fails halfway due to network/cloud issues
# shellcheck disable=SC2086
crate_name=$(grep -m 1 '^name = ' $Cargo_toml | cut -f 3 -d ' ' | tr -d \")
numRetries=30
for ((i = 1 ; i <= numRetries ; i++)); do
echo "Attempt ${i} of ${numRetries}"
# shellcheck disable=SC2086
if [[ $(is_crate_version_uploaded $crate_name $expectedCrateVersion) = True ]] ; then
echo "Found ${crate_name} version ${expectedCrateVersion} on crates.io"
break
fi
echo "Did not find ${crate_name} version ${expectedCrateVersion} on crates.io. Sleeping for 2 seconds."
sleep 2
done
done
exit 0

View File

@ -45,7 +45,9 @@ beta)
CHANNEL_BRANCH=$BETA_CHANNEL
;;
stable)
CHANNEL_BRANCH=$STABLE_CHANNEL
# Set to whatever branch 'testnet' is on.
# TODO: Revert to $STABLE_CHANNEL for TdS
CHANNEL_BRANCH=$BETA_CHANNEL
;;
*)
echo "Error: Invalid PUBLISH_CHANNEL=$PUBLISH_CHANNEL"
@ -53,7 +55,7 @@ stable)
;;
esac
if [[ $BUILDKITE_BRANCH != "$CHANNEL_BRANCH" ]]; then
if [[ $CI_BRANCH != "$CHANNEL_BRANCH" ]]; then
(
cat <<EOF
steps:

View File

@ -3,8 +3,20 @@ set -e
cd "$(dirname "$0")/.."
if [[ -n $APPVEYOR ]]; then
# Bootstrap rust build environment
source ci/env.sh
source ci/rust-version.sh
appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
./rustup-init -yv --default-toolchain $rust_stable --default-host x86_64-pc-windows-msvc
export PATH="$PATH:$USERPROFILE/.cargo/bin"
rustc -vV
cargo -vV
fi
DRYRUN=
if [[ -z $BUILDKITE_BRANCH ]]; then
if [[ -z $CI_BRANCH ]]; then
DRYRUN="echo"
CHANNEL=unknown
fi
@ -12,12 +24,9 @@ fi
eval "$(ci/channel-info.sh)"
TAG=
if [[ -n "$BUILDKITE_TAG" ]]; then
CHANNEL_OR_TAG=$BUILDKITE_TAG
TAG="$BUILDKITE_TAG"
elif [[ -n "$TRIGGERED_BUILDKITE_TAG" ]]; then
CHANNEL_OR_TAG=$TRIGGERED_BUILDKITE_TAG
TAG="$TRIGGERED_BUILDKITE_TAG"
if [[ -n "$CI_TAG" ]]; then
CHANNEL_OR_TAG=$CI_TAG
TAG="$CI_TAG"
else
CHANNEL_OR_TAG=$CHANNEL
fi
@ -27,12 +36,17 @@ if [[ -z $CHANNEL_OR_TAG ]]; then
exit 1
fi
case "$(uname)" in
Darwin)
PERF_LIBS=false
case "$CI_OS_NAME" in
osx)
TARGET=x86_64-apple-darwin
;;
Linux)
linux)
TARGET=x86_64-unknown-linux-gnu
PERF_LIBS=true
;;
windows)
TARGET=x86_64-pc-windows-msvc
;;
*)
TARGET=unknown-unknown-unknown
@ -56,18 +70,21 @@ echo --- Creating tarball
source ci/rust-version.sh stable
scripts/cargo-install-all.sh +"$rust_stable" solana-release
rm -rf target/perf-libs
./fetch-perf-libs.sh
mkdir solana-release/target
cp -a target/perf-libs solana-release/target/
if $PERF_LIBS; then
rm -rf target/perf-libs
./fetch-perf-libs.sh
mkdir solana-release/target
cp -a target/perf-libs solana-release/target/
# shellcheck source=/dev/null
source ./target/perf-libs/env.sh
(
cd validator
cargo +"$rust_stable" install --path . --features=cuda --root ../solana-release-cuda
)
cp solana-release-cuda/bin/solana-validator solana-release/bin/solana-validator-cuda
fi
# shellcheck source=/dev/null
source ./target/perf-libs/env.sh
(
cd validator
cargo +"$rust_stable" install --path . --features=cuda --root ../solana-release-cuda
)
cp solana-release-cuda/bin/solana-validator solana-release/bin/solana-validator-cuda
cp -a scripts multinode-demo solana-release/
# Add a wrapper script for validator.sh
@ -88,41 +105,64 @@ EOF
set -e
cd "$(dirname "$0")"/..
export USE_INSTALL=1
exec multinode-demo/clear-validator-config.sh "$@"
exec multinode-demo/clear-config.sh "$@"
EOF
chmod +x solana-release/bin/clear-config.sh
tar jvcf solana-release-$TARGET.tar.bz2 solana-release/
cp solana-release/bin/solana-install solana-install-$TARGET
cp solana-release/bin/solana-install-init solana-install-init-$TARGET
)
echo --- Saving build artifacts
source ci/upload-ci-artifact.sh
upload-ci-artifact solana-release-$TARGET.tar.bz2
if [[ -n $DO_NOT_PUBLISH_TAR ]]; then
echo Skipped due to DO_NOT_PUBLISH_TAR
exit 0
# Metrics tarball is platform agnostic, only publish it from Linux
MAYBE_METRICS_TARBALL=
if [[ "$CI_OS_NAME" = linux ]]; then
metrics/create-metrics-tarball.sh
MAYBE_METRICS_TARBALL=solana-metrics.tar.bz2
fi
for file in solana-release-$TARGET.tar.bz2 solana-install-$TARGET; do
echo --- AWS S3 Store: $file
(
set -x
$DRYRUN docker run \
--rm \
--env AWS_ACCESS_KEY_ID \
--env AWS_SECRET_ACCESS_KEY \
--volume "$PWD:/solana" \
eremite/aws-cli:2018.12.18 \
/usr/bin/s3cmd --acl-public put /solana/"$file" s3://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
source ci/upload-ci-artifact.sh
echo Published to:
$DRYRUN ci/format-url.sh http://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
)
for file in solana-release-$TARGET.tar.bz2 solana-install-init-"$TARGET"* $MAYBE_METRICS_TARBALL; do
upload-ci-artifact "$file"
if [[ -n $TAG ]]; then
ci/upload-github-release-asset.sh $file
if [[ -n $DO_NOT_PUBLISH_TAR ]]; then
echo "Skipped $file due to DO_NOT_PUBLISH_TAR"
continue
fi
if [[ -n $BUILDKITE ]]; then
echo --- AWS S3 Store: "$file"
(
set -x
$DRYRUN docker run \
--rm \
--env AWS_ACCESS_KEY_ID \
--env AWS_SECRET_ACCESS_KEY \
--volume "$PWD:/solana" \
eremite/aws-cli:2018.12.18 \
/usr/bin/s3cmd --acl-public put /solana/"$file" s3://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
echo Published to:
$DRYRUN ci/format-url.sh http://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
)
if [[ -n $TAG ]]; then
ci/upload-github-release-asset.sh "$file"
fi
elif [[ -n $TRAVIS ]]; then
# .travis.yml uploads everything in the travis-s3-upload/ directory to release.solana.com
mkdir -p travis-s3-upload/"$CHANNEL_OR_TAG"
cp -v "$file" travis-s3-upload/"$CHANNEL_OR_TAG"/
if [[ -n $TAG ]]; then
# .travis.yaml uploads everything in the travis-release-upload/ directory to
# the associated Github Release
mkdir -p travis-release-upload/
cp -v "$file" travis-release-upload/
fi
elif [[ -n $APPVEYOR ]]; then
# Add artifacts for .appveyor.yml to upload
appveyor PushArtifact "$file" -FileName "$CHANNEL_OR_TAG"/"$file"
fi
done

View File

@ -13,11 +13,14 @@
# $ source ci/rust-version.sh
#
export rust_stable=1.34.0
export rust_stable_docker_image=solanalabs/rust:1.34.0
stable_version=1.35.0
nightly_version=2019-06-20
export rust_nightly=nightly-2019-05-01
export rust_nightly_docker_image=solanalabs/rust-nightly:2019-05-01
export rust_stable="$stable_version"
export rust_stable_docker_image=solanalabs/rust:"$stable_version"
export rust_nightly=nightly-"$nightly_version"
export rust_nightly_docker_image=solanalabs/rust-nightly:"$nightly_version"
[[ -z $1 ]] || (

View File

@ -30,8 +30,8 @@ set -o pipefail
export RUST_BACKTRACE=1
UPLOAD_METRICS=""
TARGET_BRANCH=$BUILDKITE_BRANCH
if [[ -z $BUILDKITE_BRANCH ]] || ./ci/is-pr.sh; then
TARGET_BRANCH=$CI_BRANCH
if [[ -z $CI_BRANCH ]] || [[ -n $CI_PULL_REQUEST ]]; then
TARGET_BRANCH=$EDGE_CHANNEL
else
UPLOAD_METRICS="upload"
@ -40,6 +40,10 @@ fi
BENCH_FILE=bench_output.log
BENCH_ARTIFACT=current_bench_results.log
# Clear the C dependency files, if dependeny moves these files are not regenerated
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
# Ensure all dependencies are built
_ cargo +$rust_nightly build --all --release

View File

@ -5,15 +5,37 @@ cd "$(dirname "$0")/.."
source ci/_
source ci/rust-version.sh stable
source ci/rust-version.sh nightly
export RUST_BACKTRACE=1
export RUSTFLAGS="-D warnings"
do_bpf_check() {
_ cargo +"$rust_stable" fmt --all -- --check
_ cargo +"$rust_nightly" clippy --all -- --version
_ cargo +"$rust_nightly" clippy --all -- --deny=warnings
_ cargo +"$rust_stable" audit
}
(
(
cd sdk/bpf/rust/rust-utils
do_bpf_check
)
for project in programs/bpf/rust/*/ ; do
(
cd "$project"
do_bpf_check
)
done
)
_ cargo +"$rust_stable" fmt --all -- --check
_ cargo +"$rust_stable" clippy --all -- --version
_ cargo +"$rust_stable" clippy --all -- --deny=warnings
_ cargo +"$rust_stable" audit
_ ci/nits.sh
_ ci/order-crates-for-publishing.py
_ book/build.sh
echo --- ok

View File

@ -25,7 +25,7 @@ source scripts/ulimit-n.sh
scripts/coverage.sh
report=coverage-"${BUILDKITE_COMMIT:0:9}".tar.gz
report=coverage-"${CI_COMMIT:0:9}".tar.gz
mv target/cov/report.tar.gz "$report"
upload-ci-artifact "$report"
annotate --style success --context lcov-report \
@ -39,5 +39,5 @@ else
bash <(curl -s https://codecov.io/bash) -X gcov -f target/cov/lcov.info
annotate --style success --context codecov.io \
"CodeCov report: https://codecov.io/github/solana-labs/solana/commit/${BUILDKITE_COMMIT:0:9}"
"CodeCov report: https://codecov.io/github/solana-labs/solana/commit/${CI_COMMIT:0:9}"
fi

View File

@ -19,7 +19,14 @@ source scripts/ulimit-n.sh
# Clear cached json keypair files
rm -rf "$HOME/.config/solana"
# Run tbe appropriate test based on entrypoint
# Clear the C dependency files, if dependeny moves these files are not regenerated
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
# Clear the BPF sysroot files, they are not automatically rebuilt
rm -rf target/xargo # Issue #3105
# Run the appropriate test based on entrypoint
testName=$(basename "$0" .sh)
case $testName in
test-stable)
@ -35,8 +42,10 @@ test-stable-perf)
.rs$ \
Cargo.lock$ \
Cargo.toml$ \
ci/test-stable-perf.sh \
ci/test-stable.sh \
^ci/test-stable-perf.sh \
^ci/test-stable.sh \
^core/build.rs \
^fetch-perf-libs.sh \
^programs/ \
^sdk/ \
|| {
@ -52,10 +61,8 @@ test-stable-perf)
--no-default-features --features=bpf_c,bpf_rust
# Run root package tests with these features
ROOT_FEATURES=erasure,chacha
if [[ $(uname) = Darwin ]]; then
./build-perf-libs.sh
else
ROOT_FEATURES=
if [[ $(uname) = Linux ]]; then
# Enable persistence mode to keep the CUDA kernel driver loaded, avoiding a
# lengthy and unexpected delay the first time CUDA is involved when the driver
# is not yet loaded.
@ -65,7 +72,7 @@ test-stable-perf)
./fetch-perf-libs.sh
# shellcheck source=/dev/null
source ./target/perf-libs/env.sh
ROOT_FEATURES=$ROOT_FEATURES,cuda
ROOT_FEATURES=cuda
fi
# Run root package library tests

View File

@ -311,6 +311,9 @@ if ! $skipStart; then
if [[ -n $NO_LEDGER_VERIFY ]]; then
args+=(-o noLedgerVerify)
fi
if [[ -n $NO_INSTALL_CHECK ]]; then
args+=(-o noInstallCheck)
fi
if [[ -n $maybeHashesPerTick ]]; then
# shellcheck disable=SC2206 # Do not want to quote $maybeHashesPerTick
args+=($maybeHashesPerTick)
@ -324,10 +327,11 @@ if ! $skipStart; then
args+=(-F)
fi
# shellcheck disable=SC2154 # SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu comes from .buildkite/env/
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
args+=(-i update_manifest_keypair.json)
if $deployUpdateManifest; then
rm -f update_manifest_keypair.json
args+=(--deploy-update linux)
args+=(--deploy-update osx)
args+=(--deploy-update windows)
fi
# shellcheck disable=SC2086 # Don't want to double quote the $maybeXYZ variables

View File

@ -132,19 +132,16 @@ case $TESTNET in
testnet-edge|testnet-edge-perf)
CHANNEL_OR_TAG=edge
CHANNEL_BRANCH=$EDGE_CHANNEL
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
testnet-beta|testnet-beta-perf)
CHANNEL_OR_TAG=beta
CHANNEL_BRANCH=$BETA_CHANNEL
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
testnet)
CHANNEL_OR_TAG=$STABLE_CHANNEL_LATEST_TAG
CHANNEL_BRANCH=$STABLE_CHANNEL
: "${EC2_NODE_COUNT:=10}"
: "${GCE_NODE_COUNT:=}"
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
testnet-perf)
CHANNEL_OR_TAG=$STABLE_CHANNEL_LATEST_TAG
@ -155,7 +152,6 @@ testnet-demo)
CHANNEL_BRANCH=$BETA_CHANNEL
: "${GCE_NODE_COUNT:=150}"
: "${GCE_LOW_QUOTA_NODE_COUNT:=70}"
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
*)
echo "Error: Invalid TESTNET=$TESTNET"
@ -188,7 +184,7 @@ if [[ -n $TESTNET_TAG ]]; then
CHANNEL_OR_TAG=$TESTNET_TAG
else
if [[ $BUILDKITE_BRANCH != "$CHANNEL_BRANCH" ]]; then
if [[ $CI_BRANCH != "$CHANNEL_BRANCH" ]]; then
(
cat <<EOF
steps:
@ -216,6 +212,7 @@ sanity() {
testnet-edge)
(
set -x
NO_INSTALL_CHECK=1 \
NO_LEDGER_VERIFY=1 \
ci/testnet-sanity.sh edge-testnet-solana-com ec2 us-west-1a
)
@ -232,6 +229,7 @@ sanity() {
testnet-beta)
(
set -x
NO_INSTALL_CHECK=1 \
NO_LEDGER_VERIFY=1 \
ci/testnet-sanity.sh beta-testnet-solana-com ec2 us-west-1a
)
@ -327,8 +325,7 @@ deploy() {
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
${maybeDelete:+-D}
)
;;
testnet-edge-perf)
@ -342,8 +339,7 @@ deploy() {
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
${maybeDelete:+-D}
)
;;
testnet-beta)
@ -355,8 +351,7 @@ deploy() {
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
${maybeDelete:+-D}
)
;;
testnet-beta-perf)
@ -370,8 +365,7 @@ deploy() {
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
${maybeDelete:+-D}
)
;;
testnet)
@ -415,8 +409,7 @@ deploy() {
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
${maybeDelete:+-D}
)
;;
testnet-demo)
@ -436,8 +429,7 @@ deploy() {
${skipCreate:+-e} \
${maybeSkipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
${maybeDelete:+-D}
if [[ -n $GCE_LOW_QUOTA_NODE_COUNT ]]; then
# shellcheck disable=SC2068
@ -448,8 +440,7 @@ deploy() {
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
${maybeDelete:+-D}
fi
)
;;

View File

@ -64,6 +64,7 @@ for zone in "$@"; do
${NO_LEDGER_VERIFY:+-o noLedgerVerify} \
${NO_VALIDATOR_SANITY:+-o noValidatorSanity} \
${REJECT_EXTRA_NODES:+-o rejectExtraNodes} \
${NO_INSTALL_CHECK:+-o noInstallCheck} \
$zone || ok=false
net/net.sh logs

View File

@ -8,8 +8,6 @@
#
set -e
REPO_SLUG=solana-labs/solana
if [[ -z $1 ]]; then
echo No files specified
exit 1
@ -20,31 +18,30 @@ if [[ -z $GITHUB_TOKEN ]]; then
exit 1
fi
if [[ -n $BUILDKITE_TAG ]]; then
TAG=$BUILDKITE_TAG
elif [[ -n $TRIGGERED_BUILDKITE_TAG ]]; then
TAG=$TRIGGERED_BUILDKITE_TAG
if [[ -z $CI_TAG ]]; then
echo Error: CI_TAG not defined
exit 1
fi
if [[ -z $TAG ]]; then
echo Error: TAG not defined
if [[ -z $CI_REPO_SLUG ]]; then
echo Error: CI_REPO_SLUG not defined
exit 1
fi
releaseId=$( \
curl -s "https://api.github.com/repos/$REPO_SLUG/releases/tags/$TAG" \
curl -s "https://api.github.com/repos/$CI_REPO_SLUG/releases/tags/$CI_TAG" \
| grep -m 1 \"id\": \
| sed -ne 's/^[^0-9]*\([0-9]*\),$/\1/p' \
)
echo "Github release id for $TAG is $releaseId"
echo "Github release id for $CI_TAG is $releaseId"
for file in "$@"; do
echo "--- Uploading $file to tag $TAG of $REPO_SLUG"
echo "--- Uploading $file to tag $CI_TAG of $CI_REPO_SLUG"
curl \
--data-binary @"$file" \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/octet-stream" \
"https://uploads.github.com/repos/$REPO_SLUG/releases/$releaseId/assets?name=$(basename "$file")"
"https://uploads.github.com/repos/$CI_REPO_SLUG/releases/$releaseId/assets?name=$(basename "$file")"
echo
done

View File

@ -1,6 +1,6 @@
[package]
name = "solana-client"
version = "0.15.0"
version = "0.16.2"
description = "Solana Client"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -11,16 +11,18 @@ edition = "2018"
[dependencies]
bincode = "1.1.4"
bs58 = "0.2.0"
jsonrpc-core = "12.0.0"
log = "0.4.2"
jsonrpc-core = "10.1.0"
reqwest = "0.9.17"
serde = "1.0.89"
serde_derive = "1.0.91"
rand = "0.6.5"
rayon = "1.1.0"
reqwest = "0.9.18"
serde = "1.0.92"
serde_derive = "1.0.92"
serde_json = "1.0.39"
solana-netutil = { path = "../netutil", version = "0.15.0" }
solana-sdk = { path = "../sdk", version = "0.15.0" }
solana-netutil = { path = "../netutil", version = "0.16.2" }
solana-sdk = { path = "../sdk", version = "0.16.2" }
[dev-dependencies]
jsonrpc-core = "10.1.0"
jsonrpc-http-server = "10.1.0"
solana-logger = { path = "../logger", version = "0.15.0" }
jsonrpc-core = "12.0.0"
jsonrpc-http-server = "12.0.0"
solana-logger = { path = "../logger", version = "0.16.2" }

View File

@ -60,6 +60,7 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
serde_json::to_value(response).unwrap()
}
RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)),
RpcRequest::GetSlot => Value::Number(Number::from(0)),
RpcRequest::SendTransaction => Value::String(SIGNATURE.to_string()),
_ => Value::Null,
};

View File

@ -36,7 +36,18 @@ pub fn sample_txs<T>(
total_elapsed = start_time.elapsed();
let elapsed = now.elapsed();
now = Instant::now();
let mut txs = client.get_transaction_count().expect("transaction count");
let mut txs;
match client.get_transaction_count() {
Err(e) => {
// ThinClient with multiple options should pick a better one now.
info!("Couldn't get transaction count {:?}", e);
sleep(Duration::from_secs(sample_period));
continue;
}
Ok(tx_count) => {
txs = tx_count;
}
}
if txs < last_txs {
info!("Expected txs({}) >= last_txs({})", txs, last_txs);

View File

@ -75,6 +75,25 @@ impl RpcClient {
Ok(result)
}
pub fn get_slot(&self) -> io::Result<u64> {
let response = self
.client
.send(&RpcRequest::GetSlot, None, 0)
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetSlot request failure: {:?}", err),
)
})?;
serde_json::from_value(response).map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetSlot parse failure: {}", err),
)
})
}
pub fn send_and_confirm_transaction<T: KeypairUtil>(
&self,
transaction: &mut Transaction,

View File

@ -12,6 +12,7 @@ pub enum RpcRequest {
GetNumBlocksSinceSignatureConfirmation,
GetRecentBlockhash,
GetSignatureStatus,
GetSlot,
GetSlotLeader,
GetEpochVoteAccounts,
GetStorageBlockhash,
@ -39,6 +40,7 @@ impl RpcRequest {
}
RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
RpcRequest::GetSignatureStatus => "getSignatureStatus",
RpcRequest::GetSlot => "getSlot",
RpcRequest::GetSlotLeader => "getSlotLeader",
RpcRequest::GetEpochVoteAccounts => "getEpochVoteAccounts",
RpcRequest::GetStorageBlockhash => "getStorageBlockhash",
@ -104,6 +106,10 @@ mod tests {
let request = test_request.build_request_json(1, None);
assert_eq!(request["method"], "getRecentBlockhash");
let test_request = RpcRequest::GetSlot;
let request = test_request.build_request_json(1, None);
assert_eq!(request["method"], "getSlot");
let test_request = RpcRequest::GetTransactionCount;
let request = test_request.build_request_json(1, None);
assert_eq!(request["method"], "getTransactionCount");

View File

@ -6,6 +6,7 @@
use crate::rpc_client::RpcClient;
use bincode::{serialize_into, serialized_size};
use log::*;
use solana_sdk::account::Account;
use solana_sdk::client::{AsyncClient, Client, SyncClient};
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::hash::Hash;
@ -15,17 +16,100 @@ use solana_sdk::packet::PACKET_DATA_SIZE;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
use solana_sdk::system_instruction;
use solana_sdk::timing::duration_as_ms;
use solana_sdk::transaction::{self, Transaction};
use solana_sdk::transport::Result as TransportResult;
use std::io;
use std::net::{SocketAddr, UdpSocket};
use std::time::Duration;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::RwLock;
use std::time::{Duration, Instant};
struct ClientOptimizer {
cur_index: AtomicUsize,
experiment_index: AtomicUsize,
experiment_done: AtomicBool,
times: RwLock<Vec<u64>>,
num_clients: usize,
}
fn min_index(array: &[u64]) -> (u64, usize) {
let mut min_time = std::u64::MAX;
let mut min_index = 0;
for (i, time) in array.iter().enumerate() {
if *time < min_time {
min_time = *time;
min_index = i;
}
}
(min_time, min_index)
}
impl ClientOptimizer {
fn new(num_clients: usize) -> Self {
Self {
cur_index: AtomicUsize::new(0),
experiment_index: AtomicUsize::new(0),
experiment_done: AtomicBool::new(false),
times: RwLock::new(vec![std::u64::MAX; num_clients]),
num_clients,
}
}
fn experiment(&self) -> usize {
if self.experiment_index.load(Ordering::Relaxed) < self.num_clients {
let old = self.experiment_index.fetch_add(1, Ordering::Relaxed);
if old < self.num_clients {
old
} else {
self.best()
}
} else {
self.best()
}
}
fn report(&self, index: usize, time_ms: u64) {
if self.num_clients > 1
&& (!self.experiment_done.load(Ordering::Relaxed) || time_ms == std::u64::MAX)
{
trace!(
"report {} with {} exp: {}",
index,
time_ms,
self.experiment_index.load(Ordering::Relaxed)
);
self.times.write().unwrap()[index] = time_ms;
if index == (self.num_clients - 1) || time_ms == std::u64::MAX {
let times = self.times.read().unwrap();
let (min_time, min_index) = min_index(&times);
trace!(
"done experimenting min: {} time: {} times: {:?}",
min_index,
min_time,
times
);
// Only 1 thread should grab the num_clients-1 index, so this should be ok.
self.cur_index.store(min_index, Ordering::Relaxed);
self.experiment_done.store(true, Ordering::Relaxed);
}
}
}
fn best(&self) -> usize {
self.cur_index.load(Ordering::Relaxed)
}
}
/// An object for querying and sending transactions to the network.
pub struct ThinClient {
transactions_addr: SocketAddr,
transactions_socket: UdpSocket,
rpc_client: RpcClient,
transactions_addrs: Vec<SocketAddr>,
rpc_clients: Vec<RpcClient>,
optimizer: ClientOptimizer,
}
impl ThinClient {
@ -59,12 +143,39 @@ impl ThinClient {
rpc_client: RpcClient,
) -> Self {
Self {
rpc_client,
transactions_addr,
transactions_socket,
transactions_addrs: vec![transactions_addr],
rpc_clients: vec![rpc_client],
optimizer: ClientOptimizer::new(0),
}
}
pub fn new_from_addrs(
transactions_addrs: Vec<SocketAddr>,
transactions_socket: UdpSocket,
rpc_sockets: Vec<SocketAddr>,
) -> Self {
assert!(!transactions_addrs.is_empty());
assert!(!rpc_sockets.is_empty());
assert_eq!(rpc_sockets.len(), transactions_addrs.len());
let rpc_len = rpc_sockets.len();
let rpc_clients: Vec<_> = rpc_sockets.into_iter().map(RpcClient::new_socket).collect();
Self {
transactions_addrs,
transactions_socket,
rpc_clients,
optimizer: ClientOptimizer::new(rpc_len),
}
}
fn transactions_addr(&self) -> &SocketAddr {
&self.transactions_addrs[self.optimizer.best()]
}
fn rpc_client(&self) -> &RpcClient {
&self.rpc_clients[self.optimizer.best()]
}
/// Retry a sending a signed Transaction to the server for processing.
pub fn retry_transfer_until_confirmed(
&self,
@ -100,15 +211,19 @@ impl ThinClient {
serialize_into(&mut wr, &transaction)
.expect("serialize Transaction in pub fn transfer_signed");
self.transactions_socket
.send_to(&buf[..], &self.transactions_addr)?;
.send_to(&buf[..], &self.transactions_addr())?;
if self
.poll_for_signature_confirmation(&transaction.signatures[0], min_confirmed_blocks)
.is_ok()
{
return Ok(transaction.signatures[0]);
}
info!("{} tries failed transfer to {}", x, self.transactions_addr);
let (blockhash, _fee_calculator) = self.rpc_client.get_recent_blockhash()?;
info!(
"{} tries failed transfer to {}",
x,
self.transactions_addr()
);
let (blockhash, _fee_calculator) = self.rpc_client().get_recent_blockhash()?;
transaction.sign(keypairs, blockhash);
}
Err(io::Error::new(
@ -123,39 +238,40 @@ impl ThinClient {
polling_frequency: &Duration,
timeout: &Duration,
) -> io::Result<u64> {
self.rpc_client
self.rpc_client()
.poll_balance_with_timeout(pubkey, polling_frequency, timeout)
}
pub fn poll_get_balance(&self, pubkey: &Pubkey) -> io::Result<u64> {
self.rpc_client.poll_get_balance(pubkey)
self.rpc_client().poll_get_balance(pubkey)
}
pub fn wait_for_balance(&self, pubkey: &Pubkey, expected_balance: Option<u64>) -> Option<u64> {
self.rpc_client.wait_for_balance(pubkey, expected_balance)
self.rpc_client().wait_for_balance(pubkey, expected_balance)
}
/// Check a signature in the bank. This method blocks
/// until the server sends a response.
pub fn check_signature(&self, signature: &Signature) -> bool {
self.rpc_client.check_signature(signature)
self.rpc_client().check_signature(signature)
}
pub fn fullnode_exit(&self) -> io::Result<bool> {
self.rpc_client.fullnode_exit()
self.rpc_client().fullnode_exit()
}
pub fn get_num_blocks_since_signature_confirmation(
&mut self,
sig: &Signature,
) -> io::Result<usize> {
self.rpc_client
self.rpc_client()
.get_num_blocks_since_signature_confirmation(sig)
}
}
impl Client for ThinClient {
fn transactions_addr(&self) -> String {
self.transactions_addr.to_string()
self.transactions_addr().to_string()
}
}
@ -188,20 +304,40 @@ impl SyncClient for ThinClient {
}
fn get_account_data(&self, pubkey: &Pubkey) -> TransportResult<Option<Vec<u8>>> {
Ok(self.rpc_client.get_account_data(pubkey).ok())
Ok(self.rpc_client().get_account_data(pubkey).ok())
}
fn get_account(&self, pubkey: &Pubkey) -> TransportResult<Option<Account>> {
Ok(self.rpc_client().get_account(pubkey).ok())
}
fn get_balance(&self, pubkey: &Pubkey) -> TransportResult<u64> {
let balance = self.rpc_client.get_balance(pubkey)?;
let balance = self.rpc_client().get_balance(pubkey)?;
Ok(balance)
}
fn get_recent_blockhash(&self) -> TransportResult<(Hash, FeeCalculator)> {
let index = self.optimizer.experiment();
let now = Instant::now();
let recent_blockhash = self.rpc_clients[index].get_recent_blockhash();
match recent_blockhash {
Ok(recent_blockhash) => {
self.optimizer.report(index, duration_as_ms(&now.elapsed()));
Ok(recent_blockhash)
}
Err(e) => {
self.optimizer.report(index, std::u64::MAX);
Err(e)?
}
}
}
fn get_signature_status(
&self,
signature: &Signature,
) -> TransportResult<Option<transaction::Result<()>>> {
let status = self
.rpc_client
.rpc_client()
.get_signature_status(&signature.to_string())
.map_err(|err| {
io::Error::new(
@ -212,13 +348,29 @@ impl SyncClient for ThinClient {
Ok(status)
}
fn get_recent_blockhash(&self) -> TransportResult<(Hash, FeeCalculator)> {
Ok(self.rpc_client.get_recent_blockhash()?)
fn get_slot(&self) -> TransportResult<u64> {
let slot = self.rpc_client().get_slot().map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("send_transaction failed with error {:?}", err),
)
})?;
Ok(slot)
}
fn get_transaction_count(&self) -> TransportResult<u64> {
let transaction_count = self.rpc_client.get_transaction_count()?;
Ok(transaction_count)
let index = self.optimizer.experiment();
let now = Instant::now();
match self.rpc_client().get_transaction_count() {
Ok(transaction_count) => {
self.optimizer.report(index, duration_as_ms(&now.elapsed()));
Ok(transaction_count)
}
Err(e) => {
self.optimizer.report(index, std::u64::MAX);
Err(e)?
}
}
}
/// Poll the server until the signature has been confirmed by at least `min_confirmed_blocks`
@ -228,16 +380,17 @@ impl SyncClient for ThinClient {
min_confirmed_blocks: usize,
) -> TransportResult<()> {
Ok(self
.rpc_client
.rpc_client()
.poll_for_signature_confirmation(signature, min_confirmed_blocks)?)
}
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)?)
let new_blockhash = self.rpc_client().get_new_blockhash(blockhash)?;
Ok(new_blockhash)
}
}
@ -249,7 +402,7 @@ impl AsyncClient for ThinClient {
.expect("serialize Transaction in pub fn transfer_signed");
assert!(buf.len() < PACKET_DATA_SIZE);
self.transactions_socket
.send_to(&buf[..], &self.transactions_addr)?;
.send_to(&buf[..], &self.transactions_addr())?;
Ok(transaction.signatures[0])
}
fn async_send_message(
@ -296,3 +449,28 @@ pub fn create_client_with_timeout(
let (_, transactions_socket) = solana_netutil::bind_in_range(range).unwrap();
ThinClient::new_socket_with_timeout(rpc, tpu, transactions_socket, timeout)
}
#[cfg(test)]
mod tests {
use super::*;
use rayon::prelude::*;
#[test]
fn test_client_optimizer() {
solana_logger::setup();
const NUM_CLIENTS: usize = 5;
let optimizer = ClientOptimizer::new(NUM_CLIENTS);
(0..NUM_CLIENTS).into_par_iter().for_each(|_| {
let index = optimizer.experiment();
optimizer.report(index, (NUM_CLIENTS - index) as u64);
});
let index = optimizer.experiment();
optimizer.report(index, 50);
assert_eq!(optimizer.best(), NUM_CLIENTS - 1);
optimizer.report(optimizer.best(), std::u64::MAX);
assert_eq!(optimizer.best(), NUM_CLIENTS - 2);
}
}

View File

@ -1,7 +1,7 @@
[package]
name = "solana"
description = "Blockchain, Rebuilt for Scale"
version = "0.15.0"
version = "0.16.2"
documentation = "https://docs.rs/solana"
homepage = "https://solana.com/"
readme = "../README.md"
@ -14,69 +14,73 @@ edition = "2018"
codecov = { repository = "solana-labs/solana", branch = "master", service = "github" }
[features]
chacha = []
cuda = []
erasure = []
kvstore = ["solana-kvstore"]
[dependencies]
bincode = "1.1.4"
bs58 = "0.2.0"
byteorder = "1.3.1"
byteorder = "1.3.2"
chrono = { version = "0.4.0", features = ["serde"] }
crc = { version = "1.8.1", optional = true }
core_affinity = "0.5.9"
crc = { version = "1.8.1", optional = true }
hashbrown = "0.2.0"
indexmap = "1.0"
itertools = "0.8.0"
jsonrpc-core = "11.0.0"
jsonrpc-derive = "11.0.0"
jsonrpc-http-server = "11.0.0"
jsonrpc-pubsub = "11.0.0"
jsonrpc-ws-server = "11.0.0"
libc = "0.2.55"
jsonrpc-core = "12.0.0"
jsonrpc-derive = "12.0.0"
jsonrpc-http-server = "12.0.0"
jsonrpc-pubsub = "12.0.0"
jsonrpc-ws-server = "12.0.0"
libc = "0.2.58"
log = "0.4.2"
memmap = { version = "0.7.0", optional = true }
nix = "0.14.0"
nix = "0.14.1"
num-traits = "0.2"
rand = "0.6.5"
rand_chacha = "0.1.1"
rayon = "1.0.0"
reed-solomon-erasure = "3.1.1"
reqwest = "0.9.17"
rayon = "1.1.0"
reqwest = "0.9.18"
rocksdb = "0.11.0"
serde = "1.0.89"
serde_derive = "1.0.91"
serde = "1.0.92"
serde_derive = "1.0.92"
serde_json = "1.0.39"
solana-budget-api = { path = "../programs/budget_api", version = "0.15.0" }
solana-budget-program = { path = "../programs/budget_program", version = "0.15.0" }
solana-client = { path = "../client", version = "0.15.0" }
solana-drone = { path = "../drone", version = "0.15.0" }
solana-budget-api = { path = "../programs/budget_api", version = "0.16.2" }
solana-budget-program = { path = "../programs/budget_program", version = "0.16.2" }
solana-chacha-sys = { path = "../chacha-sys", version = "0.16.2" }
solana-client = { path = "../client", version = "0.16.2" }
solana-config-program = { path = "../programs/config_program", version = "0.16.2" }
solana-drone = { path = "../drone", version = "0.16.2" }
solana-ed25519-dalek = "0.2.0"
solana-kvstore = { path = "../kvstore", version = "0.15.0" , optional = true }
solana-logger = { path = "../logger", version = "0.15.0" }
solana-metrics = { path = "../metrics", version = "0.15.0" }
solana-netutil = { path = "../netutil", version = "0.15.0" }
solana-runtime = { path = "../runtime", version = "0.15.0" }
solana-sdk = { path = "../sdk", version = "0.15.0" }
solana-stake-api = { path = "../programs/stake_api", version = "0.15.0" }
solana-stake-program = { path = "../programs/stake_program", version = "0.15.0" }
solana-storage-api = { path = "../programs/storage_api", version = "0.15.0" }
solana-storage-program = { path = "../programs/storage_program", version = "0.15.0" }
solana-vote-api = { path = "../programs/vote_api", version = "0.15.0" }
solana-vote-program = { path = "../programs/vote_program", version = "0.15.0" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.15.0" }
solana-config-program = { path = "../programs/config_program", version = "0.15.0" }
solana-vote-signer = { path = "../vote-signer", version = "0.15.0" }
sys-info = "0.5.6"
solana-exchange-program = { path = "../programs/exchange_program", version = "0.16.2" }
solana-kvstore = { path = "../kvstore", version = "0.16.2", optional = true }
solana-logger = { path = "../logger", version = "0.16.2" }
solana-metrics = { path = "../metrics", version = "0.16.2" }
solana-netutil = { path = "../netutil", version = "0.16.2" }
solana-runtime = { path = "../runtime", version = "0.16.2" }
solana-sdk = { path = "../sdk", version = "0.16.2" }
solana-stake-api = { path = "../programs/stake_api", version = "0.16.2" }
solana-stake-program = { path = "../programs/stake_program", version = "0.16.2" }
solana-storage-api = { path = "../programs/storage_api", version = "0.16.2" }
solana-storage-program = { path = "../programs/storage_program", version = "0.16.2" }
solana-vote-api = { path = "../programs/vote_api", version = "0.16.2" }
solana-vote-program = { path = "../programs/vote_program", version = "0.16.2" }
solana-vote-signer = { path = "../vote-signer", version = "0.16.2" }
sys-info = "0.5.7"
tokio = "0.1"
tokio-codec = "0.1"
untrusted = "0.6.2"
# reed-solomon-erasure's simd_c feature fails to build for x86_64-pc-windows-msvc, use pure-rust
[target.'cfg(windows)'.dependencies]
reed-solomon-erasure = { version = "3.1.1", features = ["pure-rust"] }
[target.'cfg(not(windows))'.dependencies]
reed-solomon-erasure = "3.1.1"
[dev-dependencies]
hex-literal = "0.2.0"
matches = "0.1.6"
[[bench]]
name = "banking_stage"
@ -99,5 +103,5 @@ name = "sigverify_stage"
name = "poh"
[[bench]]
required-features = ["chacha"]
name = "chacha"
required-features = ["chacha"]

View File

@ -22,7 +22,7 @@ use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Signature;
use solana_sdk::system_transaction;
use solana_sdk::timing::{
duration_as_ms, timestamp, DEFAULT_TICKS_PER_SLOT, MAX_RECENT_BLOCKHASHES,
duration_as_us, timestamp, DEFAULT_TICKS_PER_SLOT, MAX_RECENT_BLOCKHASHES,
};
use std::iter;
use std::sync::atomic::Ordering;
@ -33,18 +33,20 @@ use test::Bencher;
fn check_txs(receiver: &Arc<Receiver<WorkingBankEntries>>, ref_tx_count: usize) {
let mut total = 0;
let now = Instant::now();
loop {
let entries = receiver.recv_timeout(Duration::new(1, 0));
if let Ok((_, entries)) = entries {
for (entry, _) in &entries {
total += entry.transactions.len();
}
} else {
break;
}
if total >= ref_tx_count {
break;
}
if now.elapsed().as_secs() > 60 {
break;
}
}
assert_eq!(total, ref_tx_count);
}
@ -89,7 +91,8 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
solana_logger::setup();
let num_threads = BankingStage::num_threads() as usize;
// a multiple of packet chunk 2X duplicates to avoid races
let txes = 192 * num_threads * 2;
const CHUNKS: usize = 32;
let txes = 192 * num_threads * CHUNKS;
let mint_total = 1_000_000_000_000;
let GenesisBlockInfo {
mut genesis_block,
@ -167,7 +170,7 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
);
poh_recorder.lock().unwrap().set_bank(&bank);
let half_len = verified.len() / 2;
let chunk_len = verified.len() / CHUNKS;
let mut start = 0;
// This is so that the signal_receiver does not go out of scope after the closure.
@ -177,18 +180,33 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
let signal_receiver2 = signal_receiver.clone();
bencher.iter(move || {
let now = Instant::now();
for v in verified[start..start + half_len].chunks(verified.len() / num_threads) {
trace!("sending... {}..{} {}", start, start + half_len, timestamp());
let mut sent = 0;
for v in verified[start..start + chunk_len].chunks(verified.len() / num_threads) {
trace!(
"sending... {}..{} {}",
start,
start + chunk_len,
timestamp()
);
for xv in v {
sent += xv.0.packets.len();
}
verified_sender.send(v.to_vec()).unwrap();
}
check_txs(&signal_receiver2, txes / 2);
trace!(
"time: {} checked: {}",
duration_as_ms(&now.elapsed()),
txes / 2
);
check_txs(&signal_receiver2, txes / CHUNKS);
// This signature clear may not actually clear the signatures
// in this chunk, but since we rotate between 32 chunks then
// we should clear them by the time we come around again to re-use that chunk.
bank.clear_signatures();
start += half_len;
trace!(
"time: {} checked: {} sent: {}",
duration_as_us(&now.elapsed()),
txes / CHUNKS,
sent,
);
start += chunk_len;
start %= verified.len();
});
drop(vote_sender);

View File

@ -0,0 +1,48 @@
#![feature(test)]
extern crate test;
use solana::entry::EntrySlice;
use solana::entry::{next_entry_mut, Entry};
use solana_sdk::hash::{hash, Hash};
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;
use test::Bencher;
const NUM_HASHES: u64 = 400;
const NUM_ENTRIES: usize = 800;
#[bench]
fn bench_poh_verify_ticks(bencher: &mut Bencher) {
let zero = Hash::default();
let mut cur_hash = hash(&zero.as_ref());
let start = *&cur_hash;
let mut ticks: Vec<Entry> = Vec::with_capacity(NUM_ENTRIES);
for _ in 0..NUM_ENTRIES {
ticks.push(next_entry_mut(&mut cur_hash, NUM_HASHES, vec![]));
}
bencher.iter(|| {
ticks.verify(&start);
})
}
#[bench]
fn bench_poh_verify_transaction_entries(bencher: &mut Bencher) {
let zero = Hash::default();
let mut cur_hash = hash(&zero.as_ref());
let start = *&cur_hash;
let keypair1 = Keypair::new();
let pubkey1 = keypair1.pubkey();
let mut ticks: Vec<Entry> = Vec::with_capacity(NUM_ENTRIES);
for _ in 0..NUM_ENTRIES {
let tx = system_transaction::create_user_account(&keypair1, &pubkey1, 42, cur_hash);
ticks.push(next_entry_mut(&mut cur_hash, NUM_HASHES, vec![tx]));
}
bencher.iter(|| {
ticks.verify(&start);
})
}

View File

@ -5,44 +5,41 @@ use std::path::Path;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
let perf_libs_dir = {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let mut path = Path::new(&manifest_dir);
path = path.parent().unwrap();
path.join(Path::new("target/perf-libs"))
};
let perf_libs_dir = perf_libs_dir.to_str().unwrap();
if env::var("CARGO_FEATURE_CUDA").is_ok() {
println!("cargo:rustc-cfg=cuda");
// Ensure `perf_libs_dir` exists. It's been observed that
// a cargo:rerun-if-changed= directive with a non-existent
// directory triggers a rebuild on every |cargo build| invocation
fs::create_dir_all(&perf_libs_dir).unwrap_or_else(|err| {
if err.kind() != std::io::ErrorKind::AlreadyExists {
panic!("Unable to create {}: {:?}", perf_libs_dir, err);
}
});
let perf_libs_dir = {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let mut path = Path::new(&manifest_dir);
path = path.parent().unwrap();
let mut path = path.join(Path::new("target/perf-libs"));
path.push(
env::var("SOLANA_PERF_LIBS_CUDA")
.unwrap_or_else(|err| panic!("SOLANA_PERF_LIBS_CUDA not defined: {}", err)),
);
path
};
let perf_libs_dir = perf_libs_dir.to_str().unwrap();
let chacha = !env::var("CARGO_FEATURE_CHACHA").is_err();
let cuda = !env::var("CARGO_FEATURE_CUDA").is_err();
if chacha || cuda {
// Ensure `perf_libs_dir` exists. It's been observed that
// a cargo:rerun-if-changed= directive with a non-existent
// directory triggers a rebuild on every |cargo build| invocation
fs::create_dir_all(&perf_libs_dir).unwrap_or_else(|err| {
if err.kind() != std::io::ErrorKind::AlreadyExists {
panic!("Unable to create {}: {:?}", perf_libs_dir, err);
}
});
println!("cargo:rerun-if-changed={}", perf_libs_dir);
println!("cargo:rustc-link-search=native={}", perf_libs_dir);
}
if chacha {
println!("cargo:rerun-if-changed={}/libcpu-crypt.a", perf_libs_dir);
}
if cuda {
let cuda_home = match env::var("CUDA_HOME") {
Ok(cuda_home) => cuda_home,
Err(_) => String::from("/usr/local/cuda"),
};
println!("cargo:rerun-if-changed={}/libcuda-crypt.a", perf_libs_dir);
println!("cargo:rustc-link-lib=static=cuda-crypt");
println!("cargo:rustc-link-search=native={}/lib64", cuda_home);
println!("cargo:rustc-link-lib=dylib=cudart");
println!("cargo:rustc-link-lib=dylib=cuda");
println!("cargo:rustc-link-lib=dylib=cudadevrt");
if cfg!(windows) {
println!("cargo:rerun-if-changed={}/libcuda-crypt.dll", perf_libs_dir);
} else if cfg!(target_os = "macos") {
println!(
"cargo:rerun-if-changed={}/libcuda-crypt.dylib",
perf_libs_dir
);
} else {
println!("cargo:rerun-if-changed={}/libcuda-crypt.so", perf_libs_dir);
}
}
}

View File

@ -1,10 +1,16 @@
//! The `bank_forks` module implments BankForks a DAG of checkpointed Banks
use hashbrown::{HashMap, HashSet};
use bincode::{deserialize_from, serialize_into};
use solana_metrics::inc_new_counter_info;
use solana_runtime::bank::Bank;
use solana_runtime::bank::{Bank, BankRc, StatusCacheRc};
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::timing;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::fs::File;
use std::io::{BufReader, BufWriter, Error, ErrorKind};
use std::ops::Index;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Instant;
@ -12,6 +18,8 @@ pub struct BankForks {
banks: HashMap<u64, Arc<Bank>>,
working_bank: Arc<Bank>,
root: u64,
slots: HashSet<u64>,
snapshot_path: Option<String>,
}
impl Index<u64> for BankForks {
@ -30,6 +38,8 @@ impl BankForks {
banks,
working_bank,
root: 0,
slots: HashSet::new(),
snapshot_path: None,
}
}
@ -45,6 +55,7 @@ impl BankForks {
}
/// Create a map of bank slot id to the set of all of its descendants
#[allow(clippy::or_fun_call)]
pub fn descendants(&self) -> HashMap<u64, HashSet<u64>> {
let mut descendants = HashMap::new();
for bank in self.banks.values() {
@ -91,6 +102,8 @@ impl BankForks {
root,
banks,
working_bank,
slots: HashSet::new(),
snapshot_path: None,
}
}
@ -138,9 +151,211 @@ impl BankForks {
}
fn prune_non_root(&mut self, root: u64) {
let slots: HashSet<u64> = self
.banks
.iter()
.filter(|(_, b)| b.is_frozen())
.map(|(k, _)| *k)
.collect();
let descendants = self.descendants();
self.banks
.retain(|slot, _| descendants[&root].contains(slot))
.retain(|slot, _| descendants[&root].contains(slot));
if self.snapshot_path.is_some() {
let diff: HashSet<_> = slots.symmetric_difference(&self.slots).collect();
trace!("prune non root {} - {:?}", root, diff);
for slot in diff.iter() {
if **slot > root {
let _ = self.add_snapshot(**slot, root);
} else {
BankForks::remove_snapshot(**slot, &self.snapshot_path);
}
}
}
self.slots = slots.clone();
}
fn get_io_error(error: &str) -> Error {
warn!("BankForks error: {:?}", error);
Error::new(ErrorKind::Other, error)
}
fn get_snapshot_path(path: &Option<String>) -> PathBuf {
Path::new(&path.clone().unwrap()).to_path_buf()
}
pub fn add_snapshot(&self, slot: u64, root: u64) -> Result<(), Error> {
let path = BankForks::get_snapshot_path(&self.snapshot_path);
fs::create_dir_all(path.clone())?;
let bank_file = format!("{}", slot);
let bank_file_path = path.join(bank_file);
trace!("path: {:?}", bank_file_path);
let file = File::create(bank_file_path)?;
let mut stream = BufWriter::new(file);
let bank_slot = self.get(slot);
if bank_slot.is_none() {
return Err(BankForks::get_io_error("bank_forks get error"));
}
let bank = bank_slot.unwrap().clone();
serialize_into(&mut stream, &*bank)
.map_err(|_| BankForks::get_io_error("serialize bank error"))?;
let mut parent_slot: u64 = 0;
if let Some(parent_bank) = bank.parent() {
parent_slot = parent_bank.slot();
}
serialize_into(&mut stream, &parent_slot)
.map_err(|_| BankForks::get_io_error("serialize bank parent error"))?;
serialize_into(&mut stream, &root)
.map_err(|_| BankForks::get_io_error("serialize root error"))?;
serialize_into(&mut stream, &bank.src)
.map_err(|_| BankForks::get_io_error("serialize bank status cache error"))?;
serialize_into(&mut stream, &bank.rc)
.map_err(|_| BankForks::get_io_error("serialize bank accounts error"))?;
Ok(())
}
pub fn remove_snapshot(slot: u64, path: &Option<String>) {
let path = BankForks::get_snapshot_path(path);
let bank_file = format!("{}", slot);
let bank_file_path = path.join(bank_file);
let _ = fs::remove_file(bank_file_path);
}
pub fn set_snapshot_config(&mut self, path: Option<String>) {
self.snapshot_path = path;
}
fn load_snapshots(
names: &[u64],
bank0: &mut Bank,
bank_maps: &mut Vec<(u64, u64, Bank)>,
status_cache_rc: &StatusCacheRc,
snapshot_path: &Option<String>,
) -> Option<u64> {
let path = BankForks::get_snapshot_path(snapshot_path);
let mut bank_root: Option<u64> = None;
for bank_slot in names.iter().rev() {
let bank_path = format!("{}", bank_slot);
let bank_file_path = path.join(bank_path.clone());
info!("Load from {:?}", bank_file_path);
let file = File::open(bank_file_path);
if file.is_err() {
warn!("Snapshot file open failed for {}", bank_slot);
continue;
}
let file = file.unwrap();
let mut stream = BufReader::new(file);
let bank: Result<Bank, std::io::Error> = deserialize_from(&mut stream)
.map_err(|_| BankForks::get_io_error("deserialize bank error"));
let slot: Result<u64, std::io::Error> = deserialize_from(&mut stream)
.map_err(|_| BankForks::get_io_error("deserialize bank parent error"));
let parent_slot = if slot.is_ok() { slot.unwrap() } else { 0 };
let root: Result<u64, std::io::Error> = deserialize_from(&mut stream)
.map_err(|_| BankForks::get_io_error("deserialize root error"));
let status_cache: Result<StatusCacheRc, std::io::Error> = deserialize_from(&mut stream)
.map_err(|_| BankForks::get_io_error("deserialize bank status cache error"));
if bank_root.is_none() && bank0.rc.update_from_stream(&mut stream).is_ok() {
bank_root = Some(root.unwrap());
}
if bank_root.is_some() {
match bank {
Ok(v) => {
if status_cache.is_ok() {
status_cache_rc.append(&status_cache.unwrap());
}
bank_maps.push((*bank_slot, parent_slot, v));
}
Err(_) => warn!("Load snapshot failed for {}", bank_slot),
}
} else {
BankForks::remove_snapshot(*bank_slot, snapshot_path);
warn!("Load snapshot rc failed for {}", bank_slot);
}
}
bank_root
}
fn setup_banks(
bank_maps: &mut Vec<(u64, u64, Bank)>,
bank_rc: &BankRc,
status_cache_rc: &StatusCacheRc,
) -> (HashMap<u64, Arc<Bank>>, HashSet<u64>, u64) {
let mut banks = HashMap::new();
let mut slots = HashSet::new();
let (last_slot, last_parent_slot, mut last_bank) = bank_maps.remove(0);
last_bank.set_bank_rc(&bank_rc, &status_cache_rc);
while let Some((slot, parent_slot, mut bank)) = bank_maps.pop() {
bank.set_bank_rc(&bank_rc, &status_cache_rc);
if parent_slot != 0 {
if let Some(parent) = banks.get(&parent_slot) {
bank.set_parent(parent);
}
}
if slot > 0 {
banks.insert(slot, Arc::new(bank));
slots.insert(slot);
}
}
if last_parent_slot != 0 {
if let Some(parent) = banks.get(&last_parent_slot) {
last_bank.set_parent(parent);
}
}
banks.insert(last_slot, Arc::new(last_bank));
slots.insert(last_slot);
(banks, slots, last_slot)
}
pub fn load_from_snapshot(
genesis_block: &GenesisBlock,
account_paths: Option<String>,
snapshot_path: &Option<String>,
) -> Result<Self, Error> {
let path = BankForks::get_snapshot_path(snapshot_path);
let paths = fs::read_dir(path)?;
let mut names = paths
.filter_map(|entry| {
entry.ok().and_then(|e| {
e.path()
.file_name()
.and_then(|n| n.to_str().map(|s| s.parse::<u64>().unwrap()))
})
})
.collect::<Vec<u64>>();
names.sort();
let mut bank_maps = vec![];
let status_cache_rc = StatusCacheRc::default();
let id = (names[names.len() - 1] + 1) as usize;
let mut bank0 =
Bank::create_with_genesis(&genesis_block, account_paths.clone(), &status_cache_rc, id);
bank0.freeze();
let bank_root = BankForks::load_snapshots(
&names,
&mut bank0,
&mut bank_maps,
&status_cache_rc,
snapshot_path,
);
if bank_maps.is_empty() || bank_root.is_none() {
BankForks::remove_snapshot(0, snapshot_path);
return Err(Error::new(ErrorKind::Other, "no snapshots loaded"));
}
let root = bank_root.unwrap();
let (banks, slots, last_slot) =
BankForks::setup_banks(&mut bank_maps, &bank0.rc, &status_cache_rc);
let working_bank = banks[&last_slot].clone();
Ok(BankForks {
banks,
working_bank,
root,
slots,
snapshot_path: snapshot_path.clone(),
})
}
}
@ -150,6 +365,10 @@ mod tests {
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;
use std::env;
use std::fs::remove_dir_all;
#[test]
fn test_bank_forks() {
@ -174,8 +393,8 @@ mod tests {
let bank = Bank::new_from_parent(&bank0, &Pubkey::default(), 2);
bank_forks.insert(bank);
let descendants = bank_forks.descendants();
let children: Vec<u64> = descendants[&0].iter().cloned().collect();
assert_eq!(children, vec![1, 2]);
let children: HashSet<u64> = [1u64, 2u64].to_vec().into_iter().collect();
assert_eq!(children, *descendants.get(&0).unwrap());
assert!(descendants[&1].is_empty());
assert!(descendants[&2].is_empty());
}
@ -219,4 +438,112 @@ mod tests {
assert_eq!(bank_forks.active_banks(), vec![1]);
}
struct TempPaths {
pub paths: String,
}
#[macro_export]
macro_rules! tmp_bank_accounts_name {
() => {
&format!("{}-{}", file!(), line!())
};
}
#[macro_export]
macro_rules! get_tmp_bank_accounts_path {
() => {
get_tmp_bank_accounts_path(tmp_bank_accounts_name!())
};
}
impl Drop for TempPaths {
fn drop(&mut self) {
let paths: Vec<String> = self.paths.split(',').map(|s| s.to_string()).collect();
paths.iter().for_each(|p| {
let _ignored = remove_dir_all(p);
});
}
}
fn get_paths_vec(paths: &str) -> Vec<String> {
paths.split(',').map(|s| s.to_string()).collect()
}
fn get_tmp_snapshots_path() -> TempPaths {
let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string());
let path = format!("{}/snapshots", out_dir);
TempPaths {
paths: path.to_string(),
}
}
fn get_tmp_bank_accounts_path(paths: &str) -> TempPaths {
let vpaths = get_paths_vec(paths);
let out_dir = env::var("OUT_DIR").unwrap_or_else(|_| "target".to_string());
let vpaths: Vec<_> = vpaths
.iter()
.map(|path| format!("{}/{}", out_dir, path))
.collect();
TempPaths {
paths: vpaths.join(","),
}
}
fn restore_from_snapshot(
genesis_block: &GenesisBlock,
bank_forks: BankForks,
account_paths: Option<String>,
last_slot: u64,
) {
let new =
BankForks::load_from_snapshot(&genesis_block, account_paths, &bank_forks.snapshot_path)
.unwrap();
for (slot, _) in new.banks.iter() {
if *slot > 0 {
let bank = bank_forks.banks.get(slot).unwrap().clone();
let new_bank = new.banks.get(slot).unwrap();
bank.compare_bank(&new_bank);
}
}
assert_eq!(new.working_bank().slot(), last_slot);
for (slot, _) in new.banks.iter() {
BankForks::remove_snapshot(*slot, &bank_forks.snapshot_path);
}
}
#[test]
fn test_bank_forks_snapshot_n() {
solana_logger::setup();
let path = get_tmp_bank_accounts_path!();
let spath = get_tmp_snapshots_path();
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(10_000);
for index in 0..10 {
let bank0 = Bank::new_with_paths(&genesis_block, Some(path.paths.clone()));
bank0.freeze();
let slot = bank0.slot();
let mut bank_forks = BankForks::new(0, bank0);
bank_forks.set_snapshot_config(Some(spath.paths.clone()));
bank_forks.add_snapshot(slot, 0).unwrap();
for forks in 0..index {
let bank = Bank::new_from_parent(&bank_forks[forks], &Pubkey::default(), forks + 1);
let key1 = Keypair::new().pubkey();
let tx = system_transaction::create_user_account(
&mint_keypair,
&key1,
1,
genesis_block.hash(),
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
bank.freeze();
let slot = bank.slot();
bank_forks.insert(bank);
bank_forks.add_snapshot(slot, 0).unwrap();
}
restore_from_snapshot(&genesis_block, bank_forks, Some(path.paths.clone()), index);
}
}
}

View File

@ -67,8 +67,7 @@ impl BankingStage {
poh_recorder,
verified_receiver,
verified_vote_receiver,
2, // 1 for voting, 1 for banking.
// More than 2 threads is slower in testnet testing.
4,
)
}
@ -86,7 +85,7 @@ impl BankingStage {
// This thread talks to poh_service and broadcasts the entries once they have been recorded.
// Once an entry has been recorded, its blockhash is registered with the bank.
let exit = Arc::new(AtomicBool::new(false));
let my_pubkey = cluster_info.read().unwrap().id();
// Many banks that process transactions in parallel.
let bank_thread_hdls: Vec<JoinHandle<()>> = (0..num_threads)
.map(|i| {
@ -105,6 +104,7 @@ impl BankingStage {
.name("solana-banking-stage-tx".to_string())
.spawn(move || {
Self::process_loop(
my_pubkey,
&verified_receiver,
&poh_recorder,
&cluster_info,
@ -242,14 +242,13 @@ impl BankingStage {
}
fn process_buffered_packets(
my_pubkey: &Pubkey,
socket: &std::net::UdpSocket,
poh_recorder: &Arc<Mutex<PohRecorder>>,
cluster_info: &Arc<RwLock<ClusterInfo>>,
buffered_packets: &mut Vec<PacketsAndOffsets>,
enable_forwarding: bool,
) -> Result<()> {
let rcluster_info = cluster_info.read().unwrap();
let (decision, next_leader) = {
let poh = poh_recorder.lock().unwrap();
let next_leader = poh.next_slot_leader();
@ -258,7 +257,7 @@ impl BankingStage {
next_leader,
poh.bank().is_some(),
poh.would_be_leader(DEFAULT_TICKS_PER_SLOT * 2),
&rcluster_info.id(),
my_pubkey,
),
next_leader,
)
@ -266,28 +265,31 @@ impl BankingStage {
match decision {
BufferedPacketsDecision::Consume => {
let mut unprocessed = Self::consume_buffered_packets(
&rcluster_info.id(),
poh_recorder,
buffered_packets,
)?;
let mut unprocessed =
Self::consume_buffered_packets(my_pubkey, poh_recorder, buffered_packets)?;
buffered_packets.append(&mut unprocessed);
Ok(())
}
BufferedPacketsDecision::Forward => {
if enable_forwarding {
next_leader.map_or(Ok(()), |leader_pubkey| {
rcluster_info
.lookup(&leader_pubkey)
.map_or(Ok(()), |leader| {
let _ = Self::forward_buffered_packets(
&socket,
&leader.tpu_via_blobs,
&buffered_packets,
);
buffered_packets.clear();
Ok(())
})
let leader_addr = {
cluster_info
.read()
.unwrap()
.lookup(&leader_pubkey)
.map(|leader| leader.tpu_via_blobs)
};
leader_addr.map_or(Ok(()), |leader_addr| {
let _ = Self::forward_buffered_packets(
&socket,
&leader_addr,
&buffered_packets,
);
buffered_packets.clear();
Ok(())
})
})
} else {
buffered_packets.clear();
@ -299,6 +301,7 @@ impl BankingStage {
}
pub fn process_loop(
my_pubkey: Pubkey,
verified_receiver: &Arc<Mutex<Receiver<VerifiedPackets>>>,
poh_recorder: &Arc<Mutex<PohRecorder>>,
cluster_info: &Arc<RwLock<ClusterInfo>>,
@ -311,6 +314,7 @@ impl BankingStage {
loop {
if !buffered_packets.is_empty() {
Self::process_buffered_packets(
&my_pubkey,
&socket,
poh_recorder,
cluster_info,
@ -331,11 +335,11 @@ impl BankingStage {
};
match Self::process_packets(
&my_pubkey,
&verified_receiver,
&poh_recorder,
recv_start,
recv_timeout,
cluster_info,
id,
) {
Err(Error::RecvTimeoutError(RecvTimeoutError::Timeout)) => (),
@ -370,26 +374,23 @@ impl BankingStage {
.collect()
}
fn record_transactions<'a, 'b>(
bank: &'a Bank,
txs: &'b [Transaction],
fn record_transactions(
bank_slot: u64,
txs: &[Transaction],
results: &[transaction::Result<()>],
poh: &Arc<Mutex<PohRecorder>>,
recordable_txs: &'b mut Vec<&'b Transaction>,
) -> Result<LockedAccountsResults<'a, 'b, &'b Transaction>> {
) -> Result<()> {
let processed_transactions: Vec<_> = results
.iter()
.zip(txs.iter())
.filter_map(|(r, x)| {
if Bank::can_commit(r) {
recordable_txs.push(x);
Some(x.clone())
} else {
None
}
})
.collect();
let record_locks = bank.lock_record_accounts(recordable_txs);
debug!("processed: {} ", processed_transactions.len());
// unlock all the accounts with errors which are filtered by the above `filter_map`
if !processed_transactions.is_empty() {
@ -401,16 +402,16 @@ impl BankingStage {
// record and unlock will unlock all the successful transactions
poh.lock()
.unwrap()
.record(bank.slot(), hash, processed_transactions)?;
.record(bank_slot, hash, processed_transactions)?;
}
Ok(record_locks)
Ok(())
}
fn process_and_record_transactions_locked(
bank: &Bank,
txs: &[Transaction],
poh: &Arc<Mutex<PohRecorder>>,
lock_results: &LockedAccountsResults<Transaction>,
lock_results: &LockedAccountsResults,
) -> Result<()> {
let now = Instant::now();
// Use a shorter maximum age when adding transactions into the pipeline. This will reduce
@ -423,12 +424,10 @@ impl BankingStage {
let freeze_lock = bank.freeze_lock();
let mut recordable_txs = vec![];
let (record_time, record_locks) = {
let record_time = {
let now = Instant::now();
let record_locks =
Self::record_transactions(bank, txs, &results, poh, &mut recordable_txs)?;
(now.elapsed(), record_locks)
Self::record_transactions(bank.slot(), txs, &results, poh)?;
now.elapsed()
};
let commit_time = {
@ -437,7 +436,6 @@ impl BankingStage {
now.elapsed()
};
drop(record_locks);
drop(freeze_lock);
debug!(
@ -701,11 +699,11 @@ impl BankingStage {
/// Process the incoming packets
pub fn process_packets(
my_pubkey: &Pubkey,
verified_receiver: &Arc<Mutex<Receiver<VerifiedPackets>>>,
poh: &Arc<Mutex<PohRecorder>>,
recv_start: &mut Instant,
recv_timeout: Duration,
cluster_info: &Arc<RwLock<ClusterInfo>>,
id: u32,
) -> Result<UnprocessedPackets> {
let mms = verified_receiver
@ -747,7 +745,6 @@ impl BankingStage {
if processed < verified_txs_len {
let next_leader = poh.lock().unwrap().next_slot_leader();
let my_pubkey = cluster_info.read().unwrap().id();
// Walk thru rest of the transactions and filter out the invalid (e.g. too old) ones
while let Some((msgs, vers)) = mms_iter.next() {
let packet_indexes = Self::generate_packet_indexes(vers);
@ -1183,14 +1180,8 @@ mod tests {
];
let mut results = vec![Ok(()), Ok(())];
BankingStage::record_transactions(
&bank,
&transactions,
&results,
&poh_recorder,
&mut vec![],
)
.unwrap();
BankingStage::record_transactions(bank.slot(), &transactions, &results, &poh_recorder)
.unwrap();
let (_, entries) = entry_receiver.recv().unwrap();
assert_eq!(entries[0].0.transactions.len(), transactions.len());
@ -1199,27 +1190,15 @@ mod tests {
1,
InstructionError::new_result_with_negative_lamports(),
));
BankingStage::record_transactions(
&bank,
&transactions,
&results,
&poh_recorder,
&mut vec![],
)
.unwrap();
BankingStage::record_transactions(bank.slot(), &transactions, &results, &poh_recorder)
.unwrap();
let (_, entries) = entry_receiver.recv().unwrap();
assert_eq!(entries[0].0.transactions.len(), transactions.len());
// Other TransactionErrors should not be recorded
results[0] = Err(TransactionError::AccountNotFound);
BankingStage::record_transactions(
&bank,
&transactions,
&results,
&poh_recorder,
&mut vec![],
)
.unwrap();
BankingStage::record_transactions(bank.slot(), &transactions, &results, &poh_recorder)
.unwrap();
let (_, entries) = entry_receiver.recv().unwrap();
assert_eq!(entries[0].0.transactions.len(), transactions.len() - 1);
}

View File

@ -10,10 +10,6 @@ use serde_json::json;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use std::cell::RefCell;
use std::io::prelude::*;
use std::net::Shutdown;
use std::os::unix::net::UnixStream;
use std::path::Path;
pub trait EntryWriter: std::fmt::Debug {
fn write(&self, payload: String) -> Result<()>;
@ -48,16 +44,29 @@ pub struct EntrySocket {
socket: String,
}
const MESSAGE_TERMINATOR: &str = "\n";
impl EntryWriter for EntrySocket {
#[cfg(not(windows))]
fn write(&self, payload: String) -> Result<()> {
use std::io::prelude::*;
use std::net::Shutdown;
use std::os::unix::net::UnixStream;
use std::path::Path;
const MESSAGE_TERMINATOR: &str = "\n";
let mut socket = UnixStream::connect(Path::new(&self.socket))?;
socket.write_all(payload.as_bytes())?;
socket.write_all(MESSAGE_TERMINATOR.as_bytes())?;
socket.shutdown(Shutdown::Write)?;
Ok(())
}
#[cfg(windows)]
fn write(&self, _payload: String) -> Result<()> {
Err(crate::result::Error::from(std::io::Error::new(
std::io::ErrorKind::Other,
"EntryWriter::write() not implemented for windows",
)))
}
}
pub trait BlockstreamEvents {

View File

@ -11,7 +11,7 @@ use solana_kvstore as kvstore;
use bincode::deserialize;
use hashbrown::HashMap;
use std::collections::HashMap;
#[cfg(not(feature = "kvstore"))]
use rocksdb;
@ -84,6 +84,7 @@ pub struct Blocktree {
db: Arc<Database>,
meta_cf: LedgerColumn<cf::SlotMeta>,
data_cf: LedgerColumn<cf::Data>,
dead_slots_cf: LedgerColumn<cf::DeadSlots>,
erasure_cf: LedgerColumn<cf::Coding>,
erasure_meta_cf: LedgerColumn<cf::ErasureMeta>,
orphans_cf: LedgerColumn<cf::Orphans>,
@ -97,6 +98,8 @@ pub struct Blocktree {
pub const META_CF: &str = "meta";
// Column family for the data in a leader slot
pub const DATA_CF: &str = "data";
// Column family for slots that have been marked as dead
pub const DEAD_SLOTS_CF: &str = "dead_slots";
// Column family for erasure data
pub const ERASURE_CF: &str = "erasure";
pub const ERASURE_META_CF: &str = "erasure_meta";
@ -124,6 +127,9 @@ impl Blocktree {
// Create the data column family
let data_cf = db.column();
// Create the dead slots column family
let dead_slots_cf = db.column();
// Create the erasure column family
let erasure_cf = db.column();
@ -143,6 +149,7 @@ impl Blocktree {
db,
meta_cf,
data_cf,
dead_slots_cf,
erasure_cf,
erasure_meta_cf,
orphans_cf,
@ -177,6 +184,15 @@ impl Blocktree {
self.meta_cf.get(slot)
}
pub fn is_full(&self, slot: u64) -> bool {
if let Ok(meta) = self.meta_cf.get(slot) {
if let Some(meta) = meta {
return meta.is_full();
}
}
false
}
pub fn erasure_meta(&self, slot: u64, set_index: u64) -> Result<Option<ErasureMeta>> {
self.erasure_meta_cf.get((slot, set_index))
}
@ -799,7 +815,17 @@ impl Blocktree {
let result: HashMap<u64, Vec<u64>> = slots
.iter()
.zip(slot_metas)
.filter_map(|(height, meta)| meta.map(|meta| (*height, meta.next_slots)))
.filter_map(|(height, meta)| {
meta.map(|meta| {
let valid_next_slots: Vec<u64> = meta
.next_slots
.iter()
.cloned()
.filter(|s| !self.is_dead(*s))
.collect();
(*height, valid_next_slots)
})
})
.collect();
Ok(result)
@ -818,18 +844,12 @@ impl Blocktree {
}
}
pub fn set_root(&self, new_root: u64, prev_root: u64) -> Result<()> {
let mut current_slot = new_root;
pub fn set_roots(&self, rooted_slots: &[u64]) -> Result<()> {
unsafe {
let mut batch_processor = self.db.batch_processor();
let mut write_batch = batch_processor.batch()?;
if new_root == 0 {
write_batch.put::<cf::Root>(0, &true)?;
} else {
while current_slot != prev_root {
write_batch.put::<cf::Root>(current_slot, &true)?;
current_slot = self.meta(current_slot).unwrap().unwrap().parent_slot;
}
for slot in rooted_slots {
write_batch.put::<cf::Root>(*slot, &true)?;
}
batch_processor.write(write_batch)?;
@ -837,6 +857,22 @@ impl Blocktree {
Ok(())
}
pub fn is_dead(&self, slot: u64) -> bool {
if let Some(true) = self
.db
.get::<cf::DeadSlots>(slot)
.expect("fetch from DeadSlots column family failed")
{
true
} else {
false
}
}
pub fn set_dead_slot(&self, slot: u64) -> Result<()> {
self.dead_slots_cf.put(slot, &true)
}
pub fn get_orphans(&self, max: Option<usize>) -> Vec<u64> {
let mut results = vec![];
@ -3128,30 +3164,12 @@ pub mod tests {
}
#[test]
fn test_set_root() {
fn test_set_roots() {
let blocktree_path = get_tmp_ledger_path!();
let blocktree = Blocktree::open(&blocktree_path).unwrap();
blocktree.set_root(0, 0).unwrap();
let chained_slots = vec![0, 2, 4, 7, 12, 15];
// Make a chain of slots
let all_blobs = make_chaining_slot_entries(&chained_slots, 10);
// Insert the chain of slots into the ledger
for (slot_blobs, _) in all_blobs {
blocktree.insert_data_blobs(&slot_blobs[..]).unwrap();
}
blocktree.set_root(4, 0).unwrap();
for i in &chained_slots[0..3] {
assert!(blocktree.is_root(*i));
}
for i in &chained_slots[3..] {
assert!(!blocktree.is_root(*i));
}
blocktree.set_root(15, 4).unwrap();
blocktree.set_roots(&chained_slots).unwrap();
for i in chained_slots {
assert!(blocktree.is_root(i));

View File

@ -28,6 +28,10 @@ pub mod columns {
/// Data Column
pub struct Data;
#[derive(Debug)]
/// Data Column
pub struct DeadSlots;
#[derive(Debug)]
/// The erasure meta column
pub struct ErasureMeta;

View File

@ -100,6 +100,25 @@ impl Column<Kvs> for cf::Data {
}
}
impl Column<Kvs> for cf::DeadSlots {
const NAME: &'static str = super::DEAD_SLOTS;
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::Orphans {
const NAME: &'static str = super::ORPHANS_CF;
type Index = u64;

View File

@ -30,7 +30,7 @@ impl Backend for Rocks {
type Error = rocksdb::Error;
fn open(path: &Path) -> Result<Rocks> {
use crate::blocktree::db::columns::{Coding, Data, ErasureMeta, Orphans, Root, SlotMeta};
use crate::blocktree::db::columns::{Coding, Data, DeadSlots, ErasureMeta, Orphans, Root, SlotMeta};
fs::create_dir_all(&path)?;
@ -40,6 +40,7 @@ impl Backend for Rocks {
// Column family names
let meta_cf_descriptor = ColumnFamilyDescriptor::new(SlotMeta::NAME, get_cf_options());
let data_cf_descriptor = ColumnFamilyDescriptor::new(Data::NAME, get_cf_options());
let dead_slots_cf_descriptor = ColumnFamilyDescriptor::new(DeadSlots::NAME, get_cf_options());
let erasure_cf_descriptor = ColumnFamilyDescriptor::new(Coding::NAME, get_cf_options());
let erasure_meta_cf_descriptor =
ColumnFamilyDescriptor::new(ErasureMeta::NAME, get_cf_options());
@ -49,6 +50,7 @@ impl Backend for Rocks {
let cfs = vec![
meta_cf_descriptor,
data_cf_descriptor,
dead_slots_cf_descriptor,
erasure_cf_descriptor,
erasure_meta_cf_descriptor,
orphans_cf_descriptor,
@ -62,11 +64,12 @@ impl Backend for Rocks {
}
fn columns(&self) -> Vec<&'static str> {
use crate::blocktree::db::columns::{Coding, Data, ErasureMeta, Orphans, Root, SlotMeta};
use crate::blocktree::db::columns::{Coding, Data, DeadSlots, ErasureMeta, Orphans, Root, SlotMeta};
vec![
Coding::NAME,
ErasureMeta::NAME,
DeadSlots::NAME,
Data::NAME,
Orphans::NAME,
Root::NAME,
@ -161,6 +164,25 @@ impl Column<Rocks> for cf::Data {
}
}
impl Column<Rocks> for cf::DeadSlots {
const NAME: &'static str = super::DEAD_SLOTS_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::DeadSlots {
type Type = bool;
}
impl Column<Rocks> for cf::Orphans {
const NAME: &'static str = super::ORPHANS_CF;
type Index = u64;

View File

@ -51,7 +51,7 @@ mod tests {
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();
blocktree.set_roots(&[0]).unwrap();
let ticks_per_slot = 5;
/*
Build a blocktree in the ledger with the following fork structure:
@ -98,7 +98,7 @@ mod tests {
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, 4, fork_point, fork_hash);
// Set a root
blocktree.set_root(3, 0).unwrap();
blocktree.set_roots(&[1, 2, 3]).unwrap();
// Trying to get an iterator on a different fork will error
assert!(RootedSlotIterator::new(4, &blocktree).is_err());

View File

@ -3,18 +3,26 @@ use crate::blocktree::Blocktree;
use crate::entry::{Entry, EntrySlice};
use crate::leader_schedule_cache::LeaderScheduleCache;
use rayon::prelude::*;
use solana_metrics::{datapoint, inc_new_counter_debug};
use rayon::ThreadPool;
use solana_metrics::{datapoint, datapoint_error, inc_new_counter_debug};
use solana_runtime::bank::Bank;
use solana_runtime::locked_accounts_results::LockedAccountsResults;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::timing::duration_as_ms;
use solana_sdk::timing::MAX_RECENT_BLOCKHASHES;
use solana_sdk::transaction::Result;
use solana_sdk::transaction::Transaction;
use std::result;
use std::sync::Arc;
use std::time::{Duration, Instant};
pub const NUM_THREADS: u32 = 10;
use std::cell::RefCell;
thread_local!(static PAR_THREAD_POOL: RefCell<ThreadPool> = RefCell::new(rayon::ThreadPoolBuilder::new()
.num_threads(sys_info::cpu_num().unwrap_or(NUM_THREADS) as usize)
.build()
.unwrap()));
fn first_err(results: &[Result<()>]) -> Result<()> {
for r in results {
if r.is_err() {
@ -24,37 +32,38 @@ fn first_err(results: &[Result<()>]) -> Result<()> {
Ok(())
}
fn par_execute_entries(
bank: &Bank,
entries: &[(&Entry, LockedAccountsResults<Transaction>)],
) -> Result<()> {
fn par_execute_entries(bank: &Bank, entries: &[(&Entry, LockedAccountsResults)]) -> Result<()> {
inc_new_counter_debug!("bank-par_execute_entries-count", entries.len());
let results: Vec<Result<()>> = entries
.into_par_iter()
.map(|(e, locked_accounts)| {
let results = bank.load_execute_and_commit_transactions(
&e.transactions,
locked_accounts,
MAX_RECENT_BLOCKHASHES,
);
let mut first_err = None;
for (r, tx) in results.iter().zip(e.transactions.iter()) {
if let Err(ref e) = r {
if first_err.is_none() {
first_err = Some(r.clone());
let results: Vec<Result<()>> = PAR_THREAD_POOL.with(|thread_pool| {
thread_pool.borrow().install(|| {
entries
.into_par_iter()
.map(|(e, locked_accounts)| {
let results = bank.load_execute_and_commit_transactions(
&e.transactions,
locked_accounts,
MAX_RECENT_BLOCKHASHES,
);
let mut first_err = None;
for (r, tx) in results.iter().zip(e.transactions.iter()) {
if let Err(ref e) = r {
if first_err.is_none() {
first_err = Some(r.clone());
}
if !Bank::can_commit(&r) {
warn!("Unexpected validator error: {:?}, tx: {:?}", e, tx);
datapoint_error!(
"validator_process_entry_error",
("error", format!("error: {:?}, tx: {:?}", e, tx), String)
);
}
}
}
if !Bank::can_commit(&r) {
warn!("Unexpected validator error: {:?}, tx: {:?}", e, tx);
datapoint!(
"validator_process_entry_error",
("error", format!("error: {:?}, tx: {:?}", e, tx), String)
);
}
}
}
first_err.unwrap_or(Ok(()))
first_err.unwrap_or(Ok(()))
})
.collect()
})
.collect();
});
first_err(&results)
}
@ -155,7 +164,7 @@ pub fn process_blocktree(
vec![(slot, meta, bank, entry_height, last_entry_hash)]
};
blocktree.set_root(0, 0).expect("Couldn't set first root");
blocktree.set_roots(&[0]).expect("Couldn't set first root");
let leader_schedule_cache = LeaderScheduleCache::new(*pending_slots[0].2.epoch_schedule(), 0);
@ -420,7 +429,7 @@ pub mod tests {
info!("last_fork1_entry.hash: {:?}", last_fork1_entry_hash);
info!("last_fork2_entry.hash: {:?}", last_fork2_entry_hash);
blocktree.set_root(4, 0).unwrap();
blocktree.set_roots(&[4, 1, 0]).unwrap();
let (bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None).unwrap();
@ -494,8 +503,7 @@ pub mod tests {
info!("last_fork1_entry.hash: {:?}", last_fork1_entry_hash);
info!("last_fork2_entry.hash: {:?}", last_fork2_entry_hash);
blocktree.set_root(0, 0).unwrap();
blocktree.set_root(1, 0).unwrap();
blocktree.set_roots(&[0, 1]).unwrap();
let (bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None).unwrap();
@ -571,10 +579,11 @@ pub mod tests {
}
// Set a root on the last slot of the last confirmed epoch
blocktree.set_root(last_slot, 0).unwrap();
let rooted_slots: Vec<_> = (0..=last_slot).collect();
blocktree.set_roots(&rooted_slots).unwrap();
// Set a root on the next slot of the confrimed epoch
blocktree.set_root(last_slot + 1, last_slot).unwrap();
blocktree.set_roots(&[last_slot + 1]).unwrap();
// Check that we can properly restart the ledger / leader scheduler doesn't fail
let (bank_forks, bank_forks_info, _) =

View File

@ -1,183 +1,87 @@
//! A stage to broadcast data from a leader node to validators
//!
use self::fail_entry_verification_broadcast_run::FailEntryVerificationBroadcastRun;
use self::standard_broadcast_run::StandardBroadcastRun;
use crate::blocktree::Blocktree;
use crate::cluster_info::{ClusterInfo, ClusterInfoError, DATA_PLANE_FANOUT};
use crate::entry::EntrySlice;
use crate::cluster_info::{ClusterInfo, ClusterInfoError};
use crate::erasure::CodingGenerator;
use crate::packet::index_blobs_with_genesis;
use crate::poh_recorder::WorkingBankEntries;
use crate::result::{Error, Result};
use crate::service::Service;
use crate::staking_utils;
use rayon::prelude::*;
use rayon::ThreadPool;
use solana_metrics::{
datapoint, inc_new_counter_debug, inc_new_counter_error, inc_new_counter_info,
inc_new_counter_warn,
};
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::timing::duration_as_ms;
use std::net::UdpSocket;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Receiver, RecvTimeoutError};
use std::sync::{Arc, RwLock};
use std::thread::{self, Builder, JoinHandle};
use std::time::{Duration, Instant};
use std::time::Instant;
mod broadcast_utils;
mod fail_entry_verification_broadcast_run;
mod standard_broadcast_run;
pub const NUM_THREADS: u32 = 10;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum BroadcastStageReturnType {
ChannelDisconnected,
}
#[derive(Default)]
struct BroadcastStats {
num_entries: Vec<usize>,
run_elapsed: Vec<u64>,
to_blobs_elapsed: Vec<u64>,
#[derive(PartialEq, Clone, Debug)]
pub enum BroadcastStageType {
Standard,
FailEntryVerification,
}
struct Broadcast {
id: Pubkey,
coding_generator: CodingGenerator,
stats: BroadcastStats,
impl BroadcastStageType {
pub fn new_broadcast_stage(
&self,
sock: UdpSocket,
cluster_info: Arc<RwLock<ClusterInfo>>,
receiver: Receiver<WorkingBankEntries>,
exit_sender: &Arc<AtomicBool>,
blocktree: &Arc<Blocktree>,
) -> BroadcastStage {
match self {
BroadcastStageType::Standard => BroadcastStage::new(
sock,
cluster_info,
receiver,
exit_sender,
blocktree,
StandardBroadcastRun::new(),
),
BroadcastStageType::FailEntryVerification => BroadcastStage::new(
sock,
cluster_info,
receiver,
exit_sender,
blocktree,
FailEntryVerificationBroadcastRun::new(),
),
}
}
}
impl Broadcast {
trait BroadcastRun {
fn run(
&mut self,
broadcast: &mut Broadcast,
cluster_info: &Arc<RwLock<ClusterInfo>>,
receiver: &Receiver<WorkingBankEntries>,
sock: &UdpSocket,
blocktree: &Arc<Blocktree>,
genesis_blockhash: &Hash,
) -> Result<()> {
let timer = Duration::new(1, 0);
let (mut bank, entries) = receiver.recv_timeout(timer)?;
let mut max_tick_height = bank.max_tick_height();
) -> Result<()>;
}
let run_start = Instant::now();
let mut num_entries = entries.len();
let mut ventries = Vec::new();
let mut last_tick = entries.last().map(|v| v.1).unwrap_or(0);
ventries.push(entries);
assert!(last_tick <= max_tick_height);
if last_tick != max_tick_height {
while let Ok((same_bank, entries)) = receiver.try_recv() {
// If the bank changed, that implies the previous slot was interrupted and we do not have to
// broadcast its entries.
if same_bank.slot() != bank.slot() {
num_entries = 0;
ventries.clear();
bank = same_bank.clone();
max_tick_height = bank.max_tick_height();
}
num_entries += entries.len();
last_tick = entries.last().map(|v| v.1).unwrap_or(0);
ventries.push(entries);
assert!(last_tick <= max_tick_height,);
if last_tick == max_tick_height {
break;
}
}
}
let bank_epoch = bank.get_stakers_epoch(bank.slot());
let mut broadcast_table = cluster_info
.read()
.unwrap()
.sorted_tvu_peers(staking_utils::staked_nodes_at_epoch(&bank, bank_epoch).as_ref());
inc_new_counter_warn!("broadcast_service-num_peers", broadcast_table.len() + 1);
// Layer 1, leader nodes are limited to the fanout size.
broadcast_table.truncate(DATA_PLANE_FANOUT);
inc_new_counter_info!("broadcast_service-entries_received", num_entries);
let to_blobs_start = Instant::now();
let blobs: Vec<_> = ventries
.into_par_iter()
.map(|p| {
let entries: Vec<_> = p.into_iter().map(|e| e.0).collect();
entries.to_shared_blobs()
})
.flatten()
.collect();
let blob_index = blocktree
.meta(bank.slot())
.expect("Database error")
.map(|meta| meta.consumed)
.unwrap_or(0);
index_blobs_with_genesis(
&blobs,
&self.id,
genesis_blockhash,
blob_index,
bank.slot(),
bank.parent().map_or(0, |parent| parent.slot()),
);
let contains_last_tick = last_tick == max_tick_height;
if contains_last_tick {
blobs.last().unwrap().write().unwrap().set_is_last_in_slot();
}
blocktree.write_shared_blobs(&blobs)?;
let coding = self.coding_generator.next(&blobs);
let to_blobs_elapsed = duration_as_ms(&to_blobs_start.elapsed());
let broadcast_start = Instant::now();
// Send out data
ClusterInfo::broadcast(&self.id, contains_last_tick, &broadcast_table, sock, &blobs)?;
inc_new_counter_debug!("streamer-broadcast-sent", blobs.len());
// send out erasures
ClusterInfo::broadcast(&self.id, false, &broadcast_table, sock, &coding)?;
self.update_broadcast_stats(
duration_as_ms(&broadcast_start.elapsed()),
duration_as_ms(&run_start.elapsed()),
num_entries,
to_blobs_elapsed,
blob_index,
);
Ok(())
}
fn update_broadcast_stats(
&mut self,
broadcast_elapsed: u64,
run_elapsed: u64,
num_entries: usize,
to_blobs_elapsed: u64,
blob_index: u64,
) {
inc_new_counter_info!("broadcast_service-time_ms", broadcast_elapsed as usize);
self.stats.num_entries.push(num_entries);
self.stats.to_blobs_elapsed.push(to_blobs_elapsed);
self.stats.run_elapsed.push(run_elapsed);
if self.stats.num_entries.len() >= 16 {
info!(
"broadcast: entries: {:?} blob times ms: {:?} broadcast times ms: {:?}",
self.stats.num_entries, self.stats.to_blobs_elapsed, self.stats.run_elapsed
);
self.stats.num_entries.clear();
self.stats.to_blobs_elapsed.clear();
self.stats.run_elapsed.clear();
}
datapoint!("broadcast-service", ("transmit-index", blob_index, i64));
}
struct Broadcast {
coding_generator: CodingGenerator,
thread_pool: ThreadPool,
}
// Implement a destructor for the BroadcastStage thread to signal it exited
@ -209,20 +113,21 @@ impl BroadcastStage {
cluster_info: &Arc<RwLock<ClusterInfo>>,
receiver: &Receiver<WorkingBankEntries>,
blocktree: &Arc<Blocktree>,
genesis_blockhash: &Hash,
mut broadcast_stage_run: impl BroadcastRun,
) -> BroadcastStageReturnType {
let me = cluster_info.read().unwrap().my_data().clone();
let coding_generator = CodingGenerator::default();
let mut broadcast = Broadcast {
id: me.id,
coding_generator,
stats: BroadcastStats::default(),
thread_pool: rayon::ThreadPoolBuilder::new()
.num_threads(sys_info::cpu_num().unwrap_or(NUM_THREADS) as usize)
.build()
.unwrap(),
};
loop {
if let Err(e) =
broadcast.run(&cluster_info, receiver, sock, blocktree, genesis_blockhash)
broadcast_stage_run.run(&mut broadcast, &cluster_info, receiver, sock, blocktree)
{
match e {
Error::RecvTimeoutError(RecvTimeoutError::Disconnected) | Error::SendError => {
@ -255,17 +160,16 @@ impl BroadcastStage {
/// which will then close FetchStage in the Tpu, and then the rest of the Tpu,
/// completing the cycle.
#[allow(clippy::too_many_arguments)]
pub fn new(
fn new(
sock: UdpSocket,
cluster_info: Arc<RwLock<ClusterInfo>>,
receiver: Receiver<WorkingBankEntries>,
exit_sender: &Arc<AtomicBool>,
blocktree: &Arc<Blocktree>,
genesis_blockhash: &Hash,
broadcast_stage_run: impl BroadcastRun + Send + 'static,
) -> Self {
let blocktree = blocktree.clone();
let exit_sender = exit_sender.clone();
let genesis_blockhash = *genesis_blockhash;
let thread_hdl = Builder::new()
.name("solana-broadcaster".to_string())
.spawn(move || {
@ -275,7 +179,7 @@ impl BroadcastStage {
&cluster_info,
&receiver,
&blocktree,
&genesis_blockhash,
broadcast_stage_run,
)
})
.unwrap();
@ -302,6 +206,7 @@ mod test {
use crate::service::Service;
use solana_runtime::bank::Bank;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use std::sync::atomic::AtomicBool;
use std::sync::mpsc::channel;
@ -347,7 +252,7 @@ mod test {
entry_receiver,
&exit_sender,
&blocktree,
&Hash::default(),
StandardBroadcastRun::new(),
);
MockBroadcastStage {

View File

@ -0,0 +1,156 @@
use crate::entry::Entry;
use crate::entry::EntrySlice;
use crate::erasure::CodingGenerator;
use crate::packet::{self, SharedBlob};
use crate::poh_recorder::WorkingBankEntries;
use crate::result::Result;
use rayon::prelude::*;
use rayon::ThreadPool;
use solana_runtime::bank::Bank;
use solana_sdk::signature::{Keypair, KeypairUtil, Signable};
use std::sync::mpsc::Receiver;
use std::sync::Arc;
use std::time::{Duration, Instant};
pub(super) struct ReceiveResults {
pub ventries: Vec<Vec<(Entry, u64)>>,
pub num_entries: usize,
pub time_elapsed: Duration,
pub bank: Arc<Bank>,
pub last_tick: u64,
}
impl ReceiveResults {
pub fn new(
ventries: Vec<Vec<(Entry, u64)>>,
num_entries: usize,
time_elapsed: Duration,
bank: Arc<Bank>,
last_tick: u64,
) -> Self {
Self {
ventries,
num_entries,
time_elapsed,
bank,
last_tick,
}
}
}
pub(super) fn recv_slot_blobs(receiver: &Receiver<WorkingBankEntries>) -> Result<ReceiveResults> {
let timer = Duration::new(1, 0);
let (mut bank, entries) = receiver.recv_timeout(timer)?;
let recv_start = Instant::now();
let mut max_tick_height = bank.max_tick_height();
let mut num_entries = entries.len();
let mut ventries = Vec::new();
let mut last_tick = entries.last().map(|v| v.1).unwrap_or(0);
ventries.push(entries);
assert!(last_tick <= max_tick_height);
if last_tick != max_tick_height {
while let Ok((same_bank, entries)) = receiver.try_recv() {
// If the bank changed, that implies the previous slot was interrupted and we do not have to
// broadcast its entries.
if same_bank.slot() != bank.slot() {
num_entries = 0;
ventries.clear();
bank = same_bank.clone();
max_tick_height = bank.max_tick_height();
}
num_entries += entries.len();
last_tick = entries.last().map(|v| v.1).unwrap_or(0);
ventries.push(entries);
assert!(last_tick <= max_tick_height,);
if last_tick == max_tick_height {
break;
}
}
}
let recv_end = recv_start.elapsed();
let receive_results = ReceiveResults::new(ventries, num_entries, recv_end, bank, last_tick);
Ok(receive_results)
}
pub(super) fn entries_to_blobs(
ventries: Vec<Vec<(Entry, u64)>>,
thread_pool: &ThreadPool,
latest_blob_index: u64,
last_tick: u64,
bank: &Bank,
keypair: &Keypair,
coding_generator: &mut CodingGenerator,
) -> (Vec<SharedBlob>, Vec<SharedBlob>) {
let blobs = generate_data_blobs(
ventries,
thread_pool,
latest_blob_index,
last_tick,
&bank,
&keypair,
);
let coding = generate_coding_blobs(&blobs, &thread_pool, coding_generator, &keypair);
(blobs, coding)
}
pub(super) fn generate_data_blobs(
ventries: Vec<Vec<(Entry, u64)>>,
thread_pool: &ThreadPool,
latest_blob_index: u64,
last_tick: u64,
bank: &Bank,
keypair: &Keypair,
) -> Vec<SharedBlob> {
let blobs: Vec<SharedBlob> = thread_pool.install(|| {
ventries
.into_par_iter()
.map(|p| {
let entries: Vec<_> = p.into_iter().map(|e| e.0).collect();
entries.to_shared_blobs()
})
.flatten()
.collect()
});
packet::index_blobs(
&blobs,
&keypair.pubkey(),
latest_blob_index,
bank.slot(),
bank.parent().map_or(0, |parent| parent.slot()),
);
if last_tick == bank.max_tick_height() {
blobs.last().unwrap().write().unwrap().set_is_last_in_slot();
}
// Make sure not to modify the blob header or data after signing it here
thread_pool.install(|| {
blobs.par_iter().for_each(|b| {
b.write().unwrap().sign(keypair);
})
});
blobs
}
pub(super) fn generate_coding_blobs(
blobs: &[SharedBlob],
thread_pool: &ThreadPool,
coding_generator: &mut CodingGenerator,
keypair: &Keypair,
) -> Vec<SharedBlob> {
let coding = coding_generator.next(&blobs);
thread_pool.install(|| {
coding.par_iter().for_each(|c| {
c.write().unwrap().sign(keypair);
})
});
coding
}

View File

@ -0,0 +1,70 @@
use super::*;
use solana_sdk::hash::Hash;
pub(super) struct FailEntryVerificationBroadcastRun {}
impl FailEntryVerificationBroadcastRun {
pub(super) fn new() -> Self {
Self {}
}
}
impl BroadcastRun for FailEntryVerificationBroadcastRun {
fn run(
&mut self,
broadcast: &mut Broadcast,
cluster_info: &Arc<RwLock<ClusterInfo>>,
receiver: &Receiver<WorkingBankEntries>,
sock: &UdpSocket,
blocktree: &Arc<Blocktree>,
) -> Result<()> {
// 1) Pull entries from banking stage
let mut receive_results = broadcast_utils::recv_slot_blobs(receiver)?;
let bank = receive_results.bank.clone();
let last_tick = receive_results.last_tick;
// 2) Convert entries to blobs + generate coding blobs. Set a garbage PoH on the last entry
// in the slot to make verification fail on validators
if last_tick == bank.max_tick_height() {
let mut last_entry = receive_results
.ventries
.last_mut()
.unwrap()
.last_mut()
.unwrap();
last_entry.0.hash = Hash::default();
}
let keypair = &cluster_info.read().unwrap().keypair.clone();
let latest_blob_index = blocktree
.meta(bank.slot())
.expect("Database error")
.map(|meta| meta.consumed)
.unwrap_or(0);
let (data_blobs, coding_blobs) = broadcast_utils::entries_to_blobs(
receive_results.ventries,
&broadcast.thread_pool,
latest_blob_index,
last_tick,
&bank,
&keypair,
&mut broadcast.coding_generator,
);
blocktree.write_shared_blobs(data_blobs.iter().chain(coding_blobs.iter()))?;
// 3) Start broadcast step
let bank_epoch = bank.get_stakers_epoch(bank.slot());
let stakes = staking_utils::staked_nodes_at_epoch(&bank, bank_epoch);
// Broadcast data + erasures
cluster_info.read().unwrap().broadcast(
sock,
data_blobs.iter().chain(coding_blobs.iter()),
stakes.as_ref(),
)?;
Ok(())
}
}

View File

@ -0,0 +1,116 @@
use super::broadcast_utils;
use super::*;
#[derive(Default)]
struct BroadcastStats {
num_entries: Vec<usize>,
run_elapsed: Vec<u64>,
to_blobs_elapsed: Vec<u64>,
}
pub(super) struct StandardBroadcastRun {
stats: BroadcastStats,
}
impl StandardBroadcastRun {
pub(super) fn new() -> Self {
Self {
stats: BroadcastStats::default(),
}
}
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));
}
}
impl BroadcastRun for StandardBroadcastRun {
fn run(
&mut self,
broadcast: &mut Broadcast,
cluster_info: &Arc<RwLock<ClusterInfo>>,
receiver: &Receiver<WorkingBankEntries>,
sock: &UdpSocket,
blocktree: &Arc<Blocktree>,
) -> Result<()> {
// 1) Pull entries from banking stage
let receive_results = broadcast_utils::recv_slot_blobs(receiver)?;
let receive_elapsed = receive_results.time_elapsed;
let num_entries = receive_results.num_entries;
let bank = receive_results.bank.clone();
let last_tick = receive_results.last_tick;
inc_new_counter_info!("broadcast_service-entries_received", num_entries);
// 2) Convert entries to blobs + generate coding blobs
let to_blobs_start = Instant::now();
let keypair = &cluster_info.read().unwrap().keypair.clone();
let latest_blob_index = blocktree
.meta(bank.slot())
.expect("Database error")
.map(|meta| meta.consumed)
.unwrap_or(0);
let (data_blobs, coding_blobs) = broadcast_utils::entries_to_blobs(
receive_results.ventries,
&broadcast.thread_pool,
latest_blob_index,
last_tick,
&bank,
&keypair,
&mut broadcast.coding_generator,
);
blocktree.write_shared_blobs(data_blobs.iter().chain(coding_blobs.iter()))?;
let to_blobs_elapsed = to_blobs_start.elapsed();
// 3) Start broadcast step
let broadcast_start = Instant::now();
let bank_epoch = bank.get_stakers_epoch(bank.slot());
let stakes = staking_utils::staked_nodes_at_epoch(&bank, bank_epoch);
// Broadcast data + erasures
cluster_info.read().unwrap().broadcast(
sock,
data_blobs.iter().chain(coding_blobs.iter()),
stakes.as_ref(),
)?;
inc_new_counter_debug!(
"streamer-broadcast-sent",
data_blobs.len() + coding_blobs.len()
);
let broadcast_elapsed = broadcast_start.elapsed();
self.update_broadcast_stats(
duration_as_ms(&broadcast_elapsed),
duration_as_ms(&(receive_elapsed + to_blobs_elapsed + broadcast_elapsed)),
num_entries,
duration_as_ms(&to_blobs_elapsed),
latest_blob_index,
);
Ok(())
}
}

View File

@ -6,32 +6,11 @@ use std::io::{BufWriter, Write};
use std::path::Path;
use std::sync::Arc;
pub use solana_chacha_sys::chacha_cbc_encrypt;
pub const CHACHA_BLOCK_SIZE: usize = 64;
pub const CHACHA_KEY_SIZE: usize = 32;
#[link(name = "cpu-crypt")]
extern "C" {
fn chacha20_cbc_encrypt(
input: *const u8,
output: *mut u8,
in_len: usize,
key: *const u8,
ivec: *mut u8,
);
}
pub fn chacha_cbc_encrypt(input: &[u8], output: &mut [u8], key: &[u8], ivec: &mut [u8]) {
unsafe {
chacha20_cbc_encrypt(
input.as_ptr(),
output.as_mut_ptr(),
input.len(),
key.as_ptr(),
ivec.as_mut_ptr(),
);
}
}
pub fn chacha_cbc_encrypt_ledger(
blocktree: &Arc<Blocktree>,
slice: u64,
@ -154,7 +133,7 @@ mod tests {
hasher.hash(&buf[..size]);
// golden needs to be updated if blob stuff changes....
let golden: Hash = "9xb2Asf7UK5G8WqPwsvzo5xwLi4dixBSDiYKCtYRikA"
let golden: Hash = "E2HZjSC6VgH4nmEiTbMDATTeBcFjwSYz7QYvU7doGNhD"
.parse()
.unwrap();

View File

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

View File

@ -24,12 +24,17 @@ use crate::repair_service::RepairType;
use crate::result::Result;
use crate::staking_utils;
use crate::streamer::{BlobReceiver, BlobSender};
use crate::weighted_shuffle::weighted_shuffle;
use bincode::{deserialize, serialize};
use core::cmp;
use hashbrown::HashMap;
use itertools::Itertools;
use rand::SeedableRng;
use rand::{thread_rng, Rng};
use rand_chacha::ChaChaRng;
use rayon::prelude::*;
use solana_metrics::{datapoint_debug, inc_new_counter_debug, inc_new_counter_error};
use solana_metrics::{
datapoint_debug, inc_new_counter_debug, inc_new_counter_error, inc_new_counter_warn,
};
use solana_netutil::{
bind_in_range, bind_to, find_available_port_in_range, multi_bind_in_range, PortRange,
};
@ -39,10 +44,11 @@ use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil, Signable, Signature};
use solana_sdk::timing::{duration_as_ms, timestamp};
use solana_sdk::transaction::Transaction;
use std::borrow::Borrow;
use std::borrow::Cow;
use std::cmp::min;
use std::collections::BTreeSet;
use std::collections::{BTreeSet, HashMap};
use std::fmt;
use std::io;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
@ -83,7 +89,7 @@ pub struct ClusterInfo {
pub struct Locality {
/// The bounds of the neighborhood represented by this locality
pub neighbor_bounds: (usize, usize),
/// The `avalanche` layer this locality is in
/// The `turbine` layer this locality is in
pub layer_ix: usize,
/// The bounds of the current layer
pub layer_bounds: (usize, usize),
@ -122,7 +128,7 @@ impl Signable for PruneData {
self.pubkey
}
fn signable_data(&self) -> Vec<u8> {
fn signable_data(&self) -> Cow<[u8]> {
#[derive(Serialize)]
struct SignData {
pubkey: Pubkey,
@ -136,7 +142,7 @@ impl Signable for PruneData {
destination: self.destination,
wallclock: self.wallclock,
};
serialize(&data).expect("serialize PruneData")
Cow::Owned(serialize(&data).expect("serialize PruneData"))
}
fn get_signature(&self) -> Signature {
@ -490,57 +496,69 @@ impl ClusterInfo {
&& !ContactInfo::is_valid_address(&contact_info.tpu)
}
fn sort_by_stake<S: std::hash::BuildHasher>(
fn stake_weighted_shuffle<S: std::hash::BuildHasher>(
peers: &[ContactInfo],
stakes: Option<&HashMap<Pubkey, u64, S>>,
rng: ChaChaRng,
) -> Vec<(u64, ContactInfo)> {
let mut peers_with_stakes: Vec<_> = peers
let (stake_weights, peers_with_stakes): (Vec<_>, Vec<_>) = peers
.iter()
.map(|c| {
(
stakes.map_or(0, |stakes| *stakes.get(&c.id).unwrap_or(&0)),
c.clone(),
)
let stake = stakes.map_or(0, |stakes| *stakes.get(&c.id).unwrap_or(&0));
// For stake weighted shuffle a valid weight is atleast 1. Weight 0 is
// assumed to be missing entry. So let's make sure stake weights are atleast 1
(cmp::max(1, stake), (stake, c.clone()))
})
.sorted_by(|(_, (l_stake, l_info)), (_, (r_stake, r_info))| {
if r_stake == l_stake {
r_info.id.cmp(&l_info.id)
} else {
r_stake.cmp(&l_stake)
}
})
.unzip();
let shuffle = weighted_shuffle(stake_weights, rng);
let mut out: Vec<(u64, ContactInfo)> = shuffle
.iter()
.map(|x| peers_with_stakes[*x].clone())
.collect();
peers_with_stakes.sort_unstable_by(|(l_stake, l_info), (r_stake, r_info)| {
if r_stake == l_stake {
r_info.id.cmp(&l_info.id)
} else {
r_stake.cmp(&l_stake)
}
});
peers_with_stakes.dedup();
peers_with_stakes
out.dedup();
out
}
/// Return sorted Retransmit peers and index of `Self.id()` as if it were in that list
fn sorted_peers_and_index<S: std::hash::BuildHasher>(
pub fn shuffle_peers_and_index<S: std::hash::BuildHasher>(
&self,
stakes: Option<&HashMap<Pubkey, u64, S>>,
rng: ChaChaRng,
) -> (usize, Vec<ContactInfo>) {
let mut peers = self.retransmit_peers();
peers.push(self.lookup(&self.id()).unwrap().clone());
let contacts_and_stakes: Vec<_> = ClusterInfo::sort_by_stake(&peers, stakes);
let contacts_and_stakes: Vec<_> = ClusterInfo::stake_weighted_shuffle(&peers, stakes, rng);
let mut index = 0;
let peers: Vec<_> = contacts_and_stakes
.into_iter()
.enumerate()
.filter_map(|(i, (_, peer))| {
.map(|(i, (_, peer))| {
if peer.id == self.id() {
index = i;
None
} else {
Some(peer)
}
peer
})
.collect();
(index, peers)
}
pub fn sorted_tvu_peers(&self, stakes: Option<&HashMap<Pubkey, u64>>) -> Vec<ContactInfo> {
pub fn sorted_tvu_peers(
&self,
stakes: Option<&HashMap<Pubkey, u64>>,
rng: ChaChaRng,
) -> Vec<ContactInfo> {
let peers = self.tvu_peers();
let peers_with_stakes: Vec<_> = ClusterInfo::sort_by_stake(&peers, stakes);
let peers_with_stakes: Vec<_> = ClusterInfo::stake_weighted_shuffle(&peers, stakes, rng);
peers_with_stakes
.iter()
.map(|(_, peer)| (*peer).clone())
@ -692,40 +710,45 @@ impl ClusterInfo {
/// broadcast messages from the leader to layer 1 nodes
/// # Remarks
pub fn broadcast(
id: &Pubkey,
contains_last_tick: bool,
broadcast_table: &[ContactInfo],
pub fn broadcast<I>(
&self,
s: &UdpSocket,
blobs: &[SharedBlob],
) -> Result<()> {
if broadcast_table.is_empty() {
debug!("{}:not enough peers in cluster_info table", id);
inc_new_counter_error!("cluster_info-broadcast-not_enough_peers_error", 1);
Err(ClusterInfoError::NoPeers)?;
}
blobs: I,
stakes: Option<&HashMap<Pubkey, u64>>,
) -> Result<()>
where
I: IntoIterator,
I::Item: Borrow<SharedBlob>,
{
let mut last_err = Ok(());
let mut broadcast_table_len = 0;
let mut blobs_len = 0;
blobs.into_iter().for_each(|b| {
blobs_len += 1;
let blob = b.borrow().read().unwrap();
let broadcast_table = self.sorted_tvu_peers(stakes, ChaChaRng::from_seed(blob.seed()));
broadcast_table_len = cmp::max(broadcast_table_len, broadcast_table.len());
let orders = Self::create_broadcast_orders(contains_last_tick, blobs, broadcast_table);
trace!("broadcast orders table {}", orders.len());
let errs = Self::send_orders(id, s, orders);
for e in errs {
if let Err(e) = &e {
trace!("{}: broadcast result {:?}", id, e);
if !broadcast_table.is_empty() {
if let Err(e) = s.send_to(&blob.data[..blob.meta.size], &broadcast_table[0].tvu) {
trace!("{}: broadcast result {:?}", self.id(), e);
last_err = Err(e);
}
}
e?;
});
last_err?;
inc_new_counter_debug!("cluster_info-broadcast-max_idx", blobs_len);
if broadcast_table_len != 0 {
inc_new_counter_warn!("broadcast_service-num_peers", broadcast_table_len + 1);
}
inc_new_counter_debug!("cluster_info-broadcast-max_idx", blobs.len());
Ok(())
}
/// retransmit messages to a list of nodes
/// # Remarks
/// We need to avoid having obj locked while doing any io, such as the `send_to`
/// We need to avoid having obj locked while doing a io, such as the `send_to`
pub fn retransmit_to(
obj: &Arc<RwLock<Self>>,
peers: &[ContactInfo],
@ -772,94 +795,6 @@ impl ClusterInfo {
Ok(())
}
fn send_orders(
id: &Pubkey,
s: &UdpSocket,
orders: Vec<(SharedBlob, Vec<&ContactInfo>)>,
) -> Vec<io::Result<usize>> {
orders
.into_iter()
.flat_map(|(b, vs)| {
let blob = b.read().unwrap();
let ids_and_tvus = if log_enabled!(log::Level::Trace) {
let v_ids = vs.iter().map(|v| v.id);
let tvus = vs.iter().map(|v| v.tvu);
let ids_and_tvus = v_ids.zip(tvus).collect();
trace!(
"{}: BROADCAST idx: {} sz: {} to {:?} coding: {}",
id,
blob.index(),
blob.meta.size,
ids_and_tvus,
blob.is_coding()
);
ids_and_tvus
} else {
vec![]
};
assert!(blob.meta.size <= BLOB_SIZE);
let send_errs_for_blob: Vec<_> = vs
.iter()
.map(move |v| {
let e = s.send_to(&blob.data[..blob.meta.size], &v.tvu);
trace!(
"{}: done broadcast {} to {:?}",
id,
blob.meta.size,
ids_and_tvus
);
e
})
.collect();
send_errs_for_blob
})
.collect()
}
pub fn create_broadcast_orders<'a, T>(
contains_last_tick: bool,
blobs: &[T],
broadcast_table: &'a [ContactInfo],
) -> Vec<(T, Vec<&'a ContactInfo>)>
where
T: Clone,
{
// enumerate all the blobs in the window, those are the indices
// transmit them to nodes, starting from a different node.
if blobs.is_empty() {
return vec![];
}
let mut orders = Vec::with_capacity(blobs.len());
let x = thread_rng().gen_range(0, broadcast_table.len());
for (i, blob) in blobs.iter().enumerate() {
let br_idx = (x + i) % broadcast_table.len();
trace!("broadcast order data br_idx {}", br_idx);
orders.push((blob.clone(), vec![&broadcast_table[br_idx]]));
}
if contains_last_tick {
// Broadcast the last tick to everyone on the network so it doesn't get dropped
// (Need to maximize probability the next leader in line sees this handoff tick
// despite packet drops)
// If we had a tick at max_tick_height, then we know it must be the last
// Blob in the broadcast, There cannot be an entry that got sent after the
// last tick, guaranteed by the PohService).
orders.push((
blobs.last().unwrap().clone(),
broadcast_table.iter().collect(),
));
}
orders
}
pub fn window_index_request_bytes(&self, slot: u64, blob_index: u64) -> Result<Vec<u8>> {
let req = Protocol::RequestWindowIndex(self.my_data().clone(), slot, blob_index);
let out = serialize(&req)?;
@ -887,33 +822,34 @@ impl ClusterInfo {
}
let n = thread_rng().gen::<usize>() % valid.len();
let addr = valid[n].gossip; // send the request to the peer's gossip port
let out = {
match repair_request {
RepairType::Blob(slot, blob_index) => {
datapoint_debug!(
"cluster_info-repair",
("repair-slot", *slot, i64),
("repair-ix", *blob_index, i64)
);
self.window_index_request_bytes(*slot, *blob_index)?
}
RepairType::HighestBlob(slot, blob_index) => {
datapoint_debug!(
"cluster_info-repair_highest",
("repair-highest-slot", *slot, i64),
("repair-highest-ix", *blob_index, i64)
);
self.window_highest_index_request_bytes(*slot, *blob_index)?
}
RepairType::Orphan(slot) => {
datapoint_debug!("cluster_info-repair_orphan", ("repair-orphan", *slot, i64));
self.orphan_bytes(*slot)?
}
}
};
let out = self.map_repair_request(repair_request)?;
Ok((addr, out))
}
pub fn map_repair_request(&self, repair_request: &RepairType) -> Result<Vec<u8>> {
match repair_request {
RepairType::Blob(slot, blob_index) => {
datapoint_debug!(
"cluster_info-repair",
("repair-slot", *slot, i64),
("repair-ix", *blob_index, i64)
);
Ok(self.window_index_request_bytes(*slot, *blob_index)?)
}
RepairType::HighestBlob(slot, blob_index) => {
datapoint_debug!(
"cluster_info-repair_highest",
("repair-highest-slot", *slot, i64),
("repair-highest-ix", *blob_index, i64)
);
Ok(self.window_highest_index_request_bytes(*slot, *blob_index)?)
}
RepairType::Orphan(slot) => {
datapoint_debug!("cluster_info-repair_orphan", ("repair-orphan", *slot, i64));
Ok(self.orphan_bytes(*slot)?)
}
}
}
// If the network entrypoint hasn't been discovered yet, add it to the crds table
fn add_entrypoint(&mut self, pulls: &mut Vec<(Pubkey, Bloom<Hash>, SocketAddr, CrdsValue)>) {
match &self.entrypoint {
@ -967,18 +903,18 @@ impl ClusterInfo {
}
fn new_push_requests(&mut self) -> Vec<(SocketAddr, Protocol)> {
let self_id = self.gossip.id;
let (_, peers, msgs) = self.gossip.new_push_messages(timestamp());
peers
let (_, push_messages) = self.gossip.new_push_messages(timestamp());
push_messages
.into_iter()
.filter_map(|p| {
let peer_label = CrdsValueLabel::ContactInfo(p);
.filter_map(|(peer, messages)| {
let peer_label = CrdsValueLabel::ContactInfo(peer);
self.gossip
.crds
.lookup(&peer_label)
.and_then(CrdsValue::contact_info)
.map(|p| p.gossip)
.map(|p| (p.gossip, messages))
})
.map(|peer| (peer, Protocol::PushMessage(self_id, msgs.clone())))
.map(|(peer, msgs)| (peer, Protocol::PushMessage(self_id, msgs)))
.collect()
}
@ -1156,7 +1092,7 @@ impl ClusterInfo {
if caller.contact_info().is_none() {
return vec![];
}
let mut from = caller.contact_info().cloned().unwrap();
let from = caller.contact_info().unwrap();
if from.id == self_id {
warn!(
"PullRequest ignored, I'm talking to myself: me={} remoteme={}",
@ -1174,15 +1110,10 @@ impl ClusterInfo {
let len = data.len();
trace!("get updates since response {}", len);
let rsp = Protocol::PullResponse(self_id, data);
// The remote node may not know its public IP:PORT. Record what it looks like to us.
// This may or may not be correct for everybody, but it's better than leaving the remote with
// an unspecified address in our table
if from.gossip.ip().is_unspecified() {
inc_new_counter_debug!("cluster_info-window-request-updates-unspec-gossip", 1);
from.gossip = *from_addr;
}
// The remote node may not know its public IP:PORT. Instead of responding to the caller's
// gossip addr, respond to the origin addr.
inc_new_counter_debug!("cluster_info-pull_request-rsp", len);
to_shared_blob(rsp, from.gossip).ok().into_iter().collect()
to_shared_blob(rsp, *from_addr).ok().into_iter().collect()
}
fn handle_pull_response(me: &Arc<RwLock<Self>>, from: &Pubkey, data: Vec<CrdsValue>) {
let len = data.len();
@ -1487,7 +1418,7 @@ impl ClusterInfo {
}
}
/// Avalanche logic
/// Turbine logic
/// 1 - For the current node find out if it is in layer 1
/// 1.1 - If yes, then broadcast to all layer 1 nodes
/// 1 - using the layer 1 index, broadcast to all layer 2 nodes assuming you know neighborhood size
@ -1495,12 +1426,11 @@ impl ClusterInfo {
/// 1 - also check if there are nodes in the next layer and repeat the layer 1 to layer 2 logic
/// Returns Neighbor Nodes and Children Nodes `(neighbors, children)` for a given node based on its stake (Bank Balance)
pub fn compute_retransmit_peers<S: std::hash::BuildHasher>(
stakes: Option<&HashMap<Pubkey, u64, S>>,
cluster_info: &Arc<RwLock<ClusterInfo>>,
pub fn compute_retransmit_peers(
fanout: usize,
my_index: usize,
peers: Vec<ContactInfo>,
) -> (Vec<ContactInfo>, Vec<ContactInfo>) {
let (my_index, peers) = cluster_info.read().unwrap().sorted_peers_and_index(stakes);
//calc num_layers and num_neighborhoods using the total number of nodes
let (num_layers, layer_indices) = ClusterInfo::describe_data_plane(peers.len(), fanout);
@ -2091,11 +2021,14 @@ mod tests {
let mut cluster_info = ClusterInfo::new(contact_info.clone(), Arc::new(keypair));
cluster_info.set_leader(&leader.id);
cluster_info.insert_info(peer.clone());
cluster_info.gossip.refresh_push_active_set(&HashMap::new());
//check that all types of gossip messages are signed correctly
let (_, _, vals) = cluster_info.gossip.new_push_messages(timestamp());
let (_, push_messages) = cluster_info.gossip.new_push_messages(timestamp());
// there should be some pushes ready
assert!(vals.len() > 0);
vals.par_iter().for_each(|v| assert!(v.verify()));
assert_eq!(push_messages.len() > 0, true);
push_messages
.values()
.for_each(|v| v.par_iter().for_each(|v| assert!(v.verify())));
let (_, _, val) = cluster_info
.gossip

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