Compare commits
256 Commits
Author | SHA1 | Date | |
---|---|---|---|
8631be42ac | |||
09367369ef | |||
23b8c95cc4 | |||
e61392b057 | |||
7deba20395 | |||
274c097f84 | |||
1c7cea1af4 | |||
4406496d2f | |||
a15fa4840c | |||
c9030660d6 | |||
fdeda769d0 | |||
53edd26578 | |||
2433cdd6d6 | |||
77b34c278e | |||
90e993fd9a | |||
251054d8c9 | |||
d4bb7cec69 | |||
a3f6b04345 | |||
ba4a5053dd | |||
6b47a259c3 | |||
c9ec13cf1f | |||
906a6ab837 | |||
d0e478a9f8 | |||
b560b64d33 | |||
057af41716 | |||
a44b8abd48 | |||
8778ecaed5 | |||
a02542ada3 | |||
ea17c6883f | |||
706306645b | |||
da9e930788 | |||
8b8e066bbe | |||
3473350b62 | |||
59d7eb5216 | |||
55ba934137 | |||
4c3dcb7f7e | |||
3a879db8af | |||
d2107270ea | |||
007afe22d0 | |||
93a1d10e15 | |||
d57a7c8f21 | |||
6db39829c8 | |||
ec76826493 | |||
d4ddb6265b | |||
7a8233d7ca | |||
95bc051129 | |||
02bcf4f8e2 | |||
4b0d4e9834 | |||
bf4cdc091a | |||
5f2cf2b44d | |||
03aae5eb5f | |||
5f1ce81fbc | |||
fc582aa57c | |||
13676e9614 | |||
7636a0521f | |||
ad06354a18 | |||
953cb93e44 | |||
38b2957a8e | |||
9eb39df93f | |||
f34ce94347 | |||
5c6411fc06 | |||
3ab428693a | |||
7ffaf2ad29 | |||
a5e4a1f2d8 | |||
6ae99848f4 | |||
5342b3a53f | |||
50a3c98d21 | |||
ca2b7a2c5b | |||
e900623817 | |||
a46e953ebb | |||
3bbc51a6ae | |||
7ae2464cf3 | |||
58a36ce484 | |||
61bd9e6a28 | |||
495ab631d6 | |||
93906847f9 | |||
70f4c4a974 | |||
ad6078da2d | |||
2a617f2d07 | |||
6af3c6ecbc | |||
a5938e5a21 | |||
8eaa8d8788 | |||
51940eec9b | |||
d1e16b2bc4 | |||
97051c87b4 | |||
2521913654 | |||
803c87b4bd | |||
8ff9ee3a06 | |||
25bf2c6062 | |||
fc80b77fc4 | |||
5a7707362c | |||
0102ee3fa9 | |||
d29cb19a73 | |||
1cc66f0cd7 | |||
e2cfc513eb | |||
8115a962e9 | |||
0c804e2ef2 | |||
cbe36a7a63 | |||
174825fecf | |||
c7e80bebdc | |||
57abc370fa | |||
42ab421a87 | |||
9ab27291f5 | |||
27e4e9cb8d | |||
b0cf65dfc8 | |||
bfc97c682c | |||
231c9b25d1 | |||
f536d805ed | |||
cb6e7426a4 | |||
71f391ae04 | |||
12bf34f059 | |||
a3a14d6b5b | |||
5e22db017a | |||
2cd09957b4 | |||
d1ec6c0b8b | |||
7722723400 | |||
4a42cfc42a | |||
976d744b0d | |||
62de02c8d4 | |||
2741a5ce3b | |||
0c818acd90 | |||
4d04915302 | |||
7576411c59 | |||
ab2bed6e8f | |||
f66a5ba579 | |||
9253d786f8 | |||
d1be1ee49e | |||
65833eacd8 | |||
921994e588 | |||
91dfd962e5 | |||
18067dcb55 | |||
7501e1b0f0 | |||
1bd2c1de20 | |||
a9ef20d43f | |||
00a9d66fed | |||
7508ffe703 | |||
6c2368bfc9 | |||
6bcb797260 | |||
329945e56f | |||
1f80346d97 | |||
38e661d7ba | |||
592c4efd17 | |||
1629745bd3 | |||
02e078c22e | |||
ea9e7c710a | |||
ee5aa0c1a2 | |||
55dee2901e | |||
2b0824d18b | |||
b0709ea0ac | |||
81b5499f7a | |||
c96ce99705 | |||
8dcd2d11e1 | |||
777aae9059 | |||
4dd1340236 | |||
889b06e1d4 | |||
f511296ee8 | |||
c19eb717b4 | |||
dd54369e1b | |||
bb563b4835 | |||
d061fadede | |||
577cd2bd3a | |||
9d1c8657e2 | |||
67216ac7f5 | |||
e63b24c39f | |||
d963f7afb4 | |||
a07bf4870a | |||
8422d4b3fb | |||
ff4731cce2 | |||
659aaafff6 | |||
175651c497 | |||
085e773f27 | |||
5d3140c040 | |||
d8d7238920 | |||
418c3cd4cf | |||
9f532cb50f | |||
c35f4927cd | |||
2d0f4b5c8c | |||
fe59ee61e6 | |||
bda2acb06d | |||
ac3fe1da02 | |||
57813041d2 | |||
7e446da82c | |||
d94f5c94a3 | |||
4d9aee4794 | |||
f32c152bce | |||
5caf9110e0 | |||
d1c80143ea | |||
bb132df121 | |||
b3af1c7e57 | |||
14cba53338 | |||
dc8abbe9e3 | |||
dd06001ed8 | |||
298b7de2e2 | |||
74cbc6953f | |||
27e5203078 | |||
73787e162c | |||
74ae40be41 | |||
8dd58e9cea | |||
7d86179c60 | |||
8115cf1360 | |||
3e5d45053d | |||
061319f35a | |||
dc75837500 | |||
dfe26f5275 | |||
f4385f7ad2 | |||
8df4cf2895 | |||
dad62e132e | |||
0d4131ae68 | |||
a2539e1892 | |||
210659e6c3 | |||
15a0fb1fa9 | |||
4db31f5b48 | |||
b38a535c63 | |||
218b02aaf8 | |||
d6e7cbd4e8 | |||
f2fda14333 | |||
1c576d4a68 | |||
f6232e1b3c | |||
ad71fa3f12 | |||
9c326c7c71 | |||
ac545cadaf | |||
082d8fff36 | |||
2c3632a042 | |||
7b23e79922 | |||
9afd14a0c6 | |||
f42aa34ab6 | |||
43af91ff4b | |||
a920a0f946 | |||
4a8a6d0b3f | |||
a64b8a2705 | |||
0198f9e8af | |||
1582a3a927 | |||
e713f0e5fb | |||
77031000c4 | |||
635a962fba | |||
c59ec2dcff | |||
abc6c5e264 | |||
87cfac12dd | |||
60b43e34b6 | |||
9ab6222f03 | |||
2298dd5c07 | |||
822d166115 | |||
8f71580615 | |||
cc3352ff06 | |||
8f5928b7c7 | |||
888e9617ff | |||
4695c4cf7d | |||
bbfc56ff7f | |||
4103d99018 | |||
c375ce1fcd | |||
df813b31c5 | |||
7db92e2951 | |||
6585518f78 | |||
6398e256e4 | |||
b64ebb45e7 | |||
307ac66d9c |
42
.appveyor.yml
Normal file
42
.appveyor.yml
Normal file
@ -0,0 +1,42 @@
|
||||
version: '{build}'
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v[0-9.]+\.[0-9.]+/
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.cargo'
|
||||
- '%APPVEYOR_BUILD_FOLDER%\target'
|
||||
|
||||
clone_folder: d:\projects\solana
|
||||
|
||||
build_script:
|
||||
- bash ci/publish-tarball.sh
|
||||
|
||||
notifications:
|
||||
- provider: Slack
|
||||
incoming_webhook:
|
||||
secure: GJsBey+F5apAtUm86MHVJ68Uqa6WN1SImcuIc4TsTZrDhA8K1QWUNw9FFQPybUWDyOcS5dly3kubnUqlGt9ux6Ad2efsfRIQYWv0tOVXKeY=
|
||||
channel: ci-status
|
||||
on_build_success: false
|
||||
on_build_failure: true
|
||||
on_build_status_changed: true
|
||||
|
||||
deploy:
|
||||
- provider: S3
|
||||
access_key_id:
|
||||
secure: fTbJl6JpFebR40J7cOWZ2mXBa3kIvEiXgzxAj6L3N7A=
|
||||
secret_access_key:
|
||||
secure: vItsBXb2rEFLvkWtVn/Rcxu5a5+2EwC+b7GsA0waJy9hXh6XuBAD0lnHd9re3g/4
|
||||
bucket: release.solana.com
|
||||
region: us-west-1
|
||||
set_public: true
|
||||
|
||||
- provider: GitHub
|
||||
auth_token:
|
||||
secure: 81fEmPZ0cV1wLtNuUrcmtgxKF6ROQF1+/ft5m+fHX21z6PoeCbaNo8cTyLioWBj7
|
||||
draft: false
|
||||
prerelease: false
|
||||
on:
|
||||
appveyor_repo_tag: true
|
14
.buildkite/env/secrets.ejson
vendored
14
.buildkite/env/secrets.ejson
vendored
@ -1,12 +1,12 @@
|
||||
{
|
||||
"_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f",
|
||||
"environment": {
|
||||
"CODECOV_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:3K68mE38LJ2RB98VWmjuNLFBNn1XTGR4:cR4r05/TOZQKmEZp1v4CSgUJtC6QJiOaL85QjXW0qZ061fMnsBA8AtAPMDoDq4WCGOZM1A==]",
|
||||
"CRATES_IO_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:GGRTYDjMXksevzR6kq4Jx+FaIQZz50RU:xkbwDxcgoCyU+aT2tiI9mymigrEl6YiOr3axe3aX70ELIBKbCdPGilXP/wixvKi94g2u]",
|
||||
"GEOLOCATION_API_KEY": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:U2PZLi5MU3Ru/zK1SilianEeizcMvxml:AJKf2OAtDHmJh0KyXrBnNnistItZvVVP3cZ7ZLtrVupjmWN/PzmKwSsXeCNObWS+]",
|
||||
"GITHUB_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:0NJNlpD/O19mvOakCGBYDhIDfySxWFSC:Dz4NXv9x6ncRQ1u9sVoWOcqmkg0sI09qmefghB0GXZgPcFGgn6T0mw7ynNnbUvjyH8dLruKHauk=]",
|
||||
"INFLUX_DATABASE": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:SzwHIeOVpmbTcGQOGngoFgYumsLZJUGq:t7Rpk49njsWvoM+ztv5Uwuiz]",
|
||||
"INFLUX_PASSWORD": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:/MUs+q7pdGrUjzwcq+6pgIFxur4hxdqu:am22z2E2dtmw1f1J1Mq5JLcUHZsrEjQAJ0pp21M4AZeJbNO6bVb44d9zSkHj7xdN6U+GNlCk+wU=]",
|
||||
"INFLUX_USERNAME": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:XjghH20xGVWro9B+epGlJaJcW8Wze0Bi:ZIdOtXudTY5TqKseDU7gVvQXfmXV99Xh]"
|
||||
"CODECOV_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:JnxhrIxh09AvqdJgrVSYmb7PxSrh19aE:07WzVExCHEd1lJ1m8QizRRthGri+WBNeZRKjjEvsy5eo4gv3HD7zVEm42tVTGkqITKkBNQ==]",
|
||||
"CRATES_IO_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:d0jJqC32/axwzq/N7kMRmpxKhnRrhtpt:zvcPHwkOzGnjhNkAQSejwdy1Jkr9wR1qXFFCnfIjyt/XQYubzB1tLkoly/qdmeb5]",
|
||||
"GEOLOCATION_API_KEY": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R4gfB6Ey4i50HyfLt4UZDLBqg3qHEUye:UfZCOgt8XI6Y2g+ivCRVoS1fjFycFs7/GSevvCqh1B50mG0+hzpEyzXQLuKG5OeI]",
|
||||
"GITHUB_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:Vq2dkGTOzfEpRht0BAGHFp/hDogMvXJe:tFXHg1epVt2mq9hkuc5sRHe+KAnVREi/p8S+IZu67XRyzdiA/nGak1k860FXYuuzuaE0QWekaEc=]",
|
||||
"INFLUX_DATABASE": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:5KI9WBkXx3R/W4m256mU5MJOE7N8aAT9:Cb8QFELZ9I60t5zhJ9h55Kcs]",
|
||||
"INFLUX_PASSWORD": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:hQRMpLCrav+OYkNphkeM4hagdVoZv5Iw:AUO76rr6+gF1OLJA8ZLSG8wHKXgYCPNk6gRCV8rBhZBJ4KwDaxpvOhMl7bxxXG6jol7v4aRa/Lk=]",
|
||||
"INFLUX_USERNAME": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R7BNmQjfeqoGDAFTJu9bYTGHol2NgnYN:Q2tOT/EBcFvhFk+DKLKmVU7tLCpVC3Ui]"
|
||||
}
|
||||
}
|
||||
|
@ -3,16 +3,3 @@ root: ./docs/src
|
||||
structure:
|
||||
readme: introduction.md
|
||||
summary: SUMMARY.md
|
||||
|
||||
redirects:
|
||||
wallet: ./wallet-guide/README.md
|
||||
wallet/app-wallets: ./wallet-guide/apps.md
|
||||
wallet/app-wallets/trust-wallet: ./wallet-guide/trust-wallet.md
|
||||
wallet/app-wallets/ledger-live: ./wallet-guide/ledger-live.md
|
||||
wallet/cli-wallets: ./wallet-guide/cli.md
|
||||
wallet/cli-wallets/paper-wallet: ./paper-wallet/README.md
|
||||
wallet/cli-wallets/paper-wallet/paper-wallet-usage: ./paper-wallet/paper-wallet-usage.md
|
||||
wallet/cli-wallets/remote-wallet: ./hardware-wallets/README.md
|
||||
wallet/cli-wallets/remote-wallet/ledger: ./hardware-wallets/ledger.md
|
||||
wallet/cli-wallets/file-system-wallet: ./file-system-wallet/README.md
|
||||
wallet/support: ./wallet-guide/support.md
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,5 @@
|
||||
/docs/html/
|
||||
/docs/src/tests.ok
|
||||
/docs/src/cli/usage.md
|
||||
/docs/src/.gitbook/assets/*.svg
|
||||
/farf/
|
||||
/solana-release/
|
||||
|
44
.mergify.yml
44
.mergify.yml
@ -1,40 +1,9 @@
|
||||
# Validate your changes with:
|
||||
#
|
||||
# $ curl -F 'data=@.mergify.yml' https://gh.mergify.io/validate/
|
||||
# $ curl -F 'data=@.mergify.yml' https://gh.mergify.io/validate
|
||||
#
|
||||
# https://doc.mergify.io/
|
||||
pull_request_rules:
|
||||
- name: automatic merge (squash) on CI success
|
||||
conditions:
|
||||
- status-success=buildkite/solana
|
||||
#- status-success=Travis CI - Pull Request
|
||||
- status-success=ci-gate
|
||||
- label=automerge
|
||||
- author≠@dont-squash-my-commits
|
||||
actions:
|
||||
merge:
|
||||
method: squash
|
||||
# Join the dont-squash-my-commits group if you won't like your commits squashed
|
||||
- name: automatic merge (rebase) on CI success
|
||||
conditions:
|
||||
- status-success=buildkite/solana
|
||||
#- status-success=Travis CI - Pull Request
|
||||
- status-success=ci-gate
|
||||
- label=automerge
|
||||
- author=@dont-squash-my-commits
|
||||
actions:
|
||||
merge:
|
||||
method: rebase
|
||||
- name: remove automerge label on CI failure
|
||||
conditions:
|
||||
- label=automerge
|
||||
- "#status-failure!=0"
|
||||
actions:
|
||||
label:
|
||||
remove:
|
||||
- automerge
|
||||
comment:
|
||||
message: automerge label removed due to a CI failure
|
||||
- name: remove outdated reviews
|
||||
conditions:
|
||||
- base=master
|
||||
@ -50,13 +19,20 @@ pull_request_rules:
|
||||
label:
|
||||
add:
|
||||
- automerge
|
||||
- name: v0.23 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.23
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.23
|
||||
- name: v1.0 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v1.0
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.0
|
||||
- name: v1.1 backport
|
||||
@ -65,7 +41,6 @@ pull_request_rules:
|
||||
- label=v1.1
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.1
|
||||
- name: v1.2 backport
|
||||
@ -74,6 +49,5 @@ pull_request_rules:
|
||||
- label=v1.2
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.2
|
||||
|
@ -18,8 +18,6 @@ branches:
|
||||
- master
|
||||
- /^v\d+\.\d+/
|
||||
|
||||
if: type IN (api, cron) OR tag IS present
|
||||
|
||||
notifications:
|
||||
slack:
|
||||
on_success: change
|
||||
|
@ -45,7 +45,7 @@ $ git pull --rebase upstream master
|
||||
|
||||
If there are no functional changes, PRs can be very large and that's no
|
||||
problem. If, however, your changes are making meaningful changes or additions,
|
||||
then about 1,000 lines of changes is about the most you should ask a Solana
|
||||
then about 1000 lines of changes is about the most you should ask a Solana
|
||||
maintainer to review.
|
||||
|
||||
### Should I send small PRs as I develop large, new components?
|
||||
|
6902
Cargo.lock
generated
6902
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,6 @@ members = [
|
||||
"bench-exchange",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"accounts-bench",
|
||||
"banking-bench",
|
||||
"chacha",
|
||||
"chacha-cuda",
|
||||
@ -11,8 +10,6 @@ members = [
|
||||
"cli-config",
|
||||
"client",
|
||||
"core",
|
||||
"dos",
|
||||
"download-utils",
|
||||
"faucet",
|
||||
"perf",
|
||||
"validator",
|
||||
@ -27,7 +24,6 @@ members = [
|
||||
"logger",
|
||||
"log-analyzer",
|
||||
"merkle-tree",
|
||||
"streamer",
|
||||
"measure",
|
||||
"metrics",
|
||||
"net-shaper",
|
||||
@ -52,13 +48,11 @@ members = [
|
||||
"sdk",
|
||||
"sdk-c",
|
||||
"scripts",
|
||||
"stake-accounts",
|
||||
"stake-monitor",
|
||||
"sys-tuner",
|
||||
"transaction-status",
|
||||
"upload-perf",
|
||||
"net-utils",
|
||||
"version",
|
||||
"vote-signer",
|
||||
"cli",
|
||||
"rayon-threadlimit",
|
||||
|
41
README.md
41
README.md
@ -9,7 +9,46 @@ Blockchain Rebuilt for Scale
|
||||
Solana™ is a new blockchain architecture built from the ground up for scale. The architecture supports
|
||||
up to 710 thousand transactions per second on a gigabit network.
|
||||
|
||||
Read all about it at [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com/v/master).
|
||||
Documentation
|
||||
===
|
||||
|
||||
Before you jump into the code, review the documentation [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com).
|
||||
|
||||
(The _latest_ development version of the docs is [available here](https://docs.solana.com/v/master).)
|
||||
|
||||
Release Binaries
|
||||
===
|
||||
Official release binaries are available at [Github Releases](https://github.com/solana-labs/solana/releases).
|
||||
|
||||
Additionally we provide pre-release binaries for the latest code on the edge and
|
||||
beta channels. Note that these pre-release binaries may be less stable than an
|
||||
official release.
|
||||
|
||||
### Edge channel
|
||||
#### Linux (x86_64-unknown-linux-gnu)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
|
||||
#### mac OS (x86_64-apple-darwin)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-apple-darwin.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
|
||||
#### Windows (x86_64-pc-windows-msvc)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-pc-windows-msvc.tar.bz2)
|
||||
* [solana-install-init.exe](http://release.solana.com/edge/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
|
||||
#### All platforms
|
||||
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/edge/solana-metrics.tar.bz2)
|
||||
|
||||
### Beta channel
|
||||
#### Linux (x86_64-unknown-linux-gnu)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
|
||||
#### mac OS (x86_64-apple-darwin)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-apple-darwin.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
|
||||
#### Windows (x86_64-pc-windows-msvc)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-pc-windows-msvc.tar.bz2)
|
||||
* [solana-install-init.exe](http://release.solana.com/beta/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
|
||||
#### All platforms
|
||||
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/beta/solana-metrics.tar.bz2)
|
||||
|
||||
Developing
|
||||
===
|
||||
|
@ -1,22 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-accounts-bench"
|
||||
version = "1.1.23"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.6"
|
||||
rayon = "1.3.0"
|
||||
solana-logger = { path = "../logger", version = "1.1.23" }
|
||||
solana-runtime = { path = "../runtime", version = "1.1.23" }
|
||||
solana-measure = { path = "../measure", version = "1.1.23" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.23" }
|
||||
rand = "0.7.0"
|
||||
clap = "2.33.0"
|
||||
crossbeam-channel = "0.4"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
@ -1,105 +0,0 @@
|
||||
use clap::{value_t, App, Arg};
|
||||
use rayon::prelude::*;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_runtime::{
|
||||
accounts::{create_test_accounts, update_accounts, Accounts},
|
||||
accounts_index::Ancestors,
|
||||
};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
|
||||
let matches = App::new("crate")
|
||||
.about("about")
|
||||
.version("version")
|
||||
.arg(
|
||||
Arg::with_name("num_slots")
|
||||
.long("num_slots")
|
||||
.takes_value(true)
|
||||
.value_name("SLOTS")
|
||||
.help("Number of slots to store to."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num_accounts")
|
||||
.long("num_accounts")
|
||||
.takes_value(true)
|
||||
.value_name("NUM_ACCOUNTS")
|
||||
.help("Total number of accounts"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("iterations")
|
||||
.long("iterations")
|
||||
.takes_value(true)
|
||||
.value_name("ITERATIONS")
|
||||
.help("Number of bench iterations"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("clean")
|
||||
.long("clean")
|
||||
.takes_value(false)
|
||||
.help("Run clean"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let num_slots = value_t!(matches, "num_slots", usize).unwrap_or(4);
|
||||
let num_accounts = value_t!(matches, "num_accounts", usize).unwrap_or(10_000);
|
||||
let iterations = value_t!(matches, "iterations", usize).unwrap_or(20);
|
||||
let clean = matches.is_present("clean");
|
||||
println!("clean: {:?}", clean);
|
||||
|
||||
let path = PathBuf::from("farf/accounts-bench");
|
||||
if fs::remove_dir_all(path.clone()).is_err() {
|
||||
println!("Warning: Couldn't remove {:?}", path);
|
||||
}
|
||||
let accounts = Accounts::new(vec![path]);
|
||||
println!("Creating {} accounts", num_accounts);
|
||||
let mut create_time = Measure::start("create accounts");
|
||||
let pubkeys: Vec<_> = (0..num_slots)
|
||||
.into_par_iter()
|
||||
.map(|slot| {
|
||||
let mut pubkeys: Vec<Pubkey> = vec![];
|
||||
create_test_accounts(
|
||||
&accounts,
|
||||
&mut pubkeys,
|
||||
num_accounts / num_slots,
|
||||
slot as u64,
|
||||
);
|
||||
pubkeys
|
||||
})
|
||||
.collect();
|
||||
let pubkeys: Vec<_> = pubkeys.into_iter().flatten().collect();
|
||||
create_time.stop();
|
||||
println!(
|
||||
"created {} accounts in {} slots {}",
|
||||
(num_accounts / num_slots) * num_slots,
|
||||
num_slots,
|
||||
create_time
|
||||
);
|
||||
let mut ancestors: Ancestors = vec![(0, 0)].into_iter().collect();
|
||||
for i in 1..num_slots {
|
||||
ancestors.insert(i as u64, i - 1);
|
||||
accounts.add_root(i as u64);
|
||||
}
|
||||
for x in 0..iterations {
|
||||
if clean {
|
||||
let mut time = Measure::start("clean");
|
||||
accounts.accounts_db.clean_accounts();
|
||||
time.stop();
|
||||
println!("{}", time);
|
||||
for slot in 0..num_slots {
|
||||
update_accounts(&accounts, &pubkeys, ((x + 1) * num_slots + slot) as u64);
|
||||
accounts.add_root((x * num_slots + slot) as u64);
|
||||
}
|
||||
} else {
|
||||
let mut pubkeys: Vec<Pubkey> = vec![];
|
||||
let mut time = Measure::start("hash");
|
||||
let hash = accounts.accounts_db.update_accounts_hash(0, &ancestors);
|
||||
time.stop();
|
||||
println!("hash: {} {}", hash, time);
|
||||
create_test_accounts(&accounts, &mut pubkeys, 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-archiver-lib"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
description = "Solana Archiver Library"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -10,34 +10,30 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.2.1"
|
||||
crossbeam-channel = "0.4"
|
||||
ed25519-dalek = "=1.0.0-pre.3"
|
||||
crossbeam-channel = "0.3"
|
||||
ed25519-dalek = "=1.0.0-pre.1"
|
||||
log = "0.4.8"
|
||||
rand = "0.7.0"
|
||||
rand_chacha = "0.2.2"
|
||||
solana-client = { path = "../client", version = "1.1.23" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.1.23" }
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
solana-client = { path = "../client", version = "1.0.14" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.14" }
|
||||
thiserror = "1.0"
|
||||
serde = "1.0.105"
|
||||
serde_json = "1.0.48"
|
||||
serde = "1.0.104"
|
||||
serde_json = "1.0.46"
|
||||
serde_derive = "1.0.103"
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.23" }
|
||||
solana-chacha = { path = "../chacha", version = "1.1.23" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.1.23" }
|
||||
solana-ledger = { path = "../ledger", version = "1.1.23" }
|
||||
solana-logger = { path = "../logger", version = "1.1.23" }
|
||||
solana-perf = { path = "../perf", version = "1.1.23" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.23" }
|
||||
solana-core = { path = "../core", version = "1.1.23" }
|
||||
solana-streamer = { path = "../streamer", version = "1.1.23" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.1.23" }
|
||||
solana-metrics = { path = "../metrics", version = "1.1.23" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.14" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.14" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.14" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.14" }
|
||||
solana-logger = { path = "../logger", version = "1.0.14" }
|
||||
solana-perf = { path = "../perf", version = "1.0.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.14" }
|
||||
solana-core = { path = "../core", version = "1.0.14" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.14" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.14" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.2"
|
||||
hex = "0.4.0"
|
||||
|
||||
[lib]
|
||||
name = "solana_archiver_lib"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::result::ArchiverError;
|
||||
use crossbeam_channel::unbounded;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rand_chacha::{rand_core::SeedableRng, ChaChaRng};
|
||||
use rand::{thread_rng, Rng, SeedableRng};
|
||||
use rand_chacha::ChaChaRng;
|
||||
use solana_archiver_utils::sample_file;
|
||||
use solana_chacha::chacha::{chacha_cbc_encrypt_ledger, CHACHA_BLOCK_SIZE};
|
||||
use solana_client::{
|
||||
@ -10,14 +10,16 @@ use solana_client::{
|
||||
};
|
||||
use solana_core::{
|
||||
cluster_info::{ClusterInfo, Node, VALIDATOR_PORT_RANGE},
|
||||
cluster_slots::ClusterSlots,
|
||||
contact_info::ContactInfo,
|
||||
gossip_service::GossipService,
|
||||
repair_service::{self, RepairService, RepairSlotRange, RepairStats, RepairStrategy},
|
||||
packet::{limited_deserialize, PACKET_DATA_SIZE},
|
||||
repair_service,
|
||||
repair_service::{RepairService, RepairSlotRange, RepairStrategy},
|
||||
serve_repair::ServeRepair,
|
||||
shred_fetch_stage::ShredFetchStage,
|
||||
sigverify_stage::{DisabledSigVerifier, SigVerifyStage},
|
||||
storage_stage::NUM_STORAGE_SAMPLES,
|
||||
streamer::{receiver, responder, PacketReceiver},
|
||||
window_service::WindowService,
|
||||
};
|
||||
use solana_ledger::{
|
||||
@ -25,7 +27,6 @@ use solana_ledger::{
|
||||
};
|
||||
use solana_net_utils::bind_in_range;
|
||||
use solana_perf::packet::Packets;
|
||||
use solana_perf::packet::{limited_deserialize, PACKET_DATA_SIZE};
|
||||
use solana_perf::recycler::Recycler;
|
||||
use solana_sdk::packet::Packet;
|
||||
use solana_sdk::{
|
||||
@ -44,7 +45,6 @@ use solana_storage_program::{
|
||||
storage_contract::StorageContract,
|
||||
storage_instruction::{self, StorageAccountType},
|
||||
};
|
||||
use solana_streamer::streamer::{receiver, responder, PacketReceiver};
|
||||
use std::{
|
||||
io::{self, ErrorKind},
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
||||
@ -52,7 +52,7 @@ use std::{
|
||||
result,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
sync::mpsc::{channel, Receiver, Sender},
|
||||
sync::Arc,
|
||||
sync::{Arc, RwLock},
|
||||
thread::{sleep, spawn, JoinHandle},
|
||||
time::Duration,
|
||||
};
|
||||
@ -184,10 +184,10 @@ impl Archiver {
|
||||
|
||||
info!("Archiver: id: {}", keypair.pubkey());
|
||||
info!("Creating cluster info....");
|
||||
let cluster_info = ClusterInfo::new(node.info.clone(), keypair.clone());
|
||||
let mut cluster_info = ClusterInfo::new(node.info.clone(), keypair.clone());
|
||||
cluster_info.set_entrypoint(cluster_entrypoint.clone());
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let cluster_slots = Arc::new(ClusterSlots::default());
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
|
||||
// Note for now, this ledger will not contain any of the existing entries
|
||||
// in the ledger located at ledger_path, and will only append on newly received
|
||||
// entries after being passed to window_service
|
||||
@ -199,7 +199,7 @@ impl Archiver {
|
||||
|
||||
info!("Connecting to the cluster via {:?}", cluster_entrypoint);
|
||||
let (nodes, _) =
|
||||
match solana_core::gossip_service::discover_cluster(&cluster_entrypoint.gossip, 2) {
|
||||
match solana_core::gossip_service::discover_cluster(&cluster_entrypoint.gossip, 1) {
|
||||
Ok(nodes_and_archivers) => nodes_and_archivers,
|
||||
Err(e) => {
|
||||
//shutdown services before exiting
|
||||
@ -262,7 +262,6 @@ impl Archiver {
|
||||
repair_socket,
|
||||
shred_fetch_receiver,
|
||||
slot_sender,
|
||||
cluster_slots,
|
||||
) {
|
||||
Ok(window_service) => window_service,
|
||||
Err(e) => {
|
||||
@ -307,7 +306,7 @@ impl Archiver {
|
||||
fn run(
|
||||
meta: &mut ArchiverMeta,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
cluster_info: Arc<ClusterInfo>,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
archiver_keypair: &Arc<Keypair>,
|
||||
storage_keypair: &Arc<Keypair>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
@ -364,12 +363,12 @@ impl Archiver {
|
||||
}
|
||||
|
||||
fn redeem_rewards(
|
||||
cluster_info: &ClusterInfo,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
archiver_keypair: &Arc<Keypair>,
|
||||
storage_keypair: &Arc<Keypair>,
|
||||
client_commitment: CommitmentConfig,
|
||||
) {
|
||||
let nodes = cluster_info.tvu_peers();
|
||||
let nodes = cluster_info.read().unwrap().tvu_peers();
|
||||
let client = solana_core::gossip_service::get_client(&nodes);
|
||||
|
||||
if let Ok(Some(account)) =
|
||||
@ -401,10 +400,9 @@ impl Archiver {
|
||||
}
|
||||
|
||||
// Find a segment to replicate and download it.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn setup(
|
||||
meta: &mut ArchiverMeta,
|
||||
cluster_info: Arc<ClusterInfo>,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
node_info: &ContactInfo,
|
||||
@ -412,7 +410,6 @@ impl Archiver {
|
||||
repair_socket: Arc<UdpSocket>,
|
||||
shred_fetch_receiver: PacketReceiver,
|
||||
slot_sender: Sender<u64>,
|
||||
cluster_slots: Arc<ClusterSlots>,
|
||||
) -> Result<WindowService> {
|
||||
let slots_per_segment =
|
||||
match Self::get_segment_config(&cluster_info, meta.client_commitment) {
|
||||
@ -470,7 +467,6 @@ impl Archiver {
|
||||
RepairStrategy::RepairRange(repair_slot_range),
|
||||
&Arc::new(LeaderScheduleCache::default()),
|
||||
|_, _, _, _| true,
|
||||
cluster_slots,
|
||||
);
|
||||
info!("waiting for ledger download");
|
||||
Self::wait_for_segment_download(
|
||||
@ -490,7 +486,7 @@ impl Archiver {
|
||||
blockstore: &Arc<Blockstore>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
node_info: &ContactInfo,
|
||||
cluster_info: Arc<ClusterInfo>,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
) {
|
||||
info!(
|
||||
"window created, waiting for ledger download starting at slot {:?}",
|
||||
@ -518,8 +514,11 @@ impl Archiver {
|
||||
contact_info.tvu = "0.0.0.0:0".parse().unwrap();
|
||||
contact_info.wallclock = timestamp();
|
||||
// copy over the adopted shred_version from the entrypoint
|
||||
contact_info.shred_version = cluster_info.my_shred_version();
|
||||
cluster_info.update_contact_info(|current| *current = contact_info);
|
||||
contact_info.shred_version = cluster_info.read().unwrap().my_data().shred_version;
|
||||
{
|
||||
let mut cluster_info_w = cluster_info.write().unwrap();
|
||||
cluster_info_w.insert_self(contact_info);
|
||||
}
|
||||
}
|
||||
|
||||
fn encrypt_ledger(meta: &mut ArchiverMeta, blockstore: &Arc<Blockstore>) -> Result<()> {
|
||||
@ -622,12 +621,12 @@ impl Archiver {
|
||||
|
||||
fn submit_mining_proof(
|
||||
meta: &ArchiverMeta,
|
||||
cluster_info: &ClusterInfo,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
archiver_keypair: &Arc<Keypair>,
|
||||
storage_keypair: &Arc<Keypair>,
|
||||
) {
|
||||
// No point if we've got no storage account...
|
||||
let nodes = cluster_info.tvu_peers();
|
||||
let nodes = cluster_info.read().unwrap().tvu_peers();
|
||||
let client = solana_core::gossip_service::get_client(&nodes);
|
||||
let storage_balance = client
|
||||
.poll_get_balance_with_commitment(&storage_keypair.pubkey(), meta.client_commitment);
|
||||
@ -685,10 +684,13 @@ impl Archiver {
|
||||
}
|
||||
|
||||
fn get_segment_config(
|
||||
cluster_info: &ClusterInfo,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
client_commitment: CommitmentConfig,
|
||||
) -> Result<u64> {
|
||||
let rpc_peers = cluster_info.all_rpc_peers();
|
||||
let rpc_peers = {
|
||||
let cluster_info = cluster_info.read().unwrap();
|
||||
cluster_info.all_rpc_peers()
|
||||
};
|
||||
debug!("rpc peers: {:?}", rpc_peers);
|
||||
if !rpc_peers.is_empty() {
|
||||
let rpc_client = {
|
||||
@ -696,10 +698,16 @@ impl Archiver {
|
||||
RpcClient::new_socket(rpc_peers[node_index].rpc)
|
||||
};
|
||||
Ok(rpc_client
|
||||
.send::<u64>(
|
||||
RpcRequest::GetSlotsPerSegment,
|
||||
.send(
|
||||
&RpcRequest::GetSlotsPerSegment,
|
||||
serde_json::json!([client_commitment]),
|
||||
0,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("Error while making rpc request {:?}", err);
|
||||
ArchiverError::ClientError(err)
|
||||
})?
|
||||
.as_u64()
|
||||
.unwrap())
|
||||
} else {
|
||||
Err(ArchiverError::NoRpcPeers)
|
||||
@ -708,7 +716,7 @@ impl Archiver {
|
||||
|
||||
/// Waits until the first segment is ready, and returns the current segment
|
||||
fn poll_for_segment(
|
||||
cluster_info: &ClusterInfo,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
slots_per_segment: u64,
|
||||
previous_blockhash: &Hash,
|
||||
exit: &Arc<AtomicBool>,
|
||||
@ -728,24 +736,38 @@ impl Archiver {
|
||||
|
||||
/// Poll for a different blockhash and associated max_slot than `previous_blockhash`
|
||||
fn poll_for_blockhash_and_slot(
|
||||
cluster_info: &ClusterInfo,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
slots_per_segment: u64,
|
||||
previous_blockhash: &Hash,
|
||||
exit: &Arc<AtomicBool>,
|
||||
) -> Result<(Hash, u64)> {
|
||||
info!("waiting for the next turn...");
|
||||
loop {
|
||||
let rpc_peers = cluster_info.all_rpc_peers();
|
||||
let rpc_peers = {
|
||||
let cluster_info = cluster_info.read().unwrap();
|
||||
cluster_info.all_rpc_peers()
|
||||
};
|
||||
debug!("rpc peers: {:?}", rpc_peers);
|
||||
if !rpc_peers.is_empty() {
|
||||
let rpc_client = {
|
||||
let node_index = thread_rng().gen_range(0, rpc_peers.len());
|
||||
RpcClient::new_socket(rpc_peers[node_index].rpc)
|
||||
};
|
||||
let response = rpc_client
|
||||
.send(
|
||||
&RpcRequest::GetStorageTurn,
|
||||
serde_json::value::Value::Null,
|
||||
0,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("Error while making rpc request {:?}", err);
|
||||
ArchiverError::ClientError(err)
|
||||
})?;
|
||||
let RpcStorageTurn {
|
||||
blockhash: storage_blockhash,
|
||||
slot: turn_slot,
|
||||
} = rpc_client.send(RpcRequest::GetStorageTurn, serde_json::value::Value::Null)?;
|
||||
} = serde_json::from_value::<RpcStorageTurn>(response)
|
||||
.map_err(ArchiverError::JsonError)?;
|
||||
let turn_blockhash = storage_blockhash.parse().map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
@ -817,14 +839,13 @@ impl Archiver {
|
||||
repair_service::MAX_REPAIR_LENGTH,
|
||||
&repair_slot_range,
|
||||
);
|
||||
let mut repair_stats = RepairStats::default();
|
||||
//iter over the repairs and send them
|
||||
if let Ok(repairs) = repairs {
|
||||
let reqs: Vec<_> = repairs
|
||||
.into_iter()
|
||||
.filter_map(|repair_request| {
|
||||
serve_repair
|
||||
.map_repair_request(&repair_request, &mut repair_stats, Some(0))
|
||||
.map_repair_request(&repair_request)
|
||||
.map(|result| ((archiver_info.gossip, result), repair_request))
|
||||
.ok()
|
||||
})
|
||||
|
@ -1,3 +1,4 @@
|
||||
use serde_json;
|
||||
use solana_client::client_error;
|
||||
use solana_ledger::blockstore;
|
||||
use solana_sdk::transport;
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-archiver-utils"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
description = "Solana Archiver Utils"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -10,19 +10,16 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
rand = "0.7.0"
|
||||
solana-chacha = { path = "../chacha", version = "1.1.23" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.1.23" }
|
||||
solana-ledger = { path = "../ledger", version = "1.1.23" }
|
||||
solana-logger = { path = "../logger", version = "1.1.23" }
|
||||
solana-perf = { path = "../perf", version = "1.1.23" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.23" }
|
||||
rand = "0.6.5"
|
||||
solana-chacha = { path = "../chacha", version = "1.0.14" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.14" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.14" }
|
||||
solana-logger = { path = "../logger", version = "1.0.14" }
|
||||
solana-perf = { path = "../perf", version = "1.0.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.14" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.2"
|
||||
hex = "0.4.0"
|
||||
|
||||
[lib]
|
||||
name = "solana_archiver_utils"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -2,22 +2,19 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-archiver"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
console = "0.10.0"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.1.23" }
|
||||
solana-core = { path = "../core", version = "1.1.23" }
|
||||
solana-logger = { path = "../logger", version = "1.1.23" }
|
||||
solana-metrics = { path = "../metrics", version = "1.1.23" }
|
||||
solana-archiver-lib = { path = "../archiver-lib", version = "1.1.23" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.23" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.23" }
|
||||
console = "0.9.2"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.14" }
|
||||
solana-core = { path = "../core", version = "1.0.14" }
|
||||
solana-logger = { path = "../logger", version = "1.0.14" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.14" }
|
||||
solana-archiver-lib = { path = "../archiver-lib", version = "1.0.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.14" }
|
||||
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -2,26 +2,19 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
crossbeam-channel = "0.4"
|
||||
log = "0.4.6"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.3.0"
|
||||
solana-core = { path = "../core", version = "1.1.23" }
|
||||
solana-streamer = { path = "../streamer", version = "1.1.23" }
|
||||
solana-perf = { path = "../perf", version = "1.1.23" }
|
||||
solana-ledger = { path = "../ledger", version = "1.1.23" }
|
||||
solana-logger = { path = "../logger", version = "1.1.23" }
|
||||
solana-runtime = { path = "../runtime", version = "1.1.23" }
|
||||
solana-measure = { path = "../measure", version = "1.1.23" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.23" }
|
||||
solana-version = { path = "../version", version = "1.1.23" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
rayon = "1.2.0"
|
||||
solana-core = { path = "../core", version = "1.0.14" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.14" }
|
||||
solana-logger = { path = "../logger", version = "1.0.14" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.14" }
|
||||
solana-measure = { path = "../measure", version = "1.0.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.14" }
|
||||
rand = "0.6.5"
|
||||
crossbeam-channel = "0.3"
|
||||
|
@ -1,38 +1,30 @@
|
||||
use clap::{crate_description, crate_name, value_t, App, Arg};
|
||||
use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana_core::{
|
||||
banking_stage::{create_test_recorder, BankingStage},
|
||||
cluster_info::ClusterInfo,
|
||||
cluster_info::Node,
|
||||
poh_recorder::PohRecorder,
|
||||
poh_recorder::WorkingBankEntry,
|
||||
};
|
||||
use solana_ledger::{
|
||||
bank_forks::BankForks,
|
||||
blockstore::Blockstore,
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
get_tmp_ledger_path,
|
||||
};
|
||||
use solana_core::banking_stage::{create_test_recorder, BankingStage};
|
||||
use solana_core::cluster_info::ClusterInfo;
|
||||
use solana_core::cluster_info::Node;
|
||||
use solana_core::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_core::packet::to_packets_chunked;
|
||||
use solana_core::poh_recorder::PohRecorder;
|
||||
use solana_core::poh_recorder::WorkingBankEntry;
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::Keypair,
|
||||
signature::Signature,
|
||||
system_transaction,
|
||||
timing::{duration_as_us, timestamp},
|
||||
transaction::Transaction,
|
||||
};
|
||||
use std::{
|
||||
sync::{atomic::Ordering, mpsc::Receiver, Arc, Mutex},
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::Keypair;
|
||||
use solana_sdk::signature::Signature;
|
||||
use solana_sdk::system_transaction;
|
||||
use solana_sdk::timing::{duration_as_us, timestamp};
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn check_txs(
|
||||
receiver: &Arc<Receiver<WorkingBankEntry>>,
|
||||
@ -65,22 +57,15 @@ fn check_txs(
|
||||
no_bank
|
||||
}
|
||||
|
||||
fn make_accounts_txs(
|
||||
total_num_transactions: usize,
|
||||
hash: Hash,
|
||||
same_payer: bool,
|
||||
) -> Vec<Transaction> {
|
||||
fn make_accounts_txs(txes: usize, mint_keypair: &Keypair, hash: Hash) -> Vec<Transaction> {
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
let payer_key = Keypair::new();
|
||||
let dummy = system_transaction::transfer(&payer_key, &to_pubkey, 1, hash);
|
||||
(0..total_num_transactions)
|
||||
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();
|
||||
if !same_payer {
|
||||
new.message.account_keys[0] = Pubkey::new_rand();
|
||||
}
|
||||
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
|
||||
@ -104,61 +89,13 @@ fn bytes_as_usize(bytes: &[u8]) -> usize {
|
||||
bytes[0] as usize | (bytes[1] as usize) << 8
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
|
||||
let matches = App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(solana_version::version!())
|
||||
.arg(
|
||||
Arg::with_name("num_chunks")
|
||||
.long("num-chunks")
|
||||
.takes_value(true)
|
||||
.value_name("SIZE")
|
||||
.help("Number of transaction chunks."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("packets_per_chunk")
|
||||
.long("packets-per-chunk")
|
||||
.takes_value(true)
|
||||
.value_name("SIZE")
|
||||
.help("Packets per chunk"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("skip_sanity")
|
||||
.long("skip-sanity")
|
||||
.takes_value(false)
|
||||
.help("Skip transaction sanity execution"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("same_payer")
|
||||
.long("same-payer")
|
||||
.takes_value(false)
|
||||
.help("Use the same payer for transfers"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("iterations")
|
||||
.long("iterations")
|
||||
.takes_value(true)
|
||||
.help("Number of iterations"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num_threads")
|
||||
.long("num-threads")
|
||||
.takes_value(true)
|
||||
.help("Number of iterations"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let num_threads =
|
||||
value_t!(matches, "num_threads", usize).unwrap_or(BankingStage::num_threads() as usize);
|
||||
let num_threads = BankingStage::num_threads() as usize;
|
||||
// a multiple of packet chunk duplicates to avoid races
|
||||
let num_chunks = value_t!(matches, "num_chunks", usize).unwrap_or(16);
|
||||
let packets_per_chunk = value_t!(matches, "packets_per_chunk", usize).unwrap_or(192);
|
||||
let iterations = value_t!(matches, "iterations", usize).unwrap_or(1000);
|
||||
|
||||
let total_num_transactions = num_chunks * num_threads * packets_per_chunk;
|
||||
const CHUNKS: usize = 8 * 2;
|
||||
const PACKETS_PER_BATCH: usize = 192;
|
||||
let txes = PACKETS_PER_BATCH * num_threads * CHUNKS;
|
||||
let mint_total = 1_000_000_000_000;
|
||||
let GenesisConfigInfo {
|
||||
genesis_config,
|
||||
@ -172,44 +109,34 @@ fn main() {
|
||||
let mut bank_forks = BankForks::new(0, bank0);
|
||||
let mut bank = bank_forks.working_bank();
|
||||
|
||||
info!("threads: {} txs: {}", num_threads, total_num_transactions);
|
||||
info!("threads: {} txs: {}", num_threads, txes);
|
||||
|
||||
let same_payer = matches.is_present("same_payer");
|
||||
let mut transactions =
|
||||
make_accounts_txs(total_num_transactions, genesis_config.hash(), same_payer);
|
||||
let mut transactions = make_accounts_txs(txes, &mint_keypair, genesis_config.hash());
|
||||
|
||||
// fund all the accounts
|
||||
transactions.iter().for_each(|tx| {
|
||||
let mut fund = system_transaction::transfer(
|
||||
let fund = system_transaction::transfer(
|
||||
&mint_keypair,
|
||||
&tx.message.account_keys[0],
|
||||
mint_total / total_num_transactions as u64,
|
||||
mint_total / txes as u64,
|
||||
genesis_config.hash(),
|
||||
);
|
||||
// Ignore any pesky duplicate signature errors in the case we are using single-payer
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
fund.signatures = vec![Signature::new(&sig[0..64])];
|
||||
let x = bank.process_transaction(&fund);
|
||||
x.unwrap();
|
||||
});
|
||||
|
||||
let skip_sanity = matches.is_present("skip_sanity");
|
||||
if !skip_sanity {
|
||||
//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 error: {:?}", res);
|
||||
});
|
||||
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 error: {:?}", r);
|
||||
}
|
||||
bank.clear_signatures();
|
||||
//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");
|
||||
}
|
||||
|
||||
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), packets_per_chunk);
|
||||
bank.clear_signatures();
|
||||
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
{
|
||||
let blockstore = Arc::new(
|
||||
@ -218,7 +145,7 @@ fn main() {
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, None);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
@ -228,7 +155,7 @@ fn main() {
|
||||
);
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
|
||||
let chunk_len = verified.len() / num_chunks;
|
||||
let chunk_len = verified.len() / CHUNKS;
|
||||
let mut start = 0;
|
||||
|
||||
// This is so that the signal_receiver does not go out of scope after the closure.
|
||||
@ -237,17 +164,17 @@ fn main() {
|
||||
let signal_receiver = Arc::new(signal_receiver);
|
||||
let mut total_us = 0;
|
||||
let mut tx_total_us = 0;
|
||||
let base_tx_count = bank.transaction_count();
|
||||
let mut txs_processed = 0;
|
||||
let mut root = 1;
|
||||
let collector = Pubkey::new_rand();
|
||||
const ITERS: usize = 1_000;
|
||||
let config = Config {
|
||||
packets_per_batch: packets_per_chunk,
|
||||
packets_per_batch: PACKETS_PER_BATCH,
|
||||
chunk_len,
|
||||
num_threads,
|
||||
};
|
||||
let mut total_sent = 0;
|
||||
for _ in 0..iterations {
|
||||
for _ in 0..ITERS {
|
||||
let now = Instant::now();
|
||||
let mut sent = 0;
|
||||
|
||||
@ -288,11 +215,7 @@ fn main() {
|
||||
sleep(Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
if check_txs(
|
||||
&signal_receiver,
|
||||
total_num_transactions / num_chunks,
|
||||
&poh_recorder,
|
||||
) {
|
||||
if check_txs(&signal_receiver, txes / CHUNKS, &poh_recorder) {
|
||||
debug!(
|
||||
"resetting bank {} tx count: {} txs_proc: {}",
|
||||
bank.slot(),
|
||||
@ -323,7 +246,7 @@ fn main() {
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
assert!(poh_recorder.lock().unwrap().bank().is_some());
|
||||
if bank.slot() > 32 {
|
||||
bank_forks.set_root(root, &None, None);
|
||||
bank_forks.set_root(root, &None);
|
||||
root += 1;
|
||||
}
|
||||
debug!(
|
||||
@ -344,7 +267,7 @@ fn main() {
|
||||
debug!(
|
||||
"time: {} us checked: {} sent: {}",
|
||||
duration_as_us(&now.elapsed()),
|
||||
total_num_transactions / num_chunks,
|
||||
txes / CHUNKS,
|
||||
sent,
|
||||
);
|
||||
total_sent += sent;
|
||||
@ -355,26 +278,20 @@ fn main() {
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
tx.signatures[0] = Signature::new(&sig[0..64]);
|
||||
}
|
||||
verified = to_packets_chunked(&transactions.clone(), packets_per_chunk);
|
||||
verified = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
|
||||
}
|
||||
|
||||
start += chunk_len;
|
||||
start %= verified.len();
|
||||
}
|
||||
let txs_processed = bank_forks.working_bank().transaction_count();
|
||||
debug!("processed: {} base: {}", txs_processed, base_tx_count);
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_total', 'median': '{:.2}'}}",
|
||||
"{{'name': 'banking_bench_total', 'median': '{}'}}",
|
||||
(1000.0 * 1000.0 * total_sent as f64) / (total_us as f64),
|
||||
);
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_tx_total', 'median': '{:.2}'}}",
|
||||
"{{'name': 'banking_bench_tx_total', 'median': '{}'}}",
|
||||
(1000.0 * 1000.0 * total_sent as f64) / (tx_total_us as f64),
|
||||
);
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_success_tx_total', 'median': '{:.2}'}}",
|
||||
(1000.0 * 1000.0 * (txs_processed - base_tx_count) as f64) / (total_us as f64),
|
||||
);
|
||||
|
||||
drop(verified_sender);
|
||||
drop(vote_sender);
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -10,28 +10,25 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
clap = "2.32.0"
|
||||
itertools = "0.9.0"
|
||||
itertools = "0.8.2"
|
||||
log = "0.4.8"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.3.0"
|
||||
serde_json = "1.0.48"
|
||||
rand = "0.6.5"
|
||||
rayon = "1.2.0"
|
||||
serde_json = "1.0.46"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.1.23" }
|
||||
solana-core = { path = "../core", version = "1.1.23" }
|
||||
solana-genesis = { path = "../genesis", version = "1.1.23" }
|
||||
solana-client = { path = "../client", version = "1.1.23" }
|
||||
solana-faucet = { path = "../faucet", version = "1.1.23" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.1.23" }
|
||||
solana-logger = { path = "../logger", version = "1.1.23" }
|
||||
solana-metrics = { path = "../metrics", version = "1.1.23" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.23" }
|
||||
solana-runtime = { path = "../runtime", version = "1.1.23" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.23" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.14" }
|
||||
solana-core = { path = "../core", version = "1.0.14" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.14" }
|
||||
solana-client = { path = "../client", version = "1.0.14" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.14" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.0.14" }
|
||||
solana-logger = { path = "../logger", version = "1.0.14" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.14" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.14" }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.1.23" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.14" }
|
||||
|
@ -86,7 +86,7 @@ fn test_exchange_bank_client() {
|
||||
solana_logger::setup();
|
||||
let (genesis_config, identity) = create_genesis_config(100_000_000_000_000);
|
||||
let mut bank = Bank::new(&genesis_config);
|
||||
bank.add_static_program("exchange_program", id(), process_instruction);
|
||||
bank.add_instruction_processor(id(), process_instruction);
|
||||
let clients = vec![BankClient::new(bank)];
|
||||
|
||||
let mut config = Config::default();
|
||||
|
@ -2,17 +2,14 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.1.23" }
|
||||
solana-streamer = { path = "../streamer", version = "1.1.23" }
|
||||
solana-logger = { path = "../logger", version = "1.1.23" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.23" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.14" }
|
||||
solana-core = { path = "../core", version = "1.0.14" }
|
||||
solana-logger = { path = "../logger", version = "1.0.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.14" }
|
||||
|
@ -1,6 +1,6 @@
|
||||
use clap::{crate_description, crate_name, App, Arg};
|
||||
use solana_streamer::packet::{Packet, Packets, PacketsRecycler, PACKET_DATA_SIZE};
|
||||
use solana_streamer::streamer::{receiver, PacketReceiver};
|
||||
use solana_core::packet::{Packet, Packets, PacketsRecycler, PACKET_DATA_SIZE};
|
||||
use solana_core::streamer::{receiver, PacketReceiver};
|
||||
use std::cmp::max;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -11,30 +11,27 @@ homepage = "https://solana.com/"
|
||||
bincode = "1.2.1"
|
||||
clap = "2.33.0"
|
||||
log = "0.4.8"
|
||||
rayon = "1.3.0"
|
||||
serde_json = "1.0.48"
|
||||
rayon = "1.2.0"
|
||||
serde_json = "1.0.46"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.1.23" }
|
||||
solana-core = { path = "../core", version = "1.1.23" }
|
||||
solana-genesis = { path = "../genesis", version = "1.1.23" }
|
||||
solana-client = { path = "../client", version = "1.1.23" }
|
||||
solana-faucet = { path = "../faucet", version = "1.1.23" }
|
||||
#solana-librapay = { path = "../programs/librapay", version = "1.1.8", optional = true }
|
||||
solana-logger = { path = "../logger", version = "1.1.23" }
|
||||
solana-metrics = { path = "../metrics", version = "1.1.23" }
|
||||
solana-measure = { path = "../measure", version = "1.1.23" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.23" }
|
||||
solana-runtime = { path = "../runtime", version = "1.1.23" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.23" }
|
||||
#solana-move-loader-program = { path = "../programs/move_loader", version = "1.1.8", optional = true }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.14" }
|
||||
solana-core = { path = "../core", version = "1.0.14" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.14" }
|
||||
solana-client = { path = "../client", version = "1.0.14" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.14" }
|
||||
solana-librapay = { path = "../programs/librapay", version = "1.0.14", optional = true }
|
||||
solana-logger = { path = "../logger", version = "1.0.14" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.14" }
|
||||
solana-measure = { path = "../measure", version = "1.0.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.14" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.14" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader", version = "1.0.14", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.4.0"
|
||||
serial_test = "0.3.2"
|
||||
serial_test_derive = "0.4.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.1.23" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.14" }
|
||||
|
||||
#[features]
|
||||
#move = ["solana-librapay", "solana-move-loader-program"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
[features]
|
||||
move = ["solana-librapay", "solana-move-loader-program"]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-chacha-cuda"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
description = "Solana Chacha Cuda APIs"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -10,18 +10,15 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.1.23" }
|
||||
solana-chacha = { path = "../chacha", version = "1.1.23" }
|
||||
solana-ledger = { path = "../ledger", version = "1.1.23" }
|
||||
solana-logger = { path = "../logger", version = "1.1.23" }
|
||||
solana-perf = { path = "../perf", version = "1.1.23" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.23" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.14" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.14" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.14" }
|
||||
solana-logger = { path = "../logger", version = "1.0.14" }
|
||||
solana-perf = { path = "../perf", version = "1.0.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.14" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2.1"
|
||||
|
||||
[lib]
|
||||
name = "solana_chacha_cuda"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-chacha-sys"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
description = "Solana chacha-sys"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -10,6 +10,3 @@ edition = "2018"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.49"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-chacha"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
description = "Solana Chacha APIs"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -10,19 +10,16 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
rand = "0.7.0"
|
||||
rand_chacha = "0.2.2"
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.1.23" }
|
||||
solana-ledger = { path = "../ledger", version = "1.1.23" }
|
||||
solana-logger = { path = "../logger", version = "1.1.23" }
|
||||
solana-perf = { path = "../perf", version = "1.1.23" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.23" }
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.14" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.14" }
|
||||
solana-logger = { path = "../logger", version = "1.0.14" }
|
||||
solana-perf = { path = "../perf", version = "1.0.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.14" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2.1"
|
||||
|
||||
[lib]
|
||||
name = "solana_chacha"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -2,16 +2,6 @@
|
||||
# Build steps that run after the primary pipeline on pushes and tags.
|
||||
# Pull requests to not run these steps.
|
||||
steps:
|
||||
- command: "ci/publish-tarball.sh"
|
||||
timeout_in_minutes: 60
|
||||
name: "publish tarball"
|
||||
- command: "ci/publish-docs.sh"
|
||||
timeout_in_minutes: 15
|
||||
name: "publish docs"
|
||||
- command: "ci/publish-bpf-sdk.sh"
|
||||
timeout_in_minutes: 5
|
||||
name: "publish bpf sdk"
|
||||
- wait
|
||||
- command: "sdk/docker-solana/build.sh"
|
||||
timeout_in_minutes: 60
|
||||
name: "publish docker"
|
||||
@ -19,6 +9,12 @@ steps:
|
||||
timeout_in_minutes: 240
|
||||
name: "publish crate"
|
||||
branches: "!master"
|
||||
# - command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-move.sh"
|
||||
# name: "move"
|
||||
# timeout_in_minutes: 20
|
||||
- command: "ci/publish-bpf-sdk.sh"
|
||||
timeout_in_minutes: 5
|
||||
name: "publish bpf sdk"
|
||||
- command: "ci/publish-tarball.sh"
|
||||
timeout_in_minutes: 60
|
||||
name: "publish tarball"
|
||||
- command: "ci/publish-docs.sh"
|
||||
timeout_in_minutes: 15
|
||||
name: "publish docs"
|
||||
|
@ -2,15 +2,12 @@
|
||||
# other than those in docs/ are modified
|
||||
|
||||
steps:
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
|
||||
name: "coverage"
|
||||
timeout_in_minutes: 30
|
||||
- wait
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-stable.sh"
|
||||
name: "stable"
|
||||
timeout_in_minutes: 60
|
||||
artifact_paths: "log-*.txt"
|
||||
- command: "ci/shellcheck.sh"
|
||||
name: "shellcheck"
|
||||
timeout_in_minutes: 5
|
||||
|
||||
- wait
|
||||
|
||||
- command: "ci/test-stable-perf.sh"
|
||||
name: "stable-perf"
|
||||
timeout_in_minutes: 40
|
||||
@ -20,7 +17,21 @@ steps:
|
||||
- command: "ci/test-bench.sh"
|
||||
name: "bench"
|
||||
timeout_in_minutes: 30
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-stable.sh"
|
||||
name: "stable"
|
||||
timeout_in_minutes: 60
|
||||
artifact_paths: "log-*.txt"
|
||||
agents:
|
||||
- "queue=rpc-test-capable"
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-move.sh"
|
||||
name: "move"
|
||||
timeout_in_minutes: 20
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-local-cluster.sh"
|
||||
name: "local-cluster"
|
||||
timeout_in_minutes: 45
|
||||
artifact_paths: "log-*.txt"
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
|
||||
name: "coverage"
|
||||
timeout_in_minutes: 30
|
||||
agents:
|
||||
- "queue=rpc-test-capable"
|
||||
|
@ -5,15 +5,9 @@
|
||||
# Release tags use buildkite-release.yml instead
|
||||
|
||||
steps:
|
||||
- command: "ci/test-sanity.sh"
|
||||
name: "sanity"
|
||||
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: 20
|
||||
- command: "ci/shellcheck.sh"
|
||||
name: "shellcheck"
|
||||
timeout_in_minutes: 5
|
||||
|
||||
- wait
|
||||
|
||||
|
@ -49,7 +49,7 @@ else
|
||||
# ~/.cargo
|
||||
ARGS+=(--volume "$PWD:/home")
|
||||
fi
|
||||
ARGS+=(--env "HOME=/home" --env "CARGO_HOME=/home/.cargo")
|
||||
ARGS+=(--env "CARGO_HOME=/home/.cargo")
|
||||
|
||||
# kcov tries to set the personality of the binary which docker
|
||||
# doesn't allow by default.
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM solanalabs/rust:1.43.0
|
||||
FROM solanalabs/rust:1.42.0
|
||||
ARG date
|
||||
|
||||
RUN set -x \
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Note: when the rust version is changed also modify
|
||||
# ci/rust-version.sh to pick up the new image tag
|
||||
FROM rust:1.43.0
|
||||
FROM rust:1.42.0
|
||||
|
||||
# Add Google Protocol Buffers for Libra's metrics library.
|
||||
ENV PROTOC_VERSION 3.8.0
|
||||
@ -32,7 +32,6 @@ RUN set -x \
|
||||
&& cargo install cargo-audit \
|
||||
&& cargo install svgbob_cli \
|
||||
&& cargo install mdbook \
|
||||
&& cargo install mdbook-linkcheck \
|
||||
&& rustc --version \
|
||||
&& cargo --version \
|
||||
&& curl -OL https://github.com/google/protobuf/releases/download/v$PROTOC_VERSION/$PROTOC_ZIP \
|
||||
|
@ -71,7 +71,7 @@ echo --- Creating release tarball
|
||||
export CHANNEL
|
||||
|
||||
source ci/rust-version.sh stable
|
||||
scripts/cargo-install-all.sh +"$rust_stable" solana-release
|
||||
scripts/cargo-install-all.sh +"$rust_stable" --use-move solana-release
|
||||
|
||||
tar cvf solana-release-$TARGET.tar solana-release
|
||||
bzip2 solana-release-$TARGET.tar
|
||||
@ -95,8 +95,9 @@ fi
|
||||
source ci/upload-ci-artifact.sh
|
||||
|
||||
for file in solana-release-$TARGET.tar.bz2 solana-release-$TARGET.yml solana-install-init-"$TARGET"* $MAYBE_TARBALLS; do
|
||||
upload-ci-artifact "$file"
|
||||
|
||||
if [[ -n $DO_NOT_PUBLISH_TAR ]]; then
|
||||
upload-ci-artifact "$file"
|
||||
echo "Skipped $file due to DO_NOT_PUBLISH_TAR"
|
||||
continue
|
||||
fi
|
||||
|
@ -2,10 +2,8 @@
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
# shellcheck source=multinode-demo/common.sh
|
||||
source multinode-demo/common.sh
|
||||
|
||||
rm -rf config/run/init-completed config/ledger config/snapshot-ledger
|
||||
rm -f config/run/init-completed
|
||||
|
||||
timeout 15 ./run.sh &
|
||||
pid=$!
|
||||
@ -19,16 +17,6 @@ while [[ ! -f config/run/init-completed ]]; do
|
||||
fi
|
||||
done
|
||||
|
||||
snapshot_slot=1
|
||||
|
||||
# wait a bit longer than snapshot_slot
|
||||
while [[ $($solana_cli --url http://localhost:8899 slot --commitment recent) -le $((snapshot_slot + 1)) ]]; do
|
||||
sleep 1
|
||||
done
|
||||
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"validatorExit"}' http://localhost:8899
|
||||
|
||||
wait $pid
|
||||
|
||||
$solana_ledger_tool create-snapshot --ledger config/ledger "$snapshot_slot" config/snapshot-ledger
|
||||
cp config/ledger/genesis.tar.bz2 config/snapshot-ledger
|
||||
$solana_ledger_tool verify --ledger config/snapshot-ledger
|
||||
|
@ -1,30 +1,28 @@
|
||||
#
|
||||
# This file maintains the rust versions for use by CI.
|
||||
#
|
||||
# Build with stable rust, updating the stable toolchain if necessary:
|
||||
# $ source ci/rust-version.sh stable
|
||||
# $ cargo +"$rust_stable" build
|
||||
#
|
||||
# Build with nightly rust, updating the nightly toolchain if necessary:
|
||||
# $ source ci/rust-version.sh nightly
|
||||
# $ cargo +"$rust_nightly" build
|
||||
#
|
||||
# Obtain the environment variables without any automatic toolchain updating:
|
||||
# $ source ci/rust-version.sh
|
||||
#
|
||||
# Obtain the environment variables updating both stable and nightly, only stable, or
|
||||
# only nightly:
|
||||
# $ source ci/rust-version.sh all
|
||||
# $ source ci/rust-version.sh stable
|
||||
# $ source ci/rust-version.sh nightly
|
||||
|
||||
# Then to build with either stable or nightly:
|
||||
# $ cargo +"$rust_stable" build
|
||||
# $ cargo +"$rust_nightly" build
|
||||
#
|
||||
|
||||
if [[ -n $RUST_STABLE_VERSION ]]; then
|
||||
stable_version="$RUST_STABLE_VERSION"
|
||||
else
|
||||
stable_version=1.43.0
|
||||
stable_version=1.42.0
|
||||
fi
|
||||
|
||||
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
||||
nightly_version="$RUST_NIGHTLY_VERSION"
|
||||
else
|
||||
nightly_version=2020-04-23
|
||||
nightly_version=2020-03-12
|
||||
fi
|
||||
|
||||
|
||||
@ -53,10 +51,6 @@ export rust_nightly_docker_image=solanalabs/rust-nightly:"$nightly_version"
|
||||
nightly)
|
||||
rustup_install "$rust_nightly"
|
||||
;;
|
||||
all)
|
||||
rustup_install "$rust_stable"
|
||||
rustup_install "$rust_nightly"
|
||||
;;
|
||||
*)
|
||||
echo "Note: ignoring unknown argument: $1"
|
||||
;;
|
||||
|
@ -25,7 +25,7 @@ source ci/_
|
||||
source ci/upload-ci-artifact.sh
|
||||
|
||||
eval "$(ci/channel-info.sh)"
|
||||
source ci/rust-version.sh all
|
||||
source ci/rust-version.sh nightly
|
||||
|
||||
set -o pipefail
|
||||
export RUST_BACKTRACE=1
|
||||
@ -67,9 +67,8 @@ _ cargo +$rust_nightly bench --manifest-path core/Cargo.toml ${V:+--verbose} \
|
||||
_ cargo +$rust_nightly bench --manifest-path programs/bpf/Cargo.toml ${V:+--verbose} --features=bpf_c \
|
||||
-- -Z unstable-options --format=json --nocapture | tee -a "$BENCH_FILE"
|
||||
|
||||
# Run banking/accounts bench. Doesn't require nightly, but use since it is already built.
|
||||
# Run banking bench. Doesn't require nightly, but use since it is already built.
|
||||
_ cargo +$rust_nightly run --release --manifest-path banking-bench/Cargo.toml ${V:+--verbose} | tee -a "$BENCH_FILE"
|
||||
_ cargo +$rust_nightly run --release --manifest-path accounts-bench/Cargo.toml ${V:+--verbose} -- --num_accounts 10000 --num_slots 4 | tee -a "$BENCH_FILE"
|
||||
|
||||
# `solana-upload-perf` disabled as it can take over 30 minutes to complete for some
|
||||
# reason
|
||||
|
@ -10,6 +10,9 @@ source ci/rust-version.sh nightly
|
||||
export RUST_BACKTRACE=1
|
||||
export RUSTFLAGS="-D warnings"
|
||||
|
||||
# Look for failed mergify.io backports
|
||||
_ git show HEAD --check --oneline
|
||||
|
||||
_ cargo +"$rust_stable" fmt --all -- --check
|
||||
|
||||
# Clippy gets stuck for unknown reasons if sdk-c is included in the build, so check it separately.
|
||||
@ -20,8 +23,10 @@ _ cargo +"$rust_stable" clippy --manifest-path sdk-c/Cargo.toml -- --deny=warnin
|
||||
|
||||
_ cargo +"$rust_stable" audit --version
|
||||
_ cargo +"$rust_stable" audit --ignore RUSTSEC-2020-0002 --ignore RUSTSEC-2020-0008
|
||||
_ ci/nits.sh
|
||||
_ ci/order-crates-for-publishing.py
|
||||
_ docs/build.sh
|
||||
_ ci/check-ssh-keys.sh
|
||||
|
||||
{
|
||||
cd programs/bpf
|
||||
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
source ci/_
|
||||
|
||||
(
|
||||
echo --- git diff --check
|
||||
set -x
|
||||
# Look for failed mergify.io backports by searching leftover conflict markers
|
||||
# Also check for any trailing whitespaces!
|
||||
if [[ -n $BUILDKITE_PULL_REQUEST_BASE_BRANCH ]]; then
|
||||
base_branch=$BUILDKITE_PULL_REQUEST_BASE_BRANCH
|
||||
else
|
||||
base_branch=$BUILDKITE_BRANCH
|
||||
fi
|
||||
git fetch origin "$base_branch"
|
||||
git diff "$(git merge-base HEAD "origin/$base_branch")..HEAD" --check --oneline
|
||||
)
|
||||
|
||||
echo
|
||||
|
||||
_ ci/nits.sh
|
||||
_ ci/check-ssh-keys.sh
|
||||
|
||||
echo --- ok
|
@ -38,16 +38,11 @@ test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
|
||||
# Clear the BPF sysroot files, they are not automatically rebuilt
|
||||
rm -rf target/xargo # Issue #3105
|
||||
|
||||
# Limit compiler jobs to reduce memory usage
|
||||
# on machines with 2gb/thread of memory
|
||||
NPROC=$(nproc)
|
||||
NPROC=$((NPROC>14 ? 14 : NPROC))
|
||||
|
||||
echo "Executing $testName"
|
||||
case $testName in
|
||||
test-stable)
|
||||
_ cargo +"$rust_stable" test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
#_ cargo +"$rust_stable" test --manifest-path bench-tps/Cargo.toml --features=move ${V:+--verbose} test_bench_tps_local_cluster_move -- --nocapture
|
||||
_ cargo +"$rust_stable" test --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
_ cargo +"$rust_stable" test --manifest-path bench-tps/Cargo.toml --features=move ${V:+--verbose} test_bench_tps_local_cluster_move -- --nocapture
|
||||
;;
|
||||
test-stable-perf)
|
||||
ci/affects-files.sh \
|
||||
|
@ -23,14 +23,10 @@ if [[ -z $CI_TAG ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Force CI_REPO_SLUG since sometimes
|
||||
# BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG is not set correctly, causing the
|
||||
# artifact upload to fail
|
||||
CI_REPO_SLUG=solana-labs/solana
|
||||
#if [[ -z $CI_REPO_SLUG ]]; then
|
||||
# echo Error: CI_REPO_SLUG not defined
|
||||
# exit 1
|
||||
#fi
|
||||
if [[ -z $CI_REPO_SLUG ]]; then
|
||||
echo Error: CI_REPO_SLUG not defined
|
||||
exit 1
|
||||
fi
|
||||
|
||||
releaseId=$( \
|
||||
curl -s "https://api.github.com/repos/$CI_REPO_SLUG/releases/tags/$CI_TAG" \
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-clap-utils"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
description = "Solana utilities for the clap"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -11,8 +11,8 @@ edition = "2018"
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
rpassword = "4.0"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.1.23" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.23" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.14" }
|
||||
thiserror = "1.0.11"
|
||||
tiny-bip39 = "0.7.0"
|
||||
url = "2.1.0"
|
||||
@ -20,6 +20,3 @@ chrono = "0.4"
|
||||
|
||||
[lib]
|
||||
name = "solana_clap_utils"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -1,22 +0,0 @@
|
||||
use crate::ArgConstant;
|
||||
use clap::Arg;
|
||||
|
||||
pub const COMMITMENT_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "commitment",
|
||||
long: "commitment",
|
||||
help: "Return information at the selected commitment level",
|
||||
};
|
||||
|
||||
pub fn commitment_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
commitment_arg_with_default("recent")
|
||||
}
|
||||
|
||||
pub fn commitment_arg_with_default<'a, 'b>(default_value: &'static str) -> Arg<'a, 'b> {
|
||||
Arg::with_name(COMMITMENT_ARG.name)
|
||||
.long(COMMITMENT_ARG.long)
|
||||
.takes_value(true)
|
||||
.possible_values(&["recent", "root", "max"])
|
||||
.default_value(default_value)
|
||||
.value_name("COMMITMENT_LEVEL")
|
||||
.help(COMMITMENT_ARG.help)
|
||||
}
|
@ -7,7 +7,6 @@ use clap::ArgMatches;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
clock::UnixTimestamp,
|
||||
commitment_config::CommitmentConfig,
|
||||
native_token::sol_to_lamports,
|
||||
pubkey::Pubkey,
|
||||
signature::{read_keypair_file, Keypair, Signature, Signer},
|
||||
@ -63,21 +62,6 @@ pub fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option<Keypair> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keypairs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Keypair>> {
|
||||
matches.values_of(name).map(|values| {
|
||||
values
|
||||
.filter_map(|value| {
|
||||
if value == ASK_KEYWORD {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
keypair_from_seed_phrase(name, skip_validation, true).ok()
|
||||
} else {
|
||||
read_keypair_file(value).ok()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
// Return a pubkey for an argument that can itself be parsed into a pubkey,
|
||||
// or is a filename that can be read as a keypair
|
||||
pub fn pubkey_of(matches: &ArgMatches<'_>, name: &str) -> Option<Pubkey> {
|
||||
@ -117,7 +101,7 @@ pub fn pubkeys_sigs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<(Pubk
|
||||
pub fn signer_of(
|
||||
matches: &ArgMatches<'_>,
|
||||
name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<(Option<Box<dyn Signer>>, Option<Pubkey>), Box<dyn std::error::Error>> {
|
||||
if let Some(location) = matches.value_of(name) {
|
||||
let signer = signer_from_path(matches, location, name, wallet_manager)?;
|
||||
@ -131,7 +115,7 @@ pub fn signer_of(
|
||||
pub fn pubkey_of_signer(
|
||||
matches: &ArgMatches<'_>,
|
||||
name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<Pubkey>, Box<dyn std::error::Error>> {
|
||||
if let Some(location) = matches.value_of(name) {
|
||||
Ok(Some(pubkey_from_path(
|
||||
@ -148,7 +132,7 @@ pub fn pubkey_of_signer(
|
||||
pub fn pubkeys_of_multiple_signers(
|
||||
matches: &ArgMatches<'_>,
|
||||
name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<Vec<Pubkey>>, Box<dyn std::error::Error>> {
|
||||
if let Some(pubkey_matches) = matches.values_of(name) {
|
||||
let mut pubkeys: Vec<Pubkey> = vec![];
|
||||
@ -164,7 +148,7 @@ pub fn pubkeys_of_multiple_signers(
|
||||
pub fn resolve_signer(
|
||||
matches: &ArgMatches<'_>,
|
||||
name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
Ok(resolve_signer_from_path(
|
||||
matches,
|
||||
@ -178,15 +162,6 @@ pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> {
|
||||
value_of(matches, name).map(sol_to_lamports)
|
||||
}
|
||||
|
||||
pub fn commitment_of(matches: &ArgMatches<'_>, name: &str) -> Option<CommitmentConfig> {
|
||||
matches.value_of(name).map(|value| match value {
|
||||
"max" => CommitmentConfig::max(),
|
||||
"recent" => CommitmentConfig::recent(),
|
||||
"root" => CommitmentConfig::root(),
|
||||
_ => CommitmentConfig::default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -6,86 +6,50 @@ use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{read_keypair_file, Signature},
|
||||
};
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
fn is_parsable_generic<U, T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
U: FromStr,
|
||||
U::Err: Display,
|
||||
{
|
||||
string
|
||||
.as_ref()
|
||||
.parse::<U>()
|
||||
.map(|_| ())
|
||||
.map_err(|err| format!("error parsing '{}': {}", string, err))
|
||||
}
|
||||
|
||||
// Return an error if string cannot be parsed as type T.
|
||||
// Takes a String to avoid second type parameter when used as a clap validator
|
||||
pub fn is_parsable<T>(string: String) -> Result<(), String>
|
||||
where
|
||||
T: FromStr,
|
||||
T::Err: Display,
|
||||
{
|
||||
is_parsable_generic::<T, String>(string)
|
||||
}
|
||||
|
||||
// Return an error if a pubkey cannot be parsed.
|
||||
pub fn is_pubkey<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
is_parsable_generic::<Pubkey, _>(string)
|
||||
pub fn is_pubkey(string: String) -> Result<(), String> {
|
||||
match string.parse::<Pubkey>() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(format!("{}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Return an error if a hash cannot be parsed.
|
||||
pub fn is_hash<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
is_parsable_generic::<Hash, _>(string)
|
||||
pub fn is_hash(string: String) -> Result<(), String> {
|
||||
match string.parse::<Hash>() {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(format!("{}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Return an error if a keypair file cannot be parsed.
|
||||
pub fn is_keypair<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
read_keypair_file(string.as_ref())
|
||||
pub fn is_keypair(string: String) -> Result<(), String> {
|
||||
read_keypair_file(&string)
|
||||
.map(|_| ())
|
||||
.map_err(|err| format!("{}", err))
|
||||
}
|
||||
|
||||
// Return an error if a keypair file cannot be parsed
|
||||
pub fn is_keypair_or_ask_keyword<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
if string.as_ref() == ASK_KEYWORD {
|
||||
pub fn is_keypair_or_ask_keyword(string: String) -> Result<(), String> {
|
||||
if string.as_str() == ASK_KEYWORD {
|
||||
return Ok(());
|
||||
}
|
||||
read_keypair_file(string.as_ref())
|
||||
read_keypair_file(&string)
|
||||
.map(|_| ())
|
||||
.map_err(|err| format!("{}", err))
|
||||
}
|
||||
|
||||
// Return an error if string cannot be parsed as pubkey string or keypair file location
|
||||
pub fn is_pubkey_or_keypair<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
is_pubkey(string.as_ref()).or_else(|_| is_keypair(string))
|
||||
pub fn is_pubkey_or_keypair(string: String) -> Result<(), String> {
|
||||
is_pubkey(string.clone()).or_else(|_| is_keypair(string))
|
||||
}
|
||||
|
||||
// Return an error if string cannot be parsed as a pubkey string, or a valid Signer that can
|
||||
// produce a pubkey()
|
||||
pub fn is_valid_pubkey<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
match parse_keypair_path(string.as_ref()) {
|
||||
pub fn is_valid_pubkey(string: String) -> Result<(), String> {
|
||||
match parse_keypair_path(&string) {
|
||||
KeypairUrl::Filepath(path) => is_keypair(path),
|
||||
_ => Ok(()),
|
||||
}
|
||||
@ -99,19 +63,13 @@ where
|
||||
// when paired with an offline `--signer` argument to provide a Presigner (pubkey + signature).
|
||||
// Clap validators can't check multiple fields at once, so the verification that a `--signer` is
|
||||
// also provided and correct happens in parsing, not in validation.
|
||||
pub fn is_valid_signer<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
pub fn is_valid_signer(string: String) -> Result<(), String> {
|
||||
is_valid_pubkey(string)
|
||||
}
|
||||
|
||||
// Return an error if string cannot be parsed as pubkey=signature string
|
||||
pub fn is_pubkey_sig<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let mut signer = string.as_ref().split('=');
|
||||
pub fn is_pubkey_sig(string: String) -> Result<(), String> {
|
||||
let mut signer = string.split('=');
|
||||
match Pubkey::from_str(
|
||||
signer
|
||||
.next()
|
||||
@ -132,11 +90,8 @@ where
|
||||
}
|
||||
|
||||
// Return an error if a url cannot be parsed.
|
||||
pub fn is_url<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
match url::Url::parse(string.as_ref()) {
|
||||
pub fn is_url(string: String) -> Result<(), String> {
|
||||
match url::Url::parse(&string) {
|
||||
Ok(url) => {
|
||||
if url.has_host() {
|
||||
Ok(())
|
||||
@ -148,26 +103,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_slot<T>(slot: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
is_parsable_generic::<Slot, _>(slot)
|
||||
pub fn is_slot(slot: String) -> Result<(), String> {
|
||||
slot.parse::<Slot>()
|
||||
.map(|_| ())
|
||||
.map_err(|e| format!("{}", e))
|
||||
}
|
||||
|
||||
pub fn is_port<T>(port: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
is_parsable_generic::<u16, _>(port)
|
||||
pub fn is_port(port: String) -> Result<(), String> {
|
||||
port.parse::<u16>()
|
||||
.map(|_| ())
|
||||
.map_err(|e| format!("{}", e))
|
||||
}
|
||||
|
||||
pub fn is_valid_percentage<T>(percentage: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
pub fn is_valid_percentage(percentage: String) -> Result<(), String> {
|
||||
percentage
|
||||
.as_ref()
|
||||
.parse::<u8>()
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
@ -187,11 +136,8 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_amount<T>(amount: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
if amount.as_ref().parse::<u64>().is_ok() || amount.as_ref().parse::<f64>().is_ok() {
|
||||
pub fn is_amount(amount: String) -> Result<(), String> {
|
||||
if amount.parse::<u64>().is_ok() || amount.parse::<f64>().is_ok() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
@ -201,20 +147,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_rfc3339_datetime<T>(value: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
DateTime::parse_from_rfc3339(value.as_ref())
|
||||
pub fn is_rfc3339_datetime(value: String) -> Result<(), String> {
|
||||
DateTime::parse_from_rfc3339(&value)
|
||||
.map(|_| ())
|
||||
.map_err(|e| format!("{}", e))
|
||||
}
|
||||
|
||||
pub fn is_derivation<T>(value: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let value = value.as_ref().replace("'", "");
|
||||
pub fn is_derivation(value: String) -> Result<(), String> {
|
||||
let value = value.replace("'", "");
|
||||
let mut parts = value.split('/');
|
||||
let account = parts.next().unwrap();
|
||||
account
|
||||
@ -246,14 +186,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_is_derivation() {
|
||||
assert_eq!(is_derivation("2"), Ok(()));
|
||||
assert_eq!(is_derivation("0"), Ok(()));
|
||||
assert_eq!(is_derivation("65537"), Ok(()));
|
||||
assert_eq!(is_derivation("0/2"), Ok(()));
|
||||
assert_eq!(is_derivation("0'/2'"), Ok(()));
|
||||
assert!(is_derivation("a").is_err());
|
||||
assert!(is_derivation("4294967296").is_err());
|
||||
assert!(is_derivation("a/b").is_err());
|
||||
assert!(is_derivation("0/4294967296").is_err());
|
||||
assert_eq!(is_derivation("2".to_string()), Ok(()));
|
||||
assert_eq!(is_derivation("0".to_string()), Ok(()));
|
||||
assert_eq!(is_derivation("65537".to_string()), Ok(()));
|
||||
assert_eq!(is_derivation("0/2".to_string()), Ok(()));
|
||||
assert_eq!(is_derivation("0'/2'".to_string()), Ok(()));
|
||||
assert!(is_derivation("a".to_string()).is_err());
|
||||
assert!(is_derivation("4294967296".to_string()).is_err());
|
||||
assert!(is_derivation("a/b".to_string()).is_err());
|
||||
assert!(is_derivation("0/4294967296".to_string()).is_err());
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use clap::ArgMatches;
|
||||
use rpassword::prompt_password_stderr;
|
||||
use solana_remote_wallet::{
|
||||
remote_keypair::generate_remote_keypair,
|
||||
remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager},
|
||||
remote_wallet::{RemoteWalletError, RemoteWalletManager},
|
||||
};
|
||||
use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
@ -64,7 +64,7 @@ pub fn signer_from_path(
|
||||
matches: &ArgMatches,
|
||||
path: &str,
|
||||
keypair_name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<Box<dyn Signer>, Box<dyn error::Error>> {
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Ask => {
|
||||
@ -88,9 +88,6 @@ pub fn signer_from_path(
|
||||
Ok(Box::new(read_keypair(&mut stdin)?))
|
||||
}
|
||||
KeypairUrl::Usb(path) => {
|
||||
if wallet_manager.is_none() {
|
||||
*wallet_manager = maybe_wallet_manager()?;
|
||||
}
|
||||
if let Some(wallet_manager) = wallet_manager {
|
||||
Ok(Box::new(generate_remote_keypair(
|
||||
path,
|
||||
@ -125,7 +122,7 @@ pub fn pubkey_from_path(
|
||||
matches: &ArgMatches,
|
||||
path: &str,
|
||||
keypair_name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<Pubkey, Box<dyn error::Error>> {
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Pubkey(pubkey) => Ok(pubkey),
|
||||
@ -137,7 +134,7 @@ pub fn resolve_signer_from_path(
|
||||
matches: &ArgMatches,
|
||||
path: &str,
|
||||
keypair_name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<String>, Box<dyn error::Error>> {
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Ask => {
|
||||
@ -161,9 +158,6 @@ pub fn resolve_signer_from_path(
|
||||
read_keypair(&mut stdin).map(|_| None)
|
||||
}
|
||||
KeypairUrl::Usb(path) => {
|
||||
if wallet_manager.is_none() {
|
||||
*wallet_manager = maybe_wallet_manager()?;
|
||||
}
|
||||
if let Some(wallet_manager) = wallet_manager {
|
||||
let path = generate_remote_keypair(
|
||||
path,
|
||||
|
@ -42,7 +42,6 @@ impl std::fmt::Debug for DisplayError {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod commitment;
|
||||
pub mod input_parsers;
|
||||
pub mod input_validators;
|
||||
pub mod keypair;
|
||||
|
@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-config"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -11,10 +11,7 @@ homepage = "https://solana.com/"
|
||||
[dependencies]
|
||||
dirs = "2.0.2"
|
||||
lazy_static = "1.4.0"
|
||||
serde = "1.0.105"
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.103"
|
||||
serde_yaml = "0.8.11"
|
||||
url = "2.1.1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Wallet settings that can be configured for long-term use
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, io};
|
||||
use std::io;
|
||||
use url::Url;
|
||||
|
||||
lazy_static! {
|
||||
@ -17,8 +17,6 @@ pub struct Config {
|
||||
pub json_rpc_url: String,
|
||||
pub websocket_url: String,
|
||||
pub keypair_path: String,
|
||||
#[serde(default)]
|
||||
pub address_labels: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -28,7 +26,7 @@ impl Default for Config {
|
||||
keypair_path.extend(&[".config", "solana", "id.json"]);
|
||||
keypair_path.to_str().unwrap().to_string()
|
||||
};
|
||||
let json_rpc_url = "https://api.mainnet-beta.solana.com".to_string();
|
||||
let json_rpc_url = "http://127.0.0.1:8899".to_string();
|
||||
|
||||
// Empty websocket_url string indicates the client should
|
||||
// `Config::compute_websocket_url(&json_rpc_url)`
|
||||
@ -38,7 +36,6 @@ impl Default for Config {
|
||||
json_rpc_url,
|
||||
websocket_url,
|
||||
keypair_path,
|
||||
address_labels: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,38 +60,17 @@ impl Config {
|
||||
ws_url
|
||||
.set_scheme(if is_secure { "wss" } else { "ws" })
|
||||
.expect("unable to set scheme");
|
||||
if let Some(port) = json_rpc_url.port() {
|
||||
ws_url.set_port(Some(port + 1)).expect("unable to set port");
|
||||
}
|
||||
let ws_port = match json_rpc_url.port() {
|
||||
Some(port) => port + 1,
|
||||
None => {
|
||||
if is_secure {
|
||||
8901
|
||||
} else {
|
||||
8900
|
||||
}
|
||||
}
|
||||
};
|
||||
ws_url.set_port(Some(ws_port)).expect("unable to set port");
|
||||
ws_url.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn compute_websocket_url() {
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"http://devnet.solana.com"),
|
||||
"ws://devnet.solana.com/".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"https://devnet.solana.com"),
|
||||
"wss://devnet.solana.com/".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"http://example.com:8899"),
|
||||
"ws://example.com:8900/".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"https://example.com:1234"),
|
||||
"wss://example.com:1235/".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(Config::compute_websocket_url(&"garbage"), String::new());
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -11,49 +11,45 @@ homepage = "https://solana.com/"
|
||||
[dependencies]
|
||||
bincode = "1.2.1"
|
||||
bs58 = "0.3.0"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
chrono = { version = "0.4.10", features = ["serde"] }
|
||||
clap = "2.33.0"
|
||||
criterion-stats = "0.3.0"
|
||||
ctrlc = { version = "3.1.4", features = ["termination"] }
|
||||
console = "0.10.0"
|
||||
console = "0.9.2"
|
||||
dirs = "2.0.2"
|
||||
log = "0.4.8"
|
||||
Inflector = "0.11.4"
|
||||
indicatif = "0.14.0"
|
||||
humantime = "2.0.0"
|
||||
num-traits = "0.2"
|
||||
pretty-hex = "0.1.1"
|
||||
reqwest = { version = "0.10.4", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serde = "1.0.105"
|
||||
reqwest = { version = "0.10.1", default-features = false, features = ["blocking", "rustls-tls"] }
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.48"
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.1.23" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.1.23" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.1.23" }
|
||||
solana-client = { path = "../client", version = "1.1.23" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.1.23" }
|
||||
solana-faucet = { path = "../faucet", version = "1.1.23" }
|
||||
solana-logger = { path = "../logger", version = "1.1.23" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.23" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.1.23" }
|
||||
solana-runtime = { path = "../runtime", version = "1.1.23" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.23" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.1.23" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.1.23" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.1.23" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.1.23" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.1.23" }
|
||||
thiserror = "1.0.13"
|
||||
serde_json = "1.0.46"
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.14" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.14" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.0.14" }
|
||||
solana-client = { path = "../client", version = "1.0.14" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.0.14" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.14" }
|
||||
solana-logger = { path = "../logger", version = "1.0.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.14" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.14" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.14" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.14" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.14" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.14" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.0.14" }
|
||||
titlecase = "1.1.0"
|
||||
thiserror = "1.0.11"
|
||||
url = "2.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-core = { path = "../core", version = "1.1.23" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.1.23" }
|
||||
solana-core = { path = "../core", version = "1.0.14" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.14" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "solana"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
726
cli/src/cli.rs
726
cli/src/cli.rs
File diff suppressed because it is too large
Load Diff
@ -1,982 +0,0 @@
|
||||
use crate::{cli::build_balance_message, display::writeln_name_value};
|
||||
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
|
||||
use console::{style, Emoji};
|
||||
use inflector::cases::titlecase::to_title_case;
|
||||
use serde::Serialize;
|
||||
use serde_json::{Map, Value};
|
||||
use solana_client::rpc_response::{
|
||||
RpcAccountBalance, RpcEpochInfo, RpcKeyedAccount, RpcSupply, RpcVoteAccountInfo,
|
||||
};
|
||||
use solana_sdk::{
|
||||
clock::{self, Epoch, Slot, UnixTimestamp},
|
||||
native_token::lamports_to_sol,
|
||||
stake_history::StakeHistoryEntry,
|
||||
};
|
||||
use solana_stake_program::stake_state::{Authorized, Lockup};
|
||||
use solana_vote_program::{
|
||||
authorized_voters::AuthorizedVoters,
|
||||
vote_state::{BlockTimestamp, Lockout},
|
||||
};
|
||||
use std::{collections::BTreeMap, fmt, time::Duration};
|
||||
|
||||
static WARNING: Emoji = Emoji("⚠️", "!");
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum OutputFormat {
|
||||
Display,
|
||||
Json,
|
||||
JsonCompact,
|
||||
}
|
||||
|
||||
impl OutputFormat {
|
||||
pub fn formatted_string<T>(&self, item: &T) -> String
|
||||
where
|
||||
T: Serialize + fmt::Display,
|
||||
{
|
||||
match self {
|
||||
OutputFormat::Display => format!("{}", item),
|
||||
OutputFormat::Json => serde_json::to_string_pretty(item).unwrap(),
|
||||
OutputFormat::JsonCompact => serde_json::to_value(item).unwrap().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliAccount {
|
||||
#[serde(flatten)]
|
||||
pub keyed_account: RpcKeyedAccount,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliAccount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Public Key:", &self.keyed_account.pubkey)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Balance:",
|
||||
&build_balance_message(
|
||||
self.keyed_account.account.lamports,
|
||||
self.use_lamports_unit,
|
||||
true,
|
||||
),
|
||||
)?;
|
||||
writeln_name_value(f, "Owner:", &self.keyed_account.account.owner)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Executable:",
|
||||
&self.keyed_account.account.executable.to_string(),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Rent Epoch:",
|
||||
&self.keyed_account.account.rent_epoch.to_string(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct CliBlockProduction {
|
||||
pub epoch: Epoch,
|
||||
pub start_slot: Slot,
|
||||
pub end_slot: Slot,
|
||||
pub total_slots: usize,
|
||||
pub total_blocks_produced: usize,
|
||||
pub total_slots_skipped: usize,
|
||||
pub leaders: Vec<CliBlockProductionEntry>,
|
||||
pub individual_slot_status: Vec<CliSlotStatus>,
|
||||
#[serde(skip_serializing)]
|
||||
pub verbose: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliBlockProduction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<44} {:>15} {:>15} {:>15} {:>23}",
|
||||
"Identity Pubkey",
|
||||
"Leader Slots",
|
||||
"Blocks Produced",
|
||||
"Skipped Slots",
|
||||
"Skipped Slot Percentage",
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for leader in &self.leaders {
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
|
||||
leader.identity_pubkey,
|
||||
leader.leader_slots,
|
||||
leader.blocks_produced,
|
||||
leader.skipped_slots,
|
||||
leader.skipped_slots as f64 / leader.leader_slots as f64 * 100.
|
||||
)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
|
||||
format!("Epoch {} total:", self.epoch),
|
||||
self.total_slots,
|
||||
self.total_blocks_produced,
|
||||
self.total_slots_skipped,
|
||||
self.total_slots_skipped as f64 / self.total_slots as f64 * 100.
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" (using data from {} slots: {} to {})",
|
||||
self.total_slots, self.start_slot, self.end_slot
|
||||
)?;
|
||||
if self.verbose {
|
||||
writeln!(f)?;
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(" {:<15} {:<44}", "Slot", "Identity Pubkey")).bold(),
|
||||
)?;
|
||||
for status in &self.individual_slot_status {
|
||||
if status.skipped {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<15} {:<44} SKIPPED",
|
||||
status.slot, status.leader
|
||||
))
|
||||
.red()
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(" {:<15} {:<44}", status.slot, status.leader))
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliBlockProductionEntry {
|
||||
pub identity_pubkey: String,
|
||||
pub leader_slots: u64,
|
||||
pub blocks_produced: u64,
|
||||
pub skipped_slots: u64,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliSlotStatus {
|
||||
pub slot: Slot,
|
||||
pub leader: String,
|
||||
pub skipped: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliEpochInfo {
|
||||
#[serde(flatten)]
|
||||
pub epoch_info: RpcEpochInfo,
|
||||
}
|
||||
|
||||
impl From<RpcEpochInfo> for CliEpochInfo {
|
||||
fn from(epoch_info: RpcEpochInfo) -> Self {
|
||||
Self { epoch_info }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliEpochInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Slot:", &self.epoch_info.absolute_slot.to_string())?;
|
||||
writeln_name_value(f, "Epoch:", &self.epoch_info.epoch.to_string())?;
|
||||
let start_slot = self.epoch_info.absolute_slot - self.epoch_info.slot_index;
|
||||
let end_slot = start_slot + self.epoch_info.slots_in_epoch;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Epoch Slot Range:",
|
||||
&format!("[{}..{})", start_slot, end_slot),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Epoch Completed Percent:",
|
||||
&format!(
|
||||
"{:>3.3}%",
|
||||
self.epoch_info.slot_index as f64 / self.epoch_info.slots_in_epoch as f64 * 100_f64
|
||||
),
|
||||
)?;
|
||||
let remaining_slots_in_epoch = self.epoch_info.slots_in_epoch - self.epoch_info.slot_index;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Epoch Completed Slots:",
|
||||
&format!(
|
||||
"{}/{} ({} remaining)",
|
||||
self.epoch_info.slot_index,
|
||||
self.epoch_info.slots_in_epoch,
|
||||
remaining_slots_in_epoch
|
||||
),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Epoch Completed Time:",
|
||||
&format!(
|
||||
"{}/{} ({} remaining)",
|
||||
slot_to_human_time(self.epoch_info.slot_index),
|
||||
slot_to_human_time(self.epoch_info.slots_in_epoch),
|
||||
slot_to_human_time(remaining_slots_in_epoch)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn slot_to_human_time(slot: Slot) -> String {
|
||||
humantime::format_duration(Duration::from_secs(
|
||||
slot * clock::DEFAULT_TICKS_PER_SLOT / clock::DEFAULT_TICKS_PER_SECOND,
|
||||
))
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliValidators {
|
||||
pub total_active_stake: u64,
|
||||
pub total_current_stake: u64,
|
||||
pub total_deliquent_stake: u64,
|
||||
pub current_validators: Vec<CliValidator>,
|
||||
pub delinquent_validators: Vec<CliValidator>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliValidators {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn write_vote_account(
|
||||
f: &mut fmt::Formatter,
|
||||
validator: &CliValidator,
|
||||
total_active_stake: u64,
|
||||
use_lamports_unit: bool,
|
||||
delinquent: bool,
|
||||
) -> fmt::Result {
|
||||
fn non_zero_or_dash(v: u64) -> String {
|
||||
if v == 0 {
|
||||
"-".into()
|
||||
} else {
|
||||
format!("{}", v)
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"{} {:<44} {:<44} {:>9}% {:>8} {:>10} {:>7} {}",
|
||||
if delinquent {
|
||||
WARNING.to_string()
|
||||
} else {
|
||||
" ".to_string()
|
||||
},
|
||||
validator.identity_pubkey,
|
||||
validator.vote_account_pubkey,
|
||||
validator.commission,
|
||||
non_zero_or_dash(validator.last_vote),
|
||||
non_zero_or_dash(validator.root_slot),
|
||||
validator.credits,
|
||||
if validator.activated_stake > 0 {
|
||||
format!(
|
||||
"{} ({:.2}%)",
|
||||
build_balance_message(validator.activated_stake, use_lamports_unit, true),
|
||||
100. * validator.activated_stake as f64 / total_active_stake as f64
|
||||
)
|
||||
} else {
|
||||
"-".into()
|
||||
},
|
||||
)
|
||||
}
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Active Stake:",
|
||||
&build_balance_message(self.total_active_stake, self.use_lamports_unit, true),
|
||||
)?;
|
||||
if self.total_deliquent_stake > 0 {
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Current Stake:",
|
||||
&format!(
|
||||
"{} ({:0.2}%)",
|
||||
&build_balance_message(self.total_current_stake, self.use_lamports_unit, true),
|
||||
100. * self.total_current_stake as f64 / self.total_active_stake as f64
|
||||
),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Delinquent Stake:",
|
||||
&format!(
|
||||
"{} ({:0.2}%)",
|
||||
&build_balance_message(
|
||||
self.total_deliquent_stake,
|
||||
self.use_lamports_unit,
|
||||
true
|
||||
),
|
||||
100. * self.total_deliquent_stake as f64 / self.total_active_stake as f64
|
||||
),
|
||||
)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<44} {:<44} {} {} {} {:>7} {}",
|
||||
"Identity Pubkey",
|
||||
"Vote Account Pubkey",
|
||||
"Commission",
|
||||
"Last Vote",
|
||||
"Root Block",
|
||||
"Credits",
|
||||
"Active Stake",
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for validator in &self.current_validators {
|
||||
write_vote_account(
|
||||
f,
|
||||
validator,
|
||||
self.total_active_stake,
|
||||
self.use_lamports_unit,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
for validator in &self.delinquent_validators {
|
||||
write_vote_account(
|
||||
f,
|
||||
validator,
|
||||
self.total_active_stake,
|
||||
self.use_lamports_unit,
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliValidator {
|
||||
pub identity_pubkey: String,
|
||||
pub vote_account_pubkey: String,
|
||||
pub commission: u8,
|
||||
pub last_vote: u64,
|
||||
pub root_slot: u64,
|
||||
pub credits: u64,
|
||||
pub activated_stake: u64,
|
||||
}
|
||||
|
||||
impl CliValidator {
|
||||
pub fn new(vote_account: &RpcVoteAccountInfo, current_epoch: Epoch) -> Self {
|
||||
Self {
|
||||
identity_pubkey: vote_account.node_pubkey.to_string(),
|
||||
vote_account_pubkey: vote_account.vote_pubkey.to_string(),
|
||||
commission: vote_account.commission,
|
||||
last_vote: vote_account.last_vote,
|
||||
root_slot: vote_account.root_slot,
|
||||
credits: vote_account
|
||||
.epoch_credits
|
||||
.iter()
|
||||
.find_map(|(epoch, credits, _)| {
|
||||
if *epoch == current_epoch {
|
||||
Some(*credits)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(0),
|
||||
activated_stake: vote_account.activated_stake,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliNonceAccount {
|
||||
pub balance: u64,
|
||||
pub minimum_balance_for_rent_exemption: u64,
|
||||
pub nonce: Option<String>,
|
||||
pub lamports_per_signature: Option<u64>,
|
||||
pub authority: Option<String>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliNonceAccount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"Balance: {}",
|
||||
build_balance_message(self.balance, self.use_lamports_unit, true)
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Minimum Balance Required: {}",
|
||||
build_balance_message(
|
||||
self.minimum_balance_for_rent_exemption,
|
||||
self.use_lamports_unit,
|
||||
true
|
||||
)
|
||||
)?;
|
||||
let nonce = self.nonce.as_deref().unwrap_or("uninitialized");
|
||||
writeln!(f, "Nonce: {}", nonce)?;
|
||||
if let Some(fees) = self.lamports_per_signature {
|
||||
writeln!(f, "Fee: {} lamports per signature", fees)?;
|
||||
} else {
|
||||
writeln!(f, "Fees: uninitialized")?;
|
||||
}
|
||||
let authority = self.authority.as_deref().unwrap_or("uninitialized");
|
||||
writeln!(f, "Authority: {}", authority)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliStakeVec(Vec<CliKeyedStakeState>);
|
||||
|
||||
impl CliStakeVec {
|
||||
pub fn new(list: Vec<CliKeyedStakeState>) -> Self {
|
||||
Self(list)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliStakeVec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for state in &self.0 {
|
||||
writeln!(f)?;
|
||||
write!(f, "{}", state)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliKeyedStakeState {
|
||||
pub stake_pubkey: String,
|
||||
#[serde(flatten)]
|
||||
pub stake_state: CliStakeState,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliKeyedStakeState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "Stake Pubkey: {}", self.stake_pubkey)?;
|
||||
write!(f, "{}", self.stake_state)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliStakeState {
|
||||
pub stake_type: CliStakeType,
|
||||
pub total_stake: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delegated_stake: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delegated_vote_account_address: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub activation_epoch: Option<Epoch>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deactivation_epoch: Option<Epoch>,
|
||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||
pub authorized: Option<CliAuthorized>,
|
||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||
pub lockup: Option<CliLockup>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliStakeState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn show_authorized(f: &mut fmt::Formatter, authorized: &CliAuthorized) -> fmt::Result {
|
||||
writeln!(f, "Stake Authority: {}", authorized.staker)?;
|
||||
writeln!(f, "Withdraw Authority: {}", authorized.withdrawer)?;
|
||||
Ok(())
|
||||
}
|
||||
fn show_lockup(f: &mut fmt::Formatter, lockup: &CliLockup) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"Lockup Timestamp: {} (UnixTimestamp: {})",
|
||||
DateTime::<Utc>::from_utc(
|
||||
NaiveDateTime::from_timestamp(lockup.unix_timestamp, 0),
|
||||
Utc
|
||||
)
|
||||
.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
lockup.unix_timestamp
|
||||
)?;
|
||||
writeln!(f, "Lockup Epoch: {}", lockup.epoch)?;
|
||||
writeln!(f, "Lockup Custodian: {}", lockup.custodian)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
match self.stake_type {
|
||||
CliStakeType::RewardsPool => writeln!(f, "Stake account is a rewards pool")?,
|
||||
CliStakeType::Uninitialized => writeln!(f, "Stake account is uninitialized")?,
|
||||
CliStakeType::Initialized => {
|
||||
writeln!(
|
||||
f,
|
||||
"Total Stake: {}",
|
||||
build_balance_message(self.total_stake, self.use_lamports_unit, true)
|
||||
)?;
|
||||
writeln!(f, "Stake account is undelegated")?;
|
||||
show_authorized(f, self.authorized.as_ref().unwrap())?;
|
||||
show_lockup(f, self.lockup.as_ref().unwrap())?;
|
||||
}
|
||||
CliStakeType::Stake => {
|
||||
writeln!(
|
||||
f,
|
||||
"Total Stake: {}",
|
||||
build_balance_message(self.total_stake, self.use_lamports_unit, true)
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Delegated Stake: {}",
|
||||
build_balance_message(
|
||||
self.delegated_stake.unwrap(),
|
||||
self.use_lamports_unit,
|
||||
true
|
||||
)
|
||||
)?;
|
||||
if let Some(delegated_vote_account_address) = &self.delegated_vote_account_address {
|
||||
writeln!(
|
||||
f,
|
||||
"Delegated Vote Account Address: {}",
|
||||
delegated_vote_account_address
|
||||
)?;
|
||||
}
|
||||
writeln!(
|
||||
f,
|
||||
"Stake activates starting from epoch: {}",
|
||||
self.activation_epoch.unwrap()
|
||||
)?;
|
||||
if let Some(deactivation_epoch) = self.deactivation_epoch {
|
||||
writeln!(
|
||||
f,
|
||||
"Stake deactivates starting from epoch: {}",
|
||||
deactivation_epoch
|
||||
)?;
|
||||
}
|
||||
show_authorized(f, self.authorized.as_ref().unwrap())?;
|
||||
show_lockup(f, self.lockup.as_ref().unwrap())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum CliStakeType {
|
||||
Stake,
|
||||
RewardsPool,
|
||||
Uninitialized,
|
||||
Initialized,
|
||||
}
|
||||
|
||||
impl Default for CliStakeType {
|
||||
fn default() -> Self {
|
||||
Self::Uninitialized
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliStakeHistory {
|
||||
pub entries: Vec<CliStakeHistoryEntry>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliStakeHistory {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<5} {:>20} {:>20} {:>20}",
|
||||
"Epoch", "Effective Stake", "Activating Stake", "Deactivating Stake",
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for entry in &self.entries {
|
||||
writeln!(
|
||||
f,
|
||||
" {:>5} {:>20} {:>20} {:>20} {}",
|
||||
entry.epoch,
|
||||
build_balance_message(entry.effective_stake, self.use_lamports_unit, false),
|
||||
build_balance_message(entry.activating_stake, self.use_lamports_unit, false),
|
||||
build_balance_message(entry.deactivating_stake, self.use_lamports_unit, false),
|
||||
if self.use_lamports_unit {
|
||||
"lamports"
|
||||
} else {
|
||||
"SOL"
|
||||
}
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&(Epoch, StakeHistoryEntry)> for CliStakeHistoryEntry {
|
||||
fn from((epoch, entry): &(Epoch, StakeHistoryEntry)) -> Self {
|
||||
Self {
|
||||
epoch: *epoch,
|
||||
effective_stake: entry.effective,
|
||||
activating_stake: entry.activating,
|
||||
deactivating_stake: entry.deactivating,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliStakeHistoryEntry {
|
||||
pub epoch: Epoch,
|
||||
pub effective_stake: u64,
|
||||
pub activating_stake: u64,
|
||||
pub deactivating_stake: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliAuthorized {
|
||||
pub staker: String,
|
||||
pub withdrawer: String,
|
||||
}
|
||||
|
||||
impl From<&Authorized> for CliAuthorized {
|
||||
fn from(authorized: &Authorized) -> Self {
|
||||
Self {
|
||||
staker: authorized.staker.to_string(),
|
||||
withdrawer: authorized.withdrawer.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliLockup {
|
||||
pub unix_timestamp: UnixTimestamp,
|
||||
pub epoch: Epoch,
|
||||
pub custodian: String,
|
||||
}
|
||||
|
||||
impl From<&Lockup> for CliLockup {
|
||||
fn from(lockup: &Lockup) -> Self {
|
||||
Self {
|
||||
unix_timestamp: lockup.unix_timestamp,
|
||||
epoch: lockup.epoch,
|
||||
custodian: lockup.custodian.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliValidatorInfoVec(Vec<CliValidatorInfo>);
|
||||
|
||||
impl CliValidatorInfoVec {
|
||||
pub fn new(list: Vec<CliValidatorInfo>) -> Self {
|
||||
Self(list)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliValidatorInfoVec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.0.is_empty() {
|
||||
writeln!(f, "No validator info accounts found")?;
|
||||
}
|
||||
for validator_info in &self.0 {
|
||||
writeln!(f)?;
|
||||
write!(f, "{}", validator_info)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliValidatorInfo {
|
||||
pub identity_pubkey: String,
|
||||
pub info_pubkey: String,
|
||||
pub info: Map<String, Value>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliValidatorInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_name_value(f, "Validator Identity Pubkey:", &self.identity_pubkey)?;
|
||||
writeln_name_value(f, " Info Pubkey:", &self.info_pubkey)?;
|
||||
for (key, value) in self.info.iter() {
|
||||
writeln_name_value(
|
||||
f,
|
||||
&format!(" {}:", to_title_case(key)),
|
||||
&value.as_str().unwrap_or("?"),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliVoteAccount {
|
||||
pub account_balance: u64,
|
||||
pub validator_identity: String,
|
||||
#[serde(flatten)]
|
||||
pub authorized_voters: CliAuthorizedVoters,
|
||||
pub authorized_withdrawer: String,
|
||||
pub credits: u64,
|
||||
pub commission: u8,
|
||||
pub root_slot: Option<Slot>,
|
||||
pub recent_timestamp: BlockTimestamp,
|
||||
pub votes: Vec<CliLockout>,
|
||||
pub epoch_voting_history: Vec<CliEpochVotingHistory>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliVoteAccount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"Account Balance: {}",
|
||||
build_balance_message(self.account_balance, self.use_lamports_unit, true)
|
||||
)?;
|
||||
writeln!(f, "Validator Identity: {}", self.validator_identity)?;
|
||||
writeln!(f, "Authorized Voters: {}", self.authorized_voters)?;
|
||||
writeln!(f, "Authorized Withdrawer: {}", self.authorized_withdrawer)?;
|
||||
writeln!(f, "Credits: {}", self.credits)?;
|
||||
writeln!(f, "Commission: {}%", self.commission)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Root Slot: {}",
|
||||
match self.root_slot {
|
||||
Some(slot) => slot.to_string(),
|
||||
None => "~".to_string(),
|
||||
}
|
||||
)?;
|
||||
writeln!(f, "Recent Timestamp: {:?}", self.recent_timestamp)?;
|
||||
if !self.votes.is_empty() {
|
||||
writeln!(f, "Recent Votes:")?;
|
||||
for vote in &self.votes {
|
||||
writeln!(
|
||||
f,
|
||||
"- slot: {}\n confirmation count: {}",
|
||||
vote.slot, vote.confirmation_count
|
||||
)?;
|
||||
}
|
||||
writeln!(f, "Epoch Voting History:")?;
|
||||
for epoch_info in &self.epoch_voting_history {
|
||||
writeln!(
|
||||
f,
|
||||
"- epoch: {}\n slots in epoch: {}\n credits earned: {}",
|
||||
epoch_info.epoch, epoch_info.slots_in_epoch, epoch_info.credits_earned,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliAuthorizedVoters {
|
||||
authorized_voters: BTreeMap<Epoch, String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliAuthorizedVoters {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self.authorized_voters)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AuthorizedVoters> for CliAuthorizedVoters {
|
||||
fn from(authorized_voters: &AuthorizedVoters) -> Self {
|
||||
let mut voter_map: BTreeMap<Epoch, String> = BTreeMap::new();
|
||||
for (epoch, voter) in authorized_voters.iter() {
|
||||
voter_map.insert(*epoch, voter.to_string());
|
||||
}
|
||||
Self {
|
||||
authorized_voters: voter_map,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliEpochVotingHistory {
|
||||
pub epoch: Epoch,
|
||||
pub slots_in_epoch: u64,
|
||||
pub credits_earned: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliLockout {
|
||||
pub slot: Slot,
|
||||
pub confirmation_count: u32,
|
||||
}
|
||||
|
||||
impl From<&Lockout> for CliLockout {
|
||||
fn from(lockout: &Lockout) -> Self {
|
||||
Self {
|
||||
slot: lockout.slot,
|
||||
confirmation_count: lockout.confirmation_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliBlockTime {
|
||||
pub slot: Slot,
|
||||
pub timestamp: UnixTimestamp,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliBlockTime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_name_value(f, "Block:", &self.slot.to_string())?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Date:",
|
||||
&format!(
|
||||
"{} (UnixTimestamp: {})",
|
||||
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.timestamp, 0), Utc)
|
||||
.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
self.timestamp
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliSignOnlyData {
|
||||
pub blockhash: String,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub signers: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub absent: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub bad_sig: Vec<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliSignOnlyData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Blockhash:", &self.blockhash)?;
|
||||
if !self.signers.is_empty() {
|
||||
writeln!(f, "{}", style("Signers (Pubkey=Signature):").bold())?;
|
||||
for signer in self.signers.iter() {
|
||||
writeln!(f, " {}", signer)?;
|
||||
}
|
||||
}
|
||||
if !self.absent.is_empty() {
|
||||
writeln!(f, "{}", style("Absent Signers (Pubkey):").bold())?;
|
||||
for pubkey in self.absent.iter() {
|
||||
writeln!(f, " {}", pubkey)?;
|
||||
}
|
||||
}
|
||||
if !self.bad_sig.is_empty() {
|
||||
writeln!(f, "{}", style("Bad Signatures (Pubkey):").bold())?;
|
||||
for pubkey in self.bad_sig.iter() {
|
||||
writeln!(f, " {}", pubkey)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliSignature {
|
||||
pub signature: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliSignature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Signature:", &self.signature)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliAccountBalances {
|
||||
pub accounts: Vec<RpcAccountBalance>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliAccountBalances {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!("{:<44} {}", "Address", "Balance",)).bold()
|
||||
)?;
|
||||
for account in &self.accounts {
|
||||
writeln!(
|
||||
f,
|
||||
"{:<44} {}",
|
||||
account.address,
|
||||
&format!("{} SOL", lamports_to_sol(account.lamports))
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliSupply {
|
||||
pub total: u64,
|
||||
pub circulating: u64,
|
||||
pub non_circulating: u64,
|
||||
pub non_circulating_accounts: Vec<String>,
|
||||
#[serde(skip_serializing)]
|
||||
pub print_accounts: bool,
|
||||
}
|
||||
|
||||
impl From<RpcSupply> for CliSupply {
|
||||
fn from(rpc_supply: RpcSupply) -> Self {
|
||||
Self {
|
||||
total: rpc_supply.total,
|
||||
circulating: rpc_supply.circulating,
|
||||
non_circulating: rpc_supply.non_circulating,
|
||||
non_circulating_accounts: rpc_supply.non_circulating_accounts,
|
||||
print_accounts: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliSupply {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_name_value(f, "Total:", &format!("{} SOL", lamports_to_sol(self.total)))?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Circulating:",
|
||||
&format!("{} SOL", lamports_to_sol(self.circulating)),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Non-Circulating:",
|
||||
&format!("{} SOL", lamports_to_sol(self.non_circulating)),
|
||||
)?;
|
||||
if self.print_accounts {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Non-Circulating Accounts:", " ")?;
|
||||
for account in &self.non_circulating_accounts {
|
||||
writeln!(f, " {}", account)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,6 @@
|
||||
use crate::cli::SettingType;
|
||||
use console::style;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use solana_sdk::{
|
||||
hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize,
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_transaction_status::RpcTransactionStatusMeta;
|
||||
use std::{fmt, io};
|
||||
use solana_sdk::hash::Hash;
|
||||
|
||||
// Pretty print a "name value"
|
||||
pub fn println_name_value(name: &str, value: &str) {
|
||||
@ -18,15 +12,6 @@ pub fn println_name_value(name: &str, value: &str) {
|
||||
println!("{} {}", style(name).bold(), styled_value);
|
||||
}
|
||||
|
||||
pub fn writeln_name_value(f: &mut fmt::Formatter, name: &str, value: &str) -> fmt::Result {
|
||||
let styled_value = if value == "" {
|
||||
style("(not set)").italic()
|
||||
} else {
|
||||
style(value)
|
||||
};
|
||||
writeln!(f, "{} {}", style(name).bold(), styled_value)
|
||||
}
|
||||
|
||||
pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
|
||||
let description = match setting_type {
|
||||
SettingType::Explicit => "",
|
||||
@ -64,149 +49,3 @@ pub fn println_signers(
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
pub fn write_transaction<W: io::Write>(
|
||||
w: &mut W,
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<RpcTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
) -> io::Result<()> {
|
||||
let message = &transaction.message;
|
||||
writeln!(
|
||||
w,
|
||||
"{}Recent Blockhash: {:?}",
|
||||
prefix, message.recent_blockhash
|
||||
)?;
|
||||
for (signature_index, signature) in transaction.signatures.iter().enumerate() {
|
||||
writeln!(
|
||||
w,
|
||||
"{}Signature {}: {:?}",
|
||||
prefix, signature_index, signature
|
||||
)?;
|
||||
}
|
||||
writeln!(w, "{}{:?}", prefix, message.header)?;
|
||||
for (account_index, account) in message.account_keys.iter().enumerate() {
|
||||
writeln!(w, "{}Account {}: {:?}", prefix, account_index, account)?;
|
||||
}
|
||||
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
||||
let program_pubkey = message.account_keys[instruction.program_id_index as usize];
|
||||
writeln!(w, "{}Instruction {}", prefix, instruction_index)?;
|
||||
writeln!(
|
||||
w,
|
||||
"{} Program: {} ({})",
|
||||
prefix, program_pubkey, instruction.program_id_index
|
||||
)?;
|
||||
for (account_index, account) in instruction.accounts.iter().enumerate() {
|
||||
let account_pubkey = message.account_keys[*account as usize];
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {}: {} ({})",
|
||||
prefix, account_index, account_pubkey, account
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut raw = true;
|
||||
if program_pubkey == solana_vote_program::id() {
|
||||
if let Ok(vote_instruction) = limited_deserialize::<
|
||||
solana_vote_program::vote_instruction::VoteInstruction,
|
||||
>(&instruction.data)
|
||||
{
|
||||
writeln!(w, "{} {:?}", prefix, vote_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
} else if program_pubkey == solana_stake_program::id() {
|
||||
if let Ok(stake_instruction) = limited_deserialize::<
|
||||
solana_stake_program::stake_instruction::StakeInstruction,
|
||||
>(&instruction.data)
|
||||
{
|
||||
writeln!(w, "{} {:?}", prefix, stake_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
} else if program_pubkey == solana_sdk::system_program::id() {
|
||||
if let Ok(system_instruction) = limited_deserialize::<
|
||||
solana_sdk::system_instruction::SystemInstruction,
|
||||
>(&instruction.data)
|
||||
{
|
||||
writeln!(w, "{} {:?}", prefix, system_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
}
|
||||
|
||||
if raw {
|
||||
writeln!(w, "{} Data: {:?}", prefix, instruction.data)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(transaction_status) = transaction_status {
|
||||
writeln!(
|
||||
w,
|
||||
"{}Status: {}",
|
||||
prefix,
|
||||
match &transaction_status.status {
|
||||
Ok(_) => "Ok".into(),
|
||||
Err(err) => err.to_string(),
|
||||
}
|
||||
)?;
|
||||
writeln!(
|
||||
w,
|
||||
"{} Fee: {} SOL",
|
||||
prefix,
|
||||
lamports_to_sol(transaction_status.fee)
|
||||
)?;
|
||||
assert_eq!(
|
||||
transaction_status.pre_balances.len(),
|
||||
transaction_status.post_balances.len()
|
||||
);
|
||||
for (i, (pre, post)) in transaction_status
|
||||
.pre_balances
|
||||
.iter()
|
||||
.zip(transaction_status.post_balances.iter())
|
||||
.enumerate()
|
||||
{
|
||||
if pre == post {
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {} balance: {} SOL",
|
||||
prefix,
|
||||
i,
|
||||
lamports_to_sol(*pre)
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {} balance: {} SOL -> {} SOL",
|
||||
prefix,
|
||||
i,
|
||||
lamports_to_sol(*pre),
|
||||
lamports_to_sol(*post)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeln!(w, "{}Status: Unavailable", prefix)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn println_transaction(
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<RpcTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
) {
|
||||
let mut w = Vec::new();
|
||||
if write_transaction(&mut w, transaction, transaction_status, prefix).is_ok() {
|
||||
if let Ok(s) = String::from_utf8(w) {
|
||||
print!("{}", s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new process bar for processing that will take an unknown amount of time
|
||||
pub fn new_spinner_progress_bar() -> ProgressBar {
|
||||
let progress_bar = ProgressBar::new(42);
|
||||
progress_bar
|
||||
.set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}"));
|
||||
progress_bar.enable_steady_tick(100);
|
||||
progress_bar
|
||||
}
|
||||
|
@ -1,8 +1,4 @@
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod cli;
|
||||
pub mod cli_output;
|
||||
pub mod cluster_query;
|
||||
pub mod display;
|
||||
pub mod nonce;
|
||||
|
@ -2,15 +2,15 @@ use clap::{crate_description, crate_name, AppSettings, Arg, ArgGroup, ArgMatches
|
||||
use console::style;
|
||||
|
||||
use solana_clap_utils::{
|
||||
input_validators::is_url, keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, DisplayError,
|
||||
input_validators::is_url, keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, offline::SIGN_ONLY_ARG,
|
||||
DisplayError,
|
||||
};
|
||||
use solana_cli::{
|
||||
cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners},
|
||||
cli_output::OutputFormat,
|
||||
display::{println_name_value, println_name_value_or},
|
||||
};
|
||||
use solana_cli_config::{Config, CONFIG_FILE};
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_remote_wallet::remote_wallet::{maybe_wallet_manager, RemoteWalletManager};
|
||||
use std::{error, sync::Arc};
|
||||
|
||||
fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error>> {
|
||||
@ -102,7 +102,7 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
|
||||
pub fn parse_args<'a>(
|
||||
matches: &ArgMatches<'_>,
|
||||
mut wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<(CliConfig<'a>, CliSigners), Box<dyn error::Error>> {
|
||||
let config = if let Some(config_file) = matches.value_of("config_file") {
|
||||
Config::load(config_file).unwrap_or_default()
|
||||
@ -125,16 +125,7 @@ pub fn parse_args<'a>(
|
||||
);
|
||||
|
||||
let CliCommandInfo { command, signers } =
|
||||
parse_command(&matches, &default_signer_path, &mut wallet_manager)?;
|
||||
|
||||
let output_format = matches
|
||||
.value_of("output_format")
|
||||
.map(|value| match value {
|
||||
"json" => OutputFormat::Json,
|
||||
"json-compact" => OutputFormat::JsonCompact,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.unwrap_or(OutputFormat::Display);
|
||||
parse_command(&matches, &default_signer_path, wallet_manager.as_ref())?;
|
||||
|
||||
Ok((
|
||||
CliConfig {
|
||||
@ -145,7 +136,6 @@ pub fn parse_args<'a>(
|
||||
keypair_path: default_signer_path,
|
||||
rpc_client: None,
|
||||
verbose: matches.is_present("verbose"),
|
||||
output_format,
|
||||
},
|
||||
signers,
|
||||
))
|
||||
@ -207,14 +197,6 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.global(true)
|
||||
.help("Show additional information"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output_format")
|
||||
.long("output")
|
||||
.global(true)
|
||||
.takes_value(true)
|
||||
.possible_values(&["json", "json-compact"])
|
||||
.help("Return information in specified output format. Supports: json, json-compact"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
|
||||
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
|
||||
@ -256,12 +238,18 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
|
||||
fn do_main(matches: &ArgMatches<'_>) -> Result<(), Box<dyn error::Error>> {
|
||||
if parse_settings(&matches)? {
|
||||
let mut wallet_manager = None;
|
||||
let wallet_manager = maybe_wallet_manager()?;
|
||||
|
||||
let (mut config, signers) = parse_args(&matches, &mut wallet_manager)?;
|
||||
let (mut config, signers) = parse_args(&matches, wallet_manager)?;
|
||||
config.signers = signers.iter().map(|s| s.as_ref()).collect();
|
||||
let result = process_command(&config)?;
|
||||
println!("{}", result);
|
||||
let (_, submatches) = matches.subcommand();
|
||||
let sign_only = submatches
|
||||
.map(|m| m.is_present(SIGN_ONLY_ARG.name))
|
||||
.unwrap_or(false);
|
||||
if !sign_only {
|
||||
println!("{}", result);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
118
cli/src/nonce.rs
118
cli/src/nonce.rs
@ -1,10 +1,7 @@
|
||||
use crate::{
|
||||
cli::{
|
||||
check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
|
||||
ProcessResult, SignerIndex,
|
||||
},
|
||||
cli_output::CliNonceAccount,
|
||||
use crate::cli::{
|
||||
build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
|
||||
SignerIndex,
|
||||
};
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{
|
||||
@ -24,8 +21,9 @@ use solana_sdk::{
|
||||
},
|
||||
pubkey::Pubkey,
|
||||
system_instruction::{
|
||||
advance_nonce_account, authorize_nonce_account, create_nonce_account,
|
||||
create_nonce_account_with_seed, withdraw_nonce_account, NonceError, SystemError,
|
||||
advance_nonce_account, authorize_nonce_account, create_address_with_seed,
|
||||
create_nonce_account, create_nonce_account_with_seed, withdraw_nonce_account, NonceError,
|
||||
SystemError,
|
||||
},
|
||||
system_program,
|
||||
transaction::Transaction,
|
||||
@ -279,7 +277,7 @@ pub fn data_from_state(state: &State) -> Result<&Data, CliNonceError> {
|
||||
pub fn parse_authorize_nonce_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
let new_authority = pubkey_of_signer(matches, "new_authority", wallet_manager)?.unwrap();
|
||||
@ -307,7 +305,7 @@ pub fn parse_authorize_nonce_account(
|
||||
pub fn parse_nonce_create_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let (nonce_account, nonce_account_pubkey) =
|
||||
signer_of(matches, "nonce_account_keypair", wallet_manager)?;
|
||||
@ -336,7 +334,7 @@ pub fn parse_nonce_create_account(
|
||||
|
||||
pub fn parse_get_nonce(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account_pubkey =
|
||||
pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
@ -350,7 +348,7 @@ pub fn parse_get_nonce(
|
||||
pub fn parse_new_nonce(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
let (nonce_authority, nonce_authority_pubkey) =
|
||||
@ -375,7 +373,7 @@ pub fn parse_new_nonce(
|
||||
|
||||
pub fn parse_show_nonce_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account_pubkey =
|
||||
pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
@ -393,7 +391,7 @@ pub fn parse_show_nonce_account(
|
||||
pub fn parse_withdraw_from_nonce_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
let destination_account_pubkey =
|
||||
@ -462,8 +460,8 @@ pub fn process_authorize_nonce_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<NonceError>(result, &config)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<NonceError>(result)
|
||||
}
|
||||
|
||||
pub fn process_create_nonce_account(
|
||||
@ -476,7 +474,7 @@ pub fn process_create_nonce_account(
|
||||
) -> ProcessResult {
|
||||
let nonce_account_pubkey = config.signers[nonce_account].pubkey();
|
||||
let nonce_account_address = if let Some(seed) = seed.clone() {
|
||||
Pubkey::create_with_seed(&nonce_account_pubkey, &seed, &system_program::id())?
|
||||
create_address_with_seed(&nonce_account_pubkey, &seed, &system_program::id())?
|
||||
} else {
|
||||
nonce_account_pubkey
|
||||
};
|
||||
@ -539,8 +537,8 @@ pub fn process_create_nonce_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
|
||||
pub fn process_get_nonce(rpc_client: &RpcClient, nonce_account_pubkey: &Pubkey) -> ProcessResult {
|
||||
@ -580,32 +578,46 @@ pub fn process_new_nonce(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
let result = rpc_client
|
||||
.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0], nonce_authority]);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
|
||||
pub fn process_show_nonce_account(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
nonce_account_pubkey: &Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
let nonce_account = get_account(rpc_client, nonce_account_pubkey)?;
|
||||
let print_account = |data: Option<&nonce::state::Data>| {
|
||||
let mut nonce_account = CliNonceAccount {
|
||||
balance: nonce_account.lamports,
|
||||
minimum_balance_for_rent_exemption: rpc_client
|
||||
.get_minimum_balance_for_rent_exemption(State::size())?,
|
||||
use_lamports_unit,
|
||||
..CliNonceAccount::default()
|
||||
};
|
||||
if let Some(ref data) = data {
|
||||
nonce_account.nonce = Some(data.blockhash.to_string());
|
||||
nonce_account.lamports_per_signature = Some(data.fee_calculator.lamports_per_signature);
|
||||
nonce_account.authority = Some(data.authority.to_string());
|
||||
println!(
|
||||
"Balance: {}",
|
||||
build_balance_message(nonce_account.lamports, use_lamports_unit, true)
|
||||
);
|
||||
println!(
|
||||
"Minimum Balance Required: {}",
|
||||
build_balance_message(
|
||||
rpc_client.get_minimum_balance_for_rent_exemption(State::size())?,
|
||||
use_lamports_unit,
|
||||
true
|
||||
)
|
||||
);
|
||||
match data {
|
||||
Some(ref data) => {
|
||||
println!("Nonce: {}", data.blockhash);
|
||||
println!(
|
||||
"Fee: {} lamports per signature",
|
||||
data.fee_calculator.lamports_per_signature
|
||||
);
|
||||
println!("Authority: {}", data.authority);
|
||||
}
|
||||
None => {
|
||||
println!("Nonce: uninitialized");
|
||||
println!("Fees: uninitialized");
|
||||
println!("Authority: uninitialized");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(config.output_format.formatted_string(&nonce_account))
|
||||
Ok("".to_string())
|
||||
};
|
||||
match state_from_account(&nonce_account)? {
|
||||
State::Uninitialized => print_account(None),
|
||||
@ -639,8 +651,8 @@ pub fn process_withdraw_from_nonce_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<NonceError>(result, &config)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<NonceError>(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -686,12 +698,7 @@ mod tests {
|
||||
&Pubkey::default().to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(
|
||||
&test_authorize_nonce_account,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
parse_command(&test_authorize_nonce_account, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::AuthorizeNonceAccount {
|
||||
nonce_account: nonce_account_pubkey,
|
||||
@ -712,12 +719,7 @@ mod tests {
|
||||
&authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(
|
||||
&test_authorize_nonce_account,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
parse_command(&test_authorize_nonce_account, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::AuthorizeNonceAccount {
|
||||
nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
|
||||
@ -739,7 +741,7 @@ mod tests {
|
||||
"50",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_nonce_account, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_create_nonce_account, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateNonceAccount {
|
||||
nonce_account: 1,
|
||||
@ -764,7 +766,7 @@ mod tests {
|
||||
&authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_nonce_account, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_create_nonce_account, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateNonceAccount {
|
||||
nonce_account: 1,
|
||||
@ -786,7 +788,7 @@ mod tests {
|
||||
&nonce_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_get_nonce, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_get_nonce, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::GetNonce(nonce_account_keypair.pubkey()),
|
||||
signers: vec![],
|
||||
@ -800,7 +802,7 @@ mod tests {
|
||||
.get_matches_from(vec!["test", "new-nonce", &keypair_file]);
|
||||
let nonce_account = read_keypair_file(&keypair_file).unwrap();
|
||||
assert_eq!(
|
||||
parse_command(&test_new_nonce, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_new_nonce, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::NewNonce {
|
||||
nonce_account: nonce_account.pubkey(),
|
||||
@ -820,7 +822,7 @@ mod tests {
|
||||
]);
|
||||
let nonce_account = read_keypair_file(&keypair_file).unwrap();
|
||||
assert_eq!(
|
||||
parse_command(&test_new_nonce, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_new_nonce, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::NewNonce {
|
||||
nonce_account: nonce_account.pubkey(),
|
||||
@ -840,7 +842,7 @@ mod tests {
|
||||
&nonce_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_show_nonce_account, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_show_nonce_account, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::ShowNonceAccount {
|
||||
nonce_account_pubkey: nonce_account_keypair.pubkey(),
|
||||
@ -862,7 +864,7 @@ mod tests {
|
||||
parse_command(
|
||||
&test_withdraw_from_nonce_account,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
@ -890,7 +892,7 @@ mod tests {
|
||||
parse_command(
|
||||
&test_withdraw_from_nonce_account,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
|
@ -79,47 +79,32 @@ pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
|
||||
let object: Value = serde_json::from_str(&reply).unwrap();
|
||||
let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
|
||||
let blockhash = blockhash_str.parse::<Hash>().unwrap();
|
||||
let mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
|
||||
let signer_strings = object.get("signers");
|
||||
if let Some(sig_strings) = signer_strings {
|
||||
present_signers = sig_strings
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|signer_string| {
|
||||
let mut signer = signer_string.as_str().unwrap().split('=');
|
||||
let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
|
||||
let sig = Signature::from_str(signer.next().unwrap()).unwrap();
|
||||
(key, sig)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
let mut absent_signers: Vec<Pubkey> = Vec::new();
|
||||
let signer_strings = object.get("absent");
|
||||
if let Some(sig_strings) = signer_strings {
|
||||
absent_signers = sig_strings
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
let mut bad_signers: Vec<Pubkey> = Vec::new();
|
||||
let signer_strings = object.get("badSig");
|
||||
if let Some(sig_strings) = signer_strings {
|
||||
bad_signers = sig_strings
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
let signer_strings = object.get("signers").unwrap().as_array().unwrap();
|
||||
let present_signers = signer_strings
|
||||
.iter()
|
||||
.map(|signer_string| {
|
||||
let mut signer = signer_string.as_str().unwrap().split('=');
|
||||
let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
|
||||
let sig = Signature::from_str(signer.next().unwrap()).unwrap();
|
||||
(key, sig)
|
||||
})
|
||||
.collect();
|
||||
let signer_strings = object.get("absent").unwrap().as_array().unwrap();
|
||||
let absent_signers = signer_strings
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
let signer_strings = object.get("badSig").unwrap().as_array().unwrap();
|
||||
let bad_signers = signer_strings
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
SignOnly {
|
||||
blockhash,
|
||||
present_signers,
|
||||
|
352
cli/src/stake.rs
352
cli/src/stake.rs
@ -1,14 +1,15 @@
|
||||
use crate::{
|
||||
cli::{
|
||||
check_account_for_fee, check_unique_pubkeys, fee_payer_arg, generate_unique_signers,
|
||||
log_instruction_custom_error, nonce_authority_arg, return_signers, CliCommand,
|
||||
CliCommandInfo, CliConfig, CliError, ProcessResult, SignerIndex, FEE_PAYER_ARG,
|
||||
build_balance_message, check_account_for_fee, check_unique_pubkeys, fee_payer_arg,
|
||||
generate_unique_signers, log_instruction_custom_error, nonce_authority_arg, return_signers,
|
||||
CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult, SignerIndex, FEE_PAYER_ARG,
|
||||
},
|
||||
cli_output::{CliStakeHistory, CliStakeHistoryEntry, CliStakeState, CliStakeType},
|
||||
nonce::{check_nonce_account, nonce_arg, NONCE_ARG, NONCE_AUTHORITY_ARG},
|
||||
offline::{blockhash_query::BlockhashQuery, *},
|
||||
};
|
||||
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
|
||||
use clap::{App, Arg, ArgGroup, ArgMatches, SubCommand};
|
||||
use console::style;
|
||||
use solana_clap_utils::{input_parsers::*, input_validators::*, offline::*, ArgConstant};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
@ -16,7 +17,7 @@ use solana_sdk::{
|
||||
account_utils::StateMut,
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
system_instruction::SystemError,
|
||||
system_instruction::{create_address_with_seed, SystemError},
|
||||
sysvar::{
|
||||
stake_history::{self, StakeHistory},
|
||||
Sysvar,
|
||||
@ -85,7 +86,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.takes_value(true)
|
||||
.validator(is_amount)
|
||||
.required(true)
|
||||
.help("The amount to send to the stake account, in SOL")
|
||||
.help("The amount of send to the vote account, in SOL")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("custodian")
|
||||
@ -313,14 +314,6 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.arg(nonce_arg())
|
||||
.arg(nonce_authority_arg())
|
||||
.arg(fee_payer_arg())
|
||||
.arg(
|
||||
Arg::with_name("custodian")
|
||||
.long("custodian")
|
||||
.takes_value(true)
|
||||
.value_name("KEYPAIR")
|
||||
.validator(is_valid_signer)
|
||||
.help("Authority to override account lockup")
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("stake-set-lockup")
|
||||
@ -410,7 +403,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
pub fn parse_stake_create_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let seed = matches.value_of("seed").map(|s| s.to_string());
|
||||
let epoch = value_of(matches, "lockup_epoch").unwrap_or(0);
|
||||
@ -462,7 +455,7 @@ pub fn parse_stake_create_account(
|
||||
pub fn parse_stake_delegate_stake(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@ -504,7 +497,7 @@ pub fn parse_stake_delegate_stake(
|
||||
pub fn parse_stake_authorize(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@ -587,7 +580,7 @@ pub fn parse_stake_authorize(
|
||||
pub fn parse_split_stake(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@ -632,7 +625,7 @@ pub fn parse_split_stake(
|
||||
pub fn parse_stake_deactivate_stake(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@ -669,7 +662,7 @@ pub fn parse_stake_deactivate_stake(
|
||||
pub fn parse_stake_withdraw_stake(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@ -684,15 +677,11 @@ pub fn parse_stake_withdraw_stake(
|
||||
let (nonce_authority, nonce_authority_pubkey) =
|
||||
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
|
||||
let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
|
||||
|
||||
let mut bulk_signers = vec![withdraw_authority, fee_payer];
|
||||
if nonce_account.is_some() {
|
||||
bulk_signers.push(nonce_authority);
|
||||
}
|
||||
if custodian.is_some() {
|
||||
bulk_signers.push(custodian);
|
||||
}
|
||||
let signer_info =
|
||||
generate_unique_signers(bulk_signers, matches, default_signer_path, wallet_manager)?;
|
||||
|
||||
@ -707,7 +696,6 @@ pub fn parse_stake_withdraw_stake(
|
||||
nonce_account,
|
||||
nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
|
||||
fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
|
||||
custodian: custodian_pubkey.and_then(|_| signer_info.index_of(custodian_pubkey)),
|
||||
},
|
||||
signers: signer_info.signers,
|
||||
})
|
||||
@ -716,7 +704,7 @@ pub fn parse_stake_withdraw_stake(
|
||||
pub fn parse_stake_set_lockup(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@ -761,7 +749,7 @@ pub fn parse_stake_set_lockup(
|
||||
|
||||
pub fn parse_show_stake_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@ -802,7 +790,7 @@ pub fn process_create_stake_account(
|
||||
) -> ProcessResult {
|
||||
let stake_account = config.signers[stake_account];
|
||||
let stake_account_address = if let Some(seed) = seed {
|
||||
Pubkey::create_with_seed(&stake_account.pubkey(), &seed, &solana_stake_program::id())?
|
||||
create_address_with_seed(&stake_account.pubkey(), &seed, &solana_stake_program::id())?
|
||||
} else {
|
||||
stake_account.pubkey()
|
||||
};
|
||||
@ -881,7 +869,7 @@ pub fn process_create_stake_account(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx, &config)
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@ -894,8 +882,8 @@ pub fn process_create_stake_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -946,7 +934,7 @@ pub fn process_stake_authorize(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx, &config)
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@ -959,8 +947,8 @@ pub fn process_stake_authorize(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<StakeError>(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1000,7 +988,7 @@ pub fn process_deactivate_stake_account(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx, &config)
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@ -1013,8 +1001,8 @@ pub fn process_deactivate_stake_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<StakeError>(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1026,7 +1014,6 @@ pub fn process_withdraw_stake(
|
||||
destination_account_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
withdraw_authority: SignerIndex,
|
||||
custodian: Option<SignerIndex>,
|
||||
sign_only: bool,
|
||||
blockhash_query: &BlockhashQuery,
|
||||
nonce_account: Option<&Pubkey>,
|
||||
@ -1036,14 +1023,12 @@ pub fn process_withdraw_stake(
|
||||
let (recent_blockhash, fee_calculator) =
|
||||
blockhash_query.get_blockhash_and_fee_calculator(rpc_client)?;
|
||||
let withdraw_authority = config.signers[withdraw_authority];
|
||||
let custodian = custodian.map(|index| config.signers[index]);
|
||||
|
||||
let ixs = vec![stake_instruction::withdraw(
|
||||
stake_account_pubkey,
|
||||
&withdraw_authority.pubkey(),
|
||||
destination_account_pubkey,
|
||||
lamports,
|
||||
custodian.map(|signer| signer.pubkey()).as_ref(),
|
||||
)];
|
||||
|
||||
let fee_payer = config.signers[fee_payer];
|
||||
@ -1063,7 +1048,7 @@ pub fn process_withdraw_stake(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx, &config)
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@ -1076,8 +1061,8 @@ pub fn process_withdraw_stake(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1123,7 +1108,7 @@ pub fn process_split_stake(
|
||||
let stake_authority = config.signers[stake_authority];
|
||||
|
||||
let split_stake_account_address = if let Some(seed) = split_stake_account_seed {
|
||||
Pubkey::create_with_seed(
|
||||
create_address_with_seed(
|
||||
&split_stake_account.pubkey(),
|
||||
&seed,
|
||||
&solana_stake_program::id(),
|
||||
@ -1197,7 +1182,7 @@ pub fn process_split_stake(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx, &config)
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@ -1210,8 +1195,8 @@ pub fn process_split_stake(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<StakeError>(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1254,7 +1239,7 @@ pub fn process_stake_set_lockup(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx, &config)
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@ -1267,66 +1252,84 @@ pub fn process_stake_set_lockup(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<StakeError>(result)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_stake_state(
|
||||
stake_lamports: u64,
|
||||
stake_state: &StakeState,
|
||||
use_lamports_unit: bool,
|
||||
) -> CliStakeState {
|
||||
pub fn print_stake_state(stake_lamports: u64, stake_state: &StakeState, use_lamports_unit: bool) {
|
||||
fn show_authorized(authorized: &Authorized) {
|
||||
println!("Stake Authority: {}", authorized.staker);
|
||||
println!("Withdraw Authority: {}", authorized.withdrawer);
|
||||
}
|
||||
fn show_lockup(lockup: &Lockup) {
|
||||
println!(
|
||||
"Lockup Timestamp: {} (UnixTimestamp: {})",
|
||||
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(lockup.unix_timestamp, 0), Utc)
|
||||
.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
lockup.unix_timestamp
|
||||
);
|
||||
println!("Lockup Epoch: {}", lockup.epoch);
|
||||
println!("Lockup Custodian: {}", lockup.custodian);
|
||||
}
|
||||
match stake_state {
|
||||
StakeState::Stake(
|
||||
Meta {
|
||||
authorized, lockup, ..
|
||||
},
|
||||
stake,
|
||||
) => CliStakeState {
|
||||
stake_type: CliStakeType::Stake,
|
||||
total_stake: stake_lamports,
|
||||
delegated_stake: Some(stake.delegation.stake),
|
||||
delegated_vote_account_address: if stake.delegation.voter_pubkey != Pubkey::default() {
|
||||
Some(stake.delegation.voter_pubkey.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
activation_epoch: Some(if stake.delegation.activation_epoch < std::u64::MAX {
|
||||
stake.delegation.activation_epoch
|
||||
} else {
|
||||
0
|
||||
}),
|
||||
deactivation_epoch: if stake.delegation.deactivation_epoch < std::u64::MAX {
|
||||
Some(stake.delegation.deactivation_epoch)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
authorized: Some(authorized.into()),
|
||||
lockup: Some(lockup.into()),
|
||||
use_lamports_unit,
|
||||
},
|
||||
StakeState::RewardsPool => CliStakeState {
|
||||
stake_type: CliStakeType::RewardsPool,
|
||||
..CliStakeState::default()
|
||||
},
|
||||
StakeState::Uninitialized => CliStakeState::default(),
|
||||
) => {
|
||||
println!(
|
||||
"Total Stake: {}",
|
||||
build_balance_message(stake_lamports, use_lamports_unit, true)
|
||||
);
|
||||
println!("Credits Observed: {}", stake.credits_observed);
|
||||
println!(
|
||||
"Delegated Stake: {}",
|
||||
build_balance_message(stake.delegation.stake, use_lamports_unit, true)
|
||||
);
|
||||
if stake.delegation.voter_pubkey != Pubkey::default() {
|
||||
println!(
|
||||
"Delegated Vote Account Address: {}",
|
||||
stake.delegation.voter_pubkey
|
||||
);
|
||||
}
|
||||
println!(
|
||||
"Stake activates starting from epoch: {}",
|
||||
if stake.delegation.activation_epoch < std::u64::MAX {
|
||||
stake.delegation.activation_epoch
|
||||
} else {
|
||||
0
|
||||
}
|
||||
);
|
||||
if stake.delegation.deactivation_epoch < std::u64::MAX {
|
||||
println!(
|
||||
"Stake deactivates starting from epoch: {}",
|
||||
stake.delegation.deactivation_epoch
|
||||
);
|
||||
}
|
||||
show_authorized(&authorized);
|
||||
show_lockup(&lockup);
|
||||
}
|
||||
StakeState::RewardsPool => println!("Stake account is a rewards pool"),
|
||||
StakeState::Uninitialized => println!("Stake account is uninitialized"),
|
||||
StakeState::Initialized(Meta {
|
||||
authorized, lockup, ..
|
||||
}) => CliStakeState {
|
||||
stake_type: CliStakeType::Initialized,
|
||||
total_stake: stake_lamports,
|
||||
authorized: Some(authorized.into()),
|
||||
lockup: Some(lockup.into()),
|
||||
use_lamports_unit,
|
||||
..CliStakeState::default()
|
||||
},
|
||||
}) => {
|
||||
println!(
|
||||
"Total Stake: {}",
|
||||
build_balance_message(stake_lamports, use_lamports_unit, true)
|
||||
);
|
||||
println!("Stake account is undelegated");
|
||||
show_authorized(&authorized);
|
||||
show_lockup(&lockup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_show_stake_account(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
_config: &CliConfig,
|
||||
stake_account_pubkey: &Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
@ -1340,8 +1343,8 @@ pub fn process_show_stake_account(
|
||||
}
|
||||
match stake_account.state() {
|
||||
Ok(stake_state) => {
|
||||
let state = build_stake_state(stake_account.lamports, &stake_state, use_lamports_unit);
|
||||
Ok(config.output_format.formatted_string(&state))
|
||||
print_stake_state(stake_account.lamports, &stake_state, use_lamports_unit);
|
||||
Ok("".to_string())
|
||||
}
|
||||
Err(err) => Err(CliError::RpcRequestError(format!(
|
||||
"Account data could not be deserialized to stake state: {}",
|
||||
@ -1353,7 +1356,7 @@ pub fn process_show_stake_account(
|
||||
|
||||
pub fn process_show_stake_history(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
_config: &CliConfig,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
let stake_history_account = rpc_client.get_account(&stake_history::id())?;
|
||||
@ -1361,15 +1364,27 @@ pub fn process_show_stake_history(
|
||||
CliError::RpcRequestError("Failed to deserialize stake history".to_string())
|
||||
})?;
|
||||
|
||||
let mut entries: Vec<CliStakeHistoryEntry> = vec![];
|
||||
for entry in stake_history.deref() {
|
||||
entries.push(entry.into());
|
||||
println!();
|
||||
println!(
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<5} {:>20} {:>20} {:>20}",
|
||||
"Epoch", "Effective Stake", "Activating Stake", "Deactivating Stake",
|
||||
))
|
||||
.bold()
|
||||
);
|
||||
|
||||
for (epoch, entry) in stake_history.deref() {
|
||||
println!(
|
||||
" {:>5} {:>20} {:>20} {:>20} {}",
|
||||
epoch,
|
||||
build_balance_message(entry.effective, use_lamports_unit, false),
|
||||
build_balance_message(entry.activating, use_lamports_unit, false),
|
||||
build_balance_message(entry.deactivating, use_lamports_unit, false),
|
||||
if use_lamports_unit { "lamports" } else { "SOL" }
|
||||
);
|
||||
}
|
||||
let stake_history_output = CliStakeHistory {
|
||||
entries,
|
||||
use_lamports_unit,
|
||||
};
|
||||
Ok(config.output_format.formatted_string(&stake_history_output))
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -1462,7 +1477,7 @@ pub fn process_delegate_stake(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx, &config)
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@ -1475,8 +1490,8 @@ pub fn process_delegate_stake(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<StakeError>(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1510,9 +1525,6 @@ mod tests {
|
||||
let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let stake_authority_keypair = Keypair::new();
|
||||
write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
let (custodian_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let custodian_keypair = Keypair::new();
|
||||
write_keypair(&custodian_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
|
||||
// stake-authorize subcommand
|
||||
let stake_account_string = stake_account_pubkey.to_string();
|
||||
@ -1530,7 +1542,7 @@ mod tests {
|
||||
&new_withdraw_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1564,7 +1576,7 @@ mod tests {
|
||||
&withdraw_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1602,7 +1614,7 @@ mod tests {
|
||||
&withdraw_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1632,7 +1644,7 @@ mod tests {
|
||||
&new_stake_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1656,7 +1668,7 @@ mod tests {
|
||||
&stake_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1686,7 +1698,7 @@ mod tests {
|
||||
&withdraw_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1713,7 +1725,7 @@ mod tests {
|
||||
&new_withdraw_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1741,7 +1753,7 @@ mod tests {
|
||||
&withdraw_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1779,7 +1791,7 @@ mod tests {
|
||||
"--sign-only",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1812,7 +1824,7 @@ mod tests {
|
||||
&pubkey.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1858,7 +1870,7 @@ mod tests {
|
||||
&pubkey2.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1890,7 +1902,7 @@ mod tests {
|
||||
&blockhash_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1927,7 +1939,7 @@ mod tests {
|
||||
&nonce_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1963,7 +1975,7 @@ mod tests {
|
||||
&fee_payer_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -1997,7 +2009,7 @@ mod tests {
|
||||
&signer,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@ -2038,7 +2050,7 @@ mod tests {
|
||||
"43",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_stake_account, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_create_stake_account, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateStakeAccount {
|
||||
stake_account: 1,
|
||||
@ -2079,12 +2091,7 @@ mod tests {
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
parse_command(
|
||||
&test_create_stake_account2,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
parse_command(&test_create_stake_account2, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateStakeAccount {
|
||||
stake_account: 1,
|
||||
@ -2137,12 +2144,7 @@ mod tests {
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
parse_command(
|
||||
&test_create_stake_account2,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
parse_command(&test_create_stake_account2, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateStakeAccount {
|
||||
stake_account: 1,
|
||||
@ -2178,7 +2180,7 @@ mod tests {
|
||||
&vote_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2207,7 +2209,7 @@ mod tests {
|
||||
&stake_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2238,7 +2240,7 @@ mod tests {
|
||||
&vote_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2267,7 +2269,7 @@ mod tests {
|
||||
&blockhash_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2297,7 +2299,7 @@ mod tests {
|
||||
"--sign-only",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2331,7 +2333,7 @@ mod tests {
|
||||
&key1.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2377,7 +2379,7 @@ mod tests {
|
||||
&key2.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2414,7 +2416,7 @@ mod tests {
|
||||
&fee_payer_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2444,14 +2446,13 @@ mod tests {
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::WithdrawStake {
|
||||
stake_account_pubkey,
|
||||
destination_account_pubkey: stake_account_pubkey,
|
||||
lamports: 42_000_000_000,
|
||||
withdraw_authority: 0,
|
||||
custodian: None,
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
@ -2474,14 +2475,13 @@ mod tests {
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::WithdrawStake {
|
||||
stake_account_pubkey,
|
||||
destination_account_pubkey: stake_account_pubkey,
|
||||
lamports: 42_000_000_000,
|
||||
withdraw_authority: 1,
|
||||
custodian: None,
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
@ -2497,39 +2497,6 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
// Test WithdrawStake Subcommand w/ custodian
|
||||
let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"withdraw-stake",
|
||||
&stake_account_string,
|
||||
&stake_account_string,
|
||||
"42",
|
||||
"--custodian",
|
||||
&custodian_keypair_file,
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::WithdrawStake {
|
||||
stake_account_pubkey,
|
||||
destination_account_pubkey: stake_account_pubkey,
|
||||
lamports: 42_000_000_000,
|
||||
withdraw_authority: 0,
|
||||
custodian: Some(1),
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
read_keypair_file(&custodian_keypair_file).unwrap().into()
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
// Test WithdrawStake Subcommand w/ authority and offline nonce
|
||||
let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
@ -2552,14 +2519,13 @@ mod tests {
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::WithdrawStake {
|
||||
stake_account_pubkey,
|
||||
destination_account_pubkey: stake_account_pubkey,
|
||||
lamports: 42_000_000_000,
|
||||
withdraw_authority: 0,
|
||||
custodian: None,
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account),
|
||||
@ -2585,7 +2551,7 @@ mod tests {
|
||||
&stake_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2609,7 +2575,7 @@ mod tests {
|
||||
&stake_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2640,7 +2606,7 @@ mod tests {
|
||||
&blockhash_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2667,7 +2633,7 @@ mod tests {
|
||||
"--sign-only",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2698,7 +2664,7 @@ mod tests {
|
||||
&key1.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2741,7 +2707,7 @@ mod tests {
|
||||
&key2.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2772,7 +2738,7 @@ mod tests {
|
||||
&fee_payer_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@ -2806,7 +2772,7 @@ mod tests {
|
||||
"50",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_split_stake_account, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_split_stake_account, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::SplitStake {
|
||||
stake_account_pubkey: stake_account_keypair.pubkey(),
|
||||
@ -2867,7 +2833,7 @@ mod tests {
|
||||
&stake_signer,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_split_stake_account, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_split_stake_account, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::SplitStake {
|
||||
stake_account_pubkey: stake_account_keypair.pubkey(),
|
||||
|
@ -102,7 +102,7 @@ impl StorageSubCommands for App<'_, '_> {
|
||||
pub fn parse_storage_create_archiver_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let account_owner =
|
||||
pubkey_of_signer(matches, "storage_account_owner", wallet_manager)?.unwrap();
|
||||
@ -130,7 +130,7 @@ pub fn parse_storage_create_archiver_account(
|
||||
pub fn parse_storage_create_validator_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let account_owner =
|
||||
pubkey_of_signer(matches, "storage_account_owner", wallet_manager)?.unwrap();
|
||||
@ -158,7 +158,7 @@ pub fn parse_storage_create_validator_account(
|
||||
pub fn parse_storage_claim_reward(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let node_account_pubkey =
|
||||
pubkey_of_signer(matches, "node_account_pubkey", wallet_manager)?.unwrap();
|
||||
@ -180,7 +180,7 @@ pub fn parse_storage_claim_reward(
|
||||
|
||||
pub fn parse_storage_get_account_command(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let storage_account_pubkey =
|
||||
pubkey_of_signer(matches, "storage_account_pubkey", wallet_manager)?.unwrap();
|
||||
@ -242,8 +242,8 @@ pub fn process_create_storage_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
|
||||
pub fn process_claim_storage_reward(
|
||||
@ -266,7 +266,7 @@ pub fn process_claim_storage_reward(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let signature = rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
|
||||
let signature = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &signers)?;
|
||||
Ok(signature.to_string())
|
||||
}
|
||||
|
||||
@ -330,7 +330,7 @@ mod tests {
|
||||
parse_command(
|
||||
&test_create_archiver_storage_account,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
@ -362,7 +362,7 @@ mod tests {
|
||||
parse_command(
|
||||
&test_create_validator_storage_account,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
@ -385,7 +385,7 @@ mod tests {
|
||||
&storage_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_claim_storage_reward, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_claim_storage_reward, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::ClaimStorageReward {
|
||||
node_account_pubkey: pubkey,
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
cli::{check_account_for_fee, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
||||
cli_output::{CliValidatorInfo, CliValidatorInfoVec},
|
||||
display::println_name_value,
|
||||
};
|
||||
use bincode::deserialize;
|
||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
@ -25,6 +25,7 @@ use solana_sdk::{
|
||||
transaction::Transaction,
|
||||
};
|
||||
use std::{error, sync::Arc};
|
||||
use titlecase::titlecase;
|
||||
|
||||
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
|
||||
pub const MAX_LONG_FIELD_LENGTH: usize = 300;
|
||||
@ -228,7 +229,7 @@ impl ValidatorInfoSubCommands for App<'_, '_> {
|
||||
pub fn parse_validator_info_command(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let info_pubkey = pubkey_of(matches, "info_pubkey");
|
||||
// Prepare validator info
|
||||
@ -367,18 +368,14 @@ pub fn process_set_validator_info(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let signature_str = rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
|
||||
let signature_str = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &signers)?;
|
||||
|
||||
println!("Success! Validator info published at: {:?}", info_pubkey);
|
||||
println!("{}", signature_str);
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
pub fn process_get_validator_info(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
pubkey: Option<Pubkey>,
|
||||
) -> ProcessResult {
|
||||
pub fn process_get_validator_info(rpc_client: &RpcClient, pubkey: Option<Pubkey>) -> ProcessResult {
|
||||
let validator_info: Vec<(Pubkey, Account)> = if let Some(validator_info_pubkey) = pubkey {
|
||||
vec![(
|
||||
validator_info_pubkey,
|
||||
@ -397,22 +394,24 @@ pub fn process_get_validator_info(
|
||||
.collect()
|
||||
};
|
||||
|
||||
let mut validator_info_list: Vec<CliValidatorInfo> = vec![];
|
||||
if validator_info.is_empty() {
|
||||
println!("No validator info accounts found");
|
||||
}
|
||||
for (validator_info_pubkey, validator_info_account) in validator_info.iter() {
|
||||
let (validator_pubkey, validator_info) =
|
||||
parse_validator_info(&validator_info_pubkey, &validator_info_account)?;
|
||||
validator_info_list.push(CliValidatorInfo {
|
||||
identity_pubkey: validator_pubkey.to_string(),
|
||||
info_pubkey: validator_info_pubkey.to_string(),
|
||||
info: validator_info,
|
||||
});
|
||||
println!();
|
||||
println_name_value("Validator Identity Pubkey:", &validator_pubkey.to_string());
|
||||
println_name_value(" Info Pubkey:", &validator_info_pubkey.to_string());
|
||||
for (key, value) in validator_info.iter() {
|
||||
println_name_value(
|
||||
&format!(" {}:", titlecase(key)),
|
||||
&value.as_str().unwrap_or("?"),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(config
|
||||
.output_format
|
||||
.formatted_string(&CliValidatorInfoVec::new(validator_info_list)))
|
||||
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
354
cli/src/vote.rs
354
cli/src/vote.rs
@ -1,22 +1,19 @@
|
||||
use crate::{
|
||||
cli::{
|
||||
check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
|
||||
ProcessResult, SignerIndex,
|
||||
},
|
||||
cli_output::{CliEpochVotingHistory, CliLockout, CliVoteAccount},
|
||||
use crate::cli::{
|
||||
build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, CliSignerInfo,
|
||||
ProcessResult, SignerIndex,
|
||||
};
|
||||
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{
|
||||
commitment::{commitment_arg, COMMITMENT_ARG},
|
||||
input_parsers::*,
|
||||
input_validators::*,
|
||||
};
|
||||
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
account::Account, commitment_config::CommitmentConfig, message::Message, pubkey::Pubkey,
|
||||
system_instruction::SystemError, transaction::Transaction,
|
||||
account::Account,
|
||||
commitment_config::CommitmentConfig,
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
system_instruction::{create_address_with_seed, SystemError},
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_vote_program::{
|
||||
vote_instruction::{self, withdraw, VoteError},
|
||||
@ -95,17 +92,9 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Vote account in which to set the authorized voter"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized")
|
||||
.index(2)
|
||||
.value_name("AUTHORIZED_KEYPAIR")
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("Current authorized vote signer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_authorized_pubkey")
|
||||
.index(3)
|
||||
.index(2)
|
||||
.value_name("AUTHORIZED_PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
@ -125,17 +114,9 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Vote account in which to set the authorized withdrawer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized")
|
||||
.index(2)
|
||||
.value_name("AUTHORIZED_KEYPAIR")
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("Current authorized withdrawer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_authorized_pubkey")
|
||||
.index(3)
|
||||
.index(2)
|
||||
.value_name("AUTHORIZED_PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
@ -174,41 +155,18 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.help("Authorized withdrawer keypair"),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-update-commission")
|
||||
.about("Update the vote account's commission")
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Vote account to update"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("commission")
|
||||
.index(2)
|
||||
.value_name("PERCENTAGE")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_percentage)
|
||||
.help("The new commission")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized_withdrawer")
|
||||
.index(3)
|
||||
.value_name("AUTHORIZED_KEYPAIR")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("Authorized withdrawer keypair"),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-account")
|
||||
.about("Show the contents of a vote account")
|
||||
.alias("show-vote-account")
|
||||
.arg(
|
||||
Arg::with_name("confirmed")
|
||||
.long("confirmed")
|
||||
.takes_value(false)
|
||||
.help(
|
||||
"Return information at maximum-lockout commitment level",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
@ -223,8 +181,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.long("lamports")
|
||||
.takes_value(false)
|
||||
.help("Display balance in lamports instead of SOL"),
|
||||
)
|
||||
.arg(commitment_arg()),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("withdraw-from-vote-account")
|
||||
@ -271,9 +228,9 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
pub fn parse_create_vote_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let (vote_account, vote_account_pubkey) = signer_of(matches, "vote_account", wallet_manager)?;
|
||||
let (vote_account, _) = signer_of(matches, "vote_account", wallet_manager)?;
|
||||
let seed = matches.value_of("seed").map(|s| s.to_string());
|
||||
let (identity_account, identity_pubkey) =
|
||||
signer_of(matches, "identity_account", wallet_manager)?;
|
||||
@ -291,7 +248,6 @@ pub fn parse_create_vote_account(
|
||||
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
vote_account: signer_info.index_of(vote_account_pubkey).unwrap(),
|
||||
seed,
|
||||
identity_account: signer_info.index_of(identity_pubkey).unwrap(),
|
||||
authorized_voter,
|
||||
@ -305,18 +261,17 @@ pub fn parse_create_vote_account(
|
||||
pub fn parse_vote_authorize(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
vote_authorize: VoteAuthorize,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
let new_authorized_pubkey =
|
||||
pubkey_of_signer(matches, "new_authorized_pubkey", wallet_manager)?.unwrap();
|
||||
let (authorized, _) = signer_of(matches, "authorized", wallet_manager)?;
|
||||
|
||||
let payer_provided = None;
|
||||
let signer_info = generate_unique_signers(
|
||||
vec![payer_provided, authorized],
|
||||
let authorized_voter_provided = None;
|
||||
let CliSignerInfo { signers } = generate_unique_signers(
|
||||
vec![authorized_voter_provided],
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
@ -328,21 +283,20 @@ pub fn parse_vote_authorize(
|
||||
new_authorized_pubkey,
|
||||
vote_authorize,
|
||||
},
|
||||
signers: signer_info.signers,
|
||||
signers,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_vote_update_validator(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
let (new_identity_account, new_identity_pubkey) =
|
||||
signer_of(matches, "new_identity_account", wallet_manager)?;
|
||||
let (authorized_withdrawer, authorized_withdrawer_pubkey) =
|
||||
signer_of(matches, "authorized_withdrawer", wallet_manager)?;
|
||||
let (authorized_withdrawer, _) = signer_of(matches, "authorized_withdrawer", wallet_manager)?;
|
||||
|
||||
let payer_provided = None;
|
||||
let signer_info = generate_unique_signers(
|
||||
@ -356,36 +310,6 @@ pub fn parse_vote_update_validator(
|
||||
command: CliCommand::VoteUpdateValidator {
|
||||
vote_account_pubkey,
|
||||
new_identity_account: signer_info.index_of(new_identity_pubkey).unwrap(),
|
||||
withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
|
||||
},
|
||||
signers: signer_info.signers,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_vote_update_commission(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
let (authorized_withdrawer, authorized_withdrawer_pubkey) =
|
||||
signer_of(matches, "authorized_withdrawer", wallet_manager)?;
|
||||
let commission = value_t_or_exit!(matches, "commission", u8);
|
||||
|
||||
let payer_provided = None;
|
||||
let signer_info = generate_unique_signers(
|
||||
vec![payer_provided, authorized_withdrawer],
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
)?;
|
||||
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::VoteUpdateCommission {
|
||||
vote_account_pubkey,
|
||||
commission,
|
||||
withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
|
||||
},
|
||||
signers: signer_info.signers,
|
||||
})
|
||||
@ -393,12 +317,16 @@ pub fn parse_vote_update_commission(
|
||||
|
||||
pub fn parse_vote_get_account_command(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
let use_lamports_unit = matches.is_present("lamports");
|
||||
let commitment_config = commitment_of(matches, COMMITMENT_ARG.long).unwrap();
|
||||
let commitment_config = if matches.is_present("confirmed") {
|
||||
CommitmentConfig::default()
|
||||
} else {
|
||||
CommitmentConfig::recent()
|
||||
};
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::ShowVoteAccount {
|
||||
pubkey: vote_account_pubkey,
|
||||
@ -412,7 +340,7 @@ pub fn parse_vote_get_account_command(
|
||||
pub fn parse_withdraw_from_vote_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
@ -444,17 +372,16 @@ pub fn parse_withdraw_from_vote_account(
|
||||
pub fn process_create_vote_account(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
vote_account: SignerIndex,
|
||||
seed: &Option<String>,
|
||||
identity_account: SignerIndex,
|
||||
authorized_voter: &Option<Pubkey>,
|
||||
authorized_withdrawer: &Option<Pubkey>,
|
||||
commission: u8,
|
||||
) -> ProcessResult {
|
||||
let vote_account = config.signers[vote_account];
|
||||
let vote_account = config.signers[1];
|
||||
let vote_account_pubkey = vote_account.pubkey();
|
||||
let vote_account_address = if let Some(seed) = seed {
|
||||
Pubkey::create_with_seed(&vote_account_pubkey, &seed, &solana_vote_program::id())?
|
||||
create_address_with_seed(&vote_account_pubkey, &seed, &solana_vote_program::id())?
|
||||
} else {
|
||||
vote_account_pubkey
|
||||
};
|
||||
@ -521,8 +448,8 @@ pub fn process_create_vote_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
|
||||
pub fn process_vote_authorize(
|
||||
@ -532,24 +459,16 @@ pub fn process_vote_authorize(
|
||||
new_authorized_pubkey: &Pubkey,
|
||||
vote_authorize: VoteAuthorize,
|
||||
) -> ProcessResult {
|
||||
// If the `authorized_account` is also the fee payer, `config.signers` will only have one
|
||||
// keypair in it
|
||||
let authorized = if config.signers.len() == 2 {
|
||||
config.signers[1]
|
||||
} else {
|
||||
config.signers[0]
|
||||
};
|
||||
|
||||
check_unique_pubkeys(
|
||||
(&authorized.pubkey(), "authorized_account".to_string()),
|
||||
(vote_account_pubkey, "vote_account_pubkey".to_string()),
|
||||
(new_authorized_pubkey, "new_authorized_pubkey".to_string()),
|
||||
)?;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let ixs = vec![vote_instruction::authorize(
|
||||
vote_account_pubkey, // vote account to update
|
||||
&authorized.pubkey(), // current authorized
|
||||
new_authorized_pubkey, // new vote signer/withdrawer
|
||||
vote_authorize, // vote or withdraw
|
||||
vote_account_pubkey, // vote account to update
|
||||
&config.signers[0].pubkey(), // current authorized voter
|
||||
new_authorized_pubkey, // new vote signer/withdrawer
|
||||
vote_authorize, // vote or withdraw
|
||||
)];
|
||||
|
||||
let message = Message::new_with_payer(&ixs, Some(&config.signers[0].pubkey()));
|
||||
@ -561,8 +480,9 @@ pub fn process_vote_authorize(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
let result =
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0]]);
|
||||
log_instruction_custom_error::<VoteError>(result)
|
||||
}
|
||||
|
||||
pub fn process_vote_update_validator(
|
||||
@ -570,9 +490,8 @@ pub fn process_vote_update_validator(
|
||||
config: &CliConfig,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
new_identity_account: SignerIndex,
|
||||
withdraw_authority: SignerIndex,
|
||||
) -> ProcessResult {
|
||||
let authorized_withdrawer = config.signers[withdraw_authority];
|
||||
let authorized_withdrawer = config.signers[1];
|
||||
let new_identity_account = config.signers[new_identity_account];
|
||||
let new_identity_pubkey = new_identity_account.pubkey();
|
||||
check_unique_pubkeys(
|
||||
@ -580,7 +499,7 @@ pub fn process_vote_update_validator(
|
||||
(&new_identity_pubkey, "new_identity_account".to_string()),
|
||||
)?;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let ixs = vec![vote_instruction::update_validator_identity(
|
||||
let ixs = vec![vote_instruction::update_node(
|
||||
vote_account_pubkey,
|
||||
&authorized_withdrawer.pubkey(),
|
||||
&new_identity_pubkey,
|
||||
@ -595,36 +514,8 @@ pub fn process_vote_update_validator(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
}
|
||||
|
||||
pub fn process_vote_update_commission(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
commission: u8,
|
||||
withdraw_authority: SignerIndex,
|
||||
) -> ProcessResult {
|
||||
let authorized_withdrawer = config.signers[withdraw_authority];
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let ixs = vec![vote_instruction::update_commission(
|
||||
vote_account_pubkey,
|
||||
&authorized_withdrawer.pubkey(),
|
||||
commission,
|
||||
)];
|
||||
|
||||
let message = Message::new_with_payer(&ixs, Some(&config.signers[0].pubkey()));
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
check_account_for_fee(
|
||||
rpc_client,
|
||||
&config.signers[0].pubkey(),
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<VoteError>(result)
|
||||
}
|
||||
|
||||
fn get_vote_account(
|
||||
@ -657,7 +548,7 @@ fn get_vote_account(
|
||||
|
||||
pub fn process_show_vote_account(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
_config: &CliConfig,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
commitment_config: CommitmentConfig,
|
||||
@ -667,38 +558,46 @@ pub fn process_show_vote_account(
|
||||
|
||||
let epoch_schedule = rpc_client.get_epoch_schedule()?;
|
||||
|
||||
let mut votes: Vec<CliLockout> = vec![];
|
||||
let mut epoch_voting_history: Vec<CliEpochVotingHistory> = vec![];
|
||||
if !vote_state.votes.is_empty() {
|
||||
for vote in &vote_state.votes {
|
||||
votes.push(vote.into());
|
||||
println!(
|
||||
"Account Balance: {}",
|
||||
build_balance_message(vote_account.lamports, use_lamports_unit, true)
|
||||
);
|
||||
println!("Validator Identity: {}", vote_state.node_pubkey);
|
||||
println!("Authorized Voter: {:?}", vote_state.authorized_voters());
|
||||
println!(
|
||||
"Authorized Withdrawer: {}",
|
||||
vote_state.authorized_withdrawer
|
||||
);
|
||||
println!("Credits: {}", vote_state.credits());
|
||||
println!("Commission: {}%", vote_state.commission);
|
||||
println!(
|
||||
"Root Slot: {}",
|
||||
match vote_state.root_slot {
|
||||
Some(slot) => slot.to_string(),
|
||||
None => "~".to_string(),
|
||||
}
|
||||
);
|
||||
println!("Recent Timestamp: {:?}", vote_state.last_timestamp);
|
||||
if !vote_state.votes.is_empty() {
|
||||
println!("recent votes:");
|
||||
for vote in &vote_state.votes {
|
||||
println!(
|
||||
"- slot: {}\n confirmation count: {}",
|
||||
vote.slot, vote.confirmation_count
|
||||
);
|
||||
}
|
||||
|
||||
println!("Epoch Voting History:");
|
||||
for (epoch, credits, prev_credits) in vote_state.epoch_credits() {
|
||||
let credits_earned = credits - prev_credits;
|
||||
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(*epoch);
|
||||
epoch_voting_history.push(CliEpochVotingHistory {
|
||||
epoch: *epoch,
|
||||
slots_in_epoch,
|
||||
credits_earned,
|
||||
});
|
||||
println!(
|
||||
"- epoch: {}\n slots in epoch: {}\n credits earned: {}",
|
||||
epoch, slots_in_epoch, credits_earned,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let vote_account_data = CliVoteAccount {
|
||||
account_balance: vote_account.lamports,
|
||||
validator_identity: vote_state.node_pubkey.to_string(),
|
||||
authorized_voters: vote_state.authorized_voters().into(),
|
||||
authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
|
||||
credits: vote_state.credits(),
|
||||
commission: vote_state.commission,
|
||||
root_slot: vote_state.root_slot,
|
||||
recent_timestamp: vote_state.last_timestamp.clone(),
|
||||
votes,
|
||||
epoch_voting_history,
|
||||
use_lamports_unit,
|
||||
};
|
||||
|
||||
Ok(config.output_format.formatted_string(&vote_account_data))
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
pub fn process_withdraw_from_vote_account(
|
||||
@ -728,8 +627,9 @@ pub fn process_withdraw_from_vote_account(
|
||||
&fee_calculator,
|
||||
&transaction.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&transaction);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
let result =
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&mut transaction, &config.signers);
|
||||
log_instruction_custom_error::<VoteError>(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -762,11 +662,10 @@ mod tests {
|
||||
"test",
|
||||
"vote-authorize-voter",
|
||||
&pubkey_string,
|
||||
&default_keypair_file,
|
||||
&pubkey2_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize_voter, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_authorize_voter, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::VoteAuthorize {
|
||||
vote_account_pubkey: pubkey,
|
||||
@ -777,32 +676,6 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
let authorized_keypair = Keypair::new();
|
||||
let (authorized_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
write_keypair(&authorized_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
|
||||
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"vote-authorize-voter",
|
||||
&pubkey_string,
|
||||
&authorized_keypair_file,
|
||||
&pubkey2_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize_voter, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::VoteAuthorize {
|
||||
vote_account_pubkey: pubkey,
|
||||
new_authorized_pubkey: pubkey2,
|
||||
vote_authorize: VoteAuthorize::Voter
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
read_keypair_file(&authorized_keypair_file).unwrap().into(),
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
let (keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let keypair = Keypair::new();
|
||||
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
|
||||
@ -819,10 +692,9 @@ mod tests {
|
||||
"10",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_vote_account, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_create_vote_account, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
vote_account: 1,
|
||||
seed: None,
|
||||
identity_account: 2,
|
||||
authorized_voter: None,
|
||||
@ -848,10 +720,9 @@ mod tests {
|
||||
&identity_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_vote_account2, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_create_vote_account2, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
vote_account: 1,
|
||||
seed: None,
|
||||
identity_account: 2,
|
||||
authorized_voter: None,
|
||||
@ -881,10 +752,9 @@ mod tests {
|
||||
&authed.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_vote_account3, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_create_vote_account3, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
vote_account: 1,
|
||||
seed: None,
|
||||
identity_account: 2,
|
||||
authorized_voter: Some(authed),
|
||||
@ -912,10 +782,9 @@ mod tests {
|
||||
&authed.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_vote_account4, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_create_vote_account4, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
vote_account: 1,
|
||||
seed: None,
|
||||
identity_account: 2,
|
||||
authorized_voter: None,
|
||||
@ -938,12 +807,11 @@ mod tests {
|
||||
&keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_update_validator, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_update_validator, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::VoteUpdateValidator {
|
||||
vote_account_pubkey: pubkey,
|
||||
new_identity_account: 2,
|
||||
withdraw_authority: 1,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
@ -953,28 +821,6 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
let test_update_commission = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"vote-update-commission",
|
||||
&pubkey_string,
|
||||
"42",
|
||||
&keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_update_commission, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::VoteUpdateCommission {
|
||||
vote_account_pubkey: pubkey,
|
||||
commission: 42,
|
||||
withdraw_authority: 1,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
Box::new(read_keypair_file(&keypair_file).unwrap()),
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
// Test WithdrawFromVoteAccount subcommand
|
||||
let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
@ -987,7 +833,7 @@ mod tests {
|
||||
parse_command(
|
||||
&test_withdraw_from_vote_account,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
@ -1018,7 +864,7 @@ mod tests {
|
||||
parse_command(
|
||||
&test_withdraw_from_vote_account,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
|
@ -1,6 +1,5 @@
|
||||
use solana_cli::{
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
cli_output::OutputFormat,
|
||||
nonce,
|
||||
offline::{
|
||||
blockhash_query::{self, BlockhashQuery},
|
||||
@ -14,6 +13,7 @@ use solana_sdk::{
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
system_instruction::create_address_with_seed,
|
||||
system_program,
|
||||
};
|
||||
use std::{fs::remove_dir_all, sync::mpsc::channel, thread::sleep, time::Duration};
|
||||
@ -120,7 +120,6 @@ fn full_battery_tests(
|
||||
&faucet_addr,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
2000,
|
||||
&config_payer,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(2000, &rpc_client, &config_payer.signers[0].pubkey());
|
||||
@ -131,7 +130,7 @@ fn full_battery_tests(
|
||||
config_nonce.signers = vec![&nonce_keypair];
|
||||
|
||||
let nonce_account = if let Some(seed) = seed.as_ref() {
|
||||
Pubkey::create_with_seed(
|
||||
create_address_with_seed(
|
||||
&config_nonce.signers[0].pubkey(),
|
||||
seed,
|
||||
&system_program::id(),
|
||||
@ -267,7 +266,6 @@ fn test_create_account_with_seed() {
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: 1,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
..TestValidatorOptions::default()
|
||||
});
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
@ -277,7 +275,6 @@ fn test_create_account_with_seed() {
|
||||
let offline_nonce_authority_signer = keypair_from_seed(&[1u8; 32]).unwrap();
|
||||
let online_nonce_creator_signer = keypair_from_seed(&[2u8; 32]).unwrap();
|
||||
let to_address = Pubkey::new(&[3u8; 32]);
|
||||
let config = CliConfig::default();
|
||||
|
||||
// Setup accounts
|
||||
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
||||
@ -286,7 +283,6 @@ fn test_create_account_with_seed() {
|
||||
&faucet_addr,
|
||||
&offline_nonce_authority_signer.pubkey(),
|
||||
42,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(
|
||||
@ -294,7 +290,6 @@ fn test_create_account_with_seed() {
|
||||
&faucet_addr,
|
||||
&online_nonce_creator_signer.pubkey(),
|
||||
4242,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(42, &rpc_client, &offline_nonce_authority_signer.pubkey());
|
||||
@ -306,7 +301,7 @@ fn test_create_account_with_seed() {
|
||||
let authority_pubkey = offline_nonce_authority_signer.pubkey();
|
||||
let seed = authority_pubkey.to_string()[0..32].to_string();
|
||||
let nonce_address =
|
||||
Pubkey::create_with_seed(&creator_pubkey, &seed, &system_program::id()).unwrap();
|
||||
create_address_with_seed(&creator_pubkey, &seed, &system_program::id()).unwrap();
|
||||
check_balance(0, &rpc_client, &nonce_address);
|
||||
|
||||
let mut creator_config = CliConfig::default();
|
||||
@ -343,13 +338,11 @@ fn test_create_account_with_seed() {
|
||||
to: to_address,
|
||||
from: 0,
|
||||
sign_only: true,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_address),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
authority_config.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&authority_config).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
let authority_presigner = sign_only.presigner_of(&authority_pubkey).unwrap();
|
||||
@ -365,7 +358,6 @@ fn test_create_account_with_seed() {
|
||||
to: to_address,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_address),
|
||||
sign_only.blockhash,
|
||||
|
@ -2,7 +2,6 @@ use chrono::prelude::*;
|
||||
use serde_json::Value;
|
||||
use solana_cli::{
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig, PayCommand},
|
||||
cli_output::OutputFormat,
|
||||
nonce,
|
||||
offline::{
|
||||
blockhash_query::{self, BlockhashQuery},
|
||||
@ -70,7 +69,6 @@ fn test_cli_timestamp_tx() {
|
||||
&faucet_addr,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
50,
|
||||
&config_witness,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(50, &rpc_client, &config_payer.signers[0].pubkey());
|
||||
@ -80,7 +78,6 @@ fn test_cli_timestamp_tx() {
|
||||
&faucet_addr,
|
||||
&config_witness.signers[0].pubkey(),
|
||||
1,
|
||||
&config_witness,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -157,7 +154,6 @@ fn test_cli_witness_tx() {
|
||||
&faucet_addr,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
50,
|
||||
&config_witness,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(
|
||||
@ -165,7 +161,6 @@ fn test_cli_witness_tx() {
|
||||
&faucet_addr,
|
||||
&config_witness.signers[0].pubkey(),
|
||||
1,
|
||||
&config_witness,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -239,7 +234,6 @@ fn test_cli_cancel_tx() {
|
||||
&faucet_addr,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
50,
|
||||
&config_witness,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -313,7 +307,6 @@ fn test_offline_pay_tx() {
|
||||
&faucet_addr,
|
||||
&config_offline.signers[0].pubkey(),
|
||||
50,
|
||||
&config_offline,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -322,7 +315,6 @@ fn test_offline_pay_tx() {
|
||||
&faucet_addr,
|
||||
&config_online.signers[0].pubkey(),
|
||||
50,
|
||||
&config_offline,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(50, &rpc_client, &config_offline.signers[0].pubkey());
|
||||
@ -336,7 +328,6 @@ fn test_offline_pay_tx() {
|
||||
sign_only: true,
|
||||
..PayCommand::default()
|
||||
});
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
|
||||
check_balance(50, &rpc_client, &config_offline.signers[0].pubkey());
|
||||
@ -397,7 +388,6 @@ fn test_nonced_pay_tx() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
50 + minimum_nonce_balance,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(
|
||||
|
@ -1,6 +1,5 @@
|
||||
use solana_cli::{
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
cli_output::OutputFormat,
|
||||
nonce,
|
||||
offline::{
|
||||
blockhash_query::{self, BlockhashQuery},
|
||||
@ -15,6 +14,7 @@ use solana_sdk::{
|
||||
nonce::State as NonceState,
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
system_instruction::create_address_with_seed,
|
||||
};
|
||||
use solana_stake_program::{
|
||||
stake_instruction::LockupArgs,
|
||||
@ -60,7 +60,6 @@ fn test_stake_delegation_force() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -68,7 +67,6 @@ fn test_stake_delegation_force() {
|
||||
let vote_keypair = Keypair::new();
|
||||
config.signers = vec![&default_signer, &vote_keypair];
|
||||
config.command = CliCommand::CreateVoteAccount {
|
||||
vote_account: 1,
|
||||
seed: None,
|
||||
identity_account: 0,
|
||||
authorized_voter: None,
|
||||
@ -158,12 +156,11 @@ fn test_seed_stake_delegation_and_deactivation() {
|
||||
&faucet_addr,
|
||||
&config_validator.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config_validator,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
|
||||
let stake_address = Pubkey::create_with_seed(
|
||||
let stake_address = create_address_with_seed(
|
||||
&config_validator.signers[0].pubkey(),
|
||||
"hi there",
|
||||
&solana_stake_program::id(),
|
||||
@ -249,7 +246,6 @@ fn test_stake_delegation_and_deactivation() {
|
||||
&faucet_addr,
|
||||
&config_validator.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config_validator,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
@ -346,7 +342,6 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
&faucet_addr,
|
||||
&config_validator.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config_offline,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
@ -356,7 +351,6 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
&faucet_addr,
|
||||
&config_offline.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config_validator,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &config_offline.signers[0].pubkey());
|
||||
@ -392,7 +386,6 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@ -478,7 +471,6 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -588,7 +580,6 @@ fn test_stake_authorize() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -606,7 +597,6 @@ fn test_stake_authorize() {
|
||||
&faucet_addr,
|
||||
&config_offline.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -714,7 +704,6 @@ fn test_stake_authorize() {
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sign_reply = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@ -823,7 +812,6 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: SIG_FEE,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
..TestValidatorOptions::default()
|
||||
});
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(alice, sender, None);
|
||||
@ -853,16 +841,13 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &default_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &default_pubkey, 100_000).unwrap();
|
||||
check_balance(100_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &payer_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &payer_pubkey, 100_000).unwrap();
|
||||
check_balance(100_000, &rpc_client, &payer_pubkey);
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap();
|
||||
check_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create stake account, identity is authority
|
||||
@ -916,7 +901,6 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sign_reply = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@ -955,7 +939,6 @@ fn test_stake_split() {
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: 1,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
..TestValidatorOptions::default()
|
||||
});
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(alice, sender, None);
|
||||
@ -982,13 +965,11 @@ fn test_stake_split() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
500_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(500_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap();
|
||||
check_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create stake account, identity is authority
|
||||
@ -1056,7 +1037,6 @@ fn test_stake_split() {
|
||||
lamports: 2 * minimum_stake_balance,
|
||||
fee_payer: 0,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@ -1106,7 +1086,6 @@ fn test_stake_set_lockup() {
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: 1,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
..TestValidatorOptions::default()
|
||||
});
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(alice, sender, None);
|
||||
@ -1133,13 +1112,11 @@ fn test_stake_set_lockup() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
500_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(500_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap();
|
||||
check_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create stake account, identity is authority
|
||||
@ -1313,7 +1290,6 @@ fn test_stake_set_lockup() {
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@ -1386,13 +1362,11 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
200_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(200_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap();
|
||||
check_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create nonce account
|
||||
@ -1434,7 +1408,6 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
fee_payer: 0,
|
||||
from: 0,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@ -1476,7 +1449,6 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
destination_account_pubkey: recipient_pubkey,
|
||||
lamports: 42,
|
||||
withdraw_authority: 0,
|
||||
custodian: None,
|
||||
sign_only: true,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_pubkey),
|
||||
@ -1492,7 +1464,6 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
destination_account_pubkey: recipient_pubkey,
|
||||
lamports: 42,
|
||||
withdraw_authority: 0,
|
||||
custodian: None,
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_pubkey),
|
||||
@ -1552,7 +1523,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let seed_address =
|
||||
Pubkey::create_with_seed(&stake_pubkey, seed, &solana_stake_program::id()).unwrap();
|
||||
create_address_with_seed(&stake_pubkey, seed, &solana_stake_program::id()).unwrap();
|
||||
check_balance(50_000, &rpc_client, &seed_address);
|
||||
|
||||
server.close().unwrap();
|
||||
|
@ -1,6 +1,5 @@
|
||||
use solana_cli::{
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
cli_output::OutputFormat,
|
||||
nonce,
|
||||
offline::{
|
||||
blockhash_query::{self, BlockhashQuery},
|
||||
@ -41,7 +40,6 @@ fn test_transfer() {
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: 1,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
..TestValidatorOptions::default()
|
||||
});
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
@ -60,8 +58,7 @@ fn test_transfer() {
|
||||
let sender_pubkey = config.signers[0].pubkey();
|
||||
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 50_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 50_000).unwrap();
|
||||
check_balance(50_000, &rpc_client, &sender_pubkey);
|
||||
check_balance(0, &rpc_client, &recipient_pubkey);
|
||||
|
||||
@ -71,7 +68,6 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -89,7 +85,7 @@ fn test_transfer() {
|
||||
process_command(&offline).unwrap_err();
|
||||
|
||||
let offline_pubkey = offline.signers[0].pubkey();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 50, &config).unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 50).unwrap();
|
||||
check_balance(50, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Offline transfer
|
||||
@ -99,13 +95,11 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: true,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
offline.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@ -116,7 +110,6 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -154,7 +147,6 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
nonce_hash,
|
||||
@ -195,7 +187,6 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: true,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
@ -211,7 +202,6 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
sign_only.blockhash,
|
||||
@ -239,7 +229,6 @@ fn test_transfer_multisession_signing() {
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: 1,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
..TestValidatorOptions::default()
|
||||
});
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
@ -250,24 +239,16 @@ fn test_transfer_multisession_signing() {
|
||||
let offline_from_signer = keypair_from_seed(&[2u8; 32]).unwrap();
|
||||
let offline_fee_payer_signer = keypair_from_seed(&[3u8; 32]).unwrap();
|
||||
let from_null_signer = NullSigner::new(&offline_from_signer.pubkey());
|
||||
let config = CliConfig::default();
|
||||
|
||||
// Setup accounts
|
||||
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&offline_from_signer.pubkey(),
|
||||
43,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_from_signer.pubkey(), 43)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&offline_fee_payer_signer.pubkey(),
|
||||
3,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(43, &rpc_client, &offline_from_signer.pubkey());
|
||||
@ -288,13 +269,11 @@ fn test_transfer_multisession_signing() {
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: true,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
fee_payer_config.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&fee_payer_config).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
assert!(!sign_only.has_all_signers());
|
||||
@ -314,13 +293,11 @@ fn test_transfer_multisession_signing() {
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: true,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
from_config.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&from_config).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@ -337,7 +314,6 @@ fn test_transfer_multisession_signing() {
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
|
@ -48,7 +48,6 @@ fn test_vote_authorize_and_withdraw() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -57,7 +56,6 @@ fn test_vote_authorize_and_withdraw() {
|
||||
let vote_account_pubkey = vote_account_keypair.pubkey();
|
||||
config.signers = vec![&default_signer, &vote_account_keypair];
|
||||
config.command = CliCommand::CreateVoteAccount {
|
||||
vote_account: 1,
|
||||
seed: None,
|
||||
identity_account: 0,
|
||||
authorized_voter: None,
|
||||
@ -112,7 +110,6 @@ fn test_vote_authorize_and_withdraw() {
|
||||
config.command = CliCommand::VoteUpdateValidator {
|
||||
vote_account_pubkey,
|
||||
new_identity_account: 2,
|
||||
withdraw_authority: 1,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-client"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
description = "Solana Client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -14,15 +14,15 @@ bs58 = "0.3.0"
|
||||
indicatif = "0.14.0"
|
||||
jsonrpc-core = "14.0.5"
|
||||
log = "0.4.8"
|
||||
rayon = "1.3.0"
|
||||
reqwest = { version = "0.10.4", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serde = "1.0.105"
|
||||
rayon = "1.2.0"
|
||||
reqwest = { version = "0.10.1", default-features = false, features = ["blocking", "rustls-tls"] }
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.48"
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.1.23" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.23" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.23" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.1.23" }
|
||||
serde_json = "1.0.46"
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.14" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.0.14" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.14" }
|
||||
thiserror = "1.0"
|
||||
tungstenite = "0.10.1"
|
||||
url = "2.1.1"
|
||||
@ -31,7 +31,4 @@ url = "2.1.1"
|
||||
assert_matches = "1.3.0"
|
||||
jsonrpc-core = "14.0.5"
|
||||
jsonrpc-http-server = "14.0.6"
|
||||
solana-logger = { path = "../logger", version = "1.1.23" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
solana-logger = { path = "../logger", version = "1.0.14" }
|
||||
|
@ -50,29 +50,29 @@ impl Into<TransportError> for ClientErrorKind {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{kind}")]
|
||||
pub struct ClientError {
|
||||
request: Option<rpc_request::RpcRequest>,
|
||||
|
||||
command: Option<&'static str>,
|
||||
#[source]
|
||||
#[error(transparent)]
|
||||
kind: ClientErrorKind,
|
||||
}
|
||||
|
||||
impl ClientError {
|
||||
pub fn new_with_request(kind: ClientErrorKind, request: rpc_request::RpcRequest) -> Self {
|
||||
pub fn new_with_command(kind: ClientErrorKind, command: &'static str) -> Self {
|
||||
Self {
|
||||
request: Some(request),
|
||||
command: Some(command),
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_with_request(self, request: rpc_request::RpcRequest) -> Self {
|
||||
pub fn into_with_command(self, command: &'static str) -> Self {
|
||||
Self {
|
||||
request: Some(request),
|
||||
command: Some(command),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request(&self) -> Option<&rpc_request::RpcRequest> {
|
||||
self.request.as_ref()
|
||||
pub fn command(&self) -> Option<&'static str> {
|
||||
self.command
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> &ClientErrorKind {
|
||||
@ -83,7 +83,7 @@ impl ClientError {
|
||||
impl From<ClientErrorKind> for ClientError {
|
||||
fn from(kind: ClientErrorKind) -> Self {
|
||||
Self {
|
||||
request: None,
|
||||
command: None,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
@ -92,7 +92,7 @@ impl From<ClientErrorKind> for ClientError {
|
||||
impl From<TransportError> for ClientError {
|
||||
fn from(err: TransportError) -> Self {
|
||||
Self {
|
||||
request: None,
|
||||
command: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
@ -107,7 +107,7 @@ impl Into<TransportError> for ClientError {
|
||||
impl From<std::io::Error> for ClientError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
Self {
|
||||
request: None,
|
||||
command: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
@ -116,7 +116,7 @@ impl From<std::io::Error> for ClientError {
|
||||
impl From<reqwest::Error> for ClientError {
|
||||
fn from(err: reqwest::Error) -> Self {
|
||||
Self {
|
||||
request: None,
|
||||
command: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
@ -125,7 +125,7 @@ impl From<reqwest::Error> for ClientError {
|
||||
impl From<rpc_request::RpcError> for ClientError {
|
||||
fn from(err: rpc_request::RpcError) -> Self {
|
||||
Self {
|
||||
request: None,
|
||||
command: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
@ -134,7 +134,7 @@ impl From<rpc_request::RpcError> for ClientError {
|
||||
impl From<serde_json::error::Error> for ClientError {
|
||||
fn from(err: serde_json::error::Error) -> Self {
|
||||
Self {
|
||||
request: None,
|
||||
command: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
@ -143,7 +143,7 @@ impl From<serde_json::error::Error> for ClientError {
|
||||
impl From<SignerError> for ClientError {
|
||||
fn from(err: SignerError) -> Self {
|
||||
Self {
|
||||
request: None,
|
||||
command: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
@ -152,7 +152,7 @@ impl From<SignerError> for ClientError {
|
||||
impl From<TransactionError> for ClientError {
|
||||
fn from(err: TransactionError) -> Self {
|
||||
Self {
|
||||
request: None,
|
||||
command: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
use crate::{client_error::Result, rpc_request::RpcRequest};
|
||||
|
||||
pub(crate) trait GenericRpcClientRequest {
|
||||
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value>;
|
||||
fn send(
|
||||
&self,
|
||||
request: &RpcRequest,
|
||||
params: serde_json::Value,
|
||||
retries: usize,
|
||||
) -> Result<serde_json::Value>;
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ pub mod perf_utils;
|
||||
pub mod pubsub_client;
|
||||
pub mod rpc_client;
|
||||
pub mod rpc_client_request;
|
||||
pub mod rpc_config;
|
||||
pub mod rpc_request;
|
||||
pub mod rpc_response;
|
||||
pub mod thin_client;
|
||||
|
@ -8,8 +8,7 @@ use serde_json::{Number, Value};
|
||||
use solana_sdk::{
|
||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||
instruction::InstructionError,
|
||||
signature::Signature,
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
transaction::{self, TransactionError},
|
||||
};
|
||||
use solana_transaction_status::TransactionStatus;
|
||||
use std::{collections::HashMap, sync::RwLock};
|
||||
@ -38,8 +37,13 @@ impl MockRpcClientRequest {
|
||||
}
|
||||
|
||||
impl GenericRpcClientRequest for MockRpcClientRequest {
|
||||
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value> {
|
||||
if let Some(value) = self.mocks.write().unwrap().remove(&request) {
|
||||
fn send(
|
||||
&self,
|
||||
request: &RpcRequest,
|
||||
_params: serde_json::Value,
|
||||
_retries: usize,
|
||||
) -> Result<serde_json::Value> {
|
||||
if let Some(value) = self.mocks.write().unwrap().remove(request) {
|
||||
return Ok(value);
|
||||
}
|
||||
if self.url == "fails" {
|
||||
@ -101,17 +105,7 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
|
||||
}
|
||||
RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)),
|
||||
RpcRequest::GetSlot => Value::Number(Number::from(0)),
|
||||
RpcRequest::SendTransaction => {
|
||||
let signature = if self.url == "malicious" {
|
||||
Signature::new(&[8; 64]).to_string()
|
||||
} else {
|
||||
let tx_str = params.as_array().unwrap()[0].as_str().unwrap().to_string();
|
||||
let data = bs58::decode(tx_str).into_vec().unwrap();
|
||||
let tx: Transaction = bincode::deserialize(&data).unwrap();
|
||||
tx.signatures[0].to_string()
|
||||
};
|
||||
Value::String(signature)
|
||||
}
|
||||
RpcRequest::SendTransaction => Value::String(SIGNATURE.to_string()),
|
||||
RpcRequest::GetMinimumBalanceForRentExemption => Value::Number(Number::from(1234)),
|
||||
_ => Value::Null,
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,8 @@ use crate::{
|
||||
rpc_request::{RpcError, RpcRequest},
|
||||
};
|
||||
use log::*;
|
||||
use reqwest::{self, header::CONTENT_TYPE, StatusCode};
|
||||
use reqwest::{self, header::CONTENT_TYPE};
|
||||
use solana_sdk::clock::{DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT};
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
pub struct RpcClientRequest {
|
||||
@ -28,13 +29,17 @@ impl RpcClientRequest {
|
||||
}
|
||||
|
||||
impl GenericRpcClientRequest for RpcClientRequest {
|
||||
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value> {
|
||||
fn send(
|
||||
&self,
|
||||
request: &RpcRequest,
|
||||
params: serde_json::Value,
|
||||
mut retries: usize,
|
||||
) -> Result<serde_json::Value> {
|
||||
// Concurrent requests are not supported so reuse the same request id for all requests
|
||||
let request_id = 1;
|
||||
|
||||
let request_json = request.build_request_json(request_id, params);
|
||||
|
||||
let mut too_many_requests_retries = 5;
|
||||
loop {
|
||||
match self
|
||||
.client
|
||||
@ -45,19 +50,6 @@ impl GenericRpcClientRequest for RpcClientRequest {
|
||||
{
|
||||
Ok(response) => {
|
||||
if !response.status().is_success() {
|
||||
if response.status() == StatusCode::TOO_MANY_REQUESTS
|
||||
&& too_many_requests_retries > 0
|
||||
{
|
||||
too_many_requests_retries -= 1;
|
||||
debug!(
|
||||
"Server responded with {:?}, {} retries left",
|
||||
response, too_many_requests_retries
|
||||
);
|
||||
|
||||
// Sleep for 500ms to give the server a break
|
||||
sleep(Duration::from_millis(500));
|
||||
continue;
|
||||
}
|
||||
return Err(response.error_for_status().unwrap_err().into());
|
||||
}
|
||||
|
||||
@ -71,8 +63,17 @@ impl GenericRpcClientRequest for RpcClientRequest {
|
||||
}
|
||||
return Ok(json["result"].clone());
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err.into());
|
||||
Err(e) => {
|
||||
info!("{:?} failed, {} retries left: {:?}", request, retries, e);
|
||||
if retries == 0 {
|
||||
return Err(e.into());
|
||||
}
|
||||
retries -= 1;
|
||||
|
||||
// Sleep for approximately half a slot
|
||||
sleep(Duration::from_millis(
|
||||
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +0,0 @@
|
||||
use solana_sdk::{clock::Epoch, commitment_config::CommitmentConfig};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSignatureStatusConfig {
|
||||
pub search_transaction_history: Option<bool>,
|
||||
// DEPRECATED
|
||||
#[serde(flatten)]
|
||||
pub commitment: Option<CommitmentConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSendTransactionConfig {
|
||||
pub skip_preflight: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSimulateTransactionConfig {
|
||||
pub sig_verify: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RpcLargestAccountsFilter {
|
||||
Circulating,
|
||||
NonCirculating,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcLargestAccountsConfig {
|
||||
#[serde(flatten)]
|
||||
pub commitment: Option<CommitmentConfig>,
|
||||
pub filter: Option<RpcLargestAccountsFilter>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcStakeConfig {
|
||||
pub epoch: Option<Epoch>,
|
||||
#[serde(flatten)]
|
||||
pub commitment: Option<CommitmentConfig>,
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
use serde_json::{json, Value};
|
||||
use std::fmt;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub enum RpcRequest {
|
||||
DeregisterNode,
|
||||
ValidatorExit,
|
||||
@ -12,22 +11,16 @@ pub enum RpcRequest {
|
||||
GetClusterNodes,
|
||||
GetConfirmedBlock,
|
||||
GetConfirmedBlocks,
|
||||
GetConfirmedSignaturesForAddress,
|
||||
GetConfirmedTransaction,
|
||||
GetEpochInfo,
|
||||
GetEpochSchedule,
|
||||
GetFeeCalculatorForBlockhash,
|
||||
GetFeeRateGovernor,
|
||||
GetFees,
|
||||
GetGenesisHash,
|
||||
GetIdentity,
|
||||
GetInflationGovernor,
|
||||
GetInflationRate,
|
||||
GetLargestAccounts,
|
||||
GetInflation,
|
||||
GetLeaderSchedule,
|
||||
GetMinimumBalanceForRentExemption,
|
||||
GetProgramAccounts,
|
||||
GetRecentBlockhash,
|
||||
GetFeeCalculatorForBlockhash,
|
||||
GetFeeRateGovernor,
|
||||
GetSignatureStatuses,
|
||||
GetSlot,
|
||||
GetSlotLeader,
|
||||
@ -35,21 +28,20 @@ pub enum RpcRequest {
|
||||
GetStorageTurnRate,
|
||||
GetSlotsPerSegment,
|
||||
GetStoragePubkeysForSlot,
|
||||
GetSupply,
|
||||
GetTotalSupply,
|
||||
GetTransactionCount,
|
||||
GetVersion,
|
||||
GetVoteAccounts,
|
||||
MinimumLedgerSlot,
|
||||
RegisterNode,
|
||||
RequestAirdrop,
|
||||
SendTransaction,
|
||||
SimulateTransaction,
|
||||
SignVote,
|
||||
GetMinimumBalanceForRentExemption,
|
||||
MinimumLedgerSlot,
|
||||
}
|
||||
|
||||
impl fmt::Display for RpcRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
impl RpcRequest {
|
||||
pub(crate) fn build_request_json(&self, id: u64, params: Value) -> Value {
|
||||
let jsonrpc = "2.0";
|
||||
let method = match self {
|
||||
RpcRequest::DeregisterNode => "deregisterNode",
|
||||
RpcRequest::ValidatorExit => "validatorExit",
|
||||
@ -59,22 +51,16 @@ impl fmt::Display for RpcRequest {
|
||||
RpcRequest::GetClusterNodes => "getClusterNodes",
|
||||
RpcRequest::GetConfirmedBlock => "getConfirmedBlock",
|
||||
RpcRequest::GetConfirmedBlocks => "getConfirmedBlocks",
|
||||
RpcRequest::GetConfirmedSignaturesForAddress => "getConfirmedSignaturesForAddress",
|
||||
RpcRequest::GetConfirmedTransaction => "getConfirmedTransaction",
|
||||
RpcRequest::GetEpochInfo => "getEpochInfo",
|
||||
RpcRequest::GetEpochSchedule => "getEpochSchedule",
|
||||
RpcRequest::GetFeeCalculatorForBlockhash => "getFeeCalculatorForBlockhash",
|
||||
RpcRequest::GetFeeRateGovernor => "getFeeRateGovernor",
|
||||
RpcRequest::GetFees => "getFees",
|
||||
RpcRequest::GetGenesisHash => "getGenesisHash",
|
||||
RpcRequest::GetIdentity => "getIdentity",
|
||||
RpcRequest::GetInflationGovernor => "getInflationGovernor",
|
||||
RpcRequest::GetInflationRate => "getInflationRate",
|
||||
RpcRequest::GetLargestAccounts => "getLargestAccounts",
|
||||
RpcRequest::GetInflation => "getInflation",
|
||||
RpcRequest::GetLeaderSchedule => "getLeaderSchedule",
|
||||
RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption",
|
||||
RpcRequest::GetProgramAccounts => "getProgramAccounts",
|
||||
RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
|
||||
RpcRequest::GetFeeCalculatorForBlockhash => "getFeeCalculatorForBlockhash",
|
||||
RpcRequest::GetFeeRateGovernor => "getFeeRateGovernor",
|
||||
RpcRequest::GetSignatureStatuses => "getSignatureStatuses",
|
||||
RpcRequest::GetSlot => "getSlot",
|
||||
RpcRequest::GetSlotLeader => "getSlotLeader",
|
||||
@ -82,35 +68,20 @@ impl fmt::Display for RpcRequest {
|
||||
RpcRequest::GetStorageTurnRate => "getStorageTurnRate",
|
||||
RpcRequest::GetSlotsPerSegment => "getSlotsPerSegment",
|
||||
RpcRequest::GetStoragePubkeysForSlot => "getStoragePubkeysForSlot",
|
||||
RpcRequest::GetSupply => "getSupply",
|
||||
RpcRequest::GetTotalSupply => "getTotalSupply",
|
||||
RpcRequest::GetTransactionCount => "getTransactionCount",
|
||||
RpcRequest::GetVersion => "getVersion",
|
||||
RpcRequest::GetVoteAccounts => "getVoteAccounts",
|
||||
RpcRequest::MinimumLedgerSlot => "minimumLedgerSlot",
|
||||
RpcRequest::RegisterNode => "registerNode",
|
||||
RpcRequest::RequestAirdrop => "requestAirdrop",
|
||||
RpcRequest::SendTransaction => "sendTransaction",
|
||||
RpcRequest::SimulateTransaction => "simulateTransaction",
|
||||
RpcRequest::SignVote => "signVote",
|
||||
RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption",
|
||||
RpcRequest::MinimumLedgerSlot => "minimumLedgerSlot",
|
||||
};
|
||||
|
||||
write!(f, "{}", method)
|
||||
}
|
||||
}
|
||||
|
||||
pub const NUM_LARGEST_ACCOUNTS: usize = 20;
|
||||
pub const MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS: usize = 256;
|
||||
pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE: u64 = 10_000;
|
||||
pub const MAX_GET_CONFIRMED_BLOCKS_RANGE: u64 = 500_000;
|
||||
|
||||
impl RpcRequest {
|
||||
pub(crate) fn build_request_json(self, id: u64, params: Value) -> Value {
|
||||
let jsonrpc = "2.0";
|
||||
json!({
|
||||
"jsonrpc": jsonrpc,
|
||||
"id": id,
|
||||
"method": format!("{}", self),
|
||||
"method": method,
|
||||
"params": params,
|
||||
})
|
||||
}
|
||||
@ -149,6 +120,10 @@ mod tests {
|
||||
let request = test_request.build_request_json(1, Value::Null);
|
||||
assert_eq!(request["method"], "getEpochInfo");
|
||||
|
||||
let test_request = RpcRequest::GetInflation;
|
||||
let request = test_request.build_request_json(1, Value::Null);
|
||||
assert_eq!(request["method"], "getInflation");
|
||||
|
||||
let test_request = RpcRequest::GetRecentBlockhash;
|
||||
let request = test_request.build_request_json(1, Value::Null);
|
||||
assert_eq!(request["method"], "getRecentBlockhash");
|
||||
|
@ -3,7 +3,6 @@ use solana_sdk::{
|
||||
account::Account,
|
||||
clock::{Epoch, Slot},
|
||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||
inflation::Inflation,
|
||||
pubkey::Pubkey,
|
||||
transaction::{Result, TransactionError},
|
||||
};
|
||||
@ -36,14 +35,6 @@ pub struct RpcBlockhashFeeCalculator {
|
||||
pub fee_calculator: FeeCalculator,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcFees {
|
||||
pub blockhash: String,
|
||||
pub fee_calculator: FeeCalculator,
|
||||
pub last_valid_slot: Slot,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcFeeCalculator {
|
||||
@ -56,37 +47,6 @@ pub struct RpcFeeRateGovernor {
|
||||
pub fee_rate_governor: FeeRateGovernor,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcInflationGovernor {
|
||||
pub initial: f64,
|
||||
pub terminal: f64,
|
||||
pub taper: f64,
|
||||
pub foundation: f64,
|
||||
pub foundation_term: f64,
|
||||
}
|
||||
|
||||
impl From<Inflation> for RpcInflationGovernor {
|
||||
fn from(inflation: Inflation) -> Self {
|
||||
Self {
|
||||
initial: inflation.initial,
|
||||
terminal: inflation.terminal,
|
||||
taper: inflation.taper,
|
||||
foundation: inflation.foundation,
|
||||
foundation_term: inflation.foundation_term,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcInflationRate {
|
||||
pub total: f64,
|
||||
pub validator: f64,
|
||||
pub foundation: f64,
|
||||
pub epoch: Epoch,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcKeyedAccount {
|
||||
@ -148,8 +108,6 @@ pub struct RpcContactInfo {
|
||||
pub tpu: Option<SocketAddr>,
|
||||
/// JSON RPC port
|
||||
pub rpc: Option<SocketAddr>,
|
||||
/// Software version
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
/// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot
|
||||
@ -169,9 +127,6 @@ pub struct RpcEpochInfo {
|
||||
|
||||
/// The absolute current slot
|
||||
pub absolute_slot: Slot,
|
||||
|
||||
/// The current block height
|
||||
pub block_height: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
@ -231,49 +186,9 @@ pub struct RpcSignatureConfirmation {
|
||||
pub status: Result<()>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSimulateTransactionResult {
|
||||
pub err: Option<TransactionError>,
|
||||
pub logs: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcStorageTurn {
|
||||
pub blockhash: String,
|
||||
pub slot: Slot,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcAccountBalance {
|
||||
pub address: String,
|
||||
pub lamports: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSupply {
|
||||
pub total: u64,
|
||||
pub circulating: u64,
|
||||
pub non_circulating: u64,
|
||||
pub non_circulating_accounts: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum StakeActivationState {
|
||||
Activating,
|
||||
Active,
|
||||
Deactivating,
|
||||
Inactive,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcStakeActivation {
|
||||
pub state: StakeActivationState,
|
||||
pub active: u64,
|
||||
pub inactive: u64,
|
||||
}
|
||||
|
@ -440,7 +440,7 @@ impl SyncClient for ThinClient {
|
||||
match recent_blockhash {
|
||||
Ok(Response { value, .. }) => {
|
||||
self.optimizer.report(index, duration_as_ms(&now.elapsed()));
|
||||
Ok((value.0, value.1))
|
||||
Ok(value)
|
||||
}
|
||||
Err(e) => {
|
||||
self.optimizer.report(index, std::u64::MAX);
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "solana-core"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.1.23"
|
||||
version = "1.0.14"
|
||||
documentation = "https://docs.rs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
readme = "../README.md"
|
||||
@ -17,68 +17,66 @@ codecov = { repository = "solana-labs/solana", branch = "master", service = "git
|
||||
bincode = "1.2.1"
|
||||
bv = { version = "0.11.1", features = ["serde"] }
|
||||
bs58 = "0.3.0"
|
||||
byteorder = "1.3.4"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
byteorder = "1.3.2"
|
||||
chrono = { version = "0.4.10", features = ["serde"] }
|
||||
compression = "0.1.5"
|
||||
core_affinity = "0.5.10"
|
||||
crossbeam-channel = "0.4"
|
||||
ed25519-dalek = "=1.0.0-pre.3"
|
||||
crossbeam-channel = "0.3"
|
||||
fs_extra = "1.1.0"
|
||||
flate2 = "1.0"
|
||||
indexmap = "1.3"
|
||||
itertools = "0.9.0"
|
||||
itertools = "0.8.2"
|
||||
jsonrpc-core = "14.0.5"
|
||||
jsonrpc-core-client = { version = "14.0.5", features = ["ws"] }
|
||||
jsonrpc-derive = "14.0.5"
|
||||
jsonrpc-http-server = "14.0.6"
|
||||
jsonrpc-pubsub = "14.0.6"
|
||||
jsonrpc-ws-server = "14.0.6"
|
||||
libc = "0.2.66"
|
||||
log = "0.4.8"
|
||||
nix = "0.17.0"
|
||||
num_cpus = "1.0.0"
|
||||
num-traits = "0.2"
|
||||
rand = "0.7.0"
|
||||
rand_chacha = "0.2.2"
|
||||
rayon = "1.3.0"
|
||||
regex = "1.3.6"
|
||||
serde = "1.0.105"
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
rayon = "1.2.0"
|
||||
regex = "1.3.4"
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.48"
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.1.23" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.1.23" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.1.23" }
|
||||
solana-client = { path = "../client", version = "1.1.23" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.1.23" }
|
||||
solana-faucet = { path = "../faucet", version = "1.1.23" }
|
||||
solana-genesis-programs = { path = "../genesis-programs", version = "1.1.23" }
|
||||
solana-ledger = { path = "../ledger", version = "1.1.23" }
|
||||
solana-logger = { path = "../logger", version = "1.1.23" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.1.23" }
|
||||
solana-metrics = { path = "../metrics", version = "1.1.23" }
|
||||
solana-measure = { path = "../measure", version = "1.1.23" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.23" }
|
||||
solana-chacha-cuda = { path = "../chacha-cuda", version = "1.1.23" }
|
||||
solana-perf = { path = "../perf", version = "1.1.23" }
|
||||
solana-runtime = { path = "../runtime", version = "1.1.23" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.23" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.1.23" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.1.23" }
|
||||
solana-streamer = { path = "../streamer", version = "1.1.23" }
|
||||
solana-version = { path = "../version", version = "1.1.23" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.1.23" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.1.23" }
|
||||
solana-sys-tuner = { path = "../sys-tuner", version = "1.1.23" }
|
||||
serde_json = "1.0.46"
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.14" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.14" }
|
||||
solana-client = { path = "../client", version = "1.0.14" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.0.14" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.14" }
|
||||
ed25519-dalek = "=1.0.0-pre.1"
|
||||
solana-ledger = { path = "../ledger", version = "1.0.14" }
|
||||
solana-logger = { path = "../logger", version = "1.0.14" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.0.14" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.14" }
|
||||
solana-measure = { path = "../measure", version = "1.0.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.14" }
|
||||
solana-chacha-cuda = { path = "../chacha-cuda", version = "1.0.14" }
|
||||
solana-perf = { path = "../perf", version = "1.0.14" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.14" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.14" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.14" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.14" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.0.14" }
|
||||
solana-sys-tuner = { path = "../sys-tuner", version = "1.0.14" }
|
||||
tempfile = "3.1.0"
|
||||
thiserror = "1.0"
|
||||
tokio = "0.1"
|
||||
tokio-codec = "0.1"
|
||||
tokio-fs = "0.1"
|
||||
tokio-io = "0.1"
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.1.23" }
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.0.14" }
|
||||
trees = "0.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
matches = "0.1.6"
|
||||
reqwest = { version = "0.10.4", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serial_test = "0.4.0"
|
||||
reqwest = { version = "0.10.1", default-features = false, features = ["blocking", "rustls-tls"] }
|
||||
serial_test = "0.3.2"
|
||||
serial_test_derive = "0.4.0"
|
||||
systemstat = "0.1.5"
|
||||
|
||||
@ -106,6 +104,3 @@ name = "cluster_info"
|
||||
[[bench]]
|
||||
name = "chacha"
|
||||
required-features = ["chacha"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -9,12 +9,12 @@ use rayon::prelude::*;
|
||||
use solana_core::banking_stage::{create_test_recorder, BankingStage};
|
||||
use solana_core::cluster_info::ClusterInfo;
|
||||
use solana_core::cluster_info::Node;
|
||||
use solana_core::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_core::packet::to_packets_chunked;
|
||||
use solana_core::poh_recorder::WorkingBankEntry;
|
||||
use solana_ledger::blockstore_processor::process_entries;
|
||||
use solana_ledger::entry::{next_hash, Entry};
|
||||
use solana_ledger::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path};
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_perf::test_tx::test_tx;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::genesis_config::GenesisConfig;
|
||||
@ -190,7 +190,7 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) {
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, None);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let _banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
|
@ -3,19 +3,13 @@
|
||||
extern crate test;
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
use solana_core::broadcast_stage::broadcast_metrics::TransmitShredsStats;
|
||||
use solana_core::broadcast_stage::{broadcast_shreds, get_broadcast_peers};
|
||||
use solana_core::cluster_info::{ClusterInfo, Node};
|
||||
use solana_core::contact_info::ContactInfo;
|
||||
use solana_ledger::shred::{Shred, NONCE_SHRED_PAYLOAD_SIZE};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::timing::timestamp;
|
||||
use std::sync::RwLock;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::UdpSocket,
|
||||
sync::{atomic::AtomicU64, Arc},
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::net::UdpSocket;
|
||||
use std::sync::Arc;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
@ -26,8 +20,10 @@ fn broadcast_shreds_bench(bencher: &mut Bencher) {
|
||||
let mut cluster_info = ClusterInfo::new_with_invalid_keypair(leader_info.info.clone());
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
|
||||
const SHRED_SIZE: usize = 1024;
|
||||
const NUM_SHREDS: usize = 32;
|
||||
let shreds = vec![Shred::new_empty_data_shred(NONCE_SHRED_PAYLOAD_SIZE); NUM_SHREDS];
|
||||
let shreds = vec![vec![0; SHRED_SIZE]; NUM_SHREDS];
|
||||
let seeds = vec![[0u8; 32]; NUM_SHREDS];
|
||||
let mut stakes = HashMap::new();
|
||||
const NUM_PEERS: usize = 200;
|
||||
for _ in 0..NUM_PEERS {
|
||||
@ -37,20 +33,10 @@ fn broadcast_shreds_bench(bencher: &mut Bencher) {
|
||||
stakes.insert(id, thread_rng().gen_range(1, NUM_PEERS) as u64);
|
||||
}
|
||||
let stakes = Arc::new(stakes);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let (peers, peers_and_stakes) = get_broadcast_peers(&cluster_info, Some(stakes.clone()));
|
||||
let shreds = Arc::new(shreds);
|
||||
let last_datapoint = Arc::new(AtomicU64::new(0));
|
||||
bencher.iter(move || {
|
||||
let shreds = shreds.clone();
|
||||
broadcast_shreds(
|
||||
&socket,
|
||||
&shreds,
|
||||
&peers_and_stakes,
|
||||
&peers,
|
||||
&last_datapoint,
|
||||
&mut TransmitShredsStats::default(),
|
||||
)
|
||||
.unwrap();
|
||||
cluster_info
|
||||
.broadcast_shreds(&socket, shreds, &seeds, Some(stakes.clone()))
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
@ -6,12 +6,12 @@ extern crate test;
|
||||
use log::*;
|
||||
use solana_core::cluster_info::{ClusterInfo, Node};
|
||||
use solana_core::contact_info::ContactInfo;
|
||||
use solana_core::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_core::packet::to_packets_chunked;
|
||||
use solana_core::retransmit_stage::retransmitter;
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use solana_ledger::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_ledger::leader_schedule_cache::LeaderScheduleCache;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_perf::test_tx::test_tx;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
@ -45,7 +45,7 @@ fn bench_retransmitter(bencher: &mut Bencher) {
|
||||
peer_sockets.push(socket);
|
||||
}
|
||||
let peer_sockets = Arc::new(peer_sockets);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
|
||||
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(100_000);
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
|
@ -5,7 +5,7 @@ extern crate test;
|
||||
use solana_ledger::entry::{create_ticks, Entry};
|
||||
use solana_ledger::shred::{
|
||||
max_entries_per_n_shred, max_ticks_per_n_shreds, Shred, Shredder, RECOMMENDED_FEC_RATE,
|
||||
SIZE_OF_NONCE_DATA_SHRED_PAYLOAD,
|
||||
SIZE_OF_DATA_SHRED_PAYLOAD,
|
||||
};
|
||||
use solana_perf::test_tx;
|
||||
use solana_sdk::hash::Hash;
|
||||
@ -29,11 +29,10 @@ fn make_large_unchained_entries(txs_per_entry: u64, num_entries: u64) -> Vec<Ent
|
||||
#[bench]
|
||||
fn bench_shredder_ticks(bencher: &mut Bencher) {
|
||||
let kp = Arc::new(Keypair::new());
|
||||
let shred_size = SIZE_OF_NONCE_DATA_SHRED_PAYLOAD;
|
||||
let shred_size = SIZE_OF_DATA_SHRED_PAYLOAD;
|
||||
let num_shreds = ((1000 * 1000) + (shred_size - 1)) / shred_size;
|
||||
// ~1Mb
|
||||
let num_ticks =
|
||||
max_ticks_per_n_shreds(1, Some(SIZE_OF_NONCE_DATA_SHRED_PAYLOAD)) * num_shreds as u64;
|
||||
let num_ticks = max_ticks_per_n_shreds(1) * num_shreds as u64;
|
||||
let entries = create_ticks(num_ticks, 0, Hash::default());
|
||||
bencher.iter(|| {
|
||||
let shredder = Shredder::new(1, 0, RECOMMENDED_FEC_RATE, kp.clone(), 0, 0).unwrap();
|
||||
@ -44,14 +43,10 @@ fn bench_shredder_ticks(bencher: &mut Bencher) {
|
||||
#[bench]
|
||||
fn bench_shredder_large_entries(bencher: &mut Bencher) {
|
||||
let kp = Arc::new(Keypair::new());
|
||||
let shred_size = SIZE_OF_NONCE_DATA_SHRED_PAYLOAD;
|
||||
let shred_size = SIZE_OF_DATA_SHRED_PAYLOAD;
|
||||
let num_shreds = ((1000 * 1000) + (shred_size - 1)) / shred_size;
|
||||
let txs_per_entry = 128;
|
||||
let num_entries = max_entries_per_n_shred(
|
||||
&make_test_entry(txs_per_entry),
|
||||
num_shreds as u64,
|
||||
Some(shred_size),
|
||||
);
|
||||
let num_entries = max_entries_per_n_shred(&make_test_entry(txs_per_entry), num_shreds as u64);
|
||||
let entries = make_large_unchained_entries(txs_per_entry, num_entries);
|
||||
// 1Mb
|
||||
bencher.iter(|| {
|
||||
@ -63,10 +58,10 @@ fn bench_shredder_large_entries(bencher: &mut Bencher) {
|
||||
#[bench]
|
||||
fn bench_deshredder(bencher: &mut Bencher) {
|
||||
let kp = Arc::new(Keypair::new());
|
||||
let shred_size = SIZE_OF_NONCE_DATA_SHRED_PAYLOAD;
|
||||
let shred_size = SIZE_OF_DATA_SHRED_PAYLOAD;
|
||||
// ~10Mb
|
||||
let num_shreds = ((10000 * 1000) + (shred_size - 1)) / shred_size;
|
||||
let num_ticks = max_ticks_per_n_shreds(1, Some(shred_size)) * num_shreds as u64;
|
||||
let num_ticks = max_ticks_per_n_shreds(1) * num_shreds as u64;
|
||||
let entries = create_ticks(num_ticks, 0, Hash::default());
|
||||
let shredder = Shredder::new(1, 0, RECOMMENDED_FEC_RATE, kp, 0, 0).unwrap();
|
||||
let data_shreds = shredder.entries_to_shreds(&entries, true, 0).0;
|
||||
@ -78,7 +73,7 @@ fn bench_deshredder(bencher: &mut Bencher) {
|
||||
|
||||
#[bench]
|
||||
fn bench_deserialize_hdr(bencher: &mut Bencher) {
|
||||
let data = vec![0; SIZE_OF_NONCE_DATA_SHRED_PAYLOAD];
|
||||
let data = vec![0; SIZE_OF_DATA_SHRED_PAYLOAD];
|
||||
|
||||
let shred = Shred::new_from_data(2, 1, 1, Some(&data), true, true, 0, 0, 1);
|
||||
|
||||
|
@ -6,9 +6,9 @@ extern crate test;
|
||||
use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use solana_core::packet::to_packets_chunked;
|
||||
use solana_core::sigverify::TransactionSigVerifier;
|
||||
use solana_core::sigverify_stage::SigVerifyStage;
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_perf::test_tx::test_tx;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
|
@ -1,51 +0,0 @@
|
||||
// Service to clean up dead slots in accounts_db
|
||||
//
|
||||
// This can be expensive since we have to walk the append vecs being cleaned up.
|
||||
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, RwLock,
|
||||
};
|
||||
use std::thread::{self, sleep, Builder, JoinHandle};
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct AccountsBackgroundService {
|
||||
t_background: JoinHandle<()>,
|
||||
}
|
||||
|
||||
const INTERVAL_MS: u64 = 100;
|
||||
|
||||
impl AccountsBackgroundService {
|
||||
pub fn new(bank_forks: Arc<RwLock<BankForks>>, exit: &Arc<AtomicBool>) -> Self {
|
||||
info!("AccountsBackgroundService active");
|
||||
let exit = exit.clone();
|
||||
let t_background = Builder::new()
|
||||
.name("solana-accounts-background".to_string())
|
||||
.spawn(move || loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
let bank = bank_forks.read().unwrap().working_bank();
|
||||
|
||||
bank.process_dead_slots();
|
||||
|
||||
// Currently, given INTERVAL_MS, we process 1 slot/100 ms
|
||||
let shrunken_account_count = bank.process_stale_slot();
|
||||
if shrunken_account_count > 0 {
|
||||
datapoint_info!(
|
||||
"stale_slot_shrink",
|
||||
("accounts", shrunken_account_count, i64)
|
||||
);
|
||||
}
|
||||
|
||||
sleep(Duration::from_millis(INTERVAL_MS));
|
||||
})
|
||||
.unwrap();
|
||||
Self { t_background }
|
||||
}
|
||||
|
||||
pub fn join(self) -> thread::Result<()> {
|
||||
self.t_background.join()
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::RecvTimeoutError,
|
||||
Arc,
|
||||
Arc, RwLock,
|
||||
},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
time::Duration,
|
||||
@ -30,7 +30,7 @@ impl AccountsHashVerifier {
|
||||
snapshot_package_receiver: SnapshotPackageReceiver,
|
||||
snapshot_package_sender: Option<SnapshotPackageSender>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
cluster_info: &Arc<ClusterInfo>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
trusted_validators: Option<HashSet<Pubkey>>,
|
||||
halt_on_trusted_validators_accounts_hash_mismatch: bool,
|
||||
fault_injection_rate_slots: u64,
|
||||
@ -72,7 +72,7 @@ impl AccountsHashVerifier {
|
||||
|
||||
fn process_snapshot(
|
||||
snapshot_package: SnapshotPackage,
|
||||
cluster_info: &ClusterInfo,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
trusted_validators: &Option<HashSet<Pubkey>>,
|
||||
halt_on_trusted_validator_accounts_hash_mismatch: bool,
|
||||
snapshot_package_sender: &Option<SnapshotPackageSender>,
|
||||
@ -111,11 +111,14 @@ impl AccountsHashVerifier {
|
||||
if sender.send(snapshot_package).is_err() {}
|
||||
}
|
||||
|
||||
cluster_info.push_accounts_hashes(hashes.clone());
|
||||
cluster_info
|
||||
.write()
|
||||
.unwrap()
|
||||
.push_accounts_hashes(hashes.clone());
|
||||
}
|
||||
|
||||
fn should_halt(
|
||||
cluster_info: &ClusterInfo,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
trusted_validators: &Option<HashSet<Pubkey>>,
|
||||
slot_to_hash: &mut HashMap<Slot, Hash>,
|
||||
) -> bool {
|
||||
@ -123,9 +126,11 @@ impl AccountsHashVerifier {
|
||||
let mut highest_slot = 0;
|
||||
if let Some(trusted_validators) = trusted_validators.as_ref() {
|
||||
for trusted_validator in trusted_validators {
|
||||
let is_conflicting = cluster_info.get_accounts_hash_for_node(trusted_validator, |accounts_hashes|
|
||||
let cluster_info_r = cluster_info.read().unwrap();
|
||||
if let Some(accounts_hashes) =
|
||||
cluster_info_r.get_accounts_hash_for_node(trusted_validator)
|
||||
{
|
||||
accounts_hashes.iter().any(|(slot, hash)| {
|
||||
for (slot, hash) in accounts_hashes {
|
||||
if let Some(reference_hash) = slot_to_hash.get(slot) {
|
||||
if *hash != *reference_hash {
|
||||
error!("Trusted validator {} produced conflicting hashes for slot: {} ({} != {})",
|
||||
@ -134,21 +139,16 @@ impl AccountsHashVerifier {
|
||||
hash,
|
||||
reference_hash,
|
||||
);
|
||||
true
|
||||
|
||||
return true;
|
||||
} else {
|
||||
verified_count += 1;
|
||||
false
|
||||
}
|
||||
} else {
|
||||
highest_slot = std::cmp::max(*slot, highest_slot);
|
||||
slot_to_hash.insert(*slot, *hash);
|
||||
false
|
||||
}
|
||||
})
|
||||
}).unwrap_or(false);
|
||||
|
||||
if is_conflicting {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -181,7 +181,7 @@ mod tests {
|
||||
|
||||
let contact_info = ContactInfo::new_localhost(&keypair.pubkey(), 0);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
|
||||
let mut trusted_validators = HashSet::new();
|
||||
let mut slot_to_hash = HashMap::new();
|
||||
@ -196,7 +196,8 @@ mod tests {
|
||||
let hash2 = hash(&[2]);
|
||||
{
|
||||
let message = make_accounts_hashes_message(&validator1, vec![(0, hash1)]).unwrap();
|
||||
cluster_info.push_message(message);
|
||||
let mut cluster_info_w = cluster_info.write().unwrap();
|
||||
cluster_info_w.push_message(message);
|
||||
}
|
||||
slot_to_hash.insert(0, hash2);
|
||||
trusted_validators.insert(validator1.pubkey());
|
||||
@ -216,7 +217,7 @@ mod tests {
|
||||
|
||||
let contact_info = ContactInfo::new_localhost(&keypair.pubkey(), 0);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
|
||||
let trusted_validators = HashSet::new();
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
@ -243,8 +244,9 @@ mod tests {
|
||||
0,
|
||||
);
|
||||
}
|
||||
let cluster_hashes = cluster_info
|
||||
.get_accounts_hash_for_node(&keypair.pubkey(), |c| c.clone())
|
||||
let cluster_info_r = cluster_info.read().unwrap();
|
||||
let cluster_hashes = cluster_info_r
|
||||
.get_accounts_hash_for_node(&keypair.pubkey())
|
||||
.unwrap();
|
||||
info!("{:?}", cluster_hashes);
|
||||
assert_eq!(hashes.len(), MAX_SNAPSHOT_HASHES);
|
||||
|
@ -3,6 +3,7 @@
|
||||
//! can do its processing in parallel with signature verification on the GPU.
|
||||
use crate::{
|
||||
cluster_info::ClusterInfo,
|
||||
packet::{limited_deserialize, Packet, Packets, PACKETS_PER_BATCH},
|
||||
poh_recorder::{PohRecorder, PohRecorderError, WorkingBankEntry},
|
||||
poh_service::PohService,
|
||||
};
|
||||
@ -16,11 +17,7 @@ use solana_ledger::{
|
||||
};
|
||||
use solana_measure::{measure::Measure, thread_mem_usage};
|
||||
use solana_metrics::{inc_new_counter_debug, inc_new_counter_info, inc_new_counter_warn};
|
||||
use solana_perf::{
|
||||
cuda_runtime::PinnedVec,
|
||||
packet::{limited_deserialize, Packet, Packets, PACKETS_PER_BATCH},
|
||||
perf_libs,
|
||||
};
|
||||
use solana_perf::{cuda_runtime::PinnedVec, perf_libs};
|
||||
use solana_runtime::{
|
||||
accounts_db::ErrorCounters,
|
||||
bank::{Bank, TransactionBalancesSet, TransactionProcessResult},
|
||||
@ -41,7 +38,7 @@ use std::{
|
||||
net::UdpSocket,
|
||||
sync::atomic::AtomicBool,
|
||||
sync::mpsc::Receiver,
|
||||
sync::{Arc, Mutex},
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
time::Duration,
|
||||
time::Instant,
|
||||
@ -51,7 +48,7 @@ type PacketsAndOffsets = (Packets, Vec<usize>);
|
||||
pub type UnprocessedPackets = Vec<PacketsAndOffsets>;
|
||||
|
||||
/// Transaction forwarding
|
||||
pub const FORWARD_TRANSACTIONS_TO_LEADER_AT_SLOT_OFFSET: u64 = 1;
|
||||
pub const FORWARD_TRANSACTIONS_TO_LEADER_AT_SLOT_OFFSET: u64 = 4;
|
||||
|
||||
// Fixed thread size seems to be fastest on GCP setup
|
||||
pub const NUM_THREADS: u32 = 4;
|
||||
@ -76,7 +73,7 @@ impl BankingStage {
|
||||
/// Create the stage using `bank`. Exit when `verified_receiver` is dropped.
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(
|
||||
cluster_info: &Arc<ClusterInfo>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
verified_receiver: CrossbeamReceiver<Vec<Packets>>,
|
||||
verified_vote_receiver: CrossbeamReceiver<Vec<Packets>>,
|
||||
@ -93,7 +90,7 @@ impl BankingStage {
|
||||
}
|
||||
|
||||
fn new_num_threads(
|
||||
cluster_info: &Arc<ClusterInfo>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
verified_receiver: CrossbeamReceiver<Vec<Packets>>,
|
||||
verified_vote_receiver: CrossbeamReceiver<Vec<Packets>>,
|
||||
@ -104,7 +101,7 @@ impl BankingStage {
|
||||
// Single thread to generate entries from many banks.
|
||||
// This thread talks to poh_service and broadcasts the entries once they have been recorded.
|
||||
// Once an entry has been recorded, its blockhash is registered with the bank.
|
||||
let my_pubkey = cluster_info.id();
|
||||
let my_pubkey = cluster_info.read().unwrap().id();
|
||||
// Many banks that process transactions in parallel.
|
||||
let bank_thread_hdls: Vec<JoinHandle<()>> = (0..num_threads)
|
||||
.map(|i| {
|
||||
@ -287,12 +284,12 @@ impl BankingStage {
|
||||
my_pubkey: &Pubkey,
|
||||
socket: &std::net::UdpSocket,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
buffered_packets: &mut Vec<PacketsAndOffsets>,
|
||||
enable_forwarding: bool,
|
||||
batch_limit: usize,
|
||||
transaction_status_sender: Option<TransactionStatusSender>,
|
||||
) -> BufferedPacketsDecision {
|
||||
) {
|
||||
let (leader_at_slot_offset, poh_has_bank, would_be_leader) = {
|
||||
let poh = poh_recorder.lock().unwrap();
|
||||
(
|
||||
@ -331,7 +328,10 @@ impl BankingStage {
|
||||
next_leader.map_or((), |leader_pubkey| {
|
||||
let leader_addr = {
|
||||
cluster_info
|
||||
.lookup_contact_info(&leader_pubkey, |leader| leader.tpu_forwards)
|
||||
.read()
|
||||
.unwrap()
|
||||
.lookup(&leader_pubkey)
|
||||
.map(|leader| leader.tpu_forwards)
|
||||
};
|
||||
|
||||
leader_addr.map_or((), |leader_addr| {
|
||||
@ -349,14 +349,13 @@ impl BankingStage {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
decision
|
||||
}
|
||||
|
||||
pub fn process_loop(
|
||||
my_pubkey: Pubkey,
|
||||
verified_receiver: &CrossbeamReceiver<Vec<Packets>>,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
recv_start: &mut Instant,
|
||||
enable_forwarding: bool,
|
||||
id: u32,
|
||||
@ -366,8 +365,8 @@ impl BankingStage {
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
let mut buffered_packets = vec![];
|
||||
loop {
|
||||
while !buffered_packets.is_empty() {
|
||||
let decision = Self::process_buffered_packets(
|
||||
if !buffered_packets.is_empty() {
|
||||
Self::process_buffered_packets(
|
||||
&my_pubkey,
|
||||
&socket,
|
||||
poh_recorder,
|
||||
@ -377,11 +376,6 @@ impl BankingStage {
|
||||
batch_limit,
|
||||
transaction_status_sender.clone(),
|
||||
);
|
||||
if decision == BufferedPacketsDecision::Hold {
|
||||
// If we are waiting on a new bank,
|
||||
// check the receiver for more transactions/for exiting
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let recv_timeout = if !buffered_packets.is_empty() {
|
||||
@ -509,7 +503,7 @@ impl BankingStage {
|
||||
// expires.
|
||||
let txs = batch.transactions();
|
||||
let pre_balances = if transaction_status_sender.is_some() {
|
||||
bank.collect_balances(batch)
|
||||
bank.collect_balances(txs)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
@ -545,11 +539,10 @@ impl BankingStage {
|
||||
.processing_results;
|
||||
|
||||
if let Some(sender) = transaction_status_sender {
|
||||
let post_balances = bank.collect_balances(batch);
|
||||
let post_balances = bank.collect_balances(txs);
|
||||
send_transaction_status_batch(
|
||||
bank.clone(),
|
||||
batch.transactions(),
|
||||
batch.iteration_order_vec(),
|
||||
transaction_statuses,
|
||||
TransactionBalancesSet::new(pre_balances, post_balances),
|
||||
sender,
|
||||
@ -1016,7 +1009,10 @@ pub fn create_test_recorder(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
cluster_info::Node, poh_recorder::WorkingBank,
|
||||
cluster_info::Node,
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
packet::to_packets,
|
||||
poh_recorder::WorkingBank,
|
||||
transaction_status_service::TransactionStatusService,
|
||||
};
|
||||
use crossbeam_channel::unbounded;
|
||||
@ -1024,10 +1020,8 @@ mod tests {
|
||||
use solana_ledger::{
|
||||
blockstore::entries_to_test_shreds,
|
||||
entry::{next_entry, Entry, EntrySlice},
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
get_tmp_ledger_path,
|
||||
};
|
||||
use solana_perf::packet::to_packets;
|
||||
use solana_runtime::bank::HashAgeKind;
|
||||
use solana_sdk::{
|
||||
instruction::InstructionError,
|
||||
@ -1053,7 +1047,7 @@ mod tests {
|
||||
let (exit, poh_recorder, poh_service, _entry_receiever) =
|
||||
create_test_recorder(&bank, &blockstore, None);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
@ -1093,7 +1087,7 @@ mod tests {
|
||||
let (exit, poh_recorder, poh_service, entry_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, Some(poh_config));
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
@ -1156,7 +1150,7 @@ mod tests {
|
||||
let (exit, poh_recorder, poh_service, entry_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, Some(poh_config));
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
@ -1297,7 +1291,7 @@ mod tests {
|
||||
create_test_recorder(&bank, &blockstore, Some(poh_config));
|
||||
let cluster_info =
|
||||
ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let _banking_stage = BankingStage::new_num_threads(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
@ -1995,14 +1989,14 @@ mod tests {
|
||||
meta.err,
|
||||
Some(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::Custom(1)
|
||||
InstructionError::CustomError(1)
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
meta.status,
|
||||
Err(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::Custom(1)
|
||||
InstructionError::CustomError(1)
|
||||
))
|
||||
);
|
||||
} else {
|
||||
|
283
core/src/blockstream.rs
Normal file
283
core/src/blockstream.rs
Normal file
@ -0,0 +1,283 @@
|
||||
//! The `blockstream` module provides a method for streaming entries out via a
|
||||
//! local unix socket, to provide client services such as a block explorer with
|
||||
//! real-time access to entries.
|
||||
|
||||
use bincode::serialize;
|
||||
use chrono::{SecondsFormat, Utc};
|
||||
use serde_json::json;
|
||||
use solana_ledger::entry::Entry;
|
||||
use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey};
|
||||
use std::cell::RefCell;
|
||||
use std::io::Result;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub trait EntryWriter: std::fmt::Debug {
|
||||
fn write(&self, payload: String) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EntryVec {
|
||||
values: RefCell<Vec<String>>,
|
||||
}
|
||||
|
||||
impl EntryWriter for EntryVec {
|
||||
fn write(&self, payload: String) -> Result<()> {
|
||||
self.values.borrow_mut().push(payload);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl EntryVec {
|
||||
pub fn new() -> Self {
|
||||
EntryVec {
|
||||
values: RefCell::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> Vec<String> {
|
||||
self.values.borrow().clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EntrySocket {
|
||||
unix_socket: PathBuf,
|
||||
}
|
||||
|
||||
impl EntryWriter for EntrySocket {
|
||||
#[cfg(not(windows))]
|
||||
fn write(&self, payload: String) -> Result<()> {
|
||||
use std::io::prelude::*;
|
||||
use std::net::Shutdown;
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
const MESSAGE_TERMINATOR: &str = "\n";
|
||||
|
||||
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)?;
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(windows)]
|
||||
fn write(&self, _payload: String) -> Result<()> {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"EntryWriter::write() not implemented for windows",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BlockstreamEvents {
|
||||
fn emit_entry_event(
|
||||
&self,
|
||||
slot: Slot,
|
||||
tick_height: u64,
|
||||
leader_pubkey: &Pubkey,
|
||||
entries: &Entry,
|
||||
) -> Result<()>;
|
||||
fn emit_block_event(
|
||||
&self,
|
||||
slot: Slot,
|
||||
tick_height: u64,
|
||||
leader_pubkey: &Pubkey,
|
||||
blockhash: Hash,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Blockstream<T: EntryWriter> {
|
||||
pub output: T,
|
||||
}
|
||||
|
||||
impl<T> BlockstreamEvents for Blockstream<T>
|
||||
where
|
||||
T: EntryWriter,
|
||||
{
|
||||
fn emit_entry_event(
|
||||
&self,
|
||||
slot: Slot,
|
||||
tick_height: u64,
|
||||
leader_pubkey: &Pubkey,
|
||||
entry: &Entry,
|
||||
) -> Result<()> {
|
||||
let transactions: Vec<Vec<u8>> = serialize_transactions(entry);
|
||||
let stream_entry = json!({
|
||||
"num_hashes": entry.num_hashes,
|
||||
"hash": entry.hash,
|
||||
"transactions": transactions
|
||||
});
|
||||
let json_entry = serde_json::to_string(&stream_entry)?;
|
||||
let payload = format!(
|
||||
r#"{{"dt":"{}","t":"entry","s":{},"h":{},"l":"{:?}","entry":{}}}"#,
|
||||
Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true),
|
||||
slot,
|
||||
tick_height,
|
||||
leader_pubkey,
|
||||
json_entry,
|
||||
);
|
||||
self.output.write(payload)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_block_event(
|
||||
&self,
|
||||
slot: Slot,
|
||||
tick_height: u64,
|
||||
leader_pubkey: &Pubkey,
|
||||
blockhash: Hash,
|
||||
) -> Result<()> {
|
||||
let payload = format!(
|
||||
r#"{{"dt":"{}","t":"block","s":{},"h":{},"l":"{:?}","hash":"{:?}"}}"#,
|
||||
Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true),
|
||||
slot,
|
||||
tick_height,
|
||||
leader_pubkey,
|
||||
blockhash,
|
||||
);
|
||||
self.output.write(payload)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub type SocketBlockstream = Blockstream<EntrySocket>;
|
||||
|
||||
impl SocketBlockstream {
|
||||
pub fn new(unix_socket: &Path) -> Self {
|
||||
Blockstream {
|
||||
output: EntrySocket {
|
||||
unix_socket: unix_socket.to_path_buf(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type MockBlockstream = Blockstream<EntryVec>;
|
||||
|
||||
impl MockBlockstream {
|
||||
pub fn new(_: &Path) -> Self {
|
||||
Blockstream {
|
||||
output: EntryVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> Vec<String> {
|
||||
self.output.entries()
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_transactions(entry: &Entry) -> Vec<Vec<u8>> {
|
||||
entry
|
||||
.transactions
|
||||
.iter()
|
||||
.map(|tx| serialize(&tx).unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use serde_json::Value;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::system_transaction;
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn test_serialize_transactions() {
|
||||
let entry = Entry::new(&Hash::default(), 1, vec![]);
|
||||
let empty_vec: Vec<Vec<u8>> = vec![];
|
||||
assert_eq!(serialize_transactions(&entry), empty_vec);
|
||||
|
||||
let keypair0 = Keypair::new();
|
||||
let keypair1 = Keypair::new();
|
||||
let tx0 = system_transaction::transfer(&keypair0, &keypair1.pubkey(), 1, Hash::default());
|
||||
let tx1 = system_transaction::transfer(&keypair1, &keypair0.pubkey(), 2, Hash::default());
|
||||
let serialized_tx0 = serialize(&tx0).unwrap();
|
||||
let serialized_tx1 = serialize(&tx1).unwrap();
|
||||
let entry = Entry::new(&Hash::default(), 1, vec![tx0, tx1]);
|
||||
assert_eq!(
|
||||
serialize_transactions(&entry),
|
||||
vec![serialized_tx0, serialized_tx1]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blockstream() -> () {
|
||||
let blockstream = MockBlockstream::new(&PathBuf::from("test_stream"));
|
||||
let ticks_per_slot = 5;
|
||||
|
||||
let mut blockhash = Hash::default();
|
||||
let mut entries = Vec::new();
|
||||
let mut expected_entries = Vec::new();
|
||||
|
||||
let tick_height_initial = 1;
|
||||
let tick_height_final = tick_height_initial + ticks_per_slot + 2;
|
||||
let mut curr_slot = 0;
|
||||
let leader_pubkey = Pubkey::new_rand();
|
||||
|
||||
for tick_height in tick_height_initial..=tick_height_final {
|
||||
if tick_height == 5 {
|
||||
blockstream
|
||||
.emit_block_event(curr_slot, tick_height, &leader_pubkey, blockhash)
|
||||
.unwrap();
|
||||
curr_slot += 1;
|
||||
}
|
||||
let entry = Entry::new(&mut blockhash, 1, vec![]); // just ticks
|
||||
blockhash = entry.hash;
|
||||
blockstream
|
||||
.emit_entry_event(curr_slot, tick_height, &leader_pubkey, &entry)
|
||||
.unwrap();
|
||||
expected_entries.push(entry.clone());
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
blockstream.entries().len() as u64,
|
||||
// one entry per tick (1..=N+2) is +3, plus one block
|
||||
ticks_per_slot + 3 + 1
|
||||
);
|
||||
|
||||
let mut j = 0;
|
||||
let mut matched_entries = 0;
|
||||
let mut matched_slots = HashSet::new();
|
||||
let mut matched_blocks = HashSet::new();
|
||||
|
||||
for item in blockstream.entries() {
|
||||
let json: Value = serde_json::from_str(&item).unwrap();
|
||||
let dt_str = json["dt"].as_str().unwrap();
|
||||
|
||||
// Ensure `ts` field parses as valid DateTime
|
||||
let _dt: DateTime<FixedOffset> = DateTime::parse_from_rfc3339(dt_str).unwrap();
|
||||
|
||||
let item_type = json["t"].as_str().unwrap();
|
||||
match item_type {
|
||||
"block" => {
|
||||
let hash = json["hash"].to_string();
|
||||
matched_blocks.insert(hash);
|
||||
}
|
||||
|
||||
"entry" => {
|
||||
let slot = json["s"].as_u64().unwrap();
|
||||
matched_slots.insert(slot);
|
||||
let entry_obj = json["entry"].clone();
|
||||
let entry: Entry = serde_json::from_value(entry_obj).unwrap();
|
||||
|
||||
assert_eq!(entry, expected_entries[j]);
|
||||
matched_entries += 1;
|
||||
j += 1;
|
||||
}
|
||||
|
||||
_ => {
|
||||
assert!(false, "unknown item type {}", item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(matched_entries, expected_entries.len());
|
||||
assert_eq!(matched_slots.len(), 2);
|
||||
assert_eq!(matched_blocks.len(), 1);
|
||||
}
|
||||
}
|
228
core/src/blockstream_service.rs
Normal file
228
core/src/blockstream_service.rs
Normal file
@ -0,0 +1,228 @@
|
||||
//! The `blockstream_service` implements optional streaming of entries and block metadata
|
||||
//! using the `blockstream` module, providing client services such as a block explorer with
|
||||
//! real-time access to entries.
|
||||
|
||||
use crate::blockstream::BlockstreamEvents;
|
||||
#[cfg(test)]
|
||||
use crate::blockstream::MockBlockstream as Blockstream;
|
||||
#[cfg(not(test))]
|
||||
use crate::blockstream::SocketBlockstream as Blockstream;
|
||||
use crate::result::{Error, Result};
|
||||
use solana_ledger::blockstore::Blockstore;
|
||||
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;
|
||||
use std::thread::{self, Builder, JoinHandle};
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct BlockstreamService {
|
||||
t_blockstream: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl BlockstreamService {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(
|
||||
slot_full_receiver: Receiver<(u64, Pubkey)>,
|
||||
blockstore: Arc<Blockstore>,
|
||||
unix_socket: &Path,
|
||||
exit: &Arc<AtomicBool>,
|
||||
) -> Self {
|
||||
let mut blockstream = Blockstream::new(unix_socket);
|
||||
let exit = exit.clone();
|
||||
let t_blockstream = Builder::new()
|
||||
.name("solana-blockstream".to_string())
|
||||
.spawn(move || loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
if let Err(e) =
|
||||
Self::process_entries(&slot_full_receiver, &blockstore, &mut blockstream)
|
||||
{
|
||||
match e {
|
||||
Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break,
|
||||
Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (),
|
||||
_ => info!("Error from process_entries: {:?}", e),
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
Self { t_blockstream }
|
||||
}
|
||||
fn process_entries(
|
||||
slot_full_receiver: &Receiver<(u64, Pubkey)>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
blockstream: &mut Blockstream,
|
||||
) -> Result<()> {
|
||||
let timeout = Duration::new(1, 0);
|
||||
let (slot, slot_leader) = slot_full_receiver.recv_timeout(timeout)?;
|
||||
|
||||
// Slot might not exist due to LedgerCleanupService, check first
|
||||
let blockstore_meta = blockstore.meta(slot).unwrap();
|
||||
if let Some(blockstore_meta) = blockstore_meta {
|
||||
// Return error to main loop. Thread won't exit, will just log the error
|
||||
let entries = blockstore.get_slot_entries(slot, 0, None)?;
|
||||
let _parent_slot = if slot == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(blockstore_meta.parent_slot)
|
||||
};
|
||||
let ticks_per_slot = entries.iter().filter(|entry| entry.is_tick()).count() as u64;
|
||||
let mut tick_height = ticks_per_slot * slot;
|
||||
|
||||
for (i, entry) in entries.iter().enumerate() {
|
||||
if entry.is_tick() {
|
||||
tick_height += 1;
|
||||
}
|
||||
blockstream
|
||||
.emit_entry_event(slot, tick_height, &slot_leader, &entry)
|
||||
.unwrap_or_else(|e| {
|
||||
debug!("Blockstream error: {:?}, {:?}", e, blockstream.output);
|
||||
});
|
||||
if i == entries.len() - 1 {
|
||||
blockstream
|
||||
.emit_block_event(slot, tick_height, &slot_leader, entry.hash)
|
||||
.unwrap_or_else(|e| {
|
||||
debug!("Blockstream error: {:?}, {:?}", e, blockstream.output);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn join(self) -> thread::Result<()> {
|
||||
self.t_blockstream.join()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use bincode::{deserialize, serialize};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use serde_json::Value;
|
||||
use solana_ledger::create_new_tmp_ledger;
|
||||
use solana_ledger::entry::{create_ticks, Entry};
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::system_transaction;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
fn test_blockstream_service_process_entries() {
|
||||
let ticks_per_slot = 5;
|
||||
let leader_pubkey = Pubkey::new_rand();
|
||||
|
||||
// Set up genesis config and blockstore
|
||||
let GenesisConfigInfo {
|
||||
mut genesis_config, ..
|
||||
} = create_genesis_config(1000);
|
||||
genesis_config.ticks_per_slot = ticks_per_slot;
|
||||
|
||||
let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_config);
|
||||
let blockstore = Blockstore::open(&ledger_path).unwrap();
|
||||
|
||||
// Set up blockstream
|
||||
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();
|
||||
|
||||
// Create entries - 4 ticks + 1 populated entry + 1 tick
|
||||
let mut entries = create_ticks(4, 0, Hash::default());
|
||||
|
||||
let keypair = Keypair::new();
|
||||
let mut blockhash = entries[3].hash;
|
||||
let tx = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, Hash::default());
|
||||
let entry = Entry::new(&mut blockhash, 1, vec![tx]);
|
||||
blockhash = entry.hash;
|
||||
entries.push(entry);
|
||||
let final_tick = create_ticks(1, 0, blockhash);
|
||||
entries.extend_from_slice(&final_tick);
|
||||
|
||||
let expected_entries = entries.clone();
|
||||
let expected_tick_heights = [6, 7, 8, 9, 9, 10];
|
||||
|
||||
blockstore
|
||||
.write_entries(
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
ticks_per_slot,
|
||||
None,
|
||||
true,
|
||||
&Arc::new(Keypair::new()),
|
||||
entries,
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
slot_full_sender.send((1, leader_pubkey)).unwrap();
|
||||
BlockstreamService::process_entries(
|
||||
&slot_full_receiver,
|
||||
&Arc::new(blockstore),
|
||||
&mut blockstream,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(blockstream.entries().len(), 7);
|
||||
|
||||
let (entry_events, block_events): (Vec<Value>, Vec<Value>) = blockstream
|
||||
.entries()
|
||||
.iter()
|
||||
.map(|item| {
|
||||
let json: Value = serde_json::from_str(&item).unwrap();
|
||||
let dt_str = json["dt"].as_str().unwrap();
|
||||
// Ensure `ts` field parses as valid DateTime
|
||||
let _dt: DateTime<FixedOffset> = DateTime::parse_from_rfc3339(dt_str).unwrap();
|
||||
json
|
||||
})
|
||||
.partition(|json| {
|
||||
let item_type = json["t"].as_str().unwrap();
|
||||
item_type == "entry"
|
||||
});
|
||||
for (i, json) in entry_events.iter().enumerate() {
|
||||
let height = json["h"].as_u64().unwrap();
|
||||
assert_eq!(height, expected_tick_heights[i]);
|
||||
let entry_obj = json["entry"].clone();
|
||||
let tx = entry_obj["transactions"].as_array().unwrap();
|
||||
let entry: Entry;
|
||||
if tx.len() == 0 {
|
||||
entry = serde_json::from_value(entry_obj).unwrap();
|
||||
} else {
|
||||
let entry_json = entry_obj.as_object().unwrap();
|
||||
entry = Entry {
|
||||
num_hashes: entry_json.get("num_hashes").unwrap().as_u64().unwrap(),
|
||||
hash: serde_json::from_value(entry_json.get("hash").unwrap().clone()).unwrap(),
|
||||
transactions: entry_json
|
||||
.get("transactions")
|
||||
.unwrap()
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(j, tx)| {
|
||||
let tx_vec: Vec<u8> = serde_json::from_value(tx.clone()).unwrap();
|
||||
// Check explicitly that transaction matches bincode-serialized format
|
||||
assert_eq!(
|
||||
tx_vec,
|
||||
serialize(&expected_entries[i].transactions[j]).unwrap()
|
||||
);
|
||||
deserialize(&tx_vec).unwrap()
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
}
|
||||
assert_eq!(entry, expected_entries[i]);
|
||||
}
|
||||
for json in block_events {
|
||||
let slot = json["s"].as_u64().unwrap();
|
||||
assert_eq!(1, slot);
|
||||
let height = json["h"].as_u64().unwrap();
|
||||
assert_eq!(2 * ticks_per_slot, height);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,51 +1,30 @@
|
||||
//! A stage to broadcast data from a leader node to validators
|
||||
use self::{
|
||||
broadcast_fake_shreds_run::BroadcastFakeShredsRun, broadcast_metrics::*,
|
||||
fail_entry_verification_broadcast_run::FailEntryVerificationBroadcastRun,
|
||||
standard_broadcast_run::StandardBroadcastRun,
|
||||
};
|
||||
use crate::contact_info::ContactInfo;
|
||||
use crate::crds_gossip_pull::CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS;
|
||||
use crate::weighted_shuffle::weighted_best;
|
||||
use crate::{
|
||||
cluster_info::{ClusterInfo, ClusterInfoError},
|
||||
poh_recorder::WorkingBankEntry,
|
||||
result::{Error, Result},
|
||||
};
|
||||
use crossbeam_channel::{
|
||||
Receiver as CrossbeamReceiver, RecvTimeoutError as CrossbeamRecvTimeoutError,
|
||||
Sender as CrossbeamSender,
|
||||
};
|
||||
use solana_ledger::{blockstore::Blockstore, shred::Shred, staking_utils};
|
||||
use solana_measure::measure::Measure;
|
||||
use self::broadcast_fake_shreds_run::BroadcastFakeShredsRun;
|
||||
use self::fail_entry_verification_broadcast_run::FailEntryVerificationBroadcastRun;
|
||||
use self::standard_broadcast_run::StandardBroadcastRun;
|
||||
use crate::cluster_info::{ClusterInfo, ClusterInfoError};
|
||||
use crate::poh_recorder::WorkingBankEntry;
|
||||
use crate::result::{Error, Result};
|
||||
use solana_ledger::blockstore::Blockstore;
|
||||
use solana_ledger::shred::Shred;
|
||||
use solana_ledger::staking_utils;
|
||||
use solana_metrics::{inc_new_counter_error, inc_new_counter_info};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::timing::timestamp;
|
||||
use solana_sdk::{clock::Slot, pubkey::Pubkey};
|
||||
use solana_streamer::sendmmsg::send_mmsg;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::UdpSocket,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
sync::mpsc::{channel, Receiver, RecvError, RecvTimeoutError, Sender},
|
||||
sync::{Arc, Mutex},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::collections::HashMap;
|
||||
use std::net::UdpSocket;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{channel, Receiver, RecvError, RecvTimeoutError, Sender};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread::{self, Builder, JoinHandle};
|
||||
use std::time::Instant;
|
||||
|
||||
pub const NUM_INSERT_THREADS: usize = 2;
|
||||
|
||||
mod broadcast_fake_shreds_run;
|
||||
pub mod broadcast_metrics;
|
||||
pub(crate) mod broadcast_utils;
|
||||
mod fail_entry_verification_broadcast_run;
|
||||
mod standard_broadcast_run;
|
||||
|
||||
pub(crate) const NUM_INSERT_THREADS: usize = 2;
|
||||
pub(crate) type RetransmitSlotsSender = CrossbeamSender<HashMap<Slot, Arc<Bank>>>;
|
||||
pub(crate) type RetransmitSlotsReceiver = CrossbeamReceiver<HashMap<Slot, Arc<Bank>>>;
|
||||
pub(crate) type RecordReceiver = Receiver<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>;
|
||||
pub(crate) type TransmitReceiver = Receiver<(TransmitShreds, Option<BroadcastShredBatchInfo>)>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum BroadcastStageReturnType {
|
||||
ChannelDisconnected,
|
||||
@ -62,20 +41,18 @@ impl BroadcastStageType {
|
||||
pub fn new_broadcast_stage(
|
||||
&self,
|
||||
sock: Vec<UdpSocket>,
|
||||
cluster_info: Arc<ClusterInfo>,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
receiver: Receiver<WorkingBankEntry>,
|
||||
retransmit_slots_receiver: RetransmitSlotsReceiver,
|
||||
exit_sender: &Arc<AtomicBool>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
shred_version: u16,
|
||||
) -> BroadcastStage {
|
||||
let keypair = cluster_info.keypair.clone();
|
||||
let keypair = cluster_info.read().unwrap().keypair.clone();
|
||||
match self {
|
||||
BroadcastStageType::Standard => BroadcastStage::new(
|
||||
sock,
|
||||
cluster_info,
|
||||
receiver,
|
||||
retransmit_slots_receiver,
|
||||
exit_sender,
|
||||
blockstore,
|
||||
StandardBroadcastRun::new(keypair, shred_version),
|
||||
@ -85,7 +62,6 @@ impl BroadcastStageType {
|
||||
sock,
|
||||
cluster_info,
|
||||
receiver,
|
||||
retransmit_slots_receiver,
|
||||
exit_sender,
|
||||
blockstore,
|
||||
FailEntryVerificationBroadcastRun::new(keypair, shred_version),
|
||||
@ -95,7 +71,6 @@ impl BroadcastStageType {
|
||||
sock,
|
||||
cluster_info,
|
||||
receiver,
|
||||
retransmit_slots_receiver,
|
||||
exit_sender,
|
||||
blockstore,
|
||||
BroadcastFakeShredsRun::new(keypair, 0, shred_version),
|
||||
@ -104,24 +79,24 @@ impl BroadcastStageType {
|
||||
}
|
||||
}
|
||||
|
||||
pub type TransmitShreds = (Option<Arc<HashMap<Pubkey, u64>>>, Arc<Vec<Shred>>);
|
||||
type TransmitShreds = (Option<Arc<HashMap<Pubkey, u64>>>, Arc<Vec<Shred>>);
|
||||
trait BroadcastRun {
|
||||
fn run(
|
||||
&mut self,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receiver: &Receiver<WorkingBankEntry>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
socket_sender: &Sender<TransmitShreds>,
|
||||
blockstore_sender: &Sender<Arc<Vec<Shred>>>,
|
||||
) -> Result<()>;
|
||||
fn transmit(
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<TransmitReceiver>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<TransmitShreds>>>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
sock: &UdpSocket,
|
||||
) -> Result<()>;
|
||||
fn record(
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<RecordReceiver>>,
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<Arc<Vec<Shred>>>>>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
@ -153,34 +128,32 @@ impl BroadcastStage {
|
||||
fn run(
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receiver: &Receiver<WorkingBankEntry>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
socket_sender: &Sender<TransmitShreds>,
|
||||
blockstore_sender: &Sender<Arc<Vec<Shred>>>,
|
||||
mut broadcast_stage_run: impl BroadcastRun,
|
||||
) -> BroadcastStageReturnType {
|
||||
loop {
|
||||
let res =
|
||||
broadcast_stage_run.run(blockstore, receiver, socket_sender, blockstore_sender);
|
||||
let res = Self::handle_error(res, "run");
|
||||
let res = Self::handle_error(res);
|
||||
if let Some(res) = res {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn handle_error(r: Result<()>, name: &str) -> Option<BroadcastStageReturnType> {
|
||||
fn handle_error(r: Result<()>) -> Option<BroadcastStageReturnType> {
|
||||
if let Err(e) = r {
|
||||
match e {
|
||||
Error::RecvTimeoutError(RecvTimeoutError::Disconnected)
|
||||
| Error::SendError
|
||||
| Error::RecvError(RecvError)
|
||||
| Error::CrossbeamRecvTimeoutError(CrossbeamRecvTimeoutError::Disconnected) => {
|
||||
| Error::RecvError(RecvError) => {
|
||||
return Some(BroadcastStageReturnType::ChannelDisconnected);
|
||||
}
|
||||
Error::RecvTimeoutError(RecvTimeoutError::Timeout)
|
||||
| Error::CrossbeamRecvTimeoutError(CrossbeamRecvTimeoutError::Timeout) => (),
|
||||
Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (),
|
||||
Error::ClusterInfoError(ClusterInfoError::NoPeers) => (), // TODO: Why are the unit-tests throwing hundreds of these?
|
||||
_ => {
|
||||
inc_new_counter_error!("streamer-broadcaster-error", 1, 1);
|
||||
error!("{} broadcaster error: {:?}", name, e);
|
||||
error!("broadcaster error: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -205,9 +178,8 @@ impl BroadcastStage {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
socks: Vec<UdpSocket>,
|
||||
cluster_info: Arc<ClusterInfo>,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
receiver: Receiver<WorkingBankEntry>,
|
||||
retransmit_slots_receiver: RetransmitSlotsReceiver,
|
||||
exit_sender: &Arc<AtomicBool>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
broadcast_stage_run: impl BroadcastRun + Send + 'static + Clone,
|
||||
@ -217,8 +189,6 @@ impl BroadcastStage {
|
||||
let (socket_sender, socket_receiver) = channel();
|
||||
let (blockstore_sender, blockstore_receiver) = channel();
|
||||
let bs_run = broadcast_stage_run.clone();
|
||||
|
||||
let socket_sender_ = socket_sender.clone();
|
||||
let thread_hdl = Builder::new()
|
||||
.name("solana-broadcaster".to_string())
|
||||
.spawn(move || {
|
||||
@ -226,7 +196,7 @@ impl BroadcastStage {
|
||||
Self::run(
|
||||
&btree,
|
||||
&receiver,
|
||||
&socket_sender_,
|
||||
&socket_sender,
|
||||
&blockstore_sender,
|
||||
bs_run,
|
||||
)
|
||||
@ -236,13 +206,13 @@ impl BroadcastStage {
|
||||
let socket_receiver = Arc::new(Mutex::new(socket_receiver));
|
||||
for sock in socks.into_iter() {
|
||||
let socket_receiver = socket_receiver.clone();
|
||||
let mut bs_transmit = broadcast_stage_run.clone();
|
||||
let bs_transmit = broadcast_stage_run.clone();
|
||||
let cluster_info = cluster_info.clone();
|
||||
let t = Builder::new()
|
||||
.name("solana-broadcaster-transmit".to_string())
|
||||
.spawn(move || loop {
|
||||
let res = bs_transmit.transmit(&socket_receiver, &cluster_info, &sock);
|
||||
let res = Self::handle_error(res, "solana-broadcaster-transmit");
|
||||
let res = Self::handle_error(res);
|
||||
if let Some(res) = res {
|
||||
return res;
|
||||
}
|
||||
@ -253,13 +223,13 @@ impl BroadcastStage {
|
||||
let blockstore_receiver = Arc::new(Mutex::new(blockstore_receiver));
|
||||
for _ in 0..NUM_INSERT_THREADS {
|
||||
let blockstore_receiver = blockstore_receiver.clone();
|
||||
let mut bs_record = broadcast_stage_run.clone();
|
||||
let bs_record = broadcast_stage_run.clone();
|
||||
let btree = blockstore.clone();
|
||||
let t = Builder::new()
|
||||
.name("solana-broadcaster-record".to_string())
|
||||
.spawn(move || loop {
|
||||
let res = bs_record.record(&blockstore_receiver, &btree);
|
||||
let res = Self::handle_error(res, "solana-broadcaster-record");
|
||||
let res = Self::handle_error(res);
|
||||
if let Some(res) = res {
|
||||
return res;
|
||||
}
|
||||
@ -268,68 +238,9 @@ impl BroadcastStage {
|
||||
thread_hdls.push(t);
|
||||
}
|
||||
|
||||
let blockstore = blockstore.clone();
|
||||
let retransmit_thread = Builder::new()
|
||||
.name("solana-broadcaster-retransmit".to_string())
|
||||
.spawn(move || loop {
|
||||
if let Some(res) = Self::handle_error(
|
||||
Self::check_retransmit_signals(
|
||||
&blockstore,
|
||||
&retransmit_slots_receiver,
|
||||
&socket_sender,
|
||||
),
|
||||
"solana-broadcaster-retransmit-check_retransmit_signals",
|
||||
) {
|
||||
return res;
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
thread_hdls.push(retransmit_thread);
|
||||
Self { thread_hdls }
|
||||
}
|
||||
|
||||
fn check_retransmit_signals(
|
||||
blockstore: &Blockstore,
|
||||
retransmit_slots_receiver: &RetransmitSlotsReceiver,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
) -> Result<()> {
|
||||
let timer = Duration::from_millis(100);
|
||||
|
||||
// Check for a retransmit signal
|
||||
let mut retransmit_slots = retransmit_slots_receiver.recv_timeout(timer)?;
|
||||
while let Ok(new_retransmit_slots) = retransmit_slots_receiver.try_recv() {
|
||||
retransmit_slots.extend(new_retransmit_slots);
|
||||
}
|
||||
|
||||
for (_, bank) in retransmit_slots.iter() {
|
||||
let bank_epoch = bank.get_leader_schedule_epoch(bank.slot());
|
||||
let stakes = staking_utils::staked_nodes_at_epoch(&bank, bank_epoch);
|
||||
let stakes = stakes.map(Arc::new);
|
||||
let data_shreds = Arc::new(
|
||||
blockstore
|
||||
.get_data_shreds_for_slot(bank.slot(), 0)
|
||||
.expect("My own shreds must be reconstructable"),
|
||||
);
|
||||
|
||||
if !data_shreds.is_empty() {
|
||||
socket_sender.send(((stakes.clone(), data_shreds), None))?;
|
||||
}
|
||||
|
||||
let coding_shreds = Arc::new(
|
||||
blockstore
|
||||
.get_coding_shreds_for_slot(bank.slot(), 0)
|
||||
.expect("My own shreds must be reconstructable"),
|
||||
);
|
||||
|
||||
if !coding_shreds.is_empty() {
|
||||
socket_sender.send(((stakes.clone(), coding_shreds), None))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn join(self) -> thread::Result<BroadcastStageReturnType> {
|
||||
for thread_hdl in self.thread_hdls.into_iter() {
|
||||
let _ = thread_hdl.join();
|
||||
@ -338,241 +249,23 @@ impl BroadcastStage {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_peer_stats(
|
||||
num_live_peers: i64,
|
||||
broadcast_len: i64,
|
||||
last_datapoint_submit: &Arc<AtomicU64>,
|
||||
) {
|
||||
let now = timestamp();
|
||||
let last = last_datapoint_submit.load(Ordering::Relaxed);
|
||||
if now - last > 1000
|
||||
&& last_datapoint_submit.compare_and_swap(last, now, Ordering::Relaxed) == last
|
||||
{
|
||||
datapoint_info!(
|
||||
"cluster_info-num_nodes",
|
||||
("live_count", num_live_peers, i64),
|
||||
("broadcast_count", broadcast_len, i64)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_broadcast_peers<S: std::hash::BuildHasher>(
|
||||
cluster_info: &ClusterInfo,
|
||||
stakes: Option<Arc<HashMap<Pubkey, u64, S>>>,
|
||||
) -> (Vec<ContactInfo>, Vec<(u64, usize)>) {
|
||||
use crate::cluster_info;
|
||||
let mut peers = cluster_info.tvu_peers();
|
||||
let peers_and_stakes = cluster_info::stake_weight_peers(&mut peers, stakes);
|
||||
(peers, peers_and_stakes)
|
||||
}
|
||||
|
||||
/// broadcast messages from the leader to layer 1 nodes
|
||||
/// # Remarks
|
||||
pub fn broadcast_shreds(
|
||||
s: &UdpSocket,
|
||||
shreds: &Arc<Vec<Shred>>,
|
||||
peers_and_stakes: &[(u64, usize)],
|
||||
peers: &[ContactInfo],
|
||||
last_datapoint_submit: &Arc<AtomicU64>,
|
||||
transmit_stats: &mut TransmitShredsStats,
|
||||
) -> Result<()> {
|
||||
let broadcast_len = peers_and_stakes.len();
|
||||
if broadcast_len == 0 {
|
||||
update_peer_stats(1, 1, last_datapoint_submit);
|
||||
return Ok(());
|
||||
}
|
||||
let mut shred_select = Measure::start("shred_select");
|
||||
let packets: Vec<_> = shreds
|
||||
.iter()
|
||||
.map(|shred| {
|
||||
let broadcast_index = weighted_best(&peers_and_stakes, shred.seed());
|
||||
|
||||
(&shred.payload, &peers[broadcast_index].tvu)
|
||||
})
|
||||
.collect();
|
||||
shred_select.stop();
|
||||
transmit_stats.shred_select += shred_select.as_us();
|
||||
|
||||
let mut sent = 0;
|
||||
let mut send_mmsg_time = Measure::start("send_mmsg");
|
||||
while sent < packets.len() {
|
||||
match send_mmsg(s, &packets[sent..]) {
|
||||
Ok(n) => sent += n,
|
||||
Err(e) => {
|
||||
return Err(Error::IO(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
send_mmsg_time.stop();
|
||||
transmit_stats.send_mmsg_elapsed += send_mmsg_time.as_us();
|
||||
|
||||
let num_live_peers = num_live_peers(&peers);
|
||||
update_peer_stats(
|
||||
num_live_peers,
|
||||
broadcast_len as i64 + 1,
|
||||
last_datapoint_submit,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn distance(a: u64, b: u64) -> u64 {
|
||||
if a > b {
|
||||
a - b
|
||||
} else {
|
||||
b - a
|
||||
}
|
||||
}
|
||||
|
||||
fn num_live_peers(peers: &[ContactInfo]) -> i64 {
|
||||
let mut num_live_peers = 1i64;
|
||||
peers.iter().for_each(|p| {
|
||||
// A peer is considered live if they generated their contact info recently
|
||||
if distance(timestamp(), p.wallclock) <= CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS {
|
||||
num_live_peers += 1;
|
||||
}
|
||||
});
|
||||
num_live_peers
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::cluster_info::{ClusterInfo, Node};
|
||||
use crossbeam_channel::unbounded;
|
||||
use solana_ledger::{
|
||||
blockstore::{make_slot_entries, Blockstore},
|
||||
entry::create_ticks,
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
get_tmp_ledger_path,
|
||||
shred::{max_ticks_per_n_shreds, Shredder, RECOMMENDED_FEC_RATE},
|
||||
};
|
||||
use crate::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_ledger::entry::create_ticks;
|
||||
use solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use std::{
|
||||
path::Path, sync::atomic::AtomicBool, sync::mpsc::channel, sync::Arc, thread::sleep,
|
||||
};
|
||||
|
||||
pub fn make_transmit_shreds(
|
||||
slot: Slot,
|
||||
num: u64,
|
||||
stakes: Option<Arc<HashMap<Pubkey, u64>>>,
|
||||
) -> (
|
||||
Vec<Shred>,
|
||||
Vec<Shred>,
|
||||
Vec<TransmitShreds>,
|
||||
Vec<TransmitShreds>,
|
||||
) {
|
||||
let num_entries = max_ticks_per_n_shreds(num, None);
|
||||
let (data_shreds, _) = make_slot_entries(slot, 0, num_entries);
|
||||
let keypair = Arc::new(Keypair::new());
|
||||
let shredder = Shredder::new(slot, 0, RECOMMENDED_FEC_RATE, keypair, 0, 0)
|
||||
.expect("Expected to create a new shredder");
|
||||
|
||||
let coding_shreds = shredder.data_shreds_to_coding_shreds(&data_shreds[0..]);
|
||||
(
|
||||
data_shreds.clone(),
|
||||
coding_shreds.clone(),
|
||||
data_shreds
|
||||
.into_iter()
|
||||
.map(|s| (stakes.clone(), Arc::new(vec![s])))
|
||||
.collect(),
|
||||
coding_shreds
|
||||
.into_iter()
|
||||
.map(|s| (stakes.clone(), Arc::new(vec![s])))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn check_all_shreds_received(
|
||||
transmit_receiver: &TransmitReceiver,
|
||||
mut data_index: u64,
|
||||
mut coding_index: u64,
|
||||
num_expected_data_shreds: u64,
|
||||
num_expected_coding_shreds: u64,
|
||||
) {
|
||||
while let Ok((new_retransmit_slots, _)) = transmit_receiver.try_recv() {
|
||||
if new_retransmit_slots.1[0].is_data() {
|
||||
for data_shred in new_retransmit_slots.1.iter() {
|
||||
assert_eq!(data_shred.index() as u64, data_index);
|
||||
data_index += 1;
|
||||
}
|
||||
} else {
|
||||
assert_eq!(new_retransmit_slots.1[0].index() as u64, coding_index);
|
||||
for coding_shred in new_retransmit_slots.1.iter() {
|
||||
assert_eq!(coding_shred.index() as u64, coding_index);
|
||||
coding_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(num_expected_data_shreds, data_index);
|
||||
assert_eq!(num_expected_coding_shreds, coding_index);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_live_peers() {
|
||||
let mut ci = ContactInfo::default();
|
||||
ci.wallclock = std::u64::MAX;
|
||||
assert_eq!(num_live_peers(&[ci.clone()]), 1);
|
||||
ci.wallclock = timestamp() - 1;
|
||||
assert_eq!(num_live_peers(&[ci.clone()]), 2);
|
||||
ci.wallclock = timestamp() - CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS - 1;
|
||||
assert_eq!(num_live_peers(&[ci]), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duplicate_retransmit_signal() {
|
||||
// Setup
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||
let (transmit_sender, transmit_receiver) = channel();
|
||||
let (retransmit_slots_sender, retransmit_slots_receiver) = unbounded();
|
||||
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(100_000);
|
||||
let bank0 = Arc::new(Bank::new(&genesis_config));
|
||||
|
||||
// Make some shreds
|
||||
let updated_slot = 0;
|
||||
let (all_data_shreds, all_coding_shreds, _, _all_coding_transmit_shreds) =
|
||||
make_transmit_shreds(updated_slot, 10, None);
|
||||
let num_data_shreds = all_data_shreds.len();
|
||||
let num_coding_shreds = all_coding_shreds.len();
|
||||
assert!(num_data_shreds >= 10);
|
||||
|
||||
// Insert all the shreds
|
||||
blockstore
|
||||
.insert_shreds(all_data_shreds, None, true)
|
||||
.unwrap();
|
||||
blockstore
|
||||
.insert_shreds(all_coding_shreds, None, true)
|
||||
.unwrap();
|
||||
|
||||
// Insert duplicate retransmit signal, blocks should
|
||||
// only be retransmitted once
|
||||
retransmit_slots_sender
|
||||
.send(vec![(updated_slot, bank0.clone())].into_iter().collect())
|
||||
.unwrap();
|
||||
retransmit_slots_sender
|
||||
.send(vec![(updated_slot, bank0.clone())].into_iter().collect())
|
||||
.unwrap();
|
||||
BroadcastStage::check_retransmit_signals(
|
||||
&blockstore,
|
||||
&retransmit_slots_receiver,
|
||||
&transmit_sender,
|
||||
)
|
||||
.unwrap();
|
||||
// Check all the data shreds were received only once
|
||||
check_all_shreds_received(
|
||||
&transmit_receiver,
|
||||
0,
|
||||
0,
|
||||
num_data_shreds as u64,
|
||||
num_coding_shreds as u64,
|
||||
);
|
||||
}
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
struct MockBroadcastStage {
|
||||
blockstore: Arc<Blockstore>,
|
||||
@ -584,7 +277,6 @@ pub mod test {
|
||||
leader_pubkey: &Pubkey,
|
||||
ledger_path: &Path,
|
||||
entry_receiver: Receiver<WorkingBankEntry>,
|
||||
retransmit_slots_receiver: RetransmitSlotsReceiver,
|
||||
) -> MockBroadcastStage {
|
||||
// Make the database ledger
|
||||
let blockstore = Arc::new(Blockstore::open(ledger_path).unwrap());
|
||||
@ -597,22 +289,21 @@ pub mod test {
|
||||
let broadcast_buddy = Node::new_localhost_with_pubkey(&buddy_keypair.pubkey());
|
||||
|
||||
// Fill the cluster_info with the buddy's info
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(leader_info.info.clone());
|
||||
let mut cluster_info = ClusterInfo::new_with_invalid_keypair(leader_info.info.clone());
|
||||
cluster_info.insert_info(broadcast_buddy.info);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
|
||||
let exit_sender = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
|
||||
let bank = Arc::new(Bank::new(&genesis_config));
|
||||
|
||||
let leader_keypair = cluster_info.keypair.clone();
|
||||
let leader_keypair = cluster_info.read().unwrap().keypair.clone();
|
||||
// Start up the broadcast stage
|
||||
let broadcast_service = BroadcastStage::new(
|
||||
leader_info.sockets.broadcast,
|
||||
cluster_info,
|
||||
entry_receiver,
|
||||
retransmit_slots_receiver,
|
||||
&exit_sender,
|
||||
&blockstore,
|
||||
StandardBroadcastRun::new(leader_keypair, 0),
|
||||
@ -635,12 +326,10 @@ pub mod test {
|
||||
let leader_keypair = Keypair::new();
|
||||
|
||||
let (entry_sender, entry_receiver) = channel();
|
||||
let (retransmit_slots_sender, retransmit_slots_receiver) = unbounded();
|
||||
let broadcast_service = setup_dummy_broadcast_service(
|
||||
&leader_keypair.pubkey(),
|
||||
&ledger_path,
|
||||
entry_receiver,
|
||||
retransmit_slots_receiver,
|
||||
);
|
||||
let start_tick_height;
|
||||
let max_tick_height;
|
||||
@ -659,7 +348,6 @@ pub mod test {
|
||||
.expect("Expect successful send to broadcast service");
|
||||
}
|
||||
}
|
||||
|
||||
sleep(Duration::from_millis(2000));
|
||||
|
||||
trace!(
|
||||
@ -676,7 +364,6 @@ pub mod test {
|
||||
assert_eq!(entries.len(), max_tick_height as usize);
|
||||
|
||||
drop(entry_sender);
|
||||
drop(retransmit_slots_sender);
|
||||
broadcast_service
|
||||
.broadcast_service
|
||||
.join()
|
||||
|
@ -28,8 +28,8 @@ impl BroadcastRun for BroadcastFakeShredsRun {
|
||||
&mut self,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receiver: &Receiver<WorkingBankEntry>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
socket_sender: &Sender<TransmitShreds>,
|
||||
blockstore_sender: &Sender<Arc<Vec<Shred>>>,
|
||||
) -> Result<()> {
|
||||
// 1) Pull entries from banking stage
|
||||
let receive_results = broadcast_utils::recv_slot_entries(receiver)?;
|
||||
@ -83,32 +83,26 @@ impl BroadcastRun for BroadcastFakeShredsRun {
|
||||
}
|
||||
|
||||
let data_shreds = Arc::new(data_shreds);
|
||||
blockstore_sender.send((data_shreds.clone(), None))?;
|
||||
blockstore_sender.send(data_shreds.clone())?;
|
||||
|
||||
// 3) Start broadcast step
|
||||
//some indicates fake shreds
|
||||
socket_sender.send((
|
||||
(Some(Arc::new(HashMap::new())), Arc::new(fake_data_shreds)),
|
||||
None,
|
||||
))?;
|
||||
socket_sender.send((
|
||||
(Some(Arc::new(HashMap::new())), Arc::new(fake_coding_shreds)),
|
||||
None,
|
||||
))?;
|
||||
socket_sender.send((Some(Arc::new(HashMap::new())), Arc::new(fake_data_shreds)))?;
|
||||
socket_sender.send((Some(Arc::new(HashMap::new())), Arc::new(fake_coding_shreds)))?;
|
||||
//none indicates real shreds
|
||||
socket_sender.send(((None, data_shreds), None))?;
|
||||
socket_sender.send(((None, Arc::new(coding_shreds)), None))?;
|
||||
socket_sender.send((None, data_shreds))?;
|
||||
socket_sender.send((None, Arc::new(coding_shreds)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn transmit(
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<TransmitReceiver>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<TransmitShreds>>>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
sock: &UdpSocket,
|
||||
) -> Result<()> {
|
||||
for ((stakes, data_shreds), _) in receiver.lock().unwrap().iter() {
|
||||
let peers = cluster_info.tvu_peers();
|
||||
for (stakes, data_shreds) in receiver.lock().unwrap().iter() {
|
||||
let peers = cluster_info.read().unwrap().tvu_peers();
|
||||
peers.iter().enumerate().for_each(|(i, peer)| {
|
||||
if i <= self.partition && stakes.is_some() {
|
||||
// Send fake shreds to the first N peers
|
||||
@ -125,11 +119,11 @@ impl BroadcastRun for BroadcastFakeShredsRun {
|
||||
Ok(())
|
||||
}
|
||||
fn record(
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<RecordReceiver>>,
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<Arc<Vec<Shred>>>>>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
) -> Result<()> {
|
||||
for (data_shreds, _) in receiver.lock().unwrap().iter() {
|
||||
for data_shreds in receiver.lock().unwrap().iter() {
|
||||
blockstore.insert_shreds(data_shreds.to_vec(), None, true)?;
|
||||
}
|
||||
Ok(())
|
||||
@ -145,7 +139,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tvu_peers_ordering() {
|
||||
let cluster = ClusterInfo::new_with_invalid_keypair(ContactInfo::new_localhost(
|
||||
let mut cluster = ClusterInfo::new_with_invalid_keypair(ContactInfo::new_localhost(
|
||||
&Pubkey::new_rand(),
|
||||
0,
|
||||
));
|
||||
|
@ -1,296 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
pub(crate) trait BroadcastStats {
|
||||
fn update(&mut self, new_stats: &Self);
|
||||
fn report_stats(&mut self, slot: Slot, slot_start: Instant);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct BroadcastShredBatchInfo {
|
||||
pub(crate) slot: Slot,
|
||||
pub(crate) num_expected_batches: Option<usize>,
|
||||
pub(crate) slot_start_ts: Instant,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct ProcessShredsStats {
|
||||
// Per-slot elapsed time
|
||||
pub(crate) shredding_elapsed: u64,
|
||||
pub(crate) receive_elapsed: u64,
|
||||
}
|
||||
impl ProcessShredsStats {
|
||||
pub(crate) fn update(&mut self, new_stats: &ProcessShredsStats) {
|
||||
self.shredding_elapsed += new_stats.shredding_elapsed;
|
||||
self.receive_elapsed += new_stats.receive_elapsed;
|
||||
}
|
||||
pub(crate) fn reset(&mut self) {
|
||||
*self = Self::default();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct TransmitShredsStats {
|
||||
pub transmit_elapsed: u64,
|
||||
pub send_mmsg_elapsed: u64,
|
||||
pub get_peers_elapsed: u64,
|
||||
pub shred_select: u64,
|
||||
pub num_shreds: usize,
|
||||
}
|
||||
|
||||
impl BroadcastStats for TransmitShredsStats {
|
||||
fn update(&mut self, new_stats: &TransmitShredsStats) {
|
||||
self.transmit_elapsed += new_stats.transmit_elapsed;
|
||||
self.send_mmsg_elapsed += new_stats.send_mmsg_elapsed;
|
||||
self.get_peers_elapsed += new_stats.get_peers_elapsed;
|
||||
self.num_shreds += new_stats.num_shreds;
|
||||
self.shred_select += new_stats.shred_select;
|
||||
}
|
||||
fn report_stats(&mut self, slot: Slot, slot_start: Instant) {
|
||||
datapoint_info!(
|
||||
"broadcast-transmit-shreds-stats",
|
||||
("slot", slot as i64, i64),
|
||||
(
|
||||
"end_to_end_elapsed",
|
||||
// `slot_start` signals when the first batch of shreds was
|
||||
// received, used to measure duration of broadcast
|
||||
slot_start.elapsed().as_micros() as i64,
|
||||
i64
|
||||
),
|
||||
("transmit_elapsed", self.transmit_elapsed as i64, i64),
|
||||
("send_mmsg_elapsed", self.send_mmsg_elapsed as i64, i64),
|
||||
("get_peers_elapsed", self.get_peers_elapsed as i64, i64),
|
||||
("num_shreds", self.num_shreds as i64, i64),
|
||||
("shred_select", self.shred_select as i64, i64),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct InsertShredsStats {
|
||||
pub(crate) insert_shreds_elapsed: u64,
|
||||
pub(crate) num_shreds: usize,
|
||||
}
|
||||
impl BroadcastStats for InsertShredsStats {
|
||||
fn update(&mut self, new_stats: &InsertShredsStats) {
|
||||
self.insert_shreds_elapsed += new_stats.insert_shreds_elapsed;
|
||||
self.num_shreds += new_stats.num_shreds;
|
||||
}
|
||||
fn report_stats(&mut self, slot: Slot, slot_start: Instant) {
|
||||
datapoint_info!(
|
||||
"broadcast-insert-shreds-stats",
|
||||
("slot", slot as i64, i64),
|
||||
(
|
||||
"end_to_end_elapsed",
|
||||
// `slot_start` signals when the first batch of shreds was
|
||||
// received, used to measure duration of broadcast
|
||||
slot_start.elapsed().as_micros() as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"insert_shreds_elapsed",
|
||||
self.insert_shreds_elapsed as i64,
|
||||
i64
|
||||
),
|
||||
("num_shreds", self.num_shreds as i64, i64),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Tracks metrics of type `T` acrosss multiple threads
|
||||
#[derive(Default)]
|
||||
pub(crate) struct BatchCounter<T: BroadcastStats + Default> {
|
||||
// The number of batches processed across all threads so far
|
||||
num_batches: usize,
|
||||
// Filled in when the last batch of shreds is received,
|
||||
// signals how many batches of shreds to expect
|
||||
num_expected_batches: Option<usize>,
|
||||
broadcast_shred_stats: T,
|
||||
}
|
||||
|
||||
impl<T: BroadcastStats + Default> BatchCounter<T> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn num_batches(&self) -> usize {
|
||||
self.num_batches
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct SlotBroadcastStats<T: BroadcastStats + Default>(HashMap<Slot, BatchCounter<T>>);
|
||||
|
||||
impl<T: BroadcastStats + Default> SlotBroadcastStats<T> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn get(&self, slot: Slot) -> Option<&BatchCounter<T>> {
|
||||
self.0.get(&slot)
|
||||
}
|
||||
pub(crate) fn update(&mut self, new_stats: &T, batch_info: &Option<BroadcastShredBatchInfo>) {
|
||||
if let Some(batch_info) = batch_info {
|
||||
let mut should_delete = false;
|
||||
{
|
||||
let slot_batch_counter = self.0.entry(batch_info.slot).or_default();
|
||||
slot_batch_counter.broadcast_shred_stats.update(new_stats);
|
||||
// Only count the ones where `broadcast_shred_batch_info`.is_some(), because
|
||||
// there could potentially be other `retransmit` slots inserted into the
|
||||
// transmit pipeline (signaled by ReplayStage) that are not created by the
|
||||
// main shredding/broadcast pipeline
|
||||
slot_batch_counter.num_batches += 1;
|
||||
if let Some(num_expected_batches) = batch_info.num_expected_batches {
|
||||
slot_batch_counter.num_expected_batches = Some(num_expected_batches);
|
||||
}
|
||||
if let Some(num_expected_batches) = slot_batch_counter.num_expected_batches {
|
||||
if slot_batch_counter.num_batches == num_expected_batches {
|
||||
slot_batch_counter
|
||||
.broadcast_shred_stats
|
||||
.report_stats(batch_info.slot, batch_info.slot_start_ts);
|
||||
should_delete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if should_delete {
|
||||
self.0
|
||||
.remove(&batch_info.slot)
|
||||
.expect("delete should be successful");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[derive(Default)]
|
||||
struct TestStats {
|
||||
sender: Option<Sender<(usize, Slot, Instant)>>,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl BroadcastStats for TestStats {
|
||||
fn update(&mut self, new_stats: &TestStats) {
|
||||
self.count += new_stats.count;
|
||||
self.sender = new_stats.sender.clone();
|
||||
}
|
||||
fn report_stats(&mut self, slot: Slot, slot_start: Instant) {
|
||||
self.sender
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send((self.count, slot, slot_start))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_broadcast() {
|
||||
let start = Instant::now();
|
||||
let mut slot_broadcast_stats = SlotBroadcastStats::default();
|
||||
slot_broadcast_stats.update(
|
||||
&TransmitShredsStats {
|
||||
transmit_elapsed: 1,
|
||||
get_peers_elapsed: 2,
|
||||
send_mmsg_elapsed: 3,
|
||||
shred_select: 4,
|
||||
num_shreds: 5,
|
||||
},
|
||||
&Some(BroadcastShredBatchInfo {
|
||||
slot: 0,
|
||||
num_expected_batches: Some(2),
|
||||
slot_start_ts: start.clone(),
|
||||
}),
|
||||
);
|
||||
|
||||
// Singular update
|
||||
let slot_0_stats = slot_broadcast_stats.0.get(&0).unwrap();
|
||||
assert_eq!(slot_0_stats.num_batches, 1);
|
||||
assert_eq!(slot_0_stats.num_expected_batches.unwrap(), 2);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.transmit_elapsed, 1);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.get_peers_elapsed, 2);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.send_mmsg_elapsed, 3);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.shred_select, 4);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.num_shreds, 5);
|
||||
|
||||
slot_broadcast_stats.update(
|
||||
&TransmitShredsStats {
|
||||
transmit_elapsed: 7,
|
||||
get_peers_elapsed: 8,
|
||||
send_mmsg_elapsed: 9,
|
||||
shred_select: 10,
|
||||
num_shreds: 11,
|
||||
},
|
||||
&None,
|
||||
);
|
||||
|
||||
// If BroadcastShredBatchInfo == None, then update should be ignored
|
||||
let slot_0_stats = slot_broadcast_stats.0.get(&0).unwrap();
|
||||
assert_eq!(slot_0_stats.num_batches, 1);
|
||||
assert_eq!(slot_0_stats.num_expected_batches.unwrap(), 2);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.transmit_elapsed, 1);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.get_peers_elapsed, 2);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.send_mmsg_elapsed, 3);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.shred_select, 4);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.num_shreds, 5);
|
||||
|
||||
// If another batch is given, then total number of batches == num_expected_batches == 2,
|
||||
// so the batch should be purged from the HashMap
|
||||
slot_broadcast_stats.update(
|
||||
&TransmitShredsStats {
|
||||
transmit_elapsed: 1,
|
||||
get_peers_elapsed: 1,
|
||||
send_mmsg_elapsed: 1,
|
||||
shred_select: 1,
|
||||
num_shreds: 1,
|
||||
},
|
||||
&Some(BroadcastShredBatchInfo {
|
||||
slot: 0,
|
||||
num_expected_batches: None,
|
||||
slot_start_ts: start.clone(),
|
||||
}),
|
||||
);
|
||||
|
||||
assert!(slot_broadcast_stats.0.get(&0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_multi_threaded() {
|
||||
for round in 0..50 {
|
||||
let start = Instant::now();
|
||||
let slot_broadcast_stats = Arc::new(Mutex::new(SlotBroadcastStats::default()));
|
||||
let num_threads = 5;
|
||||
let slot = 0;
|
||||
let (sender, receiver) = channel();
|
||||
let thread_handles: Vec<_> = (0..num_threads)
|
||||
.into_iter()
|
||||
.map(|i| {
|
||||
let slot_broadcast_stats = slot_broadcast_stats.clone();
|
||||
let sender = Some(sender.clone());
|
||||
let test_stats = TestStats { sender, count: 1 };
|
||||
let mut broadcast_batch_info = BroadcastShredBatchInfo {
|
||||
slot,
|
||||
num_expected_batches: None,
|
||||
slot_start_ts: start.clone(),
|
||||
};
|
||||
if i == round % num_threads {
|
||||
broadcast_batch_info.num_expected_batches = Some(num_threads);
|
||||
}
|
||||
Builder::new()
|
||||
.name("test_update_multi_threaded".to_string())
|
||||
.spawn(move || {
|
||||
slot_broadcast_stats
|
||||
.lock()
|
||||
.unwrap()
|
||||
.update(&test_stats, &Some(broadcast_batch_info))
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
for t in thread_handles {
|
||||
t.join().unwrap();
|
||||
}
|
||||
|
||||
assert!(slot_broadcast_stats.lock().unwrap().0.get(&slot).is_none());
|
||||
let (returned_count, returned_slot, returned_instant) = receiver.recv().unwrap();
|
||||
assert_eq!(returned_count, num_threads);
|
||||
assert_eq!(returned_slot, slot);
|
||||
assert_eq!(returned_instant, returned_instant);
|
||||
}
|
||||
}
|
||||
}
|
@ -76,7 +76,7 @@ pub(super) fn recv_slot_entries(receiver: &Receiver<WorkingBankEntry>) -> Result
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_ledger::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use crate::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_sdk::genesis_config::GenesisConfig;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::system_transaction;
|
||||
|
@ -23,8 +23,8 @@ impl BroadcastRun for FailEntryVerificationBroadcastRun {
|
||||
&mut self,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receiver: &Receiver<WorkingBankEntry>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
socket_sender: &Sender<TransmitShreds>,
|
||||
blockstore_sender: &Sender<Arc<Vec<Shred>>>,
|
||||
) -> Result<()> {
|
||||
// 1) Pull entries from banking stage
|
||||
let mut receive_results = broadcast_utils::recv_slot_entries(receiver)?;
|
||||
@ -61,43 +61,38 @@ impl BroadcastRun for FailEntryVerificationBroadcastRun {
|
||||
);
|
||||
|
||||
let data_shreds = Arc::new(data_shreds);
|
||||
blockstore_sender.send((data_shreds.clone(), None))?;
|
||||
blockstore_sender.send(data_shreds.clone())?;
|
||||
// 3) Start broadcast step
|
||||
let bank_epoch = bank.get_leader_schedule_epoch(bank.slot());
|
||||
let stakes = staking_utils::staked_nodes_at_epoch(&bank, bank_epoch);
|
||||
|
||||
let stakes = stakes.map(Arc::new);
|
||||
socket_sender.send(((stakes.clone(), data_shreds), None))?;
|
||||
socket_sender.send(((stakes, Arc::new(coding_shreds)), None))?;
|
||||
socket_sender.send((stakes.clone(), data_shreds))?;
|
||||
socket_sender.send((stakes, Arc::new(coding_shreds)))?;
|
||||
Ok(())
|
||||
}
|
||||
fn transmit(
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<TransmitReceiver>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<TransmitShreds>>>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
sock: &UdpSocket,
|
||||
) -> Result<()> {
|
||||
let ((stakes, shreds), _) = receiver.lock().unwrap().recv()?;
|
||||
let (stakes, shreds) = receiver.lock().unwrap().recv()?;
|
||||
let all_seeds: Vec<[u8; 32]> = shreds.iter().map(|s| s.seed()).collect();
|
||||
// Broadcast data
|
||||
let (peers, peers_and_stakes) = get_broadcast_peers(cluster_info, stakes);
|
||||
|
||||
broadcast_shreds(
|
||||
sock,
|
||||
&shreds,
|
||||
&peers_and_stakes,
|
||||
&peers,
|
||||
&Arc::new(AtomicU64::new(0)),
|
||||
&mut TransmitShredsStats::default(),
|
||||
)?;
|
||||
|
||||
let all_shred_bufs: Vec<Vec<u8>> = shreds.to_vec().into_iter().map(|s| s.payload).collect();
|
||||
cluster_info
|
||||
.write()
|
||||
.unwrap()
|
||||
.broadcast_shreds(sock, all_shred_bufs, &all_seeds, stakes)?;
|
||||
Ok(())
|
||||
}
|
||||
fn record(
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<RecordReceiver>>,
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<Arc<Vec<Shred>>>>>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
) -> Result<()> {
|
||||
let (all_shreds, _) = receiver.lock().unwrap().recv()?;
|
||||
let all_shreds = receiver.lock().unwrap().recv()?;
|
||||
blockstore
|
||||
.insert_shreds(all_shreds.to_vec(), None, true)
|
||||
.expect("Failed to insert shreds in blockstore");
|
||||
|
@ -1,54 +1,53 @@
|
||||
use super::{
|
||||
broadcast_utils::{self, ReceiveResults},
|
||||
*,
|
||||
};
|
||||
use super::broadcast_utils::{self, ReceiveResults};
|
||||
use super::*;
|
||||
use crate::broadcast_stage::broadcast_utils::UnfinishedSlotInfo;
|
||||
use solana_ledger::{
|
||||
entry::Entry,
|
||||
shred::{Shred, Shredder, RECOMMENDED_FEC_RATE, SHRED_TICK_REFERENCE_MASK},
|
||||
};
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Keypair, timing::duration_as_us};
|
||||
use solana_ledger::entry::Entry;
|
||||
use solana_ledger::shred::{Shred, Shredder, RECOMMENDED_FEC_RATE, SHRED_TICK_REFERENCE_MASK};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::Keypair;
|
||||
use solana_sdk::timing::duration_as_us;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Default)]
|
||||
struct BroadcastStats {
|
||||
// Per-slot elapsed time
|
||||
shredding_elapsed: u64,
|
||||
insert_shreds_elapsed: u64,
|
||||
broadcast_elapsed: u64,
|
||||
receive_elapsed: u64,
|
||||
seed_elapsed: u64,
|
||||
}
|
||||
|
||||
impl BroadcastStats {
|
||||
fn reset(&mut self) {
|
||||
self.insert_shreds_elapsed = 0;
|
||||
self.shredding_elapsed = 0;
|
||||
self.broadcast_elapsed = 0;
|
||||
self.receive_elapsed = 0;
|
||||
self.seed_elapsed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StandardBroadcastRun {
|
||||
process_shreds_stats: ProcessShredsStats,
|
||||
transmit_shreds_stats: Arc<Mutex<SlotBroadcastStats<TransmitShredsStats>>>,
|
||||
insert_shreds_stats: Arc<Mutex<SlotBroadcastStats<InsertShredsStats>>>,
|
||||
pub(super) struct StandardBroadcastRun {
|
||||
stats: Arc<RwLock<BroadcastStats>>,
|
||||
unfinished_slot: Option<UnfinishedSlotInfo>,
|
||||
current_slot_and_parent: Option<(u64, u64)>,
|
||||
slot_broadcast_start: Option<Instant>,
|
||||
keypair: Arc<Keypair>,
|
||||
shred_version: u16,
|
||||
last_datapoint_submit: Arc<AtomicU64>,
|
||||
num_batches: usize,
|
||||
broadcast_peer_cache: Arc<RwLock<BroadcastPeerCache>>,
|
||||
last_peer_update: Arc<AtomicU64>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct BroadcastPeerCache {
|
||||
peers: Vec<ContactInfo>,
|
||||
peers_and_stakes: Vec<(u64, usize)>,
|
||||
}
|
||||
|
||||
impl StandardBroadcastRun {
|
||||
pub(super) fn new(keypair: Arc<Keypair>, shred_version: u16) -> Self {
|
||||
Self {
|
||||
process_shreds_stats: ProcessShredsStats::default(),
|
||||
transmit_shreds_stats: Arc::new(Mutex::new(SlotBroadcastStats::default())),
|
||||
insert_shreds_stats: Arc::new(Mutex::new(SlotBroadcastStats::default())),
|
||||
stats: Arc::new(RwLock::new(BroadcastStats::default())),
|
||||
unfinished_slot: None,
|
||||
current_slot_and_parent: None,
|
||||
slot_broadcast_start: None,
|
||||
keypair,
|
||||
shred_version,
|
||||
last_datapoint_submit: Arc::new(AtomicU64::new(0)),
|
||||
num_batches: 0,
|
||||
broadcast_peer_cache: Arc::new(RwLock::new(BroadcastPeerCache::default())),
|
||||
last_peer_update: Arc::new(AtomicU64::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,7 +130,7 @@ impl StandardBroadcastRun {
|
||||
#[cfg(test)]
|
||||
fn test_process_receive_results(
|
||||
&mut self,
|
||||
cluster_info: &ClusterInfo,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
sock: &UdpSocket,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receive_results: ReceiveResults,
|
||||
@ -143,7 +142,6 @@ impl StandardBroadcastRun {
|
||||
let brecv = Arc::new(Mutex::new(brecv));
|
||||
//data
|
||||
let _ = self.transmit(&srecv, cluster_info, sock);
|
||||
let _ = self.record(&brecv, blockstore);
|
||||
//coding
|
||||
let _ = self.transmit(&srecv, cluster_info, sock);
|
||||
let _ = self.record(&brecv, blockstore);
|
||||
@ -153,8 +151,8 @@ impl StandardBroadcastRun {
|
||||
fn process_receive_results(
|
||||
&mut self,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
socket_sender: &Sender<TransmitShreds>,
|
||||
blockstore_sender: &Sender<Arc<Vec<Shred>>>,
|
||||
receive_results: ReceiveResults,
|
||||
) -> Result<()> {
|
||||
let mut receive_elapsed = receive_results.time_elapsed;
|
||||
@ -162,13 +160,11 @@ impl StandardBroadcastRun {
|
||||
let bank = receive_results.bank.clone();
|
||||
let last_tick_height = receive_results.last_tick_height;
|
||||
inc_new_counter_info!("broadcast_service-entries_received", num_entries);
|
||||
let old_broadcast_start = self.slot_broadcast_start;
|
||||
let old_num_batches = self.num_batches;
|
||||
|
||||
if self.current_slot_and_parent.is_none()
|
||||
|| bank.slot() != self.current_slot_and_parent.unwrap().0
|
||||
{
|
||||
self.slot_broadcast_start = Some(Instant::now());
|
||||
self.num_batches = 0;
|
||||
let slot = bank.slot();
|
||||
let parent_slot = bank.parent_slot();
|
||||
|
||||
@ -183,19 +179,19 @@ impl StandardBroadcastRun {
|
||||
self.check_for_interrupted_slot(bank.ticks_per_slot() as u8);
|
||||
|
||||
// 2) Convert entries to shreds and coding shreds
|
||||
|
||||
let (shredder, next_shred_index) = self.init_shredder(
|
||||
blockstore,
|
||||
(bank.tick_height() % bank.ticks_per_slot()) as u8,
|
||||
);
|
||||
let is_last_in_slot = last_tick_height == bank.max_tick_height();
|
||||
let data_shreds = self.entries_to_data_shreds(
|
||||
let mut data_shreds = self.entries_to_data_shreds(
|
||||
&shredder,
|
||||
next_shred_index,
|
||||
&receive_results.entries,
|
||||
is_last_in_slot,
|
||||
last_tick_height == bank.max_tick_height(),
|
||||
);
|
||||
// Insert the first shred so blockstore stores that the leader started this block
|
||||
// This must be done before the blocks are sent out over the wire.
|
||||
//Insert the first shred so blockstore stores that the leader started this block
|
||||
//This must be done before the blocks are sent out over the wire.
|
||||
if !data_shreds.is_empty() && data_shreds[0].index() == 0 {
|
||||
let first = vec![data_shreds[0].clone()];
|
||||
blockstore
|
||||
@ -203,56 +199,26 @@ impl StandardBroadcastRun {
|
||||
.expect("Failed to insert shreds in blockstore");
|
||||
}
|
||||
let last_data_shred = data_shreds.len();
|
||||
if let Some(last_shred) = last_unfinished_slot_shred {
|
||||
data_shreds.push(last_shred);
|
||||
}
|
||||
let to_shreds_elapsed = to_shreds_start.elapsed();
|
||||
|
||||
let bank_epoch = bank.get_leader_schedule_epoch(bank.slot());
|
||||
let stakes = staking_utils::staked_nodes_at_epoch(&bank, bank_epoch);
|
||||
let stakes = stakes.map(Arc::new);
|
||||
|
||||
// Broadcast the last shred of the interrupted slot if necessary
|
||||
if let Some(last_shred) = last_unfinished_slot_shred {
|
||||
let batch_info = Some(BroadcastShredBatchInfo {
|
||||
slot: last_shred.slot(),
|
||||
num_expected_batches: Some(old_num_batches + 1),
|
||||
slot_start_ts: old_broadcast_start.expect(
|
||||
"Old broadcast start time for previous slot must exist if the previous slot
|
||||
was interrupted",
|
||||
),
|
||||
});
|
||||
let last_shred = Arc::new(vec![last_shred]);
|
||||
socket_sender.send(((stakes.clone(), last_shred.clone()), batch_info.clone()))?;
|
||||
blockstore_sender.send((last_shred, batch_info))?;
|
||||
}
|
||||
|
||||
// Increment by two batches, one for the data batch, one for the coding batch.
|
||||
self.num_batches += 2;
|
||||
let num_expected_batches = {
|
||||
if is_last_in_slot {
|
||||
Some(self.num_batches)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let batch_info = Some(BroadcastShredBatchInfo {
|
||||
slot: bank.slot(),
|
||||
num_expected_batches,
|
||||
slot_start_ts: self
|
||||
.slot_broadcast_start
|
||||
.clone()
|
||||
.expect("Start timestamp must exist for a slot if we're broadcasting the slot"),
|
||||
});
|
||||
|
||||
let data_shreds = Arc::new(data_shreds);
|
||||
socket_sender.send(((stakes.clone(), data_shreds.clone()), batch_info.clone()))?;
|
||||
blockstore_sender.send((data_shreds.clone(), batch_info.clone()))?;
|
||||
socket_sender.send((stakes.clone(), data_shreds.clone()))?;
|
||||
blockstore_sender.send(data_shreds.clone())?;
|
||||
let coding_shreds = shredder.data_shreds_to_coding_shreds(&data_shreds[0..last_data_shred]);
|
||||
let coding_shreds = Arc::new(coding_shreds);
|
||||
socket_sender.send(((stakes, coding_shreds.clone()), batch_info.clone()))?;
|
||||
blockstore_sender.send((coding_shreds, batch_info))?;
|
||||
self.process_shreds_stats.update(&ProcessShredsStats {
|
||||
socket_sender.send((stakes, coding_shreds))?;
|
||||
self.update_broadcast_stats(BroadcastStats {
|
||||
shredding_elapsed: duration_as_us(&to_shreds_elapsed),
|
||||
receive_elapsed: duration_as_us(&receive_elapsed),
|
||||
..BroadcastStats::default()
|
||||
});
|
||||
|
||||
if last_tick_height == bank.max_tick_height() {
|
||||
self.report_and_reset_stats();
|
||||
self.unfinished_slot = None;
|
||||
@ -261,15 +227,10 @@ impl StandardBroadcastRun {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert(
|
||||
&mut self,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
shreds: Arc<Vec<Shred>>,
|
||||
broadcast_shred_batch_info: Option<BroadcastShredBatchInfo>,
|
||||
) -> Result<()> {
|
||||
fn insert(&self, blockstore: &Arc<Blockstore>, shreds: Arc<Vec<Shred>>) -> Result<()> {
|
||||
// Insert shreds into blockstore
|
||||
let insert_shreds_start = Instant::now();
|
||||
// The first shred is inserted synchronously
|
||||
//The first shred is inserted synchronously
|
||||
let data_shreds = if !shreds.is_empty() && shreds[0].index() == 0 {
|
||||
shreds[1..].to_vec()
|
||||
} else {
|
||||
@ -279,93 +240,66 @@ impl StandardBroadcastRun {
|
||||
.insert_shreds(data_shreds, None, true)
|
||||
.expect("Failed to insert shreds in blockstore");
|
||||
let insert_shreds_elapsed = insert_shreds_start.elapsed();
|
||||
let new_insert_shreds_stats = InsertShredsStats {
|
||||
self.update_broadcast_stats(BroadcastStats {
|
||||
insert_shreds_elapsed: duration_as_us(&insert_shreds_elapsed),
|
||||
num_shreds: shreds.len(),
|
||||
};
|
||||
self.update_insertion_metrics(&new_insert_shreds_stats, &broadcast_shred_batch_info);
|
||||
..BroadcastStats::default()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_insertion_metrics(
|
||||
&mut self,
|
||||
new_insertion_shreds_stats: &InsertShredsStats,
|
||||
broadcast_shred_batch_info: &Option<BroadcastShredBatchInfo>,
|
||||
) {
|
||||
let mut insert_shreds_stats = self.insert_shreds_stats.lock().unwrap();
|
||||
insert_shreds_stats.update(new_insertion_shreds_stats, broadcast_shred_batch_info);
|
||||
}
|
||||
|
||||
fn broadcast(
|
||||
&mut self,
|
||||
&self,
|
||||
sock: &UdpSocket,
|
||||
cluster_info: &ClusterInfo,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
stakes: Option<Arc<HashMap<Pubkey, u64>>>,
|
||||
shreds: Arc<Vec<Shred>>,
|
||||
broadcast_shred_batch_info: Option<BroadcastShredBatchInfo>,
|
||||
) -> Result<()> {
|
||||
const BROADCAST_PEER_UPDATE_INTERVAL_MS: u64 = 1000;
|
||||
trace!("Broadcasting {:?} shreds", shreds.len());
|
||||
// Get the list of peers to broadcast to
|
||||
let mut get_peers_time = Measure::start("broadcast::get_peers");
|
||||
let now = timestamp();
|
||||
let last = self.last_peer_update.load(Ordering::Relaxed);
|
||||
if now - last > BROADCAST_PEER_UPDATE_INTERVAL_MS
|
||||
&& self
|
||||
.last_peer_update
|
||||
.compare_and_swap(now, last, Ordering::Relaxed)
|
||||
== last
|
||||
{
|
||||
let mut w_broadcast_peer_cache = self.broadcast_peer_cache.write().unwrap();
|
||||
let (peers, peers_and_stakes) = get_broadcast_peers(cluster_info, stakes);
|
||||
w_broadcast_peer_cache.peers = peers;
|
||||
w_broadcast_peer_cache.peers_and_stakes = peers_and_stakes;
|
||||
}
|
||||
get_peers_time.stop();
|
||||
let r_broadcast_peer_cache = self.broadcast_peer_cache.read().unwrap();
|
||||
let seed_start = Instant::now();
|
||||
let seeds: Vec<[u8; 32]> = shreds.iter().map(|s| s.seed()).collect();
|
||||
let seed_elapsed = seed_start.elapsed();
|
||||
|
||||
let mut transmit_stats = TransmitShredsStats::default();
|
||||
// Broadcast the shreds
|
||||
let mut transmit_time = Measure::start("broadcast_shreds");
|
||||
broadcast_shreds(
|
||||
sock,
|
||||
&shreds,
|
||||
&r_broadcast_peer_cache.peers_and_stakes,
|
||||
&r_broadcast_peer_cache.peers,
|
||||
&self.last_datapoint_submit,
|
||||
&mut transmit_stats,
|
||||
)?;
|
||||
drop(r_broadcast_peer_cache);
|
||||
transmit_time.stop();
|
||||
let broadcast_start = Instant::now();
|
||||
let shred_bufs: Vec<Vec<u8>> = shreds.to_vec().into_iter().map(|s| s.payload).collect();
|
||||
trace!("Broadcasting {:?} shreds", shred_bufs.len());
|
||||
|
||||
transmit_stats.transmit_elapsed = transmit_time.as_us();
|
||||
transmit_stats.get_peers_elapsed = get_peers_time.as_us();
|
||||
transmit_stats.num_shreds = shreds.len();
|
||||
cluster_info
|
||||
.write()
|
||||
.unwrap()
|
||||
.broadcast_shreds(sock, shred_bufs, &seeds, stakes)?;
|
||||
|
||||
// Process metrics
|
||||
self.update_transmit_metrics(&transmit_stats, &broadcast_shred_batch_info);
|
||||
let broadcast_elapsed = broadcast_start.elapsed();
|
||||
|
||||
self.update_broadcast_stats(BroadcastStats {
|
||||
broadcast_elapsed: duration_as_us(&broadcast_elapsed),
|
||||
seed_elapsed: duration_as_us(&seed_elapsed),
|
||||
..BroadcastStats::default()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_transmit_metrics(
|
||||
&mut self,
|
||||
new_transmit_shreds_stats: &TransmitShredsStats,
|
||||
broadcast_shred_batch_info: &Option<BroadcastShredBatchInfo>,
|
||||
) {
|
||||
let mut transmit_shreds_stats = self.transmit_shreds_stats.lock().unwrap();
|
||||
transmit_shreds_stats.update(new_transmit_shreds_stats, broadcast_shred_batch_info);
|
||||
fn update_broadcast_stats(&self, stats: BroadcastStats) {
|
||||
let mut wstats = self.stats.write().unwrap();
|
||||
wstats.receive_elapsed += stats.receive_elapsed;
|
||||
wstats.shredding_elapsed += stats.shredding_elapsed;
|
||||
wstats.insert_shreds_elapsed += stats.insert_shreds_elapsed;
|
||||
wstats.broadcast_elapsed += stats.broadcast_elapsed;
|
||||
wstats.seed_elapsed += stats.seed_elapsed;
|
||||
}
|
||||
|
||||
fn report_and_reset_stats(&mut self) {
|
||||
let stats = &self.process_shreds_stats;
|
||||
let stats = self.stats.read().unwrap();
|
||||
assert!(self.unfinished_slot.is_some());
|
||||
datapoint_info!(
|
||||
"broadcast-process-shreds-stats",
|
||||
"broadcast-bank-stats",
|
||||
("slot", self.unfinished_slot.unwrap().slot as i64, i64),
|
||||
("shredding_time", stats.shredding_elapsed as i64, i64),
|
||||
("insertion_time", stats.insert_shreds_elapsed as i64, i64),
|
||||
("broadcast_time", stats.broadcast_elapsed as i64, i64),
|
||||
("receive_time", stats.receive_elapsed as i64, i64),
|
||||
("seed", stats.seed_elapsed as i64, i64),
|
||||
(
|
||||
"num_data_shreds",
|
||||
"num_shreds",
|
||||
i64::from(self.unfinished_slot.unwrap().next_shred_index),
|
||||
i64
|
||||
),
|
||||
@ -375,7 +309,8 @@ impl StandardBroadcastRun {
|
||||
i64
|
||||
),
|
||||
);
|
||||
self.process_shreds_stats.reset();
|
||||
drop(stats);
|
||||
self.stats.write().unwrap().reset();
|
||||
}
|
||||
}
|
||||
|
||||
@ -384,8 +319,8 @@ impl BroadcastRun for StandardBroadcastRun {
|
||||
&mut self,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receiver: &Receiver<WorkingBankEntry>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
socket_sender: &Sender<TransmitShreds>,
|
||||
blockstore_sender: &Sender<Arc<Vec<Shred>>>,
|
||||
) -> Result<()> {
|
||||
let receive_results = broadcast_utils::recv_slot_entries(receiver)?;
|
||||
self.process_receive_results(
|
||||
@ -396,21 +331,21 @@ impl BroadcastRun for StandardBroadcastRun {
|
||||
)
|
||||
}
|
||||
fn transmit(
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<TransmitReceiver>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<TransmitShreds>>>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
sock: &UdpSocket,
|
||||
) -> Result<()> {
|
||||
let ((stakes, shreds), slot_start_ts) = receiver.lock().unwrap().recv()?;
|
||||
self.broadcast(sock, cluster_info, stakes, shreds, slot_start_ts)
|
||||
let (stakes, shreds) = receiver.lock().unwrap().recv()?;
|
||||
self.broadcast(sock, cluster_info, stakes, shreds)
|
||||
}
|
||||
fn record(
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<RecordReceiver>>,
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<Arc<Vec<Shred>>>>>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
) -> Result<()> {
|
||||
let (shreds, slot_start_ts) = receiver.lock().unwrap().recv()?;
|
||||
self.insert(blockstore, shreds, slot_start_ts)
|
||||
let shreds = receiver.lock().unwrap().recv()?;
|
||||
self.insert(blockstore, shreds)
|
||||
}
|
||||
}
|
||||
|
||||
@ -418,17 +353,18 @@ impl BroadcastRun for StandardBroadcastRun {
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::cluster_info::{ClusterInfo, Node};
|
||||
use solana_ledger::genesis_utils::create_genesis_config;
|
||||
use crate::genesis_utils::create_genesis_config;
|
||||
use solana_ledger::{
|
||||
blockstore::Blockstore, entry::create_ticks, get_tmp_ledger_path,
|
||||
shred::max_ticks_per_n_shreds,
|
||||
};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::{
|
||||
clock::Slot,
|
||||
genesis_config::GenesisConfig,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
|
||||
fn setup(
|
||||
@ -436,7 +372,7 @@ mod test {
|
||||
) -> (
|
||||
Arc<Blockstore>,
|
||||
GenesisConfig,
|
||||
Arc<ClusterInfo>,
|
||||
Arc<RwLock<ClusterInfo>>,
|
||||
Arc<Bank>,
|
||||
Arc<Keypair>,
|
||||
UdpSocket,
|
||||
@ -449,12 +385,12 @@ mod test {
|
||||
let leader_keypair = Arc::new(Keypair::new());
|
||||
let leader_pubkey = leader_keypair.pubkey();
|
||||
let leader_info = Node::new_localhost_with_pubkey(&leader_pubkey);
|
||||
let cluster_info = Arc::new(ClusterInfo::new_with_invalid_keypair(
|
||||
let cluster_info = Arc::new(RwLock::new(ClusterInfo::new_with_invalid_keypair(
|
||||
leader_info.info.clone(),
|
||||
));
|
||||
)));
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
let mut genesis_config = create_genesis_config(10_000).genesis_config;
|
||||
genesis_config.ticks_per_slot = max_ticks_per_n_shreds(num_shreds_per_slot, None) + 1;
|
||||
genesis_config.ticks_per_slot = max_ticks_per_n_shreds(num_shreds_per_slot) + 1;
|
||||
let bank0 = Arc::new(Bank::new(&genesis_config));
|
||||
(
|
||||
blockstore,
|
||||
@ -526,48 +462,29 @@ mod test {
|
||||
// Make sure the slot is not complete
|
||||
assert!(!blockstore.is_full(0));
|
||||
// Modify the stats, should reset later
|
||||
standard_broadcast_run.process_shreds_stats.receive_elapsed = 10;
|
||||
// Broadcast stats should exist, and 2 batches should have been sent,
|
||||
// one for data, one for coding
|
||||
assert_eq!(
|
||||
standard_broadcast_run
|
||||
.transmit_shreds_stats
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(unfinished_slot.slot)
|
||||
.unwrap()
|
||||
.num_batches(),
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
standard_broadcast_run
|
||||
.insert_shreds_stats
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(unfinished_slot.slot)
|
||||
.unwrap()
|
||||
.num_batches(),
|
||||
2
|
||||
);
|
||||
standard_broadcast_run
|
||||
.stats
|
||||
.write()
|
||||
.unwrap()
|
||||
.receive_elapsed = 10;
|
||||
|
||||
// Try to fetch ticks from blockstore, nothing should break
|
||||
assert_eq!(blockstore.get_slot_entries(0, 0).unwrap(), ticks0);
|
||||
assert_eq!(blockstore.get_slot_entries(0, 0, None).unwrap(), ticks0);
|
||||
assert_eq!(
|
||||
blockstore.get_slot_entries(0, num_shreds_per_slot).unwrap(),
|
||||
blockstore
|
||||
.get_slot_entries(0, num_shreds_per_slot, None)
|
||||
.unwrap(),
|
||||
vec![],
|
||||
);
|
||||
|
||||
// Step 2: Make a transmission for another bank that interrupts the transmission for
|
||||
// slot 0
|
||||
let bank2 = Arc::new(Bank::new_from_parent(&bank0, &leader_keypair.pubkey(), 2));
|
||||
let interrupted_slot = unfinished_slot.slot;
|
||||
|
||||
// Interrupting the slot should cause the unfinished_slot and stats to reset
|
||||
let num_shreds = 1;
|
||||
assert!(num_shreds < num_shreds_per_slot);
|
||||
let ticks1 = create_ticks(
|
||||
max_ticks_per_n_shreds(num_shreds, None),
|
||||
0,
|
||||
genesis_config.hash(),
|
||||
);
|
||||
let ticks1 = create_ticks(max_ticks_per_n_shreds(num_shreds), 0, genesis_config.hash());
|
||||
let receive_results = ReceiveResults {
|
||||
entries: ticks1.clone(),
|
||||
time_elapsed: Duration::new(2, 0),
|
||||
@ -587,28 +504,16 @@ mod test {
|
||||
|
||||
// Check that the stats were reset as well
|
||||
assert_eq!(
|
||||
standard_broadcast_run.process_shreds_stats.receive_elapsed,
|
||||
standard_broadcast_run.stats.read().unwrap().receive_elapsed,
|
||||
0
|
||||
);
|
||||
|
||||
// Broadcast stats for interrupted slot should be cleared
|
||||
assert!(standard_broadcast_run
|
||||
.transmit_shreds_stats
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(interrupted_slot)
|
||||
.is_none());
|
||||
assert!(standard_broadcast_run
|
||||
.insert_shreds_stats
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(interrupted_slot)
|
||||
.is_none());
|
||||
|
||||
// Try to fetch the incomplete ticks from blockstore, should succeed
|
||||
assert_eq!(blockstore.get_slot_entries(0, 0).unwrap(), ticks0);
|
||||
assert_eq!(blockstore.get_slot_entries(0, 0, None).unwrap(), ticks0);
|
||||
assert_eq!(
|
||||
blockstore.get_slot_entries(0, num_shreds_per_slot).unwrap(),
|
||||
blockstore
|
||||
.get_slot_entries(0, num_shreds_per_slot, None)
|
||||
.unwrap(),
|
||||
vec![],
|
||||
);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,343 +0,0 @@
|
||||
use crate::{
|
||||
cluster_info::ClusterInfo, contact_info::ContactInfo, epoch_slots::EpochSlots,
|
||||
serve_repair::RepairType,
|
||||
};
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use solana_runtime::epoch_stakes::NodeIdToVoteAccounts;
|
||||
use solana_sdk::{clock::Slot, pubkey::Pubkey};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
pub type SlotPubkeys = HashMap<Arc<Pubkey>, u64>;
|
||||
pub type ClusterSlotsMap = RwLock<HashMap<Slot, Arc<RwLock<SlotPubkeys>>>>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ClusterSlots {
|
||||
cluster_slots: ClusterSlotsMap,
|
||||
keys: RwLock<HashSet<Arc<Pubkey>>>,
|
||||
since: RwLock<Option<u64>>,
|
||||
validator_stakes: RwLock<Arc<NodeIdToVoteAccounts>>,
|
||||
epoch: RwLock<Option<u64>>,
|
||||
self_id: RwLock<Pubkey>,
|
||||
}
|
||||
|
||||
impl ClusterSlots {
|
||||
pub fn lookup(&self, slot: Slot) -> Option<Arc<RwLock<SlotPubkeys>>> {
|
||||
self.cluster_slots.read().unwrap().get(&slot).cloned()
|
||||
}
|
||||
pub fn update(&self, root: Slot, cluster_info: &ClusterInfo, bank_forks: &RwLock<BankForks>) {
|
||||
self.update_peers(cluster_info, bank_forks);
|
||||
let since = *self.since.read().unwrap();
|
||||
let epoch_slots = cluster_info.get_epoch_slots_since(since);
|
||||
self.update_internal(root, epoch_slots);
|
||||
}
|
||||
fn update_internal(&self, root: Slot, epoch_slots: (Vec<EpochSlots>, Option<u64>)) {
|
||||
let (epoch_slots_list, since) = epoch_slots;
|
||||
for epoch_slots in epoch_slots_list {
|
||||
let slots = epoch_slots.to_slots(root);
|
||||
for slot in &slots {
|
||||
if *slot <= root {
|
||||
continue;
|
||||
}
|
||||
let pubkey = Arc::new(epoch_slots.from);
|
||||
let exists = self.keys.read().unwrap().get(&pubkey).is_some();
|
||||
if !exists {
|
||||
self.keys.write().unwrap().insert(pubkey.clone());
|
||||
}
|
||||
let from = self.keys.read().unwrap().get(&pubkey).unwrap().clone();
|
||||
let balance = self
|
||||
.validator_stakes
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&from)
|
||||
.map(|v| v.total_stake)
|
||||
.unwrap_or(0);
|
||||
|
||||
let mut slot_pubkeys = self.cluster_slots.read().unwrap().get(slot).cloned();
|
||||
if slot_pubkeys.is_none() {
|
||||
let new_slot_pubkeys = Arc::new(RwLock::new(HashMap::default()));
|
||||
self.cluster_slots
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(*slot, new_slot_pubkeys.clone());
|
||||
slot_pubkeys = Some(new_slot_pubkeys);
|
||||
}
|
||||
|
||||
slot_pubkeys
|
||||
.unwrap()
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(from.clone(), balance);
|
||||
}
|
||||
}
|
||||
self.cluster_slots.write().unwrap().retain(|x, _| *x > root);
|
||||
self.keys
|
||||
.write()
|
||||
.unwrap()
|
||||
.retain(|x| Arc::strong_count(x) > 1);
|
||||
*self.since.write().unwrap() = since;
|
||||
}
|
||||
pub fn collect(&self, id: &Pubkey) -> HashSet<Slot> {
|
||||
self.cluster_slots
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|(_, keys)| keys.read().unwrap().get(id).is_some())
|
||||
.map(|(slot, _)| slot)
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn update_peers(&self, cluster_info: &ClusterInfo, bank_forks: &RwLock<BankForks>) {
|
||||
let root_bank = bank_forks.read().unwrap().root_bank().clone();
|
||||
let root_epoch = root_bank.epoch();
|
||||
let my_epoch = *self.epoch.read().unwrap();
|
||||
|
||||
if Some(root_epoch) != my_epoch {
|
||||
let validator_stakes = root_bank
|
||||
.epoch_stakes(root_epoch)
|
||||
.expect(
|
||||
"Bank must have epoch stakes
|
||||
for its own epoch",
|
||||
)
|
||||
.node_id_to_vote_accounts()
|
||||
.clone();
|
||||
|
||||
*self.validator_stakes.write().unwrap() = validator_stakes;
|
||||
let id = cluster_info.id();
|
||||
*self.self_id.write().unwrap() = id;
|
||||
*self.epoch.write().unwrap() = Some(root_epoch);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_weights(&self, slot: Slot, repair_peers: &[ContactInfo]) -> Vec<(u64, usize)> {
|
||||
let slot_peers = self.lookup(slot);
|
||||
repair_peers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| {
|
||||
let peer_stake = slot_peers
|
||||
.as_ref()
|
||||
.and_then(|v| v.read().unwrap().get(&x.id).cloned())
|
||||
.unwrap_or(0);
|
||||
(
|
||||
1 + peer_stake
|
||||
+ self
|
||||
.validator_stakes
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&x.id)
|
||||
.map(|v| v.total_stake)
|
||||
.unwrap_or(0),
|
||||
i,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn generate_repairs_for_missing_slots(
|
||||
&self,
|
||||
self_id: &Pubkey,
|
||||
root: Slot,
|
||||
) -> Vec<RepairType> {
|
||||
let my_slots = self.collect(self_id);
|
||||
self.cluster_slots
|
||||
.read()
|
||||
.unwrap()
|
||||
.keys()
|
||||
.filter(|x| **x > root)
|
||||
.filter(|x| !my_slots.contains(*x))
|
||||
.map(|x| RepairType::HighestShred(*x, 0))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_runtime::epoch_stakes::NodeVoteAccounts;
|
||||
|
||||
#[test]
|
||||
fn test_default() {
|
||||
let cs = ClusterSlots::default();
|
||||
assert!(cs.cluster_slots.read().unwrap().is_empty());
|
||||
assert!(cs.since.read().unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_noop() {
|
||||
let cs = ClusterSlots::default();
|
||||
cs.update_internal(0, (vec![], None));
|
||||
assert!(cs.cluster_slots.read().unwrap().is_empty());
|
||||
assert!(cs.since.read().unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_empty() {
|
||||
let cs = ClusterSlots::default();
|
||||
let epoch_slot = EpochSlots::default();
|
||||
cs.update_internal(0, (vec![epoch_slot], Some(0)));
|
||||
assert_eq!(*cs.since.read().unwrap(), Some(0));
|
||||
assert!(cs.lookup(0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_rooted() {
|
||||
//root is 0, so it should clear out the slot
|
||||
let cs = ClusterSlots::default();
|
||||
let mut epoch_slot = EpochSlots::default();
|
||||
epoch_slot.fill(&[0], 0);
|
||||
cs.update_internal(0, (vec![epoch_slot], Some(0)));
|
||||
assert_eq!(*cs.since.read().unwrap(), Some(0));
|
||||
assert!(cs.lookup(0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_new_slot() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut epoch_slot = EpochSlots::default();
|
||||
epoch_slot.fill(&[1], 0);
|
||||
cs.update_internal(0, (vec![epoch_slot], Some(0)));
|
||||
assert_eq!(*cs.since.read().unwrap(), Some(0));
|
||||
assert!(cs.lookup(0).is_none());
|
||||
assert!(cs.lookup(1).is_some());
|
||||
assert_eq!(
|
||||
cs.lookup(1)
|
||||
.unwrap()
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&Pubkey::default()),
|
||||
Some(&0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_weights() {
|
||||
let cs = ClusterSlots::default();
|
||||
let ci = ContactInfo::default();
|
||||
assert_eq!(cs.compute_weights(0, &[ci]), vec![(1, 0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_best_peer_2() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut c1 = ContactInfo::default();
|
||||
let mut c2 = ContactInfo::default();
|
||||
let mut map = HashMap::new();
|
||||
let k1 = Pubkey::new_rand();
|
||||
let k2 = Pubkey::new_rand();
|
||||
map.insert(Arc::new(k1.clone()), std::u64::MAX / 2);
|
||||
map.insert(Arc::new(k2.clone()), 0);
|
||||
cs.cluster_slots
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(0, Arc::new(RwLock::new(map)));
|
||||
c1.id = k1;
|
||||
c2.id = k2;
|
||||
assert_eq!(
|
||||
cs.compute_weights(0, &[c1, c2]),
|
||||
vec![(std::u64::MAX / 2 + 1, 0), (1, 1)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_best_peer_3() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut c1 = ContactInfo::default();
|
||||
let mut c2 = ContactInfo::default();
|
||||
let mut map = HashMap::new();
|
||||
let k1 = Pubkey::new_rand();
|
||||
let k2 = Pubkey::new_rand();
|
||||
map.insert(Arc::new(k2.clone()), 0);
|
||||
cs.cluster_slots
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(0, Arc::new(RwLock::new(map)));
|
||||
//make sure default weights are used as well
|
||||
let validator_stakes: HashMap<_, _> = vec![(
|
||||
*Arc::new(k1.clone()),
|
||||
NodeVoteAccounts {
|
||||
total_stake: std::u64::MAX / 2,
|
||||
vote_accounts: vec![Pubkey::default()],
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
*cs.validator_stakes.write().unwrap() = Arc::new(validator_stakes);
|
||||
c1.id = k1;
|
||||
c2.id = k2;
|
||||
assert_eq!(
|
||||
cs.compute_weights(0, &[c1, c2]),
|
||||
vec![(std::u64::MAX / 2 + 1, 0), (1, 1)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_new_staked_slot() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut epoch_slot = EpochSlots::default();
|
||||
epoch_slot.fill(&[1], 0);
|
||||
|
||||
let map = Arc::new(
|
||||
vec![(
|
||||
Pubkey::default(),
|
||||
NodeVoteAccounts {
|
||||
total_stake: 1,
|
||||
vote_accounts: vec![Pubkey::default()],
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
|
||||
*cs.validator_stakes.write().unwrap() = map;
|
||||
cs.update_internal(0, (vec![epoch_slot], None));
|
||||
assert!(cs.lookup(1).is_some());
|
||||
assert_eq!(
|
||||
cs.lookup(1)
|
||||
.unwrap()
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&Pubkey::default()),
|
||||
Some(&1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_repairs() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut epoch_slot = EpochSlots::default();
|
||||
epoch_slot.fill(&[1], 0);
|
||||
cs.update_internal(0, (vec![epoch_slot], None));
|
||||
let self_id = Pubkey::new_rand();
|
||||
assert_eq!(
|
||||
cs.generate_repairs_for_missing_slots(&self_id, 0),
|
||||
vec![RepairType::HighestShred(1, 0)]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collect_my_slots() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut epoch_slot = EpochSlots::default();
|
||||
epoch_slot.fill(&[1], 0);
|
||||
let self_id = epoch_slot.from;
|
||||
cs.update_internal(0, (vec![epoch_slot], None));
|
||||
let slots: Vec<Slot> = cs.collect(&self_id).into_iter().collect();
|
||||
assert_eq!(slots, vec![1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_repairs_existing() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut epoch_slot = EpochSlots::default();
|
||||
epoch_slot.fill(&[1], 0);
|
||||
let self_id = epoch_slot.from;
|
||||
cs.update_internal(0, (vec![epoch_slot], None));
|
||||
assert!(cs
|
||||
.generate_repairs_for_missing_slots(&self_id, 0)
|
||||
.is_empty());
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
use crate::{consensus::VOTE_THRESHOLD_SIZE, rpc_subscriptions::RpcSubscriptions};
|
||||
use solana_ledger::blockstore::Blockstore;
|
||||
use crate::consensus::VOTE_THRESHOLD_SIZE;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_metrics::datapoint_info;
|
||||
use solana_metrics::inc_new_counter_info;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::clock::Slot;
|
||||
use solana_vote_program::{vote_state::VoteState, vote_state::MAX_LOCKOUT_HISTORY};
|
||||
@ -14,19 +13,9 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CacheSlotInfo {
|
||||
pub current_slot: Slot,
|
||||
pub node_root: Slot,
|
||||
pub largest_confirmed_root: Slot,
|
||||
pub highest_confirmed_slot: Slot,
|
||||
}
|
||||
|
||||
pub type BlockCommitmentArray = [u64; MAX_LOCKOUT_HISTORY + 1];
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BlockCommitment {
|
||||
pub commitment: BlockCommitmentArray,
|
||||
pub commitment: [u64; MAX_LOCKOUT_HISTORY],
|
||||
}
|
||||
|
||||
impl BlockCommitment {
|
||||
@ -39,29 +28,18 @@ impl BlockCommitment {
|
||||
assert!(confirmation_count > 0 && confirmation_count <= MAX_LOCKOUT_HISTORY);
|
||||
self.commitment[confirmation_count - 1]
|
||||
}
|
||||
|
||||
pub fn increase_rooted_stake(&mut self, stake: u64) {
|
||||
self.commitment[MAX_LOCKOUT_HISTORY] += stake;
|
||||
}
|
||||
|
||||
pub fn get_rooted_stake(&self) -> u64 {
|
||||
self.commitment[MAX_LOCKOUT_HISTORY]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new(commitment: BlockCommitmentArray) -> Self {
|
||||
pub(crate) fn new(commitment: [u64; MAX_LOCKOUT_HISTORY]) -> Self {
|
||||
Self { commitment }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BlockCommitmentCache {
|
||||
block_commitment: HashMap<Slot, BlockCommitment>,
|
||||
largest_confirmed_root: Slot,
|
||||
total_stake: u64,
|
||||
bank: Arc<Bank>,
|
||||
blockstore: Arc<Blockstore>,
|
||||
root: Slot,
|
||||
highest_confirmed_slot: Slot,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BlockCommitmentCache {
|
||||
@ -81,33 +59,15 @@ impl std::fmt::Debug for BlockCommitmentCache {
|
||||
impl BlockCommitmentCache {
|
||||
pub fn new(
|
||||
block_commitment: HashMap<Slot, BlockCommitment>,
|
||||
largest_confirmed_root: Slot,
|
||||
total_stake: u64,
|
||||
bank: Arc<Bank>,
|
||||
blockstore: Arc<Blockstore>,
|
||||
root: Slot,
|
||||
highest_confirmed_slot: Slot,
|
||||
) -> Self {
|
||||
Self {
|
||||
block_commitment,
|
||||
largest_confirmed_root,
|
||||
total_stake,
|
||||
bank,
|
||||
blockstore,
|
||||
root,
|
||||
highest_confirmed_slot,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_with_blockstore(blockstore: Arc<Blockstore>) -> Self {
|
||||
Self {
|
||||
block_commitment: HashMap::default(),
|
||||
largest_confirmed_root: Slot::default(),
|
||||
total_stake: u64::default(),
|
||||
bank: Arc::new(Bank::default()),
|
||||
blockstore,
|
||||
root: Slot::default(),
|
||||
highest_confirmed_slot: Slot::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,10 +75,6 @@ impl BlockCommitmentCache {
|
||||
self.block_commitment.get(&slot)
|
||||
}
|
||||
|
||||
pub fn largest_confirmed_root(&self) -> Slot {
|
||||
self.largest_confirmed_root
|
||||
}
|
||||
|
||||
pub fn total_stake(&self) -> u64 {
|
||||
self.total_stake
|
||||
}
|
||||
@ -135,26 +91,6 @@ impl BlockCommitmentCache {
|
||||
self.root
|
||||
}
|
||||
|
||||
pub fn highest_confirmed_slot(&self) -> Slot {
|
||||
self.highest_confirmed_slot
|
||||
}
|
||||
|
||||
fn highest_slot_with_confirmation_count(&self, confirmation_count: usize) -> Slot {
|
||||
assert!(confirmation_count > 0 && confirmation_count <= MAX_LOCKOUT_HISTORY);
|
||||
for slot in (self.root()..self.slot()).rev() {
|
||||
if let Some(count) = self.get_confirmation_count(slot) {
|
||||
if count >= confirmation_count {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.root
|
||||
}
|
||||
|
||||
fn calculate_highest_confirmed_slot(&self) -> Slot {
|
||||
self.highest_slot_with_confirmation_count(1)
|
||||
}
|
||||
|
||||
pub fn get_confirmation_count(&self, slot: Slot) -> Option<usize> {
|
||||
self.get_lockout_count(slot, VOTE_THRESHOLD_SIZE)
|
||||
}
|
||||
@ -174,49 +110,16 @@ impl BlockCommitmentCache {
|
||||
0
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_confirmed_rooted(&self, slot: Slot) -> bool {
|
||||
slot <= self.largest_confirmed_root()
|
||||
&& (self.blockstore.is_root(slot) || self.bank.status_cache_ancestors().contains(&slot))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_for_tests_with_blockstore(blockstore: Arc<Blockstore>) -> Self {
|
||||
pub fn new_for_tests() -> Self {
|
||||
let mut block_commitment: HashMap<Slot, BlockCommitment> = HashMap::new();
|
||||
block_commitment.insert(0, BlockCommitment::default());
|
||||
Self {
|
||||
block_commitment,
|
||||
blockstore,
|
||||
total_stake: 42,
|
||||
largest_confirmed_root: Slot::default(),
|
||||
bank: Arc::new(Bank::default()),
|
||||
root: Slot::default(),
|
||||
highest_confirmed_slot: Slot::default(),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_for_tests_with_blockstore_bank(
|
||||
blockstore: Arc<Blockstore>,
|
||||
bank: Arc<Bank>,
|
||||
root: Slot,
|
||||
) -> Self {
|
||||
let mut block_commitment: HashMap<Slot, BlockCommitment> = HashMap::new();
|
||||
block_commitment.insert(0, BlockCommitment::default());
|
||||
Self {
|
||||
block_commitment,
|
||||
blockstore,
|
||||
total_stake: 42,
|
||||
largest_confirmed_root: root,
|
||||
bank,
|
||||
root,
|
||||
highest_confirmed_slot: root,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_largest_confirmed_root(&mut self, root: Slot) {
|
||||
self.largest_confirmed_root = root;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommitmentAggregationData {
|
||||
@ -235,18 +138,6 @@ impl CommitmentAggregationData {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_largest_confirmed_root(mut rooted_stake: Vec<(Slot, u64)>, total_stake: u64) -> Slot {
|
||||
rooted_stake.sort_by(|a, b| a.0.cmp(&b.0).reverse());
|
||||
let mut stake_sum = 0;
|
||||
for (root, stake) in rooted_stake {
|
||||
stake_sum += stake;
|
||||
if (stake_sum as f64 / total_stake as f64) > VOTE_THRESHOLD_SIZE {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
pub struct AggregateCommitmentService {
|
||||
t_commitment: JoinHandle<()>,
|
||||
}
|
||||
@ -255,7 +146,6 @@ impl AggregateCommitmentService {
|
||||
pub fn new(
|
||||
exit: &Arc<AtomicBool>,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
subscriptions: Arc<RpcSubscriptions>,
|
||||
) -> (Sender<CommitmentAggregationData>, Self) {
|
||||
let (sender, receiver): (
|
||||
Sender<CommitmentAggregationData>,
|
||||
@ -273,7 +163,7 @@ impl AggregateCommitmentService {
|
||||
}
|
||||
|
||||
if let Err(RecvTimeoutError::Disconnected) =
|
||||
Self::run(&receiver, &block_commitment_cache, &subscriptions, &exit_)
|
||||
Self::run(&receiver, &block_commitment_cache, &exit_)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@ -286,7 +176,6 @@ impl AggregateCommitmentService {
|
||||
fn run(
|
||||
receiver: &Receiver<CommitmentAggregationData>,
|
||||
block_commitment_cache: &RwLock<BlockCommitmentCache>,
|
||||
subscriptions: &Arc<RpcSubscriptions>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
) -> Result<(), RecvTimeoutError> {
|
||||
loop {
|
||||
@ -306,50 +195,27 @@ impl AggregateCommitmentService {
|
||||
}
|
||||
|
||||
let mut aggregate_commitment_time = Measure::start("aggregate-commitment-ms");
|
||||
let (block_commitment, rooted_stake) =
|
||||
Self::aggregate_commitment(&ancestors, &aggregation_data.bank);
|
||||
|
||||
let largest_confirmed_root =
|
||||
get_largest_confirmed_root(rooted_stake, aggregation_data.total_staked);
|
||||
let block_commitment = Self::aggregate_commitment(&ancestors, &aggregation_data.bank);
|
||||
|
||||
let mut new_block_commitment = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
largest_confirmed_root,
|
||||
aggregation_data.total_staked,
|
||||
aggregation_data.bank,
|
||||
block_commitment_cache.read().unwrap().blockstore.clone(),
|
||||
aggregation_data.root,
|
||||
aggregation_data.root,
|
||||
);
|
||||
new_block_commitment.highest_confirmed_slot =
|
||||
new_block_commitment.calculate_highest_confirmed_slot();
|
||||
|
||||
let mut w_block_commitment_cache = block_commitment_cache.write().unwrap();
|
||||
|
||||
std::mem::swap(&mut *w_block_commitment_cache, &mut new_block_commitment);
|
||||
aggregate_commitment_time.stop();
|
||||
datapoint_info!(
|
||||
"block-commitment-cache",
|
||||
(
|
||||
"aggregate-commitment-ms",
|
||||
aggregate_commitment_time.as_ms() as i64,
|
||||
i64
|
||||
)
|
||||
inc_new_counter_info!(
|
||||
"aggregate-commitment-ms",
|
||||
aggregate_commitment_time.as_ms() as usize
|
||||
);
|
||||
|
||||
subscriptions.notify_subscribers(CacheSlotInfo {
|
||||
current_slot: w_block_commitment_cache.slot(),
|
||||
node_root: w_block_commitment_cache.root(),
|
||||
largest_confirmed_root: w_block_commitment_cache.largest_confirmed_root(),
|
||||
highest_confirmed_slot: w_block_commitment_cache.highest_confirmed_slot(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn aggregate_commitment(
|
||||
ancestors: &[Slot],
|
||||
bank: &Bank,
|
||||
) -> (HashMap<Slot, BlockCommitment>, Vec<(Slot, u64)>) {
|
||||
pub fn aggregate_commitment(ancestors: &[Slot], bank: &Bank) -> HashMap<Slot, BlockCommitment> {
|
||||
assert!(!ancestors.is_empty());
|
||||
|
||||
// Check ancestors is sorted
|
||||
@ -358,7 +224,6 @@ impl AggregateCommitmentService {
|
||||
}
|
||||
|
||||
let mut commitment = HashMap::new();
|
||||
let mut rooted_stake: Vec<(Slot, u64)> = Vec::new();
|
||||
for (_, (lamports, account)) in bank.vote_accounts().into_iter() {
|
||||
if lamports == 0 {
|
||||
continue;
|
||||
@ -371,19 +236,17 @@ impl AggregateCommitmentService {
|
||||
let vote_state = vote_state.unwrap();
|
||||
Self::aggregate_commitment_for_vote_account(
|
||||
&mut commitment,
|
||||
&mut rooted_stake,
|
||||
&vote_state,
|
||||
ancestors,
|
||||
lamports,
|
||||
);
|
||||
}
|
||||
|
||||
(commitment, rooted_stake)
|
||||
commitment
|
||||
}
|
||||
|
||||
fn aggregate_commitment_for_vote_account(
|
||||
commitment: &mut HashMap<Slot, BlockCommitment>,
|
||||
rooted_stake: &mut Vec<(Slot, u64)>,
|
||||
vote_state: &VoteState,
|
||||
ancestors: &[Slot],
|
||||
lamports: u64,
|
||||
@ -396,13 +259,12 @@ impl AggregateCommitmentService {
|
||||
commitment
|
||||
.entry(*a)
|
||||
.or_insert_with(BlockCommitment::default)
|
||||
.increase_rooted_stake(lamports);
|
||||
.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports);
|
||||
} else {
|
||||
ancestors_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
rooted_stake.push((root, lamports));
|
||||
}
|
||||
|
||||
for vote in &vote_state.votes {
|
||||
@ -428,11 +290,8 @@ impl AggregateCommitmentService {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_ledger::{
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
get_tmp_ledger_path,
|
||||
};
|
||||
use solana_sdk::{genesis_config::GenesisConfig, pubkey::Pubkey};
|
||||
use crate::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_stake_program::stake_state;
|
||||
use solana_vote_program::vote_state::{self, VoteStateVersions};
|
||||
|
||||
@ -449,8 +308,6 @@ mod tests {
|
||||
#[test]
|
||||
fn test_get_confirmations() {
|
||||
let bank = Arc::new(Bank::default());
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||
// Build BlockCommitmentCache with votes at depths 0 and 1 for 2 slots
|
||||
let mut cache0 = BlockCommitment::default();
|
||||
cache0.increase_confirmation_stake(1, 5);
|
||||
@ -468,8 +325,7 @@ mod tests {
|
||||
block_commitment.entry(0).or_insert(cache0.clone());
|
||||
block_commitment.entry(1).or_insert(cache1.clone());
|
||||
block_commitment.entry(2).or_insert(cache2.clone());
|
||||
let block_commitment_cache =
|
||||
BlockCommitmentCache::new(block_commitment, 0, 50, bank, blockstore, 0, 0);
|
||||
let block_commitment_cache = BlockCommitmentCache::new(block_commitment, 50, bank, 0);
|
||||
|
||||
assert_eq!(block_commitment_cache.get_confirmation_count(0), Some(2));
|
||||
assert_eq!(block_commitment_cache.get_confirmation_count(1), Some(1));
|
||||
@ -477,177 +333,17 @@ mod tests {
|
||||
assert_eq!(block_commitment_cache.get_confirmation_count(3), None,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_confirmed_rooted() {
|
||||
let bank = Arc::new(Bank::default());
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||
blockstore.set_roots(&[0, 1]).unwrap();
|
||||
// Build BlockCommitmentCache with rooted slots
|
||||
let mut cache0 = BlockCommitment::default();
|
||||
cache0.increase_rooted_stake(50);
|
||||
let mut cache1 = BlockCommitment::default();
|
||||
cache1.increase_rooted_stake(40);
|
||||
let mut cache2 = BlockCommitment::default();
|
||||
cache2.increase_rooted_stake(20);
|
||||
|
||||
let mut block_commitment = HashMap::new();
|
||||
block_commitment.entry(1).or_insert(cache0.clone());
|
||||
block_commitment.entry(2).or_insert(cache1.clone());
|
||||
block_commitment.entry(3).or_insert(cache2.clone());
|
||||
let largest_confirmed_root = 1;
|
||||
let block_commitment_cache = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
largest_confirmed_root,
|
||||
50,
|
||||
bank,
|
||||
blockstore,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
assert!(block_commitment_cache.is_confirmed_rooted(0));
|
||||
assert!(block_commitment_cache.is_confirmed_rooted(1));
|
||||
assert!(!block_commitment_cache.is_confirmed_rooted(2));
|
||||
assert!(!block_commitment_cache.is_confirmed_rooted(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_largest_confirmed_root() {
|
||||
assert_eq!(get_largest_confirmed_root(vec![], 10), 0);
|
||||
let mut rooted_stake = vec![];
|
||||
rooted_stake.push((0, 5));
|
||||
rooted_stake.push((1, 5));
|
||||
assert_eq!(get_largest_confirmed_root(rooted_stake, 10), 0);
|
||||
let mut rooted_stake = vec![];
|
||||
rooted_stake.push((1, 5));
|
||||
rooted_stake.push((0, 10));
|
||||
rooted_stake.push((2, 5));
|
||||
rooted_stake.push((1, 4));
|
||||
assert_eq!(get_largest_confirmed_root(rooted_stake, 10), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highest_confirmed_slot() {
|
||||
let bank = Arc::new(Bank::new(&GenesisConfig::default()));
|
||||
let bank_slot_5 = Arc::new(Bank::new_from_parent(&bank, &Pubkey::default(), 5));
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||
let total_stake = 50;
|
||||
|
||||
// Build cache with confirmation_count 2 given total_stake
|
||||
let mut cache0 = BlockCommitment::default();
|
||||
cache0.increase_confirmation_stake(1, 5);
|
||||
cache0.increase_confirmation_stake(2, 40);
|
||||
|
||||
// Build cache with confirmation_count 1 given total_stake
|
||||
let mut cache1 = BlockCommitment::default();
|
||||
cache1.increase_confirmation_stake(1, 40);
|
||||
cache1.increase_confirmation_stake(2, 5);
|
||||
|
||||
// Build cache with confirmation_count 0 given total_stake
|
||||
let mut cache2 = BlockCommitment::default();
|
||||
cache2.increase_confirmation_stake(1, 20);
|
||||
cache2.increase_confirmation_stake(2, 5);
|
||||
|
||||
let mut block_commitment = HashMap::new();
|
||||
block_commitment.entry(1).or_insert(cache0.clone()); // Slot 1, conf 2
|
||||
block_commitment.entry(2).or_insert(cache1.clone()); // Slot 2, conf 1
|
||||
block_commitment.entry(3).or_insert(cache2.clone()); // Slot 3, conf 0
|
||||
let block_commitment_cache = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
0,
|
||||
total_stake,
|
||||
bank_slot_5.clone(),
|
||||
blockstore.clone(),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 2);
|
||||
|
||||
// Build map with multiple slots at conf 1
|
||||
let mut block_commitment = HashMap::new();
|
||||
block_commitment.entry(1).or_insert(cache1.clone()); // Slot 1, conf 1
|
||||
block_commitment.entry(2).or_insert(cache1.clone()); // Slot 2, conf 1
|
||||
block_commitment.entry(3).or_insert(cache2.clone()); // Slot 3, conf 0
|
||||
let block_commitment_cache = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
0,
|
||||
total_stake,
|
||||
bank_slot_5.clone(),
|
||||
blockstore.clone(),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 2);
|
||||
|
||||
// Build map with slot gaps
|
||||
let mut block_commitment = HashMap::new();
|
||||
block_commitment.entry(1).or_insert(cache1.clone()); // Slot 1, conf 1
|
||||
block_commitment.entry(3).or_insert(cache1.clone()); // Slot 3, conf 1
|
||||
block_commitment.entry(5).or_insert(cache2.clone()); // Slot 5, conf 0
|
||||
let block_commitment_cache = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
0,
|
||||
total_stake,
|
||||
bank_slot_5.clone(),
|
||||
blockstore.clone(),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 3);
|
||||
|
||||
// Build map with no conf 1 slots, but one higher
|
||||
let mut block_commitment = HashMap::new();
|
||||
block_commitment.entry(1).or_insert(cache0.clone()); // Slot 1, conf 2
|
||||
block_commitment.entry(2).or_insert(cache2.clone()); // Slot 2, conf 0
|
||||
block_commitment.entry(3).or_insert(cache2.clone()); // Slot 3, conf 0
|
||||
let block_commitment_cache = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
0,
|
||||
total_stake,
|
||||
bank_slot_5.clone(),
|
||||
blockstore.clone(),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 1);
|
||||
|
||||
// Build map with no conf 1 or higher slots
|
||||
let mut block_commitment = HashMap::new();
|
||||
block_commitment.entry(1).or_insert(cache2.clone()); // Slot 1, conf 0
|
||||
block_commitment.entry(2).or_insert(cache2.clone()); // Slot 2, conf 0
|
||||
block_commitment.entry(3).or_insert(cache2.clone()); // Slot 3, conf 0
|
||||
let block_commitment_cache = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
0,
|
||||
total_stake,
|
||||
bank_slot_5.clone(),
|
||||
blockstore.clone(),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aggregate_commitment_for_vote_account_1() {
|
||||
let ancestors = vec![3, 4, 5, 7, 9, 11];
|
||||
let mut commitment = HashMap::new();
|
||||
let mut rooted_stake = vec![];
|
||||
let lamports = 5;
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
let root = ancestors.last().unwrap().clone();
|
||||
vote_state.root_slot = Some(root);
|
||||
let root = ancestors.last().unwrap();
|
||||
vote_state.root_slot = Some(*root);
|
||||
AggregateCommitmentService::aggregate_commitment_for_vote_account(
|
||||
&mut commitment,
|
||||
&mut rooted_stake,
|
||||
&vote_state,
|
||||
&ancestors,
|
||||
lamports,
|
||||
@ -655,17 +351,15 @@ mod tests {
|
||||
|
||||
for a in ancestors {
|
||||
let mut expected = BlockCommitment::default();
|
||||
expected.increase_rooted_stake(lamports);
|
||||
expected.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports);
|
||||
assert_eq!(*commitment.get(&a).unwrap(), expected);
|
||||
}
|
||||
assert_eq!(rooted_stake[0], (root, lamports));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aggregate_commitment_for_vote_account_2() {
|
||||
let ancestors = vec![3, 4, 5, 7, 9, 11];
|
||||
let mut commitment = HashMap::new();
|
||||
let mut rooted_stake = vec![];
|
||||
let lamports = 5;
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
@ -674,7 +368,6 @@ mod tests {
|
||||
vote_state.process_slot_vote_unchecked(*ancestors.last().unwrap());
|
||||
AggregateCommitmentService::aggregate_commitment_for_vote_account(
|
||||
&mut commitment,
|
||||
&mut rooted_stake,
|
||||
&vote_state,
|
||||
&ancestors,
|
||||
lamports,
|
||||
@ -683,7 +376,7 @@ mod tests {
|
||||
for a in ancestors {
|
||||
if a <= root {
|
||||
let mut expected = BlockCommitment::default();
|
||||
expected.increase_rooted_stake(lamports);
|
||||
expected.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports);
|
||||
assert_eq!(*commitment.get(&a).unwrap(), expected);
|
||||
} else {
|
||||
let mut expected = BlockCommitment::default();
|
||||
@ -691,14 +384,12 @@ mod tests {
|
||||
assert_eq!(*commitment.get(&a).unwrap(), expected);
|
||||
}
|
||||
}
|
||||
assert_eq!(rooted_stake[0], (root, lamports));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aggregate_commitment_for_vote_account_3() {
|
||||
let ancestors = vec![3, 4, 5, 7, 9, 10, 11];
|
||||
let mut commitment = HashMap::new();
|
||||
let mut rooted_stake = vec![];
|
||||
let lamports = 5;
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
@ -709,7 +400,6 @@ mod tests {
|
||||
vote_state.process_slot_vote_unchecked(ancestors[6]);
|
||||
AggregateCommitmentService::aggregate_commitment_for_vote_account(
|
||||
&mut commitment,
|
||||
&mut rooted_stake,
|
||||
&vote_state,
|
||||
&ancestors,
|
||||
lamports,
|
||||
@ -718,7 +408,7 @@ mod tests {
|
||||
for (i, a) in ancestors.iter().enumerate() {
|
||||
if *a <= root {
|
||||
let mut expected = BlockCommitment::default();
|
||||
expected.increase_rooted_stake(lamports);
|
||||
expected.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports);
|
||||
assert_eq!(*commitment.get(&a).unwrap(), expected);
|
||||
} else if i <= 4 {
|
||||
let mut expected = BlockCommitment::default();
|
||||
@ -730,7 +420,6 @@ mod tests {
|
||||
assert_eq!(*commitment.get(&a).unwrap(), expected);
|
||||
}
|
||||
}
|
||||
assert_eq!(rooted_stake[0], (root, lamports));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -740,8 +429,6 @@ mod tests {
|
||||
mut genesis_config, ..
|
||||
} = create_genesis_config(10_000);
|
||||
|
||||
let rooted_stake_amount = 40;
|
||||
|
||||
let sk1 = Pubkey::new_rand();
|
||||
let pk1 = Pubkey::new_rand();
|
||||
let mut vote_account1 = vote_state::create_account(&pk1, &Pubkey::new_rand(), 0, 100);
|
||||
@ -752,36 +439,12 @@ mod tests {
|
||||
let mut vote_account2 = vote_state::create_account(&pk2, &Pubkey::new_rand(), 0, 50);
|
||||
let stake_account2 =
|
||||
stake_state::create_account(&sk2, &pk2, &vote_account2, &genesis_config.rent, 50);
|
||||
let sk3 = Pubkey::new_rand();
|
||||
let pk3 = Pubkey::new_rand();
|
||||
let mut vote_account3 = vote_state::create_account(&pk3, &Pubkey::new_rand(), 0, 1);
|
||||
let stake_account3 = stake_state::create_account(
|
||||
&sk3,
|
||||
&pk3,
|
||||
&vote_account3,
|
||||
&genesis_config.rent,
|
||||
rooted_stake_amount,
|
||||
);
|
||||
let sk4 = Pubkey::new_rand();
|
||||
let pk4 = Pubkey::new_rand();
|
||||
let mut vote_account4 = vote_state::create_account(&pk4, &Pubkey::new_rand(), 0, 1);
|
||||
let stake_account4 = stake_state::create_account(
|
||||
&sk4,
|
||||
&pk4,
|
||||
&vote_account4,
|
||||
&genesis_config.rent,
|
||||
rooted_stake_amount,
|
||||
);
|
||||
|
||||
genesis_config.accounts.extend(vec![
|
||||
(pk1, vote_account1.clone()),
|
||||
(sk1, stake_account1),
|
||||
(pk2, vote_account2.clone()),
|
||||
(sk2, stake_account2),
|
||||
(pk3, vote_account3.clone()),
|
||||
(sk3, stake_account3),
|
||||
(pk4, vote_account4.clone()),
|
||||
(sk4, stake_account4),
|
||||
]);
|
||||
|
||||
// Create bank
|
||||
@ -801,20 +464,7 @@ mod tests {
|
||||
VoteState::to(&versioned, &mut vote_account2).unwrap();
|
||||
bank.store_account(&pk2, &vote_account2);
|
||||
|
||||
let mut vote_state3 = VoteState::from(&vote_account3).unwrap();
|
||||
vote_state3.root_slot = Some(1);
|
||||
let versioned = VoteStateVersions::Current(Box::new(vote_state3));
|
||||
VoteState::to(&versioned, &mut vote_account3).unwrap();
|
||||
bank.store_account(&pk3, &vote_account3);
|
||||
|
||||
let mut vote_state4 = VoteState::from(&vote_account4).unwrap();
|
||||
vote_state4.root_slot = Some(2);
|
||||
let versioned = VoteStateVersions::Current(Box::new(vote_state4));
|
||||
VoteState::to(&versioned, &mut vote_account4).unwrap();
|
||||
bank.store_account(&pk4, &vote_account4);
|
||||
|
||||
let (commitment, rooted_stake) =
|
||||
AggregateCommitmentService::aggregate_commitment(&ancestors, &bank);
|
||||
let commitment = AggregateCommitmentService::aggregate_commitment(&ancestors, &bank);
|
||||
|
||||
for a in ancestors {
|
||||
if a <= 3 {
|
||||
@ -838,7 +488,5 @@ mod tests {
|
||||
assert!(commitment.get(&a).is_none());
|
||||
}
|
||||
}
|
||||
assert_eq!(rooted_stake.len(), 2);
|
||||
assert_eq!(get_largest_confirmed_root(rooted_stake, 100), 1)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::progress_map::ProgressMap;
|
||||
use chrono::prelude::*;
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use solana_runtime::bank::Bank;
|
||||
@ -321,7 +320,7 @@ impl Tower {
|
||||
}
|
||||
pub fn check_vote_stake_threshold(
|
||||
&self,
|
||||
slot: Slot,
|
||||
slot: u64,
|
||||
stake_lockouts: &HashMap<u64, StakeLockout>,
|
||||
total_staked: u64,
|
||||
) -> bool {
|
||||
@ -332,8 +331,11 @@ impl Tower {
|
||||
if let Some(fork_stake) = stake_lockouts.get(&vote.slot) {
|
||||
let lockout = fork_stake.stake as f64 / total_staked as f64;
|
||||
trace!(
|
||||
"fork_stake slot: {}, vote slot: {}, lockout: {} fork_stake: {} total_stake: {}",
|
||||
slot, vote.slot, lockout, fork_stake.stake, total_staked
|
||||
"fork_stake slot: {} lockout: {} fork_stake: {} total_stake: {}",
|
||||
slot,
|
||||
lockout,
|
||||
fork_stake.stake,
|
||||
total_staked
|
||||
);
|
||||
if vote.confirmation_count as usize > self.threshold_depth {
|
||||
for old_vote in &self.lockouts.votes {
|
||||
@ -353,18 +355,6 @@ impl Tower {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_switch_threshold(
|
||||
&self,
|
||||
_slot: Slot,
|
||||
_ancestors: &HashMap<Slot, HashSet<u64>>,
|
||||
_descendants: &HashMap<Slot, HashSet<u64>>,
|
||||
_progress: &ProgressMap,
|
||||
_total_epoch_stake: u64,
|
||||
_epoch_vote_accounts: &HashMap<Pubkey, (u64, Account)>,
|
||||
) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Update lockouts for all the ancestors
|
||||
fn update_ancestor_lockouts(
|
||||
stake_lockouts: &mut HashMap<Slot, StakeLockout>,
|
||||
@ -478,12 +468,7 @@ impl Tower {
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
cluster_info_vote_listener::VoteTracker,
|
||||
cluster_slots::ClusterSlots,
|
||||
progress_map::ForkProgress,
|
||||
replay_stage::{HeaviestForkFailures, ReplayStage},
|
||||
};
|
||||
use crate::replay_stage::{ForkProgress, ReplayStage};
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use solana_runtime::{
|
||||
bank::Bank,
|
||||
@ -496,96 +481,107 @@ pub mod test {
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_vote_program::{
|
||||
vote_instruction,
|
||||
vote_state::{Vote, VoteStateVersions},
|
||||
vote_transaction,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::RwLock;
|
||||
use std::{thread::sleep, time::Duration};
|
||||
use trees::{tr, Tree, TreeWalk};
|
||||
use trees::{tr, Node, Tree};
|
||||
|
||||
pub(crate) struct VoteSimulator {
|
||||
pub validator_keypairs: HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
pub node_pubkeys: Vec<Pubkey>,
|
||||
pub vote_pubkeys: Vec<Pubkey>,
|
||||
pub bank_forks: RwLock<BankForks>,
|
||||
pub progress: ProgressMap,
|
||||
pub(crate) struct VoteSimulator<'a> {
|
||||
searchable_nodes: HashMap<u64, &'a Node<u64>>,
|
||||
}
|
||||
|
||||
impl VoteSimulator {
|
||||
pub(crate) fn new(num_keypairs: usize) -> Self {
|
||||
let (validator_keypairs, node_pubkeys, vote_pubkeys, bank_forks, progress) =
|
||||
Self::init_state(num_keypairs);
|
||||
Self {
|
||||
validator_keypairs,
|
||||
node_pubkeys,
|
||||
vote_pubkeys,
|
||||
bank_forks: RwLock::new(bank_forks),
|
||||
progress,
|
||||
}
|
||||
}
|
||||
pub(crate) fn fill_bank_forks(
|
||||
&mut self,
|
||||
forks: Tree<u64>,
|
||||
cluster_votes: &HashMap<Pubkey, Vec<u64>>,
|
||||
) {
|
||||
let root = forks.root().data;
|
||||
assert!(self.bank_forks.read().unwrap().get(root).is_some());
|
||||
|
||||
let mut walk = TreeWalk::from(forks);
|
||||
loop {
|
||||
if let Some(visit) = walk.get() {
|
||||
let slot = visit.node().data;
|
||||
self.progress
|
||||
.entry(slot)
|
||||
.or_insert_with(|| ForkProgress::new(Hash::default(), None, None, 0, 0));
|
||||
if self.bank_forks.read().unwrap().get(slot).is_some() {
|
||||
walk.forward();
|
||||
continue;
|
||||
}
|
||||
let parent = walk.get_parent().unwrap().data;
|
||||
let parent_bank = self.bank_forks.read().unwrap().get(parent).unwrap().clone();
|
||||
let new_bank = Bank::new_from_parent(&parent_bank, &Pubkey::default(), slot);
|
||||
for (pubkey, vote) in cluster_votes.iter() {
|
||||
if vote.contains(&parent) {
|
||||
let keypairs = self.validator_keypairs.get(pubkey).unwrap();
|
||||
let last_blockhash = parent_bank.last_blockhash();
|
||||
let vote_tx = vote_transaction::new_vote_transaction(
|
||||
// Must vote > root to be processed
|
||||
vec![parent],
|
||||
parent_bank.hash(),
|
||||
last_blockhash,
|
||||
&keypairs.node_keypair,
|
||||
&keypairs.vote_keypair,
|
||||
&keypairs.vote_keypair,
|
||||
);
|
||||
info!("voting {} {}", parent_bank.slot(), parent_bank.hash());
|
||||
new_bank.process_transaction(&vote_tx).unwrap();
|
||||
}
|
||||
}
|
||||
new_bank.freeze();
|
||||
self.bank_forks.write().unwrap().insert(new_bank);
|
||||
walk.forward();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
impl<'a> VoteSimulator<'a> {
|
||||
pub(crate) fn new(forks: &'a Tree<u64>) -> Self {
|
||||
let mut searchable_nodes = HashMap::new();
|
||||
let root = forks.root();
|
||||
searchable_nodes.insert(root.data, root);
|
||||
Self { searchable_nodes }
|
||||
}
|
||||
|
||||
pub(crate) fn simulate_vote(
|
||||
&mut self,
|
||||
vote_slot: Slot,
|
||||
my_pubkey: &Pubkey,
|
||||
bank_forks: &RwLock<BankForks>,
|
||||
cluster_votes: &mut HashMap<Pubkey, Vec<u64>>,
|
||||
validator_keypairs: &HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
my_keypairs: &ValidatorVoteKeypairs,
|
||||
progress: &mut HashMap<u64, ForkProgress>,
|
||||
tower: &mut Tower,
|
||||
) -> Vec<HeaviestForkFailures> {
|
||||
// Try to simulate the vote
|
||||
let my_keypairs = self.validator_keypairs.get(&my_pubkey).unwrap();
|
||||
) -> Vec<VoteFailures> {
|
||||
let node = self
|
||||
.find_node_and_update_simulation(vote_slot)
|
||||
.expect("Vote to simulate must be for a slot in the tree");
|
||||
|
||||
let mut missing_nodes = VecDeque::new();
|
||||
let mut current = node;
|
||||
loop {
|
||||
let current_slot = current.data;
|
||||
if bank_forks.read().unwrap().get(current_slot).is_some()
|
||||
|| tower.root().map(|r| current_slot < r).unwrap_or(false)
|
||||
{
|
||||
break;
|
||||
} else {
|
||||
missing_nodes.push_front(current);
|
||||
}
|
||||
|
||||
if let Some(parent) = current.parent() {
|
||||
current = parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create any missing banks along the path
|
||||
for missing_node in missing_nodes {
|
||||
let missing_slot = missing_node.data;
|
||||
let parent = missing_node.parent().unwrap().data;
|
||||
let parent_bank = bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(parent)
|
||||
.expect("parent bank must exist")
|
||||
.clone();
|
||||
info!("parent of {} is {}", missing_slot, parent_bank.slot(),);
|
||||
progress
|
||||
.entry(missing_slot)
|
||||
.or_insert_with(|| ForkProgress::new(parent_bank.last_blockhash()));
|
||||
|
||||
// Create the missing bank
|
||||
let new_bank =
|
||||
Bank::new_from_parent(&parent_bank, &Pubkey::default(), missing_slot);
|
||||
|
||||
// Simulate ingesting the cluster's votes for the parent into this bank
|
||||
for (pubkey, vote) in cluster_votes.iter() {
|
||||
if vote.contains(&parent_bank.slot()) {
|
||||
let keypairs = validator_keypairs.get(pubkey).unwrap();
|
||||
let node_pubkey = keypairs.node_keypair.pubkey();
|
||||
let vote_pubkey = keypairs.vote_keypair.pubkey();
|
||||
let last_blockhash = parent_bank.last_blockhash();
|
||||
let votes = Vote::new(vec![parent_bank.slot()], parent_bank.hash());
|
||||
info!("voting {} {}", parent_bank.slot(), parent_bank.hash());
|
||||
let vote_ix = vote_instruction::vote(&vote_pubkey, &vote_pubkey, votes);
|
||||
let mut vote_tx =
|
||||
Transaction::new_with_payer(vec![vote_ix], Some(&node_pubkey));
|
||||
vote_tx.partial_sign(&[&keypairs.node_keypair], last_blockhash);
|
||||
vote_tx.partial_sign(&[&keypairs.vote_keypair], last_blockhash);
|
||||
new_bank.process_transaction(&vote_tx).unwrap();
|
||||
}
|
||||
}
|
||||
new_bank.freeze();
|
||||
bank_forks.write().unwrap().insert(new_bank);
|
||||
}
|
||||
|
||||
// Now try to simulate the vote
|
||||
let my_pubkey = my_keypairs.node_keypair.pubkey();
|
||||
let my_vote_pubkey = my_keypairs.vote_keypair.pubkey();
|
||||
let ancestors = self.bank_forks.read().unwrap().ancestors();
|
||||
let mut frozen_banks: Vec<_> = self
|
||||
.bank_forks
|
||||
let ancestors = bank_forks.read().unwrap().ancestors();
|
||||
let mut frozen_banks: Vec<_> = bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.frozen_banks()
|
||||
@ -598,133 +594,99 @@ pub mod test {
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
tower,
|
||||
&mut self.progress,
|
||||
&VoteTracker::default(),
|
||||
&ClusterSlots::default(),
|
||||
&self.bank_forks,
|
||||
&mut HashSet::new(),
|
||||
progress,
|
||||
);
|
||||
|
||||
let vote_bank = self
|
||||
.bank_forks
|
||||
let bank = bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(vote_slot)
|
||||
.expect("Bank must have been created before vote simulation")
|
||||
.clone();
|
||||
|
||||
// Try to vote on the given slot
|
||||
let descendants = self.bank_forks.read().unwrap().descendants();
|
||||
let (_, _, failure_reasons) = ReplayStage::select_vote_and_reset_forks(
|
||||
&Some(vote_bank.clone()),
|
||||
&None,
|
||||
&ancestors,
|
||||
&descendants,
|
||||
&self.progress,
|
||||
&tower,
|
||||
);
|
||||
|
||||
// Make sure this slot isn't locked out or failing threshold
|
||||
info!("Checking vote: {}", vote_bank.slot());
|
||||
if !failure_reasons.is_empty() {
|
||||
return failure_reasons;
|
||||
let fork_progress = progress
|
||||
.get(&vote_slot)
|
||||
.expect("Slot for vote must exist in progress map");
|
||||
info!("Checking vote: {}", vote_slot);
|
||||
info!("lockouts: {:?}", fork_progress.fork_stats.stake_lockouts);
|
||||
let mut failures = vec![];
|
||||
if fork_progress.fork_stats.is_locked_out {
|
||||
failures.push(VoteFailures::LockedOut(vote_slot));
|
||||
}
|
||||
let vote = tower.new_vote_from_bank(&vote_bank, &my_vote_pubkey).0;
|
||||
if !fork_progress.fork_stats.vote_threshold {
|
||||
failures.push(VoteFailures::FailedThreshold(vote_slot));
|
||||
}
|
||||
if !failures.is_empty() {
|
||||
return failures;
|
||||
}
|
||||
let vote = tower.new_vote_from_bank(&bank, &my_vote_pubkey).0;
|
||||
if let Some(new_root) = tower.record_bank_vote(vote) {
|
||||
ReplayStage::handle_new_root(
|
||||
new_root,
|
||||
&self.bank_forks,
|
||||
&mut self.progress,
|
||||
&None,
|
||||
&mut HashSet::new(),
|
||||
None,
|
||||
);
|
||||
ReplayStage::handle_new_root(new_root, bank_forks, progress, &None);
|
||||
}
|
||||
|
||||
// Mark the vote for this bank under this node's pubkey so it will be
|
||||
// integrated into any future child banks
|
||||
cluster_votes.entry(my_pubkey).or_default().push(vote_slot);
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn can_progress_on_fork(
|
||||
&mut self,
|
||||
my_pubkey: &Pubkey,
|
||||
tower: &mut Tower,
|
||||
start_slot: u64,
|
||||
num_slots: u64,
|
||||
cluster_votes: &mut HashMap<Pubkey, Vec<u64>>,
|
||||
) -> bool {
|
||||
// Check that within some reasonable time, validator can make a new
|
||||
// root on this fork
|
||||
let old_root = tower.root();
|
||||
|
||||
for i in 1..num_slots {
|
||||
// The parent of the tip of the fork
|
||||
let mut fork_tip_parent = tr(start_slot + i - 1);
|
||||
// The tip of the fork
|
||||
fork_tip_parent.push_front(tr(start_slot + i));
|
||||
self.fill_bank_forks(fork_tip_parent, cluster_votes);
|
||||
if self
|
||||
.simulate_vote(i + start_slot, &my_pubkey, tower)
|
||||
.is_empty()
|
||||
{
|
||||
cluster_votes
|
||||
.entry(*my_pubkey)
|
||||
.or_default()
|
||||
.push(start_slot + i);
|
||||
}
|
||||
if old_root != tower.root() {
|
||||
return true;
|
||||
// Find a node representing the given slot
|
||||
fn find_node_and_update_simulation(&mut self, slot: u64) -> Option<&'a Node<u64>> {
|
||||
let mut successful_search_node: Option<&'a Node<u64>> = None;
|
||||
let mut found_node = None;
|
||||
for search_node in self.searchable_nodes.values() {
|
||||
if let Some((target, new_searchable_nodes)) = Self::find_node(search_node, slot) {
|
||||
successful_search_node = Some(search_node);
|
||||
found_node = Some(target);
|
||||
for node in new_searchable_nodes {
|
||||
self.searchable_nodes.insert(node.data, node);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
successful_search_node.map(|node| {
|
||||
self.searchable_nodes.remove(&node.data);
|
||||
});
|
||||
found_node
|
||||
}
|
||||
|
||||
fn init_state(
|
||||
num_keypairs: usize,
|
||||
) -> (
|
||||
HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
Vec<Pubkey>,
|
||||
Vec<Pubkey>,
|
||||
BankForks,
|
||||
ProgressMap,
|
||||
) {
|
||||
let keypairs: HashMap<_, _> = std::iter::repeat_with(|| {
|
||||
let node_keypair = Keypair::new();
|
||||
let vote_keypair = Keypair::new();
|
||||
let stake_keypair = Keypair::new();
|
||||
let node_pubkey = node_keypair.pubkey();
|
||||
(
|
||||
node_pubkey,
|
||||
ValidatorVoteKeypairs::new(node_keypair, vote_keypair, stake_keypair),
|
||||
)
|
||||
})
|
||||
.take(num_keypairs)
|
||||
.collect();
|
||||
let node_pubkeys: Vec<_> = keypairs
|
||||
.values()
|
||||
.map(|keys| keys.node_keypair.pubkey())
|
||||
.collect();
|
||||
let vote_pubkeys: Vec<_> = keypairs
|
||||
.values()
|
||||
.map(|keys| keys.vote_keypair.pubkey())
|
||||
.collect();
|
||||
fn find_node(
|
||||
node: &'a Node<u64>,
|
||||
slot: u64,
|
||||
) -> Option<(&'a Node<u64>, Vec<&'a Node<u64>>)> {
|
||||
if node.data == slot {
|
||||
Some((node, node.iter().collect()))
|
||||
} else {
|
||||
let mut search_result: Option<(&'a Node<u64>, Vec<&'a Node<u64>>)> = None;
|
||||
for child in node.iter() {
|
||||
if let Some((_, ref mut new_searchable_nodes)) = search_result {
|
||||
new_searchable_nodes.push(child);
|
||||
continue;
|
||||
}
|
||||
search_result = Self::find_node(child, slot);
|
||||
}
|
||||
|
||||
let (bank_forks, progress) = initialize_state(&keypairs, 10_000);
|
||||
(keypairs, node_pubkeys, vote_pubkeys, bank_forks, progress)
|
||||
search_result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub(crate) enum VoteFailures {
|
||||
LockedOut(u64),
|
||||
FailedThreshold(u64),
|
||||
}
|
||||
|
||||
// Setup BankForks with bank 0 and all the validator accounts
|
||||
pub(crate) fn initialize_state(
|
||||
validator_keypairs_map: &HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
stake: u64,
|
||||
) -> (BankForks, ProgressMap) {
|
||||
) -> (BankForks, HashMap<u64, ForkProgress>) {
|
||||
let validator_keypairs: Vec<_> = validator_keypairs_map.values().collect();
|
||||
let GenesisConfigInfo {
|
||||
genesis_config,
|
||||
mint_keypair,
|
||||
voting_keypair: _,
|
||||
} = create_genesis_config_with_vote_accounts(1_000_000_000, &validator_keypairs, stake);
|
||||
} = create_genesis_config_with_vote_accounts(1_000_000_000, &validator_keypairs);
|
||||
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
|
||||
@ -733,11 +695,8 @@ pub mod test {
|
||||
}
|
||||
|
||||
bank0.freeze();
|
||||
let mut progress = ProgressMap::default();
|
||||
progress.insert(
|
||||
0,
|
||||
ForkProgress::new(bank0.last_blockhash(), None, None, 0, 0),
|
||||
);
|
||||
let mut progress = HashMap::new();
|
||||
progress.insert(0, ForkProgress::new(bank0.last_blockhash()));
|
||||
(BankForks::new(0, bank0), progress)
|
||||
}
|
||||
|
||||
@ -761,26 +720,84 @@ pub mod test {
|
||||
stakes
|
||||
}
|
||||
|
||||
fn can_progress_on_fork(
|
||||
my_pubkey: &Pubkey,
|
||||
tower: &mut Tower,
|
||||
start_slot: u64,
|
||||
num_slots: u64,
|
||||
bank_forks: &RwLock<BankForks>,
|
||||
cluster_votes: &mut HashMap<Pubkey, Vec<u64>>,
|
||||
keypairs: &HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
progress: &mut HashMap<u64, ForkProgress>,
|
||||
) -> bool {
|
||||
// Check that within some reasonable time, validator can make a new
|
||||
// root on this fork
|
||||
let old_root = tower.root();
|
||||
let mut main_fork = tr(start_slot);
|
||||
let mut tip = main_fork.root_mut();
|
||||
|
||||
for i in 1..num_slots {
|
||||
tip.push_front(tr(start_slot + i));
|
||||
tip = tip.first_mut().unwrap();
|
||||
}
|
||||
let mut voting_simulator = VoteSimulator::new(&main_fork);
|
||||
for i in 1..num_slots {
|
||||
voting_simulator.simulate_vote(
|
||||
i + start_slot,
|
||||
&bank_forks,
|
||||
cluster_votes,
|
||||
&keypairs,
|
||||
keypairs.get(&my_pubkey).unwrap(),
|
||||
progress,
|
||||
tower,
|
||||
);
|
||||
if old_root != tower.root() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_votes() {
|
||||
// Init state
|
||||
let mut vote_simulator = VoteSimulator::new(1);
|
||||
let node_pubkey = vote_simulator.node_pubkeys[0];
|
||||
let mut tower = Tower::new_with_key(&node_pubkey);
|
||||
let node_keypair = Keypair::new();
|
||||
let vote_keypair = Keypair::new();
|
||||
let stake_keypair = Keypair::new();
|
||||
let node_pubkey = node_keypair.pubkey();
|
||||
|
||||
let mut keypairs = HashMap::new();
|
||||
keypairs.insert(
|
||||
node_pubkey,
|
||||
ValidatorVoteKeypairs::new(node_keypair, vote_keypair, stake_keypair),
|
||||
);
|
||||
|
||||
// Initialize BankForks
|
||||
let (bank_forks, mut progress) = initialize_state(&keypairs);
|
||||
let bank_forks = RwLock::new(bank_forks);
|
||||
|
||||
// Create the tree of banks
|
||||
let forks = tr(0) / (tr(1) / (tr(2) / (tr(3) / (tr(4) / tr(5)))));
|
||||
|
||||
// Set the voting behavior
|
||||
let mut cluster_votes = HashMap::new();
|
||||
let mut voting_simulator = VoteSimulator::new(&forks);
|
||||
let votes = vec![0, 1, 2, 3, 4, 5];
|
||||
cluster_votes.insert(node_pubkey, votes.clone());
|
||||
vote_simulator.fill_bank_forks(forks, &cluster_votes);
|
||||
|
||||
// Simulate the votes
|
||||
let mut tower = Tower::new_with_key(&node_pubkey);
|
||||
|
||||
let mut cluster_votes = HashMap::new();
|
||||
for vote in votes {
|
||||
assert!(vote_simulator
|
||||
.simulate_vote(vote, &node_pubkey, &mut tower,)
|
||||
assert!(voting_simulator
|
||||
.simulate_vote(
|
||||
vote,
|
||||
&bank_forks,
|
||||
&mut cluster_votes,
|
||||
&keypairs,
|
||||
keypairs.get(&node_pubkey).unwrap(),
|
||||
&mut progress,
|
||||
&mut tower,
|
||||
)
|
||||
.is_empty());
|
||||
}
|
||||
|
||||
@ -792,14 +809,21 @@ pub mod test {
|
||||
|
||||
#[test]
|
||||
fn test_double_partition() {
|
||||
// Init state
|
||||
let mut vote_simulator = VoteSimulator::new(2);
|
||||
let node_pubkey = vote_simulator.node_pubkeys[0];
|
||||
let vote_pubkey = vote_simulator.vote_pubkeys[0];
|
||||
let mut tower = Tower::new_with_key(&node_pubkey);
|
||||
solana_logger::setup();
|
||||
let node_keypair = Keypair::new();
|
||||
let vote_keypair = Keypair::new();
|
||||
let stake_keypair = Keypair::new();
|
||||
let node_pubkey = node_keypair.pubkey();
|
||||
let vote_pubkey = vote_keypair.pubkey();
|
||||
|
||||
let num_slots_to_try = 200;
|
||||
// Create the tree of banks
|
||||
let mut keypairs = HashMap::new();
|
||||
info!("my_pubkey: {}", node_pubkey);
|
||||
keypairs.insert(
|
||||
node_pubkey,
|
||||
ValidatorVoteKeypairs::new(node_keypair, vote_keypair, stake_keypair),
|
||||
);
|
||||
|
||||
// Create the tree of banks in a BankForks object
|
||||
let forks = tr(0)
|
||||
/ (tr(1)
|
||||
/ (tr(2)
|
||||
@ -816,37 +840,56 @@ pub mod test {
|
||||
/ (tr(44)
|
||||
// Minor fork 2
|
||||
/ (tr(45) / (tr(46) / (tr(47) / (tr(48) / (tr(49) / (tr(50)))))))
|
||||
/ (tr(110) / (tr(110 + 2 * num_slots_to_try))))))))))))));
|
||||
/ (tr(110)))))))))))));
|
||||
|
||||
// Set the successful voting behavior
|
||||
let mut cluster_votes = HashMap::new();
|
||||
let mut my_votes: Vec<Slot> = vec![];
|
||||
let next_unlocked_slot = 110;
|
||||
// Set the voting behavior
|
||||
let mut voting_simulator = VoteSimulator::new(&forks);
|
||||
let mut votes: Vec<Slot> = vec![];
|
||||
// Vote on the first minor fork
|
||||
my_votes.extend((0..=14).into_iter());
|
||||
votes.extend((0..=14).into_iter());
|
||||
// Come back to the main fork
|
||||
my_votes.extend((43..=44).into_iter());
|
||||
votes.extend((43..=44).into_iter());
|
||||
// Vote on the second minor fork
|
||||
my_votes.extend((45..=50).into_iter());
|
||||
// Vote to come back to main fork
|
||||
my_votes.push(next_unlocked_slot);
|
||||
cluster_votes.insert(node_pubkey, my_votes.clone());
|
||||
// Make the other validator vote fork to pass the threshold checks
|
||||
let other_votes = my_votes.clone();
|
||||
cluster_votes.insert(vote_simulator.node_pubkeys[1], other_votes);
|
||||
vote_simulator.fill_bank_forks(forks, &cluster_votes);
|
||||
votes.extend((45..=50).into_iter());
|
||||
|
||||
// Simulate the votes.
|
||||
for vote in &my_votes {
|
||||
let mut cluster_votes: HashMap<Pubkey, Vec<Slot>> = HashMap::new();
|
||||
let (bank_forks, mut progress) = initialize_state(&keypairs);
|
||||
let bank_forks = RwLock::new(bank_forks);
|
||||
|
||||
// Simulate the votes. Should fail on trying to come back to the main fork
|
||||
// at 106 exclusively due to threshold failure
|
||||
let mut tower = Tower::new_with_key(&node_pubkey);
|
||||
for vote in &votes {
|
||||
// All these votes should be ok
|
||||
assert!(vote_simulator
|
||||
.simulate_vote(*vote, &node_pubkey, &mut tower,)
|
||||
assert!(voting_simulator
|
||||
.simulate_vote(
|
||||
*vote,
|
||||
&bank_forks,
|
||||
&mut cluster_votes,
|
||||
&keypairs,
|
||||
keypairs.get(&node_pubkey).unwrap(),
|
||||
&mut progress,
|
||||
&mut tower,
|
||||
)
|
||||
.is_empty());
|
||||
}
|
||||
|
||||
// Try to come back to main fork
|
||||
let next_unlocked_slot = 110;
|
||||
assert!(voting_simulator
|
||||
.simulate_vote(
|
||||
next_unlocked_slot,
|
||||
&bank_forks,
|
||||
&mut cluster_votes,
|
||||
&keypairs,
|
||||
keypairs.get(&node_pubkey).unwrap(),
|
||||
&mut progress,
|
||||
&mut tower,
|
||||
)
|
||||
.is_empty());
|
||||
|
||||
info!("local tower: {:#?}", tower.lockouts.votes);
|
||||
let vote_accounts = vote_simulator
|
||||
.bank_forks
|
||||
let vote_accounts = bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(next_unlocked_slot)
|
||||
@ -856,17 +899,15 @@ pub mod test {
|
||||
let state = VoteState::from(&observed.1).unwrap();
|
||||
info!("observed tower: {:#?}", state.votes);
|
||||
|
||||
let num_slots_to_try = 200;
|
||||
cluster_votes
|
||||
.get_mut(&vote_simulator.node_pubkeys[1])
|
||||
.unwrap()
|
||||
.extend(next_unlocked_slot + 1..next_unlocked_slot + num_slots_to_try);
|
||||
assert!(vote_simulator.can_progress_on_fork(
|
||||
assert!(can_progress_on_fork(
|
||||
&node_pubkey,
|
||||
&mut tower,
|
||||
next_unlocked_slot,
|
||||
num_slots_to_try,
|
||||
200,
|
||||
&bank_forks,
|
||||
&mut cluster_votes,
|
||||
&keypairs,
|
||||
&mut progress
|
||||
));
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user