Compare commits
392 Commits
v0.16.1
...
v0.18.0-pr
Author | SHA1 | Date | |
---|---|---|---|
|
42c7d57fc0 | ||
|
efd09ecd37 | ||
|
14f6d5c82b | ||
|
c7710fdd24 | ||
|
b5aa03dd7c | ||
|
a81dd80d60 | ||
|
09ca92d416 | ||
|
56ed033233 | ||
|
e56efe237c | ||
|
3f0ff45de0 | ||
|
3709dc6558 | ||
|
6ec0318bae | ||
|
92e419f1c7 | ||
|
ccc0f2d956 | ||
|
80bb0158b7 | ||
|
f12592826f | ||
|
8d38777c1f | ||
|
832dfd4ab0 | ||
|
04d2db4dbb | ||
|
6f269e5a0e | ||
|
eb3991b9ba | ||
|
aee63f15c2 | ||
|
aced847735 | ||
|
e360e63b74 | ||
|
a6c4525998 | ||
|
77b196a226 | ||
|
b6b9c2cf56 | ||
|
59d900977d | ||
|
0f5acb86d3 | ||
|
911dee24c5 | ||
|
f03e066ec5 | ||
|
f7d3f55566 | ||
|
4298b1f595 | ||
|
870503ee36 | ||
|
4d14abbd04 | ||
|
5212b2716c | ||
|
97c0573c7d | ||
|
43cc9fcb1d | ||
|
47b5ba44e9 | ||
|
e95397e0a8 | ||
|
c7cdf8ba93 | ||
|
6ee734e1b4 | ||
|
3ab1b46ef7 | ||
|
22891b39d6 | ||
|
b6ce7ec782 | ||
|
a41c7451f1 | ||
|
6cb2040a1b | ||
|
937f9ad049 | ||
|
c2fc0f2418 | ||
|
9278201198 | ||
|
149a63100d | ||
|
d09afdbefe | ||
|
1d6bafbc77 | ||
|
01d2b4e952 | ||
|
05f3437601 | ||
|
f859243191 | ||
|
9ddc25283c | ||
|
388d4a8592 | ||
|
0b0b679120 | ||
|
3b752876ac | ||
|
9b8b7dbfd7 | ||
|
c209e14e40 | ||
|
6df1f6450f | ||
|
6d7cb23c61 | ||
|
bd7e269280 | ||
|
b05b42d74d | ||
|
af733a678a | ||
|
8a5045f05c | ||
|
4a336eb5ff | ||
|
b7e08052ae | ||
|
f6a4acfac3 | ||
|
68eff230f0 | ||
|
c78db6a94b | ||
|
294d9288d2 | ||
|
7dc5cc26a6 | ||
|
d7a2b790dc | ||
|
a7a10e12c7 | ||
|
8d243221f0 | ||
|
84368697af | ||
|
4a57cd3300 | ||
|
2214d2dbb5 | ||
|
50a991fdf9 | ||
|
4e093525c7 | ||
|
506b305959 | ||
|
e83efcfc80 | ||
|
4f1c881227 | ||
|
a642168369 | ||
|
8d296d0969 | ||
|
68b11c1c29 | ||
|
c209718a6f | ||
|
b8835312bb | ||
|
7796e87814 | ||
|
64c770275b | ||
|
855f7ff352 | ||
|
b59a99111c | ||
|
252257fe66 | ||
|
e2c9d87d91 | ||
|
9d34b80ed6 | ||
|
c63a38ae57 | ||
|
20da2604f8 | ||
|
33de2cad6d | ||
|
aef7bae60d | ||
|
54ac7ed1ea | ||
|
0180246680 | ||
|
dab7de7496 | ||
|
feaf29792f | ||
|
5f09aa36b3 | ||
|
d6c74f438a | ||
|
349ebec629 | ||
|
f4554be72c | ||
|
8537da19bb | ||
|
d1eff5d607 | ||
|
19e4f70244 | ||
|
a233a1c822 | ||
|
27bc0a22dd | ||
|
7ee8383e02 | ||
|
bab0f6be1e | ||
|
535df0026d | ||
|
3bd35dd7cc | ||
|
39d29fab82 | ||
|
fbfe1a59a6 | ||
|
77c79effc1 | ||
|
83540087c3 | ||
|
937816e67b | ||
|
c3a941086d | ||
|
1046c5e32c | ||
|
baac8d2590 | ||
|
610a02c518 | ||
|
444bd7a702 | ||
|
7afc61e0b9 | ||
|
d4d9bec2a9 | ||
|
d647a4ec57 | ||
|
536b4c1a25 | ||
|
547a7a345f | ||
|
26e380e53c | ||
|
8a12ed029c | ||
|
b41e8333b1 | ||
|
8f646e21d7 | ||
|
5608af0246 | ||
|
17b9ea3e3b | ||
|
88d4d1db7a | ||
|
cab4c88c71 | ||
|
4ec5a899f5 | ||
|
c2f74330ef | ||
|
2c8e0bcf87 | ||
|
4966ab528e | ||
|
5f81a67298 | ||
|
a0ccdccff1 | ||
|
735c7c9841 | ||
|
3a69459645 | ||
|
21cef2fe21 | ||
|
038c6ea0a7 | ||
|
81f4fd56c7 | ||
|
264a3d7dde | ||
|
43bf176fab | ||
|
baec17fdf4 | ||
|
186b514ebb | ||
|
2d42c1e33e | ||
|
9cef522eee | ||
|
a6302acfd5 | ||
|
ac72265c6b | ||
|
09da6b4b48 | ||
|
0d8f5379a0 | ||
|
02c7b89a8f | ||
|
90ae33c200 | ||
|
55c879ce2d | ||
|
1b5a332239 | ||
|
595017499e | ||
|
9b1471acae | ||
|
b766ac0899 | ||
|
e6b525a614 | ||
|
a07b17b9b5 | ||
|
9d2940d487 | ||
|
6969ece2dd | ||
|
48fc35884c | ||
|
0958905df8 | ||
|
c95cda51c9 | ||
|
3f54c0f1a6 | ||
|
4684faa5e8 | ||
|
111d0eb89b | ||
|
8b69998379 | ||
|
a21251dfea | ||
|
06cd7c1020 | ||
|
782846f295 | ||
|
19e131d710 | ||
|
9fd34cd985 | ||
|
adfb8ff2a1 | ||
|
83aa609540 | ||
|
1e1cb7c57c | ||
|
cdbd1b908a | ||
|
a12e7a2e33 | ||
|
25080f1a33 | ||
|
afa05acb32 | ||
|
d47caf2af8 | ||
|
a3a91ba222 | ||
|
751b54b60b | ||
|
488dd0e563 | ||
|
b58558ea4e | ||
|
6ad9dc18d8 | ||
|
027ebb6670 | ||
|
0ffd91df27 | ||
|
10d85f8366 | ||
|
7aad427511 | ||
|
bbd0455418 | ||
|
5174b3bc3f | ||
|
f88c72c41e | ||
|
9f678cc32a | ||
|
57036fbcc1 | ||
|
349e5001d6 | ||
|
94db9cd412 | ||
|
b505a0df22 | ||
|
acf096c5f7 | ||
|
e8583f5cfe | ||
|
5825b967d2 | ||
|
bf5bce50a4 | ||
|
77ea8b9b3e | ||
|
176cec6215 | ||
|
5ab4975c44 | ||
|
7e60ee39d9 | ||
|
3ea2933e2d | ||
|
fe87c05423 | ||
|
6b86f85916 | ||
|
04649de6a6 | ||
|
92d78451b1 | ||
|
0c87928132 | ||
|
db7e78bf99 | ||
|
adecd4cfdc | ||
|
40faaef9da | ||
|
9b54528c8e | ||
|
440d006ec1 | ||
|
6c49b10784 | ||
|
c858d1dbb3 | ||
|
741a0a8a4e | ||
|
16b6576839 | ||
|
6accf21229 | ||
|
d2b21ce8d0 | ||
|
b01990d480 | ||
|
d7fdfb7e21 | ||
|
19fe468dbc | ||
|
259a5130a8 | ||
|
0d27515d09 | ||
|
1c966aac25 | ||
|
d2b6c2e0ce | ||
|
7aecb87bce | ||
|
4a02914b30 | ||
|
7c12ecbe81 | ||
|
f093377805 | ||
|
5ac173d208 | ||
|
9f58318fc5 | ||
|
ebcdc06dc3 | ||
|
22315d88e7 | ||
|
0a36a78133 | ||
|
a25446f045 | ||
|
2860d2fe27 | ||
|
e4861f52e0 | ||
|
5698d48dc8 | ||
|
5b95685e12 | ||
|
4c90898f0b | ||
|
a191f3fd90 | ||
|
b2c776eabc | ||
|
2c8d6f87e6 | ||
|
08f6de0acd | ||
|
bd92f37553 | ||
|
2abbc89dcd | ||
|
8cad992170 | ||
|
41d0db078e | ||
|
8781aebe06 | ||
|
727c15ef8a | ||
|
e4926e4110 | ||
|
b50a3bae72 | ||
|
35ec7a5156 | ||
|
a383ea532f | ||
|
b1a678b2db | ||
|
e563a4dda3 | ||
|
dbe533385e | ||
|
f537482c86 | ||
|
aebd70ddce | ||
|
7d80cfb17a | ||
|
b8e7736af2 | ||
|
32b55e6703 | ||
|
0a949677f0 | ||
|
f777a1a74c | ||
|
d111223085 | ||
|
1ca7e9f67b | ||
|
bc8f435d45 | ||
|
5e221bf219 | ||
|
fc58b3e8c3 | ||
|
1033f52877 | ||
|
4771177f9d | ||
|
50c6b5d62d | ||
|
f9a2254688 | ||
|
49250f62aa | ||
|
22ef3c7c54 | ||
|
417e8d5064 | ||
|
1feb9bea21 | ||
|
563c42b829 | ||
|
841e5e326c | ||
|
281deae102 | ||
|
c5ba2e0883 | ||
|
eb4edd75e6 | ||
|
bb6bcd79c0 | ||
|
ef7022d638 | ||
|
2aac094f63 | ||
|
fc180f4cbf | ||
|
e26a0bf840 | ||
|
3557975c1f | ||
|
b4aebbd991 | ||
|
db13b52e6a | ||
|
f1f6537837 | ||
|
2ec5d2c7f5 | ||
|
42e5623e26 | ||
|
ab9f2adc69 | ||
|
f551b34725 | ||
|
55b8ff72d0 | ||
|
bf319ab06d | ||
|
12ef0c25b5 | ||
|
8620d0a3b2 | ||
|
933ae51fcc | ||
|
c1201e54fa | ||
|
3615445a12 | ||
|
091999a17e | ||
|
417066ad30 | ||
|
2abe051a1f | ||
|
65adce65fa | ||
|
0c8f187993 | ||
|
cbd2938035 | ||
|
0999225794 | ||
|
38b44f2496 | ||
|
c1953dca8f | ||
|
19ea5fe0c0 | ||
|
d7ed3b8024 | ||
|
a89589a1d5 | ||
|
41bda18046 | ||
|
0c832f4668 | ||
|
75b494d4a3 | ||
|
f0191a98ab | ||
|
76413cbfac | ||
|
0fa1af5d47 | ||
|
af1c70f032 | ||
|
278614fc7c | ||
|
baca35ef4d | ||
|
66552d7047 | ||
|
979df17328 | ||
|
6cec61dcfc | ||
|
a9b044f0ab | ||
|
fbea9d8621 | ||
|
44a572416d | ||
|
97c97db97e | ||
|
b8ae025f90 | ||
|
27221e28f6 | ||
|
9a52b01171 | ||
|
8cea650535 | ||
|
531679eeaf | ||
|
850f77ab3b | ||
|
4a10fd3272 | ||
|
9e2eb9e4f9 | ||
|
8120b57f17 | ||
|
f651c0922a | ||
|
8d2ec20201 | ||
|
dce1f80aac | ||
|
df1c473341 | ||
|
8a64e1ddc3 | ||
|
eb47538a82 | ||
|
861d6468ca | ||
|
d6737b8cc9 | ||
|
30592f2b12 | ||
|
1f950781c2 | ||
|
f20ba423ca | ||
|
c5e6ebb496 | ||
|
9e7f618cff | ||
|
74a06e4230 | ||
|
70f93cc126 | ||
|
3f8ff23125 | ||
|
29611fb61d | ||
|
407b1d3e6f | ||
|
206e62271b | ||
|
4e78354ab6 | ||
|
1561d9c8d4 | ||
|
0e1480b84e | ||
|
fd6047d1c5 | ||
|
b0467be393 | ||
|
1b0b095813 | ||
|
bd43724dfc | ||
|
11992946a4 | ||
|
0cc8a841ab | ||
|
23b6b85bf0 | ||
|
96b56fa6f7 | ||
|
405ca1bcb2 | ||
|
c6316bb24b | ||
|
b7f169e06e | ||
|
e4b466874c | ||
|
9911942dbd |
@@ -16,7 +16,7 @@ build_script:
|
||||
notifications:
|
||||
- provider: Slack
|
||||
incoming_webhook:
|
||||
secure: 6HTXVh+FBz29LGJb+taFOo9dqoADfo9xyAszeyXZF5Ub9t5NERytKAR35B2wb+uIOOCBF8+JhmH4437Cgf/ti4IqvURzW1QReXK7eQhn1EI=
|
||||
secure: 6HnLbeS6/Iv7JSMrrHQ7V9OSIjH/3KFzvZiinNWgQqEN0e9A6zaE4MwEXUYDWbcvVJiQneWit6dswY8Scoms2rS1PWEN5N6sjgLgyzroptc=
|
||||
channel: ci-status
|
||||
on_build_success: false
|
||||
on_build_failure: true
|
||||
@@ -25,16 +25,16 @@ notifications:
|
||||
deploy:
|
||||
- provider: S3
|
||||
access_key_id:
|
||||
secure: ptvqM/yvgeTeA12XOzybH1KYNh95AdfEvqoH9mvP2ic=
|
||||
secure: G6uzyGqbkMCXS2+sCeBCT/+s/11AHLWXCuGayfKcMEE=
|
||||
secret_access_key:
|
||||
secure: IkrgBlz5hdxvwcJdMXyyHUrpWhKa6fXLOD/8rm/rjKqYCdrba9B8V1nLZVrzXGGy
|
||||
secure: Lc+aVrbcPSXoDV7h2J7gqKT+HX0n3eEzp3JIrSP2pcKxbAikGnCtOogCiHO9/er2
|
||||
bucket: release.solana.com
|
||||
region: us-west-1
|
||||
set_public: true
|
||||
|
||||
- provider: GitHub
|
||||
auth_token:
|
||||
secure: vQ3jMl5LQrit6+TQONA3ZgQjZ/Ej62BN2ReVb2NSOwjITHMu1131hjc3dOrMEZL6
|
||||
secure: JdggY+mrznklWDcV0yvetHhD9eRcNdc627q6NcZdZAJsDidYcGgZ/tgYJiXb9D1A
|
||||
draft: false
|
||||
prerelease: false
|
||||
on:
|
||||
|
@@ -33,3 +33,10 @@ source ci/env.sh
|
||||
kill -9 "$victim" || true
|
||||
done
|
||||
)
|
||||
|
||||
# HACK: These are in our docker images, need to be removed from CARGO_HOME
|
||||
# because we try to cache downloads across builds with CARGO_HOME
|
||||
# cargo lacks a facility for "system" tooling, always tries CARGO_HOME first
|
||||
cargo uninstall cargo-audit || true
|
||||
cargo uninstall svgbob_cli || true
|
||||
cargo uninstall mdbook || true
|
||||
|
@@ -10,7 +10,13 @@
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
buildkite-agent pipeline upload ci/buildkite.yml
|
||||
if [[ -n $BUILDKITE_TAG ]]; then
|
||||
buildkite-agent annotate --style info --context release-tag \
|
||||
"https://github.com/solana-labs/solana/releases/$BUILDKITE_TAG"
|
||||
buildkite-agent pipeline upload ci/buildkite-release.yml
|
||||
else
|
||||
buildkite-agent pipeline upload ci/buildkite.yml
|
||||
fi
|
||||
|
||||
if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
|
||||
# Add helpful link back to the corresponding Github Pull Request
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,10 +11,7 @@
|
||||
**/*.rs.bk
|
||||
.cargo
|
||||
|
||||
# node config that is rsynced
|
||||
/config/
|
||||
# node config that remains local
|
||||
/config-local/
|
||||
|
||||
# log files
|
||||
*.log
|
||||
@@ -23,3 +20,4 @@ log-*.txt
|
||||
# intellij files
|
||||
/.idea/
|
||||
/solana.iml
|
||||
/.vscode/
|
||||
|
45
.mergify.yml
Normal file
45
.mergify.yml
Normal file
@@ -0,0 +1,45 @@
|
||||
# Validate your changes with:
|
||||
#
|
||||
# $ curl -F 'data=@.mergify.yml' https://gh.mergify.io/validate
|
||||
#
|
||||
# https://doc.mergify.io/
|
||||
pull_request_rules:
|
||||
- name: remove outdated reviews
|
||||
conditions:
|
||||
- base=master
|
||||
actions:
|
||||
dismiss_reviews:
|
||||
changes_requested: true
|
||||
- name: set automerge label on mergify backport PRs
|
||||
conditions:
|
||||
- author=mergify[bot]
|
||||
- head~=^mergify/bp/
|
||||
- "#status-failure=0"
|
||||
actions:
|
||||
label:
|
||||
add:
|
||||
- automerge
|
||||
- name: v0.16 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.16
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.16
|
||||
- name: v0.17 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.17
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.17
|
||||
- name: v0.18 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.18
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.18
|
@@ -4,7 +4,7 @@ os:
|
||||
language: rust
|
||||
cache: cargo
|
||||
rust:
|
||||
- 1.35.0
|
||||
- 1.36.0
|
||||
|
||||
install:
|
||||
- source ci/rust-version.sh
|
||||
|
3727
Cargo.lock
generated
3727
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
@@ -9,6 +9,7 @@ members = [
|
||||
"drone",
|
||||
"validator",
|
||||
"genesis",
|
||||
"genesis_programs",
|
||||
"gossip",
|
||||
"install",
|
||||
"keygen",
|
||||
@@ -16,6 +17,7 @@ members = [
|
||||
"ledger-tool",
|
||||
"logger",
|
||||
"merkle-tree",
|
||||
"measure",
|
||||
"metrics",
|
||||
"netutil",
|
||||
"programs/bpf",
|
||||
@@ -28,9 +30,13 @@ members = [
|
||||
"programs/exchange_api",
|
||||
"programs/exchange_program",
|
||||
"programs/failure_program",
|
||||
"programs/move_loader_api",
|
||||
"programs/move_loader_program",
|
||||
"programs/librapay_api",
|
||||
"programs/noop_program",
|
||||
"programs/stake_api",
|
||||
"programs/stake_program",
|
||||
"programs/stake_tests",
|
||||
"programs/storage_api",
|
||||
"programs/storage_program",
|
||||
"programs/token_api",
|
||||
@@ -41,7 +47,11 @@ members = [
|
||||
"runtime",
|
||||
"sdk",
|
||||
"upload-perf",
|
||||
"validator-info",
|
||||
"vote-signer",
|
||||
"wallet",
|
||||
]
|
||||
exclude = ["programs/bpf/rust/noop"]
|
||||
|
||||
exclude = [
|
||||
"programs/bpf/rust/noop",
|
||||
]
|
||||
|
@@ -127,12 +127,9 @@ Remote Testnets
|
||||
|
||||
We maintain several testnets:
|
||||
|
||||
* `testnet` - public stable testnet accessible via testnet.solana.com, with an https proxy for web apps at api.testnet.solana.com. Runs 24/7
|
||||
* `testnet` - public stable testnet accessible via testnet.solana.com. Runs 24/7
|
||||
* `testnet-beta` - public beta channel testnet accessible via beta.testnet.solana.com. Runs 24/7
|
||||
* `testnet-edge` - public edge channel testnet accessible via edge.testnet.solana.com. Runs 24/7
|
||||
* `testnet-perf` - permissioned stable testnet running a 24/7 soak test
|
||||
* `testnet-beta-perf` - permissioned beta channel testnet running a multi-hour soak test weekday mornings
|
||||
* `testnet-edge-perf` - permissioned edge channel testnet running a multi-hour soak test weekday mornings
|
||||
|
||||
## Deploy process
|
||||
|
||||
|
33
RELEASE.md
33
RELEASE.md
@@ -61,7 +61,7 @@ There are three release channels that map to branches as follows:
|
||||
|
||||
## Release Steps
|
||||
|
||||
### Advance the Channels
|
||||
### Creating a new branch from master
|
||||
|
||||
#### Create the new branch
|
||||
1. Pick your branch point for release on master.
|
||||
@@ -84,6 +84,12 @@ There are three release channels that map to branches as follows:
|
||||
At this point, `ci/channel-info.sh` should show your freshly cut release branch as
|
||||
"BETA_CHANNEL" and the previous release branch as "STABLE_CHANNEL".
|
||||
|
||||
### Update documentation
|
||||
|
||||
Document the new recommended version by updating
|
||||
```export SOLANA_RELEASE=[new scheduled TESTNET_TAG value]```
|
||||
in book/src/testnet-participation.md on the release (beta) branch.
|
||||
|
||||
### Make the Release
|
||||
|
||||
We use [github's Releases UI](https://github.com/solana-labs/solana/releases) for tagging a release.
|
||||
@@ -106,6 +112,25 @@ We use [github's Releases UI](https://github.com/solana-labs/solana/releases) fo
|
||||
1. Push your Cargo.toml change and the autogenerated Cargo.lock changes to the
|
||||
release branch.
|
||||
|
||||
### Publish updated Book
|
||||
We maintain three copies of the "book" as official documentation:
|
||||
|
||||
1) "Book" is the documentation for the latest official release. This should get manually updated whenever a new release is made. It is published here:
|
||||
https://solana-labs.github.io/book/
|
||||
|
||||
2) "Book-edge" tracks the tip of the master branch and updates automatically.
|
||||
https://solana-labs.github.io/book-edge/
|
||||
|
||||
3) "Book-beta" tracks the tip of the beta branch and updates automatically.
|
||||
https://solana-labs.github.io/book-beta/
|
||||
|
||||
To manually trigger an update of the "Book", create a new job of the manual-update-book pipeline.
|
||||
Set the tag of the latest release as the PUBLISH_BOOK_TAG environment variable.
|
||||
```bash
|
||||
PUBLISH_BOOK_TAG=v0.16.6
|
||||
```
|
||||
https://buildkite.com/solana-labs/manual-update-book
|
||||
|
||||
### Update software on testnet.solana.com
|
||||
|
||||
The testnet running on testnet.solana.com is set to use a fixed release tag
|
||||
@@ -145,12 +170,6 @@ TESTNET_TAG=[same value as used in TESTNET_TAG in the schedules]
|
||||
TESTNET_OP=create-and-start
|
||||
```
|
||||
|
||||
#### Update documentation
|
||||
|
||||
Document the new recommended version by updating
|
||||
```export SOLANA_RELEASE=[new scheduled TESTNET_TAG value]```
|
||||
in book/src/testnet-participation.md for both edge and beta channel branches.
|
||||
|
||||
### Alert the community
|
||||
|
||||
Notify Discord users on #validator-support that a new release for
|
||||
|
1
bench-exchange/.gitignore
vendored
1
bench-exchange/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "0.16.1"
|
||||
version = "0.18.0-pre0"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -12,30 +12,30 @@ publish = false
|
||||
bincode = "1.1.4"
|
||||
bs58 = "0.2.0"
|
||||
clap = "2.32.0"
|
||||
env_logger = "0.6.0"
|
||||
env_logger = "0.6.2"
|
||||
itertools = "0.8.0"
|
||||
log = "0.4.6"
|
||||
log = "0.4.8"
|
||||
num-derive = "0.2"
|
||||
num-traits = "0.2"
|
||||
rand = "0.6.5"
|
||||
rayon = "1.1.0"
|
||||
serde = "1.0.92"
|
||||
serde_derive = "1.0.92"
|
||||
serde_json = "1.0.39"
|
||||
serde = "1.0.98"
|
||||
serde_derive = "1.0.98"
|
||||
serde_json = "1.0.40"
|
||||
serde_yaml = "0.8.9"
|
||||
# solana-runtime = { path = "../solana/runtime"}
|
||||
solana = { path = "../core", version = "0.16.1" }
|
||||
solana-client = { path = "../client", version = "0.16.1" }
|
||||
solana-drone = { path = "../drone", version = "0.16.1" }
|
||||
solana-exchange-api = { path = "../programs/exchange_api", version = "0.16.1" }
|
||||
solana-exchange-program = { path = "../programs/exchange_program", version = "0.16.1" }
|
||||
solana-logger = { path = "../logger", version = "0.16.1" }
|
||||
solana-metrics = { path = "../metrics", version = "0.16.1" }
|
||||
solana-netutil = { path = "../netutil", version = "0.16.1" }
|
||||
solana-runtime = { path = "../runtime", version = "0.16.1" }
|
||||
solana-sdk = { path = "../sdk", version = "0.16.1" }
|
||||
untrusted = "0.6.2"
|
||||
ws = "0.8.1"
|
||||
solana = { path = "../core", version = "0.18.0-pre0" }
|
||||
solana-client = { path = "../client", version = "0.18.0-pre0" }
|
||||
solana-drone = { path = "../drone", version = "0.18.0-pre0" }
|
||||
solana-exchange-api = { path = "../programs/exchange_api", version = "0.18.0-pre0" }
|
||||
solana-exchange-program = { path = "../programs/exchange_program", version = "0.18.0-pre0" }
|
||||
solana-logger = { path = "../logger", version = "0.18.0-pre0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.18.0-pre0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.18.0-pre0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.18.0-pre0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.18.0-pre0" }
|
||||
untrusted = "0.7.0"
|
||||
ws = "0.9.0"
|
||||
|
||||
[features]
|
||||
cuda = ["solana/cuda"]
|
||||
|
@@ -6,10 +6,10 @@ learn how to start and interact with the exchange.
|
||||
|
||||
### Table of Contents
|
||||
[Overview](#Overview)<br>
|
||||
[Premiss](#Premiss)<br>
|
||||
[Premise](#Premise)<br>
|
||||
[Exchange startup](#Exchange-startup)<br>
|
||||
[Trade requests](#Trade-requests)<br>
|
||||
[Trade cancellations](#Trade-cancellations)<br>
|
||||
[Order Requests](#Trade-requests)<br>
|
||||
[Order Cancellations](#Trade-cancellations)<br>
|
||||
[Trade swap](#Trade-swap)<br>
|
||||
[Exchange program operations](#Exchange-program-operations)<br>
|
||||
[Quotes and OHLCV](#Quotes-and-OHLCV)<br>
|
||||
@@ -22,9 +22,9 @@ An exchange is a marketplace where one asset can be traded for another. This
|
||||
demo demonstrates one way to host an exchange on the Solana blockchain by
|
||||
emulating a currency exchange.
|
||||
|
||||
The assets are virtual tokens held by investors who may post trade requests to
|
||||
the exchange. A Swapper monitors the exchange and posts swap requests for
|
||||
matching trade orders. All the transactions can execute concurrently.
|
||||
The assets are virtual tokens held by investors who may post order requests to
|
||||
the exchange. A Matcher monitors the exchange and posts swap requests for
|
||||
matching orders. All the transactions can execute concurrently.
|
||||
|
||||
## Premise
|
||||
|
||||
@@ -42,92 +42,91 @@ matching trade orders. All the transactions can execute concurrently.
|
||||
- A request to create a token account
|
||||
- Token request
|
||||
- A request to deposit tokens of a particular type into a token account.
|
||||
- Token pair
|
||||
- A unique ordered list of two tokens. For the four types of tokens used in
|
||||
this demo, the valid pairs are AB, AC, AD, BC, BD, CD.
|
||||
- Direction of trade
|
||||
- Describes which token in the pair the investor wants to sell and buy and can
|
||||
be either "To" or "From". For example, if an investor issues a "To" trade
|
||||
for "AB" then they which to exchange A tokens to B tokens. A "From" order
|
||||
would read the other way, A tokens from B tokens.
|
||||
- Asset pair
|
||||
- A struct with fields Base and Quote, representing the two assets which make up a
|
||||
trading pair, which themselves are Tokens. The Base or 'primary' asset is the
|
||||
numerator and the Quote is the denominator for pricing purposes.
|
||||
- Order side
|
||||
- Describes which side of the market an investor wants to place a trade on. Options
|
||||
are "Bid" or "Ask", where a bid represents an offer to purchase the Base asset of
|
||||
the AssetPair for a sum of the Quote Asset and an Ask is an offer to sell Base asset
|
||||
for the Quote asset.
|
||||
- Price ratio
|
||||
- An expression of the relative prices of two tokens. They consist of the
|
||||
price of the primary token and the price of the secondary token. For
|
||||
simplicity sake, the primary token's price is always 1, which forces the
|
||||
secondary to be the common denominator. For example, if token A was worth
|
||||
2 and token B was worth 6, the price ratio would be 1:3 or just 3. Price
|
||||
ratios are represented as fixed point numbers. The fixed point scaler is
|
||||
defined in
|
||||
- An expression of the relative prices of two tokens. Calculated with the Base
|
||||
Asset as the numerator and the Quote Asset as the denominator. Ratios are
|
||||
represented as fixed point numbers. The fixed point scaler is defined in
|
||||
[exchange_state.rs](https://github.com/solana-labs/solana/blob/c2fdd1362a029dcf89c8907c562d2079d977df11/programs/exchange_api/src/exchange_state.rs#L7)
|
||||
- Trade request
|
||||
- A Solana transaction executed by the exchange requesting the trade of one
|
||||
type of token for another. Trade requests are made up of the token pair,
|
||||
the direction of the trade, quantity of the primary token, the price ratio,
|
||||
and the two token accounts to be credited/deducted. An example trade
|
||||
request looks like "T AB 5 2" which reads "Exchange 5 A tokens to B tokens
|
||||
at a price ratio of 1:2" A fulfilled trade would result in 5 A tokens
|
||||
- Order request
|
||||
- A Solana transaction sent by a trader to the exchange to submit an order.
|
||||
Order requests are made up of the token pair, the order side (bid or ask),
|
||||
quantity of the primary token, the price ratio, and the two token accounts
|
||||
to be credited/deducted. An example trade request looks like "T AB 5 2"
|
||||
which reads "Exchange 5 A tokens to B tokens at a price ratio of 1:2" A fulfilled trade would result in 5 A tokens
|
||||
deducted and 10 B tokens credited to the trade initiator's token accounts.
|
||||
Successful trade requests result in a trade order.
|
||||
- Trade order
|
||||
- The result of a successful trade request. Trade orders are stored in
|
||||
accounts owned by the submitter of the trade request. They can only be
|
||||
Successful order requests result in an order.
|
||||
- Order
|
||||
- The result of a successful order request. orders are stored in
|
||||
accounts owned by the submitter of the order request. They can only be
|
||||
canceled by their owner but can be used by anyone in a trade swap. They
|
||||
contain the same information as the trade request.
|
||||
contain the same information as the order request.
|
||||
- Price spread
|
||||
- The difference between the two matching trade orders. The spread is the
|
||||
profit of the Swapper initiating the swap request.
|
||||
- Swap requirements
|
||||
- The difference between the two matching orders. The spread is the
|
||||
profit of the Matcher initiating the swap request.
|
||||
- Match requirements
|
||||
- Policies that result in a successful trade swap.
|
||||
- Swap request
|
||||
- A request to exchange tokens between to trade orders
|
||||
- Trade swap
|
||||
- A successful trade. A swap consists of two matching trade orders that meet
|
||||
swap requirements. A trade swap may not wholly satisfy one or both of the
|
||||
trade orders in which case the trade orders are adjusted appropriately. As
|
||||
long as the swap requirements are met there will be an exchange of tokens
|
||||
between accounts. Any price spread is deposited into the Swapper's profit
|
||||
account. All trade swaps are recorded in a new account for posterity.
|
||||
- Match request
|
||||
- A request to fill two complementary orders (bid/ask), resulting if successful,
|
||||
in a trade being created.
|
||||
- Trade
|
||||
- A successful trade is created from two matching orders that meet
|
||||
swap requirements which are submitted in a Match Request by a Matcher and
|
||||
executed by the exchange. A trade may not wholly satisfy one or both of the
|
||||
orders in which case the orders are adjusted appropriately. Upon execution,
|
||||
tokens are distributed to the traders' accounts and any overlap or
|
||||
"negative spread" between orders is deposited into the Matcher's profit
|
||||
account. All successful trades are recorded in the data of a new solana
|
||||
account for posterity.
|
||||
- Investor
|
||||
- Individual investors who hold a number of tokens and wish to trade them on
|
||||
the exchange. Investors operate as Solana thin clients who own a set of
|
||||
accounts containing tokens and/or trade requests. Investors post
|
||||
accounts containing tokens and/or order requests. Investors post
|
||||
transactions to the exchange in order to request tokens and post or cancel
|
||||
trade requests.
|
||||
- Swapper
|
||||
- An agent who facilitates trading between investors. Swappers operate as
|
||||
Solana thin clients who monitor all the trade orders looking for a trade
|
||||
match. Once found, the Swapper issues a swap request to the exchange.
|
||||
Swappers are the engine of the exchange and are rewarded for their efforts by
|
||||
accumulating the price spreads of the swaps they initiate. Swappers also
|
||||
order requests.
|
||||
- Matcher
|
||||
- An agent who facilitates trading between investors. Matchers operate as
|
||||
Solana thin clients who monitor all the orders looking for a trade
|
||||
match. Once found, the Matcher issues a swap request to the exchange.
|
||||
Matchers are the engine of the exchange and are rewarded for their efforts by
|
||||
accumulating the price spreads of the swaps they initiate. Matchers also
|
||||
provide current bid/ask price and OHLCV (Open, High, Low, Close, Volume)
|
||||
information on demand via a public network port.
|
||||
- Transaction fees
|
||||
- Solana transaction fees are paid for by the transaction submitters who are
|
||||
the Investors and Swappers.
|
||||
the Investors and Matchers.
|
||||
|
||||
## Exchange startup
|
||||
|
||||
The exchange is up and running when it reaches a state where it can take
|
||||
investor's trades and Swapper's swap requests. To achieve this state the
|
||||
investors' trades and Matchers' match requests. To achieve this state the
|
||||
following must occur in order:
|
||||
|
||||
- Start the Solana blockchain
|
||||
- Start the Swapper thin-client
|
||||
- The Swapper subscribes to change notifications for all the accounts owned by
|
||||
- Start the thin-client
|
||||
- The Matcher subscribes to change notifications for all the accounts owned by
|
||||
the exchange program id. The subscription is managed via Solana's JSON RPC
|
||||
interface.
|
||||
- The Swapper starts responding to queries for bid/ask price and OHLCV
|
||||
- The Matcher starts responding to queries for bid/ask price and OHLCV
|
||||
|
||||
The Swapper responding successfully to price and OHLCV requests is the signal to
|
||||
The Matcher responding successfully to price and OHLCV requests is the signal to
|
||||
the investors that trades submitted after that point will be analyzed. <!--This
|
||||
is not ideal, and instead investors should be able to submit trades at any time,
|
||||
and the Swapper could come and go without missing a trade. One way to achieve
|
||||
this is for the Swapper to read the current state of all accounts looking for all
|
||||
open trade orders.-->
|
||||
and the Matcher could come and go without missing a trade. One way to achieve
|
||||
this is for the Matcher to read the current state of all accounts looking for all
|
||||
open orders.-->
|
||||
|
||||
Investors will initially query the exchange to discover their current balance
|
||||
for each type of token. If the investor does not already have an account for
|
||||
each type of token, they will submit account requests. Swappers as well will
|
||||
each type of token, they will submit account requests. Matcher as well will
|
||||
request accounts to hold the tokens they earn by initiating trade swaps.
|
||||
|
||||
```rust
|
||||
@@ -165,7 +164,7 @@ pub struct TokenAccountInfo {
|
||||
}
|
||||
```
|
||||
|
||||
For this demo investors or Swappers can request more tokens from the exchange at
|
||||
For this demo investors or Matcher can request more tokens from the exchange at
|
||||
any time by submitting token requests. In non-demos, an exchange of this type
|
||||
would provide another way to exchange a 3rd party asset into tokens.
|
||||
|
||||
@@ -181,19 +180,19 @@ pub enum ExchangeInstruction {
|
||||
}
|
||||
```
|
||||
|
||||
## Trade requests
|
||||
## Order Requests
|
||||
|
||||
When an investor decides to exchange a token of one type for another, they
|
||||
submit a transaction to the Solana Blockchain containing a trade request, which,
|
||||
if successful, is turned into a trade order. Trade orders do not expire but are
|
||||
cancellable. <!-- Trade orders should have a timestamp to enable trade
|
||||
expiration --> When a trade order is created, tokens are deducted from a token
|
||||
account and the trade order acts as an escrow. The tokens are held until the
|
||||
trade order is fulfilled or canceled. If the direction is `To`, then the number
|
||||
submit a transaction to the Solana Blockchain containing an order request, which,
|
||||
if successful, is turned into an order. orders do not expire but are
|
||||
cancellable. <!-- orders should have a timestamp to enable trade
|
||||
expiration --> When an order is created, tokens are deducted from a token
|
||||
account and the order acts as an escrow. The tokens are held until the
|
||||
order is fulfilled or canceled. If the direction is `To`, then the number
|
||||
of `tokens` are deducted from the primary account, if `From` then `tokens`
|
||||
multiplied by `price` are deducted from the secondary account. Trade orders are
|
||||
multiplied by `price` are deducted from the secondary account. orders are
|
||||
no longer valid when the number of `tokens` goes to zero, at which point they
|
||||
can no longer be used. <!-- Could support refilling trade orders, so trade order
|
||||
can no longer be used. <!-- Could support refilling orders, so order
|
||||
accounts are refilled rather than accumulating -->
|
||||
|
||||
```rust
|
||||
@@ -205,7 +204,7 @@ pub enum Direction {
|
||||
From,
|
||||
}
|
||||
|
||||
pub struct TradeRequestInfo {
|
||||
pub struct OrderRequestInfo {
|
||||
/// Direction of trade
|
||||
pub direction: Direction,
|
||||
|
||||
@@ -224,7 +223,7 @@ pub struct TradeRequestInfo {
|
||||
}
|
||||
|
||||
pub enum ExchangeInstruction {
|
||||
/// Trade request
|
||||
/// order request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - Token account associated with this trade
|
||||
@@ -233,7 +232,7 @@ pub enum ExchangeInstruction {
|
||||
|
||||
/// Trade accounts are populated with this structure
|
||||
pub struct TradeOrderInfo {
|
||||
/// Owner of the trade order
|
||||
/// Owner of the order
|
||||
pub owner: Pubkey,
|
||||
/// Direction of the exchange
|
||||
pub direction: Direction,
|
||||
@@ -252,7 +251,7 @@ pub struct TradeOrderInfo {
|
||||
}
|
||||
```
|
||||
|
||||
## Trade cancellations
|
||||
## Order cancellations
|
||||
|
||||
An investor may cancel a trade at anytime, but only trades they own. If the
|
||||
cancellation is successful, any tokens held in escrow are returned to the
|
||||
@@ -260,24 +259,24 @@ account from which they came.
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// Trade cancellation
|
||||
/// order cancellation
|
||||
/// key 0 - Signer
|
||||
/// key 1 -Trade order to cancel
|
||||
/// key 1 -order to cancel
|
||||
TradeCancellation,
|
||||
}
|
||||
```
|
||||
|
||||
## Trade swaps
|
||||
|
||||
The Swapper is monitoring the accounts assigned to the exchange program and
|
||||
building a trade-order table. The trade order table is used to identify
|
||||
matching trade orders which could be fulfilled. When a match is found the
|
||||
Swapper should issue a swap request. Swap requests may not satisfy the entirety
|
||||
The Matcher is monitoring the accounts assigned to the exchange program and
|
||||
building a trade-order table. The order table is used to identify
|
||||
matching orders which could be fulfilled. When a match is found the
|
||||
Matcher should issue a swap request. Swap requests may not satisfy the entirety
|
||||
of either order, but the exchange will greedily fulfill it. Any leftover tokens
|
||||
in either account will keep the trade order valid for further swap requests in
|
||||
in either account will keep the order valid for further swap requests in
|
||||
the future.
|
||||
|
||||
Matching trade orders are defined by the following swap requirements:
|
||||
Matching orders are defined by the following swap requirements:
|
||||
|
||||
- Opposite polarity (one `To` and one `From`)
|
||||
- Operate on the same token pair
|
||||
@@ -310,14 +309,14 @@ whole for clarity.
|
||||
| 5 | 1 T AB 2 10 | 2 F AB 1 5 |
|
||||
|
||||
As part of a successful swap request, the exchange will credit tokens to the
|
||||
Swapper's account equal to the difference in the price ratios or the two orders.
|
||||
These tokens are considered the Swapper's profit for initiating the trade.
|
||||
Matcher's account equal to the difference in the price ratios or the two orders.
|
||||
These tokens are considered the Matcher's profit for initiating the trade.
|
||||
|
||||
The Swapper would initiate the following swap on the order table above:
|
||||
The Matcher would initiate the following swap on the order table above:
|
||||
|
||||
- Row 1, To: Investor 1 trades 2 A tokens to 8 B tokens
|
||||
- Row 1, From: Investor 2 trades 2 A tokens from 8 B tokens
|
||||
- Swapper takes 8 B tokens as profit
|
||||
- Matcher takes 8 B tokens as profit
|
||||
|
||||
Both row 1 trades are fully realized, table becomes:
|
||||
|
||||
@@ -328,11 +327,11 @@ Both row 1 trades are fully realized, table becomes:
|
||||
| 3 | 1 T AB 2 8 | 2 F AB 3 6 |
|
||||
| 4 | 1 T AB 2 10 | 2 F AB 1 5 |
|
||||
|
||||
The Swapper would initiate the following swap:
|
||||
The Matcher would initiate the following swap:
|
||||
|
||||
- Row 1, To: Investor 1 trades 1 A token to 4 B tokens
|
||||
- Row 1, From: Investor 2 trades 1 A token from 4 B tokens
|
||||
- Swapper takes 4 B tokens as profit
|
||||
- Matcher takes 4 B tokens as profit
|
||||
|
||||
Row 1 From is not fully realized, table becomes:
|
||||
|
||||
@@ -343,11 +342,11 @@ Row 1 From is not fully realized, table becomes:
|
||||
| 3 | 1 T AB 2 10 | 2 F AB 3 6 |
|
||||
| 4 | | 2 F AB 1 5 |
|
||||
|
||||
The Swapper would initiate the following swap:
|
||||
The Matcher would initiate the following swap:
|
||||
|
||||
- Row 1, To: Investor 1 trades 1 A token to 6 B tokens
|
||||
- Row 1, From: Investor 2 trades 1 A token from 6 B tokens
|
||||
- Swapper takes 2 B tokens as profit
|
||||
- Matcher takes 2 B tokens as profit
|
||||
|
||||
Row 1 To is now fully realized, table becomes:
|
||||
|
||||
@@ -357,11 +356,11 @@ Row 1 To is now fully realized, table becomes:
|
||||
| 2 | 1 T AB 2 8 | 2 F AB 3 5 |
|
||||
| 3 | 1 T AB 2 10 | 2 F AB 1 5 |
|
||||
|
||||
The Swapper would initiate the following last swap:
|
||||
The Matcher would initiate the following last swap:
|
||||
|
||||
- Row 1, To: Investor 1 trades 2 A token to 12 B tokens
|
||||
- Row 1, From: Investor 2 trades 2 A token from 12 B tokens
|
||||
- Swapper takes 4 B tokens as profit
|
||||
- Matcher takes 4 B tokens as profit
|
||||
|
||||
Table becomes:
|
||||
|
||||
@@ -379,11 +378,11 @@ pub enum ExchangeInstruction {
|
||||
/// Trade swap request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - 'To' trade order
|
||||
/// key 3 - `From` trade order
|
||||
/// key 2 - 'To' order
|
||||
/// key 3 - `From` order
|
||||
/// key 4 - Token account associated with the To Trade
|
||||
/// key 5 - Token account associated with From trade
|
||||
/// key 6 - Token account in which to deposit the Swappers profit from the swap.
|
||||
/// key 6 - Token account in which to deposit the Matcher profit from the swap.
|
||||
SwapRequest,
|
||||
}
|
||||
|
||||
@@ -391,9 +390,9 @@ pub enum ExchangeInstruction {
|
||||
pub struct TradeSwapInfo {
|
||||
/// Pair swapped
|
||||
pub pair: TokenPair,
|
||||
/// `To` trade order
|
||||
/// `To` order
|
||||
pub to_trade_order: Pubkey,
|
||||
/// `From` trade order
|
||||
/// `From` order
|
||||
pub from_trade_order: Pubkey,
|
||||
/// Number of primary tokens exchanged
|
||||
pub primary_tokens: u64,
|
||||
@@ -424,32 +423,32 @@ pub enum ExchangeInstruction {
|
||||
/// the exchange has a limitless number of tokens it can transfer.
|
||||
TransferRequest(Token, u64),
|
||||
|
||||
/// Trade request
|
||||
/// order request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - Token account associated with this trade
|
||||
TradeRequest(TradeRequestInfo),
|
||||
|
||||
/// Trade cancellation
|
||||
/// order cancellation
|
||||
/// key 0 - Signer
|
||||
/// key 1 -Trade order to cancel
|
||||
/// key 1 -order to cancel
|
||||
TradeCancellation,
|
||||
|
||||
/// Trade swap request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - 'To' trade order
|
||||
/// key 3 - `From` trade order
|
||||
/// key 2 - 'To' order
|
||||
/// key 3 - `From` order
|
||||
/// key 4 - Token account associated with the To Trade
|
||||
/// key 5 - Token account associated with From trade
|
||||
/// key 6 - Token account in which to deposit the Swappers profit from the swap.
|
||||
/// key 6 - Token account in which to deposit the Matcher profit from the swap.
|
||||
SwapRequest,
|
||||
}
|
||||
```
|
||||
|
||||
## Quotes and OHLCV
|
||||
|
||||
The Swapper will provide current bid/ask price quotes based on trade actively and
|
||||
The Matcher will provide current bid/ask price quotes based on trade actively and
|
||||
also provide OHLCV based on some time window. The details of how the bid/ask
|
||||
price quotes are calculated are yet to be decided.
|
||||
|
||||
@@ -478,6 +477,3 @@ To also see the cluster messages:
|
||||
```bash
|
||||
$ RUST_LOG=solana_bench_exchange=info,solana=info cargo test --release -- --nocapture test_exchange_local_cluster
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@@ -332,7 +332,7 @@ fn do_tx_transfers<T>(
|
||||
|
||||
struct TradeInfo {
|
||||
trade_account: Pubkey,
|
||||
order_info: TradeOrderInfo,
|
||||
order_info: OrderInfo,
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn swapper<T>(
|
||||
@@ -509,7 +509,7 @@ fn trader<T>(
|
||||
T: Client,
|
||||
{
|
||||
// TODO Hard coded for now
|
||||
let pair = TokenPair::AB;
|
||||
let pair = AssetPair::default();
|
||||
let tokens = 1;
|
||||
let price = 1000;
|
||||
let mut account_group: usize = 0;
|
||||
@@ -527,21 +527,21 @@ fn trader<T>(
|
||||
let mut trade_infos = vec![];
|
||||
let start = account_group * batch_size as usize;
|
||||
let end = account_group * batch_size as usize + batch_size as usize;
|
||||
let mut direction = Direction::To;
|
||||
let mut side = OrderSide::Ask;
|
||||
for (signer, trade, src) in izip!(
|
||||
signers[start..end].iter(),
|
||||
trade_keys,
|
||||
srcs[start..end].iter(),
|
||||
) {
|
||||
direction = if direction == Direction::To {
|
||||
Direction::From
|
||||
side = if side == OrderSide::Ask {
|
||||
OrderSide::Bid
|
||||
} else {
|
||||
Direction::To
|
||||
OrderSide::Ask
|
||||
};
|
||||
let order_info = TradeOrderInfo {
|
||||
let order_info = OrderInfo {
|
||||
/// Owner of the trade order
|
||||
owner: Pubkey::default(), // don't care
|
||||
direction,
|
||||
side,
|
||||
pair,
|
||||
tokens,
|
||||
price,
|
||||
@@ -551,7 +551,7 @@ fn trader<T>(
|
||||
trade_account: trade.pubkey(),
|
||||
order_info,
|
||||
});
|
||||
trades.push((signer, trade.pubkey(), direction, src));
|
||||
trades.push((signer, trade.pubkey(), side, src));
|
||||
}
|
||||
account_group = (account_group + 1) % account_groups as usize;
|
||||
|
||||
@@ -562,7 +562,7 @@ fn trader<T>(
|
||||
trades.chunks(chunk_size).for_each(|chunk| {
|
||||
let trades_txs: Vec<_> = chunk
|
||||
.par_iter()
|
||||
.map(|(signer, trade, direction, src)| {
|
||||
.map(|(signer, trade, side, src)| {
|
||||
let s: &Keypair = &signer;
|
||||
let owner = &signer.pubkey();
|
||||
let space = mem::size_of::<ExchangeState>() as u64;
|
||||
@@ -571,7 +571,7 @@ fn trader<T>(
|
||||
vec![
|
||||
system_instruction::create_account(owner, trade, 1, space, &id()),
|
||||
exchange_instruction::trade_request(
|
||||
owner, trade, *direction, pair, tokens, price, src,
|
||||
owner, trade, *side, pair, tokens, price, src,
|
||||
),
|
||||
],
|
||||
blockhash,
|
||||
@@ -646,6 +646,20 @@ where
|
||||
false
|
||||
}
|
||||
|
||||
fn verify_funding_transfer<T: SyncClient + ?Sized>(
|
||||
client: &T,
|
||||
tx: &Transaction,
|
||||
amount: u64,
|
||||
) -> bool {
|
||||
for a in &tx.message().account_keys[1..] {
|
||||
if client.get_balance(a).unwrap_or(0) >= amount {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn fund_keys(client: &Client, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
|
||||
let total = lamports * (dests.len() as u64 + 1);
|
||||
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
|
||||
@@ -703,6 +717,7 @@ pub fn fund_keys(client: &Client, source: &Keypair, dests: &[Arc<Keypair>], lamp
|
||||
.collect();
|
||||
|
||||
let mut retries = 0;
|
||||
let amount = chunk[0].1[0].1;
|
||||
while !to_fund_txs.is_empty() {
|
||||
let receivers = to_fund_txs
|
||||
.iter()
|
||||
@@ -731,7 +746,7 @@ pub fn fund_keys(client: &Client, source: &Keypair, dests: &[Arc<Keypair>], lamp
|
||||
let mut waits = 0;
|
||||
loop {
|
||||
sleep(Duration::from_millis(200));
|
||||
to_fund_txs.retain(|(_, tx)| !verify_transfer(client, &tx));
|
||||
to_fund_txs.retain(|(_, tx)| !verify_funding_transfer(client, &tx, amount));
|
||||
if to_fund_txs.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ use std::{error, fmt};
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ToOrder {
|
||||
pub pubkey: Pubkey,
|
||||
pub info: TradeOrderInfo,
|
||||
pub info: OrderInfo,
|
||||
}
|
||||
|
||||
impl Ord for ToOrder {
|
||||
@@ -26,7 +26,7 @@ impl PartialOrd for ToOrder {
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct FromOrder {
|
||||
pub pubkey: Pubkey,
|
||||
pub info: TradeOrderInfo,
|
||||
pub info: OrderInfo,
|
||||
}
|
||||
|
||||
impl Ord for FromOrder {
|
||||
@@ -95,17 +95,13 @@ impl OrderBook {
|
||||
// pub fn cancel(&mut self, pubkey: Pubkey) -> Result<(), Box<dyn error::Error>> {
|
||||
// Ok(())
|
||||
// }
|
||||
pub fn push(
|
||||
&mut self,
|
||||
pubkey: Pubkey,
|
||||
info: TradeOrderInfo,
|
||||
) -> Result<(), Box<dyn error::Error>> {
|
||||
check_trade(info.direction, info.tokens, info.price)?;
|
||||
match info.direction {
|
||||
Direction::To => {
|
||||
pub fn push(&mut self, pubkey: Pubkey, info: OrderInfo) -> Result<(), Box<dyn error::Error>> {
|
||||
check_trade(info.side, info.tokens, info.price)?;
|
||||
match info.side {
|
||||
OrderSide::Ask => {
|
||||
self.to_ab.push(ToOrder { pubkey, info });
|
||||
}
|
||||
Direction::From => {
|
||||
OrderSide::Bid => {
|
||||
self.from_ab.push(FromOrder { pubkey, info });
|
||||
}
|
||||
}
|
||||
|
1
bench-streamer/.gitignore
vendored
1
bench-streamer/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/target/
|
||||
/farf/
|
||||
|
@@ -2,16 +2,16 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "0.16.1"
|
||||
version = "0.18.0-pre0"
|
||||
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.16.1" }
|
||||
solana-logger = { path = "../logger", version = "0.16.1" }
|
||||
solana-netutil = { path = "../netutil", version = "0.16.1" }
|
||||
solana = { path = "../core", version = "0.18.0-pre0" }
|
||||
solana-logger = { path = "../logger", version = "0.18.0-pre0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.18.0-pre0" }
|
||||
|
||||
[features]
|
||||
cuda = ["solana/cuda"]
|
||||
|
@@ -1,4 +1,5 @@
|
||||
use clap::{crate_description, crate_name, crate_version, App, Arg};
|
||||
use solana::packet::PacketsRecycler;
|
||||
use solana::packet::{Packet, Packets, BLOB_SIZE, PACKET_DATA_SIZE};
|
||||
use solana::result::Result;
|
||||
use solana::streamer::{receiver, PacketReceiver};
|
||||
@@ -16,7 +17,7 @@ fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
|
||||
let send = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
let mut msgs = Packets::default();
|
||||
msgs.packets.resize(10, Packet::default());
|
||||
for w in &mut msgs.packets {
|
||||
for w in msgs.packets.iter_mut() {
|
||||
w.meta.size = PACKET_DATA_SIZE;
|
||||
w.meta.set_addr(&addr);
|
||||
}
|
||||
@@ -74,6 +75,7 @@ fn main() -> Result<()> {
|
||||
|
||||
let mut read_channels = Vec::new();
|
||||
let mut read_threads = Vec::new();
|
||||
let recycler = PacketsRecycler::default();
|
||||
for _ in 0..num_sockets {
|
||||
let read = solana_netutil::bind_to(port, false).unwrap();
|
||||
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
|
||||
@@ -83,7 +85,13 @@ fn main() -> Result<()> {
|
||||
|
||||
let (s_reader, r_reader) = channel();
|
||||
read_channels.push(r_reader);
|
||||
read_threads.push(receiver(Arc::new(read), &exit, s_reader));
|
||||
read_threads.push(receiver(
|
||||
Arc::new(read),
|
||||
&exit,
|
||||
s_reader,
|
||||
recycler.clone(),
|
||||
"bench-streamer-test",
|
||||
));
|
||||
}
|
||||
|
||||
let t_producer1 = producer(&addr, exit.clone());
|
||||
|
1
bench-tps/.gitignore
vendored
1
bench-tps/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
||||
|
@@ -2,27 +2,32 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "0.16.1"
|
||||
version = "0.18.0-pre0"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.1.4"
|
||||
clap = "2.33.0"
|
||||
log = "0.4.6"
|
||||
log = "0.4.8"
|
||||
rayon = "1.1.0"
|
||||
serde = "1.0.92"
|
||||
serde_derive = "1.0.92"
|
||||
serde_json = "1.0.39"
|
||||
serde = "1.0.98"
|
||||
serde_derive = "1.0.98"
|
||||
serde_json = "1.0.40"
|
||||
serde_yaml = "0.8.9"
|
||||
solana = { path = "../core", version = "0.16.1" }
|
||||
solana-client = { path = "../client", version = "0.16.1" }
|
||||
solana-drone = { path = "../drone", version = "0.16.1" }
|
||||
solana-logger = { path = "../logger", version = "0.16.1" }
|
||||
solana-metrics = { path = "../metrics", version = "0.16.1" }
|
||||
solana-netutil = { path = "../netutil", version = "0.16.1" }
|
||||
solana-runtime = { path = "../runtime", version = "0.16.1" }
|
||||
solana-sdk = { path = "../sdk", version = "0.16.1" }
|
||||
solana = { path = "../core", version = "0.18.0-pre0" }
|
||||
solana-client = { path = "../client", version = "0.18.0-pre0" }
|
||||
solana-drone = { path = "../drone", version = "0.18.0-pre0" }
|
||||
solana-librapay-api = { path = "../programs/librapay_api", version = "0.18.0-pre0" }
|
||||
solana-logger = { path = "../logger", version = "0.18.0-pre0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.18.0-pre0" }
|
||||
solana-measure = { path = "../measure", version = "0.18.0-pre0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.18.0-pre0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.18.0-pre0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.18.0-pre0" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader_program", version = "0.18.0-pre0" }
|
||||
solana-move-loader-api = { path = "../programs/move_loader_api", version = "0.18.0-pre0" }
|
||||
|
||||
[features]
|
||||
cuda = ["solana/cuda"]
|
||||
|
@@ -1,13 +1,17 @@
|
||||
use solana_metrics;
|
||||
|
||||
use bincode;
|
||||
use log::*;
|
||||
use rayon::prelude::*;
|
||||
use solana::gen_keys::GenKeys;
|
||||
use solana_client::perf_utils::{sample_txs, SampleStats};
|
||||
use solana_drone::drone::request_airdrop_transaction;
|
||||
use solana_librapay_api::{create_genesis, upload_mint_program, upload_payment_program};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_metrics::datapoint_info;
|
||||
use solana_sdk::client::Client;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::system_instruction;
|
||||
use solana_sdk::system_transaction;
|
||||
@@ -24,6 +28,8 @@ use std::thread::Builder;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use solana_librapay_api::librapay_transaction;
|
||||
|
||||
pub const MAX_SPENDS_PER_TX: u64 = 4;
|
||||
pub const NUM_LAMPORTS_PER_ACCOUNT: u64 = 128;
|
||||
|
||||
@@ -43,6 +49,7 @@ pub struct Config {
|
||||
pub duration: Duration,
|
||||
pub tx_count: usize,
|
||||
pub sustained: bool,
|
||||
pub use_move: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -54,15 +61,19 @@ impl Default for Config {
|
||||
duration: Duration::new(std::u64::MAX, 0),
|
||||
tx_count: 500_000,
|
||||
sustained: false,
|
||||
use_move: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type LibraKeys = (Keypair, Pubkey, Pubkey, Vec<Keypair>);
|
||||
|
||||
pub fn do_bench_tps<T>(
|
||||
clients: Vec<T>,
|
||||
config: Config,
|
||||
gen_keypairs: Vec<Keypair>,
|
||||
keypair0_balance: u64,
|
||||
libra_args: Option<LibraKeys>,
|
||||
) -> u64
|
||||
where
|
||||
T: 'static + Client + Send + Sync,
|
||||
@@ -74,6 +85,7 @@ where
|
||||
duration,
|
||||
tx_count,
|
||||
sustained,
|
||||
..
|
||||
} = config;
|
||||
|
||||
let clients: Vec<_> = clients.into_iter().map(Arc::new).collect();
|
||||
@@ -165,6 +177,7 @@ where
|
||||
&keypairs[len..],
|
||||
threads,
|
||||
reclaim_lamports_back_to_source_account,
|
||||
&libra_args,
|
||||
);
|
||||
// In sustained mode overlap the transfers with generation
|
||||
// this has higher average performance but lower peak performance
|
||||
@@ -221,6 +234,74 @@ fn metrics_submit_lamport_balance(lamport_balance: u64) {
|
||||
);
|
||||
}
|
||||
|
||||
fn generate_move_txs(
|
||||
source: &[Keypair],
|
||||
dest: &[Keypair],
|
||||
reclaim: bool,
|
||||
move_keypairs: &[Keypair],
|
||||
libra_pay_program_id: &Pubkey,
|
||||
libra_mint_id: &Pubkey,
|
||||
blockhash: &Hash,
|
||||
) -> Vec<(Transaction, u64)> {
|
||||
let count = move_keypairs.len() / 2;
|
||||
let source_move = &move_keypairs[..count];
|
||||
let dest_move = &move_keypairs[count..];
|
||||
let pairs: Vec<_> = if !reclaim {
|
||||
source_move
|
||||
.iter()
|
||||
.zip(dest_move.iter())
|
||||
.zip(source.iter())
|
||||
.collect()
|
||||
} else {
|
||||
dest_move
|
||||
.iter()
|
||||
.zip(source_move.iter())
|
||||
.zip(dest.iter())
|
||||
.collect()
|
||||
};
|
||||
|
||||
pairs
|
||||
.par_iter()
|
||||
.map(|((from, to), payer)| {
|
||||
(
|
||||
librapay_transaction::transfer(
|
||||
libra_pay_program_id,
|
||||
libra_mint_id,
|
||||
&payer,
|
||||
&from,
|
||||
&to.pubkey(),
|
||||
1,
|
||||
*blockhash,
|
||||
),
|
||||
timestamp(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn generate_system_txs(
|
||||
source: &[Keypair],
|
||||
dest: &[Keypair],
|
||||
reclaim: bool,
|
||||
blockhash: &Hash,
|
||||
) -> Vec<(Transaction, u64)> {
|
||||
let pairs: Vec<_> = if !reclaim {
|
||||
source.iter().zip(dest.iter()).collect()
|
||||
} else {
|
||||
dest.iter().zip(source.iter()).collect()
|
||||
};
|
||||
|
||||
pairs
|
||||
.par_iter()
|
||||
.map(|(from, to)| {
|
||||
(
|
||||
system_transaction::create_user_account(from, &to.pubkey(), 1, *blockhash),
|
||||
timestamp(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn generate_txs(
|
||||
shared_txs: &SharedTransactions,
|
||||
blockhash: &Hash,
|
||||
@@ -228,25 +309,31 @@ fn generate_txs(
|
||||
dest: &[Keypair],
|
||||
threads: usize,
|
||||
reclaim: bool,
|
||||
libra_args: &Option<LibraKeys>,
|
||||
) {
|
||||
let tx_count = source.len();
|
||||
println!("Signing transactions... {} (reclaim={})", tx_count, reclaim);
|
||||
let signing_start = Instant::now();
|
||||
|
||||
let pairs: Vec<_> = if !reclaim {
|
||||
source.iter().zip(dest.iter()).collect()
|
||||
let transactions = if let Some((
|
||||
libra_genesis_keypair,
|
||||
libra_pay_program_id,
|
||||
_libra_mint_program_id,
|
||||
libra_keys,
|
||||
)) = libra_args
|
||||
{
|
||||
generate_move_txs(
|
||||
source,
|
||||
dest,
|
||||
reclaim,
|
||||
&libra_keys,
|
||||
libra_pay_program_id,
|
||||
&libra_genesis_keypair.pubkey(),
|
||||
blockhash,
|
||||
)
|
||||
} else {
|
||||
dest.iter().zip(source.iter()).collect()
|
||||
generate_system_txs(source, dest, reclaim, blockhash)
|
||||
};
|
||||
let transactions: Vec<_> = pairs
|
||||
.par_iter()
|
||||
.map(|(id, keypair)| {
|
||||
(
|
||||
system_transaction::create_user_account(id, &keypair.pubkey(), 1, *blockhash),
|
||||
timestamp(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let duration = signing_start.elapsed();
|
||||
let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos());
|
||||
@@ -346,10 +433,12 @@ pub fn fund_keys<T: Client>(
|
||||
source: &Keypair,
|
||||
dests: &[Keypair],
|
||||
total: u64,
|
||||
lamports_per_signature: u64,
|
||||
max_fee: u64,
|
||||
mut extra: u64,
|
||||
) {
|
||||
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
|
||||
let mut notfunded: Vec<&Keypair> = dests.iter().collect();
|
||||
let lamports_per_account = (total - (extra * max_fee)) / (notfunded.len() as u64 + 1);
|
||||
|
||||
println!("funding keys {}", dests.len());
|
||||
while !notfunded.is_empty() {
|
||||
@@ -362,7 +451,8 @@ pub fn fund_keys<T: Client>(
|
||||
break;
|
||||
}
|
||||
let start = notfunded.len() - max_units as usize;
|
||||
let per_unit = (f.1 - max_units * lamports_per_signature) / max_units;
|
||||
let fees = if extra > 0 { max_fee } else { 0 };
|
||||
let per_unit = (f.1 - lamports_per_account - fees) / max_units;
|
||||
let moves: Vec<_> = notfunded[start..]
|
||||
.iter()
|
||||
.map(|k| (k.pubkey(), per_unit))
|
||||
@@ -374,6 +464,7 @@ pub fn fund_keys<T: Client>(
|
||||
if !moves.is_empty() {
|
||||
to_fund.push((f.0, moves));
|
||||
}
|
||||
extra -= 1;
|
||||
}
|
||||
|
||||
// try to transfer a "few" at a time with recent blockhash
|
||||
@@ -388,13 +479,10 @@ pub fn fund_keys<T: Client>(
|
||||
let mut to_fund_txs: Vec<_> = chunk
|
||||
.par_iter()
|
||||
.map(|(k, m)| {
|
||||
(
|
||||
k.clone(),
|
||||
Transaction::new_unsigned_instructions(system_instruction::transfer_many(
|
||||
&k.pubkey(),
|
||||
&m,
|
||||
)),
|
||||
)
|
||||
let tx = Transaction::new_unsigned_instructions(
|
||||
system_instruction::transfer_many(&k.pubkey(), &m),
|
||||
);
|
||||
(k.clone(), tx)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -582,28 +670,172 @@ 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: u64) -> Vec<Keypair> {
|
||||
pub fn generate_keypairs(seed_keypair: &Keypair, count: u64) -> (Vec<Keypair>, u64) {
|
||||
let mut seed = [0u8; 32];
|
||||
seed.copy_from_slice(&seed_keypair.to_bytes()[..32]);
|
||||
let mut rnd = GenKeys::new(seed);
|
||||
|
||||
let mut total_keys = 1;
|
||||
let mut total_keys = 0;
|
||||
let mut extra = 0; // This variable tracks the number of keypairs needing extra transaction fees funded
|
||||
let mut delta = 1;
|
||||
while total_keys < count {
|
||||
total_keys *= MAX_SPENDS_PER_TX;
|
||||
extra += delta;
|
||||
delta *= MAX_SPENDS_PER_TX;
|
||||
total_keys += delta;
|
||||
}
|
||||
rnd.gen_n_keypairs(total_keys)
|
||||
(rnd.gen_n_keypairs(total_keys), extra)
|
||||
}
|
||||
|
||||
fn fund_move_keys<T: Client>(
|
||||
client: &T,
|
||||
funding_key: &Keypair,
|
||||
keypairs: &[Keypair],
|
||||
total: u64,
|
||||
libra_pay_program_id: &Pubkey,
|
||||
libra_mint_program_id: &Pubkey,
|
||||
libra_mint_key: &Keypair,
|
||||
) {
|
||||
let (mut blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
|
||||
|
||||
info!("creating the libra funding account..");
|
||||
let libra_funding_key = Keypair::new();
|
||||
let tx = librapay_transaction::create_account(
|
||||
funding_key,
|
||||
&libra_funding_key.pubkey(),
|
||||
1,
|
||||
blockhash,
|
||||
);
|
||||
let sig = client
|
||||
.async_send_transaction(tx)
|
||||
.expect("create_account in generate_and_fund_keypairs");
|
||||
client.poll_for_signature(&sig).unwrap();
|
||||
|
||||
info!("minting to funding keypair");
|
||||
let tx = librapay_transaction::mint_tokens(
|
||||
&libra_mint_program_id,
|
||||
funding_key,
|
||||
libra_mint_key,
|
||||
&libra_funding_key.pubkey(),
|
||||
total,
|
||||
blockhash,
|
||||
);
|
||||
let sig = client
|
||||
.async_send_transaction(tx)
|
||||
.expect("create_account in generate_and_fund_keypairs");
|
||||
client.poll_for_signature(&sig).unwrap();
|
||||
|
||||
info!("creating move accounts.. {}", keypairs.len());
|
||||
let create_len = 8;
|
||||
let mut funding_time = Measure::start("funding_time");
|
||||
for (i, keys) in keypairs.chunks(create_len).enumerate() {
|
||||
if client.get_balance(&keys[0].pubkey()).unwrap_or(0) > 0 {
|
||||
// already created these accounts.
|
||||
break;
|
||||
}
|
||||
|
||||
let mut tx_send = Measure::start("poll");
|
||||
|
||||
let pubkeys: Vec<_> = keys.iter().map(|k| k.pubkey()).collect();
|
||||
let tx = librapay_transaction::create_accounts(funding_key, &pubkeys, 1, blockhash);
|
||||
let ser_size = bincode::serialized_size(&tx).unwrap();
|
||||
let sig = client
|
||||
.async_send_transaction(tx)
|
||||
.expect("create_account in generate_and_fund_keypairs");
|
||||
tx_send.stop();
|
||||
let mut poll = Measure::start("poll");
|
||||
client.poll_for_signature(&sig).unwrap();
|
||||
poll.stop();
|
||||
if i % 10 == 0 {
|
||||
blockhash = client.get_recent_blockhash().unwrap().0;
|
||||
info!(
|
||||
"size: {} created {} accounts of {} sig: {}us send: {}us",
|
||||
ser_size,
|
||||
i,
|
||||
(keypairs.len() / create_len),
|
||||
poll.as_us(),
|
||||
tx_send.as_us()
|
||||
);
|
||||
}
|
||||
}
|
||||
funding_time.stop();
|
||||
info!("funding accounts {}ms", funding_time.as_ms());
|
||||
let mut sigs = vec![];
|
||||
let tx_count = keypairs.len();
|
||||
let amount = total / (tx_count as u64);
|
||||
for (i, key) in keypairs[..tx_count].iter().enumerate() {
|
||||
let tx = librapay_transaction::transfer(
|
||||
libra_pay_program_id,
|
||||
&libra_mint_key.pubkey(),
|
||||
funding_key,
|
||||
&libra_funding_key,
|
||||
&key.pubkey(),
|
||||
amount,
|
||||
blockhash,
|
||||
);
|
||||
|
||||
let sig = client
|
||||
.async_send_transaction(tx.clone())
|
||||
.expect("create_account in generate_and_fund_keypairs");
|
||||
|
||||
let mut poll_time = Measure::start("poll_start");
|
||||
let poll_status = client.poll_for_signature(&sig);
|
||||
poll_time.stop();
|
||||
info!(
|
||||
"i: {} poll: {:?} time: {}ms",
|
||||
i,
|
||||
poll_status,
|
||||
poll_time.as_ms()
|
||||
);
|
||||
|
||||
sigs.push((sig, key));
|
||||
|
||||
if i % 50 == 0 {
|
||||
blockhash = client.get_recent_blockhash().unwrap().0;
|
||||
}
|
||||
}
|
||||
|
||||
for (i, (sig, key)) in sigs.iter().enumerate() {
|
||||
let mut times = 0;
|
||||
loop {
|
||||
match client.poll_for_signature(&sig) {
|
||||
Ok(_) => {
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
info!("e :{:?} waiting times: {} sig: {}", e, times, sig);
|
||||
times += 1;
|
||||
sleep(Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
times = 0;
|
||||
loop {
|
||||
let balance = librapay_transaction::get_libra_balance(client, &key.pubkey()).unwrap();
|
||||
if balance < amount {
|
||||
info!("i: {} balance: {} times: {}", i, balance, times);
|
||||
times += 1;
|
||||
sleep(Duration::from_secs(1));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if i % 10 == 0 {
|
||||
info!("funding {} of {}", i, tx_count);
|
||||
}
|
||||
}
|
||||
info!("done..");
|
||||
}
|
||||
|
||||
pub fn generate_and_fund_keypairs<T: Client>(
|
||||
client: &T,
|
||||
drone_addr: Option<SocketAddr>,
|
||||
funding_pubkey: &Keypair,
|
||||
funding_key: &Keypair,
|
||||
tx_count: usize,
|
||||
lamports_per_account: u64,
|
||||
) -> Result<(Vec<Keypair>, u64)> {
|
||||
use_move: bool,
|
||||
) -> Result<(Vec<Keypair>, Option<LibraKeys>, u64)> {
|
||||
info!("Creating {} keypairs...", tx_count * 2);
|
||||
let mut keypairs = generate_keypairs(funding_pubkey, tx_count as u64 * 2);
|
||||
|
||||
let (mut keypairs, extra) = generate_keypairs(funding_key, tx_count as u64 * 2);
|
||||
info!("Get lamports...");
|
||||
|
||||
// Sample the first keypair, see if it has lamports, if so then resume.
|
||||
@@ -612,32 +844,76 @@ pub fn generate_and_fund_keypairs<T: Client>(
|
||||
.get_balance(&keypairs[tx_count * 2 - 1].pubkey())
|
||||
.unwrap_or(0);
|
||||
|
||||
let mut move_keypairs_ret = None;
|
||||
|
||||
if lamports_per_account > last_keypair_balance {
|
||||
let (_, fee_calculator) = client.get_recent_blockhash().unwrap();
|
||||
let extra =
|
||||
let (_blockhash, fee_calculator) = client.get_recent_blockhash().unwrap();
|
||||
let account_desired_balance =
|
||||
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)?;
|
||||
let extra_fees = extra * fee_calculator.max_lamports_per_signature;
|
||||
let mut total = account_desired_balance * (1 + keypairs.len() as u64) + extra_fees;
|
||||
if use_move {
|
||||
total *= 2;
|
||||
}
|
||||
info!("adding more lamports {}", extra);
|
||||
|
||||
println!("Previous key balance: {} max_fee: {} lamports_per_account: {} extra: {} desired_balance: {} total: {}",
|
||||
last_keypair_balance, fee_calculator.max_lamports_per_signature, lamports_per_account, extra,
|
||||
account_desired_balance, total
|
||||
);
|
||||
|
||||
if client.get_balance(&funding_key.pubkey()).unwrap_or(0) < total {
|
||||
airdrop_lamports(client, &drone_addr.unwrap(), funding_key, total)?;
|
||||
}
|
||||
|
||||
if use_move {
|
||||
let libra_genesis_keypair = create_genesis(&funding_key, client, 1_000_000);
|
||||
let libra_mint_program_id = upload_mint_program(&funding_key, client);
|
||||
let libra_pay_program_id = upload_payment_program(&funding_key, client);
|
||||
|
||||
// Generate another set of keypairs for move accounts.
|
||||
// Still fund the solana ones which will be used for fees.
|
||||
let seed = [0u8; 32];
|
||||
let mut rnd = GenKeys::new(seed);
|
||||
let move_keypairs = rnd.gen_n_keypairs(tx_count as u64 * 2);
|
||||
fund_move_keys(
|
||||
client,
|
||||
funding_key,
|
||||
&move_keypairs,
|
||||
total / 2,
|
||||
&libra_pay_program_id,
|
||||
&libra_mint_program_id,
|
||||
&libra_genesis_keypair,
|
||||
);
|
||||
move_keypairs_ret = Some((
|
||||
libra_genesis_keypair,
|
||||
libra_pay_program_id,
|
||||
libra_mint_program_id,
|
||||
move_keypairs,
|
||||
));
|
||||
|
||||
// Give solana keys half and move keys half the lamports.
|
||||
total /= 2;
|
||||
}
|
||||
|
||||
fund_keys(
|
||||
client,
|
||||
funding_pubkey,
|
||||
funding_key,
|
||||
&keypairs,
|
||||
total,
|
||||
fee_calculator.max_lamports_per_signature,
|
||||
extra,
|
||||
);
|
||||
}
|
||||
|
||||
// 'generate_keypairs' generates extra keys to be able to have size-aligned funding batches for fund_keys.
|
||||
keypairs.truncate(2 * tx_count);
|
||||
|
||||
Ok((keypairs, last_keypair_balance))
|
||||
Ok((keypairs, move_keypairs_ret, last_keypair_balance))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use solana::cluster_info::FULLNODE_PORT_RANGE;
|
||||
use solana::local_cluster::{ClusterConfig, LocalCluster};
|
||||
@@ -647,6 +923,7 @@ mod tests {
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_runtime::bank_client::BankClient;
|
||||
use solana_sdk::client::SyncClient;
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use solana_sdk::genesis_block::create_genesis_block;
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
@@ -665,47 +942,68 @@ mod tests {
|
||||
assert_eq!(should_switch_directions(20, 101), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bench_tps_local_cluster() {
|
||||
fn test_bench_tps_local_cluster(config: Config) {
|
||||
solana_logger::setup();
|
||||
const NUM_NODES: usize = 1;
|
||||
let cluster = LocalCluster::new(&ClusterConfig {
|
||||
node_stakes: vec![999_990; NUM_NODES],
|
||||
cluster_lamports: 2_000_000,
|
||||
cluster_lamports: 200_000_000,
|
||||
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
|
||||
native_instruction_processors: vec![solana_move_loader_program!()],
|
||||
..ClusterConfig::default()
|
||||
});
|
||||
|
||||
let drone_keypair = Keypair::new();
|
||||
cluster.transfer(&cluster.funding_keypair, &drone_keypair.pubkey(), 1_000_000);
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
run_local_drone(drone_keypair, addr_sender, None);
|
||||
let drone_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
|
||||
let mut config = Config::default();
|
||||
config.tx_count = 100;
|
||||
config.duration = Duration::from_secs(5);
|
||||
cluster.transfer(
|
||||
&cluster.funding_keypair,
|
||||
&drone_keypair.pubkey(),
|
||||
100_000_000,
|
||||
);
|
||||
|
||||
let client = create_client(
|
||||
(cluster.entry_point_info.rpc, cluster.entry_point_info.tpu),
|
||||
FULLNODE_PORT_RANGE,
|
||||
);
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
run_local_drone(drone_keypair, addr_sender, None);
|
||||
let drone_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
|
||||
let lamports_per_account = 100;
|
||||
let (keypairs, _keypair_balance) = generate_and_fund_keypairs(
|
||||
|
||||
let (keypairs, move_keypairs, _keypair_balance) = generate_and_fund_keypairs(
|
||||
&client,
|
||||
Some(drone_addr),
|
||||
&config.id,
|
||||
config.tx_count,
|
||||
lamports_per_account,
|
||||
config.use_move,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let total = do_bench_tps(vec![client], config, keypairs, 0);
|
||||
let total = do_bench_tps(vec![client], config, keypairs, 0, move_keypairs);
|
||||
assert!(total > 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bench_tps_local_cluster_solana() {
|
||||
let mut config = Config::default();
|
||||
config.tx_count = 100;
|
||||
config.duration = Duration::from_secs(10);
|
||||
|
||||
test_bench_tps_local_cluster(config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bench_tps_local_cluster_move() {
|
||||
let mut config = Config::default();
|
||||
config.tx_count = 100;
|
||||
config.duration = Duration::from_secs(20);
|
||||
config.use_move = true;
|
||||
|
||||
test_bench_tps_local_cluster(config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bench_tps_bank_client() {
|
||||
let (genesis_block, id) = create_genesis_block(10_000);
|
||||
@@ -717,10 +1015,11 @@ mod tests {
|
||||
config.tx_count = 10;
|
||||
config.duration = Duration::from_secs(5);
|
||||
|
||||
let (keypairs, _keypair_balance) =
|
||||
generate_and_fund_keypairs(&clients[0], None, &config.id, config.tx_count, 20).unwrap();
|
||||
let (keypairs, _move_keypairs, _keypair_balance) =
|
||||
generate_and_fund_keypairs(&clients[0], None, &config.id, config.tx_count, 20, false)
|
||||
.unwrap();
|
||||
|
||||
do_bench_tps(clients, config, keypairs, 0);
|
||||
do_bench_tps(clients, config, keypairs, 0, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -731,11 +1030,37 @@ mod tests {
|
||||
let tx_count = 10;
|
||||
let lamports = 20;
|
||||
|
||||
let (keypairs, _keypair_balance) =
|
||||
generate_and_fund_keypairs(&client, None, &id, tx_count, lamports).unwrap();
|
||||
let (keypairs, _move_keypairs, _keypair_balance) =
|
||||
generate_and_fund_keypairs(&client, None, &id, tx_count, lamports, false).unwrap();
|
||||
|
||||
for kp in &keypairs {
|
||||
assert!(client.get_balance(&kp.pubkey()).unwrap() >= lamports);
|
||||
assert_eq!(client.get_balance(&kp.pubkey()).unwrap(), lamports);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bench_tps_fund_keys_with_fees() {
|
||||
let (mut genesis_block, id) = create_genesis_block(10_000);
|
||||
let fee_calculator = FeeCalculator::new(11);
|
||||
genesis_block.fee_calculator = fee_calculator;
|
||||
let bank = Bank::new(&genesis_block);
|
||||
let client = BankClient::new(bank);
|
||||
let tx_count = 10;
|
||||
let lamports = 20;
|
||||
|
||||
let (keypairs, _move_keypairs, _keypair_balance) =
|
||||
generate_and_fund_keypairs(&client, None, &id, tx_count, lamports, false).unwrap();
|
||||
|
||||
let max_fee = client
|
||||
.get_recent_blockhash()
|
||||
.unwrap()
|
||||
.1
|
||||
.max_lamports_per_signature;
|
||||
for kp in &keypairs {
|
||||
assert_eq!(
|
||||
client.get_balance(&kp.pubkey()).unwrap(),
|
||||
lamports + max_fee
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ pub struct Config {
|
||||
pub write_to_client_file: bool,
|
||||
pub read_from_client_file: bool,
|
||||
pub target_lamports_per_signature: u64,
|
||||
pub use_move: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -40,6 +41,7 @@ impl Default for Config {
|
||||
write_to_client_file: false,
|
||||
read_from_client_file: false,
|
||||
target_lamports_per_signature: FeeCalculator::default().target_lamports_per_signature,
|
||||
use_move: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,6 +102,11 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
.long("sustained")
|
||||
.help("Use sustained performance mode vs. peak mode. This overlaps the tx generation with transfers."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("use-move")
|
||||
.long("use-move")
|
||||
.help("Use Move language transactions to perform transfers."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("tx_count")
|
||||
.long("tx_count")
|
||||
@@ -211,5 +218,7 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
args.target_lamports_per_signature = v.to_string().parse().expect("can't parse lamports");
|
||||
}
|
||||
|
||||
args.use_move = matches.is_present("use-move");
|
||||
|
||||
args
|
||||
}
|
||||
|
@@ -1,3 +1,7 @@
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate solana_move_loader_program;
|
||||
|
||||
mod bench;
|
||||
mod cli;
|
||||
|
||||
@@ -6,7 +10,7 @@ use crate::bench::{
|
||||
};
|
||||
use solana::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use solana_sdk::signature::Keypair;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
@@ -17,7 +21,7 @@ use std::process::exit;
|
||||
pub const NUM_SIGNATURES_FOR_TXS: u64 = 100_000 * 60 * 60 * 24 * 7;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
solana_logger::setup_with_filter("solana=info");
|
||||
solana_metrics::set_panic_hook("bench-tps");
|
||||
|
||||
let matches = cli::build_args().get_matches();
|
||||
@@ -37,10 +41,11 @@ fn main() {
|
||||
write_to_client_file,
|
||||
read_from_client_file,
|
||||
target_lamports_per_signature,
|
||||
use_move,
|
||||
} = cli_config;
|
||||
|
||||
if write_to_client_file {
|
||||
let keypairs = generate_keypairs(&id, tx_count as u64 * 2);
|
||||
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)
|
||||
@@ -78,7 +83,7 @@ fn main() {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let (keypairs, keypair_balance) = if read_from_client_file {
|
||||
let (keypairs, move_keypairs, keypair_balance) = if read_from_client_file && !use_move {
|
||||
let path = Path::new(&client_ids_and_stake_file);
|
||||
let file = File::open(path).unwrap();
|
||||
|
||||
@@ -91,7 +96,11 @@ fn main() {
|
||||
keypairs.push(Keypair::from_bytes(&bytes).unwrap());
|
||||
last_balance = balance;
|
||||
});
|
||||
(keypairs, last_balance)
|
||||
// Sort keypairs so that do_bench_tps() uses the same subset of accounts for each run.
|
||||
// This prevents the amount of storage needed for bench-tps accounts from creeping up
|
||||
// across multiple runs.
|
||||
keypairs.sort_by(|x, y| x.pubkey().to_string().cmp(&y.pubkey().to_string()));
|
||||
(keypairs, None, last_balance)
|
||||
} else {
|
||||
generate_and_fund_keypairs(
|
||||
&client,
|
||||
@@ -99,6 +108,7 @@ fn main() {
|
||||
&id,
|
||||
tx_count,
|
||||
NUM_LAMPORTS_PER_ACCOUNT,
|
||||
use_move,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("Error could not fund keys: {:?}", e);
|
||||
@@ -113,7 +123,14 @@ fn main() {
|
||||
duration,
|
||||
tx_count,
|
||||
sustained,
|
||||
use_move,
|
||||
};
|
||||
|
||||
do_bench_tps(vec![client], config, keypairs, keypair_balance);
|
||||
do_bench_tps(
|
||||
vec![client],
|
||||
config,
|
||||
keypairs,
|
||||
keypair_balance,
|
||||
move_keypairs,
|
||||
);
|
||||
}
|
||||
|
18
book/art/spv-bank-merkle.bob
Normal file
18
book/art/spv-bank-merkle.bob
Normal file
@@ -0,0 +1,18 @@
|
||||
+------------+
|
||||
| Bank-Merkle|
|
||||
+------------+
|
||||
^ ^
|
||||
/ \
|
||||
+-----------------+ +-------------+
|
||||
| Bank-Diff-Merkle| | Block-Merkle|
|
||||
+-----------------+ +-------------+
|
||||
^ ^
|
||||
/ \
|
||||
+------+ +--------------------------+
|
||||
| Hash | | Previous Bank-Diff-Merkle|
|
||||
+------+ +--------------------------+
|
||||
^ ^
|
||||
/ \
|
||||
+---------------+ +---------------+
|
||||
| Hash(Account1)| | Hash(Account2)|
|
||||
+---------------+ +---------------+
|
19
book/art/spv-block-merkle.bob
Normal file
19
book/art/spv-block-merkle.bob
Normal file
@@ -0,0 +1,19 @@
|
||||
+---------------+
|
||||
| Block-Merkle |
|
||||
+---------------+
|
||||
^ ^
|
||||
/ \
|
||||
+-------------+ +-------------+
|
||||
| Entry-Merkle| | Entry-Merkle|
|
||||
+-------------+ +-------------+
|
||||
^ ^
|
||||
/ \
|
||||
+-------+ +-------+
|
||||
| Hash | | Hash |
|
||||
+-------+ +-------+
|
||||
^ ^ ^ ^
|
||||
/ | | \
|
||||
+-----------------+ +-----------------+ +-----------------+ +---+
|
||||
| Hash(T1, status)| | Hash(T2, status)| | Hash(T3, status)| | 0 |
|
||||
+-----------------+ +-----------------+ +-----------------+ +---+
|
||||
|
@@ -3,4 +3,4 @@ set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
make -j"$(nproc)"
|
||||
make -j"$(nproc)" test
|
||||
|
@@ -4,11 +4,14 @@ MD_SRCS=$(wildcard src/*.md)
|
||||
|
||||
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/img/%.svg) $(MSC_SRCS:art/%.msc=src/img/%.svg)
|
||||
|
||||
all: html/index.html
|
||||
TARGET=html/index.html
|
||||
TEST_STAMP=src/tests.ok
|
||||
|
||||
test: src/tests.ok
|
||||
all: $(TARGET)
|
||||
|
||||
open: all
|
||||
test: $(TEST_STAMP)
|
||||
|
||||
open: $(TEST_STAMP)
|
||||
mdbook build --open
|
||||
|
||||
watch: $(SVG_IMGS)
|
||||
@@ -26,11 +29,11 @@ src/%.md: %.md
|
||||
@mkdir -p $(@D)
|
||||
@cp $< $@
|
||||
|
||||
src/tests.ok: $(SVG_IMGS) $(MD_SRCS)
|
||||
$(TEST_STAMP): $(TARGET)
|
||||
mdbook test
|
||||
touch $@
|
||||
|
||||
html/index.html: src/tests.ok
|
||||
$(TARGET): $(SVG_IMGS) $(MD_SRCS)
|
||||
mdbook build
|
||||
|
||||
clean:
|
||||
|
@@ -30,8 +30,12 @@
|
||||
- [Blocktree](blocktree.md)
|
||||
- [Gossip Service](gossip.md)
|
||||
- [The Runtime](runtime.md)
|
||||
|
||||
- [Anatomy of a Transaction](transaction.md)
|
||||
|
||||
- [API Reference](api-reference.md)
|
||||
- [Transaction](transaction-api.md)
|
||||
- [Instruction](instruction-api.md)
|
||||
- [Blockstreamer](blockstreamer.md)
|
||||
- [JSON RPC API](jsonrpc-api.md)
|
||||
- [JavaScript API](javascript-api.md)
|
||||
@@ -55,18 +59,21 @@
|
||||
- [Economic Design MVP](ed_mvp.md)
|
||||
- [References](ed_references.md)
|
||||
- [Cluster Test Framework](cluster-test-framework.md)
|
||||
- [Credit-only Accounts](credit-only-credit-debit-accounts.md)
|
||||
- [Validator](validator-proposal.md)
|
||||
- [Simple Payment and State Verification](simple-payment-and-state-verification.md)
|
||||
- [Cross-Program Invocation](cross-program-invocation.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)
|
||||
- [Tower BFT](tower-bft.md)
|
||||
- [Leader-to-Leader Transition](leader-leader-transition.md)
|
||||
- [Leader-to-Validator Transition](leader-validator-transition.md)
|
||||
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.md)
|
||||
- [Persistent Account Storage](persistent-account-storage.md)
|
||||
- [Reliable Vote Transmission](reliable-vote-transmission.md)
|
||||
- [Repair Service](repair-service.md)
|
||||
- [Testing Programs](testing-programs.md)
|
||||
- [Testing Programs](testing-programs.md)
|
||||
- [Credit-only Accounts](credit-only-credit-debit-accounts.md)
|
||||
- [Embedding the Move Langauge](embedding-move.md)
|
||||
|
@@ -4,7 +4,7 @@ A validator votes on a PoH hash for two purposes. First, the vote indicates it
|
||||
believes the ledger is valid up until that point in time. Second, since many
|
||||
valid forks may exist at a given height, the vote also indicates exclusive
|
||||
support for the fork. This document describes only the former. The latter is
|
||||
described in [fork selection](fork-selection.md).
|
||||
described in [Tower BFT](tower-bft.md).
|
||||
|
||||
## Current Design
|
||||
|
||||
@@ -50,12 +50,11 @@ log the time since the NewBlock transaction was submitted.
|
||||
|
||||
### Finality and Payouts
|
||||
|
||||
Locktower is the proposed [fork selection](fork-selection.md) algorithm. It
|
||||
proposes that payment to miners be postponed until the *stack* of validator
|
||||
votes reaches a certain depth, at which point rollback is not economically
|
||||
feasible. The vote program may therefore implement locktower. Vote instructions
|
||||
would need to reference a global locktower account so that it can track
|
||||
cross-block state.
|
||||
[Tower BFT](tower-bft.md) is the proposed fork selection algorithm. It proposes
|
||||
that payment to miners be postponed until the *stack* of validator votes reaches
|
||||
a certain depth, at which point rollback is not economically feasible. The vote
|
||||
program may therefore implement Tower BFT. Vote instructions would need to
|
||||
reference a global Tower account so that it can track cross-block state.
|
||||
|
||||
## Challenges
|
||||
|
||||
|
111
book/src/cross-program-invocation.md
Normal file
111
book/src/cross-program-invocation.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Cross-Program Invocation
|
||||
|
||||
## Problem
|
||||
|
||||
In today's implementation a client can create a transaction that modifies two
|
||||
accounts, each owned by a separate on-chain program:
|
||||
|
||||
```rust,ignore
|
||||
let message = Message::new(vec![
|
||||
token_instruction::pay(&alice_pubkey),
|
||||
acme_instruction::launch_missiles(&bob_pubkey),
|
||||
]);
|
||||
client.send_message(&[&alice_keypair, &bob_keypair], &message);
|
||||
```
|
||||
|
||||
The current implementation does not, however, allow the `acme` program to
|
||||
conveniently invoke `token` instructions on the client's behalf:
|
||||
|
||||
```rust,ignore
|
||||
let message = Message::new(vec![
|
||||
acme_instruction::pay_and_launch_missiles(&alice_pubkey, &bob_pubkey),
|
||||
]);
|
||||
client.send_message(&[&alice_keypair, &bob_keypair], &message);
|
||||
```
|
||||
|
||||
Currently, there is no way to create instruction `pay_and_launch_missiles` that executes
|
||||
`token_instruction::pay` from the `acme` program. The workaround is to extend the
|
||||
`acme` program with the implementation of the `token` program, and create `token`
|
||||
accounts with `ACME_PROGRAM_ID`, which the `acme` program is permitted to modify.
|
||||
With that workaround, `acme` can modify token-like accounts created by the `acme`
|
||||
program, but not token accounts created by the `token` program.
|
||||
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
The goal of this design is to modify Solana's runtime such that an on-chain
|
||||
program can invoke an instruction from another program.
|
||||
|
||||
Given two on-chain programs `token` and `acme`, each implementing instructions
|
||||
`pay()` and `launch_missiles()` respectively, we would ideally like to implement
|
||||
the `acme` module with a call to a function defined in the `token` module:
|
||||
|
||||
```rust,ignore
|
||||
use token;
|
||||
|
||||
fn launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||
...
|
||||
}
|
||||
|
||||
fn pay_and_launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||
token::pay(&keyed_accounts[1..])?;
|
||||
|
||||
launch_missiles(keyed_accounts)?;
|
||||
}
|
||||
```
|
||||
|
||||
The above code would require that the `token` crate be dynamically linked,
|
||||
so that a custom linker could intercept calls and validate accesses to
|
||||
`keyed_accounts`. That is, even though the client intends to modify both
|
||||
`token` and `acme` accounts, only `token` program is permitted to modify
|
||||
the `token` account, and only the `acme` program is permitted to modify
|
||||
the `acme` account.
|
||||
|
||||
Backing off from that ideal cross-program call, a slightly more
|
||||
verbose solution is to expose token's existing `process_instruction()`
|
||||
entrypoint to the acme program:
|
||||
|
||||
```rust,ignore
|
||||
use token_instruction;
|
||||
|
||||
fn launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||
...
|
||||
}
|
||||
|
||||
fn pay_and_launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||
let alice_pubkey = keyed_accounts[1].key;
|
||||
let instruction = token_instruction::pay(&alice_pubkey);
|
||||
process_instruction(&instruction)?;
|
||||
|
||||
launch_missiles(keyed_accounts)?;
|
||||
}
|
||||
```
|
||||
|
||||
where `process_instruction()` is built into Solana's runtime and responsible
|
||||
for routing the given instruction to the `token` program via the instruction's
|
||||
`program_id` field. Before invoking `pay()`, the runtime must also ensure that
|
||||
`acme` didn't modify any accounts owned by `token`. It does this by calling
|
||||
`runtime::verify_instruction()` and then afterward updating all the `pre_*`
|
||||
variables to tentatively commit `acme`'s account modifications. After `pay()`
|
||||
completes, the runtime must again ensure that `token` didn't modify any
|
||||
accounts owned by `acme`. It should call `verify_instruction()` again, but this
|
||||
time with the `token` program ID. Lastly, after `pay_and_launch_missiles()`
|
||||
completes, the runtime must call `verify_instruction()` one more time, where it
|
||||
normally would, but using all updated `pre_*` variables. If executing
|
||||
`pay_and_launch_missiles()` up to `pay()` made no invalid account changes,
|
||||
`pay()` made no invalid changes, and executing from `pay()` until
|
||||
`pay_and_launch_missiles()` returns made no invalid changes, then the runtime
|
||||
can transitively assume `pay_and_launch_missiles()` as whole made no invalid
|
||||
account changes, and therefore commit all account modifications.
|
||||
|
||||
### Setting `KeyedAccount.is_signer`
|
||||
|
||||
When `process_instruction()` is invoked, the runtime must create a new
|
||||
`KeyedAccounts` parameter using the signatures from the *original* transaction
|
||||
data. Since the `token` program is immutable and existed on-chain prior to the
|
||||
`acme` program, the runtime can safely treat the transaction signature as a
|
||||
signature of a transaction with a `token` instruction. When the runtime sees
|
||||
the given instruction references `alice_pubkey`, it looks up the key in the
|
||||
transaction to see if that key corresponds to a transaction signature. In this
|
||||
case it does and so sets `KeyedAccount.is_signer`, thereby authorizing the
|
||||
`token` program to modify Alice's account.
|
66
book/src/embedding-move.md
Normal file
66
book/src/embedding-move.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Embedding the Move Language
|
||||
|
||||
## Problem
|
||||
|
||||
Solana enables developers to write on-chain programs in general purpose
|
||||
programming languages such as C or Rust, but those programs contain
|
||||
Solana-specific mechanisms. For example, there isn't another chain that asks
|
||||
developers to create a Rust module with a `process_instruction(KeyedAccounts)`
|
||||
function. Whenever practical, Solana should offer dApp developers more portable
|
||||
options.
|
||||
|
||||
Until just recently, no popular blockchain offered a language that could expose
|
||||
the value of Solana's massively parallel [runtime](runtime.md). Solidity
|
||||
contracts, for example, do not separate references to shared data from contract
|
||||
code, and therefore need to be executed serially to ensure deterministic
|
||||
behavior. In practice we see that the most aggressively optimized EVM-based
|
||||
blockchains all seem to peak out around 1,200 TPS - a small fraction of what
|
||||
Solana can do. The Libra project, on the other hand, designed an on-chain
|
||||
programming language called Move that is more suitable for parallel execution.
|
||||
Like Solana's runtime, Move programs depend on accounts for all shared state.
|
||||
|
||||
The biggest design difference between Solana's runtime and Libra's Move VM is
|
||||
how they manage safe invocations between modules. Solana took an operating
|
||||
systems approach and Libra took the domain-specific language approach. In the
|
||||
runtime, a module must trap back into the runtime to ensure the caller's module
|
||||
did not write to data owned by the callee. Likewise, when the callee completes,
|
||||
it must again trap back to the runtime to ensure the callee did not write to
|
||||
data owned by the caller. Move, on the other hand, includes an advanced type
|
||||
system that allows these checks to be run by its bytecode verifier. Because
|
||||
Move bytecode can be verified, the cost of verification is paid just once, at
|
||||
the time the module is loaded on-chain. In the runtime, the cost is paid each
|
||||
time a transaction crosses between modules. The difference is similar in spirit
|
||||
to the difference between a dynamically-typed language like Python versus a
|
||||
statically-typed language like Java. Solana's runtime allows dApps to be
|
||||
written in general purpose programming languages, but that comes with the cost
|
||||
of runtime checks when jumping between programs.
|
||||
|
||||
This proposal attempts to define a way to embed the Move VM such that:
|
||||
|
||||
* cross-module invocations within Move do not require the runtime's
|
||||
cross-program runtime checks
|
||||
* Move programs can leverage functionality in other Solana programs and vice
|
||||
versa
|
||||
* Solana's runtime parallelism is exposed to batches of Move and non-Move
|
||||
transactions
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
### Move VM as a Solana loader
|
||||
|
||||
The Move VM shall be embedded as a Solana loader under the identifier
|
||||
`MOVE_PROGRAM_ID`, so that Move modules can be marked as `executable` with the
|
||||
VM as its `owner`. This will allow modules to load module dependencies, as well
|
||||
as allow for parallel execution of Move scripts.
|
||||
|
||||
All data accounts owned by Move modules must set their owners to the loader,
|
||||
`MOVE_PROGRAM_ID`. Since Move modules encapsulate their account data in the
|
||||
same way Solana programs encapsulate theirs, the Move module owner should be
|
||||
embedded in the account data. The runtime will grant write access to the Move
|
||||
VM, and Move grants access to the module accounts.
|
||||
|
||||
### Interacting with Solana programs
|
||||
|
||||
To invoke instructions in non-Move programs, Solana would need to extend the
|
||||
Move VM with a `process_instruction()` system call. It would work the same as
|
||||
`process_instruction()` Rust BPF programs.
|
@@ -55,7 +55,7 @@ Validators can ignore forks at other points (e.g. from the wrong leader), or
|
||||
slash the leader responsible for the fork.
|
||||
|
||||
Validators vote based on a greedy choice to maximize their reward described in
|
||||
[forks selection](fork-selection.md).
|
||||
[Tower BFT](tower-bft.md).
|
||||
|
||||
### Validator's View
|
||||
|
||||
|
@@ -22,7 +22,7 @@ gossip endpoint (a socket address).
|
||||
|
||||
Records shared over gossip are arbitrary, but signed and versioned (with a
|
||||
timestamp) as needed to make sense to the node receiving them. If a node
|
||||
recieves two records from the same source, it it updates its own copy with the
|
||||
receives two records from the same source, it updates its own copy with the
|
||||
record with the most recent timestamp.
|
||||
|
||||
## Gossip Service Interface
|
||||
@@ -34,8 +34,8 @@ Nodes send push messages to `PUSH_FANOUT` push peers.
|
||||
|
||||
Upon receiving a push message, a node examines the message for:
|
||||
|
||||
1. Duplication: if the message has been seen before, the node responds with
|
||||
`PushMessagePrune` and drops the message
|
||||
1. Duplication: if the message has been seen before, the node drops the message
|
||||
and may respond with `PushMessagePrune` if forwarded from a low staked node
|
||||
|
||||
2. New data: if the message is new to the node
|
||||
* Stores the new information with an updated version in its cluster info and
|
||||
@@ -51,7 +51,7 @@ Upon receiving a push message, a node examines the message for:
|
||||
A nodes selects its push peers at random from the active set of known peers.
|
||||
The node keeps this selection for a relatively long time. When a prune message
|
||||
is received, the node drops the push peer that sent the prune. Prune is an
|
||||
indication that there is another, faster path to that node than direct push.
|
||||
indication that there is another, higher stake weighted path to that node than direct push.
|
||||
|
||||
The set of push peers is kept fresh by rotating a new node into the set every
|
||||
`PUSH_MSG_TIMEOUT/2` milliseconds.
|
||||
|
@@ -130,7 +130,7 @@ FLAGS:
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-c, --config <PATH> Configuration file to use [default: /Users/mvines/Library/Preferences/solana/install.yml]
|
||||
-c, --config <PATH> Configuration file to use [default: .../Library/Preferences/solana/install.yml]
|
||||
|
||||
SUBCOMMANDS:
|
||||
deploy deploys a new update
|
||||
@@ -152,8 +152,8 @@ FLAGS:
|
||||
-h, --help Prints help information
|
||||
|
||||
OPTIONS:
|
||||
-d, --data_dir <PATH> Directory to store install data [default: /Users/mvines/Library/Application Support/solana]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster [default: https://api.testnet.solana.com/]
|
||||
-d, --data_dir <PATH> Directory to store install data [default: .../Library/Application Support/solana]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster [default: http://testnet.solana.com:8899]
|
||||
-p, --pubkey <PUBKEY> Public key of the update manifest [default: 9XX329sPuskWhH4DQh6k16c87dHKhXLBZTL3Gxmve8Gp]
|
||||
```
|
||||
|
||||
|
25
book/src/instruction-api.md
Normal file
25
book/src/instruction-api.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Instructions
|
||||
|
||||
For the purposes of building a [Transaction](transaction.md), a more
|
||||
verbose instruction format is used:
|
||||
|
||||
* **Instruction:**
|
||||
* **program_id:** The pubkey of the on-chain program that executes the
|
||||
instruction
|
||||
* **accounts:** An ordered list of accounts that should be passed to
|
||||
the program processing the instruction, including metadata detailing
|
||||
if an account is a signer of the transaction and if it is a credit
|
||||
only account.
|
||||
* **data:** A byte array that is passed to the program executing the
|
||||
instruction
|
||||
|
||||
A more compact form is actually included in a `Transaction`:
|
||||
|
||||
* **CompiledInstruction:**
|
||||
* **program_id_index:** The index of the `program_id` in the
|
||||
`account_keys` list
|
||||
* **accounts:** An ordered list of indices into `account_keys`
|
||||
specifying the accounds that should be passed to the program
|
||||
processing the instruction.
|
||||
* **data:** A byte array that is passed to the program executing the
|
||||
instruction
|
@@ -1,13 +1,13 @@
|
||||
# What is Solana?
|
||||
|
||||
Solana is the name of an open source project that is implementing a new,
|
||||
Solana is an open source project implementing a new,
|
||||
high-performance, permissionless blockchain. Solana is also the name of a
|
||||
company headquartered in San Francisco that maintains the open source project.
|
||||
|
||||
# About this Book
|
||||
|
||||
This book describes the Solana open source project, a blockchain built from the
|
||||
ground up for scale. The book covers why it's useful, how to use it, how it
|
||||
ground up for scale. The book covers why Solana is useful, how to use it, how it
|
||||
works, and why it will continue to work long after the company Solana closes
|
||||
its doors. The goal of the Solana architecture is to demonstrate there exists a
|
||||
set of software algorithms that when used in combination to implement a
|
||||
|
@@ -25,9 +25,16 @@ Methods
|
||||
* [getAccountInfo](#getaccountinfo)
|
||||
* [getBalance](#getbalance)
|
||||
* [getClusterNodes](#getclusternodes)
|
||||
* [getEpochInfo](#getepochinfo)
|
||||
* [getLeaderSchedule](#getleaderschedule)
|
||||
* [getProgramAccounts](#getprogramaccounts)
|
||||
* [getRecentBlockhash](#getrecentblockhash)
|
||||
* [getSignatureStatus](#getsignaturestatus)
|
||||
* [getSlot](#getslot)
|
||||
* [getSlotLeader](#getslotleader)
|
||||
* [getSlotsPerSegment](#getslotspersegment)
|
||||
* [getStorageTurn](#getstorageturn)
|
||||
* [getStorageTurnRate](#getstorageturnrate)
|
||||
* [getNumBlocksSinceSignatureConfirmation](#getnumblockssincesignatureconfirmation)
|
||||
* [getTransactionCount](#gettransactioncount)
|
||||
* [getTotalSupply](#gettotalsupply)
|
||||
@@ -96,6 +103,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
|
||||
@@ -142,28 +175,73 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
|
||||
|
||||
---
|
||||
|
||||
### getAccountInfo
|
||||
Returns all information associated with the account of provided Pubkey
|
||||
### getEpochInfo
|
||||
Returns information about the current epoch
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Pubkey of account to query, as base-58 encoded string
|
||||
None
|
||||
|
||||
##### Results:
|
||||
The result field will be a JSON object with the following sub fields:
|
||||
The result field will be an object with the following fields:
|
||||
* `epoch`, the current epoch
|
||||
* `slotIndex`, the current slot relative to the start of the current epoch
|
||||
* `slotsInEpoch`, the number of slots in this epoch
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"epoch":3,"slotIndex":126,"slotsInEpoch":256},"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getLeaderSchedule
|
||||
Returns the leader schedule for the current epoch
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
The result field will be an array of leader public keys (as base-58 encoded
|
||||
strings) for each slot in the current epoch
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLeaderSchedule"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":[...],"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getProgramAccounts
|
||||
Returns all accounts owned by the provided program Pubkey
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Pubkey of program, as base-58 encoded string
|
||||
|
||||
##### Results:
|
||||
The result field will be an array of arrays. Each sub array will contain:
|
||||
* `string` - 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}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -216,6 +294,25 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
|
||||
|
||||
-----
|
||||
|
||||
### getSlot
|
||||
Returns the current slot the node is processing
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `u64` - Current slot
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlot"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"1234","id":1}
|
||||
```
|
||||
-----
|
||||
|
||||
### getSlotLeader
|
||||
Returns the current slot leader
|
||||
|
||||
@@ -234,7 +331,67 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
||||
{"jsonrpc":"2.0","result":"ENvAW7JScgYq6o4zKZwewtkzzJgDzuJAFxYasvmEQdpS","id":1}
|
||||
```
|
||||
|
||||
-----
|
||||
----
|
||||
|
||||
### getSlotsPerSegment
|
||||
Returns the current storage segment size in terms of slots
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `u64` - Number of slots in a storage segment
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotsPerSegment"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"1024","id":1}
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### getStorageTurn
|
||||
Returns the current storage turn's blockhash and slot
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
An array consisting of
|
||||
* `string` - a Hash as base-58 encoded string indicating the blockhash of the turn slot
|
||||
* `u64` - the current storage turn slot
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurn"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC", "2048"],"id":1}
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### getStorageTurnRate
|
||||
Returns the current storage turn rate in terms of slots per turn
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `u64` - Number of slots in storage turn
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurnRate"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"1024","id":1}
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### getNumBlocksSinceSignatureConfirmation
|
||||
Returns the current number of blocks since signature has been confirmed.
|
||||
@@ -402,7 +559,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}}
|
||||
```
|
||||
|
||||
---
|
||||
|
@@ -96,7 +96,7 @@ ends up scheduled for the first two epochs because the leader schedule is also
|
||||
generated at slot 0 for the next epoch. The length of the first two epochs can
|
||||
be specified in the genesis block as well. The minimum length of the first
|
||||
epochs must be greater than or equal to the maximum rollback depth as defined in
|
||||
[fork selection](fork-selection.md).
|
||||
[Tower BFT](tower-bft.md).
|
||||
|
||||
## Leader Schedule Generation Algorithm
|
||||
|
||||
|
@@ -74,7 +74,7 @@ The program should have a list of slots which are valid storage mining slots.
|
||||
This list should be maintained by keeping track of slots which are rooted slots in which a significant
|
||||
portion of the network has voted on with a high lockout value, maybe 32-votes old. Every SLOTS\_PER\_SEGMENT
|
||||
number of slots would be added to this set. The program should check that the slot is in this set. The set can
|
||||
be maintained by receiving a AdvertiseStorageRecentBlockHash and checking with its bank/locktower state.
|
||||
be maintained by receiving a AdvertiseStorageRecentBlockHash and checking with its bank/Tower BFT state.
|
||||
|
||||
The program should do a signature verify check on the signature, public key from the transaction submitter and the message of
|
||||
the previous storage epoch PoH value.
|
||||
|
@@ -60,7 +60,7 @@ The read is satisfied by pointing to a memory-mapped location in the
|
||||
|
||||
## Root Forks
|
||||
|
||||
The [fork selection algorithm](fork-selection.md) eventually selects a fork as a
|
||||
[Tower BFT](tower-bft.md) eventually selects a fork as a
|
||||
root fork and the fork is squashed. A squashed/root fork cannot be rolled back.
|
||||
|
||||
When a fork is squashed, all accounts in its parents not already present in the
|
||||
|
172
book/src/simple-payment-and-state-verification.md
Normal file
172
book/src/simple-payment-and-state-verification.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Simple Payment and State Verification
|
||||
|
||||
It is often useful to allow low resourced clients to participate in a Solana
|
||||
cluster. Be this participation economic or contract execution, verification
|
||||
that a client's activity has been accepted by the network is typically
|
||||
expensive. This proposal lays out a mechanism for such clients to confirm that
|
||||
their actions have been committed to the ledger state with minimal resource
|
||||
expenditure and third-party trust.
|
||||
|
||||
## A Naive Approach
|
||||
|
||||
Validators store the signatures of recently confirmed transactions for a short
|
||||
period of time to ensure that they are not processed more than once. Validators
|
||||
provide a JSON RPC endpoint, which clients can use to query the cluster if a
|
||||
transaction has been recently processed. Validators also provide a PubSub
|
||||
notification, whereby a client registers to be notified when a given signature
|
||||
is observed by the validator. While these two mechanisms allow a client to
|
||||
verify a payment, they are not a proof and rely on completely trusting a
|
||||
fullnode.
|
||||
|
||||
We will describe a way to minimize this trust using Merkle Proofs to anchor the
|
||||
fullnode's response in the ledger, allowing the client to confirm on their own
|
||||
that a sufficient number of their preferred validators have confirmed a
|
||||
transaction. Requiring multiple validator attestations further reduces trust in
|
||||
the fullnode, as it increases both the technical and economic difficulty of
|
||||
compromising several other network participants.
|
||||
|
||||
## Light Clients
|
||||
|
||||
A 'light client' is a cluster participant that does not itself run a fullnode.
|
||||
This light client would provide a level of security greater than trusting a
|
||||
remote fullnode, without requiring the light client to spend a lot of resources
|
||||
verifying the ledger.
|
||||
|
||||
Rather than providing transaction signatures directly to a light client, the
|
||||
fullnode instead generates a Merkle Proof from the transaction of interest to
|
||||
the root of a Merkle Tree of all transactions in the including block. This Merkle
|
||||
Root is stored in a ledger entry which is voted on by validators, providing it
|
||||
consensus legitimacy. The additional level of security for a light client depends
|
||||
on an initial canonical set of validators the light client considers to be the
|
||||
stakeholders of the cluster. As that set is changed, the client can update its
|
||||
internal set of known validators with [receipts](#receipts). This may become
|
||||
challenging with a large number of delegated stakes.
|
||||
|
||||
Fullnodes themselves may want to use light client APIs for performance reasons.
|
||||
For example, during the initial launch of a fullnode, the fullnode may use a
|
||||
cluster provided checkpoint of the state and verify it with a receipt.
|
||||
|
||||
## Receipts
|
||||
|
||||
A receipt is a minimal proof that; a transaction has been included in a block,
|
||||
that the block has been voted on by the client's preferred set of validators and
|
||||
that the votes have reached the desired confirmation depth.
|
||||
|
||||
The receipts for both state and payments start with a Merkle Path from the
|
||||
value into a Bank-Merkle that has been voted on and included in the ledger. A
|
||||
chain of PoH Entries containing subsequent validator votes, deriving from the
|
||||
Bank-Merkle, is the confirmation proof.
|
||||
|
||||
Clients can examine this ledger data and compute the finality using Solana's fork
|
||||
selection rules.
|
||||
|
||||
### Payment Merkle Path
|
||||
|
||||
A payment receipt is a data structure that contains a Merkle Path from a
|
||||
transaction to the required set of validator votes.
|
||||
|
||||
An Entry-Merkle is a Merkle Root including all transactions in the entry, sorted
|
||||
by signature.
|
||||
|
||||
<img alt="Block Merkle Diagram" src="img/spv-block-merkle.svg" class="center"/>
|
||||
|
||||
A Block-Merkle is a Merkle root of all the Entry-Merkles sequenced in the block.
|
||||
Transaction status is necessary for the receipt because the state receipt is
|
||||
constructed for the block. Two transactions over the same state can appear in
|
||||
the block, and therefore, there is no way to infer from just the state whether a
|
||||
transaction that is committed to the ledger has succeeded or failed in modifying
|
||||
the intended state. It may not be necessary to encode the full status code, but
|
||||
a single status bit to indicate the transaction's success.
|
||||
|
||||
### State Merkle Path
|
||||
|
||||
A state receipt provides a confirmation that a specific state is committed at the
|
||||
end of the block. Inter-block state transitions do not generate a receipt.
|
||||
|
||||
For example:
|
||||
|
||||
* A sends 5 Lamports to B
|
||||
* B spends 5 Lamports
|
||||
* C sends 5 Lamports to A
|
||||
|
||||
At the end of the block, A and B are in the exact same starting state, and any
|
||||
state receipt would point to the same value for A or B.
|
||||
|
||||
The Bank-Merkle is computed from the Merkle Tree of the new state changes, along
|
||||
with the Previous Bank-Merkle, and the Block-Merkle.
|
||||
|
||||
<img alt="Bank Merkle Diagram" src="img/spv-bank-merkle.svg" class="center"/>
|
||||
|
||||
A state receipt contains only the state changes occurring in the block. A direct
|
||||
Merkle Path to the current Bank-Merkle guarantees the state value at that bank
|
||||
hash, but it cannot be used to generate a “current” receipt to the latest state
|
||||
if the state modification occurred in some previous block. There is no guarantee
|
||||
that the path provided by the validator is the latest one available out of all
|
||||
the previous Bank-Merkles.
|
||||
|
||||
Clients that want to query the chain for a receipt of the "latest" state would
|
||||
need to create a transaction that would update the Merkle Path for that account,
|
||||
such as a credit of 0 Lamports.
|
||||
|
||||
### Validator Votes
|
||||
|
||||
Leaders should coalesce the validator votes by stake weight into a single entry.
|
||||
This will reduce the number of entries necessary to create a receipt.
|
||||
|
||||
### Chain of Entries
|
||||
|
||||
A receipt has a PoH link from the payment or state Merkle Path root to a list of
|
||||
consecutive validation votes.
|
||||
|
||||
It contains the following:
|
||||
* State -> Bank-Merkle
|
||||
or
|
||||
* Transaction -> Entry-Merkle -> Block-Merkle -> Bank-Merkle
|
||||
|
||||
And a vector of PoH entries:
|
||||
|
||||
* Validator vote entries
|
||||
* Ticks
|
||||
* Light entries
|
||||
|
||||
|
||||
```rust,ignore
|
||||
/// This Entry definition skips over the transactions and only contains the
|
||||
/// hash of the transactions used to modify PoH.
|
||||
LightEntry {
|
||||
/// The number of hashes since the previous Entry ID.
|
||||
pub num_hashes: u64,
|
||||
/// The SHA-256 hash `num_hashes` after the previous Entry ID.
|
||||
hash: Hash,
|
||||
/// The Merkle Root of the transactions encoded into the Entry.
|
||||
entry_hash: Hash,
|
||||
}
|
||||
```
|
||||
|
||||
The light entries are reconstructed from Entries and simply show the entry Merkle
|
||||
Root that was mixed in to the PoH hash, instead of the full transaction set.
|
||||
|
||||
Clients do not need the starting vote state. The [fork selection](book/src/fork-selection.md) algorithm is
|
||||
defined such that only votes that appear after the transaction provide finality
|
||||
for the transaction, and finality is independent of the starting state.
|
||||
|
||||
### Verification
|
||||
|
||||
A light client that is aware of the supermajority set validators can verify a
|
||||
receipt by following the Merkle Path to the PoH chain. The Bank-Merkle is the
|
||||
Merkle Root and will appear in votes included in an Entry. The light client can
|
||||
simulate [fork selection](book/src/fork-selection.md) for the consecutive votes
|
||||
and verify that the receipt is confirmed at the desired lockout threshold.
|
||||
|
||||
### Synthetic State
|
||||
|
||||
Synthetic state should be computed into the Bank-Merkle along with the bank
|
||||
generated state.
|
||||
|
||||
For example:
|
||||
|
||||
* Epoch validator accounts and their stakes and weights.
|
||||
* Computed fee rates
|
||||
|
||||
These values should have an entry in the Bank-Merkle. They should live under
|
||||
known accounts, and therefore have an exact address in the Merkle Path.
|
@@ -11,7 +11,7 @@ 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.
|
||||
|
||||
## Basic Besign
|
||||
## Basic Design
|
||||
|
||||
The general idea is that the validator owns a Vote account. The Vote account
|
||||
tracks validator votes, counts validator generated credits, and provides any
|
||||
@@ -20,7 +20,7 @@ stakes delegated to it and has no staking weight.
|
||||
|
||||
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
|
||||
lamports staked. The Stake account is owned by the staker only. Some portion of the lamports
|
||||
stored in this account are the stake.
|
||||
|
||||
## Passive Delegation
|
||||
@@ -31,7 +31,7 @@ the Vote account or submitting votes to the account.
|
||||
|
||||
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`.
|
||||
`StakeState::Stake::voter_pubkey`.
|
||||
|
||||
## Vote and Stake accounts
|
||||
|
||||
@@ -46,15 +46,15 @@ program that its delegate has participated in validating the ledger.
|
||||
VoteState is the current state of all the votes the validator has submitted to
|
||||
the network. VoteState contains the following state information:
|
||||
|
||||
* votes - The submitted votes data structure.
|
||||
* `votes` - The submitted votes data structure.
|
||||
|
||||
* credits - The total number of rewards this vote program has generated over its
|
||||
* `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
|
||||
* `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
|
||||
* `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
|
||||
@@ -71,13 +71,17 @@ count as stakes.
|
||||
### VoteInstruction::AuthorizeVoteSigner(Pubkey)
|
||||
|
||||
* `account[0]` - RW - The VoteState
|
||||
`VoteState::authorized_vote_signer` is set to to `Pubkey`, instruction must by
|
||||
signed by Pubkey
|
||||
`VoteState::authorized_vote_signer` is set to to `Pubkey`, the transaction must by
|
||||
signed by the Vote account's current `authorized_vote_signer`. <br>
|
||||
`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.
|
||||
|
||||
|
||||
### 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)
|
||||
`VoteState::lockouts` and `VoteState::credits` are updated according to voting lockout rules see [Tower BFT](tower-bft.md)
|
||||
|
||||
|
||||
* `account[1]` - RO - A list of some N most recent slots and their hashes for the vote to be verified against.
|
||||
@@ -85,14 +89,16 @@ count as stakes.
|
||||
|
||||
### StakeState
|
||||
|
||||
A StakeState takes one of two forms, StakeState::Delegate and StakeState::MiningPool.
|
||||
A StakeState takes one of three forms, StakeState::Uninitialized, StakeState::Stake and StakeState::RewardsPool.
|
||||
|
||||
### StakeState::Delegate
|
||||
### StakeState::Stake
|
||||
|
||||
StakeState is the current delegation preference of the **staker**. StakeState
|
||||
StakeState::Stake is the current delegation preference of the **staker** and
|
||||
contains the following state information:
|
||||
|
||||
* Account::lamports - The staked lamports.
|
||||
* Account::lamports - The lamports available for staking.
|
||||
|
||||
* `stake` - the staked amount (subject to warm up and cool down) for generating rewards, always less than or equal to Account::lamports
|
||||
|
||||
* `voter_pubkey` - The pubkey of the VoteState instance the lamports are
|
||||
delegated to.
|
||||
@@ -100,56 +106,53 @@ delegated to.
|
||||
* `credits_observed` - The total credits claimed over the lifetime of the
|
||||
program.
|
||||
|
||||
### StakeState::MiningPool
|
||||
* `activated` - the epoch at which this stake was activated/delegated. The full stake will be counted after warm up.
|
||||
|
||||
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.
|
||||
* `deactivated` - the epoch at which this stake will be completely de-activated, which is `cool down` epochs after StakeInstruction::Deactivate is issued.
|
||||
|
||||
* 256 StakeState::MiningPool are initialized, each with 1/256 number of mining pool
|
||||
tokens stored as `Account::lamports`.
|
||||
### StakeState::RewardsPool
|
||||
|
||||
The stakes and the MiningPool are accounts that are owned by the same `Stake`
|
||||
program.
|
||||
To avoid a single network wide lock or contention in redemption, 256 RewardsPools are part of genesis under pre-determined keys, each with std::u64::MAX credits to be able to satisfy redemptions according to point value.
|
||||
|
||||
### StakeInstruction::Initialize
|
||||
The Stakes and the RewardsPool are accounts that are owned by the same `Stake` program.
|
||||
|
||||
* `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]`
|
||||
### StakeInstruction::DelegateStake(u64)
|
||||
|
||||
The Stake account is moved from Uninitialized to StakeState::Stake form. This is
|
||||
how stakers choose their initial delegate validator node and activate their
|
||||
stake account lamports.
|
||||
|
||||
* `account[0]` - RW - The StakeState::Stake instance. <br>
|
||||
`StakeState::Stake::credits_observed` is initialized to `VoteState::credits`,<br>
|
||||
`StakeState::Stake::voter_pubkey` is initialized to `account[1]`,<br>
|
||||
`StakeState::Stake::stake` is initialized to the u64 passed as an argument above,<br>
|
||||
`StakeState::Stake::activated` is initialized to current Bank epoch, and<br>
|
||||
`StakeState::Stake::deactivated` is initialized to std::u64::MAX
|
||||
|
||||
* `account[1]` - R - The VoteState instance.
|
||||
|
||||
* `account[2]` - R - syscall::current account, carries information about current Bank epoch
|
||||
|
||||
### StakeInstruction::RedeemVoteCredits
|
||||
|
||||
The Staker or the owner of the Stake account sends a transaction with this
|
||||
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`.
|
||||
The Vote account and the Stake account pair maintain a lifetime counter of total
|
||||
rewards generated and claimed. Rewards are paid according to a point value
|
||||
supplied by the Bank from inflation. A `point` is one credit * one staked
|
||||
lamport, rewards paid are proportional to the number of lamports staked.
|
||||
|
||||
|
||||
* `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`
|
||||
* `account[0]` - RW - The StakeState::Stake instance that is redeeming rewards.
|
||||
* `account[1]` - R - The VoteState instance, must be the same as `StakeState::voter_pubkey`
|
||||
* `account[2]` - RW - The StakeState::RewardsPool instance that will fulfill the request (picked at random).
|
||||
* `account[3]` - R - syscall::rewards account from the Bank that carries point value.
|
||||
|
||||
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
|
||||
`StakeState::Stake::credits_observed`, multiplied by `syscall::rewards::Rewards::validator_point_value`.
|
||||
`StakeState::Stake::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;
|
||||
@@ -157,24 +160,26 @@ 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
|
||||
### StakeInstruction::Deactivate
|
||||
A staker may wish to withdraw from the network. To do so he must first deactivate his stake, and wait for cool down.
|
||||
|
||||
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.
|
||||
* `account[0]` - RW - The StakeState::Stake instance that is deactivating, the transaction must be signed by this key.
|
||||
* `account[1]` - R - syscall::current account from the Bank that carries current epoch
|
||||
|
||||
* 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.
|
||||
StakeState::Stake::deactivated is set to the current epoch + cool down. The account's stake will ramp down to zero by
|
||||
that epoch, and Account::lamports will be available for withdrawal.
|
||||
|
||||
## 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.
|
||||
### StakeInstruction::Withdraw(u64)
|
||||
Lamports build up over time in a Stake account and any excess over activated stake can be withdrawn.
|
||||
|
||||
* `account[0]` - RW - The StakeState::Stake from which to withdraw, the transaction must be signed by this key.
|
||||
* `account[1]` - RW - Account that should be credited with the withdrawn lamports.
|
||||
* `account[2]` - R - syscall::current account from the Bank that carries current epoch, to calculate stake.
|
||||
|
||||
|
||||
## Benefits of the design
|
||||
|
||||
@@ -187,9 +192,6 @@ the staker to be slashed.
|
||||
* 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"/>
|
||||
|
@@ -91,6 +91,10 @@ History](#proof-of-history).
|
||||
The time, i.e. number of [slots](#slot), for which a [leader
|
||||
schedule](#leader-schedule) is valid.
|
||||
|
||||
#### finality
|
||||
|
||||
When nodes representing 2/3rd of the stake have a common [root](#root).
|
||||
|
||||
#### fork
|
||||
|
||||
A [ledger](#ledger) derived from common entries but then diverged.
|
||||
@@ -213,6 +217,15 @@ The public key of a [keypair](#keypair).
|
||||
Storage mining client, stores some part of the ledger enumerated in blocks and
|
||||
submits storage proofs to the chain. Not a full-node.
|
||||
|
||||
#### root
|
||||
|
||||
A [block](#block) or [slot](#slot) that has reached maximum [lockout](#lockout)
|
||||
on a validator. The root is the highest block that is an ancestor of all active
|
||||
forks on a validator. All ancestor blocks of a root are also transitively a
|
||||
root. Blocks that are not an ancestor and not a descendant of the root are
|
||||
excluded from consideration for consensus and can be discarded.
|
||||
|
||||
|
||||
#### runtime
|
||||
|
||||
The component of a [fullnode](#fullnode) responsible for [program](#program)
|
||||
|
@@ -74,8 +74,7 @@ The `solana-install` tool can be used to easily install and upgrade the cluster
|
||||
software on Linux x86_64 and mac OS systems.
|
||||
|
||||
```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
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.5/install/solana-install-init.sh | sh -s
|
||||
```
|
||||
|
||||
Alternatively build the `solana-install` program from source and run the
|
||||
@@ -122,8 +121,11 @@ $ ./scripts/cargo-install-all.sh .
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
|
||||
If building for CUDA, include the `cuda` feature flag as well:
|
||||
If building for CUDA (Linux only), fetch the perf-libs first then include the
|
||||
`cuda` feature flag when building:
|
||||
```bash
|
||||
$ ./fetch-perf-libs.sh
|
||||
$ source ./target/perf-libs/env.sh
|
||||
$ ./scripts/cargo-install-all.sh . cuda
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
@@ -142,31 +144,42 @@ $ solana-gossip --entrypoint testnet.solana.com:8001 spy
|
||||
# Press ^C to exit
|
||||
```
|
||||
|
||||
Now configure a key pair for your validator by running:
|
||||
Now create an identity keypair for your validator by running:
|
||||
```bash
|
||||
$ solana-keygen new -o ~/validator-keypair.json
|
||||
```
|
||||
and airdrop yourself some lamports to get started:
|
||||
```bash
|
||||
$ solana-wallet --keypair ~/validator-keypair.json airdrop 1000
|
||||
```
|
||||
|
||||
Your validator will need a vote account. Create it now with the following
|
||||
commands:
|
||||
```bash
|
||||
$ solana-keygen new -o ~/validator-vote-keypair.json
|
||||
$ VOTE_PUBKEY=$(solana-keygen pubkey ~/validator-vote-keypair.json)
|
||||
$ IDENTITY_PUBKEY=$(solana-keygen pubkey ~/validator-keypair.json)
|
||||
$ solana-wallet create-vote-account "$VOTE_PUBKEY" "$IDENTITY_PUBKEY" 1
|
||||
```
|
||||
|
||||
|
||||
Then use one of the following commands, depending on your installation
|
||||
choice, to start the node:
|
||||
|
||||
If this is a `solana-install`-installation:
|
||||
```bash
|
||||
$ clear-config.sh
|
||||
$ validator.sh --identity ~/validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
|
||||
$ validator.sh --identity ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --ledger ~/validator-config --rpc-port 8899 --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 --voting-keypair ~/validator-vote-keypair.json --ledger ~/validator-config --rpc-port 8899 --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
|
||||
$ NDEBUG=1 USE_INSTALL=1 ./multinode-demo/validator.sh --identity ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --rpc-port 8899 --poll-for-new-genesis-block testnet.solana.com
|
||||
```
|
||||
|
||||
#### Enabling CUDA
|
||||
@@ -186,6 +199,11 @@ By default the validator will dynamically select available network ports in the
|
||||
example, `validator.sh --dynamic-port-range 11000-11010 ...` will restrict the
|
||||
validator to ports 11000-11011.
|
||||
|
||||
#### Limiting ledger size to conserve disk space
|
||||
By default the validator will retain the full ledger. To conserve disk space
|
||||
start the validator with the `--limit-ledger-size`, which will instruct the
|
||||
validator to only retain the last couple hours of ledger.
|
||||
|
||||
### Validator Monitoring
|
||||
When `validator.sh` starts, it will output a validator configuration that looks
|
||||
similar to:
|
||||
@@ -217,12 +235,28 @@ $ solana-wallet show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
|
||||
|
||||
The vote pubkey for the validator can also be found by running:
|
||||
```bash
|
||||
# If this is a `solana-install`-installation run:
|
||||
$ solana-keygen pubkey ~/.local/share/solana/install/active_release/config-local/validator-vote-keypair.json
|
||||
# Otherwise run:
|
||||
$ solana-keygen pubkey ./config-local/validator-vote-keypair.json
|
||||
$ solana-keygen pubkey ~/validator-vote-keypair.json
|
||||
```
|
||||
|
||||
#### Has my validator caught up?
|
||||
After your validator boots, it may take some time to catch up with the cluster.
|
||||
Use the `get-slot` wallet command to view the current slot that the cluster is
|
||||
processing:
|
||||
```bash
|
||||
$ solana-wallet get-slot
|
||||
```
|
||||
|
||||
The current slot that your validator is processing can then been seen with:
|
||||
```bash
|
||||
$ solana-wallet --url http://127.0.0.1:8899 get-slot
|
||||
```
|
||||
|
||||
Until your validator has caught up, it will not be able to vote successfully and
|
||||
stake cannot be delegated to it.
|
||||
|
||||
Also if you find the cluster's slot advancing faster than yours, you will likely
|
||||
never catch up. This typically implies some kind of networking issue between
|
||||
your validator and the rest of the cluster.
|
||||
|
||||
#### Validator Metrics
|
||||
Metrics are available for local monitoring of your validator.
|
||||
@@ -240,3 +274,76 @@ A local InfluxDB and Grafana instance is now running on your machine. Define
|
||||
`start.sh` output and restart your validator.
|
||||
|
||||
Metrics should now be streaming and visible from your local Grafana dashboard.
|
||||
|
||||
#### Timezone For Log Messages
|
||||
Log messages emitted by your validator include a timestamp. When sharing logs
|
||||
with others to help triage issues, that timestamp can cause confusion as it does
|
||||
not contain timezone information.
|
||||
|
||||
To make it easier to compare logs between different sources we request that
|
||||
everybody use Pacific Time on their validator nodes. In Linux this can be
|
||||
accomplished by running:
|
||||
```bash
|
||||
$ sudo ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
|
||||
```
|
||||
|
||||
#### Publishing Validator Info
|
||||
|
||||
You can publish your validator information to the chain to be publicly visible
|
||||
to other users.
|
||||
|
||||
Run the solana-validator-info CLI to populate a validator-info account:
|
||||
```bash
|
||||
$ solana-validator-info publish ~/validator-keypair.json <VALIDATOR_NAME> <VALIDATOR_INFO_ARGS>
|
||||
```
|
||||
Optional fields for VALIDATOR_INFO_ARGS:
|
||||
* Website
|
||||
* Keybase Username
|
||||
* Details
|
||||
|
||||
##### Keybase
|
||||
|
||||
Including a Keybase username allows client applications (like the Solana Network
|
||||
Explorer) to automatically pull in your validator public profile, including
|
||||
cryptographic proofs, brand identity, etc. To connect your validator pubkey with
|
||||
Keybase:
|
||||
|
||||
1. Join https://keybase.io/ and complete the profile for your validator
|
||||
2. Add your validator **identity pubkey** to Keybase:
|
||||
* Create an empty file on your local computer called `validator-<PUBKEY>`
|
||||
* In Keybase, navigate to the Files section, and upload your pubkey file to
|
||||
a `solana` subdirectory in your public folder: `/keybase/public/<KEYBASE_USERNAME>/solana`
|
||||
* To check your pubkey, ensure you can successfully browse to
|
||||
`https://keybase.pub/<KEYBASE_USERNAME>/solana/validator-<PUBKEY>`
|
||||
3. Add or update your `solana-validator-info` with your Keybase username. The
|
||||
CLI will verify the `validator-<PUBKEY>` file
|
||||
|
||||
### Staking
|
||||
When your validator starts it will have no stake, which means it will ineligible to become leader.
|
||||
|
||||
Adding stake can be accomplished by using the `solana-wallet` command. First
|
||||
obtain the public key for your validator's vote account with:
|
||||
```bash
|
||||
$ solana-keygen pubkey ~/validator-config/vote-keypair.json
|
||||
```
|
||||
This will output a base58-encoded value that looks similar to
|
||||
`DhUYZR98qFLLrnHg2HWeGhBQJ9tru7nwdEfYm8L8HdR9`. Then create a stake account
|
||||
keypair with `solana-keygen`:
|
||||
```bash
|
||||
$ solana-keygen new -o ~/validator-config/stake-keypair.json
|
||||
```
|
||||
and use the wallet's `delegate-stake` command to stake your validator with 42 lamports:
|
||||
```bash
|
||||
$ solana-wallet delegate-stake ~/validator-config/stake-keypair.json [VOTE PUBKEY] 42
|
||||
```
|
||||
|
||||
Note that stake changes are applied at Epoch boundaries so it can take an hour
|
||||
or more for the change to take effect.
|
||||
|
||||
Stake can be deactivate by running:
|
||||
```bash
|
||||
$ solana-wallet deactivate-stake ~/validator-config/stake-keypair.json
|
||||
```
|
||||
Note that a stake account may only be used once, so after deactivation use the
|
||||
wallet's `withdraw-stake` command to recover the previously staked lamports.
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Fork Selection
|
||||
# Tower BFT
|
||||
|
||||
This design describes a *Fork Selection* algorithm. It addresses the following
|
||||
problems:
|
||||
This design describes Solana's *Tower BFT* algorithm. It addresses the
|
||||
following problems:
|
||||
|
||||
* Some forks may not end up accepted by the super-majority of the cluster, and
|
||||
voters need to recover from voting on such forks.
|
48
book/src/transaction-api.md
Normal file
48
book/src/transaction-api.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# The Transaction
|
||||
|
||||
### Components of a `Transaction`
|
||||
|
||||
* **Transaction:**
|
||||
* **message:** Defines the transaction
|
||||
* **header:** Details the account types of and signatures required by
|
||||
the transaction
|
||||
* **num_required_signatures:** The total number of signatures
|
||||
required to make the transaction valid.
|
||||
* **num_credit_only_signed_accounts:** The last
|
||||
`num_credit_only_signed_accounts` signatures refer to signing
|
||||
credit only accounts. Credit only accounts can be used concurrently
|
||||
by multiple parallel transactions, but their balance may only be
|
||||
increased, and their account data is read-only.
|
||||
* **num_credit_only_unsigned_accounts:** The last
|
||||
`num_credit_only_unsigned_accounts` pubkeys in `account_keys` refer
|
||||
to non-signing credit only accounts
|
||||
* **account_keys:** List of pubkeys used by the transaction, including
|
||||
by the instructions and for signatures. The first
|
||||
`num_required_signatures` pubkeys must sign the transaction.
|
||||
* **recent_blockhash:** The ID of a recent ledger entry. Validators will
|
||||
reject transactions with a `recent_blockhash` that is too old.
|
||||
* **instructions:** A list of [instructions](instruction.md) that are
|
||||
run sequentially and committed in one atomic transaction if all
|
||||
succeed.
|
||||
* **signatures:** A list of signatures applied to the transaction. The
|
||||
list is always of length `num_required_signatures`, and the signature
|
||||
at index `i` corresponds to the pubkey at index `i` in `account_keys`.
|
||||
The list is initialized with empty signatures (i.e. zeros), and
|
||||
populated as signatures are added.
|
||||
|
||||
### Transaction Signing
|
||||
|
||||
A `Transaction` is signed by using an ed25519 keypair to sign the
|
||||
serialization of the `message`. The resulting signature is placed at the
|
||||
index of `signatures` matching the index of the keypair's pubkey in
|
||||
`account_keys`.
|
||||
|
||||
### Transaction Serialization
|
||||
|
||||
`Transaction`s (and their `message`s) are serialized and deserialized
|
||||
using the [bincode](https://crates.io/crates/bincode) crate with a
|
||||
non-standard vector serialization that uses only one byte for the length
|
||||
if it can be encoded in 7 bits, 2 bytes if it fits in 14 bits, or 3
|
||||
bytes if it requires 15 or 16 bits. The vector serialization is defined
|
||||
by Solana's
|
||||
[short-vec](https://github.com/solana-labs/solana/blob/master/sdk/src/short_vec.rs).
|
43
book/src/transaction.md
Normal file
43
book/src/transaction.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Anatomy of a Transaction
|
||||
|
||||
Transactions encode lists of instructions that are executed
|
||||
sequentially, and only committed if all the instructions complete
|
||||
successfully. All account states are reverted upon the failure of a
|
||||
transaction. Each Transaction details the accounts used, including which
|
||||
must sign and which are credit only, a recent blockhash, the
|
||||
instructions, and any signatures.
|
||||
|
||||
## Accounts and Signatures
|
||||
|
||||
Each transaction explicitly lists all accounts that it needs access to.
|
||||
This includes accounts that are transferring tokens, accounts whose user
|
||||
data is being modified, and the program accounts that are being called
|
||||
by the instructions. Each account that is not an executable program can
|
||||
be marked as a requiring a signature and/or as credit only. All accounts
|
||||
marked as signers must have a valid signature in the transaction's list
|
||||
of signatures before the transaction is considered valid. Any accounts
|
||||
marked as credit only may only have their token value increased, and
|
||||
their user data is read only. Accounts are locked by the runtime,
|
||||
ensuring that they are not modified by a concurrent program while the
|
||||
transaction is running. Credit only accounts can safely be shared, so
|
||||
the runtime will allow multiple concurrent credit only locks on an
|
||||
account.
|
||||
|
||||
## Recent Blockhash
|
||||
|
||||
A Transaction includes a recent blockhash to prevent duplication and to
|
||||
give transactions lifetimes. Any transaction that is completely
|
||||
identical to a previous one is rejected, so adding a newer blockhash
|
||||
allows multiple transactions to repeat the exact same action.
|
||||
Transactions also have lifetimes that are defined by the blockhash, as
|
||||
any transaction whose blockhash is too old will be rejected.
|
||||
|
||||
## Instructions
|
||||
|
||||
Each instruction specifies a single program account (which must be
|
||||
marked executable), a subset of the transaction's accounts that should
|
||||
be passed to the program, and a data byte array instruction that is
|
||||
passed to the program. The program interprets the data array and
|
||||
operates on the accounts specified by the instructions. The program can
|
||||
return successfully, or with an error code. An error return causes the
|
||||
entire transaction to fail immediately.
|
1
chacha-sys/.gitignore
vendored
1
chacha-sys/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/target/
|
||||
/farf/
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-chacha-sys"
|
||||
version = "0.16.1"
|
||||
version = "0.18.0-pre0"
|
||||
description = "Solana chacha-sys"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -9,4 +9,4 @@ license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.37"
|
||||
cc = "1.0.38"
|
||||
|
15
ci/buildkite-release.yml
Normal file
15
ci/buildkite-release.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
# Build steps that run on a release tag
|
||||
#
|
||||
# All the steps in `buildkite.yml` are skipped and we jump directly to the
|
||||
# secondary build steps since it's assumed the commit that was tagged is known
|
||||
# to be good so there's no need to rebuild and retest it.
|
||||
steps:
|
||||
- trigger: "solana-secondary"
|
||||
branches: "!pull/*"
|
||||
async: true
|
||||
build:
|
||||
message: "${BUILDKITE_MESSAGE}"
|
||||
commit: "${BUILDKITE_COMMIT}"
|
||||
branch: "${BUILDKITE_BRANCH}"
|
||||
env:
|
||||
TRIGGERED_BUILDKITE_TAG: "${BUILDKITE_TAG}"
|
@@ -1,16 +1,19 @@
|
||||
#
|
||||
# Build steps that run after the primary pipeline on pushes and tags.
|
||||
# Pull requests to not run these steps.
|
||||
steps:
|
||||
- command: "sdk/docker-solana/build.sh"
|
||||
timeout_in_minutes: 20
|
||||
timeout_in_minutes: 60
|
||||
name: "publish docker"
|
||||
- command: "ci/publish-crate.sh"
|
||||
timeout_in_minutes: 40
|
||||
timeout_in_minutes: 90
|
||||
name: "publish crate"
|
||||
branches: "!master"
|
||||
- command: "ci/publish-bpf-sdk.sh"
|
||||
timeout_in_minutes: 5
|
||||
name: "publish bpf sdk"
|
||||
- command: "ci/publish-tarball.sh"
|
||||
timeout_in_minutes: 25
|
||||
timeout_in_minutes: 60
|
||||
name: "publish tarball"
|
||||
- command: "ci/publish-book.sh"
|
||||
timeout_in_minutes: 15
|
||||
|
@@ -1,10 +1,13 @@
|
||||
# Build steps that run on pushes and pull requests.
|
||||
#
|
||||
# Release tags use buildkite-release.yml instead
|
||||
steps:
|
||||
- command: "ci/shellcheck.sh"
|
||||
name: "shellcheck"
|
||||
timeout_in_minutes: 5
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-checks.sh"
|
||||
name: "checks"
|
||||
timeout_in_minutes: 15
|
||||
timeout_in_minutes: 35
|
||||
- wait
|
||||
- command: "ci/test-stable-perf.sh"
|
||||
name: "stable-perf"
|
||||
|
@@ -1,6 +1,10 @@
|
||||
# Note: when the rust version is changed also modify
|
||||
# ci/rust-version.sh to pick up the new image tag
|
||||
FROM rust:1.35.0
|
||||
FROM rust:1.36.0
|
||||
|
||||
# Add Google Protocol Buffers for Libra's metrics library.
|
||||
ENV PROTOC_VERSION 3.8.0
|
||||
ENV PROTOC_ZIP protoc-$PROTOC_VERSION-linux-x86_64.zip
|
||||
|
||||
RUN set -x \
|
||||
&& apt update \
|
||||
@@ -20,6 +24,8 @@ RUN set -x \
|
||||
mscgen \
|
||||
rsync \
|
||||
sudo \
|
||||
golang \
|
||||
unzip \
|
||||
\
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rustup component add rustfmt \
|
||||
@@ -28,4 +34,8 @@ RUN set -x \
|
||||
&& cargo install svgbob_cli \
|
||||
&& cargo install mdbook \
|
||||
&& rustc --version \
|
||||
&& cargo --version
|
||||
&& cargo --version \
|
||||
&& curl -OL https://github.com/google/protobuf/releases/download/v$PROTOC_VERSION/$PROTOC_ZIP \
|
||||
&& unzip -o $PROTOC_ZIP -d /usr/local bin/protoc \
|
||||
&& unzip -o $PROTOC_ZIP -d /usr/local include/* \
|
||||
&& rm -f $PROTOC_ZIP
|
||||
|
@@ -59,7 +59,7 @@ if [[ -n $CI ]]; then
|
||||
fi
|
||||
if [[ $CI_LINUX = True ]]; then
|
||||
export CI_OS_NAME=linux
|
||||
elif [[ $CI_WINDOWS = True ]]; then
|
||||
else
|
||||
export CI_OS_NAME=windows
|
||||
fi
|
||||
export CI_REPO_SLUG=$APPVEYOR_REPO_NAME
|
||||
|
@@ -5,7 +5,6 @@ skipSetup=false
|
||||
iterations=1
|
||||
restartInterval=never
|
||||
rollingRestart=false
|
||||
maybeNoLeaderRotation=
|
||||
extraNodes=0
|
||||
walletRpcPort=:8899
|
||||
|
||||
@@ -54,9 +53,6 @@ while getopts "ch?i:k:brxR" opt; do
|
||||
k)
|
||||
restartInterval=$OPTARG
|
||||
;;
|
||||
b)
|
||||
maybeNoLeaderRotation="--stake 0"
|
||||
;;
|
||||
x)
|
||||
extraNodes=$((extraNodes + 1))
|
||||
;;
|
||||
@@ -78,24 +74,25 @@ source scripts/configure-metrics.sh
|
||||
nodes=(
|
||||
"multinode-demo/drone.sh"
|
||||
"multinode-demo/bootstrap-leader.sh \
|
||||
--enable-rpc-exit \
|
||||
--no-restart \
|
||||
--init-complete-file init-complete-node1.log"
|
||||
--init-complete-file init-complete-node1.log \
|
||||
--dynamic-port-range 8000-8019"
|
||||
"multinode-demo/validator.sh \
|
||||
$maybeNoLeaderRotation \
|
||||
--enable-rpc-exit \
|
||||
--no-restart \
|
||||
--dynamic-port-range 8020-8039
|
||||
--init-complete-file init-complete-node2.log \
|
||||
--rpc-port 18899"
|
||||
)
|
||||
|
||||
for i in $(seq 1 $extraNodes); do
|
||||
portStart=$((8040 + i * 20))
|
||||
portEnd=$((portStart + 19))
|
||||
nodes+=(
|
||||
"multinode-demo/validator.sh \
|
||||
--no-restart \
|
||||
--dynamic-port-range $portStart-$portEnd
|
||||
--label dyn$i \
|
||||
--init-complete-file init-complete-node$((2 + i)).log \
|
||||
$maybeNoLeaderRotation"
|
||||
--init-complete-file init-complete-node$((2 + i)).log"
|
||||
)
|
||||
done
|
||||
numNodes=$((2 + extraNodes))
|
||||
@@ -125,21 +122,26 @@ startNode() {
|
||||
echo "log: $log"
|
||||
}
|
||||
|
||||
waitForNodeToInit() {
|
||||
declare initCompleteFile=$1
|
||||
while [[ ! -r $initCompleteFile ]]; do
|
||||
if [[ $SECONDS -ge 240 ]]; then
|
||||
echo "^^^ +++"
|
||||
echo "Error: $initCompleteFile not found in $SECONDS seconds"
|
||||
exit 1
|
||||
fi
|
||||
echo "Waiting for $initCompleteFile ($SECONDS)..."
|
||||
sleep 2
|
||||
done
|
||||
echo "Found $initCompleteFile"
|
||||
}
|
||||
|
||||
initCompleteFiles=()
|
||||
waitForAllNodesToInit() {
|
||||
echo "--- ${#initCompleteFiles[@]} nodes booting"
|
||||
SECONDS=
|
||||
for initCompleteFile in "${initCompleteFiles[@]}"; do
|
||||
while [[ ! -r $initCompleteFile ]]; do
|
||||
if [[ $SECONDS -ge 240 ]]; then
|
||||
echo "^^^ +++"
|
||||
echo "Error: $initCompleteFile not found in $SECONDS seconds"
|
||||
exit 1
|
||||
fi
|
||||
echo "Waiting for $initCompleteFile ($SECONDS)..."
|
||||
sleep 2
|
||||
done
|
||||
echo "Found $initCompleteFile"
|
||||
waitForNodeToInit "$initCompleteFile"
|
||||
done
|
||||
echo "All nodes finished booting in $SECONDS seconds"
|
||||
}
|
||||
@@ -162,6 +164,13 @@ startNodes() {
|
||||
if $addLogs; then
|
||||
logs+=("$(getNodeLogFile "$i" "$cmd")")
|
||||
fi
|
||||
|
||||
# 1 == bootstrap leader, wait until it boots before starting
|
||||
# other validators
|
||||
if [[ "$i" -eq 1 ]]; then
|
||||
SECONDS=
|
||||
waitForNodeToInit "$initCompleteFile"
|
||||
fi
|
||||
done
|
||||
|
||||
waitForAllNodesToInit
|
||||
@@ -261,7 +270,7 @@ verifyLedger() {
|
||||
(
|
||||
source multinode-demo/common.sh
|
||||
set -x
|
||||
$solana_ledger_tool --ledger "$SOLANA_CONFIG_DIR"/$ledger-ledger verify
|
||||
$solana_ledger_tool --ledger "$SOLANA_CONFIG_DIR"/$ledger verify
|
||||
) || flag_error
|
||||
done
|
||||
}
|
||||
@@ -300,7 +309,6 @@ else
|
||||
fi
|
||||
startNodes
|
||||
lastTransactionCount=
|
||||
enforceTransactionCountAdvance=true
|
||||
while [[ $iteration -le $iterations ]]; do
|
||||
echo "--- Node count ($iteration)"
|
||||
(
|
||||
@@ -336,36 +344,20 @@ while [[ $iteration -le $iterations ]]; do
|
||||
transactionCount=$(sed -e 's/{"jsonrpc":"2.0","result":\([0-9]*\),"id":1}/\1/' log-transactionCount.txt)
|
||||
if [[ -n $lastTransactionCount ]]; then
|
||||
echo "--- Transaction count check: $lastTransactionCount < $transactionCount"
|
||||
if $enforceTransactionCountAdvance; then
|
||||
if [[ $lastTransactionCount -ge $transactionCount ]]; then
|
||||
echo "Error: Transaction count is not advancing"
|
||||
echo "* lastTransactionCount: $lastTransactionCount"
|
||||
echo "* transactionCount: $transactionCount"
|
||||
flag_error
|
||||
fi
|
||||
else
|
||||
echo "enforceTransactionCountAdvance=false"
|
||||
if [[ $lastTransactionCount -ge $transactionCount ]]; then
|
||||
echo "Error: Transaction count is not advancing"
|
||||
echo "* lastTransactionCount: $lastTransactionCount"
|
||||
echo "* transactionCount: $transactionCount"
|
||||
flag_error
|
||||
fi
|
||||
enforceTransactionCountAdvance=true
|
||||
fi
|
||||
lastTransactionCount=$transactionCount
|
||||
|
||||
echo "--- Wallet sanity ($iteration)"
|
||||
flag_error_if_no_leader_rotation() {
|
||||
# TODO: Stop ignoring wallet sanity failures when leader rotation is enabled
|
||||
# once https://github.com/solana-labs/solana/issues/2474 is fixed
|
||||
if [[ -n $maybeNoLeaderRotation ]]; then
|
||||
flag_error
|
||||
else
|
||||
# Wallet error occurred (and was ignored) so transactionCount may not
|
||||
# advance on the next iteration
|
||||
enforceTransactionCountAdvance=false
|
||||
fi
|
||||
}
|
||||
(
|
||||
set -x
|
||||
timeout 60s scripts/wallet-sanity.sh --url http://127.0.0.1"$walletRpcPort"
|
||||
) || flag_error_if_no_leader_rotation
|
||||
) || flag_error
|
||||
|
||||
iteration=$((iteration + 1))
|
||||
|
||||
|
10
ci/nits.sh
10
ci/nits.sh
@@ -46,16 +46,22 @@ if _ git --no-pager grep -n 'Default::default()' -- '*.rs'; then
|
||||
fi
|
||||
|
||||
# Let's keep a .gitignore for every crate, ensure it's got
|
||||
# /target/ in it
|
||||
# /target/ and /farf/ in it
|
||||
declare gitignores_ok=true
|
||||
for i in $(git --no-pager ls-files \*/Cargo.toml ); do
|
||||
dir=$(dirname "$i")
|
||||
if [[ ! -f $dir/.gitignore ]]; then
|
||||
echo 'error: nits.sh .gitnore missing for crate '"$dir" >&2
|
||||
gitignores_ok=false
|
||||
elif ! grep -q -e '^/target/$' "$dir"/.gitignore; then
|
||||
else
|
||||
if ! grep -q -e '^/target/$' "$dir"/.gitignore; then
|
||||
echo 'error: nits.sh "/target/" apparently missing from '"$dir"'/.gitignore' >&2
|
||||
gitignores_ok=false
|
||||
fi
|
||||
if ! grep -q -e '^/farf/$' "$dir"/.gitignore ; then
|
||||
echo 'error: nits.sh "/farf/" apparently missing from '"$dir"'/.gitignore' >&2
|
||||
gitignores_ok=false
|
||||
fi
|
||||
fi
|
||||
done
|
||||
"$gitignores_ok"
|
||||
|
@@ -2,8 +2,50 @@
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
BOOK="book"
|
||||
|
||||
book/build.sh
|
||||
source ci/rust-version.sh stable
|
||||
eval "$(ci/channel-info.sh)"
|
||||
|
||||
if [[ -n $PUBLISH_BOOK_TAG ]]; then
|
||||
CURRENT_TAG="$(git describe --tags)"
|
||||
COMMIT_TO_PUBLISH="$(git rev-list -n 1 "${PUBLISH_BOOK_TAG}")"
|
||||
|
||||
# book is manually published at a specified release tag
|
||||
if [[ $PUBLISH_BOOK_TAG != "$CURRENT_TAG" ]]; then
|
||||
(
|
||||
cat <<EOF
|
||||
steps:
|
||||
- trigger: "$BUILDKITE_PIPELINE_SLUG"
|
||||
async: true
|
||||
build:
|
||||
message: "$BUILDKITE_MESSAGE"
|
||||
commit: "$COMMIT_TO_PUBLISH"
|
||||
env:
|
||||
PUBLISH_BOOK_TAG: "$PUBLISH_BOOK_TAG"
|
||||
EOF
|
||||
) | buildkite-agent pipeline upload
|
||||
exit 0
|
||||
fi
|
||||
repo=git@github.com:solana-labs/book.git
|
||||
else
|
||||
# book-edge and book-beta are published automatically on the tip of the branch
|
||||
case $CHANNEL in
|
||||
edge)
|
||||
repo=git@github.com:solana-labs/book-edge.git
|
||||
;;
|
||||
beta)
|
||||
repo=git@github.com:solana-labs/book-beta.git
|
||||
;;
|
||||
*)
|
||||
echo "--- publish skipped"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
BOOK=$CHANNEL
|
||||
fi
|
||||
|
||||
ci/docker-run.sh "$rust_stable_docker_image" bash -exc "book/build.sh"
|
||||
|
||||
echo --- create book repo
|
||||
(
|
||||
@@ -16,22 +58,7 @@ echo --- create book repo
|
||||
git commit -m "${CI_COMMIT:-local}"
|
||||
)
|
||||
|
||||
eval "$(ci/channel-info.sh)"
|
||||
# Only publish the book from the edge and beta channels for now.
|
||||
case $CHANNEL in
|
||||
edge)
|
||||
repo=git@github.com:solana-labs/book-edge.git
|
||||
;;
|
||||
beta)
|
||||
repo=git@github.com:solana-labs/book.git
|
||||
;;
|
||||
*)
|
||||
echo "--- publish skipped"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "--- publish $CHANNEL"
|
||||
echo "--- publish $BOOK"
|
||||
cd book/html/
|
||||
git remote add origin $repo
|
||||
git fetch origin master
|
||||
|
@@ -2,6 +2,7 @@
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
source ci/semver_bash/semver.sh
|
||||
source ci/rust-version.sh stable
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
is_crate_version_uploaded() {
|
||||
@@ -25,35 +26,56 @@ expectedCrateVersion="$MAJOR.$MINOR.$PATCH$SPECIAL"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cargoCommand="cargo publish --token $CRATES_IO_TOKEN"
|
||||
|
||||
Cargo_tomls=$(ci/order-crates-for-publishing.py)
|
||||
|
||||
for Cargo_toml in $Cargo_tomls; do
|
||||
echo "-- $Cargo_toml"
|
||||
echo "--- $Cargo_toml"
|
||||
grep -q "^version = \"$expectedCrateVersion\"$" "$Cargo_toml" || {
|
||||
echo "Error: $Cargo_toml version is not $expectedCrateVersion"
|
||||
exit 1
|
||||
}
|
||||
|
||||
crate_name=$(grep -m 1 '^name = ' "$Cargo_toml" | cut -f 3 -d ' ' | tr -d \")
|
||||
|
||||
if grep -q "^publish = false" "$Cargo_toml"; then
|
||||
echo "$crate_name is is marked as unpublishable"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ $(is_crate_version_uploaded "$crate_name" "$expectedCrateVersion") = True ]] ; then
|
||||
echo "${crate_name} version ${expectedCrateVersion} is already on crates.io"
|
||||
continue
|
||||
fi
|
||||
|
||||
(
|
||||
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
|
||||
cargoCommand="cargo publish --token $CRATES_IO_TOKEN"
|
||||
ci/docker-run.sh "$rust_stable_docker_image" 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
|
||||
if [[ $(is_crate_version_uploaded "$crate_name" "$expectedCrateVersion") = True ]] ; then
|
||||
echo "Found ${crate_name} version ${expectedCrateVersion} on crates.io REST API"
|
||||
|
||||
really_uploaded=0
|
||||
(
|
||||
set -x
|
||||
rm -rf crate-test
|
||||
cargo +"$rust_stable" init crate-test
|
||||
cd crate-test/
|
||||
echo "${crate_name} = \"${expectedCrateVersion}\"" >> Cargo.toml
|
||||
echo "[workspace]" >> Cargo.toml
|
||||
cargo +"$rust_stable" check
|
||||
) && really_uploaded=1
|
||||
if ((really_uploaded)); then
|
||||
break;
|
||||
fi
|
||||
echo "${crate_name} not yet available for download from crates.io"
|
||||
fi
|
||||
echo "Did not find ${crate_name} version ${expectedCrateVersion} on crates.io. Sleeping for 2 seconds."
|
||||
sleep 2
|
||||
|
@@ -49,7 +49,8 @@ windows)
|
||||
TARGET=x86_64-pc-windows-msvc
|
||||
;;
|
||||
*)
|
||||
TARGET=unknown-unknown-unknown
|
||||
echo CI_OS_NAME unset
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -70,6 +71,12 @@ echo --- Creating tarball
|
||||
source ci/rust-version.sh stable
|
||||
scripts/cargo-install-all.sh +"$rust_stable" solana-release
|
||||
|
||||
# Reduce the archive size until
|
||||
# https://github.com/appveyor/ci/issues/2997 is fixed
|
||||
if [[ -n $APPVEYOR ]]; then
|
||||
rm -f solana-release/bin/solana-validator.exe solana-release/bin/solana-bench-exchange.exe
|
||||
fi
|
||||
|
||||
if $PERF_LIBS; then
|
||||
rm -rf target/perf-libs
|
||||
./fetch-perf-libs.sh
|
||||
@@ -94,22 +101,14 @@ echo --- Creating tarball
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
export USE_INSTALL=1
|
||||
export REQUIRE_LEDGER_DIR=1
|
||||
export REQUIRE_KEYPAIRS=1
|
||||
exec multinode-demo/validator.sh "$@"
|
||||
EOF
|
||||
chmod +x solana-release/bin/validator.sh
|
||||
|
||||
# Add a wrapper script for clear-config.sh
|
||||
# TODO: Remove multinode/... from tarball
|
||||
cat > solana-release/bin/clear-config.sh <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
export USE_INSTALL=1
|
||||
exec multinode-demo/clear-config.sh "$@"
|
||||
EOF
|
||||
chmod +x solana-release/bin/clear-config.sh
|
||||
|
||||
tar jvcf solana-release-$TARGET.tar.bz2 solana-release/
|
||||
tar cvf solana-release-$TARGET.tar solana-release
|
||||
bzip2 solana-release-$TARGET.tar
|
||||
cp solana-release/bin/solana-install-init solana-install-init-$TARGET
|
||||
)
|
||||
|
||||
|
@@ -13,8 +13,8 @@
|
||||
# $ source ci/rust-version.sh
|
||||
#
|
||||
|
||||
stable_version=1.35.0
|
||||
nightly_version=2019-06-20
|
||||
stable_version=1.36.0
|
||||
nightly_version=2019-07-19
|
||||
|
||||
export rust_stable="$stable_version"
|
||||
export rust_stable_docker_image=solanalabs/rust:"$stable_version"
|
||||
|
@@ -10,32 +10,25 @@ 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 --version
|
||||
_ cargo +"$rust_stable" clippy --all -- --deny=warnings
|
||||
_ cargo +"$rust_stable" audit
|
||||
_ cargo +"$rust_stable" audit --version
|
||||
_ cargo +"$rust_stable" audit --ignore RUSTSEC-2019-0011 # https://github.com/solana-labs/solana/issues/5207
|
||||
_ ci/nits.sh
|
||||
_ ci/order-crates-for-publishing.py
|
||||
_ book/build.sh
|
||||
|
||||
for project in sdk/bpf/rust/{rust-no-std,rust-utils,rust-test} programs/bpf/rust/*/ ; do
|
||||
echo "+++ do_bpf_check $project"
|
||||
(
|
||||
cd "$project"
|
||||
_ cargo +"$rust_stable" fmt --all -- --check
|
||||
_ cargo +"$rust_nightly" test --all
|
||||
_ cargo +"$rust_nightly" clippy --version
|
||||
_ cargo +"$rust_nightly" clippy --all -- --deny=warnings
|
||||
_ cargo +"$rust_stable" audit
|
||||
)
|
||||
done
|
||||
|
||||
echo --- ok
|
||||
|
@@ -33,7 +33,7 @@ test-stable)
|
||||
echo "Executing $testName"
|
||||
|
||||
_ cargo +"$rust_stable" build --all ${V:+--verbose}
|
||||
_ cargo +"$rust_stable" test --all ${V:+--verbose} -- --nocapture --test-threads=1
|
||||
_ cargo +"$rust_stable" test --all ${V:+--verbose} -- --nocapture
|
||||
;;
|
||||
test-stable-perf)
|
||||
echo "Executing $testName"
|
||||
@@ -77,7 +77,7 @@ test-stable-perf)
|
||||
|
||||
# Run root package library tests
|
||||
_ cargo +"$rust_stable" build --all ${V:+--verbose} --features="$ROOT_FEATURES"
|
||||
_ cargo +"$rust_stable" test --manifest-path=core/Cargo.toml ${V:+--verbose} --features="$ROOT_FEATURES" -- --nocapture --test-threads=1
|
||||
_ cargo +"$rust_stable" test --manifest-path=core/Cargo.toml ${V:+--verbose} --features="$ROOT_FEATURES" -- --nocapture
|
||||
;;
|
||||
*)
|
||||
echo "Error: Unknown test: $testName"
|
||||
|
@@ -24,6 +24,14 @@ blockstreamer=false
|
||||
deployUpdateManifest=true
|
||||
fetchLogs=true
|
||||
maybeHashesPerTick=
|
||||
maybeDisableAirdrops=
|
||||
maybeInternalNodesStakeLamports=
|
||||
maybeInternalNodesLamports=
|
||||
maybeExternalPrimordialAccountsFile=
|
||||
maybeLamports=
|
||||
maybeLetsEncrypt=
|
||||
maybeFullnodeAdditionalDiskSize=
|
||||
maybeNoSnapshot=
|
||||
|
||||
usage() {
|
||||
exitcode=0
|
||||
@@ -62,11 +70,28 @@ Deploys a CD testnet
|
||||
-s - Skip start. Nodes will still be created or configured, but network software will not be started.
|
||||
-S - Stop network software without tearing down nodes.
|
||||
-f - Discard validator nodes that didn't bootup successfully
|
||||
-w - Skip time-consuming "bells and whistles" that are
|
||||
unnecessary for a high-node count demo testnet
|
||||
|
||||
--no-airdrop
|
||||
- If set, disables airdrops. Nodes must be funded in genesis block when airdrops are disabled.
|
||||
--internal-nodes-stake-lamports NUM_LAMPORTS
|
||||
- Amount to stake internal nodes.
|
||||
--internal-nodes-lamports NUM_LAMPORTS
|
||||
- Amount to fund internal nodes in genesis block
|
||||
--external-accounts-file FILE_PATH
|
||||
- Path to external Primordial Accounts file, if it exists.
|
||||
--hashes-per-tick NUM_HASHES|sleep|auto
|
||||
- Override the default --hashes-per-tick for the cluster
|
||||
--lamports NUM_LAMPORTS
|
||||
- Specify the number of lamports to mint (default 100000000000000)
|
||||
--skip-deploy-update
|
||||
- If set, will skip software update deployment
|
||||
--skip-remote-log-retrieval
|
||||
- If set, will not fetch logs from remote nodes
|
||||
--letsencrypt [dns name]
|
||||
- Attempt to generate a TLS certificate using this DNS name
|
||||
--fullnode-additional-disk-size-gb [number]
|
||||
- Size of additional disk in GB for all fullnodes
|
||||
--no-snapshot
|
||||
- If set, disables booting validators from a snapshot
|
||||
|
||||
Note: the SOLANA_METRICS_CONFIG environment variable is used to configure
|
||||
metrics
|
||||
@@ -82,6 +107,39 @@ while [[ -n $1 ]]; do
|
||||
if [[ $1 = --hashes-per-tick ]]; then
|
||||
maybeHashesPerTick="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 = --lamports ]]; then
|
||||
maybeLamports="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 = --no-airdrop ]]; then
|
||||
maybeDisableAirdrops="$1"
|
||||
shift 1
|
||||
elif [[ $1 = --internal-nodes-stake-lamports ]]; then
|
||||
maybeInternalNodesStakeLamports="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 = --internal-nodes-lamports ]]; then
|
||||
maybeInternalNodesLamports="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 = --external-accounts-file ]]; then
|
||||
maybeExternalPrimordialAccountsFile="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 = --skip-deploy-update ]]; then
|
||||
deployUpdateManifest=false
|
||||
shift 1
|
||||
elif [[ $1 = --skip-remote-log-retrieval ]]; then
|
||||
fetchLogs=false
|
||||
shift 1
|
||||
elif [[ $1 = --letsencrypt ]]; then
|
||||
maybeLetsEncrypt="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 = --fullnode-additional-disk-size-gb ]]; then
|
||||
maybeFullnodeAdditionalDiskSize="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 == --machine-type* ]]; then # Bypass quoted long args for GPUs
|
||||
shortArgs+=("$1")
|
||||
shift
|
||||
elif [[ $1 = --no-snapshot ]]; then
|
||||
maybeNoSnapshot="$1"
|
||||
shift 1
|
||||
else
|
||||
usage "Unknown long option: $1"
|
||||
fi
|
||||
@@ -228,6 +286,11 @@ if ! $skipCreate; then
|
||||
# shellcheck disable=SC2206
|
||||
create_args+=(${zone_args[@]})
|
||||
|
||||
if [[ -n $maybeLetsEncrypt ]]; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote $maybeLetsEncrypt
|
||||
create_args+=($maybeLetsEncrypt)
|
||||
fi
|
||||
|
||||
if $blockstreamer; then
|
||||
create_args+=(-u)
|
||||
fi
|
||||
@@ -256,6 +319,11 @@ if ! $skipCreate; then
|
||||
create_args+=(-f)
|
||||
fi
|
||||
|
||||
if [[ -n $maybeFullnodeAdditionalDiskSize ]]; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote
|
||||
create_args+=($maybeFullnodeAdditionalDiskSize)
|
||||
fi
|
||||
|
||||
time net/"$cloudProvider".sh create "${create_args[@]}"
|
||||
else
|
||||
echo "--- $cloudProvider.sh config"
|
||||
@@ -318,7 +386,6 @@ if ! $skipStart; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote $maybeHashesPerTick
|
||||
args+=($maybeHashesPerTick)
|
||||
fi
|
||||
|
||||
if $reuseLedger; then
|
||||
args+=(-r)
|
||||
fi
|
||||
@@ -334,7 +401,32 @@ if ! $skipStart; then
|
||||
args+=(--deploy-update windows)
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086 # Don't want to double quote the $maybeXYZ variables
|
||||
if [[ -n $maybeDisableAirdrops ]]; then
|
||||
# shellcheck disable=SC2206
|
||||
args+=($maybeDisableAirdrops)
|
||||
fi
|
||||
if [[ -n $maybeInternalNodesStakeLamports ]]; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote $maybeInternalNodesStakeLamports
|
||||
args+=($maybeInternalNodesStakeLamports)
|
||||
fi
|
||||
if [[ -n $maybeInternalNodesLamports ]]; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote $maybeInternalNodesLamports
|
||||
args+=($maybeInternalNodesLamports)
|
||||
fi
|
||||
if [[ -n $maybeExternalPrimordialAccountsFile ]]; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote $maybeExternalPrimordialAccountsFile
|
||||
args+=($maybeExternalPrimordialAccountsFile)
|
||||
fi
|
||||
if [[ -n $maybeLamports ]]; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote $maybeLamports
|
||||
args+=($maybeLamports)
|
||||
fi
|
||||
|
||||
if [[ -n $maybeNoSnapshot ]]; then
|
||||
# shellcheck disable=SC2206
|
||||
args+=($maybeNoSnapshot)
|
||||
fi
|
||||
|
||||
time net/net.sh "${args[@]}"
|
||||
) || ok=false
|
||||
|
||||
|
@@ -44,6 +44,8 @@ steps:
|
||||
value: "testnet-beta-perf"
|
||||
- label: "testnet-demo"
|
||||
value: "testnet-demo"
|
||||
- label: "tds"
|
||||
value: "tds"
|
||||
- select: "Operation"
|
||||
key: "testnet-operation"
|
||||
default: "sanity-or-restart"
|
||||
@@ -140,8 +142,6 @@ testnet-beta|testnet-beta-perf)
|
||||
testnet)
|
||||
CHANNEL_OR_TAG=$STABLE_CHANNEL_LATEST_TAG
|
||||
CHANNEL_BRANCH=$STABLE_CHANNEL
|
||||
: "${EC2_NODE_COUNT:=10}"
|
||||
: "${GCE_NODE_COUNT:=}"
|
||||
;;
|
||||
testnet-perf)
|
||||
CHANNEL_OR_TAG=$STABLE_CHANNEL_LATEST_TAG
|
||||
@@ -153,6 +153,10 @@ testnet-demo)
|
||||
: "${GCE_NODE_COUNT:=150}"
|
||||
: "${GCE_LOW_QUOTA_NODE_COUNT:=70}"
|
||||
;;
|
||||
tds)
|
||||
CHANNEL_OR_TAG=edge
|
||||
CHANNEL_BRANCH=$EDGE_CHANNEL
|
||||
;;
|
||||
*)
|
||||
echo "Error: Invalid TESTNET=$TESTNET"
|
||||
exit 1
|
||||
@@ -197,7 +201,6 @@ steps:
|
||||
TESTNET: "$TESTNET"
|
||||
TESTNET_OP: "$TESTNET_OP"
|
||||
TESTNET_DB_HOST: "$TESTNET_DB_HOST"
|
||||
EC2_NODE_COUNT: "$EC2_NODE_COUNT"
|
||||
GCE_NODE_COUNT: "$GCE_NODE_COUNT"
|
||||
GCE_LOW_QUOTA_NODE_COUNT: "$GCE_LOW_QUOTA_NODE_COUNT"
|
||||
EOF
|
||||
@@ -214,7 +217,7 @@ sanity() {
|
||||
set -x
|
||||
NO_INSTALL_CHECK=1 \
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
ci/testnet-sanity.sh edge-testnet-solana-com ec2 us-west-1a
|
||||
ci/testnet-sanity.sh edge-testnet-solana-com gce us-west1-b
|
||||
)
|
||||
;;
|
||||
testnet-edge-perf)
|
||||
@@ -231,7 +234,7 @@ sanity() {
|
||||
set -x
|
||||
NO_INSTALL_CHECK=1 \
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
ci/testnet-sanity.sh beta-testnet-solana-com ec2 us-west-1a
|
||||
ci/testnet-sanity.sh beta-testnet-solana-com gce us-west1-b
|
||||
)
|
||||
;;
|
||||
testnet-beta-perf)
|
||||
@@ -246,19 +249,9 @@ sanity() {
|
||||
testnet)
|
||||
(
|
||||
set -x
|
||||
|
||||
ok=true
|
||||
if [[ -n $EC2_NODE_COUNT ]]; then
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
ci/testnet-sanity.sh testnet-solana-com ec2 "${EC2_ZONES[0]}" || ok=false
|
||||
elif [[ -n $GCE_NODE_COUNT ]]; then
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
ci/testnet-sanity.sh testnet-solana-com gce "${GCE_ZONES[0]}" || ok=false
|
||||
else
|
||||
echo "Error: no EC2 or GCE nodes"
|
||||
ok=false
|
||||
fi
|
||||
$ok
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
NO_VALIDATOR_SANITY=1 \
|
||||
ci/testnet-sanity.sh testnet-solana-com gce us-west1-b
|
||||
)
|
||||
;;
|
||||
testnet-perf)
|
||||
@@ -287,6 +280,14 @@ sanity() {
|
||||
$ok
|
||||
)
|
||||
;;
|
||||
tds)
|
||||
(
|
||||
set -x
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
NO_VALIDATOR_SANITY=1 \
|
||||
ci/testnet-sanity.sh tds-solana-com gce "${GCE_ZONES[0]}" -f
|
||||
)
|
||||
;;
|
||||
*)
|
||||
echo "Error: Invalid TESTNET=$TESTNET"
|
||||
exit 1
|
||||
@@ -320,8 +321,9 @@ deploy() {
|
||||
testnet-edge)
|
||||
(
|
||||
set -x
|
||||
ci/testnet-deploy.sh -p edge-testnet-solana-com -C ec2 -z us-west-1a \
|
||||
-t "$CHANNEL_OR_TAG" -n 3 -c 0 -u -P -a eipalloc-0ccd4f2239886fa94 \
|
||||
ci/testnet-deploy.sh -p edge-testnet-solana-com -C gce -z us-west1-b \
|
||||
-t "$CHANNEL_OR_TAG" -n 2 -c 0 -u -P \
|
||||
-a edge-testnet-solana-com --letsencrypt edge.testnet.solana.com \
|
||||
${skipCreate:+-e} \
|
||||
${skipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
@@ -346,8 +348,9 @@ deploy() {
|
||||
(
|
||||
set -x
|
||||
NO_VALIDATOR_SANITY=1 \
|
||||
ci/testnet-deploy.sh -p beta-testnet-solana-com -C ec2 -z us-west-1a \
|
||||
-t "$CHANNEL_OR_TAG" -n 3 -c 0 -u -P -a eipalloc-0f286cf8a0771ce35 \
|
||||
ci/testnet-deploy.sh -p beta-testnet-solana-com -C gce -z us-west1-b \
|
||||
-t "$CHANNEL_OR_TAG" -n 2 -c 0 -u -P \
|
||||
-a beta-testnet-solana-com --letsencrypt beta.testnet.solana.com \
|
||||
${skipCreate:+-e} \
|
||||
${skipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
@@ -371,29 +374,14 @@ deploy() {
|
||||
testnet)
|
||||
(
|
||||
set -x
|
||||
|
||||
if [[ -n $GCE_NODE_COUNT ]] || [[ -n $skipStart ]]; then
|
||||
maybeSkipStart="skip"
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2068
|
||||
ci/testnet-deploy.sh -p testnet-solana-com -C ec2 ${EC2_ZONE_ARGS[@]} \
|
||||
-t "$CHANNEL_OR_TAG" -n "$EC2_NODE_COUNT" -c 0 -u -P -f -a eipalloc-0fa502bf95f6f18b2 \
|
||||
${skipCreate:+-e} \
|
||||
${maybeSkipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
${maybeDelete:+-D}
|
||||
|
||||
if [[ -n $GCE_NODE_COUNT ]]; then
|
||||
# shellcheck disable=SC2068
|
||||
ci/testnet-deploy.sh -p testnet-solana-com -C gce ${GCE_ZONE_ARGS[@]} \
|
||||
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P -f \
|
||||
NO_VALIDATOR_SANITY=1 \
|
||||
ci/testnet-deploy.sh -p testnet-solana-com -C gce -z us-west1-b \
|
||||
-t "$CHANNEL_OR_TAG" -n 2 -c 0 -u -P \
|
||||
-a testnet-solana-com --letsencrypt testnet.solana.com \
|
||||
${skipCreate:+-e} \
|
||||
${skipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
${maybeDelete:+-D} \
|
||||
-x
|
||||
fi
|
||||
${maybeDelete:+-D}
|
||||
)
|
||||
;;
|
||||
testnet-perf)
|
||||
@@ -424,7 +412,9 @@ deploy() {
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
NO_VALIDATOR_SANITY=1 \
|
||||
ci/testnet-deploy.sh -p demo-testnet-solana-com -C gce ${GCE_ZONE_ARGS[@]} \
|
||||
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P -u -f -w \
|
||||
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P -u -f \
|
||||
--skip-deploy-update \
|
||||
--skip-remote-log-retrieval \
|
||||
-a demo-testnet-solana-com \
|
||||
${skipCreate:+-e} \
|
||||
${maybeSkipStart:+-s} \
|
||||
@@ -436,7 +426,9 @@ deploy() {
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
NO_VALIDATOR_SANITY=1 \
|
||||
ci/testnet-deploy.sh -p demo-testnet-solana-com2 -C gce ${GCE_LOW_QUOTA_ZONE_ARGS[@]} \
|
||||
-t "$CHANNEL_OR_TAG" -n "$GCE_LOW_QUOTA_NODE_COUNT" -c 0 -P -f -x -w \
|
||||
-t "$CHANNEL_OR_TAG" -n "$GCE_LOW_QUOTA_NODE_COUNT" -c 0 -P -f -x \
|
||||
--skip-deploy-update \
|
||||
--skip-remote-log-retrieval \
|
||||
${skipCreate:+-e} \
|
||||
${skipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
@@ -444,6 +436,124 @@ deploy() {
|
||||
fi
|
||||
)
|
||||
;;
|
||||
tds)
|
||||
(
|
||||
set -x
|
||||
|
||||
# Allow cluster configuration to be overridden from env vars
|
||||
|
||||
if [[ -z $TDS_ZONES ]]; then
|
||||
TDS_ZONES="us-west1-a,us-central1-a,europe-west4-a"
|
||||
fi
|
||||
GCE_CLOUD_ZONES=(); while read -r -d, ; do GCE_CLOUD_ZONES+=( "$REPLY" ); done <<< "${TDS_ZONES},"
|
||||
|
||||
if [[ -z $TDS_NODE_COUNT ]]; then
|
||||
TDS_NODE_COUNT="3"
|
||||
fi
|
||||
|
||||
if [[ -z $TDS_CLIENT_COUNT ]]; then
|
||||
TDS_CLIENT_COUNT="1"
|
||||
fi
|
||||
|
||||
if [[ -z $ENABLE_GPU ]]; then
|
||||
maybeGpu=(-G "--machine-type n1-standard-16 --accelerator count=2,type=nvidia-tesla-v100")
|
||||
elif [[ $ENABLE_GPU == skip ]]; then
|
||||
maybeGpu=()
|
||||
else
|
||||
maybeGpu=(-G "${ENABLE_GPU}")
|
||||
fi
|
||||
|
||||
if [[ -z $HASHES_PER_TICK ]]; then
|
||||
maybeHashesPerTick="--hashes-per-tick auto"
|
||||
elif [[ $HASHES_PER_TICK == skip ]]; then
|
||||
maybeHashesPerTick=""
|
||||
else
|
||||
maybeHashesPerTick="--hashes-per-tick ${HASHES_PER_TICK}"
|
||||
fi
|
||||
|
||||
if [[ -z $DISABLE_AIRDROPS ]]; then
|
||||
DISABLE_AIRDROPS="true"
|
||||
fi
|
||||
|
||||
if [[ $DISABLE_AIRDROPS == true ]] ; then
|
||||
maybeDisableAirdrops="--no-airdrop"
|
||||
else
|
||||
maybeDisableAirdrops=""
|
||||
fi
|
||||
|
||||
if [[ -z $INTERNAL_NODES_STAKE_LAMPORTS ]]; then
|
||||
maybeInternalNodesStakeLamports="--internal-nodes-stake-lamports 1000000000000"
|
||||
elif [[ $INTERNAL_NODES_STAKE_LAMPORTS == skip ]]; then
|
||||
maybeInternalNodesStakeLamports=""
|
||||
else
|
||||
maybeInternalNodesStakeLamports="--internal-nodes-stake-lamports ${INTERNAL_NODES_STAKE_LAMPORTS}"
|
||||
fi
|
||||
|
||||
if [[ -z $INTERNAL_NODES_LAMPORTS ]]; then
|
||||
maybeInternalNodesLamports="--internal-nodes-lamports 2000000000000"
|
||||
elif [[ $INTERNAL_NODES_LAMPORTS == skip ]]; then
|
||||
maybeInternalNodesLamports=""
|
||||
else
|
||||
maybeInternalNodesLamports="--internal-nodes-lamports ${INTERNAL_NODES_LAMPORTS}"
|
||||
fi
|
||||
|
||||
EXTERNAL_ACCOUNTS_FILE=/tmp/validator.yml
|
||||
if [[ -z $EXTERNAL_ACCOUNTS_FILE_URL ]]; then
|
||||
EXTERNAL_ACCOUNTS_FILE_URL=https://raw.githubusercontent.com/solana-labs/tour-de-sol/master/validators/all.yml
|
||||
wget ${EXTERNAL_ACCOUNTS_FILE_URL} -O ${EXTERNAL_ACCOUNTS_FILE}
|
||||
maybeExternalAccountsFile="--external-accounts-file ${EXTERNAL_ACCOUNTS_FILE}"
|
||||
elif [[ $EXTERNAL_ACCOUNTS_FILE_URL == skip ]]; then
|
||||
maybeExternalAccountsFile=""
|
||||
else
|
||||
wget ${EXTERNAL_ACCOUNTS_FILE_URL} -O ${EXTERNAL_ACCOUNTS_FILE}
|
||||
maybeExternalAccountsFile="--external-accounts-file ${EXTERNAL_ACCOUNTS_FILE}"
|
||||
fi
|
||||
|
||||
if [[ -z $LAMPORTS ]]; then
|
||||
maybeLamports="--lamports 8589934592000000000"
|
||||
elif [[ $LAMPORTS == skip ]]; then
|
||||
maybeLamports=""
|
||||
else
|
||||
maybeLamports="--lamports ${LAMPORTS}"
|
||||
fi
|
||||
|
||||
if [[ -z $ADDITIONAL_DISK_SIZE_GB ]]; then
|
||||
maybeAdditionalDisk="--fullnode-additional-disk-size-gb 32000"
|
||||
elif [[ $ADDITIONAL_DISK_SIZE_GB == skip ]]; then
|
||||
maybeAdditionalDisk=""
|
||||
else
|
||||
maybeAdditionalDisk="--fullnode-additional-disk-size-gb ${ADDITIONAL_DISK_SIZE_GB}"
|
||||
fi
|
||||
|
||||
|
||||
# Multiple V100 GPUs are available in us-west1, us-central1 and europe-west4
|
||||
# shellcheck disable=SC2068
|
||||
# shellcheck disable=SC2086
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
NO_VALIDATOR_SANITY=1 \
|
||||
ci/testnet-deploy.sh -p tds-solana-com -C gce \
|
||||
"${maybeGpu[@]}" \
|
||||
-d pd-ssd \
|
||||
${GCE_CLOUD_ZONES[@]/#/-z } \
|
||||
-t "$CHANNEL_OR_TAG" \
|
||||
-n ${TDS_NODE_COUNT} \
|
||||
-c ${TDS_CLIENT_COUNT} \
|
||||
-P -u \
|
||||
-a tds-solana-com --letsencrypt tds.solana.com \
|
||||
${maybeHashesPerTick} \
|
||||
${skipCreate:+-e} \
|
||||
${skipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
${maybeDelete:+-D} \
|
||||
${maybeDisableAirdrops} \
|
||||
${maybeInternalNodesStakeLamports} \
|
||||
${maybeInternalNodesLamports} \
|
||||
${maybeExternalAccountsFile} \
|
||||
${maybeLamports} \
|
||||
${maybeAdditionalDisk} \
|
||||
--skip-deploy-update
|
||||
)
|
||||
;;
|
||||
*)
|
||||
echo "Error: Invalid TESTNET=$TESTNET"
|
||||
exit 1
|
||||
|
1
client/.gitignore
vendored
1
client/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/target/
|
||||
/farf/
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-client"
|
||||
version = "0.16.1"
|
||||
version = "0.18.0-pre0"
|
||||
description = "Solana Client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -11,18 +11,18 @@ edition = "2018"
|
||||
[dependencies]
|
||||
bincode = "1.1.4"
|
||||
bs58 = "0.2.0"
|
||||
jsonrpc-core = "12.0.0"
|
||||
log = "0.4.2"
|
||||
jsonrpc-core = "13.0.0"
|
||||
log = "0.4.8"
|
||||
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.16.1" }
|
||||
solana-sdk = { path = "../sdk", version = "0.16.1" }
|
||||
reqwest = "0.9.19"
|
||||
serde = "1.0.98"
|
||||
serde_derive = "1.0.98"
|
||||
serde_json = "1.0.40"
|
||||
solana-netutil = { path = "../netutil", version = "0.18.0-pre0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.18.0-pre0" }
|
||||
|
||||
[dev-dependencies]
|
||||
jsonrpc-core = "12.0.0"
|
||||
jsonrpc-http-server = "12.0.0"
|
||||
solana-logger = { path = "../logger", version = "0.16.1" }
|
||||
jsonrpc-core = "13.0.0"
|
||||
jsonrpc-http-server = "13.0.0"
|
||||
solana-logger = { path = "../logger", version = "0.18.0-pre0" }
|
||||
|
@@ -274,6 +274,39 @@ impl RpcClient {
|
||||
self.get_account(pubkey).map(|account| account.lamports)
|
||||
}
|
||||
|
||||
pub fn get_program_accounts(&self, pubkey: &Pubkey) -> io::Result<Vec<(Pubkey, Account)>> {
|
||||
let params = json!([format!("{}", pubkey)]);
|
||||
let response = self
|
||||
.client
|
||||
.send(&RpcRequest::GetProgramAccounts, Some(params), 0)
|
||||
.map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("AccountNotFound: pubkey={}: {}", pubkey, err),
|
||||
)
|
||||
})?;
|
||||
|
||||
let accounts: Vec<(String, Account)> =
|
||||
serde_json::from_value::<Vec<(String, Account)>>(response).map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("GetProgramAccounts parse failure: {:?}", err),
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut pubkey_accounts: Vec<(Pubkey, Account)> = Vec::new();
|
||||
for (string, account) in accounts.into_iter() {
|
||||
let pubkey = string.parse().map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("GetProgramAccounts parse failure: {:?}", err),
|
||||
)
|
||||
})?;
|
||||
pubkey_accounts.push((pubkey, account));
|
||||
}
|
||||
Ok(pubkey_accounts)
|
||||
}
|
||||
|
||||
/// Request the transaction count. If the response packet is dropped by the network,
|
||||
/// this method will try again 5 times.
|
||||
pub fn get_transaction_count(&self) -> io::Result<u64> {
|
||||
@@ -443,7 +476,7 @@ impl RpcClient {
|
||||
&self,
|
||||
signature: &Signature,
|
||||
min_confirmed_blocks: usize,
|
||||
) -> io::Result<()> {
|
||||
) -> io::Result<usize> {
|
||||
let mut now = Instant::now();
|
||||
let mut confirmed_blocks = 0;
|
||||
loop {
|
||||
@@ -452,8 +485,11 @@ impl RpcClient {
|
||||
Ok(count) => {
|
||||
if confirmed_blocks != count {
|
||||
info!(
|
||||
"signature {} confirmed {} out of {}",
|
||||
signature, count, min_confirmed_blocks
|
||||
"signature {} confirmed {} out of {} after {} ms",
|
||||
signature,
|
||||
count,
|
||||
min_confirmed_blocks,
|
||||
now.elapsed().as_millis()
|
||||
);
|
||||
now = Instant::now();
|
||||
confirmed_blocks = count;
|
||||
@@ -467,12 +503,23 @@ impl RpcClient {
|
||||
}
|
||||
};
|
||||
if now.elapsed().as_secs() > 15 {
|
||||
// TODO: Return a better error.
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "signature not found"));
|
||||
info!(
|
||||
"signature {} confirmed {} out of {} failed after {} ms",
|
||||
signature,
|
||||
confirmed_blocks,
|
||||
min_confirmed_blocks,
|
||||
now.elapsed().as_millis()
|
||||
);
|
||||
if confirmed_blocks > 0 {
|
||||
return Ok(confirmed_blocks);
|
||||
} else {
|
||||
// TODO: Return a better error.
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "signature not found"));
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_millis(250));
|
||||
sleep(Duration::from_secs(1));
|
||||
}
|
||||
Ok(())
|
||||
Ok(confirmed_blocks)
|
||||
}
|
||||
|
||||
pub fn get_num_blocks_since_signature_confirmation(
|
||||
|
@@ -10,13 +10,15 @@ pub enum RpcRequest {
|
||||
GetBalance,
|
||||
GetClusterNodes,
|
||||
GetNumBlocksSinceSignatureConfirmation,
|
||||
GetProgramAccounts,
|
||||
GetRecentBlockhash,
|
||||
GetSignatureStatus,
|
||||
GetSlot,
|
||||
GetSlotLeader,
|
||||
GetEpochVoteAccounts,
|
||||
GetStorageBlockhash,
|
||||
GetStorageSlot,
|
||||
GetStorageTurn,
|
||||
GetStorageTurnRate,
|
||||
GetSlotsPerSegment,
|
||||
GetStoragePubkeysForSlot,
|
||||
GetTransactionCount,
|
||||
RegisterNode,
|
||||
@@ -38,13 +40,15 @@ impl RpcRequest {
|
||||
RpcRequest::GetNumBlocksSinceSignatureConfirmation => {
|
||||
"getNumBlocksSinceSignatureConfirmation"
|
||||
}
|
||||
RpcRequest::GetProgramAccounts => "getProgramAccounts",
|
||||
RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
|
||||
RpcRequest::GetSignatureStatus => "getSignatureStatus",
|
||||
RpcRequest::GetSlot => "getSlot",
|
||||
RpcRequest::GetSlotLeader => "getSlotLeader",
|
||||
RpcRequest::GetEpochVoteAccounts => "getEpochVoteAccounts",
|
||||
RpcRequest::GetStorageBlockhash => "getStorageBlockhash",
|
||||
RpcRequest::GetStorageSlot => "getStorageSlot",
|
||||
RpcRequest::GetStorageTurn => "getStorageTurn",
|
||||
RpcRequest::GetStorageTurnRate => "getStorageTurnRate",
|
||||
RpcRequest::GetSlotsPerSegment => "getSlotsPerSegment",
|
||||
RpcRequest::GetStoragePubkeysForSlot => "getStoragePubkeysForSlot",
|
||||
RpcRequest::GetTransactionCount => "getTransactionCount",
|
||||
RpcRequest::RegisterNode => "registerNode",
|
||||
|
@@ -16,7 +16,7 @@ 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::timing::{duration_as_ms, MAX_PROCESSING_AGE};
|
||||
use solana_sdk::transaction::{self, Transaction};
|
||||
use solana_sdk::transport::Result as TransportResult;
|
||||
use std::io;
|
||||
@@ -203,20 +203,39 @@ impl ThinClient {
|
||||
keypairs: &[&Keypair],
|
||||
transaction: &mut Transaction,
|
||||
tries: usize,
|
||||
min_confirmed_blocks: usize,
|
||||
pending_confirmations: usize,
|
||||
) -> io::Result<Signature> {
|
||||
for x in 0..tries {
|
||||
let now = Instant::now();
|
||||
let mut buf = vec![0; serialized_size(&transaction).unwrap() as usize];
|
||||
let mut wr = std::io::Cursor::new(&mut buf[..]);
|
||||
let mut num_confirmed = 0;
|
||||
let mut wait_time = MAX_PROCESSING_AGE;
|
||||
serialize_into(&mut wr, &transaction)
|
||||
.expect("serialize Transaction in pub fn transfer_signed");
|
||||
self.transactions_socket
|
||||
.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]);
|
||||
// resend the same transaction until the transaction has no chance of succeeding
|
||||
while now.elapsed().as_secs() < wait_time as u64 {
|
||||
if num_confirmed == 0 {
|
||||
// Send the transaction if there has been no confirmation (e.g. the first time)
|
||||
self.transactions_socket
|
||||
.send_to(&buf[..], &self.transactions_addr())?;
|
||||
}
|
||||
|
||||
if let Ok(confirmed_blocks) = self.poll_for_signature_confirmation(
|
||||
&transaction.signatures[0],
|
||||
pending_confirmations,
|
||||
) {
|
||||
num_confirmed = confirmed_blocks;
|
||||
if confirmed_blocks >= pending_confirmations {
|
||||
return Ok(transaction.signatures[0]);
|
||||
}
|
||||
// Since network has seen the transaction, wait longer to receive
|
||||
// all pending confirmations. Resending the transaction could result into
|
||||
// extra transaction fees
|
||||
wait_time = wait_time.max(
|
||||
MAX_PROCESSING_AGE * pending_confirmations.saturating_sub(num_confirmed),
|
||||
);
|
||||
}
|
||||
}
|
||||
info!(
|
||||
"{} tries failed transfer to {}",
|
||||
@@ -378,7 +397,7 @@ impl SyncClient for ThinClient {
|
||||
&self,
|
||||
signature: &Signature,
|
||||
min_confirmed_blocks: usize,
|
||||
) -> TransportResult<()> {
|
||||
) -> TransportResult<usize> {
|
||||
Ok(self
|
||||
.rpc_client()
|
||||
.poll_for_signature_confirmation(signature, min_confirmed_blocks)?)
|
||||
|
1
core/.gitignore
vendored
1
core/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/target/
|
||||
/farf/
|
||||
|
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "solana"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "0.16.1"
|
||||
version = "0.18.0-pre0"
|
||||
documentation = "https://docs.rs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
readme = "../README.md"
|
||||
@@ -21,55 +21,62 @@ kvstore = ["solana-kvstore"]
|
||||
bincode = "1.1.4"
|
||||
bs58 = "0.2.0"
|
||||
byteorder = "1.3.2"
|
||||
chrono = { version = "0.4.0", features = ["serde"] }
|
||||
chrono = { version = "0.4.7", features = ["serde"] }
|
||||
core_affinity = "0.5.9"
|
||||
crc = { version = "1.8.1", optional = true }
|
||||
crossbeam-channel = "0.3"
|
||||
dir-diff = "0.3.1"
|
||||
flate2 = "1.0.9"
|
||||
fs_extra = "1.1.0"
|
||||
hashbrown = "0.2.0"
|
||||
indexmap = "1.0"
|
||||
itertools = "0.8.0"
|
||||
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"
|
||||
jsonrpc-core = "13.0.0"
|
||||
jsonrpc-derive = "13.0.0"
|
||||
jsonrpc-http-server = "13.0.0"
|
||||
jsonrpc-pubsub = "13.0.0"
|
||||
jsonrpc-ws-server = "13.0.0"
|
||||
libc = "0.2.58"
|
||||
log = "0.4.2"
|
||||
log = "0.4.8"
|
||||
memmap = { version = "0.7.0", optional = true }
|
||||
nix = "0.14.1"
|
||||
num-traits = "0.2"
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
rayon = "1.1.0"
|
||||
reqwest = "0.9.18"
|
||||
reqwest = "0.9.19"
|
||||
rocksdb = "0.11.0"
|
||||
serde = "1.0.92"
|
||||
serde_derive = "1.0.92"
|
||||
serde_json = "1.0.39"
|
||||
solana-budget-api = { path = "../programs/budget_api", version = "0.16.1" }
|
||||
solana-budget-program = { path = "../programs/budget_program", version = "0.16.1" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "0.16.1" }
|
||||
solana-client = { path = "../client", version = "0.16.1" }
|
||||
solana-config-program = { path = "../programs/config_program", version = "0.16.1" }
|
||||
solana-drone = { path = "../drone", version = "0.16.1" }
|
||||
serde = "1.0.98"
|
||||
serde_derive = "1.0.98"
|
||||
serde_json = "1.0.40"
|
||||
solana-budget-api = { path = "../programs/budget_api", version = "0.18.0-pre0" }
|
||||
solana-budget-program = { path = "../programs/budget_program", version = "0.18.0-pre0" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "0.18.0-pre0" }
|
||||
solana-client = { path = "../client", version = "0.18.0-pre0" }
|
||||
solana-drone = { path = "../drone", version = "0.18.0-pre0" }
|
||||
solana-ed25519-dalek = "0.2.0"
|
||||
solana-exchange-program = { path = "../programs/exchange_program", version = "0.16.1" }
|
||||
solana-kvstore = { path = "../kvstore", version = "0.16.1", optional = true }
|
||||
solana-logger = { path = "../logger", version = "0.16.1" }
|
||||
solana-metrics = { path = "../metrics", version = "0.16.1" }
|
||||
solana-netutil = { path = "../netutil", version = "0.16.1" }
|
||||
solana-runtime = { path = "../runtime", version = "0.16.1" }
|
||||
solana-sdk = { path = "../sdk", version = "0.16.1" }
|
||||
solana-stake-api = { path = "../programs/stake_api", version = "0.16.1" }
|
||||
solana-stake-program = { path = "../programs/stake_program", version = "0.16.1" }
|
||||
solana-storage-api = { path = "../programs/storage_api", version = "0.16.1" }
|
||||
solana-storage-program = { path = "../programs/storage_program", version = "0.16.1" }
|
||||
solana-vote-api = { path = "../programs/vote_api", version = "0.16.1" }
|
||||
solana-vote-program = { path = "../programs/vote_program", version = "0.16.1" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "0.16.1" }
|
||||
solana-kvstore = { path = "../kvstore", version = "0.18.0-pre0", optional = true }
|
||||
solana-logger = { path = "../logger", version = "0.18.0-pre0" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "0.18.0-pre0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.18.0-pre0" }
|
||||
solana-measure = { path = "../measure", version = "0.18.0-pre0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.18.0-pre0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.18.0-pre0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.18.0-pre0" }
|
||||
solana-stake-api = { path = "../programs/stake_api", version = "0.18.0-pre0" }
|
||||
solana-storage-api = { path = "../programs/storage_api", version = "0.18.0-pre0" }
|
||||
solana-storage-program = { path = "../programs/storage_program", version = "0.18.0-pre0" }
|
||||
solana-vote-api = { path = "../programs/vote_api", version = "0.18.0-pre0" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "0.18.0-pre0" }
|
||||
symlink = "0.1.0"
|
||||
sys-info = "0.5.7"
|
||||
tar = "0.4.26"
|
||||
tempfile = "3.1.0"
|
||||
tokio = "0.1"
|
||||
tokio-codec = "0.1"
|
||||
untrusted = "0.6.2"
|
||||
tokio-fs = "0.1"
|
||||
tokio-io = "0.1"
|
||||
untrusted = "0.7.0"
|
||||
|
||||
# reed-solomon-erasure's simd_c feature fails to build for x86_64-pc-windows-msvc, use pure-rust
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
@@ -80,6 +87,8 @@ reed-solomon-erasure = "3.1.1"
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2.0"
|
||||
matches = "0.1.6"
|
||||
serial_test = "0.2.0"
|
||||
serial_test_derive = "0.2.0"
|
||||
|
||||
[[bench]]
|
||||
name = "banking_stage"
|
||||
|
@@ -4,6 +4,7 @@ extern crate test;
|
||||
#[macro_use]
|
||||
extern crate solana;
|
||||
|
||||
use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
@@ -17,16 +18,18 @@ use solana::poh_recorder::WorkingBankEntries;
|
||||
use solana::service::Service;
|
||||
use solana::test_tx::test_tx;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::hash::hash;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::Keypair;
|
||||
use solana_sdk::signature::KeypairUtil;
|
||||
use solana_sdk::signature::Signature;
|
||||
use solana_sdk::system_instruction;
|
||||
use solana_sdk::system_transaction;
|
||||
use solana_sdk::timing::{
|
||||
duration_as_us, timestamp, DEFAULT_TICKS_PER_SLOT, MAX_RECENT_BLOCKHASHES,
|
||||
};
|
||||
use solana_sdk::timing::{duration_as_us, timestamp};
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use std::iter;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::{Duration, Instant};
|
||||
use test::Bencher;
|
||||
@@ -76,8 +79,12 @@ fn bench_consume_buffered(bencher: &mut Bencher) {
|
||||
// This tests the performance of buffering packets.
|
||||
// If the packet buffers are copied, performance will be poor.
|
||||
bencher.iter(move || {
|
||||
let _ignored =
|
||||
BankingStage::consume_buffered_packets(&my_pubkey, &poh_recorder, &mut packets);
|
||||
let _ignored = BankingStage::consume_buffered_packets(
|
||||
&my_pubkey,
|
||||
&poh_recorder,
|
||||
&mut packets,
|
||||
10_000,
|
||||
);
|
||||
});
|
||||
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
@@ -86,13 +93,52 @@ fn bench_consume_buffered(bencher: &mut Bencher) {
|
||||
let _unused = Blocktree::destroy(&ledger_path);
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||
fn make_accounts_txs(txes: usize, mint_keypair: &Keypair, hash: Hash) -> Vec<Transaction> {
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
let dummy = system_transaction::transfer(mint_keypair, &to_pubkey, 1, hash);
|
||||
(0..txes)
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let mut new = dummy.clone();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
new.message.account_keys[0] = Pubkey::new_rand();
|
||||
new.message.account_keys[1] = Pubkey::new_rand();
|
||||
new.signatures = vec![Signature::new(&sig[0..64])];
|
||||
new
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn make_programs_txs(txes: usize, hash: Hash) -> Vec<Transaction> {
|
||||
let progs = 4;
|
||||
(0..txes)
|
||||
.into_iter()
|
||||
.map(|_| {
|
||||
let mut instructions = vec![];
|
||||
let from_key = Keypair::new();
|
||||
for _ in 1..progs {
|
||||
let to_key = Pubkey::new_rand();
|
||||
instructions.push(system_instruction::transfer(&from_key.pubkey(), &to_key, 1));
|
||||
}
|
||||
let mut new = Transaction::new_unsigned_instructions(instructions);
|
||||
new.sign(&[&from_key], hash);
|
||||
new
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
enum TransactionType {
|
||||
Accounts,
|
||||
Programs,
|
||||
}
|
||||
|
||||
fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) {
|
||||
solana_logger::setup();
|
||||
let num_threads = BankingStage::num_threads() as usize;
|
||||
// a multiple of packet chunk 2X duplicates to avoid races
|
||||
const CHUNKS: usize = 32;
|
||||
let txes = 192 * num_threads * CHUNKS;
|
||||
// a multiple of packet chunk duplicates to avoid races
|
||||
const CHUNKS: usize = 8;
|
||||
const PACKETS_PER_BATCH: usize = 192;
|
||||
let txes = PACKETS_PER_BATCH * num_threads * CHUNKS;
|
||||
let mint_total = 1_000_000_000_000;
|
||||
let GenesisBlockInfo {
|
||||
mut genesis_block,
|
||||
@@ -104,25 +150,17 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||
// during the benchmark
|
||||
genesis_block.ticks_per_slot = 10_000;
|
||||
|
||||
let (verified_sender, verified_receiver) = channel();
|
||||
let (vote_sender, vote_receiver) = channel();
|
||||
let (verified_sender, verified_receiver) = unbounded();
|
||||
let (vote_sender, vote_receiver) = unbounded();
|
||||
let bank = Arc::new(Bank::new(&genesis_block));
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
let dummy = system_transaction::transfer(&mint_keypair, &to_pubkey, 1, genesis_block.hash());
|
||||
trace!("txs: {}", txes);
|
||||
let transactions: Vec<_> = (0..txes)
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let mut new = dummy.clone();
|
||||
let from: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
let to: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
new.message.account_keys[0] = Pubkey::new(&from[0..32]);
|
||||
new.message.account_keys[1] = Pubkey::new(&to[0..32]);
|
||||
new.signatures = vec![Signature::new(&sig[0..64])];
|
||||
new
|
||||
})
|
||||
.collect();
|
||||
|
||||
debug!("threads: {} txs: {}", num_threads, txes);
|
||||
|
||||
let transactions = match tx_type {
|
||||
TransactionType::Accounts => make_accounts_txs(txes, &mint_keypair, genesis_block.hash()),
|
||||
TransactionType::Programs => make_programs_txs(txes, genesis_block.hash()),
|
||||
};
|
||||
|
||||
// fund all the accounts
|
||||
transactions.iter().for_each(|tx| {
|
||||
let fund = system_transaction::transfer(
|
||||
@@ -146,7 +184,7 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||
assert!(r.is_ok(), "sanity parallel execution");
|
||||
}
|
||||
bank.clear_signatures();
|
||||
let verified: Vec<_> = to_packets_chunked(&transactions.clone(), 192)
|
||||
let verified: Vec<_> = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH)
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let len = x.packets.len();
|
||||
@@ -182,12 +220,13 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||
let now = Instant::now();
|
||||
let mut sent = 0;
|
||||
|
||||
for v in verified[start..start + chunk_len].chunks(verified.len() / num_threads) {
|
||||
trace!(
|
||||
"sending... {}..{} {}",
|
||||
for v in verified[start..start + chunk_len].chunks(chunk_len / num_threads) {
|
||||
debug!(
|
||||
"sending... {}..{} {} v.len: {}",
|
||||
start,
|
||||
start + chunk_len,
|
||||
timestamp()
|
||||
timestamp(),
|
||||
v.len(),
|
||||
);
|
||||
for xv in v {
|
||||
sent += xv.0.packets.len();
|
||||
@@ -197,7 +236,7 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||
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
|
||||
// in this chunk, but since we rotate between CHUNKS then
|
||||
// we should clear them by the time we come around again to re-use that chunk.
|
||||
bank.clear_signatures();
|
||||
trace!(
|
||||
@@ -217,124 +256,11 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||
}
|
||||
|
||||
#[bench]
|
||||
#[ignore]
|
||||
fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
|
||||
let progs = 4;
|
||||
let num_threads = BankingStage::num_threads() as usize;
|
||||
// a multiple of packet chunk 2X duplicates to avoid races
|
||||
let txes = 96 * 100 * num_threads * 2;
|
||||
let mint_total = 1_000_000_000_000;
|
||||
let GenesisBlockInfo {
|
||||
genesis_block,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_block(mint_total);
|
||||
|
||||
let (verified_sender, verified_receiver) = channel();
|
||||
let (vote_sender, vote_receiver) = channel();
|
||||
let bank = Arc::new(Bank::new(&genesis_block));
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
let dummy = system_transaction::transfer(&mint_keypair, &to_pubkey, 1, genesis_block.hash());
|
||||
let transactions: Vec<_> = (0..txes)
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let mut new = dummy.clone();
|
||||
let from: Vec<u8> = (0..32).map(|_| thread_rng().gen()).collect();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
let to: Vec<u8> = (0..32).map(|_| thread_rng().gen()).collect();
|
||||
new.message.account_keys[0] = Pubkey::new(&from[0..32]);
|
||||
new.message.account_keys[1] = Pubkey::new(&to[0..32]);
|
||||
let prog = new.message.instructions[0].clone();
|
||||
for i in 1..progs {
|
||||
//generate programs that spend to random keys
|
||||
let to: Vec<u8> = (0..32).map(|_| thread_rng().gen()).collect();
|
||||
let to_key = Pubkey::new(&to[0..32]);
|
||||
new.message.account_keys.push(to_key);
|
||||
assert_eq!(new.message.account_keys.len(), i + 2);
|
||||
new.message.instructions.push(prog.clone());
|
||||
assert_eq!(new.message.instructions.len(), i + 1);
|
||||
new.message.instructions[i].accounts[1] = 1 + i as u8;
|
||||
assert_eq!(new.key(i, 1), Some(&to_key));
|
||||
assert_eq!(
|
||||
new.message.account_keys[new.message.instructions[i].accounts[1] as usize],
|
||||
to_key
|
||||
);
|
||||
}
|
||||
assert_eq!(new.message.instructions.len(), progs);
|
||||
new.signatures = vec![Signature::new(&sig[0..64])];
|
||||
new
|
||||
})
|
||||
.collect();
|
||||
transactions.iter().for_each(|tx| {
|
||||
let fund = system_transaction::transfer(
|
||||
&mint_keypair,
|
||||
&tx.message.account_keys[0],
|
||||
mint_total / txes as u64,
|
||||
genesis_block.hash(),
|
||||
);
|
||||
bank.process_transaction(&fund).unwrap();
|
||||
});
|
||||
//sanity check, make sure all the transactions can execute sequentially
|
||||
transactions.iter().for_each(|tx| {
|
||||
let res = bank.process_transaction(&tx);
|
||||
assert!(res.is_ok(), "sanity test transactions");
|
||||
});
|
||||
bank.clear_signatures();
|
||||
//sanity check, make sure all the transactions can execute in parallel
|
||||
let res = bank.process_transactions(&transactions);
|
||||
for r in res {
|
||||
assert!(r.is_ok(), "sanity parallel execution");
|
||||
}
|
||||
bank.clear_signatures();
|
||||
let verified: Vec<_> = to_packets_chunked(&transactions.clone(), 96)
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let len = x.packets.len();
|
||||
(x, iter::repeat(1).take(len).collect())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
{
|
||||
let blocktree = Arc::new(
|
||||
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"),
|
||||
);
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blocktree);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let _banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
verified_receiver,
|
||||
vote_receiver,
|
||||
);
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
|
||||
let mut id = genesis_block.hash();
|
||||
for _ in 0..(MAX_RECENT_BLOCKHASHES * DEFAULT_TICKS_PER_SLOT as usize) {
|
||||
id = hash(&id.as_ref());
|
||||
bank.register_tick(&id);
|
||||
}
|
||||
|
||||
let half_len = verified.len() / 2;
|
||||
let mut start = 0;
|
||||
let signal_receiver = Arc::new(signal_receiver);
|
||||
let signal_receiver2 = signal_receiver.clone();
|
||||
bencher.iter(move || {
|
||||
// make sure the transactions are still valid
|
||||
bank.register_tick(&genesis_block.hash());
|
||||
for v in verified[start..start + half_len].chunks(verified.len() / num_threads) {
|
||||
verified_sender.send(v.to_vec()).unwrap();
|
||||
}
|
||||
check_txs(&signal_receiver2, txes / 2);
|
||||
bank.clear_signatures();
|
||||
start += half_len;
|
||||
start %= verified.len();
|
||||
});
|
||||
drop(vote_sender);
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
poh_service.join().unwrap();
|
||||
}
|
||||
Blocktree::destroy(&ledger_path).unwrap();
|
||||
fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||
bench_banking(bencher, TransactionType::Accounts);
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
|
||||
bench_banking(bencher, TransactionType::Programs);
|
||||
}
|
||||
|
@@ -11,12 +11,13 @@ use rand::{thread_rng, Rng};
|
||||
use solana::blocktree::{get_tmp_ledger_path, Blocktree};
|
||||
use solana::entry::{make_large_test_entries, make_tiny_test_entries, EntrySlice};
|
||||
use solana::packet::{Blob, BLOB_HEADER_SIZE};
|
||||
use std::path::Path;
|
||||
use test::Bencher;
|
||||
|
||||
// Given some blobs and a ledger at ledger_path, benchmark writing the blobs to the ledger
|
||||
fn bench_write_blobs(bench: &mut Bencher, blobs: &mut Vec<Blob>, ledger_path: &str) {
|
||||
fn bench_write_blobs(bench: &mut Bencher, blobs: &mut Vec<Blob>, ledger_path: &Path) {
|
||||
let blocktree =
|
||||
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger");
|
||||
Blocktree::open(ledger_path).expect("Expected to be able to open database ledger");
|
||||
|
||||
let num_blobs = blobs.len();
|
||||
|
||||
@@ -36,7 +37,7 @@ fn bench_write_blobs(bench: &mut Bencher, blobs: &mut Vec<Blob>, ledger_path: &s
|
||||
}
|
||||
});
|
||||
|
||||
Blocktree::destroy(&ledger_path).expect("Expected successful database destruction");
|
||||
Blocktree::destroy(ledger_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
// Insert some blobs into the ledger in preparation for read benchmarks
|
||||
|
@@ -3,6 +3,7 @@
|
||||
extern crate test;
|
||||
|
||||
use solana::packet::to_packets;
|
||||
use solana::recycler::Recycler;
|
||||
use solana::sigverify;
|
||||
use solana::test_tx::test_tx;
|
||||
use test::Bencher;
|
||||
@@ -14,8 +15,10 @@ fn bench_sigverify(bencher: &mut Bencher) {
|
||||
// generate packet vector
|
||||
let batches = to_packets(&vec![tx; 128]);
|
||||
|
||||
let recycler = Recycler::default();
|
||||
let recycler_out = Recycler::default();
|
||||
// verify packets
|
||||
bencher.iter(|| {
|
||||
let _ans = sigverify::ed25519_verify(&batches);
|
||||
let _ans = sigverify::ed25519_verify(&batches, &recycler, &recycler_out);
|
||||
})
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@
|
||||
extern crate solana;
|
||||
extern crate test;
|
||||
|
||||
use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use solana::packet::to_packets_chunked;
|
||||
@@ -21,7 +22,7 @@ use test::Bencher;
|
||||
fn bench_sigverify_stage(bencher: &mut Bencher) {
|
||||
solana_logger::setup();
|
||||
let (packet_s, packet_r) = channel();
|
||||
let (verified_s, verified_r) = channel();
|
||||
let (verified_s, verified_r) = unbounded();
|
||||
let sigverify_disabled = false;
|
||||
let stage = SigVerifyStage::new(packet_r, sigverify_disabled, verified_s);
|
||||
|
||||
|
@@ -1,25 +1,94 @@
|
||||
//! The `bank_forks` module implments BankForks a DAG of checkpointed Banks
|
||||
|
||||
use bincode::{deserialize_from, serialize_into};
|
||||
use crate::result::{Error, Result};
|
||||
use crate::snapshot_package::SnapshotPackageSender;
|
||||
use crate::snapshot_utils;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_metrics::inc_new_counter_info;
|
||||
use solana_runtime::bank::{Bank, BankRc, StatusCacheRc};
|
||||
use solana_runtime::status_cache::MAX_CACHE_ENTRIES;
|
||||
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::io::{Error as IOError, ErrorKind};
|
||||
use std::ops::Index;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct SnapshotConfig {
|
||||
snapshot_path: PathBuf,
|
||||
snapshot_package_output_path: PathBuf,
|
||||
snapshot_interval_slots: usize,
|
||||
}
|
||||
|
||||
impl SnapshotConfig {
|
||||
pub fn new(
|
||||
snapshot_path: PathBuf,
|
||||
snapshot_package_output_path: PathBuf,
|
||||
snapshot_interval_slots: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
snapshot_path,
|
||||
snapshot_package_output_path,
|
||||
snapshot_interval_slots,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn snapshot_path(&self) -> &Path {
|
||||
self.snapshot_path.as_path()
|
||||
}
|
||||
|
||||
pub fn snapshot_package_output_path(&self) -> &Path {
|
||||
&self.snapshot_package_output_path.as_path()
|
||||
}
|
||||
|
||||
pub fn snapshot_interval_slots(&self) -> usize {
|
||||
self.snapshot_interval_slots
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BankForks {
|
||||
banks: HashMap<u64, Arc<Bank>>,
|
||||
working_bank: Arc<Bank>,
|
||||
root: u64,
|
||||
slots: HashSet<u64>,
|
||||
snapshot_path: Option<String>,
|
||||
snapshot_config: Option<SnapshotConfig>,
|
||||
last_snapshot: u64,
|
||||
confidence: HashMap<u64, Confidence>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct Confidence {
|
||||
fork_stakes: u64,
|
||||
epoch_stakes: u64,
|
||||
lockouts: u64,
|
||||
stake_weighted_lockouts: u128,
|
||||
}
|
||||
|
||||
impl Confidence {
|
||||
pub fn new(fork_stakes: u64, epoch_stakes: u64, lockouts: u64) -> Self {
|
||||
Self {
|
||||
fork_stakes,
|
||||
epoch_stakes,
|
||||
lockouts,
|
||||
stake_weighted_lockouts: 0,
|
||||
}
|
||||
}
|
||||
pub fn new_with_stake_weighted(
|
||||
fork_stakes: u64,
|
||||
epoch_stakes: u64,
|
||||
lockouts: u64,
|
||||
stake_weighted_lockouts: u128,
|
||||
) -> Self {
|
||||
Self {
|
||||
fork_stakes,
|
||||
epoch_stakes,
|
||||
lockouts,
|
||||
stake_weighted_lockouts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<u64> for BankForks {
|
||||
@@ -38,8 +107,9 @@ impl BankForks {
|
||||
banks,
|
||||
working_bank,
|
||||
root: 0,
|
||||
slots: HashSet::new(),
|
||||
snapshot_path: None,
|
||||
snapshot_config: None,
|
||||
last_snapshot: 0,
|
||||
confidence: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,17 +172,19 @@ impl BankForks {
|
||||
root,
|
||||
banks,
|
||||
working_bank,
|
||||
slots: HashSet::new(),
|
||||
snapshot_path: None,
|
||||
snapshot_config: None,
|
||||
last_snapshot: 0,
|
||||
confidence: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, bank: Bank) {
|
||||
pub fn insert(&mut self, bank: Bank) -> Arc<Bank> {
|
||||
let bank = Arc::new(bank);
|
||||
let prev = self.banks.insert(bank.slot(), bank.clone());
|
||||
assert!(prev.is_none());
|
||||
|
||||
self.working_bank = bank.clone();
|
||||
bank
|
||||
}
|
||||
|
||||
// TODO: really want to kill this...
|
||||
@@ -120,7 +192,7 @@ impl BankForks {
|
||||
self.working_bank.clone()
|
||||
}
|
||||
|
||||
pub fn set_root(&mut self, root: u64) {
|
||||
pub fn set_root(&mut self, root: u64, snapshot_package_sender: &Option<SnapshotPackageSender>) {
|
||||
self.root = root;
|
||||
let set_root_start = Instant::now();
|
||||
let root_bank = self
|
||||
@@ -136,6 +208,33 @@ impl BankForks {
|
||||
let new_tx_count = root_bank.transaction_count();
|
||||
self.prune_non_root(root);
|
||||
|
||||
// Generate a snapshot if snapshots are configured and it's been an appropriate number
|
||||
// of banks since the last snapshot
|
||||
if self.snapshot_config.is_some() {
|
||||
let config = self
|
||||
.snapshot_config
|
||||
.as_ref()
|
||||
.expect("Called package_snapshot without a snapshot configuration");
|
||||
if root - self.last_snapshot >= config.snapshot_interval_slots as u64 {
|
||||
let mut snapshot_time = Measure::start("total-snapshot-ms");
|
||||
let r = self.generate_snapshot(
|
||||
root,
|
||||
snapshot_package_sender.as_ref().unwrap(),
|
||||
snapshot_utils::get_snapshot_tar_path(&config.snapshot_package_output_path),
|
||||
);
|
||||
if r.is_err() {
|
||||
warn!("Error generating snapshot for bank: {}, err: {:?}", root, r);
|
||||
} else {
|
||||
self.last_snapshot = root;
|
||||
}
|
||||
|
||||
// Cleanup outdated snapshots
|
||||
self.purge_old_snapshots();
|
||||
snapshot_time.stop();
|
||||
inc_new_counter_info!("total-snapshot-setup-ms", snapshot_time.as_ms() as usize);
|
||||
}
|
||||
}
|
||||
|
||||
inc_new_counter_info!(
|
||||
"bank-forks_set_root_ms",
|
||||
timing::duration_as_ms(&set_root_start.elapsed()) as usize
|
||||
@@ -150,139 +249,111 @@ impl BankForks {
|
||||
self.root
|
||||
}
|
||||
|
||||
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));
|
||||
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);
|
||||
}
|
||||
fn purge_old_snapshots(&self) {
|
||||
// Remove outdated snapshots
|
||||
let config = self.snapshot_config.as_ref().unwrap();
|
||||
let names = snapshot_utils::get_snapshot_names(&config.snapshot_path);
|
||||
let num_to_remove = names.len().saturating_sub(MAX_CACHE_ENTRIES);
|
||||
for old_root in &names[..num_to_remove] {
|
||||
let r = snapshot_utils::remove_snapshot(*old_root, &config.snapshot_path);
|
||||
if r.is_err() {
|
||||
warn!("Couldn't remove snapshot at: {:?}", config.snapshot_path);
|
||||
}
|
||||
}
|
||||
self.slots = slots.clone();
|
||||
}
|
||||
|
||||
fn get_io_error(error: &str) -> Error {
|
||||
warn!("BankForks error: {:?}", error);
|
||||
Error::new(ErrorKind::Other, error)
|
||||
}
|
||||
fn generate_snapshot<P: AsRef<Path>>(
|
||||
&self,
|
||||
root: u64,
|
||||
snapshot_package_sender: &SnapshotPackageSender,
|
||||
tar_output_file: P,
|
||||
) -> Result<()> {
|
||||
let config = self.snapshot_config.as_ref().unwrap();
|
||||
|
||||
fn get_snapshot_path(path: &Option<String>) -> PathBuf {
|
||||
Path::new(&path.clone().unwrap()).to_path_buf()
|
||||
}
|
||||
// Add a snapshot for the new root
|
||||
let bank = self
|
||||
.get(root)
|
||||
.cloned()
|
||||
.expect("root must exist in BankForks");
|
||||
snapshot_utils::add_snapshot(&config.snapshot_path, &bank, root)?;
|
||||
|
||||
// Package the relevant snapshots
|
||||
let names = snapshot_utils::get_snapshot_names(&config.snapshot_path);
|
||||
|
||||
// We only care about the last MAX_CACHE_ENTRIES snapshots of roots because
|
||||
// the status cache of anything older is thrown away by the bank in
|
||||
// status_cache.prune_roots()
|
||||
let start = names.len().saturating_sub(MAX_CACHE_ENTRIES);
|
||||
let package = snapshot_utils::package_snapshot(
|
||||
&bank,
|
||||
&names[start..],
|
||||
&config.snapshot_path,
|
||||
tar_output_file,
|
||||
)?;
|
||||
|
||||
// Send the package to the packaging thread
|
||||
snapshot_package_sender.send(package)?;
|
||||
|
||||
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);
|
||||
fn prune_non_root(&mut self, root: u64) {
|
||||
let descendants = self.descendants();
|
||||
self.banks
|
||||
.retain(|slot, _| slot == &root || descendants[&root].contains(slot));
|
||||
self.confidence
|
||||
.retain(|slot, _| slot == &root || descendants[&root].contains(slot));
|
||||
}
|
||||
|
||||
pub fn set_snapshot_config(&mut self, path: Option<String>) {
|
||||
self.snapshot_path = path;
|
||||
pub fn cache_fork_confidence(
|
||||
&mut self,
|
||||
fork: u64,
|
||||
fork_stakes: u64,
|
||||
epoch_stakes: u64,
|
||||
lockouts: u64,
|
||||
) {
|
||||
self.confidence
|
||||
.entry(fork)
|
||||
.and_modify(|entry| {
|
||||
entry.fork_stakes = fork_stakes;
|
||||
entry.epoch_stakes = epoch_stakes;
|
||||
entry.lockouts = lockouts;
|
||||
})
|
||||
.or_insert_with(|| Confidence::new(fork_stakes, epoch_stakes, lockouts));
|
||||
}
|
||||
|
||||
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;
|
||||
pub fn cache_stake_weighted_lockouts(&mut self, fork: u64, stake_weighted_lockouts: u128) {
|
||||
self.confidence
|
||||
.entry(fork)
|
||||
.and_modify(|entry| {
|
||||
entry.stake_weighted_lockouts = stake_weighted_lockouts;
|
||||
})
|
||||
.or_insert(Confidence {
|
||||
fork_stakes: 0,
|
||||
epoch_stakes: 0,
|
||||
lockouts: 0,
|
||||
stake_weighted_lockouts,
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
pub fn get_fork_confidence(&self, fork: u64) -> Option<&Confidence> {
|
||||
self.confidence.get(&fork)
|
||||
}
|
||||
|
||||
bank_root
|
||||
pub fn set_snapshot_config(&mut self, snapshot_config: SnapshotConfig) {
|
||||
self.snapshot_config = Some(snapshot_config);
|
||||
}
|
||||
|
||||
pub fn snapshot_config(&self) -> &Option<SnapshotConfig> {
|
||||
&self.snapshot_config
|
||||
}
|
||||
|
||||
fn setup_banks(
|
||||
bank_maps: &mut Vec<(u64, u64, Bank)>,
|
||||
bank_rc: &BankRc,
|
||||
status_cache_rc: &StatusCacheRc,
|
||||
) -> (HashMap<u64, Arc<Bank>>, HashSet<u64>, u64) {
|
||||
) -> (HashMap<u64, Arc<Bank>>, 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);
|
||||
|
||||
@@ -295,7 +366,6 @@ impl BankForks {
|
||||
}
|
||||
if slot > 0 {
|
||||
banks.insert(slot, Arc::new(bank));
|
||||
slots.insert(slot);
|
||||
}
|
||||
}
|
||||
if last_parent_slot != 0 {
|
||||
@@ -304,57 +374,55 @@ impl BankForks {
|
||||
}
|
||||
}
|
||||
banks.insert(last_slot, Arc::new(last_bank));
|
||||
slots.insert(last_slot);
|
||||
|
||||
(banks, slots, last_slot)
|
||||
(banks, 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();
|
||||
snapshot_config: &SnapshotConfig,
|
||||
) -> Result<Self> {
|
||||
fs::create_dir_all(&snapshot_config.snapshot_path)?;
|
||||
let names = snapshot_utils::get_snapshot_names(&snapshot_config.snapshot_path);
|
||||
if names.is_empty() {
|
||||
return Err(Error::IO(IOError::new(
|
||||
ErrorKind::Other,
|
||||
"no snapshots found",
|
||||
)));
|
||||
}
|
||||
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(
|
||||
let bank_root = snapshot_utils::load_snapshots(
|
||||
&names,
|
||||
&mut bank0,
|
||||
&mut bank_maps,
|
||||
&status_cache_rc,
|
||||
snapshot_path,
|
||||
&snapshot_config.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"));
|
||||
return Err(Error::IO(IOError::new(
|
||||
ErrorKind::Other,
|
||||
"no snapshots loaded",
|
||||
)));
|
||||
}
|
||||
|
||||
let root = bank_root.unwrap();
|
||||
let (banks, slots, last_slot) =
|
||||
let (banks, 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(),
|
||||
snapshot_config: None,
|
||||
last_snapshot: *names.last().unwrap(),
|
||||
confidence: HashMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -363,12 +431,17 @@ impl BankForks {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo};
|
||||
use crate::service::Service;
|
||||
use crate::snapshot_package::SnapshotPackagerService;
|
||||
use fs_extra::dir::CopyOptions;
|
||||
use itertools::Itertools;
|
||||
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;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::mpsc::channel;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_bank_forks() {
|
||||
@@ -438,55 +511,44 @@ 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(","),
|
||||
}
|
||||
#[test]
|
||||
fn test_bank_forks_confidence_cache() {
|
||||
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
|
||||
let bank = Bank::new(&genesis_block);
|
||||
let fork = bank.slot();
|
||||
let mut bank_forks = BankForks::new(0, bank);
|
||||
assert!(bank_forks.confidence.get(&fork).is_none());
|
||||
bank_forks.cache_fork_confidence(fork, 11, 12, 13);
|
||||
assert_eq!(
|
||||
bank_forks.confidence.get(&fork).unwrap(),
|
||||
&Confidence {
|
||||
fork_stakes: 11,
|
||||
epoch_stakes: 12,
|
||||
lockouts: 13,
|
||||
stake_weighted_lockouts: 0,
|
||||
}
|
||||
);
|
||||
// Ensure that {fork_stakes, epoch_stakes, lockouts} and stake_weighted_lockouts
|
||||
// can be updated separately
|
||||
bank_forks.cache_stake_weighted_lockouts(fork, 20);
|
||||
assert_eq!(
|
||||
bank_forks.confidence.get(&fork).unwrap(),
|
||||
&Confidence {
|
||||
fork_stakes: 11,
|
||||
epoch_stakes: 12,
|
||||
lockouts: 13,
|
||||
stake_weighted_lockouts: 20,
|
||||
}
|
||||
);
|
||||
bank_forks.cache_fork_confidence(fork, 21, 22, 23);
|
||||
assert_eq!(
|
||||
bank_forks
|
||||
.confidence
|
||||
.get(&fork)
|
||||
.unwrap()
|
||||
.stake_weighted_lockouts,
|
||||
20,
|
||||
);
|
||||
}
|
||||
|
||||
fn restore_from_snapshot(
|
||||
@@ -495,9 +557,19 @@ mod tests {
|
||||
account_paths: Option<String>,
|
||||
last_slot: u64,
|
||||
) {
|
||||
let new =
|
||||
BankForks::load_from_snapshot(&genesis_block, account_paths, &bank_forks.snapshot_path)
|
||||
.unwrap();
|
||||
let snapshot_path = bank_forks
|
||||
.snapshot_config
|
||||
.as_ref()
|
||||
.map(|c| &c.snapshot_path)
|
||||
.unwrap();
|
||||
|
||||
let new = BankForks::load_from_snapshot(
|
||||
&genesis_block,
|
||||
account_paths,
|
||||
bank_forks.snapshot_config.as_ref().unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for (slot, _) in new.banks.iter() {
|
||||
if *slot > 0 {
|
||||
let bank = bank_forks.banks.get(slot).unwrap().clone();
|
||||
@@ -505,29 +577,39 @@ mod tests {
|
||||
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);
|
||||
snapshot_utils::remove_snapshot(*slot, snapshot_path).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bank_forks_snapshot_n() {
|
||||
solana_logger::setup();
|
||||
let path = get_tmp_bank_accounts_path!();
|
||||
let spath = get_tmp_snapshots_path();
|
||||
let accounts_dir = TempDir::new().unwrap();
|
||||
let snapshot_dir = TempDir::new().unwrap();
|
||||
let snapshot_output_path = TempDir::new().unwrap();
|
||||
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()));
|
||||
let bank0 = Bank::new_with_paths(
|
||||
&genesis_block,
|
||||
Some(accounts_dir.path().to_str().unwrap().to_string()),
|
||||
);
|
||||
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();
|
||||
let snapshot_config = SnapshotConfig::new(
|
||||
PathBuf::from(snapshot_dir.path()),
|
||||
PathBuf::from(snapshot_output_path.path()),
|
||||
100,
|
||||
);
|
||||
bank_forks.set_snapshot_config(snapshot_config.clone());
|
||||
let bank0 = bank_forks.get(0).unwrap();
|
||||
snapshot_utils::add_snapshot(&snapshot_config.snapshot_path, bank0, 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();
|
||||
@@ -539,11 +621,146 @@ mod tests {
|
||||
);
|
||||
assert_eq!(bank.process_transaction(&tx), Ok(()));
|
||||
bank.freeze();
|
||||
let slot = bank.slot();
|
||||
snapshot_utils::add_snapshot(&snapshot_config.snapshot_path, &bank, 0).unwrap();
|
||||
bank_forks.insert(bank);
|
||||
bank_forks.add_snapshot(slot, 0).unwrap();
|
||||
}
|
||||
restore_from_snapshot(&genesis_block, bank_forks, Some(path.paths.clone()), index);
|
||||
restore_from_snapshot(
|
||||
&genesis_block,
|
||||
bank_forks,
|
||||
Some(accounts_dir.path().to_str().unwrap().to_string()),
|
||||
index,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concurrent_snapshot_packaging() {
|
||||
solana_logger::setup();
|
||||
let accounts_dir = TempDir::new().unwrap();
|
||||
let snapshots_dir = TempDir::new().unwrap();
|
||||
let snapshot_output_path = TempDir::new().unwrap();
|
||||
let GenesisBlockInfo {
|
||||
genesis_block,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_block(10_000);
|
||||
let (sender, receiver) = channel();
|
||||
let (fake_sender, _fake_receiver) = channel();
|
||||
let bank0 = Bank::new_with_paths(
|
||||
&genesis_block,
|
||||
Some(accounts_dir.path().to_str().unwrap().to_string()),
|
||||
);
|
||||
bank0.freeze();
|
||||
|
||||
// Set up bank forks
|
||||
let mut bank_forks = BankForks::new(0, bank0);
|
||||
let snapshot_config = SnapshotConfig::new(
|
||||
PathBuf::from(snapshots_dir.path()),
|
||||
PathBuf::from(snapshot_output_path.path()),
|
||||
1,
|
||||
);
|
||||
bank_forks.set_snapshot_config(snapshot_config.clone());
|
||||
|
||||
// Take snapshot of zeroth bank
|
||||
let bank0 = bank_forks.get(0).unwrap();
|
||||
snapshot_utils::add_snapshot(&snapshot_config.snapshot_path, bank0, 0).unwrap();
|
||||
|
||||
// Create next MAX_CACHE_ENTRIES + 2 banks and snapshots. Every bank will get snapshotted
|
||||
// and the snapshot purging logic will run on every snapshot taken. This means the three
|
||||
// (including snapshot for bank0 created above) earliest snapshots will get purged by the
|
||||
// time this loop is done.
|
||||
|
||||
// Also, make a saved copy of the state of the snapshot for a bank with
|
||||
// bank.slot == saved_slot, so we can use it for a correctness check later.
|
||||
let saved_snapshots_dir = TempDir::new().unwrap();
|
||||
let saved_accounts_dir = TempDir::new().unwrap();
|
||||
let saved_slot = 4;
|
||||
let saved_tar = snapshot_config
|
||||
.snapshot_package_output_path
|
||||
.join(saved_slot.to_string());
|
||||
for forks in 0..MAX_CACHE_ENTRIES + 2 {
|
||||
let bank = Bank::new_from_parent(
|
||||
&bank_forks[forks as u64],
|
||||
&Pubkey::default(),
|
||||
(forks + 1) as u64,
|
||||
);
|
||||
let slot = bank.slot();
|
||||
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();
|
||||
bank_forks.insert(bank);
|
||||
|
||||
let package_sender = {
|
||||
if slot == saved_slot as u64 {
|
||||
// Only send one package on the real sende so that the packaging service
|
||||
// doesn't take forever to run the packaging logic on all MAX_CACHE_ENTRIES
|
||||
// later
|
||||
&sender
|
||||
} else {
|
||||
&fake_sender
|
||||
}
|
||||
};
|
||||
|
||||
bank_forks
|
||||
.generate_snapshot(
|
||||
slot,
|
||||
&package_sender,
|
||||
snapshot_config
|
||||
.snapshot_package_output_path
|
||||
.join(slot.to_string()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if slot == saved_slot as u64 {
|
||||
let options = CopyOptions::new();
|
||||
fs_extra::dir::copy(&accounts_dir, &saved_accounts_dir, &options).unwrap();
|
||||
fs_extra::dir::copy(&snapshots_dir, &saved_snapshots_dir, &options).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Purge all the outdated snapshots, including the ones needed to generate the package
|
||||
// currently sitting in the channel
|
||||
bank_forks.purge_old_snapshots();
|
||||
let mut snapshot_names = snapshot_utils::get_snapshot_names(&snapshots_dir);
|
||||
snapshot_names.sort();
|
||||
assert_eq!(
|
||||
snapshot_names,
|
||||
(3..=MAX_CACHE_ENTRIES as u64 + 2).collect_vec()
|
||||
);
|
||||
|
||||
// Create a SnapshotPackagerService to create tarballs from all the pending
|
||||
// SnapshotPackage's on the channel. By the time this service starts, we have already
|
||||
// purged the first two snapshots, which are needed by every snapshot other than
|
||||
// the last two snapshots. However, the packaging service should still be able to
|
||||
// correctly construct the earlier snapshots because the SnapshotPackage's on the
|
||||
// channel hold hard links to these deleted snapshots. We verify this is the case below.
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let snapshot_packager_service = SnapshotPackagerService::new(receiver, &exit);
|
||||
|
||||
// Close the channel so that the package service will exit after reading all the
|
||||
// packages off the channel
|
||||
drop(sender);
|
||||
|
||||
// Wait for service to finish
|
||||
snapshot_packager_service
|
||||
.join()
|
||||
.expect("SnapshotPackagerService exited with error");
|
||||
|
||||
// Check the tar we cached the state for earlier was generated correctly
|
||||
snapshot_utils::tests::verify_snapshot_tar(
|
||||
saved_tar,
|
||||
saved_snapshots_dir
|
||||
.path()
|
||||
.join(snapshots_dir.path().file_name().unwrap()),
|
||||
saved_accounts_dir
|
||||
.path()
|
||||
.join(accounts_dir.path().file_name().unwrap()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ use serde_json::json;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::cell::RefCell;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub trait EntryWriter: std::fmt::Debug {
|
||||
fn write(&self, payload: String) -> Result<()>;
|
||||
@@ -41,7 +42,7 @@ impl EntryVec {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EntrySocket {
|
||||
socket: String,
|
||||
unix_socket: PathBuf,
|
||||
}
|
||||
|
||||
impl EntryWriter for EntrySocket {
|
||||
@@ -50,11 +51,10 @@ impl EntryWriter for EntrySocket {
|
||||
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))?;
|
||||
let mut socket = UnixStream::connect(&self.unix_socket)?;
|
||||
socket.write_all(payload.as_bytes())?;
|
||||
socket.write_all(MESSAGE_TERMINATOR.as_bytes())?;
|
||||
socket.shutdown(Shutdown::Write)?;
|
||||
@@ -144,9 +144,11 @@ where
|
||||
pub type SocketBlockstream = Blockstream<EntrySocket>;
|
||||
|
||||
impl SocketBlockstream {
|
||||
pub fn new(socket: String) -> Self {
|
||||
pub fn new(unix_socket: &Path) -> Self {
|
||||
Blockstream {
|
||||
output: EntrySocket { socket },
|
||||
output: EntrySocket {
|
||||
unix_socket: unix_socket.to_path_buf(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +156,7 @@ impl SocketBlockstream {
|
||||
pub type MockBlockstream = Blockstream<EntryVec>;
|
||||
|
||||
impl MockBlockstream {
|
||||
pub fn new(_: String) -> Self {
|
||||
pub fn new(_: &Path) -> Self {
|
||||
Blockstream {
|
||||
output: EntryVec::new(),
|
||||
}
|
||||
@@ -183,6 +185,7 @@ mod test {
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::system_transaction;
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn test_serialize_transactions() {
|
||||
@@ -205,7 +208,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_blockstream() -> () {
|
||||
let blockstream = MockBlockstream::new("test_stream".to_string());
|
||||
let blockstream = MockBlockstream::new(&PathBuf::from("test_stream"));
|
||||
let ticks_per_slot = 5;
|
||||
|
||||
let mut blockhash = Hash::default();
|
||||
|
@@ -11,6 +11,7 @@ use crate::blocktree::Blocktree;
|
||||
use crate::result::{Error, Result};
|
||||
use crate::service::Service;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{Receiver, RecvTimeoutError};
|
||||
use std::sync::Arc;
|
||||
@@ -26,10 +27,10 @@ impl BlockstreamService {
|
||||
pub fn new(
|
||||
slot_full_receiver: Receiver<(u64, Pubkey)>,
|
||||
blocktree: Arc<Blocktree>,
|
||||
blockstream_socket: String,
|
||||
unix_socket: &Path,
|
||||
exit: &Arc<AtomicBool>,
|
||||
) -> Self {
|
||||
let mut blockstream = Blockstream::new(blockstream_socket);
|
||||
let mut blockstream = Blockstream::new(unix_socket);
|
||||
let exit = exit.clone();
|
||||
let t_blockstream = Builder::new()
|
||||
.name("solana-blockstream".to_string())
|
||||
@@ -116,6 +117,7 @@ mod test {
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::system_transaction;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
@@ -133,7 +135,7 @@ mod test {
|
||||
let blocktree = Blocktree::open(&ledger_path).unwrap();
|
||||
|
||||
// Set up blockstream
|
||||
let mut blockstream = Blockstream::new("test_stream".to_string());
|
||||
let mut blockstream = Blockstream::new(&PathBuf::from("test_stream"));
|
||||
|
||||
// Set up dummy channel to receive a full-slot notification
|
||||
let (slot_full_sender, slot_full_receiver) = channel();
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ use bincode::{deserialize, serialize};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
use solana_sdk::timing::Slot;
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
@@ -39,6 +40,10 @@ pub mod columns {
|
||||
#[derive(Debug)]
|
||||
/// The root column
|
||||
pub struct Root;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// The index column
|
||||
pub struct Index;
|
||||
}
|
||||
|
||||
pub trait Backend: Sized + Send + Sync {
|
||||
@@ -82,6 +87,8 @@ where
|
||||
|
||||
fn key(index: Self::Index) -> B::OwnedKey;
|
||||
fn index(key: &B::Key) -> Self::Index;
|
||||
fn slot(index: Self::Index) -> Slot;
|
||||
fn as_index(slot: Slot) -> Self::Index;
|
||||
}
|
||||
|
||||
pub trait DbCursor<B>
|
||||
@@ -405,6 +412,31 @@ where
|
||||
Ok(iter.map(|(key, value)| (C::index(&key), value)))
|
||||
}
|
||||
|
||||
pub fn delete_slot(
|
||||
&self,
|
||||
batch: &mut WriteBatch<B>,
|
||||
from: Option<Slot>,
|
||||
to: Option<Slot>,
|
||||
) -> Result<bool>
|
||||
where
|
||||
C::Index: PartialOrd + Copy,
|
||||
{
|
||||
let mut end = true;
|
||||
let iter = self.iter(from.map(C::as_index))?;
|
||||
for (index, _) in iter {
|
||||
if let Some(to) = to {
|
||||
if C::slot(index) > to {
|
||||
end = false;
|
||||
break;
|
||||
}
|
||||
};
|
||||
if let Err(e) = batch.delete::<C>(index) {
|
||||
error!("Error: {:?} while adding delete to batch {:?}", e, C::NAME)
|
||||
}
|
||||
}
|
||||
Ok(end)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn handle(&self) -> B::ColumnFamily {
|
||||
self.backend.cf_handle(C::NAME).clone()
|
||||
|
@@ -100,6 +100,25 @@ impl Column<Kvs> for cf::Data {
|
||||
}
|
||||
}
|
||||
|
||||
impl Column<Kvs> for cf::Index {
|
||||
const NAME: &'static str = super::INDEX_CF;
|
||||
type Index = u64;
|
||||
|
||||
fn key(slot: u64) -> Key {
|
||||
let mut key = Key::default();
|
||||
BigEndian::write_u64(&mut key.0[8..16], slot);
|
||||
key
|
||||
}
|
||||
|
||||
fn index(key: &Key) -> u64 {
|
||||
BigEndian::read_u64(&key.0[8..16])
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedColumn<Kvs> for cf::Index {
|
||||
type Type = crate::blocktree::meta::Index;
|
||||
}
|
||||
|
||||
impl Column<Kvs> for cf::DeadSlots {
|
||||
const NAME: &'static str = super::DEAD_SLOTS;
|
||||
type Index = u64;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use crate::erasure::{NUM_CODING, NUM_DATA};
|
||||
use crate::erasure::ErasureConfig;
|
||||
use solana_metrics::datapoint;
|
||||
use std::borrow::Borrow;
|
||||
use std::{collections::BTreeSet, ops::RangeBounds};
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
|
||||
// The Meta column family
|
||||
@@ -27,6 +27,118 @@ pub struct SlotMeta {
|
||||
pub is_connected: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
|
||||
/// Index recording presence/absence of blobs
|
||||
pub struct Index {
|
||||
pub slot: u64,
|
||||
data: DataIndex,
|
||||
coding: CodingIndex,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
|
||||
pub struct DataIndex {
|
||||
/// Map representing presence/absence of data blobs
|
||||
index: BTreeSet<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
|
||||
/// Erasure coding information
|
||||
pub struct CodingIndex {
|
||||
/// Map from set index, to hashmap from blob index to presence bool
|
||||
index: BTreeSet<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
|
||||
/// Erasure coding information
|
||||
pub struct ErasureMeta {
|
||||
/// Which erasure set in the slot this is
|
||||
pub set_index: u64,
|
||||
/// Size of shards in this erasure set
|
||||
pub size: usize,
|
||||
/// Erasure configuration for this erasure set
|
||||
config: ErasureConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ErasureMetaStatus {
|
||||
CanRecover,
|
||||
DataFull,
|
||||
StillNeed(usize),
|
||||
}
|
||||
|
||||
impl Index {
|
||||
pub(in crate::blocktree) fn new(slot: u64) -> Self {
|
||||
Index {
|
||||
slot,
|
||||
data: DataIndex::default(),
|
||||
coding: CodingIndex::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &DataIndex {
|
||||
&self.data
|
||||
}
|
||||
pub fn coding(&self) -> &CodingIndex {
|
||||
&self.coding
|
||||
}
|
||||
|
||||
pub fn data_mut(&mut self) -> &mut DataIndex {
|
||||
&mut self.data
|
||||
}
|
||||
pub fn coding_mut(&mut self) -> &mut CodingIndex {
|
||||
&mut self.coding
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: Mark: Change this when coding
|
||||
impl CodingIndex {
|
||||
pub fn present_in_bounds(&self, bounds: impl RangeBounds<u64>) -> usize {
|
||||
self.index.range(bounds).count()
|
||||
}
|
||||
|
||||
pub fn is_present(&self, index: u64) -> bool {
|
||||
self.index.contains(&index)
|
||||
}
|
||||
|
||||
pub fn set_present(&mut self, index: u64, presence: bool) {
|
||||
if presence {
|
||||
self.index.insert(index);
|
||||
} else {
|
||||
self.index.remove(&index);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_many_present(&mut self, presence: impl IntoIterator<Item = (u64, bool)>) {
|
||||
for (idx, present) in presence.into_iter() {
|
||||
self.set_present(idx, present);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DataIndex {
|
||||
pub fn present_in_bounds(&self, bounds: impl RangeBounds<u64>) -> usize {
|
||||
self.index.range(bounds).count()
|
||||
}
|
||||
|
||||
pub fn is_present(&self, index: u64) -> bool {
|
||||
self.index.contains(&index)
|
||||
}
|
||||
|
||||
pub fn set_present(&mut self, index: u64, presence: bool) {
|
||||
if presence {
|
||||
self.index.insert(index);
|
||||
} else {
|
||||
self.index.remove(&index);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_many_present(&mut self, presence: impl IntoIterator<Item = (u64, bool)>) {
|
||||
for (idx, present) in presence.into_iter() {
|
||||
self.set_present(idx, present);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SlotMeta {
|
||||
pub fn is_full(&self) -> bool {
|
||||
// last_index is std::u64::MAX when it has no information about how
|
||||
@@ -72,62 +184,37 @@ impl SlotMeta {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
|
||||
/// Erasure coding information
|
||||
pub struct ErasureMeta {
|
||||
/// Which erasure set in the slot this is
|
||||
pub set_index: u64,
|
||||
/// Size of shards in this erasure set
|
||||
pub size: usize,
|
||||
/// Bitfield representing presence/absence of data blobs
|
||||
data: u64,
|
||||
/// Bitfield representing presence/absence of coding blobs
|
||||
coding: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ErasureMetaStatus {
|
||||
CanRecover,
|
||||
DataFull,
|
||||
StillNeed(usize),
|
||||
}
|
||||
|
||||
impl ErasureMeta {
|
||||
pub fn new(set_index: u64) -> ErasureMeta {
|
||||
pub fn new(set_index: u64, config: &ErasureConfig) -> ErasureMeta {
|
||||
ErasureMeta {
|
||||
set_index,
|
||||
size: 0,
|
||||
data: 0,
|
||||
coding: 0,
|
||||
config: *config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn status(&self) -> ErasureMetaStatus {
|
||||
let (data_missing, coding_missing) =
|
||||
(NUM_DATA - self.num_data(), NUM_CODING - self.num_coding());
|
||||
if data_missing > 0 && data_missing + coding_missing <= NUM_CODING {
|
||||
assert!(self.size != 0);
|
||||
ErasureMetaStatus::CanRecover
|
||||
pub fn status(&self, index: &Index) -> ErasureMetaStatus {
|
||||
use ErasureMetaStatus::*;
|
||||
|
||||
let start_idx = self.start_index();
|
||||
let (data_end_idx, coding_end_idx) = self.end_indexes();
|
||||
|
||||
let num_coding = index.coding().present_in_bounds(start_idx..coding_end_idx);
|
||||
let num_data = index.data().present_in_bounds(start_idx..data_end_idx);
|
||||
|
||||
let (data_missing, coding_missing) = (
|
||||
self.config.num_data() - num_data,
|
||||
self.config.num_coding() - num_coding,
|
||||
);
|
||||
|
||||
let total_missing = data_missing + coding_missing;
|
||||
|
||||
if data_missing > 0 && total_missing <= self.config.num_coding() {
|
||||
CanRecover
|
||||
} else if data_missing == 0 {
|
||||
ErasureMetaStatus::DataFull
|
||||
DataFull
|
||||
} else {
|
||||
ErasureMetaStatus::StillNeed(data_missing + coding_missing - NUM_CODING)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn num_coding(&self) -> usize {
|
||||
self.coding.count_ones() as usize
|
||||
}
|
||||
|
||||
pub fn num_data(&self) -> usize {
|
||||
self.data.count_ones() as usize
|
||||
}
|
||||
|
||||
pub fn is_coding_present(&self, index: u64) -> bool {
|
||||
if let Some(position) = self.data_index_in_set(index) {
|
||||
self.coding & (1 << position) != 0
|
||||
} else {
|
||||
false
|
||||
StillNeed(total_missing - self.config.num_coding())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,207 +226,78 @@ impl ErasureMeta {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn set_coding_present(&mut self, index: u64, present: bool) {
|
||||
if let Some(position) = self.data_index_in_set(index) {
|
||||
if present {
|
||||
self.coding |= 1 << position;
|
||||
} else {
|
||||
self.coding &= !(1 << position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_data_present(&self, index: u64) -> bool {
|
||||
if let Some(position) = self.data_index_in_set(index) {
|
||||
self.data & (1 << position) != 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_data_present(&mut self, index: u64, present: bool) {
|
||||
if let Some(position) = self.data_index_in_set(index) {
|
||||
if present {
|
||||
self.data |= 1 << position;
|
||||
} else {
|
||||
self.data &= !(1 << position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_data_multi<I, Idx>(&mut self, indexes: I, present: bool)
|
||||
where
|
||||
I: IntoIterator<Item = Idx>,
|
||||
Idx: Borrow<u64>,
|
||||
{
|
||||
for index in indexes.into_iter() {
|
||||
self.set_data_present(*index.borrow(), present);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_coding_multi<I, Idx>(&mut self, indexes: I, present: bool)
|
||||
where
|
||||
I: IntoIterator<Item = Idx>,
|
||||
Idx: Borrow<u64>,
|
||||
{
|
||||
for index in indexes.into_iter() {
|
||||
self.set_coding_present(*index.borrow(), present);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_index_for(index: u64) -> u64 {
|
||||
index / NUM_DATA as u64
|
||||
}
|
||||
|
||||
pub fn data_index_in_set(&self, index: u64) -> Option<u64> {
|
||||
let set_index = Self::set_index_for(index);
|
||||
|
||||
if set_index == self.set_index {
|
||||
Some(index - self.start_index())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn coding_index_in_set(&self, index: u64) -> Option<u64> {
|
||||
self.data_index_in_set(index).map(|i| i + NUM_DATA as u64)
|
||||
pub fn set_index_for(index: u64, num_data: usize) -> u64 {
|
||||
index / num_data as u64
|
||||
}
|
||||
|
||||
pub fn start_index(&self) -> u64 {
|
||||
self.set_index * NUM_DATA as u64
|
||||
self.set_index * self.config.num_data() as u64
|
||||
}
|
||||
|
||||
/// returns a tuple of (data_end, coding_end)
|
||||
pub fn end_indexes(&self) -> (u64, u64) {
|
||||
let start = self.start_index();
|
||||
(start + NUM_DATA as u64, start + NUM_CODING as u64)
|
||||
(
|
||||
start + self.config.num_data() as u64,
|
||||
start + self.config.num_coding() as u64,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_meta_indexes() {
|
||||
use rand::{thread_rng, Rng};
|
||||
// to avoid casts everywhere
|
||||
const NUM_DATA: u64 = crate::erasure::NUM_DATA as u64;
|
||||
|
||||
let mut rng = thread_rng();
|
||||
|
||||
for _ in 0..100 {
|
||||
let set_index = rng.gen_range(0, 1_000);
|
||||
let blob_index = (set_index * NUM_DATA) + rng.gen_range(0, NUM_DATA);
|
||||
|
||||
assert_eq!(set_index, ErasureMeta::set_index_for(blob_index));
|
||||
let e_meta = ErasureMeta::new(set_index);
|
||||
|
||||
assert_eq!(e_meta.start_index(), set_index * NUM_DATA);
|
||||
let (data_end_idx, coding_end_idx) = e_meta.end_indexes();
|
||||
assert_eq!(data_end_idx, (set_index + 1) * NUM_DATA);
|
||||
assert_eq!(coding_end_idx, set_index * NUM_DATA + NUM_CODING as u64);
|
||||
}
|
||||
|
||||
let mut e_meta = ErasureMeta::new(0);
|
||||
|
||||
assert_eq!(e_meta.data_index_in_set(0), Some(0));
|
||||
assert_eq!(e_meta.data_index_in_set(NUM_DATA / 2), Some(NUM_DATA / 2));
|
||||
assert_eq!(e_meta.data_index_in_set(NUM_DATA - 1), Some(NUM_DATA - 1));
|
||||
assert_eq!(e_meta.data_index_in_set(NUM_DATA), None);
|
||||
assert_eq!(e_meta.data_index_in_set(std::u64::MAX), None);
|
||||
|
||||
e_meta.set_index = 1;
|
||||
|
||||
assert_eq!(e_meta.data_index_in_set(0), None);
|
||||
assert_eq!(e_meta.data_index_in_set(NUM_DATA - 1), None);
|
||||
assert_eq!(e_meta.data_index_in_set(NUM_DATA), Some(0));
|
||||
assert_eq!(
|
||||
e_meta.data_index_in_set(NUM_DATA * 2 - 1),
|
||||
Some(NUM_DATA - 1)
|
||||
);
|
||||
assert_eq!(e_meta.data_index_in_set(std::u64::MAX), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_meta_coding_present() {
|
||||
let mut e_meta = ErasureMeta::default();
|
||||
|
||||
e_meta.set_coding_multi(0..NUM_CODING as u64, true);
|
||||
for i in 0..NUM_CODING as u64 {
|
||||
assert_eq!(e_meta.is_coding_present(i), true);
|
||||
}
|
||||
for i in NUM_CODING as u64..NUM_DATA as u64 {
|
||||
assert_eq!(e_meta.is_coding_present(i), false);
|
||||
}
|
||||
|
||||
e_meta.set_index = ErasureMeta::set_index_for((NUM_DATA * 17) as u64);
|
||||
let start_idx = e_meta.start_index();
|
||||
e_meta.set_coding_multi(start_idx..start_idx + NUM_CODING as u64, true);
|
||||
|
||||
for i in start_idx..start_idx + NUM_CODING as u64 {
|
||||
e_meta.set_coding_present(i, true);
|
||||
assert_eq!(e_meta.is_coding_present(i), true);
|
||||
}
|
||||
for i in start_idx + NUM_CODING as u64..start_idx + NUM_DATA as u64 {
|
||||
assert_eq!(e_meta.is_coding_present(i), false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_erasure_meta_status() {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
// Local constansts just used to avoid repetitive casts
|
||||
const N_DATA: u64 = crate::erasure::NUM_DATA as u64;
|
||||
const N_CODING: u64 = crate::erasure::NUM_CODING as u64;
|
||||
use std::iter::repeat;
|
||||
|
||||
let mut e_meta = ErasureMeta::default();
|
||||
let mut rng = thread_rng();
|
||||
let data_indexes: Vec<u64> = (0..N_DATA).collect();
|
||||
let coding_indexes: Vec<u64> = (0..N_CODING).collect();
|
||||
#[test]
|
||||
fn test_erasure_meta_status() {
|
||||
use ErasureMetaStatus::*;
|
||||
|
||||
assert_eq!(e_meta.status(), ErasureMetaStatus::StillNeed(NUM_DATA));
|
||||
let set_index = 0;
|
||||
let erasure_config = ErasureConfig::default();
|
||||
|
||||
e_meta.set_data_multi(0..N_DATA, true);
|
||||
let mut e_meta = ErasureMeta::new(set_index, &erasure_config);
|
||||
let mut rng = thread_rng();
|
||||
let mut index = Index::new(0);
|
||||
e_meta.size = 1;
|
||||
|
||||
assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull);
|
||||
let data_indexes = 0..erasure_config.num_data() as u64;
|
||||
let coding_indexes = 0..erasure_config.num_coding() as u64;
|
||||
|
||||
e_meta.size = 1;
|
||||
e_meta.set_coding_multi(0..N_CODING, true);
|
||||
assert_eq!(e_meta.status(&index), StillNeed(erasure_config.num_data()));
|
||||
|
||||
assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull);
|
||||
index
|
||||
.data_mut()
|
||||
.set_many_present(data_indexes.clone().zip(repeat(true)));
|
||||
|
||||
for &idx in data_indexes.choose_multiple(&mut rng, NUM_CODING) {
|
||||
e_meta.set_data_present(idx, false);
|
||||
assert_eq!(e_meta.status(&index), DataFull);
|
||||
|
||||
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
|
||||
}
|
||||
index
|
||||
.coding_mut()
|
||||
.set_many_present(coding_indexes.clone().zip(repeat(true)));
|
||||
|
||||
e_meta.set_data_multi(0..N_DATA, true);
|
||||
for &idx in data_indexes
|
||||
.clone()
|
||||
.collect::<Vec<_>>()
|
||||
.choose_multiple(&mut rng, erasure_config.num_data())
|
||||
{
|
||||
index.data_mut().set_present(idx, false);
|
||||
|
||||
for &idx in coding_indexes.choose_multiple(&mut rng, NUM_CODING) {
|
||||
e_meta.set_coding_present(idx, false);
|
||||
assert_eq!(e_meta.status(&index), CanRecover);
|
||||
}
|
||||
|
||||
assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_meta_data_present() {
|
||||
let mut e_meta = ErasureMeta::default();
|
||||
|
||||
e_meta.set_data_multi(0..NUM_DATA as u64, true);
|
||||
for i in 0..NUM_DATA as u64 {
|
||||
assert_eq!(e_meta.is_data_present(i), true);
|
||||
}
|
||||
for i in NUM_DATA as u64..2 * NUM_DATA as u64 {
|
||||
assert_eq!(e_meta.is_data_present(i), false);
|
||||
}
|
||||
|
||||
e_meta.set_index = ErasureMeta::set_index_for((NUM_DATA * 23) as u64);
|
||||
let start_idx = e_meta.start_index();
|
||||
e_meta.set_data_multi(start_idx..start_idx + NUM_DATA as u64, true);
|
||||
|
||||
for i in start_idx..start_idx + NUM_DATA as u64 {
|
||||
assert_eq!(e_meta.is_data_present(i), true);
|
||||
}
|
||||
for i in start_idx - NUM_DATA as u64..start_idx {
|
||||
assert_eq!(e_meta.is_data_present(i), false);
|
||||
index
|
||||
.data_mut()
|
||||
.set_many_present(data_indexes.zip(repeat(true)));
|
||||
|
||||
for &idx in coding_indexes
|
||||
.collect::<Vec<_>>()
|
||||
.choose_multiple(&mut rng, erasure_config.num_coding())
|
||||
{
|
||||
index.coding_mut().set_present(idx, false);
|
||||
|
||||
assert_eq!(e_meta.status(&index), DataFull);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ use crate::blocktree::db::columns as cf;
|
||||
use crate::blocktree::db::{Backend, Column, DbCursor, IWriteBatch, TypedColumn};
|
||||
use crate::blocktree::BlocktreeError;
|
||||
use crate::result::{Error, Result};
|
||||
use solana_sdk::timing::Slot;
|
||||
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
|
||||
@@ -15,7 +16,8 @@ use std::path::Path;
|
||||
|
||||
// A good value for this is the number of cores on the machine
|
||||
const TOTAL_THREADS: i32 = 8;
|
||||
const MAX_WRITE_BUFFER_SIZE: usize = 512 * 1024 * 1024;
|
||||
const MAX_WRITE_BUFFER_SIZE: u64 = 256 * 1024 * 1024; // 256MB
|
||||
const MIN_WRITE_BUFFER_SIZE: u64 = 64 * 1024; // 64KB
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Rocks(rocksdb::DB);
|
||||
@@ -30,7 +32,9 @@ impl Backend for Rocks {
|
||||
type Error = rocksdb::Error;
|
||||
|
||||
fn open(path: &Path) -> Result<Rocks> {
|
||||
use crate::blocktree::db::columns::{Coding, Data, DeadSlots, ErasureMeta, Orphans, Root, SlotMeta};
|
||||
use crate::blocktree::db::columns::{
|
||||
Coding, Data, DeadSlots, ErasureMeta, Index, Orphans, Root, SlotMeta,
|
||||
};
|
||||
|
||||
fs::create_dir_all(&path)?;
|
||||
|
||||
@@ -38,14 +42,22 @@ impl Backend for Rocks {
|
||||
let db_options = get_db_options();
|
||||
|
||||
// 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 meta_cf_descriptor =
|
||||
ColumnFamilyDescriptor::new(SlotMeta::NAME, get_cf_options(SlotMeta::NAME));
|
||||
let data_cf_descriptor =
|
||||
ColumnFamilyDescriptor::new(Data::NAME, get_cf_options(Data::NAME));
|
||||
let dead_slots_cf_descriptor =
|
||||
ColumnFamilyDescriptor::new(DeadSlots::NAME, get_cf_options(DeadSlots::NAME));
|
||||
let erasure_cf_descriptor =
|
||||
ColumnFamilyDescriptor::new(Coding::NAME, get_cf_options(Coding::NAME));
|
||||
let erasure_meta_cf_descriptor =
|
||||
ColumnFamilyDescriptor::new(ErasureMeta::NAME, get_cf_options());
|
||||
let orphans_cf_descriptor = ColumnFamilyDescriptor::new(Orphans::NAME, get_cf_options());
|
||||
let root_cf_descriptor = ColumnFamilyDescriptor::new(Root::NAME, get_cf_options());
|
||||
ColumnFamilyDescriptor::new(ErasureMeta::NAME, get_cf_options(ErasureMeta::NAME));
|
||||
let orphans_cf_descriptor =
|
||||
ColumnFamilyDescriptor::new(Orphans::NAME, get_cf_options(Orphans::NAME));
|
||||
let root_cf_descriptor =
|
||||
ColumnFamilyDescriptor::new(Root::NAME, get_cf_options(Root::NAME));
|
||||
let index_cf_descriptor =
|
||||
ColumnFamilyDescriptor::new(Index::NAME, get_cf_options(Index::NAME));
|
||||
|
||||
let cfs = vec![
|
||||
meta_cf_descriptor,
|
||||
@@ -55,6 +67,7 @@ impl Backend for Rocks {
|
||||
erasure_meta_cf_descriptor,
|
||||
orphans_cf_descriptor,
|
||||
root_cf_descriptor,
|
||||
index_cf_descriptor,
|
||||
];
|
||||
|
||||
// Open the database
|
||||
@@ -64,13 +77,16 @@ impl Backend for Rocks {
|
||||
}
|
||||
|
||||
fn columns(&self) -> Vec<&'static str> {
|
||||
use crate::blocktree::db::columns::{Coding, Data, DeadSlots, ErasureMeta, Orphans, Root, SlotMeta};
|
||||
use crate::blocktree::db::columns::{
|
||||
Coding, Data, DeadSlots, ErasureMeta, Index, Orphans, Root, SlotMeta,
|
||||
};
|
||||
|
||||
vec![
|
||||
Coding::NAME,
|
||||
ErasureMeta::NAME,
|
||||
DeadSlots::NAME,
|
||||
Data::NAME,
|
||||
Index::NAME,
|
||||
Orphans::NAME,
|
||||
Root::NAME,
|
||||
SlotMeta::NAME,
|
||||
@@ -144,6 +160,14 @@ impl Column<Rocks> for cf::Coding {
|
||||
fn index(key: &[u8]) -> (u64, u64) {
|
||||
cf::Data::index(key)
|
||||
}
|
||||
|
||||
fn slot(index: Self::Index) -> Slot {
|
||||
index.0
|
||||
}
|
||||
|
||||
fn as_index(slot: Slot) -> Self::Index {
|
||||
(slot, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Column<Rocks> for cf::Data {
|
||||
@@ -162,6 +186,41 @@ impl Column<Rocks> for cf::Data {
|
||||
let index = BigEndian::read_u64(&key[8..16]);
|
||||
(slot, index)
|
||||
}
|
||||
|
||||
fn slot(index: Self::Index) -> Slot {
|
||||
index.0
|
||||
}
|
||||
|
||||
fn as_index(slot: Slot) -> Self::Index {
|
||||
(slot, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Column<Rocks> for cf::Index {
|
||||
const NAME: &'static str = super::INDEX_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])
|
||||
}
|
||||
|
||||
fn slot(index: Self::Index) -> Slot {
|
||||
index
|
||||
}
|
||||
|
||||
fn as_index(slot: Slot) -> Self::Index {
|
||||
slot
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedColumn<Rocks> for cf::Index {
|
||||
type Type = crate::blocktree::meta::Index;
|
||||
}
|
||||
|
||||
impl Column<Rocks> for cf::DeadSlots {
|
||||
@@ -177,6 +236,14 @@ impl Column<Rocks> for cf::DeadSlots {
|
||||
fn index(key: &[u8]) -> u64 {
|
||||
BigEndian::read_u64(&key[..8])
|
||||
}
|
||||
|
||||
fn slot(index: Self::Index) -> Slot {
|
||||
index
|
||||
}
|
||||
|
||||
fn as_index(slot: Slot) -> Self::Index {
|
||||
slot
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedColumn<Rocks> for cf::DeadSlots {
|
||||
@@ -196,6 +263,14 @@ impl Column<Rocks> for cf::Orphans {
|
||||
fn index(key: &[u8]) -> u64 {
|
||||
BigEndian::read_u64(&key[..8])
|
||||
}
|
||||
|
||||
fn slot(index: Self::Index) -> Slot {
|
||||
index
|
||||
}
|
||||
|
||||
fn as_index(slot: Slot) -> Self::Index {
|
||||
slot
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedColumn<Rocks> for cf::Orphans {
|
||||
@@ -215,6 +290,14 @@ impl Column<Rocks> for cf::Root {
|
||||
fn index(key: &[u8]) -> u64 {
|
||||
BigEndian::read_u64(&key[..8])
|
||||
}
|
||||
|
||||
fn slot(index: Self::Index) -> Slot {
|
||||
index
|
||||
}
|
||||
|
||||
fn as_index(slot: Slot) -> Self::Index {
|
||||
slot
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedColumn<Rocks> for cf::Root {
|
||||
@@ -234,6 +317,14 @@ impl Column<Rocks> for cf::SlotMeta {
|
||||
fn index(key: &[u8]) -> u64 {
|
||||
BigEndian::read_u64(&key[..8])
|
||||
}
|
||||
|
||||
fn slot(index: Self::Index) -> Slot {
|
||||
index
|
||||
}
|
||||
|
||||
fn as_index(slot: Slot) -> Self::Index {
|
||||
slot
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedColumn<Rocks> for cf::SlotMeta {
|
||||
@@ -257,6 +348,14 @@ impl Column<Rocks> for cf::ErasureMeta {
|
||||
BigEndian::write_u64(&mut key[8..], set_index);
|
||||
key
|
||||
}
|
||||
|
||||
fn slot(index: Self::Index) -> Slot {
|
||||
index.0
|
||||
}
|
||||
|
||||
fn as_index(slot: Slot) -> Self::Index {
|
||||
(slot, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedColumn<Rocks> for cf::ErasureMeta {
|
||||
@@ -307,11 +406,27 @@ impl std::convert::From<rocksdb::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cf_options() -> Options {
|
||||
fn get_cf_options(name: &'static str) -> Options {
|
||||
use crate::blocktree::db::columns::{Coding, Data};
|
||||
|
||||
let mut options = Options::default();
|
||||
options.set_max_write_buffer_number(32);
|
||||
options.set_write_buffer_size(MAX_WRITE_BUFFER_SIZE);
|
||||
options.set_max_bytes_for_level_base(MAX_WRITE_BUFFER_SIZE as u64);
|
||||
match name {
|
||||
Coding::NAME | Data::NAME => {
|
||||
// 512MB * 8 = 4GB. 2 of these columns should take no more than 8GB of RAM
|
||||
options.set_max_write_buffer_number(8);
|
||||
options.set_write_buffer_size(MAX_WRITE_BUFFER_SIZE as usize);
|
||||
options.set_target_file_size_base(MAX_WRITE_BUFFER_SIZE / 10);
|
||||
options.set_max_bytes_for_level_base(MAX_WRITE_BUFFER_SIZE);
|
||||
}
|
||||
_ => {
|
||||
// We want smaller CFs to flush faster. This results in more WAL files but lowers
|
||||
// overall WAL space utilization and increases flush frequency
|
||||
options.set_write_buffer_size(MIN_WRITE_BUFFER_SIZE as usize);
|
||||
options.set_target_file_size_base(MIN_WRITE_BUFFER_SIZE);
|
||||
options.set_max_bytes_for_level_base(MIN_WRITE_BUFFER_SIZE);
|
||||
options.set_level_zero_file_num_compaction_trigger(1);
|
||||
}
|
||||
}
|
||||
options
|
||||
}
|
||||
|
||||
@@ -322,8 +437,5 @@ fn get_db_options() -> Options {
|
||||
options.increase_parallelism(TOTAL_THREADS);
|
||||
options.set_max_background_flushes(4);
|
||||
options.set_max_background_compactions(4);
|
||||
options.set_max_write_buffer_number(32);
|
||||
options.set_write_buffer_size(MAX_WRITE_BUFFER_SIZE);
|
||||
options.set_max_bytes_for_level_base(MAX_WRITE_BUFFER_SIZE as u64);
|
||||
options
|
||||
}
|
||||
|
@@ -142,6 +142,7 @@ pub fn process_blocktree(
|
||||
genesis_block: &GenesisBlock,
|
||||
blocktree: &Blocktree,
|
||||
account_paths: Option<String>,
|
||||
verify_ledger: bool,
|
||||
) -> result::Result<(BankForks, Vec<BankForksInfo>, LeaderScheduleCache), BlocktreeProcessorError> {
|
||||
let now = Instant::now();
|
||||
info!("processing ledger...");
|
||||
@@ -166,7 +167,8 @@ pub fn process_blocktree(
|
||||
|
||||
blocktree.set_roots(&[0]).expect("Couldn't set first root");
|
||||
|
||||
let leader_schedule_cache = LeaderScheduleCache::new(*pending_slots[0].2.epoch_schedule(), 0);
|
||||
let leader_schedule_cache =
|
||||
LeaderScheduleCache::new(*pending_slots[0].2.epoch_schedule(), &pending_slots[0].2);
|
||||
|
||||
let mut fork_info = vec![];
|
||||
let mut last_status_report = Instant::now();
|
||||
@@ -204,7 +206,7 @@ pub fn process_blocktree(
|
||||
}
|
||||
|
||||
if !entries.is_empty() {
|
||||
if !entries.verify(&last_entry_hash) {
|
||||
if verify_ledger && !entries.verify(&last_entry_hash) {
|
||||
warn!(
|
||||
"Ledger proof of history failed at slot: {}, entry: {}",
|
||||
slot, entry_height
|
||||
@@ -225,7 +227,7 @@ pub fn process_blocktree(
|
||||
|
||||
if blocktree.is_root(slot) {
|
||||
root = slot;
|
||||
leader_schedule_cache.set_root(slot);
|
||||
leader_schedule_cache.set_root(&bank);
|
||||
bank.squash();
|
||||
pending_slots.clear();
|
||||
fork_info.clear();
|
||||
@@ -304,6 +306,7 @@ pub mod tests {
|
||||
use crate::genesis_utils::{
|
||||
create_genesis_block, create_genesis_block_with_leader, GenesisBlockInfo,
|
||||
};
|
||||
use rand::{thread_rng, Rng};
|
||||
use solana_runtime::epoch_schedule::EpochSchedule;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::instruction::InstructionError;
|
||||
@@ -373,7 +376,7 @@ pub mod tests {
|
||||
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, 2, 1, blockhash);
|
||||
|
||||
let (mut _bank_forks, bank_forks_info, _) =
|
||||
process_blocktree(&genesis_block, &blocktree, None).unwrap();
|
||||
process_blocktree(&genesis_block, &blocktree, None, true).unwrap();
|
||||
|
||||
assert_eq!(bank_forks_info.len(), 1);
|
||||
assert_eq!(
|
||||
@@ -432,7 +435,7 @@ pub mod tests {
|
||||
blocktree.set_roots(&[4, 1, 0]).unwrap();
|
||||
|
||||
let (bank_forks, bank_forks_info, _) =
|
||||
process_blocktree(&genesis_block, &blocktree, None).unwrap();
|
||||
process_blocktree(&genesis_block, &blocktree, None, true).unwrap();
|
||||
|
||||
assert_eq!(bank_forks_info.len(), 1); // One fork, other one is ignored b/c not a descendant of the root
|
||||
|
||||
@@ -506,7 +509,7 @@ pub mod tests {
|
||||
blocktree.set_roots(&[0, 1]).unwrap();
|
||||
|
||||
let (bank_forks, bank_forks_info, _) =
|
||||
process_blocktree(&genesis_block, &blocktree, None).unwrap();
|
||||
process_blocktree(&genesis_block, &blocktree, None, true).unwrap();
|
||||
|
||||
assert_eq!(bank_forks_info.len(), 2); // There are two forks
|
||||
assert_eq!(
|
||||
@@ -587,7 +590,7 @@ pub mod tests {
|
||||
|
||||
// Check that we can properly restart the ledger / leader scheduler doesn't fail
|
||||
let (bank_forks, bank_forks_info, _) =
|
||||
process_blocktree(&genesis_block, &blocktree, None).unwrap();
|
||||
process_blocktree(&genesis_block, &blocktree, None, true).unwrap();
|
||||
|
||||
assert_eq!(bank_forks_info.len(), 1); // There is one fork
|
||||
assert_eq!(
|
||||
@@ -723,7 +726,7 @@ pub mod tests {
|
||||
.unwrap();
|
||||
let entry_height = genesis_block.ticks_per_slot + entries.len() as u64;
|
||||
let (bank_forks, bank_forks_info, _) =
|
||||
process_blocktree(&genesis_block, &blocktree, None).unwrap();
|
||||
process_blocktree(&genesis_block, &blocktree, None, true).unwrap();
|
||||
|
||||
assert_eq!(bank_forks_info.len(), 1);
|
||||
assert_eq!(bank_forks.root(), 0);
|
||||
@@ -754,7 +757,7 @@ pub mod tests {
|
||||
|
||||
let blocktree = Blocktree::open(&ledger_path).unwrap();
|
||||
let (bank_forks, bank_forks_info, _) =
|
||||
process_blocktree(&genesis_block, &blocktree, None).unwrap();
|
||||
process_blocktree(&genesis_block, &blocktree, None, true).unwrap();
|
||||
|
||||
assert_eq!(bank_forks_info.len(), 1);
|
||||
assert_eq!(
|
||||
@@ -1264,7 +1267,7 @@ pub mod tests {
|
||||
} = create_genesis_block(1_000_000_000);
|
||||
let mut bank = Bank::new(&genesis_block);
|
||||
|
||||
const NUM_TRANSFERS: usize = 100;
|
||||
const NUM_TRANSFERS: usize = 128;
|
||||
let keypairs: Vec<_> = (0..NUM_TRANSFERS * 2).map(|_| Keypair::new()).collect();
|
||||
|
||||
// give everybody one lamport
|
||||
@@ -1275,6 +1278,7 @@ pub mod tests {
|
||||
|
||||
let mut i = 0;
|
||||
let mut hash = bank.last_blockhash();
|
||||
let mut root: Option<Arc<Bank>> = None;
|
||||
loop {
|
||||
let entries: Vec<_> = (0..NUM_TRANSFERS)
|
||||
.map(|i| {
|
||||
@@ -1320,9 +1324,19 @@ pub mod tests {
|
||||
)
|
||||
.expect("process ticks failed");
|
||||
|
||||
let parent = Arc::new(bank);
|
||||
|
||||
if i % 16 == 0 {
|
||||
root.map(|old_root| old_root.squash());
|
||||
root = Some(parent.clone());
|
||||
}
|
||||
i += 1;
|
||||
bank = Bank::new_from_parent(&Arc::new(bank), &Pubkey::default(), i as u64);
|
||||
bank.squash();
|
||||
|
||||
bank = Bank::new_from_parent(
|
||||
&parent,
|
||||
&Pubkey::default(),
|
||||
parent.slot() + thread_rng().gen_range(1, 3),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,9 +1,11 @@
|
||||
//! A stage to broadcast data from a leader node to validators
|
||||
use self::broadcast_bad_blob_sizes::BroadcastBadBlobSizes;
|
||||
use self::broadcast_fake_blobs_run::BroadcastFakeBlobsRun;
|
||||
use self::fail_entry_verification_broadcast_run::FailEntryVerificationBroadcastRun;
|
||||
use self::standard_broadcast_run::StandardBroadcastRun;
|
||||
use crate::blocktree::Blocktree;
|
||||
use crate::cluster_info::{ClusterInfo, ClusterInfoError};
|
||||
use crate::erasure::CodingGenerator;
|
||||
use crate::erasure::{CodingGenerator, ErasureConfig};
|
||||
use crate::poh_recorder::WorkingBankEntries;
|
||||
use crate::result::{Error, Result};
|
||||
use crate::service::Service;
|
||||
@@ -20,6 +22,8 @@ use std::sync::{Arc, RwLock};
|
||||
use std::thread::{self, Builder, JoinHandle};
|
||||
use std::time::Instant;
|
||||
|
||||
mod broadcast_bad_blob_sizes;
|
||||
mod broadcast_fake_blobs_run;
|
||||
mod broadcast_utils;
|
||||
mod fail_entry_verification_broadcast_run;
|
||||
mod standard_broadcast_run;
|
||||
@@ -35,6 +39,8 @@ pub enum BroadcastStageReturnType {
|
||||
pub enum BroadcastStageType {
|
||||
Standard,
|
||||
FailEntryVerification,
|
||||
BroadcastFakeBlobs,
|
||||
BroadcastBadBlobSizes,
|
||||
}
|
||||
|
||||
impl BroadcastStageType {
|
||||
@@ -45,6 +51,7 @@ impl BroadcastStageType {
|
||||
receiver: Receiver<WorkingBankEntries>,
|
||||
exit_sender: &Arc<AtomicBool>,
|
||||
blocktree: &Arc<Blocktree>,
|
||||
erasure_config: &ErasureConfig,
|
||||
) -> BroadcastStage {
|
||||
match self {
|
||||
BroadcastStageType::Standard => BroadcastStage::new(
|
||||
@@ -54,6 +61,7 @@ impl BroadcastStageType {
|
||||
exit_sender,
|
||||
blocktree,
|
||||
StandardBroadcastRun::new(),
|
||||
erasure_config,
|
||||
),
|
||||
|
||||
BroadcastStageType::FailEntryVerification => BroadcastStage::new(
|
||||
@@ -63,6 +71,27 @@ impl BroadcastStageType {
|
||||
exit_sender,
|
||||
blocktree,
|
||||
FailEntryVerificationBroadcastRun::new(),
|
||||
erasure_config,
|
||||
),
|
||||
|
||||
BroadcastStageType::BroadcastFakeBlobs => BroadcastStage::new(
|
||||
sock,
|
||||
cluster_info,
|
||||
receiver,
|
||||
exit_sender,
|
||||
blocktree,
|
||||
BroadcastFakeBlobsRun::new(0),
|
||||
erasure_config,
|
||||
),
|
||||
|
||||
BroadcastStageType::BroadcastBadBlobSizes => BroadcastStage::new(
|
||||
sock,
|
||||
cluster_info,
|
||||
receiver,
|
||||
exit_sender,
|
||||
blocktree,
|
||||
BroadcastBadBlobSizes::new(),
|
||||
erasure_config,
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -114,8 +143,9 @@ impl BroadcastStage {
|
||||
receiver: &Receiver<WorkingBankEntries>,
|
||||
blocktree: &Arc<Blocktree>,
|
||||
mut broadcast_stage_run: impl BroadcastRun,
|
||||
erasure_config: &ErasureConfig,
|
||||
) -> BroadcastStageReturnType {
|
||||
let coding_generator = CodingGenerator::default();
|
||||
let coding_generator = CodingGenerator::new_from_config(erasure_config);
|
||||
|
||||
let mut broadcast = Broadcast {
|
||||
coding_generator,
|
||||
@@ -167,9 +197,11 @@ impl BroadcastStage {
|
||||
exit_sender: &Arc<AtomicBool>,
|
||||
blocktree: &Arc<Blocktree>,
|
||||
broadcast_stage_run: impl BroadcastRun + Send + 'static,
|
||||
erasure_config: &ErasureConfig,
|
||||
) -> Self {
|
||||
let blocktree = blocktree.clone();
|
||||
let exit_sender = exit_sender.clone();
|
||||
let erasure_config = *erasure_config;
|
||||
let thread_hdl = Builder::new()
|
||||
.name("solana-broadcaster".to_string())
|
||||
.spawn(move || {
|
||||
@@ -180,6 +212,7 @@ impl BroadcastStage {
|
||||
&receiver,
|
||||
&blocktree,
|
||||
broadcast_stage_run,
|
||||
&erasure_config,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
@@ -208,6 +241,7 @@ mod test {
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::{Arc, RwLock};
|
||||
@@ -222,7 +256,7 @@ mod test {
|
||||
|
||||
fn setup_dummy_broadcast_service(
|
||||
leader_pubkey: &Pubkey,
|
||||
ledger_path: &str,
|
||||
ledger_path: &Path,
|
||||
entry_receiver: Receiver<WorkingBankEntries>,
|
||||
) -> MockBroadcastStage {
|
||||
// Make the database ledger
|
||||
@@ -253,6 +287,7 @@ mod test {
|
||||
&exit_sender,
|
||||
&blocktree,
|
||||
StandardBroadcastRun::new(),
|
||||
&ErasureConfig::default(),
|
||||
);
|
||||
|
||||
MockBroadcastStage {
|
||||
|
84
core/src/broadcast_stage/broadcast_bad_blob_sizes.rs
Normal file
84
core/src/broadcast_stage/broadcast_bad_blob_sizes.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use super::*;
|
||||
use crate::packet::BLOB_HEADER_SIZE;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::signature::Signable;
|
||||
|
||||
pub(super) struct BroadcastBadBlobSizes {}
|
||||
|
||||
impl BroadcastBadBlobSizes {
|
||||
pub(super) fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl BroadcastRun for BroadcastBadBlobSizes {
|
||||
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,
|
||||
);
|
||||
|
||||
for b in data_blobs.iter().chain(coding_blobs.iter()) {
|
||||
let mut w_b = b.write().unwrap();
|
||||
let real_size = w_b.meta.size;
|
||||
// corrupt the size in the header
|
||||
w_b.set_size(std::usize::MAX - BLOB_HEADER_SIZE);
|
||||
// resign the blob
|
||||
w_b.sign(&keypair);
|
||||
// don't corrupt the size in the meta so that broadcast will still work
|
||||
w_b.meta.size = real_size;
|
||||
}
|
||||
|
||||
blocktree.write_shared_blobs(data_blobs.iter())?;
|
||||
blocktree.put_shared_coding_blobs(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(())
|
||||
}
|
||||
}
|
167
core/src/broadcast_stage/broadcast_fake_blobs_run.rs
Normal file
167
core/src/broadcast_stage/broadcast_fake_blobs_run.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
use super::*;
|
||||
use crate::entry::Entry;
|
||||
use solana_sdk::hash::Hash;
|
||||
|
||||
pub(super) struct BroadcastFakeBlobsRun {
|
||||
last_blockhash: Hash,
|
||||
partition: usize,
|
||||
}
|
||||
|
||||
impl BroadcastFakeBlobsRun {
|
||||
pub(super) fn new(partition: usize) -> Self {
|
||||
Self {
|
||||
last_blockhash: Hash::default(),
|
||||
partition,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BroadcastRun for BroadcastFakeBlobsRun {
|
||||
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 bank = receive_results.bank.clone();
|
||||
let last_tick = receive_results.last_tick;
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
// If the last blockhash is default, a new block is being created
|
||||
// So grab the last blockhash from the parent bank
|
||||
if self.last_blockhash == Hash::default() {
|
||||
self.last_blockhash = bank.parent().unwrap().last_blockhash();
|
||||
}
|
||||
|
||||
let fake_ventries: Vec<_> = (0..receive_results.num_entries)
|
||||
.map(|_| vec![(Entry::new(&self.last_blockhash, 0, vec![]), 0)])
|
||||
.collect();
|
||||
|
||||
let (fake_data_blobs, fake_coding_blobs) = broadcast_utils::entries_to_blobs(
|
||||
fake_ventries,
|
||||
&broadcast.thread_pool,
|
||||
latest_blob_index,
|
||||
last_tick,
|
||||
&bank,
|
||||
&keypair,
|
||||
&mut broadcast.coding_generator,
|
||||
);
|
||||
|
||||
// If it's the last tick, reset the last block hash to default
|
||||
// this will cause next run to grab last bank's blockhash
|
||||
if last_tick == bank.max_tick_height() {
|
||||
self.last_blockhash = Hash::default();
|
||||
}
|
||||
|
||||
blocktree.write_shared_blobs(data_blobs.iter())?;
|
||||
blocktree.put_shared_coding_blobs(coding_blobs.iter())?;
|
||||
|
||||
// Set the forwarded flag to true, so that the blobs won't be forwarded to peers
|
||||
data_blobs
|
||||
.iter()
|
||||
.for_each(|blob| blob.write().unwrap().set_forwarded(true));
|
||||
coding_blobs
|
||||
.iter()
|
||||
.for_each(|blob| blob.write().unwrap().set_forwarded(true));
|
||||
fake_data_blobs
|
||||
.iter()
|
||||
.for_each(|blob| blob.write().unwrap().set_forwarded(true));
|
||||
fake_coding_blobs
|
||||
.iter()
|
||||
.for_each(|blob| blob.write().unwrap().set_forwarded(true));
|
||||
|
||||
// 3) Start broadcast step
|
||||
let peers = cluster_info.read().unwrap().tvu_peers();
|
||||
peers.iter().enumerate().for_each(|(i, peer)| {
|
||||
if i <= self.partition {
|
||||
// Send fake blobs to the first N peers
|
||||
fake_data_blobs.iter().for_each(|b| {
|
||||
let blob = b.read().unwrap();
|
||||
sock.send_to(&blob.data[..blob.meta.size], &peer.tvu)
|
||||
.unwrap();
|
||||
});
|
||||
fake_coding_blobs.iter().for_each(|b| {
|
||||
let blob = b.read().unwrap();
|
||||
sock.send_to(&blob.data[..blob.meta.size], &peer.tvu)
|
||||
.unwrap();
|
||||
});
|
||||
} else {
|
||||
data_blobs.iter().for_each(|b| {
|
||||
let blob = b.read().unwrap();
|
||||
sock.send_to(&blob.data[..blob.meta.size], &peer.tvu)
|
||||
.unwrap();
|
||||
});
|
||||
coding_blobs.iter().for_each(|b| {
|
||||
let blob = b.read().unwrap();
|
||||
sock.send_to(&blob.data[..blob.meta.size], &peer.tvu)
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::contact_info::ContactInfo;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
|
||||
#[test]
|
||||
fn test_tvu_peers_ordering() {
|
||||
let mut cluster = ClusterInfo::new_with_invalid_keypair(ContactInfo::new_localhost(
|
||||
&Pubkey::new_rand(),
|
||||
0,
|
||||
));
|
||||
cluster.insert_info(ContactInfo::new_with_socketaddr(&SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)),
|
||||
8080,
|
||||
)));
|
||||
cluster.insert_info(ContactInfo::new_with_socketaddr(&SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2)),
|
||||
8080,
|
||||
)));
|
||||
cluster.insert_info(ContactInfo::new_with_socketaddr(&SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::new(192, 168, 1, 3)),
|
||||
8080,
|
||||
)));
|
||||
cluster.insert_info(ContactInfo::new_with_socketaddr(&SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::new(192, 168, 1, 4)),
|
||||
8080,
|
||||
)));
|
||||
|
||||
let tvu_peers1 = cluster.tvu_peers();
|
||||
(0..5).for_each(|_| {
|
||||
cluster
|
||||
.tvu_peers()
|
||||
.iter()
|
||||
.zip(tvu_peers1.iter())
|
||||
.for_each(|(v1, v2)| {
|
||||
assert_eq!(v1, v2);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@@ -52,7 +52,8 @@ impl BroadcastRun for FailEntryVerificationBroadcastRun {
|
||||
&mut broadcast.coding_generator,
|
||||
);
|
||||
|
||||
blocktree.write_shared_blobs(data_blobs.iter().chain(coding_blobs.iter()))?;
|
||||
blocktree.write_shared_blobs(data_blobs.iter())?;
|
||||
blocktree.put_shared_coding_blobs(coding_blobs.iter())?;
|
||||
|
||||
// 3) Start broadcast step
|
||||
let bank_epoch = bank.get_stakers_epoch(bank.slot());
|
||||
|
@@ -82,7 +82,9 @@ impl BroadcastRun for StandardBroadcastRun {
|
||||
&mut broadcast.coding_generator,
|
||||
);
|
||||
|
||||
blocktree.write_shared_blobs(data_blobs.iter().chain(coding_blobs.iter()))?;
|
||||
blocktree.write_shared_blobs(data_blobs.iter())?;
|
||||
blocktree.put_shared_coding_blobs(coding_blobs.iter())?;
|
||||
|
||||
let to_blobs_elapsed = to_blobs_start.elapsed();
|
||||
|
||||
// 3) Start broadcast step
|
||||
|
@@ -1,5 +1,4 @@
|
||||
use crate::blocktree::Blocktree;
|
||||
use solana_storage_api::SLOTS_PER_SEGMENT;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{BufWriter, Write};
|
||||
@@ -14,6 +13,7 @@ pub const CHACHA_KEY_SIZE: usize = 32;
|
||||
pub fn chacha_cbc_encrypt_ledger(
|
||||
blocktree: &Arc<Blocktree>,
|
||||
slice: u64,
|
||||
slots_per_segment: u64,
|
||||
out_path: &Path,
|
||||
ivec: &mut [u8; CHACHA_BLOCK_SIZE],
|
||||
) -> io::Result<usize> {
|
||||
@@ -28,7 +28,7 @@ pub fn chacha_cbc_encrypt_ledger(
|
||||
let mut entry = slice;
|
||||
|
||||
loop {
|
||||
match blocktree.read_blobs_bytes(0, SLOTS_PER_SEGMENT - total_entries, &mut buffer, entry) {
|
||||
match blocktree.read_blobs_bytes(0, slots_per_segment - total_entries, &mut buffer, entry) {
|
||||
Ok((num_entries, entry_len)) => {
|
||||
debug!(
|
||||
"chacha: encrypting slice: {} num_entries: {} entry_len: {}",
|
||||
@@ -113,10 +113,11 @@ mod tests {
|
||||
let ledger_dir = "chacha_test_encrypt_file";
|
||||
let ledger_path = get_tmp_ledger_path(ledger_dir);
|
||||
let ticks_per_slot = 16;
|
||||
let slots_per_segment = 32;
|
||||
let blocktree = Arc::new(Blocktree::open(&ledger_path).unwrap());
|
||||
let out_path = Path::new("test_chacha_encrypt_file_output.txt.enc");
|
||||
|
||||
let entries = make_tiny_deterministic_test_entries(32);
|
||||
let entries = make_tiny_deterministic_test_entries(slots_per_segment);
|
||||
blocktree
|
||||
.write_entries(0, 0, 0, ticks_per_slot, &entries)
|
||||
.unwrap();
|
||||
@@ -125,7 +126,8 @@ mod tests {
|
||||
"abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234
|
||||
abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234"
|
||||
);
|
||||
chacha_cbc_encrypt_ledger(&blocktree, 0, out_path, &mut key).unwrap();
|
||||
chacha_cbc_encrypt_ledger(&blocktree, 0, slots_per_segment as u64, out_path, &mut key)
|
||||
.unwrap();
|
||||
let mut out_file = File::open(out_path).unwrap();
|
||||
let mut buf = vec![];
|
||||
let size = out_file.read_to_end(&mut buf).unwrap();
|
||||
@@ -133,7 +135,7 @@ mod tests {
|
||||
hasher.hash(&buf[..size]);
|
||||
|
||||
// golden needs to be updated if blob stuff changes....
|
||||
let golden: Hash = "E2HZjSC6VgH4nmEiTbMDATTeBcFjwSYz7QYvU7doGNhD"
|
||||
let golden: Hash = "7hgFLHveuv9zvHpp6qpco9AHAJKyczdgxiktEMkeghDQ"
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
|
@@ -7,7 +7,6 @@ use crate::sigverify::{
|
||||
chacha_cbc_encrypt_many_sample, chacha_end_sha_state, chacha_init_sha_state,
|
||||
};
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_storage_api::SLOTS_PER_SEGMENT;
|
||||
use std::io;
|
||||
use std::mem::size_of;
|
||||
use std::sync::Arc;
|
||||
@@ -19,6 +18,7 @@ use std::sync::Arc;
|
||||
pub fn chacha_cbc_encrypt_file_many_keys(
|
||||
blocktree: &Arc<Blocktree>,
|
||||
segment: u64,
|
||||
slots_per_segment: u64,
|
||||
ivecs: &mut [u8],
|
||||
samples: &[u64],
|
||||
) -> io::Result<Vec<Hash>> {
|
||||
@@ -46,7 +46,7 @@ pub fn chacha_cbc_encrypt_file_many_keys(
|
||||
chacha_init_sha_state(int_sha_states.as_mut_ptr(), num_keys as u32);
|
||||
}
|
||||
loop {
|
||||
match blocktree.read_blobs_bytes(entry, SLOTS_PER_SEGMENT - total_entries, &mut buffer, 0) {
|
||||
match blocktree.read_blobs_bytes(entry, slots_per_segment - total_entries, &mut buffer, 0) {
|
||||
Ok((num_entries, entry_len)) => {
|
||||
debug!(
|
||||
"chacha_cuda: encrypting segment: {} num_entries: {} entry_len: {}",
|
||||
@@ -76,9 +76,9 @@ pub fn chacha_cbc_encrypt_file_many_keys(
|
||||
entry += num_entries;
|
||||
debug!(
|
||||
"total entries: {} entry: {} segment: {} entries_per_segment: {}",
|
||||
total_entries, entry, segment, SLOTS_PER_SEGMENT
|
||||
total_entries, entry, segment, slots_per_segment
|
||||
);
|
||||
if (entry - segment) >= SLOTS_PER_SEGMENT {
|
||||
if (entry - segment) >= slots_per_segment {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -113,6 +113,7 @@ mod tests {
|
||||
use crate::entry::make_tiny_test_entries;
|
||||
use crate::replicator::sample_file;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::timing::DEFAULT_SLOTS_PER_SEGMENT;
|
||||
use std::fs::{remove_dir_all, remove_file};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@@ -121,7 +122,8 @@ mod tests {
|
||||
fn test_encrypt_file_many_keys_single() {
|
||||
solana_logger::setup();
|
||||
|
||||
let entries = make_tiny_test_entries(32);
|
||||
let slots_per_segment = 32;
|
||||
let entries = make_tiny_test_entries(slots_per_segment);
|
||||
let ledger_dir = "test_encrypt_file_many_keys_single";
|
||||
let ledger_path = get_tmp_ledger_path(ledger_dir);
|
||||
let ticks_per_slot = 16;
|
||||
@@ -140,12 +142,25 @@ mod tests {
|
||||
);
|
||||
|
||||
let mut cpu_iv = ivecs.clone();
|
||||
chacha_cbc_encrypt_ledger(&blocktree, 0, out_path, &mut cpu_iv).unwrap();
|
||||
chacha_cbc_encrypt_ledger(
|
||||
&blocktree,
|
||||
0,
|
||||
slots_per_segment as u64,
|
||||
out_path,
|
||||
&mut cpu_iv,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ref_hash = sample_file(&out_path, &samples).unwrap();
|
||||
|
||||
let hashes =
|
||||
chacha_cbc_encrypt_file_many_keys(&blocktree, 0, &mut ivecs, &samples).unwrap();
|
||||
let hashes = chacha_cbc_encrypt_file_many_keys(
|
||||
&blocktree,
|
||||
0,
|
||||
slots_per_segment as u64,
|
||||
&mut ivecs,
|
||||
&samples,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hashes[0], ref_hash);
|
||||
|
||||
@@ -178,7 +193,14 @@ mod tests {
|
||||
);
|
||||
ivec[0] = i;
|
||||
ivecs.extend(ivec.clone().iter());
|
||||
chacha_cbc_encrypt_ledger(&blocktree.clone(), 0, out_path, &mut ivec).unwrap();
|
||||
chacha_cbc_encrypt_ledger(
|
||||
&blocktree.clone(),
|
||||
0,
|
||||
DEFAULT_SLOTS_PER_SEGMENT,
|
||||
out_path,
|
||||
&mut ivec,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
ref_hashes.push(sample_file(&out_path, &samples).unwrap());
|
||||
info!(
|
||||
@@ -189,8 +211,14 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
let hashes =
|
||||
chacha_cbc_encrypt_file_many_keys(&blocktree, 0, &mut ivecs, &samples).unwrap();
|
||||
let hashes = chacha_cbc_encrypt_file_many_keys(
|
||||
&blocktree,
|
||||
0,
|
||||
DEFAULT_SLOTS_PER_SEGMENT,
|
||||
&mut ivecs,
|
||||
&samples,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hashes, ref_hashes);
|
||||
|
||||
@@ -205,6 +233,13 @@ mod tests {
|
||||
let ledger_path = get_tmp_ledger_path(ledger_dir);
|
||||
let samples = [0];
|
||||
let blocktree = Arc::new(Blocktree::open(&ledger_path).unwrap());
|
||||
assert!(chacha_cbc_encrypt_file_many_keys(&blocktree, 0, &mut keys, &samples,).is_err());
|
||||
assert!(chacha_cbc_encrypt_file_many_keys(
|
||||
&blocktree,
|
||||
0,
|
||||
DEFAULT_SLOTS_PER_SEGMENT,
|
||||
&mut keys,
|
||||
&samples,
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
|
@@ -32,9 +32,7 @@ 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, inc_new_counter_warn,
|
||||
};
|
||||
use solana_metrics::{datapoint_debug, inc_new_counter_debug, inc_new_counter_error};
|
||||
use solana_netutil::{
|
||||
bind_in_range, bind_to, find_available_port_in_range, multi_bind_in_range, PortRange,
|
||||
};
|
||||
@@ -47,7 +45,7 @@ use solana_sdk::transaction::Transaction;
|
||||
use std::borrow::Borrow;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::min;
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::collections::{BTreeSet, HashMap, HashSet};
|
||||
use std::fmt;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
@@ -65,6 +63,9 @@ pub const GOSSIP_SLEEP_MILLIS: u64 = 100;
|
||||
/// the number of slots to respond with when responding to `Orphan` requests
|
||||
pub const MAX_ORPHAN_REPAIR_RESPONSES: usize = 10;
|
||||
|
||||
/// Allow protocol messages to carry only 1KB of data a time
|
||||
const TARGET_PROTOCOL_PAYLOAD_SIZE: u64 = 1024;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ClusterInfoError {
|
||||
NoPeers,
|
||||
@@ -78,9 +79,6 @@ pub struct ClusterInfo {
|
||||
pub gossip: CrdsGossip,
|
||||
/// set the keypair that will be used to sign crds values generated. It is unset only in tests.
|
||||
pub(crate) keypair: Arc<Keypair>,
|
||||
// TODO: remove gossip_leader_pubkey once all usage of `set_leader()` and `leader_data()` is
|
||||
// purged
|
||||
gossip_leader_pubkey: Pubkey,
|
||||
/// The network entrypoint
|
||||
entrypoint: Option<ContactInfo>,
|
||||
}
|
||||
@@ -181,7 +179,6 @@ impl ClusterInfo {
|
||||
let mut me = Self {
|
||||
gossip: CrdsGossip::default(),
|
||||
keypair,
|
||||
gossip_leader_pubkey: Pubkey::default(),
|
||||
entrypoint: None,
|
||||
};
|
||||
let id = contact_info.id;
|
||||
@@ -206,7 +203,8 @@ impl ClusterInfo {
|
||||
let mut entry = CrdsValue::ContactInfo(my_data);
|
||||
entry.sign(&self.keypair);
|
||||
self.gossip.refresh_push_active_set(stakes);
|
||||
self.gossip.process_push_message(vec![entry], now);
|
||||
self.gossip
|
||||
.process_push_message(&self.id(), vec![entry], now);
|
||||
}
|
||||
|
||||
// TODO kill insert_info, only used by tests
|
||||
@@ -236,15 +234,6 @@ impl ClusterInfo {
|
||||
self.lookup(&self.id()).cloned().unwrap()
|
||||
}
|
||||
|
||||
// Deprecated: don't use leader_data().
|
||||
pub fn leader_data(&self) -> Option<&ContactInfo> {
|
||||
let leader_pubkey = self.gossip_leader_pubkey;
|
||||
if leader_pubkey == Pubkey::default() {
|
||||
return None;
|
||||
}
|
||||
self.lookup(&leader_pubkey)
|
||||
}
|
||||
|
||||
pub fn contact_info_trace(&self) -> String {
|
||||
let now = timestamp();
|
||||
let mut spy_nodes = 0;
|
||||
@@ -301,22 +290,12 @@ impl ClusterInfo {
|
||||
)
|
||||
}
|
||||
|
||||
/// Record the id of the current leader for use by `leader_tpu_via_blobs()`
|
||||
pub fn set_leader(&mut self, leader_pubkey: &Pubkey) {
|
||||
if *leader_pubkey != self.gossip_leader_pubkey {
|
||||
warn!(
|
||||
"{}: LEADER_UPDATE TO {} from {}",
|
||||
self.gossip.id, leader_pubkey, self.gossip_leader_pubkey,
|
||||
);
|
||||
self.gossip_leader_pubkey = *leader_pubkey;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_epoch_slots(&mut self, id: Pubkey, root: u64, slots: BTreeSet<u64>) {
|
||||
let now = timestamp();
|
||||
let mut entry = CrdsValue::EpochSlots(EpochSlots::new(id, root, slots, now));
|
||||
entry.sign(&self.keypair);
|
||||
self.gossip.process_push_message(vec![entry], now);
|
||||
self.gossip
|
||||
.process_push_message(&self.id(), vec![entry], now);
|
||||
}
|
||||
|
||||
pub fn push_vote(&mut self, vote: Transaction) {
|
||||
@@ -324,7 +303,8 @@ impl ClusterInfo {
|
||||
let vote = Vote::new(&self.id(), vote, now);
|
||||
let mut entry = CrdsValue::Vote(vote);
|
||||
entry.sign(&self.keypair);
|
||||
self.gossip.process_push_message(vec![entry], now);
|
||||
self.gossip
|
||||
.process_push_message(&self.id(), vec![entry], now);
|
||||
}
|
||||
|
||||
/// Get votes in the crds
|
||||
@@ -740,15 +720,16 @@ impl ClusterInfo {
|
||||
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);
|
||||
}
|
||||
datapoint_info!(
|
||||
"cluster_info-num_nodes",
|
||||
("count", broadcast_table_len + 1, i64)
|
||||
);
|
||||
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],
|
||||
@@ -871,6 +852,29 @@ impl ClusterInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits a Vec of CrdsValues into a nested Vec, trying to make sure that
|
||||
/// each Vec is no larger than `PROTOCOL_PAYLOAD_SIZE`
|
||||
/// Note: some messages cannot be contained within that size so in the worst case this returns
|
||||
/// N nested Vecs with 1 item each.
|
||||
fn split_gossip_messages(mut msgs: Vec<CrdsValue>) -> Vec<Vec<CrdsValue>> {
|
||||
let mut messages = vec![];
|
||||
while !msgs.is_empty() {
|
||||
let mut size = 0;
|
||||
let mut payload = vec![];
|
||||
while let Some(msg) = msgs.pop() {
|
||||
// always put at least one msg. The PROTOCOL_PAYLOAD_SIZE is not a hard limit
|
||||
let msg_size = msg.size();
|
||||
size += msg_size;
|
||||
payload.push(msg);
|
||||
if size > TARGET_PROTOCOL_PAYLOAD_SIZE {
|
||||
break;
|
||||
}
|
||||
}
|
||||
messages.push(payload);
|
||||
}
|
||||
messages
|
||||
}
|
||||
|
||||
fn new_pull_requests(&mut self, stakes: &HashMap<Pubkey, u64>) -> Vec<(SocketAddr, Protocol)> {
|
||||
let now = timestamp();
|
||||
let pulls: Vec<_> = self
|
||||
@@ -914,7 +918,12 @@ impl ClusterInfo {
|
||||
.and_then(CrdsValue::contact_info)
|
||||
.map(|p| (p.gossip, messages))
|
||||
})
|
||||
.map(|(peer, msgs)| (peer, Protocol::PushMessage(self_id, msgs)))
|
||||
.map(|(peer, msgs)| {
|
||||
Self::split_gossip_messages(msgs)
|
||||
.into_iter()
|
||||
.map(move |payload| (peer, Protocol::PushMessage(self_id, payload)))
|
||||
})
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -1071,12 +1080,13 @@ impl ClusterInfo {
|
||||
fn handle_blob(
|
||||
obj: &Arc<RwLock<Self>>,
|
||||
blocktree: Option<&Arc<Blocktree>>,
|
||||
stakes: &HashMap<Pubkey, u64>,
|
||||
blob: &Blob,
|
||||
) -> Vec<SharedBlob> {
|
||||
deserialize(&blob.data[..blob.meta.size])
|
||||
.into_iter()
|
||||
.flat_map(|request| {
|
||||
ClusterInfo::handle_protocol(obj, &blob.meta.addr(), blocktree, request)
|
||||
ClusterInfo::handle_protocol(obj, &blob.meta.addr(), blocktree, stakes, request)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -1092,7 +1102,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={}",
|
||||
@@ -1109,17 +1119,20 @@ impl ClusterInfo {
|
||||
.process_pull_request(caller, filter, now);
|
||||
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;
|
||||
}
|
||||
let responses: Vec<_> = Self::split_gossip_messages(data)
|
||||
.into_iter()
|
||||
.map(move |payload| Protocol::PullResponse(self_id, payload))
|
||||
.collect();
|
||||
// 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()
|
||||
responses
|
||||
.into_iter()
|
||||
.map(|rsp| to_shared_blob(rsp, *from_addr).ok().into_iter())
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn handle_pull_response(me: &Arc<RwLock<Self>>, from: &Pubkey, data: Vec<CrdsValue>) {
|
||||
let len = data.len();
|
||||
let now = Instant::now();
|
||||
@@ -1134,40 +1147,52 @@ impl ClusterInfo {
|
||||
|
||||
report_time_spent("ReceiveUpdates", &now.elapsed(), &format!(" len: {}", len));
|
||||
}
|
||||
|
||||
fn handle_push_message(
|
||||
me: &Arc<RwLock<Self>>,
|
||||
from: &Pubkey,
|
||||
data: Vec<CrdsValue>,
|
||||
stakes: &HashMap<Pubkey, u64>,
|
||||
) -> Vec<SharedBlob> {
|
||||
let self_id = me.read().unwrap().gossip.id;
|
||||
inc_new_counter_debug!("cluster_info-push_message", 1, 0, 1000);
|
||||
|
||||
let prunes: Vec<_> = me
|
||||
let updated: Vec<_> =
|
||||
me.write()
|
||||
.unwrap()
|
||||
.gossip
|
||||
.process_push_message(from, data, timestamp());
|
||||
|
||||
let updated_labels: Vec<_> = updated.into_iter().map(|u| u.value.label()).collect();
|
||||
let prunes_map: HashMap<Pubkey, HashSet<Pubkey>> = me
|
||||
.write()
|
||||
.unwrap()
|
||||
.gossip
|
||||
.process_push_message(data, timestamp());
|
||||
.prune_received_cache(updated_labels, stakes);
|
||||
|
||||
if !prunes.is_empty() {
|
||||
inc_new_counter_debug!("cluster_info-push_message-prunes", prunes.len());
|
||||
let ci = me.read().unwrap().lookup(from).cloned();
|
||||
let pushes: Vec<_> = me.write().unwrap().new_push_requests();
|
||||
inc_new_counter_debug!("cluster_info-push_message-pushes", pushes.len());
|
||||
let mut rsp: Vec<_> = ci
|
||||
.and_then(|ci| {
|
||||
let mut rsp: Vec<_> = prunes_map
|
||||
.into_iter()
|
||||
.map(|(from, prune_set)| {
|
||||
inc_new_counter_debug!("cluster_info-push_message-prunes", prune_set.len());
|
||||
me.read().unwrap().lookup(&from).cloned().and_then(|ci| {
|
||||
let mut prune_msg = PruneData {
|
||||
pubkey: self_id,
|
||||
prunes,
|
||||
prunes: prune_set.into_iter().collect(),
|
||||
signature: Signature::default(),
|
||||
destination: *from,
|
||||
destination: from,
|
||||
wallclock: timestamp(),
|
||||
};
|
||||
prune_msg.sign(&me.read().unwrap().keypair);
|
||||
let rsp = Protocol::PruneMessage(self_id, prune_msg);
|
||||
to_shared_blob(rsp, ci.gossip).ok()
|
||||
})
|
||||
.into_iter()
|
||||
.collect();
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
if !rsp.is_empty() {
|
||||
let pushes: Vec<_> = me.write().unwrap().new_push_requests();
|
||||
inc_new_counter_debug!("cluster_info-push_message-pushes", pushes.len());
|
||||
let mut blobs: Vec<_> = pushes
|
||||
.into_iter()
|
||||
.filter_map(|(remote_gossip_addr, req)| {
|
||||
@@ -1269,6 +1294,7 @@ impl ClusterInfo {
|
||||
me: &Arc<RwLock<Self>>,
|
||||
from_addr: &SocketAddr,
|
||||
blocktree: Option<&Arc<Blocktree>>,
|
||||
stakes: &HashMap<Pubkey, u64>,
|
||||
request: Protocol,
|
||||
) -> Vec<SharedBlob> {
|
||||
match request {
|
||||
@@ -1300,7 +1326,7 @@ impl ClusterInfo {
|
||||
}
|
||||
ret
|
||||
});
|
||||
Self::handle_push_message(me, &from, data)
|
||||
Self::handle_push_message(me, &from, data, stakes)
|
||||
}
|
||||
Protocol::PruneMessage(from, data) => {
|
||||
if data.verify() {
|
||||
@@ -1335,6 +1361,7 @@ impl ClusterInfo {
|
||||
fn run_listen(
|
||||
obj: &Arc<RwLock<Self>>,
|
||||
blocktree: Option<&Arc<Blocktree>>,
|
||||
bank_forks: Option<&Arc<RwLock<BankForks>>>,
|
||||
requests_receiver: &BlobReceiver,
|
||||
response_sender: &BlobSender,
|
||||
) -> Result<()> {
|
||||
@@ -1345,8 +1372,16 @@ impl ClusterInfo {
|
||||
reqs.append(&mut more);
|
||||
}
|
||||
let mut resps = Vec::new();
|
||||
|
||||
let stakes: HashMap<_, _> = match bank_forks {
|
||||
Some(ref bank_forks) => {
|
||||
staking_utils::staked_nodes(&bank_forks.read().unwrap().working_bank())
|
||||
}
|
||||
None => HashMap::new(),
|
||||
};
|
||||
|
||||
for req in reqs {
|
||||
let mut resp = Self::handle_blob(obj, blocktree, &req.read().unwrap());
|
||||
let mut resp = Self::handle_blob(obj, blocktree, &stakes, &req.read().unwrap());
|
||||
resps.append(&mut resp);
|
||||
}
|
||||
response_sender.send(resps)?;
|
||||
@@ -1355,6 +1390,7 @@ impl ClusterInfo {
|
||||
pub fn listen(
|
||||
me: Arc<RwLock<Self>>,
|
||||
blocktree: Option<Arc<Blocktree>>,
|
||||
bank_forks: Option<Arc<RwLock<BankForks>>>,
|
||||
requests_receiver: BlobReceiver,
|
||||
response_sender: BlobSender,
|
||||
exit: &Arc<AtomicBool>,
|
||||
@@ -1366,6 +1402,7 @@ impl ClusterInfo {
|
||||
let e = Self::run_listen(
|
||||
&me,
|
||||
blocktree.as_ref(),
|
||||
bank_forks.as_ref(),
|
||||
&requests_receiver,
|
||||
&response_sender,
|
||||
);
|
||||
@@ -1464,7 +1501,7 @@ pub struct Sockets {
|
||||
pub gossip: UdpSocket,
|
||||
pub tvu: Vec<UdpSocket>,
|
||||
pub tpu: Vec<UdpSocket>,
|
||||
pub tpu_via_blobs: Vec<UdpSocket>,
|
||||
pub tpu_forwards: Vec<UdpSocket>,
|
||||
pub broadcast: UdpSocket,
|
||||
pub repair: UdpSocket,
|
||||
pub retransmit: UdpSocket,
|
||||
@@ -1509,7 +1546,7 @@ impl Node {
|
||||
gossip,
|
||||
tvu: vec![tvu],
|
||||
tpu: vec![],
|
||||
tpu_via_blobs: vec![],
|
||||
tpu_forwards: vec![],
|
||||
broadcast,
|
||||
repair,
|
||||
retransmit,
|
||||
@@ -1521,7 +1558,7 @@ impl Node {
|
||||
let tpu = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let gossip = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let tvu = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let tpu_via_blobs = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let tpu_forwards = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let repair = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let rpc_port = find_available_port_in_range((1024, 65535)).unwrap();
|
||||
let rpc_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), rpc_port);
|
||||
@@ -1537,7 +1574,7 @@ impl Node {
|
||||
gossip.local_addr().unwrap(),
|
||||
tvu.local_addr().unwrap(),
|
||||
tpu.local_addr().unwrap(),
|
||||
tpu_via_blobs.local_addr().unwrap(),
|
||||
tpu_forwards.local_addr().unwrap(),
|
||||
storage.local_addr().unwrap(),
|
||||
rpc_addr,
|
||||
rpc_pubsub_addr,
|
||||
@@ -1549,7 +1586,7 @@ impl Node {
|
||||
gossip,
|
||||
tvu: vec![tvu],
|
||||
tpu: vec![tpu],
|
||||
tpu_via_blobs: vec![tpu_via_blobs],
|
||||
tpu_forwards: vec![tpu_forwards],
|
||||
broadcast,
|
||||
repair,
|
||||
retransmit,
|
||||
@@ -1583,7 +1620,7 @@ impl Node {
|
||||
|
||||
let (tpu_port, tpu_sockets) = multi_bind_in_range(port_range, 32).expect("tpu multi_bind");
|
||||
|
||||
let (tpu_via_blobs_port, tpu_via_blobs_sockets) =
|
||||
let (tpu_forwards_port, tpu_forwards_sockets) =
|
||||
multi_bind_in_range(port_range, 8).expect("tpu multi_bind");
|
||||
|
||||
let (_, repair) = Self::bind(port_range);
|
||||
@@ -1595,7 +1632,7 @@ impl Node {
|
||||
SocketAddr::new(gossip_addr.ip(), gossip_port),
|
||||
SocketAddr::new(gossip_addr.ip(), tvu_port),
|
||||
SocketAddr::new(gossip_addr.ip(), tpu_port),
|
||||
SocketAddr::new(gossip_addr.ip(), tpu_via_blobs_port),
|
||||
SocketAddr::new(gossip_addr.ip(), tpu_forwards_port),
|
||||
socketaddr_any!(),
|
||||
socketaddr_any!(),
|
||||
socketaddr_any!(),
|
||||
@@ -1609,7 +1646,7 @@ impl Node {
|
||||
gossip,
|
||||
tvu: tvu_sockets,
|
||||
tpu: tpu_sockets,
|
||||
tpu_via_blobs: tpu_via_blobs_sockets,
|
||||
tpu_forwards: tpu_forwards_sockets,
|
||||
broadcast,
|
||||
repair,
|
||||
retransmit,
|
||||
@@ -1630,9 +1667,9 @@ impl Node {
|
||||
|
||||
let empty = socketaddr_any!();
|
||||
new.info.tpu = empty;
|
||||
new.info.tpu_via_blobs = empty;
|
||||
new.info.tpu_forwards = empty;
|
||||
new.sockets.tpu = vec![];
|
||||
new.sockets.tpu_via_blobs = vec![];
|
||||
new.sockets.tpu_forwards = vec![];
|
||||
|
||||
new
|
||||
}
|
||||
@@ -1652,6 +1689,7 @@ mod tests {
|
||||
use crate::blocktree::tests::make_many_slot_entries;
|
||||
use crate::blocktree::Blocktree;
|
||||
use crate::crds_value::CrdsValueLabel;
|
||||
use crate::erasure::ErasureConfig;
|
||||
use crate::packet::BLOB_HEADER_SIZE;
|
||||
use crate::repair_service::RepairType;
|
||||
use crate::result::Error;
|
||||
@@ -1816,6 +1854,7 @@ mod tests {
|
||||
w_blob.set_size(data_size);
|
||||
w_blob.set_index(1);
|
||||
w_blob.set_slot(2);
|
||||
w_blob.set_erasure_config(&ErasureConfig::default());
|
||||
w_blob.meta.size = data_size + BLOB_HEADER_SIZE;
|
||||
}
|
||||
|
||||
@@ -1860,6 +1899,7 @@ mod tests {
|
||||
blob.set_size(data_size);
|
||||
blob.set_index(i);
|
||||
blob.set_slot(2);
|
||||
blob.set_erasure_config(&ErasureConfig::default());
|
||||
blob.meta.size = data_size + BLOB_HEADER_SIZE;
|
||||
blob
|
||||
})
|
||||
@@ -1925,17 +1965,6 @@ mod tests {
|
||||
Blocktree::destroy(&ledger_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_leader() {
|
||||
solana_logger::setup();
|
||||
let contact_info = ContactInfo::new_localhost(&Pubkey::new_rand(), 0);
|
||||
let mut cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
|
||||
let network_entry_point =
|
||||
ContactInfo::new_gossip_entry_point(&socketaddr!("127.0.0.1:1239"));
|
||||
cluster_info.insert_info(network_entry_point);
|
||||
assert!(cluster_info.leader_data().is_none());
|
||||
}
|
||||
|
||||
fn assert_in_range(x: u16, range: (u16, u16)) {
|
||||
assert!(x >= range.0);
|
||||
assert!(x < range.1);
|
||||
@@ -2019,12 +2048,9 @@ mod tests {
|
||||
//create new cluster info, leader, and peer
|
||||
let keypair = Keypair::new();
|
||||
let peer_keypair = Keypair::new();
|
||||
let leader_keypair = Keypair::new();
|
||||
let contact_info = ContactInfo::new_localhost(&keypair.pubkey(), 0);
|
||||
let leader = ContactInfo::new_localhost(&leader_keypair.pubkey(), 0);
|
||||
let peer = ContactInfo::new_localhost(&peer_keypair.pubkey(), 0);
|
||||
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
|
||||
@@ -2213,45 +2239,79 @@ mod tests {
|
||||
assert_eq!(votes, vec![]);
|
||||
assert_eq!(max_ts, new_max_ts);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_add_entrypoint() {
|
||||
let node_keypair = Arc::new(Keypair::new());
|
||||
let mut cluster_info = ClusterInfo::new(
|
||||
ContactInfo::new_localhost(&node_keypair.pubkey(), timestamp()),
|
||||
node_keypair,
|
||||
);
|
||||
let entrypoint_pubkey = Pubkey::new_rand();
|
||||
let entrypoint = ContactInfo::new_localhost(&entrypoint_pubkey, timestamp());
|
||||
cluster_info.set_entrypoint(entrypoint.clone());
|
||||
let pulls = cluster_info.new_pull_requests(&HashMap::new());
|
||||
assert_eq!(1, pulls.len());
|
||||
match pulls.get(0) {
|
||||
Some((addr, msg)) => {
|
||||
assert_eq!(*addr, entrypoint.gossip);
|
||||
match msg {
|
||||
Protocol::PullRequest(_, value) => {
|
||||
assert!(value.verify());
|
||||
assert_eq!(value.pubkey(), cluster_info.id())
|
||||
|
||||
#[test]
|
||||
fn test_add_entrypoint() {
|
||||
let node_keypair = Arc::new(Keypair::new());
|
||||
let mut cluster_info = ClusterInfo::new(
|
||||
ContactInfo::new_localhost(&node_keypair.pubkey(), timestamp()),
|
||||
node_keypair,
|
||||
);
|
||||
let entrypoint_pubkey = Pubkey::new_rand();
|
||||
let entrypoint = ContactInfo::new_localhost(&entrypoint_pubkey, timestamp());
|
||||
cluster_info.set_entrypoint(entrypoint.clone());
|
||||
let pulls = cluster_info.new_pull_requests(&HashMap::new());
|
||||
assert_eq!(1, pulls.len());
|
||||
match pulls.get(0) {
|
||||
Some((addr, msg)) => {
|
||||
assert_eq!(*addr, entrypoint.gossip);
|
||||
match msg {
|
||||
Protocol::PullRequest(_, value) => {
|
||||
assert!(value.verify());
|
||||
assert_eq!(value.pubkey(), cluster_info.id())
|
||||
}
|
||||
_ => panic!("wrong protocol"),
|
||||
}
|
||||
_ => panic!("wrong protocol"),
|
||||
}
|
||||
None => panic!("entrypoint should be a pull destination"),
|
||||
}
|
||||
None => panic!("entrypoint should be a pull destination"),
|
||||
|
||||
// now add this message back to the table and make sure after the next pull, the entrypoint is unset
|
||||
let entrypoint_crdsvalue = CrdsValue::ContactInfo(entrypoint.clone());
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
ClusterInfo::handle_pull_response(
|
||||
&cluster_info,
|
||||
&entrypoint_pubkey,
|
||||
vec![entrypoint_crdsvalue],
|
||||
);
|
||||
let pulls = cluster_info
|
||||
.write()
|
||||
.unwrap()
|
||||
.new_pull_requests(&HashMap::new());
|
||||
assert_eq!(1, pulls.len());
|
||||
assert_eq!(cluster_info.read().unwrap().entrypoint, Some(entrypoint));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_messages_small() {
|
||||
let value = CrdsValue::ContactInfo(ContactInfo::default());
|
||||
test_split_messages(value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_messages_large() {
|
||||
let mut btree_slots = BTreeSet::new();
|
||||
for i in 0..128 {
|
||||
btree_slots.insert(i);
|
||||
}
|
||||
let value = CrdsValue::EpochSlots(EpochSlots {
|
||||
from: Pubkey::default(),
|
||||
root: 0,
|
||||
slots: btree_slots,
|
||||
signature: Signature::default(),
|
||||
wallclock: 0,
|
||||
});
|
||||
test_split_messages(value);
|
||||
}
|
||||
|
||||
fn test_split_messages(value: CrdsValue) {
|
||||
const NUM_VALUES: usize = 30;
|
||||
let value_size = value.size();
|
||||
let expected_len = NUM_VALUES / (TARGET_PROTOCOL_PAYLOAD_SIZE / value_size).max(1) as usize;
|
||||
let msgs = vec![value; NUM_VALUES];
|
||||
|
||||
let split = ClusterInfo::split_gossip_messages(msgs);
|
||||
assert!(split.len() <= expected_len);
|
||||
}
|
||||
|
||||
// now add this message back to the table and make sure after the next pull, the entrypoint is unset
|
||||
let entrypoint_crdsvalue = CrdsValue::ContactInfo(entrypoint.clone());
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
ClusterInfo::handle_pull_response(
|
||||
&cluster_info,
|
||||
&entrypoint_pubkey,
|
||||
vec![entrypoint_crdsvalue],
|
||||
);
|
||||
let pulls = cluster_info
|
||||
.write()
|
||||
.unwrap()
|
||||
.new_pull_requests(&HashMap::new());
|
||||
assert_eq!(1, pulls.len());
|
||||
assert_eq!(cluster_info.read().unwrap().entrypoint, Some(entrypoint));
|
||||
}
|
||||
|
@@ -234,6 +234,7 @@ impl ClusterInfoRepairListener {
|
||||
|
||||
let _ = Self::serve_repairs_to_repairee(
|
||||
my_pubkey,
|
||||
repairee_pubkey,
|
||||
my_root,
|
||||
blocktree,
|
||||
&repairee_epoch_slots,
|
||||
@@ -249,8 +250,10 @@ impl ClusterInfoRepairListener {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn serve_repairs_to_repairee(
|
||||
my_pubkey: &Pubkey,
|
||||
repairee_pubkey: &Pubkey,
|
||||
my_root: u64,
|
||||
blocktree: &Blocktree,
|
||||
repairee_epoch_slots: &EpochSlots,
|
||||
@@ -260,14 +263,16 @@ impl ClusterInfoRepairListener {
|
||||
num_slots_to_repair: usize,
|
||||
epoch_schedule: &EpochSchedule,
|
||||
) -> Result<()> {
|
||||
let slot_iter = blocktree.rooted_slot_iterator(repairee_epoch_slots.root + 1);
|
||||
|
||||
let slot_iter = blocktree.rooted_slot_iterator(repairee_epoch_slots.root);
|
||||
if slot_iter.is_err() {
|
||||
warn!("Root for repairee is on different fork OR replay_stage hasn't marked this slot as root yet");
|
||||
info!(
|
||||
"Root for repairee is on different fork. My root: {}, repairee_root: {} repairee_pubkey: {:?}",
|
||||
my_root, repairee_epoch_slots.root, repairee_pubkey,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let slot_iter = slot_iter?;
|
||||
let mut slot_iter = slot_iter?;
|
||||
|
||||
let mut total_data_blobs_sent = 0;
|
||||
let mut total_coding_blobs_sent = 0;
|
||||
@@ -276,6 +281,10 @@ impl ClusterInfoRepairListener {
|
||||
epoch_schedule.get_stakers_epoch(repairee_epoch_slots.root);
|
||||
let max_confirmed_repairee_slot =
|
||||
epoch_schedule.get_last_slot_in_epoch(max_confirmed_repairee_epoch);
|
||||
|
||||
// Skip the first slot in the iterator because we know it's the root slot which the repairee
|
||||
// already has
|
||||
slot_iter.next();
|
||||
for (slot, slot_meta) in slot_iter {
|
||||
if slot > my_root
|
||||
|| num_slots_repaired >= num_slots_to_repair
|
||||
@@ -650,6 +659,7 @@ mod tests {
|
||||
for repairman_pubkey in &eligible_repairmen {
|
||||
ClusterInfoRepairListener::serve_repairs_to_repairee(
|
||||
&repairman_pubkey,
|
||||
&mock_repairee.id,
|
||||
num_slots - 1,
|
||||
&blocktree,
|
||||
&repairee_epoch_slots,
|
||||
@@ -719,6 +729,7 @@ mod tests {
|
||||
|
||||
ClusterInfoRepairListener::serve_repairs_to_repairee(
|
||||
&my_pubkey,
|
||||
&mock_repairee.id,
|
||||
total_slots - 1,
|
||||
&blocktree,
|
||||
&repairee_epoch_slots,
|
||||
@@ -740,6 +751,7 @@ mod tests {
|
||||
EpochSlots::new(mock_repairee.id, stakers_slot_offset, repairee_slots, 1);
|
||||
ClusterInfoRepairListener::serve_repairs_to_repairee(
|
||||
&my_pubkey,
|
||||
&mock_repairee.id,
|
||||
total_slots - 1,
|
||||
&blocktree,
|
||||
&repairee_epoch_slots,
|
||||
|
@@ -4,9 +4,9 @@ use crate::result::Result;
|
||||
use crate::service::Service;
|
||||
use crate::sigverify_stage::VerifiedPackets;
|
||||
use crate::{packet, sigverify};
|
||||
use crossbeam_channel::Sender as CrossbeamSender;
|
||||
use solana_metrics::inc_new_counter_debug;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread::{self, sleep, Builder, JoinHandle};
|
||||
use std::time::Duration;
|
||||
@@ -20,7 +20,7 @@ impl ClusterInfoVoteListener {
|
||||
exit: &Arc<AtomicBool>,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
sigverify_disabled: bool,
|
||||
sender: Sender<VerifiedPackets>,
|
||||
sender: CrossbeamSender<VerifiedPackets>,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
) -> Self {
|
||||
let exit = exit.clone();
|
||||
@@ -45,7 +45,7 @@ impl ClusterInfoVoteListener {
|
||||
exit: Arc<AtomicBool>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
sigverify_disabled: bool,
|
||||
sender: &Sender<VerifiedPackets>,
|
||||
sender: &CrossbeamSender<VerifiedPackets>,
|
||||
poh_recorder: Arc<Mutex<PohRecorder>>,
|
||||
) -> Result<()> {
|
||||
let mut last_ts = 0;
|
||||
@@ -54,7 +54,7 @@ impl ClusterInfoVoteListener {
|
||||
return Ok(());
|
||||
}
|
||||
let (votes, new_ts) = cluster_info.read().unwrap().get_votes(last_ts);
|
||||
if poh_recorder.lock().unwrap().bank().is_some() {
|
||||
if poh_recorder.lock().unwrap().has_bank() {
|
||||
last_ts = new_ts;
|
||||
inc_new_counter_debug!("cluster_info_vote_listener-recv_count", votes.len());
|
||||
let msgs = packet::to_packets(&votes);
|
||||
@@ -85,7 +85,7 @@ impl Service for ClusterInfoVoteListener {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::locktower::MAX_RECENT_VOTES;
|
||||
use crate::consensus::MAX_RECENT_VOTES;
|
||||
use crate::packet;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
|
@@ -4,10 +4,10 @@ use crate::blocktree::Blocktree;
|
||||
/// All tests must start from an entry point and a funding keypair and
|
||||
/// discover the rest of the network.
|
||||
use crate::cluster_info::FULLNODE_PORT_RANGE;
|
||||
use crate::consensus::VOTE_THRESHOLD_DEPTH;
|
||||
use crate::contact_info::ContactInfo;
|
||||
use crate::entry::{Entry, EntrySlice};
|
||||
use crate::gossip_service::discover_cluster;
|
||||
use crate::locktower::VOTE_THRESHOLD_DEPTH;
|
||||
use hashbrown::HashSet;
|
||||
use solana_client::thin_client::create_client;
|
||||
use solana_runtime::epoch_schedule::MINIMUM_SLOTS_PER_EPOCH;
|
||||
@@ -22,6 +22,7 @@ use solana_sdk::timing::{
|
||||
NUM_CONSECUTIVE_LEADER_SLOTS,
|
||||
};
|
||||
use solana_sdk::transport::TransportError;
|
||||
use std::path::Path;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -94,7 +95,7 @@ pub fn fullnode_exit(entry_point_info: &ContactInfo, nodes: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_ledger_ticks(ledger_path: &str, ticks_per_slot: usize) {
|
||||
pub fn verify_ledger_ticks(ledger_path: &Path, ticks_per_slot: usize) {
|
||||
let ledger = Blocktree::open(ledger_path).unwrap();
|
||||
let zeroth_slot = ledger.get_slot_entries(0, 0, None).unwrap();
|
||||
let last_id = zeroth_slot.last().unwrap().hash;
|
||||
|
@@ -28,8 +28,17 @@ pub struct StakeLockout {
|
||||
stake: u64,
|
||||
}
|
||||
|
||||
impl StakeLockout {
|
||||
pub fn lockout(&self) -> u64 {
|
||||
self.lockout
|
||||
}
|
||||
pub fn stake(&self) -> u64 {
|
||||
self.stake
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Locktower {
|
||||
pub struct Tower {
|
||||
epoch_stakes: EpochStakes,
|
||||
threshold_depth: usize,
|
||||
threshold_size: f64,
|
||||
@@ -68,7 +77,7 @@ impl EpochStakes {
|
||||
}
|
||||
}
|
||||
|
||||
impl Locktower {
|
||||
impl Tower {
|
||||
pub fn new_from_forks(bank_forks: &BankForks, my_pubkey: &Pubkey) -> Self {
|
||||
let mut frozen_banks: Vec<_> = bank_forks.frozen_banks().values().cloned().collect();
|
||||
frozen_banks.sort_by_key(|b| (b.parents().len(), b.slot()));
|
||||
@@ -80,7 +89,7 @@ impl Locktower {
|
||||
}
|
||||
};
|
||||
|
||||
let mut locktower = Self {
|
||||
let mut tower = Self {
|
||||
epoch_stakes,
|
||||
threshold_depth: VOTE_THRESHOLD_DEPTH,
|
||||
threshold_size: VOTE_THRESHOLD_SIZE,
|
||||
@@ -88,10 +97,9 @@ impl Locktower {
|
||||
recent_votes: VecDeque::default(),
|
||||
};
|
||||
|
||||
let bank = locktower.find_heaviest_bank(bank_forks).unwrap();
|
||||
locktower.lockouts =
|
||||
Self::initialize_lockouts_from_bank(&bank, locktower.epoch_stakes.epoch);
|
||||
locktower
|
||||
let bank = tower.find_heaviest_bank(bank_forks).unwrap();
|
||||
tower.lockouts = Self::initialize_lockouts_from_bank(&bank, tower.epoch_stakes.epoch);
|
||||
tower
|
||||
}
|
||||
pub fn new(epoch_stakes: EpochStakes, threshold_depth: usize, threshold_size: f64) -> Self {
|
||||
Self {
|
||||
@@ -120,7 +128,7 @@ impl Locktower {
|
||||
let vote_state = VoteState::from(&account);
|
||||
if vote_state.is_none() {
|
||||
datapoint_warn!(
|
||||
"locktower_warn",
|
||||
"tower_warn",
|
||||
(
|
||||
"warn",
|
||||
format!("Unable to get vote_state from account {}", key),
|
||||
@@ -141,7 +149,7 @@ impl Locktower {
|
||||
);
|
||||
debug!("observed root {}", vote_state.root_slot.unwrap_or(0) as i64);
|
||||
datapoint_info!(
|
||||
"locktower-observed",
|
||||
"tower-observed",
|
||||
(
|
||||
"slot",
|
||||
vote_state.nth_recent_vote(0).map(|v| v.slot).unwrap_or(0),
|
||||
@@ -206,8 +214,7 @@ impl Locktower {
|
||||
}
|
||||
|
||||
pub fn is_recent_epoch(&self, bank: &Bank) -> bool {
|
||||
let bank_epoch = bank.get_epoch_and_slot_index(bank.slot()).0;
|
||||
bank_epoch >= self.epoch_stakes.epoch
|
||||
bank.epoch() >= self.epoch_stakes.epoch
|
||||
}
|
||||
|
||||
pub fn update_epoch(&mut self, bank: &Bank) {
|
||||
@@ -216,21 +223,20 @@ impl Locktower {
|
||||
bank.slot(),
|
||||
self.epoch_stakes.epoch
|
||||
);
|
||||
let bank_epoch = bank.get_epoch_and_slot_index(bank.slot()).0;
|
||||
if bank_epoch != self.epoch_stakes.epoch {
|
||||
if bank.epoch() != self.epoch_stakes.epoch {
|
||||
assert!(
|
||||
self.is_recent_epoch(bank),
|
||||
"epoch_stakes cannot move backwards"
|
||||
);
|
||||
info!(
|
||||
"Locktower updated epoch bank slot: {} epoch: {}",
|
||||
"Tower updated epoch bank slot: {} epoch: {}",
|
||||
bank.slot(),
|
||||
self.epoch_stakes.epoch
|
||||
);
|
||||
self.epoch_stakes =
|
||||
EpochStakes::new_from_bank(bank, &self.epoch_stakes.delegate_pubkey);
|
||||
datapoint_info!(
|
||||
"locktower-epoch",
|
||||
"tower-epoch",
|
||||
("epoch", self.epoch_stakes.epoch, i64),
|
||||
("self_staked", self.epoch_stakes.self_staked, i64),
|
||||
("total_staked", self.epoch_stakes.total_staked, i64)
|
||||
@@ -256,7 +262,7 @@ impl Locktower {
|
||||
.retain(|vote| slots.iter().any(|slot| vote.slot == *slot));
|
||||
|
||||
datapoint_info!(
|
||||
"locktower-vote",
|
||||
"tower-vote",
|
||||
("latest", slot, i64),
|
||||
("root", self.lockouts.root_slot.unwrap_or(0), i64)
|
||||
);
|
||||
@@ -275,6 +281,10 @@ impl Locktower {
|
||||
self.lockouts.root_slot
|
||||
}
|
||||
|
||||
pub fn total_epoch_stakes(&self) -> u64 {
|
||||
self.epoch_stakes.total_staked
|
||||
}
|
||||
|
||||
pub fn calculate_weight(&self, stake_lockouts: &HashMap<u64, StakeLockout>) -> u128 {
|
||||
let mut sum = 0u128;
|
||||
let root_slot = self.lockouts.root_slot.unwrap_or(0);
|
||||
@@ -334,6 +344,27 @@ impl Locktower {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn aggregate_stake_lockouts(
|
||||
root: Option<u64>,
|
||||
ancestors: &HashMap<u64, HashSet<u64>>,
|
||||
stake_lockouts: HashMap<u64, StakeLockout>,
|
||||
) -> HashMap<u64, u128> {
|
||||
let mut stake_weighted_lockouts: HashMap<u64, u128> = HashMap::new();
|
||||
for (fork, lockout) in stake_lockouts.iter() {
|
||||
if root.is_none() || *fork >= root.unwrap() {
|
||||
let mut slot_with_ancestors = vec![*fork];
|
||||
slot_with_ancestors.extend(ancestors.get(&fork).unwrap_or(&HashSet::new()));
|
||||
for slot in slot_with_ancestors {
|
||||
if root.is_none() || slot >= root.unwrap() {
|
||||
let entry = stake_weighted_lockouts.entry(slot).or_default();
|
||||
*entry += u128::from(lockout.lockout) * u128::from(lockout.stake);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stake_weighted_lockouts
|
||||
}
|
||||
|
||||
/// Update lockouts for all the ancestors
|
||||
fn update_ancestor_lockouts(
|
||||
stake_lockouts: &mut HashMap<u64, StakeLockout>,
|
||||
@@ -429,12 +460,13 @@ mod test {
|
||||
fn test_collect_vote_lockouts_no_epoch_stakes() {
|
||||
let accounts = gen_stakes(&[(1, &[0])]);
|
||||
let epoch_stakes = EpochStakes::new_for_tests(2);
|
||||
let locktower = Locktower::new(epoch_stakes, 0, 0.67);
|
||||
let tower = Tower::new(epoch_stakes, 0, 0.67);
|
||||
let ancestors = vec![(1, vec![0].into_iter().collect()), (0, HashSet::new())]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let staked_lockouts = locktower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors);
|
||||
let staked_lockouts = tower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors);
|
||||
assert!(staked_lockouts.is_empty());
|
||||
assert_eq!(tower.epoch_stakes.total_staked, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -442,13 +474,14 @@ mod test {
|
||||
//two accounts voting for slot 0 with 1 token staked
|
||||
let accounts = gen_stakes(&[(1, &[0]), (1, &[0])]);
|
||||
let epoch_stakes = EpochStakes::new_from_stakes(0, &accounts);
|
||||
let locktower = Locktower::new(epoch_stakes, 0, 0.67);
|
||||
let tower = Tower::new(epoch_stakes, 0, 0.67);
|
||||
let ancestors = vec![(1, vec![0].into_iter().collect()), (0, HashSet::new())]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let staked_lockouts = locktower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors);
|
||||
let staked_lockouts = tower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors);
|
||||
assert_eq!(staked_lockouts[&0].stake, 2);
|
||||
assert_eq!(staked_lockouts[&0].lockout, 2 + 2 + 4 + 4);
|
||||
assert_eq!(tower.epoch_stakes.total_staked, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -457,14 +490,14 @@ mod test {
|
||||
//two accounts voting for slot 0 with 1 token staked
|
||||
let accounts = gen_stakes(&[(1, &votes), (1, &votes)]);
|
||||
let epoch_stakes = EpochStakes::new_from_stakes(0, &accounts);
|
||||
let mut locktower = Locktower::new(epoch_stakes, 0, 0.67);
|
||||
let mut tower = Tower::new(epoch_stakes, 0, 0.67);
|
||||
let mut ancestors = HashMap::new();
|
||||
for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
|
||||
locktower.record_vote(i as u64, Hash::default());
|
||||
tower.record_vote(i as u64, Hash::default());
|
||||
ancestors.insert(i as u64, (0..i as u64).into_iter().collect());
|
||||
}
|
||||
assert_eq!(locktower.lockouts.root_slot, Some(0));
|
||||
let staked_lockouts = locktower.collect_vote_lockouts(
|
||||
assert_eq!(tower.lockouts.root_slot, Some(0));
|
||||
let staked_lockouts = tower.collect_vote_lockouts(
|
||||
MAX_LOCKOUT_HISTORY as u64,
|
||||
accounts.into_iter(),
|
||||
&ancestors,
|
||||
@@ -478,8 +511,8 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_calculate_weight_skips_root() {
|
||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
locktower.lockouts.root_slot = Some(1);
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
tower.lockouts.root_slot = Some(1);
|
||||
let stakes = vec![
|
||||
(
|
||||
0,
|
||||
@@ -498,12 +531,12 @@ mod test {
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
assert_eq!(locktower.calculate_weight(&stakes), 0u128);
|
||||
assert_eq!(tower.calculate_weight(&stakes), 0u128);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_weight() {
|
||||
let locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let stakes = vec![(
|
||||
0,
|
||||
StakeLockout {
|
||||
@@ -513,12 +546,12 @@ mod test {
|
||||
)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
assert_eq!(locktower.calculate_weight(&stakes), 8u128);
|
||||
assert_eq!(tower.calculate_weight(&stakes), 8u128);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_vote_threshold_without_votes() {
|
||||
let locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let stakes = vec![(
|
||||
0,
|
||||
StakeLockout {
|
||||
@@ -528,12 +561,65 @@ mod test {
|
||||
)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
assert!(locktower.check_vote_stake_threshold(0, &stakes));
|
||||
assert!(tower.check_vote_stake_threshold(0, &stakes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aggregate_stake_lockouts() {
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
tower.lockouts.root_slot = Some(1);
|
||||
let stakes = vec![
|
||||
(
|
||||
0,
|
||||
StakeLockout {
|
||||
stake: 1,
|
||||
lockout: 32,
|
||||
},
|
||||
),
|
||||
(
|
||||
1,
|
||||
StakeLockout {
|
||||
stake: 1,
|
||||
lockout: 24,
|
||||
},
|
||||
),
|
||||
(
|
||||
2,
|
||||
StakeLockout {
|
||||
stake: 1,
|
||||
lockout: 16,
|
||||
},
|
||||
),
|
||||
(
|
||||
3,
|
||||
StakeLockout {
|
||||
stake: 1,
|
||||
lockout: 8,
|
||||
},
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let ancestors = vec![
|
||||
(0, HashSet::new()),
|
||||
(1, vec![0].into_iter().collect()),
|
||||
(2, vec![0, 1].into_iter().collect()),
|
||||
(3, vec![0, 1, 2].into_iter().collect()),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let stake_weighted_lockouts =
|
||||
Tower::aggregate_stake_lockouts(tower.root(), &ancestors, stakes);
|
||||
assert!(stake_weighted_lockouts.get(&0).is_none());
|
||||
assert_eq!(*stake_weighted_lockouts.get(&1).unwrap(), 8 + 16 + 24);
|
||||
assert_eq!(*stake_weighted_lockouts.get(&2).unwrap(), 8 + 16);
|
||||
assert_eq!(*stake_weighted_lockouts.get(&3).unwrap(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_slot_confirmed_not_enough_stake_failure() {
|
||||
let locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let stakes = vec![(
|
||||
0,
|
||||
StakeLockout {
|
||||
@@ -543,19 +629,19 @@ mod test {
|
||||
)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
assert!(!locktower.is_slot_confirmed(0, &stakes));
|
||||
assert!(!tower.is_slot_confirmed(0, &stakes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_slot_confirmed_unknown_slot() {
|
||||
let locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let stakes = HashMap::new();
|
||||
assert!(!locktower.is_slot_confirmed(0, &stakes));
|
||||
assert!(!tower.is_slot_confirmed(0, &stakes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_slot_confirmed_pass() {
|
||||
let locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let stakes = vec![(
|
||||
0,
|
||||
StakeLockout {
|
||||
@@ -565,68 +651,68 @@ mod test {
|
||||
)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
assert!(locktower.is_slot_confirmed(0, &stakes));
|
||||
assert!(tower.is_slot_confirmed(0, &stakes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_locked_out_empty() {
|
||||
let locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let descendants = HashMap::new();
|
||||
assert!(!locktower.is_locked_out(0, &descendants));
|
||||
assert!(!tower.is_locked_out(0, &descendants));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_locked_out_root_slot_child_pass() {
|
||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let descendants = vec![(0, vec![1].into_iter().collect())]
|
||||
.into_iter()
|
||||
.collect();
|
||||
locktower.lockouts.root_slot = Some(0);
|
||||
assert!(!locktower.is_locked_out(1, &descendants));
|
||||
tower.lockouts.root_slot = Some(0);
|
||||
assert!(!tower.is_locked_out(1, &descendants));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_locked_out_root_slot_sibling_fail() {
|
||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let descendants = vec![(0, vec![1].into_iter().collect())]
|
||||
.into_iter()
|
||||
.collect();
|
||||
locktower.lockouts.root_slot = Some(0);
|
||||
assert!(locktower.is_locked_out(2, &descendants));
|
||||
tower.lockouts.root_slot = Some(0);
|
||||
assert!(tower.is_locked_out(2, &descendants));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_already_voted() {
|
||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
locktower.record_vote(0, Hash::default());
|
||||
assert!(locktower.has_voted(0));
|
||||
assert!(!locktower.has_voted(1));
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
tower.record_vote(0, Hash::default());
|
||||
assert!(tower.has_voted(0));
|
||||
assert!(!tower.has_voted(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_locked_out_double_vote() {
|
||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let descendants = vec![(0, vec![1].into_iter().collect()), (1, HashSet::new())]
|
||||
.into_iter()
|
||||
.collect();
|
||||
locktower.record_vote(0, Hash::default());
|
||||
locktower.record_vote(1, Hash::default());
|
||||
assert!(locktower.is_locked_out(0, &descendants));
|
||||
tower.record_vote(0, Hash::default());
|
||||
tower.record_vote(1, Hash::default());
|
||||
assert!(tower.is_locked_out(0, &descendants));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_locked_out_child() {
|
||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let descendants = vec![(0, vec![1].into_iter().collect())]
|
||||
.into_iter()
|
||||
.collect();
|
||||
locktower.record_vote(0, Hash::default());
|
||||
assert!(!locktower.is_locked_out(1, &descendants));
|
||||
tower.record_vote(0, Hash::default());
|
||||
assert!(!tower.is_locked_out(1, &descendants));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_locked_out_sibling() {
|
||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let descendants = vec![
|
||||
(0, vec![1, 2].into_iter().collect()),
|
||||
(1, HashSet::new()),
|
||||
@@ -634,30 +720,30 @@ mod test {
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
locktower.record_vote(0, Hash::default());
|
||||
locktower.record_vote(1, Hash::default());
|
||||
assert!(locktower.is_locked_out(2, &descendants));
|
||||
tower.record_vote(0, Hash::default());
|
||||
tower.record_vote(1, Hash::default());
|
||||
assert!(tower.is_locked_out(2, &descendants));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_locked_out_last_vote_expired() {
|
||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||
let descendants = vec![(0, vec![1, 4].into_iter().collect()), (1, HashSet::new())]
|
||||
.into_iter()
|
||||
.collect();
|
||||
locktower.record_vote(0, Hash::default());
|
||||
locktower.record_vote(1, Hash::default());
|
||||
assert!(!locktower.is_locked_out(4, &descendants));
|
||||
locktower.record_vote(4, Hash::default());
|
||||
assert_eq!(locktower.lockouts.votes[0].slot, 0);
|
||||
assert_eq!(locktower.lockouts.votes[0].confirmation_count, 2);
|
||||
assert_eq!(locktower.lockouts.votes[1].slot, 4);
|
||||
assert_eq!(locktower.lockouts.votes[1].confirmation_count, 1);
|
||||
tower.record_vote(0, Hash::default());
|
||||
tower.record_vote(1, Hash::default());
|
||||
assert!(!tower.is_locked_out(4, &descendants));
|
||||
tower.record_vote(4, Hash::default());
|
||||
assert_eq!(tower.lockouts.votes[0].slot, 0);
|
||||
assert_eq!(tower.lockouts.votes[0].confirmation_count, 2);
|
||||
assert_eq!(tower.lockouts.votes[1].slot, 4);
|
||||
assert_eq!(tower.lockouts.votes[1].confirmation_count, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_vote_threshold_below_threshold() {
|
||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let stakes = vec![(
|
||||
0,
|
||||
StakeLockout {
|
||||
@@ -667,12 +753,12 @@ mod test {
|
||||
)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
locktower.record_vote(0, Hash::default());
|
||||
assert!(!locktower.check_vote_stake_threshold(1, &stakes));
|
||||
tower.record_vote(0, Hash::default());
|
||||
assert!(!tower.check_vote_stake_threshold(1, &stakes));
|
||||
}
|
||||
#[test]
|
||||
fn test_check_vote_threshold_above_threshold() {
|
||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let stakes = vec![(
|
||||
0,
|
||||
StakeLockout {
|
||||
@@ -682,13 +768,13 @@ mod test {
|
||||
)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
locktower.record_vote(0, Hash::default());
|
||||
assert!(locktower.check_vote_stake_threshold(1, &stakes));
|
||||
tower.record_vote(0, Hash::default());
|
||||
assert!(tower.check_vote_stake_threshold(1, &stakes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_vote_threshold_above_threshold_after_pop() {
|
||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let stakes = vec![(
|
||||
0,
|
||||
StakeLockout {
|
||||
@@ -698,18 +784,18 @@ mod test {
|
||||
)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
locktower.record_vote(0, Hash::default());
|
||||
locktower.record_vote(1, Hash::default());
|
||||
locktower.record_vote(2, Hash::default());
|
||||
assert!(locktower.check_vote_stake_threshold(6, &stakes));
|
||||
tower.record_vote(0, Hash::default());
|
||||
tower.record_vote(1, Hash::default());
|
||||
tower.record_vote(2, Hash::default());
|
||||
assert!(tower.check_vote_stake_threshold(6, &stakes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_vote_threshold_above_threshold_no_stake() {
|
||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let stakes = HashMap::new();
|
||||
locktower.record_vote(0, Hash::default());
|
||||
assert!(!locktower.check_vote_stake_threshold(1, &stakes));
|
||||
tower.record_vote(0, Hash::default());
|
||||
assert!(!tower.check_vote_stake_threshold(1, &stakes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -724,7 +810,7 @@ mod test {
|
||||
ancestors.insert(2, set);
|
||||
let set: HashSet<u64> = vec![0u64].into_iter().collect();
|
||||
ancestors.insert(1, set);
|
||||
Locktower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors);
|
||||
Tower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors);
|
||||
assert_eq!(stake_lockouts[&0].lockout, 2);
|
||||
assert_eq!(stake_lockouts[&1].lockout, 2);
|
||||
assert_eq!(stake_lockouts[&2].lockout, 2);
|
||||
@@ -742,12 +828,12 @@ mod test {
|
||||
slot: 2,
|
||||
confirmation_count: 1,
|
||||
};
|
||||
Locktower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors);
|
||||
Tower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors);
|
||||
let vote = Lockout {
|
||||
slot: 1,
|
||||
confirmation_count: 2,
|
||||
};
|
||||
Locktower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors);
|
||||
Tower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors);
|
||||
assert_eq!(stake_lockouts[&0].lockout, 2 + 4);
|
||||
assert_eq!(stake_lockouts[&1].lockout, 2 + 4);
|
||||
assert_eq!(stake_lockouts[&2].lockout, 2);
|
||||
@@ -760,7 +846,7 @@ mod test {
|
||||
account.lamports = 1;
|
||||
let set: HashSet<u64> = vec![0u64, 1u64].into_iter().collect();
|
||||
let ancestors: HashMap<u64, HashSet<u64>> = [(2u64, set)].into_iter().cloned().collect();
|
||||
Locktower::update_ancestor_stakes(&mut stake_lockouts, 2, account.lamports, &ancestors);
|
||||
Tower::update_ancestor_stakes(&mut stake_lockouts, 2, account.lamports, &ancestors);
|
||||
assert_eq!(stake_lockouts[&0].stake, 1);
|
||||
assert_eq!(stake_lockouts[&1].stake, 1);
|
||||
assert_eq!(stake_lockouts[&2].stake, 1);
|
||||
@@ -782,51 +868,48 @@ mod test {
|
||||
let total_stake = 4;
|
||||
let threshold_size = 0.67;
|
||||
let threshold_stake = (f64::ceil(total_stake as f64 * threshold_size)) as u64;
|
||||
let locktower_votes: Vec<u64> = (0..VOTE_THRESHOLD_DEPTH as u64).collect();
|
||||
let tower_votes: Vec<u64> = (0..VOTE_THRESHOLD_DEPTH as u64).collect();
|
||||
let accounts = gen_stakes(&[
|
||||
(threshold_stake, &[(VOTE_THRESHOLD_DEPTH - 2) as u64]),
|
||||
(total_stake - threshold_stake, &locktower_votes[..]),
|
||||
(total_stake - threshold_stake, &tower_votes[..]),
|
||||
]);
|
||||
|
||||
// Initialize locktower
|
||||
// Initialize tower
|
||||
let stakes: HashMap<_, _> = accounts.iter().map(|(pk, (s, _))| (*pk, *s)).collect();
|
||||
let epoch_stakes = EpochStakes::new(0, stakes, &Pubkey::default());
|
||||
let mut locktower = Locktower::new(epoch_stakes, VOTE_THRESHOLD_DEPTH, threshold_size);
|
||||
let mut tower = Tower::new(epoch_stakes, VOTE_THRESHOLD_DEPTH, threshold_size);
|
||||
|
||||
// CASE 1: Record the first VOTE_THRESHOLD locktower votes for fork 2. We want to
|
||||
// CASE 1: Record the first VOTE_THRESHOLD tower votes for fork 2. We want to
|
||||
// evaluate a vote on slot VOTE_THRESHOLD_DEPTH. The nth most recent vote should be
|
||||
// for slot 0, which is common to all account vote states, so we should pass the
|
||||
// threshold check
|
||||
let vote_to_evaluate = VOTE_THRESHOLD_DEPTH as u64;
|
||||
for vote in &locktower_votes {
|
||||
locktower.record_vote(*vote, Hash::default());
|
||||
for vote in &tower_votes {
|
||||
tower.record_vote(*vote, Hash::default());
|
||||
}
|
||||
let stakes_lockouts = locktower.collect_vote_lockouts(
|
||||
vote_to_evaluate,
|
||||
accounts.clone().into_iter(),
|
||||
&ancestors,
|
||||
);
|
||||
assert!(locktower.check_vote_stake_threshold(vote_to_evaluate, &stakes_lockouts));
|
||||
let staked_lockouts =
|
||||
tower.collect_vote_lockouts(vote_to_evaluate, accounts.clone().into_iter(), &ancestors);
|
||||
assert!(tower.check_vote_stake_threshold(vote_to_evaluate, &staked_lockouts));
|
||||
|
||||
// CASE 2: Now we want to evaluate a vote for slot VOTE_THRESHOLD_DEPTH + 1. This slot
|
||||
// will expire the vote in one of the vote accounts, so we should have insufficient
|
||||
// stake to pass the threshold
|
||||
let vote_to_evaluate = VOTE_THRESHOLD_DEPTH as u64 + 1;
|
||||
let stakes_lockouts =
|
||||
locktower.collect_vote_lockouts(vote_to_evaluate, accounts.into_iter(), &ancestors);
|
||||
assert!(!locktower.check_vote_stake_threshold(vote_to_evaluate, &stakes_lockouts));
|
||||
let staked_lockouts =
|
||||
tower.collect_vote_lockouts(vote_to_evaluate, accounts.into_iter(), &ancestors);
|
||||
assert!(!tower.check_vote_stake_threshold(vote_to_evaluate, &staked_lockouts));
|
||||
}
|
||||
|
||||
fn vote_and_check_recent(num_votes: usize) {
|
||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||
let start = num_votes.saturating_sub(MAX_RECENT_VOTES);
|
||||
let expected: Vec<_> = (start..num_votes)
|
||||
.map(|i| Vote::new(i as u64, Hash::default()))
|
||||
.collect();
|
||||
for i in 0..num_votes {
|
||||
locktower.record_vote(i as u64, Hash::default());
|
||||
tower.record_vote(i as u64, Hash::default());
|
||||
}
|
||||
assert_eq!(expected, locktower.recent_votes())
|
||||
assert_eq!(expected, tower.recent_votes())
|
||||
}
|
||||
|
||||
#[test]
|
@@ -23,7 +23,7 @@ pub struct ContactInfo {
|
||||
/// transactions address
|
||||
pub tpu: SocketAddr,
|
||||
/// address to forward unprocessed transactions to
|
||||
pub tpu_via_blobs: SocketAddr,
|
||||
pub tpu_forwards: SocketAddr,
|
||||
/// storage data address
|
||||
pub storage_addr: SocketAddr,
|
||||
/// address to which to send JSON-RPC requests
|
||||
@@ -78,7 +78,7 @@ impl Default for ContactInfo {
|
||||
gossip: socketaddr_any!(),
|
||||
tvu: socketaddr_any!(),
|
||||
tpu: socketaddr_any!(),
|
||||
tpu_via_blobs: socketaddr_any!(),
|
||||
tpu_forwards: socketaddr_any!(),
|
||||
storage_addr: socketaddr_any!(),
|
||||
rpc: socketaddr_any!(),
|
||||
rpc_pubsub: socketaddr_any!(),
|
||||
@@ -94,7 +94,7 @@ impl ContactInfo {
|
||||
gossip: SocketAddr,
|
||||
tvu: SocketAddr,
|
||||
tpu: SocketAddr,
|
||||
tpu_via_blobs: SocketAddr,
|
||||
tpu_forwards: SocketAddr,
|
||||
storage_addr: SocketAddr,
|
||||
rpc: SocketAddr,
|
||||
rpc_pubsub: SocketAddr,
|
||||
@@ -106,7 +106,7 @@ impl ContactInfo {
|
||||
gossip,
|
||||
tvu,
|
||||
tpu,
|
||||
tpu_via_blobs,
|
||||
tpu_forwards,
|
||||
storage_addr,
|
||||
rpc,
|
||||
rpc_pubsub,
|
||||
@@ -147,7 +147,7 @@ impl ContactInfo {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn new_with_pubkey_socketaddr(pubkey: &Pubkey, bind_addr: &SocketAddr) -> Self {
|
||||
pub(crate) fn new_with_pubkey_socketaddr(pubkey: &Pubkey, bind_addr: &SocketAddr) -> Self {
|
||||
fn next_port(addr: &SocketAddr, nxt: u16) -> SocketAddr {
|
||||
let mut nxt_addr = *addr;
|
||||
nxt_addr.set_port(addr.port() + nxt);
|
||||
@@ -157,7 +157,7 @@ impl ContactInfo {
|
||||
let tpu_addr = *bind_addr;
|
||||
let gossip_addr = next_port(&bind_addr, 1);
|
||||
let tvu_addr = next_port(&bind_addr, 2);
|
||||
let tpu_via_blobs_addr = next_port(&bind_addr, 3);
|
||||
let tpu_forwards_addr = next_port(&bind_addr, 3);
|
||||
let rpc_addr = SocketAddr::new(bind_addr.ip(), rpc_port::DEFAULT_RPC_PORT);
|
||||
let rpc_pubsub_addr = SocketAddr::new(bind_addr.ip(), rpc_port::DEFAULT_RPC_PUBSUB_PORT);
|
||||
Self::new(
|
||||
@@ -165,7 +165,7 @@ impl ContactInfo {
|
||||
gossip_addr,
|
||||
tvu_addr,
|
||||
tpu_addr,
|
||||
tpu_via_blobs_addr,
|
||||
tpu_forwards_addr,
|
||||
"0.0.0.0:0".parse().unwrap(),
|
||||
rpc_addr,
|
||||
rpc_pubsub_addr,
|
||||
@@ -233,7 +233,7 @@ impl Signable for ContactInfo {
|
||||
gossip: SocketAddr,
|
||||
tvu: SocketAddr,
|
||||
tpu: SocketAddr,
|
||||
tpu_via_blobs: SocketAddr,
|
||||
tpu_forwards: SocketAddr,
|
||||
storage_addr: SocketAddr,
|
||||
rpc: SocketAddr,
|
||||
rpc_pubsub: SocketAddr,
|
||||
@@ -247,7 +247,7 @@ impl Signable for ContactInfo {
|
||||
tvu: me.tvu,
|
||||
tpu: me.tpu,
|
||||
storage_addr: me.storage_addr,
|
||||
tpu_via_blobs: me.tpu_via_blobs,
|
||||
tpu_forwards: me.tpu_forwards,
|
||||
rpc: me.rpc,
|
||||
rpc_pubsub: me.rpc_pubsub,
|
||||
wallclock: me.wallclock,
|
||||
@@ -287,7 +287,7 @@ mod tests {
|
||||
let ci = ContactInfo::default();
|
||||
assert!(ci.gossip.ip().is_unspecified());
|
||||
assert!(ci.tvu.ip().is_unspecified());
|
||||
assert!(ci.tpu_via_blobs.ip().is_unspecified());
|
||||
assert!(ci.tpu_forwards.ip().is_unspecified());
|
||||
assert!(ci.rpc.ip().is_unspecified());
|
||||
assert!(ci.rpc_pubsub.ip().is_unspecified());
|
||||
assert!(ci.tpu.ip().is_unspecified());
|
||||
@@ -298,7 +298,7 @@ mod tests {
|
||||
let ci = ContactInfo::new_multicast();
|
||||
assert!(ci.gossip.ip().is_multicast());
|
||||
assert!(ci.tvu.ip().is_multicast());
|
||||
assert!(ci.tpu_via_blobs.ip().is_multicast());
|
||||
assert!(ci.tpu_forwards.ip().is_multicast());
|
||||
assert!(ci.rpc.ip().is_multicast());
|
||||
assert!(ci.rpc_pubsub.ip().is_multicast());
|
||||
assert!(ci.tpu.ip().is_multicast());
|
||||
@@ -310,7 +310,7 @@ mod tests {
|
||||
let ci = ContactInfo::new_gossip_entry_point(&addr);
|
||||
assert_eq!(ci.gossip, addr);
|
||||
assert!(ci.tvu.ip().is_unspecified());
|
||||
assert!(ci.tpu_via_blobs.ip().is_unspecified());
|
||||
assert!(ci.tpu_forwards.ip().is_unspecified());
|
||||
assert!(ci.rpc.ip().is_unspecified());
|
||||
assert!(ci.rpc_pubsub.ip().is_unspecified());
|
||||
assert!(ci.tpu.ip().is_unspecified());
|
||||
@@ -323,7 +323,7 @@ mod tests {
|
||||
assert_eq!(ci.tpu, addr);
|
||||
assert_eq!(ci.gossip.port(), 11);
|
||||
assert_eq!(ci.tvu.port(), 12);
|
||||
assert_eq!(ci.tpu_via_blobs.port(), 13);
|
||||
assert_eq!(ci.tpu_forwards.port(), 13);
|
||||
assert_eq!(ci.rpc.port(), 8899);
|
||||
assert_eq!(ci.rpc_pubsub.port(), 8900);
|
||||
assert!(ci.storage_addr.ip().is_unspecified());
|
||||
@@ -338,7 +338,7 @@ mod tests {
|
||||
assert_eq!(d1.id, keypair.pubkey());
|
||||
assert_eq!(d1.gossip, socketaddr!("127.0.0.1:1235"));
|
||||
assert_eq!(d1.tvu, socketaddr!("127.0.0.1:1236"));
|
||||
assert_eq!(d1.tpu_via_blobs, socketaddr!("127.0.0.1:1237"));
|
||||
assert_eq!(d1.tpu_forwards, socketaddr!("127.0.0.1:1237"));
|
||||
assert_eq!(d1.tpu, socketaddr!("127.0.0.1:1234"));
|
||||
assert_eq!(d1.rpc, socketaddr!("127.0.0.1:8899"));
|
||||
assert_eq!(d1.rpc_pubsub, socketaddr!("127.0.0.1:8900"));
|
||||
|
@@ -3,15 +3,16 @@
|
||||
//! designed to run with a simulator or over a UDP network connection with messages up to a
|
||||
//! packet::BLOB_DATA_SIZE size.
|
||||
|
||||
use crate::crds::Crds;
|
||||
use crate::crds::{Crds, VersionedCrdsValue};
|
||||
use crate::crds_gossip_error::CrdsGossipError;
|
||||
use crate::crds_gossip_pull::CrdsGossipPull;
|
||||
use crate::crds_gossip_push::{CrdsGossipPush, CRDS_GOSSIP_NUM_ACTIVE};
|
||||
use crate::crds_value::CrdsValue;
|
||||
use crate::crds_value::{CrdsValue, CrdsValueLabel};
|
||||
use solana_runtime::bloom::Bloom;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::collections::HashMap;
|
||||
use solana_sdk::signature::Signable;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
///The min size for bloom filters
|
||||
pub const CRDS_GOSSIP_BLOOM_SIZE: usize = 1000;
|
||||
@@ -39,25 +40,24 @@ impl CrdsGossip {
|
||||
pub fn set_self(&mut self, id: &Pubkey) {
|
||||
self.id = *id;
|
||||
}
|
||||
|
||||
/// process a push message to the network
|
||||
pub fn process_push_message(&mut self, values: Vec<CrdsValue>, now: u64) -> Vec<Pubkey> {
|
||||
let labels: Vec<_> = values.iter().map(CrdsValue::label).collect();
|
||||
|
||||
let results: Vec<_> = values
|
||||
pub fn process_push_message(
|
||||
&mut self,
|
||||
from: &Pubkey,
|
||||
values: Vec<CrdsValue>,
|
||||
now: u64,
|
||||
) -> Vec<VersionedCrdsValue> {
|
||||
values
|
||||
.into_iter()
|
||||
.map(|val| self.push.process_push_message(&mut self.crds, val, now))
|
||||
.collect();
|
||||
|
||||
results
|
||||
.into_iter()
|
||||
.zip(labels)
|
||||
.filter_map(|(r, d)| {
|
||||
if r == Err(CrdsGossipError::PushMessagePrune) {
|
||||
Some(d.pubkey())
|
||||
} else if let Ok(Some(val)) = r {
|
||||
.filter_map(|val| {
|
||||
let res = self
|
||||
.push
|
||||
.process_push_message(&mut self.crds, from, val, now);
|
||||
if let Ok(Some(val)) = res {
|
||||
self.pull
|
||||
.record_old_hash(val.value_hash, val.local_timestamp);
|
||||
None
|
||||
Some(val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -65,6 +65,31 @@ impl CrdsGossip {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// remove redundant paths in the network
|
||||
pub fn prune_received_cache(
|
||||
&mut self,
|
||||
labels: Vec<CrdsValueLabel>,
|
||||
stakes: &HashMap<Pubkey, u64>,
|
||||
) -> HashMap<Pubkey, HashSet<Pubkey>> {
|
||||
let id = &self.id;
|
||||
let crds = &self.crds;
|
||||
let push = &mut self.push;
|
||||
let versioned = labels
|
||||
.into_iter()
|
||||
.filter_map(|label| crds.lookup_versioned(&label));
|
||||
|
||||
let mut prune_map: HashMap<Pubkey, HashSet<_>> = HashMap::new();
|
||||
for val in versioned {
|
||||
let origin = val.value.pubkey();
|
||||
let hash = val.value_hash;
|
||||
let peers = push.prune_received_cache(id, &origin, hash, stakes);
|
||||
for from in peers {
|
||||
prune_map.entry(from).or_default().insert(origin);
|
||||
}
|
||||
}
|
||||
prune_map
|
||||
}
|
||||
|
||||
pub fn new_push_messages(&mut self, now: u64) -> (Pubkey, HashMap<Pubkey, Vec<CrdsValue>>) {
|
||||
let push_messages = self.push.new_push_messages(&self.crds, now);
|
||||
(self.id, push_messages)
|
||||
@@ -147,7 +172,7 @@ impl CrdsGossip {
|
||||
}
|
||||
if now > 5 * self.push.msg_timeout {
|
||||
let min = now - 5 * self.push.msg_timeout;
|
||||
self.push.purge_old_pushed_once_messages(min);
|
||||
self.push.purge_old_received_cache(min);
|
||||
}
|
||||
if now > self.pull.crds_timeout {
|
||||
let min = now - self.pull.crds_timeout;
|
||||
|
@@ -2,7 +2,7 @@
|
||||
pub enum CrdsGossipError {
|
||||
NoPeers,
|
||||
PushMessageTimeout,
|
||||
PushMessagePrune,
|
||||
PushMessageAlreadyReceived,
|
||||
PushMessageOldVersion,
|
||||
BadPruneDestination,
|
||||
PruneMessageTimeout,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user