Compare commits
359 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
eb0a448b99 | ||
|
e6f54ffd56 | ||
|
44315895cf | ||
|
9899e6fb39 | ||
|
da1796f97a | ||
|
501fea7a3c | ||
|
2583174cbd | ||
|
72aa24db2c | ||
|
bbf53f9050 | ||
|
e7a95b1189 | ||
|
311a1a38dc | ||
|
a1828a0e45 | ||
|
01d9062f7b | ||
|
446e02bad1 | ||
|
3955778cb6 | ||
|
4f4a0b9dc8 | ||
|
97c1c944fb | ||
|
7890957250 | ||
|
535c7a44db | ||
|
fbbc640958 | ||
|
3559062c00 | ||
|
1a7e5669bf | ||
|
5511d52c62 | ||
|
50d5978b00 | ||
|
cea722ac09 | ||
|
82b087e51e | ||
|
da682eaef6 | ||
|
688562c049 | ||
|
50e6fad0e6 | ||
|
d5cce435ce | ||
|
8f75cfe247 | ||
|
686b775e35 | ||
|
a3be6bacc6 | ||
|
b493d554ad | ||
|
0fc3bcabfd | ||
|
7cf36c488d | ||
|
89c39d0be1 | ||
|
f5eb5a3ba6 | ||
|
64c2e759ab | ||
|
a09ee672a6 | ||
|
057b5d7e24 | ||
|
fe4c59e38d | ||
|
305d5d97d8 | ||
|
05f464798f | ||
|
96d8ee9e07 | ||
|
471a3f991b | ||
|
fd9e003ae1 | ||
|
e26ff09df7 | ||
|
fdcf589f7c | ||
|
f44dfc8d04 | ||
|
c736c4633e | ||
|
7099d07fd3 | ||
|
e3b47d22d7 | ||
|
f789da1e20 | ||
|
b74bb12ebe | ||
|
ddf019c1a4 | ||
|
74d57b1c2f | ||
|
086e653a0b | ||
|
225d15bde8 | ||
|
6d8d5d1379 | ||
|
fcd2a78d73 | ||
|
5cf52c3c20 | ||
|
bc334427e3 | ||
|
d0cac2a2ea | ||
|
3743e44fb3 | ||
|
f5ef999b31 | ||
|
55d5339daa | ||
|
594b69395a | ||
|
6dc62bfb77 | ||
|
08b53c0963 | ||
|
b5baa966ac | ||
|
ff38a46af6 | ||
|
841f596b26 | ||
|
5e1497856b | ||
|
e085b580b5 | ||
|
d967ff0138 | ||
|
71e3a99742 | ||
|
640f4a1ec7 | ||
|
8d904877ef | ||
|
fbe4e95e6a | ||
|
e7e7cbe632 | ||
|
7e5b75fa7e | ||
|
60beb509f7 | ||
|
15f6b6ccd6 | ||
|
f56f3d81b5 | ||
|
098c94352d | ||
|
c929e8e02b | ||
|
80f2c485ba | ||
|
f855f4d1c0 | ||
|
81a26aa4fc | ||
|
855cf9a362 | ||
|
96ba314281 | ||
|
e52e6dfbaa | ||
|
181ff3d13e | ||
|
195ce0ed79 | ||
|
b24e301201 | ||
|
fb492efda8 | ||
|
c40216350c | ||
|
d031bbcf2e | ||
|
c183c3a5ec | ||
|
f04d4af4f2 | ||
|
ea0a3521ed | ||
|
a75898a415 | ||
|
a77fce465a | ||
|
719f162229 | ||
|
a39cc8d21f | ||
|
87767b181d | ||
|
88b19e10cb | ||
|
b42cda32ff | ||
|
2344391c48 | ||
|
207d13e429 | ||
|
360f166f5a | ||
|
b4deeb8e36 | ||
|
b3e1fde8b2 | ||
|
b838aba840 | ||
|
c8b3d0ba07 | ||
|
e7106278e9 | ||
|
63cf168fef | ||
|
71ea198a07 | ||
|
f6b65b033e | ||
|
0311ad5ddf | ||
|
f5454e62a1 | ||
|
89ea4dfa8b | ||
|
fefcfdba80 | ||
|
1072bd7640 | ||
|
a7280f117a | ||
|
d46a19098a | ||
|
44fffcbb1c | ||
|
e14c2f94f4 | ||
|
437c356626 | ||
|
fd68f8ba2e | ||
|
2374664e95 | ||
|
2cb9ca5966 | ||
|
4f247a232f | ||
|
15a2c73826 | ||
|
d23f1436c5 | ||
|
70c87d1a23 | ||
|
053ce10ce5 | ||
|
055eb360c2 | ||
|
25cd1ceeeb | ||
|
52ee3b1cee | ||
|
bbadcca414 | ||
|
e9eba97299 | ||
|
920b63944e | ||
|
8104895a07 | ||
|
c9e646b86b | ||
|
7c47db1e3d | ||
|
c619e9b560 | ||
|
ccd48923a0 | ||
|
4e797cc867 | ||
|
9627bfced3 | ||
|
f823b10597 | ||
|
c9e56c9749 | ||
|
da7482d631 | ||
|
97650c7f37 | ||
|
e738bf1c9a | ||
|
afebb2a8a5 | ||
|
4e4fd03b65 | ||
|
049ca18dc5 | ||
|
495c64556e | ||
|
747e91d434 | ||
|
6d4f6e79b0 | ||
|
98e9f34704 | ||
|
70f74174e8 | ||
|
70985f82f1 | ||
|
3b2bdd9f8a | ||
|
d33ae59fbf | ||
|
9ead7ca11a | ||
|
dbcef35f7d | ||
|
9e733d7d9b | ||
|
39f1240ec2 | ||
|
fa249721fa | ||
|
137793cd4c | ||
|
47d8608aee | ||
|
ed410aea10 | ||
|
957dfa8f73 | ||
|
98095b6f8d | ||
|
a2c32d7d0e | ||
|
b15d826476 | ||
|
ed97a2578d | ||
|
89f61f0b41 | ||
|
04cc9c1148 | ||
|
8314ab4508 | ||
|
3a98042753 | ||
|
60d316c9fd | ||
|
e324c221a6 | ||
|
61246999ac | ||
|
e476dc4eaa | ||
|
ee18e7668b | ||
|
62db7f6562 | ||
|
2e9b501355 | ||
|
089a99f1e3 | ||
|
57961b1d17 | ||
|
fe8b2b7850 | ||
|
0bf45cbab6 | ||
|
5877427389 | ||
|
25141288f4 | ||
|
b28d10d46f | ||
|
b6dc48da75 | ||
|
f2d929c12d | ||
|
c49b89091a | ||
|
23fe3a86d9 | ||
|
2f778725d6 | ||
|
93a119a51e | ||
|
65a7b536c9 | ||
|
1281483a8c | ||
|
4312841433 | ||
|
b859acbfea | ||
|
40a3885d3b | ||
|
36b7c2ea97 | ||
|
24bd4ff6d4 | ||
|
69b3f10207 | ||
|
9922f09a1d | ||
|
38a99c0c25 | ||
|
7031235714 | ||
|
dfb2356a9a | ||
|
010794806a | ||
|
6f95d5f72a | ||
|
2720b939fd | ||
|
a25c3fcf7d | ||
|
7cc4810174 | ||
|
c1a55bf249 | ||
|
f19778b7d9 | ||
|
eecdacac42 | ||
|
429f130532 | ||
|
19b9839dfc | ||
|
ad2bf3afa6 | ||
|
5c739ba236 | ||
|
9fac507606 | ||
|
d5a37cb06e | ||
|
86eb0157c0 | ||
|
072dab0948 | ||
|
e20e79f412 | ||
|
f118db81ce | ||
|
4ecb78d303 | ||
|
0a28e40606 | ||
|
4d7a5a9daf | ||
|
64cf6b4388 | ||
|
f334c3b895 | ||
|
15a7bcd4fe | ||
|
8d6636d02a | ||
|
cf896dbeee | ||
|
e5b60b75f8 | ||
|
0e155fdbd9 | ||
|
b79a337ddd | ||
|
c4050f541d | ||
|
f0b74a4ecf | ||
|
f7979378fd | ||
|
d7c5607982 | ||
|
91ab5ae990 | ||
|
605e767259 | ||
|
597618846b | ||
|
712267bf51 | ||
|
eb9cef0cd4 | ||
|
62e0e19961 | ||
|
9aee9cb867 | ||
|
2b11558b36 | ||
|
18c4e1b023 | ||
|
6bac44ed92 | ||
|
8cb622084f | ||
|
38f7e9a979 | ||
|
a536f779ee | ||
|
84a5e5ec97 | ||
|
dd33aae3cf | ||
|
be2ace47e3 | ||
|
53b074aa35 | ||
|
a4ad2925a2 | ||
|
edfbd8d65a | ||
|
e0ae54fd7e | ||
|
60297951ec | ||
|
e0f9f72a2c | ||
|
5236acf4b0 | ||
|
5dd61b5db2 | ||
|
8752bf0826 | ||
|
b1712e80ec | ||
|
2fe1a4677c | ||
|
f76c128f4f | ||
|
b143b9c3c2 | ||
|
b4178b75e7 | ||
|
c54b751df7 | ||
|
0fde9e893f | ||
|
d24abbdac9 | ||
|
3b03985f28 | ||
|
d05bfa08c7 | ||
|
9da2ac7a44 | ||
|
9e95d0fb58 | ||
|
94cad9873c | ||
|
f33171b32f | ||
|
aa6406f263 | ||
|
77864a6bee | ||
|
b51715d33c | ||
|
7d395177d4 | ||
|
77ba6d6784 | ||
|
4bf0a54ed7 | ||
|
8a526f2f53 | ||
|
43f99bdb31 | ||
|
0008dc62e4 | ||
|
7e8174fb79 | ||
|
4ad2ebcde9 | ||
|
da183d655a | ||
|
2e449276be | ||
|
8cac6835c0 | ||
|
677c184e47 | ||
|
f36cfb92f7 | ||
|
e7062de05f | ||
|
a443e2e773 | ||
|
3a6db787e2 | ||
|
f3c986385f | ||
|
3df811348f | ||
|
e8c86ed3e5 | ||
|
489a7bb576 | ||
|
688dd85e61 | ||
|
fe54a30084 | ||
|
80942841a2 | ||
|
d2808a8e29 | ||
|
f8413a28b5 | ||
|
bc96332899 | ||
|
ceeeb3c9dd | ||
|
bd058ec8f1 | ||
|
4b5ac44fc8 | ||
|
fef979f0e5 | ||
|
cca2cdf39b | ||
|
6e91996606 | ||
|
99be00d61f | ||
|
68f808026e | ||
|
0c7ab0a1bb | ||
|
3d8ccbc079 | ||
|
275d096a46 | ||
|
6d70a06b23 | ||
|
7e68b2e1bd | ||
|
f0d761630e | ||
|
1986927eb6 | ||
|
9a0ea61007 | ||
|
51a70e52f2 | ||
|
9797c93db3 | ||
|
9598114658 | ||
|
d3ef061044 | ||
|
1f102d2617 | ||
|
5e97bd3d8a | ||
|
ed06e8b85d | ||
|
10b9225edb | ||
|
b1b5ddd2b9 | ||
|
6b9b107ead | ||
|
3fef98fd1e | ||
|
e999823b4b | ||
|
1e46a5b147 | ||
|
567a1cb944 | ||
|
2996cebfaa | ||
|
7a1889aaf9 | ||
|
9188153b7d | ||
|
4b9f2e987a | ||
|
bb5c76483a | ||
|
aafbb251b9 | ||
|
dd32540ceb | ||
|
e1a9cbaf3c | ||
|
83740246fc | ||
|
7a53ca18a6 | ||
|
c1a8637cb5 | ||
|
d6831309cd |
2
.buildkite/env/secrets.ejson
vendored
2
.buildkite/env/secrets.ejson
vendored
@@ -2,6 +2,6 @@
|
||||
"_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f",
|
||||
"_comment": "These credentials are encrypted and pose no risk",
|
||||
"environment": {
|
||||
"CODECOV_TOKEN": "EJ[1:KToenD1Sr3w82lHGxz1n+j3hwNlLk/1pYrjZHlvY6kE=:hN1Q25omtJ+4yYVn+qzIsPLKT3O6J9XN:DMLNLXi/pkWgvwF6gNIcNF222sgsRR9LnwLZYj0P0wGj7q6w8YQnd1Rskj+sRroI/z5pQg==]"
|
||||
"CODECOV_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:3K68mE38LJ2RB98VWmjuNLFBNn1XTGR4:cR4r05/TOZQKmEZp1v4CSgUJtC6QJiOaL85QjXW0qZ061fMnsBA8AtAPMDoDq4WCGOZM1A==]"
|
||||
}
|
||||
}
|
||||
|
@@ -36,7 +36,4 @@ export CARGO_TARGET_CACHE=$HOME/cargo-target-cache/"$CHANNEL"-"$BUILDKITE_LABEL"
|
||||
# `std:
|
||||
# "found possibly newer version of crate `std` which `xyz` depends on
|
||||
rm -rf target/bpfel-unknown-unknown
|
||||
if [[ $BUILDKITE_LABEL = "stable-perf" ]]; then
|
||||
rm -rf target/release
|
||||
fi
|
||||
)
|
||||
|
Binary file not shown.
4
.cache/fontconfig/CACHEDIR.TAG
Normal file
4
.cache/fontconfig/CACHEDIR.TAG
Normal file
@@ -0,0 +1,4 @@
|
||||
Signature: 8a477f597d28d172789f06886806bc55
|
||||
# This file is a cache directory tag created by fontconfig.
|
||||
# For information about cache directory tags, see:
|
||||
# http://www.brynosaurus.com/cachedir/
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
/docs/html/
|
||||
/docs/src/tests.ok
|
||||
/docs/src/cli/usage.md
|
||||
/docs/src/.gitbook/assets/*.svg
|
||||
/farf/
|
||||
/solana-release/
|
||||
/solana-release.tar.bz2
|
||||
@@ -10,8 +14,6 @@
|
||||
|
||||
/config/
|
||||
|
||||
.cache
|
||||
|
||||
# log files
|
||||
*.log
|
||||
log-*.txt
|
||||
|
40
.mergify.yml
40
.mergify.yml
@@ -50,6 +50,22 @@ pull_request_rules:
|
||||
label:
|
||||
add:
|
||||
- automerge
|
||||
- name: v1.2 backport
|
||||
conditions:
|
||||
- label=v1.2
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.2
|
||||
- name: v1.3 backport
|
||||
conditions:
|
||||
- label=v1.3
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.3
|
||||
- name: v1.4 backport
|
||||
conditions:
|
||||
- label=v1.4
|
||||
@@ -58,27 +74,3 @@ pull_request_rules:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.4
|
||||
- name: v1.5 backport
|
||||
conditions:
|
||||
- label=v1.5
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.5
|
||||
- name: v1.6 backport
|
||||
conditions:
|
||||
- label=v1.6
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.6
|
||||
- name: v1.7 backport
|
||||
conditions:
|
||||
- label=v1.7
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.7
|
||||
|
@@ -73,7 +73,7 @@ jobs:
|
||||
|
||||
language: node_js
|
||||
node_js:
|
||||
- "lts/*"
|
||||
- "node"
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
2804
Cargo.lock
generated
2804
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
@@ -1,6 +1,5 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"accounts-cluster-bench",
|
||||
"bench-exchange",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
@@ -21,7 +20,6 @@ members = [
|
||||
"perf",
|
||||
"validator",
|
||||
"genesis",
|
||||
"genesis-utils",
|
||||
"gossip",
|
||||
"install",
|
||||
"keygen",
|
||||
@@ -30,8 +28,8 @@ members = [
|
||||
"local-cluster",
|
||||
"logger",
|
||||
"log-analyzer",
|
||||
"merkle-root-bench",
|
||||
"merkle-tree",
|
||||
"stake-o-matic",
|
||||
"storage-bigtable",
|
||||
"storage-proto",
|
||||
"streamer",
|
||||
@@ -39,22 +37,22 @@ members = [
|
||||
"metrics",
|
||||
"net-shaper",
|
||||
"notifier",
|
||||
"poh",
|
||||
"poh-bench",
|
||||
"program-test",
|
||||
"programs/secp256k1",
|
||||
"programs/bpf_loader",
|
||||
"programs/budget",
|
||||
"programs/config",
|
||||
"programs/exchange",
|
||||
"programs/failure",
|
||||
"programs/noop",
|
||||
"programs/ownable",
|
||||
"programs/stake",
|
||||
"programs/vest",
|
||||
"programs/vote",
|
||||
"remote-wallet",
|
||||
"rpc",
|
||||
"ramp-tps",
|
||||
"runtime",
|
||||
"runtime/store-tool",
|
||||
"sdk",
|
||||
"sdk/cargo-build-bpf",
|
||||
"sdk/cargo-test-bpf",
|
||||
@@ -68,6 +66,7 @@ members = [
|
||||
"upload-perf",
|
||||
"net-utils",
|
||||
"version",
|
||||
"vote-signer",
|
||||
"cli",
|
||||
"rayon-threadlimit",
|
||||
"watchtower",
|
||||
@@ -76,6 +75,3 @@ members = [
|
||||
exclude = [
|
||||
"programs/bpf",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
|
49
README.md
49
README.md
@@ -1,6 +1,6 @@
|
||||
<p align="center">
|
||||
<a href="https://solana.com">
|
||||
<img alt="Solana" src="https://i.imgur.com/uBVzyX3.png" width="250" />
|
||||
<img alt="Solana" src="https://i.imgur.com/OMnvVEz.png" width="250" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -29,13 +29,7 @@ On Linux systems you may need to install libssl-dev, pkg-config, zlib1g-dev, etc
|
||||
|
||||
```bash
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang make
|
||||
```
|
||||
|
||||
On Mac M1s, make sure you set up your terminal & homebrew [to use](https://5balloons.info/correct-way-to-install-and-use-homebrew-on-m1-macs/) Rosetta. You can install it with:
|
||||
|
||||
```bash
|
||||
$ softwareupdate --install-rosetta
|
||||
$ sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang
|
||||
```
|
||||
|
||||
## **2. Download the source code.**
|
||||
@@ -113,41 +107,4 @@ send us that patch!
|
||||
|
||||
# Disclaimer
|
||||
|
||||
All claims, content, designs, algorithms, estimates, roadmaps,
|
||||
specifications, and performance measurements described in this project
|
||||
are done with the Solana Foundation's ("SF") best efforts. It is up to
|
||||
the reader to check and validate their accuracy and truthfulness.
|
||||
Furthermore nothing in this project constitutes a solicitation for
|
||||
investment.
|
||||
|
||||
Any content produced by SF or developer resources that SF provides, are
|
||||
for educational and inspiration purposes only. SF does not encourage,
|
||||
induce or sanction the deployment, integration or use of any such
|
||||
applications (including the code comprising the Solana blockchain
|
||||
protocol) in violation of applicable laws or regulations and hereby
|
||||
prohibits any such deployment, integration or use. This includes use of
|
||||
any such applications by the reader (a) in violation of export control
|
||||
or sanctions laws of the United States or any other applicable
|
||||
jurisdiction, (b) if the reader is located in or ordinarily resident in
|
||||
a country or territory subject to comprehensive sanctions administered
|
||||
by the U.S. Office of Foreign Assets Control (OFAC), or (c) if the
|
||||
reader is or is working on behalf of a Specially Designated National
|
||||
(SDN) or a person subject to similar blocking or denied party
|
||||
prohibitions.
|
||||
|
||||
The reader should be aware that U.S. export control and sanctions laws
|
||||
prohibit U.S. persons (and other persons that are subject to such laws)
|
||||
from transacting with persons in certain countries and territories or
|
||||
that are on the SDN list. As a project based primarily on open-source
|
||||
software, it is possible that such sanctioned persons may nevertheless
|
||||
bypass prohibitions, obtain the code comprising the Solana blockchain
|
||||
protocol (or other project code or applications) and deploy, integrate,
|
||||
or otherwise use it. Accordingly, there is a risk to individuals that
|
||||
other persons using the Solana blockchain protocol may be sanctioned
|
||||
persons and that transactions with such persons would be a violation of
|
||||
U.S. export controls and sanctions law. This risk applies to
|
||||
individuals, organizations, and other ecosystem participants that
|
||||
deploy, integrate, or use the Solana blockchain protocol code directly
|
||||
(e.g., as a node operator), and individuals that transact on the Solana
|
||||
blockchain through light clients, third party interfaces, and/or wallet
|
||||
software.
|
||||
All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment.
|
||||
|
169
SECURITY.md
169
SECURITY.md
@@ -1,169 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
1. [Reporting security problems](#reporting)
|
||||
4. [Security Bug Bounties](#bounty)
|
||||
2. [Incident Response Process](#process)
|
||||
|
||||
<a name="reporting"></a>
|
||||
## Reporting security problems to Solana
|
||||
|
||||
**DO NOT CREATE AN ISSUE** to report a security problem. Instead, please send an
|
||||
email to security@solana.com and provide your github username so we can add you
|
||||
to a new draft security advisory for further discussion.
|
||||
|
||||
Expect a response as fast as possible, within one business day at the latest.
|
||||
|
||||
<a name="bounty"></a>
|
||||
## Security Bug Bounties
|
||||
We offer bounties for critical security issues. Please see below for more details.
|
||||
|
||||
Loss of Funds:
|
||||
$500,000 USD in locked SOL tokens (locked for 12 months)
|
||||
* Theft of funds without users signature from any account
|
||||
* Theft of funds without users interaction in system, token, stake, vote programs
|
||||
* Theft of funds that requires users signature - creating a vote program that drains the delegated stakes.
|
||||
|
||||
Consensus/Safety Violations:
|
||||
$250,000 USD in locked SOL tokens (locked for 12 months)
|
||||
* Consensus safety violation
|
||||
* Tricking a validator to accept an optimistic confirmation or rooted slot without a double vote, etc..
|
||||
|
||||
Other Attacks:
|
||||
$100,000 USD in locked SOL tokens (locked for 12 months)
|
||||
* Protocol liveness attacks,
|
||||
* Eclipse attacks,
|
||||
* Remote attacks that partition the network,
|
||||
|
||||
DoS Attacks:
|
||||
$25,000 USD in locked SOL tokens (locked for 12 months)
|
||||
* Remote resource exaustion via Non-RPC protocols
|
||||
|
||||
RPC DoS/Crashes:
|
||||
$5,000 USD in locked SOL tokens (locked for 12 months)
|
||||
* RPC attacks
|
||||
|
||||
Out of Scope:
|
||||
The following components are out of scope for the bounty program
|
||||
* Metrics: `/metrics` in the monorepo as well as https://metrics.solana.com
|
||||
* Explorer: `/explorer` in the monorepo as well as https://explorer.solana.com
|
||||
* Any encrypted credentials, auth tokens, etc. checked into the repo
|
||||
* Bugs in dependencies. Please take them upstream!
|
||||
* Attacks that require social engineering
|
||||
|
||||
Eligibility:
|
||||
* The participant submitting the bug report shall follow the process outlined within this document
|
||||
* Valid exploits can be eligible even if they are not successfully executed on the cluster
|
||||
* Multiple submissions for the same class of exploit are still eligible for compensation, though may be compensated at a lower rate, however these will be assessed on a case-by-case basis
|
||||
* Participants must complete KYC and sign the participation agreement here when the registrations are open https://solana.com/validator-registration. Security exploits will still be assessed and open for submission at all times. This needs only be done prior to distribution of tokens.
|
||||
|
||||
Payment of Bug Bounties:
|
||||
* Payments for eligible bug reports are distributed monthly.
|
||||
* Bounties for all bug reports submitted in a given month are paid out in the middle of the
|
||||
following month.
|
||||
* The SOL/USD conversion rate used for payments is the market price at the end of
|
||||
the last day of the month for the month in which the bug was submitted.
|
||||
* The reference for this price is the Closing Price given by Coingecko.com on
|
||||
that date given here:
|
||||
https://www.coingecko.com/en/coins/solana/historical_data/usd#panel
|
||||
* For example, for all bugs submitted in March 2021, the SOL/USD price for bug
|
||||
payouts is the Close price on 2021-03-31 of $19.49. This applies to all bugs
|
||||
submitted in March 2021, to be paid in mid-April 2021.
|
||||
* Bug bounties are paid out in
|
||||
[stake accounts](https://solana.com/staking) with a
|
||||
[lockup](https://docs.solana.com/staking/stake-accounts#lockups)
|
||||
expiring 12 months from the last day of the month in which the bug was submitted.
|
||||
|
||||
<a name="process"></a>
|
||||
## Incident Response Process
|
||||
|
||||
In case an incident is discovered or reported, the following process will be
|
||||
followed to contain, respond and remediate:
|
||||
|
||||
### 1. Establish a new draft security advisory
|
||||
In response to an email to security@solana.com, a member of the `solana-labs/admins` group will
|
||||
1. Create a new draft security advisory for the incident at https://github.com/solana-labs/solana/security/advisories
|
||||
1. Add the reporter's github user and the `solana-labs/security-incident-response` group to the draft security advisory
|
||||
1. Create a private fork of the repository (grey button towards the bottom of the page)
|
||||
1. Respond to the reporter by email, sharing a link to the draft security advisory
|
||||
|
||||
### 2. Triage
|
||||
Within the draft security advisory, discuss and determine the severity of the
|
||||
issue. If necessary, members of the `solana-labs/security-incident-response`
|
||||
group may add other github users to the advisory to assist.
|
||||
|
||||
If it is determined that this not a critical network issue then the advisory
|
||||
should be closed and if more follow-up is required a normal Solana public github
|
||||
issue should be created.
|
||||
|
||||
### 3. Prepare Fixes
|
||||
For the affected branches, typically all three (edge, beta and stable), prepare
|
||||
a fix for the issue and push them to the corresponding branch in the private
|
||||
repository associated with the draft security advisory.
|
||||
|
||||
There is no CI available in the private repository so you must build from source
|
||||
and manually verify fixes.
|
||||
|
||||
Code review from the reporter is ideal, as well as from multiple members of the
|
||||
core development team.
|
||||
|
||||
### 4. Notify Security Group Validators
|
||||
Once an ETA is available for the fix, a member of the
|
||||
`solana-labs/security-incident-response` group should notify the validators so
|
||||
they can prepare for an update using the "Solana Red Alert" notification system.
|
||||
|
||||
The teams are all over the world and it's critical to provide actionable
|
||||
information at the right time. Don't be the person that wakes everybody up at
|
||||
2am when a fix won't be available for hours.
|
||||
|
||||
### 5. Ship the patch
|
||||
Once the fix is accepted, a member of the
|
||||
`solana-labs/security-incident-response` group should prepare a single patch
|
||||
file for each affected branch. The commit title for the patch should only
|
||||
contain the advisory id, and not disclose any further details about the
|
||||
incident.
|
||||
|
||||
Copy the patches to https://release.solana.com/ under a subdirectory named after
|
||||
the advisory id (example:
|
||||
https://release.solana.com/GHSA-hx59-f5g4-jghh/v1.4.patch). Contact a member of
|
||||
the `solana-labs/admins` group if you require access to release.solana.com
|
||||
|
||||
Using the "Solana Red Alert" channel:
|
||||
1. Notify validators that there's an issue and a patch will be provided in X minutes
|
||||
2. If X minutes expires and there's no patch, notify of the delay and provide a
|
||||
new ETA
|
||||
3. Provide links to patches of https://release.solana.com/ for each affected branch
|
||||
|
||||
Validators can be expected to build the patch from source against the latest
|
||||
release for the affected branch.
|
||||
|
||||
Since the software version will not change after the patch is applied, request
|
||||
that each validator notify in the existing channel once they've updated. Manually
|
||||
monitor the roll out until a sufficient amount of stake has updated - typically
|
||||
at least 33.3% or 66.6% depending on the issue.
|
||||
|
||||
### 6. Public Disclosure and Release
|
||||
Once the fix has been deployed to the security group validators, the patches from the security
|
||||
advisory may be merged into the main source repository. A new official release
|
||||
for each affected branch should be shipped and all validators requested to
|
||||
upgrade as quickly as possible.
|
||||
|
||||
### 7. Security Advisory Bounty Accounting and Cleanup
|
||||
|
||||
If this issue is eligible for a bounty, prefix the title of the security
|
||||
advisory with one of the following, depending on the severity:
|
||||
* `[Bounty Category: Critical: Loss of Funds]`
|
||||
* `[Bounty Category: Critical: Loss of Availability]`
|
||||
* `[Bounty Category: Critical: DoS]`
|
||||
* `[Bounty Category: Critical: Other]`
|
||||
* `[Bounty Category: Non-critical]`
|
||||
* `[Bounty Category: RPC]`
|
||||
|
||||
Confirm with the reporter that they agree with the severity assessment, and
|
||||
discuss as required to reach a conclusion.
|
||||
|
||||
We currently do not use the Github workflow to publish security advisories.
|
||||
Once the issue and fix have been disclosed, and a bounty category is assessed if
|
||||
appropriate, the GitHub security advisory is no longer needed and can be closed.
|
||||
|
||||
Bounties are currently awarded once a quarter (TODO: link to this process, or
|
||||
inline the workflow)
|
@@ -1,11 +1,10 @@
|
||||
[package]
|
||||
name = "solana-account-decoder"
|
||||
version = "1.7.2"
|
||||
version = "1.4.14"
|
||||
description = "Solana account decoder"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-account-decoder"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
@@ -16,13 +15,14 @@ bs58 = "0.3.1"
|
||||
bv = "0.11.1"
|
||||
Inflector = "0.11.4"
|
||||
lazy_static = "1.4.0"
|
||||
serde = "1.0.122"
|
||||
serde = "1.0.112"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-config-program = { path = "../programs/config", version = "=1.7.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.2" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.7.2" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.1.1", features = ["no-entrypoint"] }
|
||||
solana-config-program = { path = "../programs/config", version = "1.4.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.4.14" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.4.14" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.4.14" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.0.0", features = ["no-entrypoint"] }
|
||||
thiserror = "1.0"
|
||||
zstd = "0.5.1"
|
||||
|
||||
|
@@ -1,11 +1,9 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod parse_account_data;
|
||||
pub mod parse_bpf_loader;
|
||||
pub mod parse_config;
|
||||
pub mod parse_nonce;
|
||||
pub mod parse_stake;
|
||||
@@ -16,10 +14,7 @@ pub mod validator_info;
|
||||
|
||||
use {
|
||||
crate::parse_account_data::{parse_account_data, AccountAdditionalData, ParsedAccount},
|
||||
solana_sdk::{
|
||||
account::ReadableAccount, account::WritableAccount, clock::Epoch,
|
||||
fee_calculator::FeeCalculator, pubkey::Pubkey,
|
||||
},
|
||||
solana_sdk::{account::Account, clock::Epoch, fee_calculator::FeeCalculator, pubkey::Pubkey},
|
||||
std::{
|
||||
io::{Read, Write},
|
||||
str::FromStr,
|
||||
@@ -27,7 +22,6 @@ use {
|
||||
};
|
||||
|
||||
pub type StringAmount = String;
|
||||
pub type StringDecimals = String;
|
||||
|
||||
/// A duplicate representation of an Account for pretty JSON serialization
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
@@ -48,7 +42,7 @@ pub enum UiAccountData {
|
||||
Binary(String, UiAccountEncoding),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum UiAccountEncoding {
|
||||
Binary, // Legacy. Retained for RPC backwards compatibility
|
||||
@@ -60,61 +54,58 @@ pub enum UiAccountEncoding {
|
||||
}
|
||||
|
||||
impl UiAccount {
|
||||
pub fn encode<T: ReadableAccount>(
|
||||
pub fn encode(
|
||||
pubkey: &Pubkey,
|
||||
account: &T,
|
||||
account: Account,
|
||||
encoding: UiAccountEncoding,
|
||||
additional_data: Option<AccountAdditionalData>,
|
||||
data_slice_config: Option<UiDataSliceConfig>,
|
||||
) -> Self {
|
||||
let data = match encoding {
|
||||
UiAccountEncoding::Binary => UiAccountData::LegacyBinary(
|
||||
bs58::encode(slice_data(&account.data(), data_slice_config)).into_string(),
|
||||
bs58::encode(slice_data(&account.data, data_slice_config)).into_string(),
|
||||
),
|
||||
UiAccountEncoding::Base58 => UiAccountData::Binary(
|
||||
bs58::encode(slice_data(&account.data(), data_slice_config)).into_string(),
|
||||
bs58::encode(slice_data(&account.data, data_slice_config)).into_string(),
|
||||
encoding,
|
||||
),
|
||||
UiAccountEncoding::Base64 => UiAccountData::Binary(
|
||||
base64::encode(slice_data(&account.data(), data_slice_config)),
|
||||
base64::encode(slice_data(&account.data, data_slice_config)),
|
||||
encoding,
|
||||
),
|
||||
UiAccountEncoding::Base64Zstd => {
|
||||
let mut encoder = zstd::stream::write::Encoder::new(Vec::new(), 0).unwrap();
|
||||
match encoder
|
||||
.write_all(slice_data(&account.data(), data_slice_config))
|
||||
.write_all(slice_data(&account.data, data_slice_config))
|
||||
.and_then(|()| encoder.finish())
|
||||
{
|
||||
Ok(zstd_data) => UiAccountData::Binary(base64::encode(zstd_data), encoding),
|
||||
Err(_) => UiAccountData::Binary(
|
||||
base64::encode(slice_data(&account.data(), data_slice_config)),
|
||||
base64::encode(slice_data(&account.data, data_slice_config)),
|
||||
UiAccountEncoding::Base64,
|
||||
),
|
||||
}
|
||||
}
|
||||
UiAccountEncoding::JsonParsed => {
|
||||
if let Ok(parsed_data) =
|
||||
parse_account_data(pubkey, &account.owner(), &account.data(), additional_data)
|
||||
parse_account_data(pubkey, &account.owner, &account.data, additional_data)
|
||||
{
|
||||
UiAccountData::Json(parsed_data)
|
||||
} else {
|
||||
UiAccountData::Binary(
|
||||
base64::encode(&account.data()),
|
||||
UiAccountEncoding::Base64,
|
||||
)
|
||||
UiAccountData::Binary(base64::encode(&account.data), UiAccountEncoding::Base64)
|
||||
}
|
||||
}
|
||||
};
|
||||
UiAccount {
|
||||
lamports: account.lamports(),
|
||||
lamports: account.lamports,
|
||||
data,
|
||||
owner: account.owner().to_string(),
|
||||
executable: account.executable(),
|
||||
rent_epoch: account.rent_epoch(),
|
||||
owner: account.owner.to_string(),
|
||||
executable: account.executable,
|
||||
rent_epoch: account.rent_epoch,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode<T: WritableAccount>(&self) -> Option<T> {
|
||||
pub fn decode(&self) -> Option<Account> {
|
||||
let data = match &self.data {
|
||||
UiAccountData::Json(_) => None,
|
||||
UiAccountData::LegacyBinary(blob) => bs58::decode(blob).into_vec().ok(),
|
||||
@@ -134,13 +125,13 @@ impl UiAccount {
|
||||
UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None,
|
||||
},
|
||||
}?;
|
||||
Some(T::create(
|
||||
self.lamports,
|
||||
Some(Account {
|
||||
lamports: self.lamports,
|
||||
data,
|
||||
Pubkey::from_str(&self.owner).ok()?,
|
||||
self.executable,
|
||||
self.rent_epoch,
|
||||
))
|
||||
owner: Pubkey::from_str(&self.owner).ok()?,
|
||||
executable: self.executable,
|
||||
rent_epoch: self.rent_epoch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +181,6 @@ fn slice_data(data: &[u8], data_slice_config: Option<UiDataSliceConfig>) -> &[u8
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use solana_sdk::account::{Account, AccountSharedData};
|
||||
|
||||
#[test]
|
||||
fn test_slice_data() {
|
||||
@@ -224,10 +214,10 @@ mod test {
|
||||
fn test_base64_zstd() {
|
||||
let encoded_account = UiAccount::encode(
|
||||
&Pubkey::default(),
|
||||
&AccountSharedData::from(Account {
|
||||
Account {
|
||||
data: vec![0; 1024],
|
||||
..Account::default()
|
||||
}),
|
||||
},
|
||||
UiAccountEncoding::Base64Zstd,
|
||||
None,
|
||||
None,
|
||||
@@ -237,9 +227,7 @@ mod test {
|
||||
UiAccountData::Binary(_, UiAccountEncoding::Base64Zstd)
|
||||
));
|
||||
|
||||
let decoded_account = encoded_account.decode::<Account>().unwrap();
|
||||
assert_eq!(decoded_account.data(), &vec![0; 1024]);
|
||||
let decoded_account = encoded_account.decode::<AccountSharedData>().unwrap();
|
||||
assert_eq!(decoded_account.data(), &vec![0; 1024]);
|
||||
let decoded_account = encoded_account.decode().unwrap();
|
||||
assert_eq!(decoded_account.data, vec![0; 1024]);
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
use crate::{
|
||||
parse_bpf_loader::parse_bpf_upgradeable_loader,
|
||||
parse_config::parse_config,
|
||||
parse_nonce::parse_nonce,
|
||||
parse_stake::parse_stake,
|
||||
@@ -9,24 +8,19 @@ use crate::{
|
||||
};
|
||||
use inflector::Inflector;
|
||||
use serde_json::Value;
|
||||
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, stake, system_program, sysvar};
|
||||
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program, sysvar};
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
|
||||
lazy_static! {
|
||||
static ref BPF_UPGRADEABLE_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader_upgradeable::id();
|
||||
static ref CONFIG_PROGRAM_ID: Pubkey = solana_config_program::id();
|
||||
static ref STAKE_PROGRAM_ID: Pubkey = stake::program::id();
|
||||
static ref STAKE_PROGRAM_ID: Pubkey = solana_stake_program::id();
|
||||
static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
|
||||
static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id();
|
||||
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v2_0();
|
||||
static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id();
|
||||
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(
|
||||
*BPF_UPGRADEABLE_LOADER_PROGRAM_ID,
|
||||
ParsableAccount::BpfUpgradeableLoader,
|
||||
);
|
||||
m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config);
|
||||
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
|
||||
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken);
|
||||
@@ -66,7 +60,6 @@ pub struct ParsedAccount {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ParsableAccount {
|
||||
BpfUpgradeableLoader,
|
||||
Config,
|
||||
Nonce,
|
||||
SplToken,
|
||||
@@ -88,12 +81,9 @@ pub fn parse_account_data(
|
||||
) -> Result<ParsedAccount, ParseAccountError> {
|
||||
let program_name = PARSABLE_PROGRAM_IDS
|
||||
.get(program_id)
|
||||
.ok_or(ParseAccountError::ProgramNotParsable)?;
|
||||
.ok_or_else(|| ParseAccountError::ProgramNotParsable)?;
|
||||
let additional_data = additional_data.unwrap_or_default();
|
||||
let parsed_json = match program_name {
|
||||
ParsableAccount::BpfUpgradeableLoader => {
|
||||
serde_json::to_value(parse_bpf_upgradeable_loader(data)?)?
|
||||
}
|
||||
ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
|
||||
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
|
||||
ParsableAccount::SplToken => {
|
||||
@@ -128,7 +118,7 @@ mod test {
|
||||
|
||||
let vote_state = VoteState::default();
|
||||
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
|
||||
let versioned = VoteStateVersions::new_current(vote_state);
|
||||
let versioned = VoteStateVersions::Current(Box::new(vote_state));
|
||||
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
|
||||
let parsed = parse_account_data(
|
||||
&account_pubkey,
|
||||
|
@@ -1,181 +0,0 @@
|
||||
use crate::{
|
||||
parse_account_data::{ParsableAccount, ParseAccountError},
|
||||
UiAccountData, UiAccountEncoding,
|
||||
};
|
||||
use bincode::{deserialize, serialized_size};
|
||||
use solana_sdk::{bpf_loader_upgradeable::UpgradeableLoaderState, pubkey::Pubkey};
|
||||
|
||||
pub fn parse_bpf_upgradeable_loader(
|
||||
data: &[u8],
|
||||
) -> Result<BpfUpgradeableLoaderAccountType, ParseAccountError> {
|
||||
let account_state: UpgradeableLoaderState = deserialize(data).map_err(|_| {
|
||||
ParseAccountError::AccountNotParsable(ParsableAccount::BpfUpgradeableLoader)
|
||||
})?;
|
||||
let parsed_account = match account_state {
|
||||
UpgradeableLoaderState::Uninitialized => BpfUpgradeableLoaderAccountType::Uninitialized,
|
||||
UpgradeableLoaderState::Buffer { authority_address } => {
|
||||
let offset = if authority_address.is_some() {
|
||||
UpgradeableLoaderState::buffer_data_offset().unwrap()
|
||||
} else {
|
||||
// This case included for code completeness; in practice, a Buffer account will
|
||||
// always have authority_address.is_some()
|
||||
UpgradeableLoaderState::buffer_data_offset().unwrap()
|
||||
- serialized_size(&Pubkey::default()).unwrap() as usize
|
||||
};
|
||||
BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
|
||||
authority: authority_address.map(|pubkey| pubkey.to_string()),
|
||||
data: UiAccountData::Binary(
|
||||
base64::encode(&data[offset as usize..]),
|
||||
UiAccountEncoding::Base64,
|
||||
),
|
||||
})
|
||||
}
|
||||
UpgradeableLoaderState::Program {
|
||||
programdata_address,
|
||||
} => BpfUpgradeableLoaderAccountType::Program(UiProgram {
|
||||
program_data: programdata_address.to_string(),
|
||||
}),
|
||||
UpgradeableLoaderState::ProgramData {
|
||||
slot,
|
||||
upgrade_authority_address,
|
||||
} => {
|
||||
let offset = if upgrade_authority_address.is_some() {
|
||||
UpgradeableLoaderState::programdata_data_offset().unwrap()
|
||||
} else {
|
||||
UpgradeableLoaderState::programdata_data_offset().unwrap()
|
||||
- serialized_size(&Pubkey::default()).unwrap() as usize
|
||||
};
|
||||
BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
|
||||
slot,
|
||||
authority: upgrade_authority_address.map(|pubkey| pubkey.to_string()),
|
||||
data: UiAccountData::Binary(
|
||||
base64::encode(&data[offset as usize..]),
|
||||
UiAccountEncoding::Base64,
|
||||
),
|
||||
})
|
||||
}
|
||||
};
|
||||
Ok(parsed_account)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
|
||||
pub enum BpfUpgradeableLoaderAccountType {
|
||||
Uninitialized,
|
||||
Buffer(UiBuffer),
|
||||
Program(UiProgram),
|
||||
ProgramData(UiProgramData),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiBuffer {
|
||||
pub authority: Option<String>,
|
||||
pub data: UiAccountData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiProgram {
|
||||
pub program_data: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiProgramData {
|
||||
pub slot: u64,
|
||||
pub authority: Option<String>,
|
||||
pub data: UiAccountData,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use bincode::serialize;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
#[test]
|
||||
fn test_parse_bpf_upgradeable_loader_accounts() {
|
||||
let bpf_loader_state = UpgradeableLoaderState::Uninitialized;
|
||||
let account_data = serialize(&bpf_loader_state).unwrap();
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&account_data).unwrap(),
|
||||
BpfUpgradeableLoaderAccountType::Uninitialized
|
||||
);
|
||||
|
||||
let program = vec![7u8; 64]; // Arbitrary program data
|
||||
|
||||
let authority = Pubkey::new_unique();
|
||||
let bpf_loader_state = UpgradeableLoaderState::Buffer {
|
||||
authority_address: Some(authority),
|
||||
};
|
||||
let mut account_data = serialize(&bpf_loader_state).unwrap();
|
||||
account_data.extend_from_slice(&program);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&account_data).unwrap(),
|
||||
BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
|
||||
authority: Some(authority.to_string()),
|
||||
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
|
||||
})
|
||||
);
|
||||
|
||||
// This case included for code completeness; in practice, a Buffer account will always have
|
||||
// authority_address.is_some()
|
||||
let bpf_loader_state = UpgradeableLoaderState::Buffer {
|
||||
authority_address: None,
|
||||
};
|
||||
let mut account_data = serialize(&bpf_loader_state).unwrap();
|
||||
account_data.extend_from_slice(&program);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&account_data).unwrap(),
|
||||
BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
|
||||
authority: None,
|
||||
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
|
||||
})
|
||||
);
|
||||
|
||||
let programdata_address = Pubkey::new_unique();
|
||||
let bpf_loader_state = UpgradeableLoaderState::Program {
|
||||
programdata_address,
|
||||
};
|
||||
let account_data = serialize(&bpf_loader_state).unwrap();
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&account_data).unwrap(),
|
||||
BpfUpgradeableLoaderAccountType::Program(UiProgram {
|
||||
program_data: programdata_address.to_string(),
|
||||
})
|
||||
);
|
||||
|
||||
let authority = Pubkey::new_unique();
|
||||
let slot = 42;
|
||||
let bpf_loader_state = UpgradeableLoaderState::ProgramData {
|
||||
slot,
|
||||
upgrade_authority_address: Some(authority),
|
||||
};
|
||||
let mut account_data = serialize(&bpf_loader_state).unwrap();
|
||||
account_data.extend_from_slice(&program);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&account_data).unwrap(),
|
||||
BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
|
||||
slot,
|
||||
authority: Some(authority.to_string()),
|
||||
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
|
||||
})
|
||||
);
|
||||
|
||||
let bpf_loader_state = UpgradeableLoaderState::ProgramData {
|
||||
slot,
|
||||
upgrade_authority_address: None,
|
||||
};
|
||||
let mut account_data = serialize(&bpf_loader_state).unwrap();
|
||||
account_data.extend_from_slice(&program);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&account_data).unwrap(),
|
||||
BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
|
||||
slot,
|
||||
authority: None,
|
||||
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@@ -6,10 +6,10 @@ use bincode::deserialize;
|
||||
use serde_json::Value;
|
||||
use solana_config_program::{get_config_data, ConfigKeys};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::stake::config::{self as stake_config, Config as StakeConfig};
|
||||
use solana_stake_program::config::Config as StakeConfig;
|
||||
|
||||
pub fn parse_config(data: &[u8], pubkey: &Pubkey) -> Result<ConfigAccountType, ParseAccountError> {
|
||||
let parsed_account = if pubkey == &stake_config::id() {
|
||||
let parsed_account = if pubkey == &solana_stake_program::config::id() {
|
||||
get_config_data(data)
|
||||
.ok()
|
||||
.and_then(|data| deserialize::<StakeConfig>(data).ok())
|
||||
@@ -91,7 +91,6 @@ mod test {
|
||||
use crate::validator_info::ValidatorInfo;
|
||||
use serde_json::json;
|
||||
use solana_config_program::create_config_account;
|
||||
use solana_sdk::account::ReadableAccount;
|
||||
|
||||
#[test]
|
||||
fn test_parse_config() {
|
||||
@@ -101,7 +100,11 @@ mod test {
|
||||
};
|
||||
let stake_config_account = create_config_account(vec![], &stake_config, 10);
|
||||
assert_eq!(
|
||||
parse_config(&stake_config_account.data(), &stake_config::id()).unwrap(),
|
||||
parse_config(
|
||||
&stake_config_account.data,
|
||||
&solana_stake_program::config::id()
|
||||
)
|
||||
.unwrap(),
|
||||
ConfigAccountType::StakeConfig(UiStakeConfig {
|
||||
warmup_cooldown_rate: 0.25,
|
||||
slash_penalty: 50,
|
||||
@@ -121,7 +124,7 @@ mod test {
|
||||
10,
|
||||
);
|
||||
assert_eq!(
|
||||
parse_config(&validator_info_config_account.data(), &info_pubkey).unwrap(),
|
||||
parse_config(&validator_info_config_account.data, &info_pubkey).unwrap(),
|
||||
ConfigAccountType::ValidatorInfo(UiConfig {
|
||||
keys: vec![
|
||||
UiConfigKey {
|
||||
|
@@ -9,13 +9,7 @@ pub fn parse_nonce(data: &[u8]) -> Result<UiNonceState, ParseAccountError> {
|
||||
.map_err(|_| ParseAccountError::from(InstructionError::InvalidAccountData))?;
|
||||
let nonce_state = nonce_state.convert_to_current();
|
||||
match nonce_state {
|
||||
// This prevents parsing an allocated System-owned account with empty data of any non-zero
|
||||
// length as `uninitialized` nonce. An empty account of the wrong length can never be
|
||||
// initialized as a nonce account, and an empty account of the correct length may not be an
|
||||
// uninitialized nonce account, since it can be assigned to another program.
|
||||
State::Uninitialized => Err(ParseAccountError::from(
|
||||
InstructionError::InvalidAccountData,
|
||||
)),
|
||||
State::Uninitialized => Ok(UiNonceState::Uninitialized),
|
||||
State::Initialized(data) => Ok(UiNonceState::Initialized(UiNonceData {
|
||||
authority: data.authority.to_string(),
|
||||
blockhash: data.blockhash.to_string(),
|
||||
|
@@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use bincode::deserialize;
|
||||
use solana_sdk::clock::{Epoch, UnixTimestamp};
|
||||
use solana_sdk::stake::state::{Authorized, Delegation, Lockup, Meta, Stake, StakeState};
|
||||
use solana_stake_program::stake_state::{Authorized, Delegation, Lockup, Meta, Stake, StakeState};
|
||||
|
||||
pub fn parse_stake(data: &[u8]) -> Result<StakeAccountType, ParseAccountError> {
|
||||
let stake_state: StakeState = deserialize(data)
|
||||
|
@@ -214,13 +214,14 @@ pub struct UiStakeHistoryEntry {
|
||||
mod test {
|
||||
use super::*;
|
||||
use solana_sdk::{
|
||||
account::create_account_for_test, fee_calculator::FeeCalculator, hash::Hash,
|
||||
account::create_account, fee_calculator::FeeCalculator, hash::Hash,
|
||||
sysvar::recent_blockhashes::IterItem,
|
||||
};
|
||||
use std::iter::FromIterator;
|
||||
|
||||
#[test]
|
||||
fn test_parse_sysvars() {
|
||||
let clock_sysvar = create_account_for_test(&Clock::default());
|
||||
let clock_sysvar = create_account(&Clock::default(), 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&clock_sysvar.data, &sysvar::clock::id()).unwrap(),
|
||||
SysvarAccountType::Clock(UiClock::default()),
|
||||
@@ -233,13 +234,13 @@ mod test {
|
||||
first_normal_epoch: 1,
|
||||
first_normal_slot: 12,
|
||||
};
|
||||
let epoch_schedule_sysvar = create_account_for_test(&epoch_schedule);
|
||||
let epoch_schedule_sysvar = create_account(&epoch_schedule, 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&epoch_schedule_sysvar.data, &sysvar::epoch_schedule::id()).unwrap(),
|
||||
SysvarAccountType::EpochSchedule(epoch_schedule),
|
||||
);
|
||||
|
||||
let fees_sysvar = create_account_for_test(&Fees::default());
|
||||
let fees_sysvar = create_account(&Fees::default(), 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&fees_sysvar.data, &sysvar::fees::id()).unwrap(),
|
||||
SysvarAccountType::Fees(UiFees::default()),
|
||||
@@ -249,10 +250,9 @@ mod test {
|
||||
let fee_calculator = FeeCalculator {
|
||||
lamports_per_signature: 10,
|
||||
};
|
||||
let recent_blockhashes: RecentBlockhashes = vec![IterItem(0, &hash, &fee_calculator)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let recent_blockhashes_sysvar = create_account_for_test(&recent_blockhashes);
|
||||
let recent_blockhashes =
|
||||
RecentBlockhashes::from_iter(vec![IterItem(0, &hash, &fee_calculator)].into_iter());
|
||||
let recent_blockhashes_sysvar = create_account(&recent_blockhashes, 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(
|
||||
&recent_blockhashes_sysvar.data,
|
||||
@@ -270,13 +270,13 @@ mod test {
|
||||
exemption_threshold: 2.0,
|
||||
burn_percent: 5,
|
||||
};
|
||||
let rent_sysvar = create_account_for_test(&rent);
|
||||
let rent_sysvar = create_account(&rent, 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&rent_sysvar.data, &sysvar::rent::id()).unwrap(),
|
||||
SysvarAccountType::Rent(rent.into()),
|
||||
);
|
||||
|
||||
let rewards_sysvar = create_account_for_test(&Rewards::default());
|
||||
let rewards_sysvar = create_account(&Rewards::default(), 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&rewards_sysvar.data, &sysvar::rewards::id()).unwrap(),
|
||||
SysvarAccountType::Rewards(UiRewards::default()),
|
||||
@@ -284,7 +284,7 @@ mod test {
|
||||
|
||||
let mut slot_hashes = SlotHashes::default();
|
||||
slot_hashes.add(1, hash);
|
||||
let slot_hashes_sysvar = create_account_for_test(&slot_hashes);
|
||||
let slot_hashes_sysvar = create_account(&slot_hashes, 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&slot_hashes_sysvar.data, &sysvar::slot_hashes::id()).unwrap(),
|
||||
SysvarAccountType::SlotHashes(vec![UiSlotHashEntry {
|
||||
@@ -295,7 +295,7 @@ mod test {
|
||||
|
||||
let mut slot_history = SlotHistory::default();
|
||||
slot_history.add(42);
|
||||
let slot_history_sysvar = create_account_for_test(&slot_history);
|
||||
let slot_history_sysvar = create_account(&slot_history, 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&slot_history_sysvar.data, &sysvar::slot_history::id()).unwrap(),
|
||||
SysvarAccountType::SlotHistory(UiSlotHistory {
|
||||
@@ -311,7 +311,7 @@ mod test {
|
||||
deactivating: 3,
|
||||
};
|
||||
stake_history.add(1, stake_history_entry.clone());
|
||||
let stake_history_sysvar = create_account_for_test(&stake_history);
|
||||
let stake_history_sysvar = create_account(&stake_history, 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&stake_history_sysvar.data, &sysvar::stake_history::id()).unwrap(),
|
||||
SysvarAccountType::StakeHistory(vec![UiStakeHistoryEntry {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
parse_account_data::{ParsableAccount, ParseAccountError},
|
||||
StringAmount, StringDecimals,
|
||||
StringAmount,
|
||||
};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use spl_token_v2_0::{
|
||||
@@ -14,23 +14,23 @@ use std::str::FromStr;
|
||||
// A helper function to convert spl_token_v2_0::id() as spl_sdk::pubkey::Pubkey to
|
||||
// solana_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_id_v2_0() -> Pubkey {
|
||||
Pubkey::new_from_array(spl_token_v2_0::id().to_bytes())
|
||||
Pubkey::from_str(&spl_token_v2_0::id().to_string()).unwrap()
|
||||
}
|
||||
|
||||
// A helper function to convert spl_token_v2_0::native_mint::id() as spl_sdk::pubkey::Pubkey to
|
||||
// solana_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_v2_0_native_mint() -> Pubkey {
|
||||
Pubkey::new_from_array(spl_token_v2_0::native_mint::id().to_bytes())
|
||||
Pubkey::from_str(&spl_token_v2_0::native_mint::id().to_string()).unwrap()
|
||||
}
|
||||
|
||||
// A helper function to convert a solana_sdk::pubkey::Pubkey to spl_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_v2_0_pubkey(pubkey: &Pubkey) -> SplTokenPubkey {
|
||||
SplTokenPubkey::new_from_array(pubkey.to_bytes())
|
||||
SplTokenPubkey::from_str(&pubkey.to_string()).unwrap()
|
||||
}
|
||||
|
||||
// A helper function to convert a spl_sdk::pubkey::Pubkey to solana_sdk::pubkey::Pubkey
|
||||
pub fn pubkey_from_spl_token_v2_0(pubkey: &SplTokenPubkey) -> Pubkey {
|
||||
Pubkey::new_from_array(pubkey.to_bytes())
|
||||
Pubkey::from_str(&pubkey.to_string()).unwrap()
|
||||
}
|
||||
|
||||
pub fn parse_token(
|
||||
@@ -158,66 +158,46 @@ impl From<AccountState> for UiAccountState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn real_number_string(amount: u64, decimals: u8) -> StringDecimals {
|
||||
let decimals = decimals as usize;
|
||||
if decimals > 0 {
|
||||
// Left-pad zeros to decimals + 1, so we at least have an integer zero
|
||||
let mut s = format!("{:01$}", amount, decimals + 1);
|
||||
// Add the decimal point (Sorry, "," locales!)
|
||||
s.insert(s.len() - decimals, '.');
|
||||
s
|
||||
} else {
|
||||
amount.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn real_number_string_trimmed(amount: u64, decimals: u8) -> StringDecimals {
|
||||
let mut s = real_number_string(amount, decimals);
|
||||
if decimals > 0 {
|
||||
let zeros_trimmed = s.trim_end_matches('0');
|
||||
s = zeros_trimmed.trim_end_matches('.').to_string();
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiTokenAmount {
|
||||
pub ui_amount: Option<f64>,
|
||||
pub ui_amount: f64,
|
||||
pub decimals: u8,
|
||||
pub amount: StringAmount,
|
||||
pub ui_amount_string: StringDecimals,
|
||||
}
|
||||
|
||||
impl UiTokenAmount {
|
||||
pub fn real_number_string(&self) -> String {
|
||||
real_number_string(
|
||||
u64::from_str(&self.amount).unwrap_or_default(),
|
||||
self.decimals as u8,
|
||||
)
|
||||
let decimals = self.decimals as usize;
|
||||
if decimals > 0 {
|
||||
let amount = u64::from_str(&self.amount).unwrap_or(0);
|
||||
|
||||
// Left-pad zeros to decimals + 1, so we at least have an integer zero
|
||||
let mut s = format!("{:01$}", amount, decimals + 1);
|
||||
|
||||
// Add the decimal point (Sorry, "," locales!)
|
||||
s.insert(s.len() - decimals, '.');
|
||||
s
|
||||
} else {
|
||||
self.amount.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn real_number_string_trimmed(&self) -> String {
|
||||
if !self.ui_amount_string.is_empty() {
|
||||
self.ui_amount_string.clone()
|
||||
} else {
|
||||
real_number_string_trimmed(
|
||||
u64::from_str(&self.amount).unwrap_or_default(),
|
||||
self.decimals as u8,
|
||||
)
|
||||
}
|
||||
let s = self.real_number_string();
|
||||
let zeros_trimmed = s.trim_end_matches('0');
|
||||
let decimal_trimmed = zeros_trimmed.trim_end_matches('.');
|
||||
decimal_trimmed.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount {
|
||||
let amount_decimals = 10_usize
|
||||
.checked_pow(decimals as u32)
|
||||
.map(|dividend| amount as f64 / dividend as f64);
|
||||
// Use `amount_to_ui_amount()` once spl_token is bumped to a version that supports it: https://github.com/solana-labs/solana-program-library/pull/211
|
||||
let amount_decimals = amount as f64 / 10_usize.pow(decimals as u32) as f64;
|
||||
UiTokenAmount {
|
||||
ui_amount: amount_decimals,
|
||||
decimals,
|
||||
amount: amount.to_string(),
|
||||
ui_amount_string: real_number_string_trimmed(amount, decimals),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,10 +253,9 @@ mod test {
|
||||
mint: mint_pubkey.to_string(),
|
||||
owner: owner_pubkey.to_string(),
|
||||
token_amount: UiTokenAmount {
|
||||
ui_amount: Some(0.42),
|
||||
ui_amount: 0.42,
|
||||
decimals: 2,
|
||||
amount: "42".to_string(),
|
||||
ui_amount_string: "0.42".to_string()
|
||||
amount: "42".to_string()
|
||||
},
|
||||
delegate: None,
|
||||
state: UiAccountState::Initialized,
|
||||
@@ -357,87 +336,17 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_ui_token_amount_real_string() {
|
||||
assert_eq!(&real_number_string(1, 0), "1");
|
||||
assert_eq!(&real_number_string_trimmed(1, 0), "1");
|
||||
let token_amount = token_amount_to_ui_amount(1, 0);
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(1, 0)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, Some(1.0));
|
||||
assert_eq!(&real_number_string(10, 0), "10");
|
||||
assert_eq!(&real_number_string_trimmed(10, 0), "10");
|
||||
let token_amount = token_amount_to_ui_amount(10, 0);
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(10, 0)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, Some(10.0));
|
||||
assert_eq!(&real_number_string(1, 9), "0.000000001");
|
||||
assert_eq!(&real_number_string_trimmed(1, 9), "0.000000001");
|
||||
assert_eq!(&token_amount.real_number_string(), "1");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "1");
|
||||
let token_amount = token_amount_to_ui_amount(1, 9);
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(1, 9)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, Some(0.000000001));
|
||||
assert_eq!(&real_number_string(1_000_000_000, 9), "1.000000000");
|
||||
assert_eq!(&real_number_string_trimmed(1_000_000_000, 9), "1");
|
||||
assert_eq!(&token_amount.real_number_string(), "0.000000001");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "0.000000001");
|
||||
let token_amount = token_amount_to_ui_amount(1_000_000_000, 9);
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(1_000_000_000, 9)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, Some(1.0));
|
||||
assert_eq!(&real_number_string(1_234_567_890, 3), "1234567.890");
|
||||
assert_eq!(&real_number_string_trimmed(1_234_567_890, 3), "1234567.89");
|
||||
assert_eq!(&token_amount.real_number_string(), "1.000000000");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "1");
|
||||
let token_amount = token_amount_to_ui_amount(1_234_567_890, 3);
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(1_234_567_890, 3)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, Some(1234567.89));
|
||||
assert_eq!(
|
||||
&real_number_string(1_234_567_890, 25),
|
||||
"0.0000000000000001234567890"
|
||||
);
|
||||
assert_eq!(
|
||||
&real_number_string_trimmed(1_234_567_890, 25),
|
||||
"0.000000000000000123456789"
|
||||
);
|
||||
let token_amount = token_amount_to_ui_amount(1_234_567_890, 20);
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(1_234_567_890, 20)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ui_token_amount_real_string_zero() {
|
||||
assert_eq!(&real_number_string(0, 0), "0");
|
||||
assert_eq!(&real_number_string_trimmed(0, 0), "0");
|
||||
let token_amount = token_amount_to_ui_amount(0, 0);
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(0, 0)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, Some(0.0));
|
||||
assert_eq!(&real_number_string(0, 9), "0.000000000");
|
||||
assert_eq!(&real_number_string_trimmed(0, 9), "0");
|
||||
let token_amount = token_amount_to_ui_amount(0, 9);
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(0, 9)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, Some(0.0));
|
||||
assert_eq!(&real_number_string(0, 25), "0.0000000000000000000000000");
|
||||
assert_eq!(&real_number_string_trimmed(0, 25), "0");
|
||||
let token_amount = token_amount_to_ui_amount(0, 20);
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(0, 20)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, None);
|
||||
assert_eq!(&token_amount.real_number_string(), "1234567.890");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "1234567.89");
|
||||
}
|
||||
}
|
||||
|
@@ -128,13 +128,11 @@ mod test {
|
||||
fn test_parse_vote() {
|
||||
let vote_state = VoteState::default();
|
||||
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
|
||||
let versioned = VoteStateVersions::new_current(vote_state);
|
||||
let versioned = VoteStateVersions::Current(Box::new(vote_state));
|
||||
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
|
||||
let expected_vote_state = UiVoteState {
|
||||
node_pubkey: Pubkey::default().to_string(),
|
||||
authorized_withdrawer: Pubkey::default().to_string(),
|
||||
..UiVoteState::default()
|
||||
};
|
||||
let mut expected_vote_state = UiVoteState::default();
|
||||
expected_vote_state.node_pubkey = Pubkey::default().to_string();
|
||||
expected_vote_state.authorized_withdrawer = Pubkey::default().to_string();
|
||||
assert_eq!(
|
||||
parse_vote(&vote_account_data).unwrap(),
|
||||
VoteAccountType::Vote(expected_vote_state)
|
||||
|
@@ -2,20 +2,20 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-accounts-bench"
|
||||
version = "1.7.2"
|
||||
version = "1.4.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.11"
|
||||
rayon = "1.5.0"
|
||||
solana-logger = { path = "../logger", version = "=1.7.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.2" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.2" }
|
||||
solana-version = { path = "../version", version = "=1.7.2" }
|
||||
log = "0.4.6"
|
||||
rayon = "1.4.0"
|
||||
solana-logger = { path = "../logger", version = "1.4.14" }
|
||||
solana-runtime = { path = "../runtime", version = "1.4.14" }
|
||||
solana-measure = { path = "../measure", version = "1.4.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.4.14" }
|
||||
solana-version = { path = "../version", version = "1.4.14" }
|
||||
rand = "0.7.0"
|
||||
clap = "2.33.1"
|
||||
crossbeam-channel = "0.4"
|
||||
|
@@ -1,17 +1,14 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
use clap::{crate_description, crate_name, value_t, App, Arg};
|
||||
use rayon::prelude::*;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_runtime::{
|
||||
accounts::{create_test_accounts, update_accounts_bench, Accounts},
|
||||
accounts_db::AccountShrinkThreshold,
|
||||
accounts_index::AccountSecondaryIndexes,
|
||||
ancestors::Ancestors,
|
||||
accounts::{create_test_accounts, update_accounts, Accounts},
|
||||
accounts_index::Ancestors,
|
||||
};
|
||||
use solana_sdk::{genesis_config::ClusterType, pubkey::Pubkey};
|
||||
use std::{env, fs, path::PathBuf};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
@@ -56,17 +53,10 @@ fn main() {
|
||||
|
||||
let path = PathBuf::from(env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_owned()))
|
||||
.join("accounts-bench");
|
||||
println!("cleaning file system: {:?}", path);
|
||||
if fs::remove_dir_all(path.clone()).is_err() {
|
||||
println!("Warning: Couldn't remove {:?}", path);
|
||||
}
|
||||
let accounts = Accounts::new_with_config(
|
||||
vec![path],
|
||||
&ClusterType::Testnet,
|
||||
AccountSecondaryIndexes::default(),
|
||||
false,
|
||||
AccountShrinkThreshold::default(),
|
||||
);
|
||||
let accounts = Accounts::new(vec![path], &ClusterType::Testnet);
|
||||
println!("Creating {} accounts", num_accounts);
|
||||
let mut create_time = Measure::start("create accounts");
|
||||
let pubkeys: Vec<_> = (0..num_slots)
|
||||
@@ -90,60 +80,28 @@ fn main() {
|
||||
num_slots,
|
||||
create_time
|
||||
);
|
||||
let mut ancestors = Vec::with_capacity(num_slots);
|
||||
ancestors.push(0);
|
||||
let mut ancestors: Ancestors = vec![(0, 0)].into_iter().collect();
|
||||
for i in 1..num_slots {
|
||||
ancestors.push(i as u64);
|
||||
ancestors.insert(i as u64, i - 1);
|
||||
accounts.add_root(i as u64);
|
||||
}
|
||||
let ancestors = Ancestors::from(ancestors);
|
||||
let mut elapsed = vec![0; iterations];
|
||||
let mut elapsed_store = vec![0; iterations];
|
||||
for x in 0..iterations {
|
||||
if clean {
|
||||
let mut time = Measure::start("clean");
|
||||
accounts.accounts_db.clean_accounts(None, false);
|
||||
accounts.accounts_db.clean_accounts(None);
|
||||
time.stop();
|
||||
println!("{}", time);
|
||||
for slot in 0..num_slots {
|
||||
update_accounts_bench(&accounts, &pubkeys, ((x + 1) * num_slots + slot) as u64);
|
||||
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 results = accounts.accounts_db.update_accounts_hash(0, &ancestors);
|
||||
let hash = accounts.accounts_db.update_accounts_hash(0, &ancestors).0;
|
||||
time.stop();
|
||||
let mut time_store = Measure::start("hash using store");
|
||||
let results_store = accounts.accounts_db.update_accounts_hash_with_index_option(
|
||||
false,
|
||||
false,
|
||||
solana_sdk::clock::Slot::default(),
|
||||
&ancestors,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
time_store.stop();
|
||||
if results != results_store {
|
||||
error!("results different: \n{:?}\n{:?}", results, results_store);
|
||||
}
|
||||
println!(
|
||||
"hash,{},{},{},{}%",
|
||||
results.0,
|
||||
time,
|
||||
time_store,
|
||||
(time_store.as_us() as f64 / time.as_us() as f64 * 100.0f64) as u32
|
||||
);
|
||||
println!("hash: {} {}", hash, time);
|
||||
create_test_accounts(&accounts, &mut pubkeys, 1, 0);
|
||||
elapsed[x] = time.as_us();
|
||||
elapsed_store[x] = time_store.as_us();
|
||||
}
|
||||
}
|
||||
|
||||
for x in elapsed {
|
||||
info!("update_accounts_hash(us),{}", x);
|
||||
}
|
||||
for x in elapsed_store {
|
||||
info!("calculate_accounts_hash_without_index(us),{}", x);
|
||||
}
|
||||
}
|
||||
|
1
accounts-cluster-bench/.gitignore
vendored
1
accounts-cluster-bench/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/farf/
|
@@ -1,35 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-accounts-cluster-bench"
|
||||
version = "1.7.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
log = "0.4.11"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.4.1"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.7.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.2" }
|
||||
solana-client = { path = "../client", version = "=1.7.2" }
|
||||
solana-core = { path = "../core", version = "=1.7.2" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.2" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.2" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.2" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.2" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.2" }
|
||||
solana-version = { path = "../version", version = "=1.7.2" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.1.1", features = ["no-entrypoint"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.7.2" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
@@ -1,747 +0,0 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use clap::{crate_description, crate_name, value_t, values_t_or_exit, App, Arg};
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana_account_decoder::parse_token::spl_token_v2_0_pubkey;
|
||||
use solana_clap_utils::input_parsers::pubkey_of;
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_faucet::faucet::{request_airdrop_transaction, FAUCET_PORT};
|
||||
use solana_gossip::gossip_service::discover;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_runtime::inline_spl_token_v2_0;
|
||||
use solana_sdk::{
|
||||
commitment_config::CommitmentConfig,
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
rpc_port::DEFAULT_RPC_PORT,
|
||||
signature::{read_keypair_file, Keypair, Signature, Signer},
|
||||
system_instruction, system_program,
|
||||
timing::timestamp,
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_transaction_status::parse_token::spl_token_v2_0_instruction;
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
process::exit,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
Arc, RwLock,
|
||||
},
|
||||
thread::{sleep, Builder, JoinHandle},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
// Create and close messages both require 2 signatures; if transaction construction changes, update
|
||||
// this magic number
|
||||
const NUM_SIGNATURES: u64 = 2;
|
||||
|
||||
pub fn airdrop_lamports(
|
||||
client: &RpcClient,
|
||||
faucet_addr: &SocketAddr,
|
||||
id: &Keypair,
|
||||
desired_balance: u64,
|
||||
) -> bool {
|
||||
let starting_balance = client.get_balance(&id.pubkey()).unwrap_or(0);
|
||||
info!("starting balance {}", starting_balance);
|
||||
|
||||
if starting_balance < desired_balance {
|
||||
let airdrop_amount = desired_balance - starting_balance;
|
||||
info!(
|
||||
"Airdropping {:?} lamports from {} for {}",
|
||||
airdrop_amount,
|
||||
faucet_addr,
|
||||
id.pubkey(),
|
||||
);
|
||||
|
||||
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
|
||||
match request_airdrop_transaction(&faucet_addr, &id.pubkey(), airdrop_amount, blockhash) {
|
||||
Ok(transaction) => {
|
||||
let mut tries = 0;
|
||||
loop {
|
||||
tries += 1;
|
||||
let result = client.send_and_confirm_transaction(&transaction);
|
||||
|
||||
if result.is_ok() {
|
||||
break;
|
||||
}
|
||||
if tries >= 5 {
|
||||
panic!(
|
||||
"Error requesting airdrop: to addr: {:?} amount: {} {:?}",
|
||||
faucet_addr, airdrop_amount, result
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
panic!(
|
||||
"Error requesting airdrop: {:?} to addr: {:?} amount: {}",
|
||||
err, faucet_addr, airdrop_amount
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let current_balance = client.get_balance(&id.pubkey()).unwrap_or_else(|e| {
|
||||
panic!("airdrop error {}", e);
|
||||
});
|
||||
info!("current balance {}...", current_balance);
|
||||
|
||||
if current_balance - starting_balance != airdrop_amount {
|
||||
info!(
|
||||
"Airdrop failed? {} {} {} {}",
|
||||
id.pubkey(),
|
||||
current_balance,
|
||||
starting_balance,
|
||||
airdrop_amount,
|
||||
);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
// signature, timestamp, id
|
||||
type PendingQueue = Vec<(Signature, u64, u64)>;
|
||||
|
||||
struct TransactionExecutor {
|
||||
sig_clear_t: JoinHandle<()>,
|
||||
sigs: Arc<RwLock<PendingQueue>>,
|
||||
cleared: Arc<RwLock<Vec<u64>>>,
|
||||
exit: Arc<AtomicBool>,
|
||||
counter: AtomicU64,
|
||||
client: RpcClient,
|
||||
}
|
||||
|
||||
impl TransactionExecutor {
|
||||
fn new(entrypoint_addr: SocketAddr) -> Self {
|
||||
let sigs = Arc::new(RwLock::new(Vec::new()));
|
||||
let cleared = Arc::new(RwLock::new(Vec::new()));
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let sig_clear_t = Self::start_sig_clear_thread(&exit, &sigs, &cleared, entrypoint_addr);
|
||||
let client =
|
||||
RpcClient::new_socket_with_commitment(entrypoint_addr, CommitmentConfig::confirmed());
|
||||
Self {
|
||||
sigs,
|
||||
cleared,
|
||||
sig_clear_t,
|
||||
exit,
|
||||
counter: AtomicU64::new(0),
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
fn num_outstanding(&self) -> usize {
|
||||
self.sigs.read().unwrap().len()
|
||||
}
|
||||
|
||||
fn push_transactions(&self, txs: Vec<Transaction>) -> Vec<u64> {
|
||||
let mut ids = vec![];
|
||||
let new_sigs = txs.into_iter().filter_map(|tx| {
|
||||
let id = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||
ids.push(id);
|
||||
match self.client.send_transaction(&tx) {
|
||||
Ok(sig) => {
|
||||
return Some((sig, timestamp(), id));
|
||||
}
|
||||
Err(e) => {
|
||||
info!("error: {:#?}", e);
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
let mut sigs_w = self.sigs.write().unwrap();
|
||||
sigs_w.extend(new_sigs);
|
||||
ids
|
||||
}
|
||||
|
||||
fn drain_cleared(&self) -> Vec<u64> {
|
||||
std::mem::take(&mut *self.cleared.write().unwrap())
|
||||
}
|
||||
|
||||
fn close(self) {
|
||||
self.exit.store(true, Ordering::Relaxed);
|
||||
self.sig_clear_t.join().unwrap();
|
||||
}
|
||||
|
||||
fn start_sig_clear_thread(
|
||||
exit: &Arc<AtomicBool>,
|
||||
sigs: &Arc<RwLock<PendingQueue>>,
|
||||
cleared: &Arc<RwLock<Vec<u64>>>,
|
||||
entrypoint_addr: SocketAddr,
|
||||
) -> JoinHandle<()> {
|
||||
let sigs = sigs.clone();
|
||||
let exit = exit.clone();
|
||||
let cleared = cleared.clone();
|
||||
Builder::new()
|
||||
.name("sig_clear".to_string())
|
||||
.spawn(move || {
|
||||
let client = RpcClient::new_socket_with_commitment(
|
||||
entrypoint_addr,
|
||||
CommitmentConfig::confirmed(),
|
||||
);
|
||||
let mut success = 0;
|
||||
let mut error_count = 0;
|
||||
let mut timed_out = 0;
|
||||
let mut last_log = Instant::now();
|
||||
while !exit.load(Ordering::Relaxed) {
|
||||
let sigs_len = sigs.read().unwrap().len();
|
||||
if sigs_len > 0 {
|
||||
let mut sigs_w = sigs.write().unwrap();
|
||||
let mut start = Measure::start("sig_status");
|
||||
let statuses: Vec<_> = sigs_w
|
||||
.chunks(200)
|
||||
.flat_map(|sig_chunk| {
|
||||
let only_sigs: Vec<_> = sig_chunk.iter().map(|s| s.0).collect();
|
||||
client
|
||||
.get_signature_statuses(&only_sigs)
|
||||
.expect("status fail")
|
||||
.value
|
||||
})
|
||||
.collect();
|
||||
let mut num_cleared = 0;
|
||||
let start_len = sigs_w.len();
|
||||
let now = timestamp();
|
||||
let mut new_ids = vec![];
|
||||
let mut i = 0;
|
||||
let mut j = 0;
|
||||
while i != sigs_w.len() {
|
||||
let mut retain = true;
|
||||
let sent_ts = sigs_w[i].1;
|
||||
if let Some(e) = &statuses[j] {
|
||||
debug!("error: {:?}", e);
|
||||
if e.status.is_ok() {
|
||||
success += 1;
|
||||
} else {
|
||||
error_count += 1;
|
||||
}
|
||||
num_cleared += 1;
|
||||
retain = false;
|
||||
} else if now - sent_ts > 30_000 {
|
||||
retain = false;
|
||||
timed_out += 1;
|
||||
}
|
||||
if !retain {
|
||||
new_ids.push(sigs_w.remove(i).2);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
let final_sigs_len = sigs_w.len();
|
||||
drop(sigs_w);
|
||||
cleared.write().unwrap().extend(new_ids);
|
||||
start.stop();
|
||||
debug!(
|
||||
"sigs len: {:?} success: {} took: {}ms cleared: {}/{}",
|
||||
final_sigs_len,
|
||||
success,
|
||||
start.as_ms(),
|
||||
num_cleared,
|
||||
start_len,
|
||||
);
|
||||
if last_log.elapsed().as_millis() > 5000 {
|
||||
info!(
|
||||
"success: {} error: {} timed_out: {}",
|
||||
success, error_count, timed_out,
|
||||
);
|
||||
last_log = Instant::now();
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_millis(200));
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
struct SeedTracker {
|
||||
max_created: Arc<AtomicU64>,
|
||||
max_closed: Arc<AtomicU64>,
|
||||
}
|
||||
|
||||
fn make_create_message(
|
||||
keypair: &Keypair,
|
||||
base_keypair: &Keypair,
|
||||
max_created_seed: Arc<AtomicU64>,
|
||||
num_instructions: usize,
|
||||
balance: u64,
|
||||
maybe_space: Option<u64>,
|
||||
mint: Option<Pubkey>,
|
||||
) -> Message {
|
||||
let space = maybe_space.unwrap_or_else(|| thread_rng().gen_range(0, 1000));
|
||||
|
||||
let instructions: Vec<_> = (0..num_instructions)
|
||||
.into_iter()
|
||||
.map(|_| {
|
||||
let program_id = if mint.is_some() {
|
||||
inline_spl_token_v2_0::id()
|
||||
} else {
|
||||
system_program::id()
|
||||
};
|
||||
let seed = max_created_seed.fetch_add(1, Ordering::Relaxed).to_string();
|
||||
let to_pubkey =
|
||||
Pubkey::create_with_seed(&base_keypair.pubkey(), &seed, &program_id).unwrap();
|
||||
let mut instructions = vec![system_instruction::create_account_with_seed(
|
||||
&keypair.pubkey(),
|
||||
&to_pubkey,
|
||||
&base_keypair.pubkey(),
|
||||
&seed,
|
||||
balance,
|
||||
space,
|
||||
&program_id,
|
||||
)];
|
||||
if let Some(mint_address) = mint {
|
||||
instructions.push(spl_token_v2_0_instruction(
|
||||
spl_token_v2_0::instruction::initialize_account(
|
||||
&spl_token_v2_0::id(),
|
||||
&spl_token_v2_0_pubkey(&to_pubkey),
|
||||
&spl_token_v2_0_pubkey(&mint_address),
|
||||
&spl_token_v2_0_pubkey(&base_keypair.pubkey()),
|
||||
)
|
||||
.unwrap(),
|
||||
));
|
||||
}
|
||||
|
||||
instructions
|
||||
})
|
||||
.collect();
|
||||
let instructions: Vec<_> = instructions.into_iter().flatten().collect();
|
||||
|
||||
Message::new(&instructions, Some(&keypair.pubkey()))
|
||||
}
|
||||
|
||||
fn make_close_message(
|
||||
keypair: &Keypair,
|
||||
base_keypair: &Keypair,
|
||||
max_closed_seed: Arc<AtomicU64>,
|
||||
num_instructions: usize,
|
||||
balance: u64,
|
||||
spl_token: bool,
|
||||
) -> Message {
|
||||
let instructions: Vec<_> = (0..num_instructions)
|
||||
.into_iter()
|
||||
.map(|_| {
|
||||
let program_id = if spl_token {
|
||||
inline_spl_token_v2_0::id()
|
||||
} else {
|
||||
system_program::id()
|
||||
};
|
||||
let seed = max_closed_seed.fetch_add(1, Ordering::Relaxed).to_string();
|
||||
let address =
|
||||
Pubkey::create_with_seed(&base_keypair.pubkey(), &seed, &program_id).unwrap();
|
||||
if spl_token {
|
||||
spl_token_v2_0_instruction(
|
||||
spl_token_v2_0::instruction::close_account(
|
||||
&spl_token_v2_0::id(),
|
||||
&spl_token_v2_0_pubkey(&address),
|
||||
&spl_token_v2_0_pubkey(&keypair.pubkey()),
|
||||
&spl_token_v2_0_pubkey(&base_keypair.pubkey()),
|
||||
&[],
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
system_instruction::transfer_with_seed(
|
||||
&address,
|
||||
&base_keypair.pubkey(),
|
||||
seed,
|
||||
&program_id,
|
||||
&keypair.pubkey(),
|
||||
balance,
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Message::new(&instructions, Some(&keypair.pubkey()))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn run_accounts_bench(
|
||||
entrypoint_addr: SocketAddr,
|
||||
faucet_addr: SocketAddr,
|
||||
payer_keypairs: &[&Keypair],
|
||||
iterations: usize,
|
||||
maybe_space: Option<u64>,
|
||||
batch_size: usize,
|
||||
close_nth_batch: u64,
|
||||
maybe_lamports: Option<u64>,
|
||||
num_instructions: usize,
|
||||
mint: Option<Pubkey>,
|
||||
) {
|
||||
assert!(num_instructions > 0);
|
||||
let client =
|
||||
RpcClient::new_socket_with_commitment(entrypoint_addr, CommitmentConfig::confirmed());
|
||||
|
||||
info!("Targeting {}", entrypoint_addr);
|
||||
|
||||
let mut last_blockhash = Instant::now();
|
||||
let mut last_log = Instant::now();
|
||||
let mut count = 0;
|
||||
let mut recent_blockhash = client.get_recent_blockhash().expect("blockhash");
|
||||
let mut tx_sent_count = 0;
|
||||
let mut total_accounts_created = 0;
|
||||
let mut total_accounts_closed = 0;
|
||||
let mut balances: Vec<_> = payer_keypairs
|
||||
.iter()
|
||||
.map(|keypair| client.get_balance(&keypair.pubkey()).unwrap_or(0))
|
||||
.collect();
|
||||
let mut last_balance = Instant::now();
|
||||
|
||||
let default_max_lamports = 1000;
|
||||
let min_balance = maybe_lamports.unwrap_or_else(|| {
|
||||
let space = maybe_space.unwrap_or(default_max_lamports);
|
||||
client
|
||||
.get_minimum_balance_for_rent_exemption(space as usize)
|
||||
.expect("min balance")
|
||||
});
|
||||
|
||||
let base_keypair = Keypair::new();
|
||||
let seed_tracker = SeedTracker {
|
||||
max_created: Arc::new(AtomicU64::default()),
|
||||
max_closed: Arc::new(AtomicU64::default()),
|
||||
};
|
||||
|
||||
info!("Starting balance(s): {:?}", balances);
|
||||
|
||||
let executor = TransactionExecutor::new(entrypoint_addr);
|
||||
|
||||
loop {
|
||||
if last_blockhash.elapsed().as_millis() > 10_000 {
|
||||
recent_blockhash = client.get_recent_blockhash().expect("blockhash");
|
||||
last_blockhash = Instant::now();
|
||||
}
|
||||
|
||||
let fee = recent_blockhash
|
||||
.1
|
||||
.lamports_per_signature
|
||||
.saturating_mul(NUM_SIGNATURES);
|
||||
let lamports = min_balance + fee;
|
||||
|
||||
for (i, balance) in balances.iter_mut().enumerate() {
|
||||
if *balance < lamports || last_balance.elapsed().as_millis() > 2000 {
|
||||
if let Ok(b) = client.get_balance(&payer_keypairs[i].pubkey()) {
|
||||
*balance = b;
|
||||
}
|
||||
last_balance = Instant::now();
|
||||
if *balance < lamports * 2 {
|
||||
info!(
|
||||
"Balance {} is less than needed: {}, doing aidrop...",
|
||||
balance, lamports
|
||||
);
|
||||
if !airdrop_lamports(
|
||||
&client,
|
||||
&faucet_addr,
|
||||
&payer_keypairs[i],
|
||||
lamports * 100_000,
|
||||
) {
|
||||
warn!("failed airdrop, exiting");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create accounts
|
||||
let sigs_len = executor.num_outstanding();
|
||||
if sigs_len < batch_size {
|
||||
let num_to_create = batch_size - sigs_len;
|
||||
if num_to_create >= payer_keypairs.len() {
|
||||
info!("creating {} new", num_to_create);
|
||||
let chunk_size = num_to_create / payer_keypairs.len();
|
||||
if chunk_size > 0 {
|
||||
for (i, keypair) in payer_keypairs.iter().enumerate() {
|
||||
let txs: Vec<_> = (0..chunk_size)
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let message = make_create_message(
|
||||
keypair,
|
||||
&base_keypair,
|
||||
seed_tracker.max_created.clone(),
|
||||
num_instructions,
|
||||
min_balance,
|
||||
maybe_space,
|
||||
mint,
|
||||
);
|
||||
let signers: Vec<&Keypair> = vec![keypair, &base_keypair];
|
||||
Transaction::new(&signers, message, recent_blockhash.0)
|
||||
})
|
||||
.collect();
|
||||
balances[i] = balances[i].saturating_sub(lamports * txs.len() as u64);
|
||||
info!("txs: {}", txs.len());
|
||||
let new_ids = executor.push_transactions(txs);
|
||||
info!("ids: {}", new_ids.len());
|
||||
tx_sent_count += new_ids.len();
|
||||
total_accounts_created += num_instructions * new_ids.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if close_nth_batch > 0 {
|
||||
let num_batches_to_close =
|
||||
total_accounts_created as u64 / (close_nth_batch * batch_size as u64);
|
||||
let expected_closed = num_batches_to_close * batch_size as u64;
|
||||
let max_closed_seed = seed_tracker.max_closed.load(Ordering::Relaxed);
|
||||
// Close every account we've created with seed between max_closed_seed..expected_closed
|
||||
if max_closed_seed < expected_closed {
|
||||
let txs: Vec<_> = (0..expected_closed - max_closed_seed)
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let message = make_close_message(
|
||||
&payer_keypairs[0],
|
||||
&base_keypair,
|
||||
seed_tracker.max_closed.clone(),
|
||||
1,
|
||||
min_balance,
|
||||
mint.is_some(),
|
||||
);
|
||||
let signers: Vec<&Keypair> = vec![&payer_keypairs[0], &base_keypair];
|
||||
Transaction::new(&signers, message, recent_blockhash.0)
|
||||
})
|
||||
.collect();
|
||||
balances[0] = balances[0].saturating_sub(fee * txs.len() as u64);
|
||||
info!("close txs: {}", txs.len());
|
||||
let new_ids = executor.push_transactions(txs);
|
||||
info!("close ids: {}", new_ids.len());
|
||||
tx_sent_count += new_ids.len();
|
||||
total_accounts_closed += new_ids.len() as u64;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let _ = executor.drain_cleared();
|
||||
}
|
||||
|
||||
count += 1;
|
||||
if last_log.elapsed().as_millis() > 3000 {
|
||||
info!(
|
||||
"total_accounts_created: {} total_accounts_closed: {} tx_sent_count: {} loop_count: {} balance(s): {:?}",
|
||||
total_accounts_created, total_accounts_closed, tx_sent_count, count, balances
|
||||
);
|
||||
last_log = Instant::now();
|
||||
}
|
||||
if iterations != 0 && count >= iterations {
|
||||
break;
|
||||
}
|
||||
if executor.num_outstanding() >= batch_size {
|
||||
sleep(Duration::from_millis(500));
|
||||
}
|
||||
}
|
||||
executor.close();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup_with_default("solana=info");
|
||||
let matches = App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(solana_version::version!())
|
||||
.arg(
|
||||
Arg::with_name("entrypoint")
|
||||
.long("entrypoint")
|
||||
.takes_value(true)
|
||||
.value_name("HOST:PORT")
|
||||
.help("RPC entrypoint address. Usually <ip>:8899"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("faucet_addr")
|
||||
.long("faucet")
|
||||
.takes_value(true)
|
||||
.value_name("HOST:PORT")
|
||||
.help("Faucet entrypoint address. Usually <ip>:9900"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("space")
|
||||
.long("space")
|
||||
.takes_value(true)
|
||||
.value_name("BYTES")
|
||||
.help("Size of accounts to create"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("lamports")
|
||||
.long("lamports")
|
||||
.takes_value(true)
|
||||
.value_name("LAMPORTS")
|
||||
.help("How many lamports to fund each account"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("identity")
|
||||
.long("identity")
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.value_name("FILE")
|
||||
.help("keypair file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("batch_size")
|
||||
.long("batch-size")
|
||||
.takes_value(true)
|
||||
.value_name("BYTES")
|
||||
.help("Number of transactions to send per batch"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("close_nth_batch")
|
||||
.long("close-frequency")
|
||||
.takes_value(true)
|
||||
.value_name("BYTES")
|
||||
.help(
|
||||
"Every `n` batches, create a batch of close transactions for
|
||||
the earliest remaining batch of accounts created.
|
||||
Note: Should be > 1 to avoid situations where the close \
|
||||
transactions will be submitted before the corresponding \
|
||||
create transactions have been confirmed",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num_instructions")
|
||||
.long("num-instructions")
|
||||
.takes_value(true)
|
||||
.value_name("NUM")
|
||||
.help("Number of accounts to create on each transaction"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("iterations")
|
||||
.long("iterations")
|
||||
.takes_value(true)
|
||||
.value_name("NUM")
|
||||
.help("Number of iterations to make"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("check_gossip")
|
||||
.long("check-gossip")
|
||||
.help("Just use entrypoint address directly"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("mint")
|
||||
.long("mint")
|
||||
.takes_value(true)
|
||||
.help("Mint address to initialize account"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let skip_gossip = !matches.is_present("check_gossip");
|
||||
|
||||
let port = if skip_gossip { DEFAULT_RPC_PORT } else { 8001 };
|
||||
let mut entrypoint_addr = SocketAddr::from(([127, 0, 0, 1], port));
|
||||
if let Some(addr) = matches.value_of("entrypoint") {
|
||||
entrypoint_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse entrypoint address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
}
|
||||
let mut faucet_addr = SocketAddr::from(([127, 0, 0, 1], FAUCET_PORT));
|
||||
if let Some(addr) = matches.value_of("faucet_addr") {
|
||||
faucet_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse entrypoint address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
}
|
||||
|
||||
let space = value_t!(matches, "space", u64).ok();
|
||||
let lamports = value_t!(matches, "lamports", u64).ok();
|
||||
let batch_size = value_t!(matches, "batch_size", usize).unwrap_or(4);
|
||||
let close_nth_batch = value_t!(matches, "close_nth_batch", u64).unwrap_or(0);
|
||||
let iterations = value_t!(matches, "iterations", usize).unwrap_or(10);
|
||||
let num_instructions = value_t!(matches, "num_instructions", usize).unwrap_or(1);
|
||||
if num_instructions == 0 || num_instructions > 500 {
|
||||
eprintln!("bad num_instructions: {}", num_instructions);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let mint = pubkey_of(&matches, "mint");
|
||||
|
||||
let payer_keypairs: Vec<_> = values_t_or_exit!(matches, "identity", String)
|
||||
.iter()
|
||||
.map(|keypair_string| {
|
||||
read_keypair_file(keypair_string)
|
||||
.unwrap_or_else(|_| panic!("bad keypair {:?}", keypair_string))
|
||||
})
|
||||
.collect();
|
||||
let mut payer_keypair_refs: Vec<&Keypair> = vec![];
|
||||
for keypair in payer_keypairs.iter() {
|
||||
payer_keypair_refs.push(keypair);
|
||||
}
|
||||
|
||||
let rpc_addr = if !skip_gossip {
|
||||
info!("Finding cluster entry: {:?}", entrypoint_addr);
|
||||
let (gossip_nodes, _validators) = discover(
|
||||
None, // keypair
|
||||
Some(&entrypoint_addr),
|
||||
None, // num_nodes
|
||||
Duration::from_secs(60), // timeout
|
||||
None, // find_node_by_pubkey
|
||||
Some(&entrypoint_addr), // find_node_by_gossip_addr
|
||||
None, // my_gossip_addr
|
||||
0, // my_shred_version
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to discover {} node: {:?}", entrypoint_addr, err);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
info!("done found {} nodes", gossip_nodes.len());
|
||||
gossip_nodes[0].rpc
|
||||
} else {
|
||||
info!("Using {:?} as the RPC address", entrypoint_addr);
|
||||
entrypoint_addr
|
||||
};
|
||||
|
||||
run_accounts_bench(
|
||||
rpc_addr,
|
||||
faucet_addr,
|
||||
&payer_keypair_refs,
|
||||
iterations,
|
||||
space,
|
||||
batch_size,
|
||||
close_nth_batch,
|
||||
lamports,
|
||||
num_instructions,
|
||||
mint,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use solana_core::validator::ValidatorConfig;
|
||||
use solana_local_cluster::{
|
||||
local_cluster::{ClusterConfig, LocalCluster},
|
||||
validator_configs::make_identical_validator_configs,
|
||||
};
|
||||
use solana_sdk::poh_config::PohConfig;
|
||||
|
||||
#[test]
|
||||
fn test_accounts_cluster_bench() {
|
||||
solana_logger::setup();
|
||||
let validator_config = ValidatorConfig::default();
|
||||
let num_nodes = 1;
|
||||
let mut config = ClusterConfig {
|
||||
cluster_lamports: 10_000_000,
|
||||
poh_config: PohConfig::new_sleep(Duration::from_millis(50)),
|
||||
node_stakes: vec![100; num_nodes],
|
||||
validator_configs: make_identical_validator_configs(&validator_config, num_nodes),
|
||||
..ClusterConfig::default()
|
||||
};
|
||||
|
||||
let faucet_addr = SocketAddr::from(([127, 0, 0, 1], 9900));
|
||||
let cluster = LocalCluster::new(&mut config);
|
||||
let iterations = 10;
|
||||
let maybe_space = None;
|
||||
let batch_size = 100;
|
||||
let close_nth_batch = 100;
|
||||
let maybe_lamports = None;
|
||||
let num_instructions = 2;
|
||||
let mut start = Measure::start("total accounts run");
|
||||
run_accounts_bench(
|
||||
cluster.entry_point_info.rpc,
|
||||
faucet_addr,
|
||||
&[&cluster.funding_keypair],
|
||||
iterations,
|
||||
maybe_space,
|
||||
batch_size,
|
||||
close_nth_batch,
|
||||
maybe_lamports,
|
||||
num_instructions,
|
||||
None,
|
||||
);
|
||||
start.stop();
|
||||
info!("{}", start);
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "1.7.2"
|
||||
version = "1.4.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -11,21 +11,19 @@ publish = false
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
crossbeam-channel = "0.4"
|
||||
log = "0.4.11"
|
||||
log = "0.4.6"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.5.0"
|
||||
solana-core = { path = "../core", version = "=1.7.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.2" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.2" }
|
||||
solana-ledger = { path = "../ledger", version = "=1.7.2" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.2" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.2" }
|
||||
solana-perf = { path = "../perf", version = "=1.7.2" }
|
||||
solana-poh = { path = "../poh", version = "=1.7.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.2" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.7.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.2" }
|
||||
solana-version = { path = "../version", version = "=1.7.2" }
|
||||
rayon = "1.4.0"
|
||||
solana-core = { path = "../core", version = "1.4.14" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.4.14" }
|
||||
solana-streamer = { path = "../streamer", version = "1.4.14" }
|
||||
solana-perf = { path = "../perf", version = "1.4.14" }
|
||||
solana-ledger = { path = "../ledger", version = "1.4.14" }
|
||||
solana-logger = { path = "../logger", version = "1.4.14" }
|
||||
solana-runtime = { path = "../runtime", version = "1.4.14" }
|
||||
solana-measure = { path = "../measure", version = "1.4.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.4.14" }
|
||||
solana-version = { path = "../version", version = "1.4.14" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -1,11 +1,15 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
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::BankingStage;
|
||||
use solana_gossip::{cluster_info::ClusterInfo, cluster_info::Node};
|
||||
use solana_core::{
|
||||
banking_stage::{create_test_recorder, BankingStage},
|
||||
cluster_info::ClusterInfo,
|
||||
cluster_info::Node,
|
||||
poh_recorder::PohRecorder,
|
||||
poh_recorder::WorkingBankEntry,
|
||||
};
|
||||
use solana_ledger::{
|
||||
blockstore::Blockstore,
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
@@ -13,10 +17,7 @@ use solana_ledger::{
|
||||
};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_poh::poh_recorder::{create_test_recorder, PohRecorder, WorkingBankEntry};
|
||||
use solana_runtime::{
|
||||
accounts_background_service::AbsRequestSender, bank::Bank, bank_forks::BankForks,
|
||||
};
|
||||
use solana_runtime::{bank::Bank, bank_forks::BankForks};
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
signature::Keypair,
|
||||
@@ -74,7 +75,7 @@ fn make_accounts_txs(
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let mut new = dummy.clone();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen::<u8>()).collect();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
if !same_payer {
|
||||
new.message.account_keys[0] = solana_sdk::pubkey::new_rand();
|
||||
}
|
||||
@@ -185,7 +186,7 @@ fn main() {
|
||||
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::<u8>()).collect();
|
||||
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();
|
||||
@@ -322,7 +323,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, &AbsRequestSender::default(), None);
|
||||
bank_forks.set_root(root, &None, None);
|
||||
root += 1;
|
||||
}
|
||||
debug!(
|
||||
@@ -351,7 +352,7 @@ fn main() {
|
||||
if bank.slot() > 0 && bank.slot() % 16 == 0 {
|
||||
for tx in transactions.iter_mut() {
|
||||
tx.message.recent_blockhash = bank.last_blockhash();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen::<u8>()).collect();
|
||||
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);
|
||||
|
@@ -1,30 +1,26 @@
|
||||
[package]
|
||||
name = "solana-banks-client"
|
||||
version = "1.7.2"
|
||||
version = "1.4.14"
|
||||
description = "Solana banks client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-banks-client"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.36"
|
||||
bincode = "1.3.1"
|
||||
borsh = "0.8.1"
|
||||
borsh-derive = "0.8.1"
|
||||
futures = "0.3"
|
||||
mio = "0.7.6"
|
||||
solana-banks-interface = { path = "../banks-interface", version = "=1.7.2" }
|
||||
solana-program = { path = "../sdk/program", version = "=1.7.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.2" }
|
||||
tarpc = { version = "0.24.1", features = ["full"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-serde = { version = "0.8", features = ["bincode"] }
|
||||
solana-banks-interface = { path = "../banks-interface", version = "1.4.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.4.14" }
|
||||
tarpc = { version = "0.23.0", features = ["full"] }
|
||||
tokio = { version = "0.3", features = ["full"] }
|
||||
tokio-serde = { version = "0.6", features = ["bincode"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.2" }
|
||||
solana-banks-server = { path = "../banks-server", version = "=1.7.2" }
|
||||
solana-runtime = { path = "../runtime", version = "1.4.14" }
|
||||
solana-banks-server = { path = "../banks-server", version = "1.4.14" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@@ -5,276 +5,200 @@
|
||||
//! but they are undocumented, may change over time, and are generally more
|
||||
//! cumbersome to use.
|
||||
|
||||
use borsh::BorshDeserialize;
|
||||
use futures::{future::join_all, Future, FutureExt};
|
||||
pub use solana_banks_interface::{BanksClient as TarpcClient, TransactionStatus};
|
||||
use async_trait::async_trait;
|
||||
use futures::future::join_all;
|
||||
pub use solana_banks_interface::{BanksClient, TransactionStatus};
|
||||
use solana_banks_interface::{BanksRequest, BanksResponse};
|
||||
use solana_program::{
|
||||
clock::Slot, fee_calculator::FeeCalculator, hash::Hash, program_pack::Pack, pubkey::Pubkey,
|
||||
rent::Rent, sysvar,
|
||||
};
|
||||
use solana_sdk::{
|
||||
account::{from_account, Account},
|
||||
clock::Slot,
|
||||
commitment_config::CommitmentLevel,
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
signature::Signature,
|
||||
transaction::{self, Transaction},
|
||||
sysvar,
|
||||
transaction::Transaction,
|
||||
transport,
|
||||
};
|
||||
use std::io::{self, Error, ErrorKind};
|
||||
use tarpc::{
|
||||
client::{self, channel::RequestDispatch, NewClient},
|
||||
context::{self, Context},
|
||||
rpc::{ClientMessage, Response},
|
||||
client, context,
|
||||
rpc::{transport::channel::UnboundedChannel, ClientMessage, Response},
|
||||
serde_transport::tcp,
|
||||
Transport,
|
||||
};
|
||||
use tokio::{net::ToSocketAddrs, time::Duration};
|
||||
use tokio_serde::formats::Bincode;
|
||||
|
||||
// This exists only for backward compatibility
|
||||
pub trait BanksClientExt {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BanksClient {
|
||||
inner: TarpcClient,
|
||||
}
|
||||
|
||||
impl BanksClient {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new<C>(
|
||||
config: client::Config,
|
||||
transport: C,
|
||||
) -> NewClient<TarpcClient, RequestDispatch<BanksRequest, BanksResponse, C>>
|
||||
where
|
||||
C: Transport<ClientMessage<BanksRequest>, Response<BanksResponse>>,
|
||||
{
|
||||
TarpcClient::new(config, transport)
|
||||
}
|
||||
|
||||
pub fn send_transaction_with_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
transaction: Transaction,
|
||||
) -> impl Future<Output = io::Result<()>> + '_ {
|
||||
self.inner.send_transaction_with_context(ctx, transaction)
|
||||
}
|
||||
|
||||
pub fn get_fees_with_commitment_and_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<(FeeCalculator, Hash, Slot)>> + '_ {
|
||||
self.inner
|
||||
.get_fees_with_commitment_and_context(ctx, commitment)
|
||||
}
|
||||
|
||||
pub fn get_transaction_status_with_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
signature: Signature,
|
||||
) -> impl Future<Output = io::Result<Option<TransactionStatus>>> + '_ {
|
||||
self.inner
|
||||
.get_transaction_status_with_context(ctx, signature)
|
||||
}
|
||||
|
||||
pub fn get_slot_with_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<Slot>> + '_ {
|
||||
self.inner.get_slot_with_context(ctx, commitment)
|
||||
}
|
||||
|
||||
pub fn process_transaction_with_commitment_and_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
transaction: Transaction,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<Option<transaction::Result<()>>>> + '_ {
|
||||
self.inner
|
||||
.process_transaction_with_commitment_and_context(ctx, transaction, commitment)
|
||||
}
|
||||
|
||||
pub fn get_account_with_commitment_and_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
address: Pubkey,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<Option<Account>>> + '_ {
|
||||
self.inner
|
||||
.get_account_with_commitment_and_context(ctx, address, commitment)
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait BanksClientExt {
|
||||
/// Send a transaction and return immediately. The server will resend the
|
||||
/// transaction until either it is accepted by the cluster or the transaction's
|
||||
/// blockhash expires.
|
||||
pub fn send_transaction(
|
||||
&mut self,
|
||||
transaction: Transaction,
|
||||
) -> impl Future<Output = io::Result<()>> + '_ {
|
||||
self.send_transaction_with_context(context::current(), transaction)
|
||||
}
|
||||
|
||||
/// Return the fee parameters associated with a recent, rooted blockhash. The cluster
|
||||
/// will use the transaction's blockhash to look up these same fee parameters and
|
||||
/// use them to calculate the transaction fee.
|
||||
pub fn get_fees(
|
||||
&mut self,
|
||||
) -> impl Future<Output = io::Result<(FeeCalculator, Hash, Slot)>> + '_ {
|
||||
self.get_fees_with_commitment_and_context(context::current(), CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the cluster rent
|
||||
pub fn get_rent(&mut self) -> impl Future<Output = io::Result<Rent>> + '_ {
|
||||
self.get_account(sysvar::rent::id()).map(|result| {
|
||||
let rent_sysvar = result?
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Rent sysvar not present"))?;
|
||||
from_account::<Rent, _>(&rent_sysvar).ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::Other, "Failed to deserialize Rent sysvar")
|
||||
})
|
||||
})
|
||||
}
|
||||
async fn send_transaction(&mut self, transaction: Transaction) -> io::Result<()>;
|
||||
|
||||
/// Return a recent, rooted blockhash from the server. The cluster will only accept
|
||||
/// transactions with a blockhash that has not yet expired. Use the `get_fees`
|
||||
/// method to get both a blockhash and the blockhash's last valid slot.
|
||||
pub fn get_recent_blockhash(&mut self) -> impl Future<Output = io::Result<Hash>> + '_ {
|
||||
self.get_fees().map(|result| Ok(result?.1))
|
||||
}
|
||||
async fn get_recent_blockhash(&mut self) -> io::Result<Hash>;
|
||||
|
||||
/// Return the fee parameters associated with a recent, rooted blockhash. The cluster
|
||||
/// will use the transaction's blockhash to look up these same fee parameters and
|
||||
/// use them to calculate the transaction fee.
|
||||
async fn get_fees(&mut self) -> io::Result<(FeeCalculator, Hash, Slot)>;
|
||||
|
||||
/// Return the cluster rent
|
||||
async fn get_rent(&mut self) -> io::Result<Rent>;
|
||||
|
||||
/// Send a transaction and return after the transaction has been rejected or
|
||||
/// reached the given level of commitment.
|
||||
pub fn process_transaction_with_commitment(
|
||||
async fn process_transaction_with_commitment(
|
||||
&mut self,
|
||||
transaction: Transaction,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = transport::Result<()>> + '_ {
|
||||
let mut ctx = context::current();
|
||||
ctx.deadline += Duration::from_secs(50);
|
||||
self.process_transaction_with_commitment_and_context(ctx, transaction, commitment)
|
||||
.map(|result| match result? {
|
||||
None => {
|
||||
Err(Error::new(ErrorKind::TimedOut, "invalid blockhash or fee-payer").into())
|
||||
}
|
||||
Some(transaction_result) => Ok(transaction_result?),
|
||||
})
|
||||
}
|
||||
) -> transport::Result<()>;
|
||||
|
||||
/// Send a transaction and return until the transaction has been finalized or rejected.
|
||||
pub fn process_transaction(
|
||||
&mut self,
|
||||
transaction: Transaction,
|
||||
) -> impl Future<Output = transport::Result<()>> + '_ {
|
||||
self.process_transaction_with_commitment(transaction, CommitmentLevel::default())
|
||||
}
|
||||
|
||||
pub async fn process_transactions_with_commitment(
|
||||
&mut self,
|
||||
transactions: Vec<Transaction>,
|
||||
commitment: CommitmentLevel,
|
||||
) -> transport::Result<()> {
|
||||
let mut clients: Vec<_> = transactions.iter().map(|_| self.clone()).collect();
|
||||
let futures = clients
|
||||
.iter_mut()
|
||||
.zip(transactions)
|
||||
.map(|(client, transaction)| {
|
||||
client.process_transaction_with_commitment(transaction, commitment)
|
||||
});
|
||||
let statuses = join_all(futures).await;
|
||||
statuses.into_iter().collect() // Convert Vec<Result<_, _>> to Result<Vec<_>>
|
||||
}
|
||||
|
||||
/// Send transactions and return until the transaction has been finalized or rejected.
|
||||
pub fn process_transactions(
|
||||
&mut self,
|
||||
transactions: Vec<Transaction>,
|
||||
) -> impl Future<Output = transport::Result<()>> + '_ {
|
||||
self.process_transactions_with_commitment(transactions, CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the most recent rooted slot height. All transactions at or below this height
|
||||
/// are said to be finalized. The cluster will not fork to a higher slot height.
|
||||
pub fn get_root_slot(&mut self) -> impl Future<Output = io::Result<Slot>> + '_ {
|
||||
self.get_slot_with_context(context::current(), CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the account at the given address at the slot corresponding to the given
|
||||
/// commitment level. If the account is not found, None is returned.
|
||||
pub fn get_account_with_commitment(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<Option<Account>>> + '_ {
|
||||
self.get_account_with_commitment_and_context(context::current(), address, commitment)
|
||||
}
|
||||
|
||||
/// Return the account at the given address at the time of the most recent root slot.
|
||||
/// If the account is not found, None is returned.
|
||||
pub fn get_account(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
) -> impl Future<Output = io::Result<Option<Account>>> + '_ {
|
||||
self.get_account_with_commitment(address, CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the unpacked account data at the given address
|
||||
/// If the account is not found, an error is returned
|
||||
pub fn get_packed_account_data<T: Pack>(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
) -> impl Future<Output = io::Result<T>> + '_ {
|
||||
self.get_account(address).map(|result| {
|
||||
let account =
|
||||
result?.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Account not found"))?;
|
||||
T::unpack_from_slice(&account.data)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Failed to deserialize account"))
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the unpacked account data at the given address
|
||||
/// If the account is not found, an error is returned
|
||||
pub fn get_account_data_with_borsh<T: BorshDeserialize>(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
) -> impl Future<Output = io::Result<T>> + '_ {
|
||||
self.get_account(address).map(|result| {
|
||||
let account =
|
||||
result?.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "account not found"))?;
|
||||
T::try_from_slice(&account.data)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the balance in lamports of an account at the given address at the slot
|
||||
/// corresponding to the given commitment level.
|
||||
pub fn get_balance_with_commitment(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<u64>> + '_ {
|
||||
self.get_account_with_commitment_and_context(context::current(), address, commitment)
|
||||
.map(|result| Ok(result?.map(|x| x.lamports).unwrap_or(0)))
|
||||
}
|
||||
|
||||
/// Return the balance in lamports of an account at the given address at the time
|
||||
/// of the most recent root slot.
|
||||
pub fn get_balance(&mut self, address: Pubkey) -> impl Future<Output = io::Result<u64>> + '_ {
|
||||
self.get_balance_with_commitment(address, CommitmentLevel::default())
|
||||
}
|
||||
/// Send a transaction and return after the transaction has been finalized or rejected.
|
||||
async fn process_transaction(&mut self, transaction: Transaction) -> transport::Result<()>;
|
||||
|
||||
/// Return the status of a transaction with a signature matching the transaction's first
|
||||
/// signature. Return None if the transaction is not found, which may be because the
|
||||
/// blockhash was expired or the fee-paying account had insufficient funds to pay the
|
||||
/// transaction fee. Note that servers rarely store the full transaction history. This
|
||||
/// method may return None if the transaction status has been discarded.
|
||||
pub fn get_transaction_status(
|
||||
async fn get_transaction_status(
|
||||
&mut self,
|
||||
signature: Signature,
|
||||
) -> impl Future<Output = io::Result<Option<TransactionStatus>>> + '_ {
|
||||
self.get_transaction_status_with_context(context::current(), signature)
|
||||
}
|
||||
) -> io::Result<Option<TransactionStatus>>;
|
||||
|
||||
/// Same as get_transaction_status, but for multiple transactions.
|
||||
pub async fn get_transaction_statuses(
|
||||
async fn get_transaction_statuses(
|
||||
&mut self,
|
||||
signatures: Vec<Signature>,
|
||||
) -> io::Result<Vec<Option<TransactionStatus>>>;
|
||||
|
||||
/// Return the most recent rooted slot height. All transactions at or below this height
|
||||
/// are said to be finalized. The cluster will not fork to a higher slot height.
|
||||
async fn get_root_slot(&mut self) -> io::Result<Slot>;
|
||||
|
||||
/// Return the account at the given address at the slot corresponding to the given
|
||||
/// commitment level. If the account is not found, None is returned.
|
||||
async fn get_account_with_commitment(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
commitment: CommitmentLevel,
|
||||
) -> io::Result<Option<Account>>;
|
||||
|
||||
/// Return the account at the given address at the time of the most recent root slot.
|
||||
/// If the account is not found, None is returned.
|
||||
async fn get_account(&mut self, address: Pubkey) -> io::Result<Option<Account>>;
|
||||
|
||||
/// Return the balance in lamports of an account at the given address at the slot
|
||||
/// corresponding to the given commitment level.
|
||||
async fn get_balance_with_commitment(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
commitment: CommitmentLevel,
|
||||
) -> io::Result<u64>;
|
||||
|
||||
/// Return the balance in lamports of an account at the given address at the time
|
||||
/// of the most recent root slot.
|
||||
async fn get_balance(&mut self, address: Pubkey) -> io::Result<u64>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl BanksClientExt for BanksClient {
|
||||
async fn send_transaction(&mut self, transaction: Transaction) -> io::Result<()> {
|
||||
self.send_transaction_with_context(context::current(), transaction)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_fees(&mut self) -> io::Result<(FeeCalculator, Hash, Slot)> {
|
||||
self.get_fees_with_commitment_and_context(context::current(), CommitmentLevel::Root)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_rent(&mut self) -> io::Result<Rent> {
|
||||
let rent_sysvar = self
|
||||
.get_account(sysvar::rent::id())
|
||||
.await?
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Rent sysvar not present"))?;
|
||||
|
||||
from_account::<Rent>(&rent_sysvar).ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::Other, "Failed to deserialize Rent sysvar")
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_recent_blockhash(&mut self) -> io::Result<Hash> {
|
||||
Ok(self.get_fees().await?.1)
|
||||
}
|
||||
|
||||
async fn process_transaction_with_commitment(
|
||||
&mut self,
|
||||
transaction: Transaction,
|
||||
commitment: CommitmentLevel,
|
||||
) -> transport::Result<()> {
|
||||
let mut ctx = context::current();
|
||||
ctx.deadline += Duration::from_secs(50);
|
||||
let result = self
|
||||
.process_transaction_with_commitment_and_context(ctx, transaction, commitment)
|
||||
.await?;
|
||||
match result {
|
||||
None => Err(Error::new(ErrorKind::TimedOut, "invalid blockhash or fee-payer").into()),
|
||||
Some(transaction_result) => Ok(transaction_result?),
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_transaction(&mut self, transaction: Transaction) -> transport::Result<()> {
|
||||
self.process_transaction_with_commitment(transaction, CommitmentLevel::default())
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_root_slot(&mut self) -> io::Result<Slot> {
|
||||
self.get_slot_with_context(context::current(), CommitmentLevel::Root)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_account_with_commitment(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
commitment: CommitmentLevel,
|
||||
) -> io::Result<Option<Account>> {
|
||||
self.get_account_with_commitment_and_context(context::current(), address, commitment)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_account(&mut self, address: Pubkey) -> io::Result<Option<Account>> {
|
||||
self.get_account_with_commitment(address, CommitmentLevel::default())
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_balance_with_commitment(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
commitment: CommitmentLevel,
|
||||
) -> io::Result<u64> {
|
||||
let account = self
|
||||
.get_account_with_commitment_and_context(context::current(), address, commitment)
|
||||
.await?;
|
||||
Ok(account.map(|x| x.lamports).unwrap_or(0))
|
||||
}
|
||||
|
||||
async fn get_balance(&mut self, address: Pubkey) -> io::Result<u64> {
|
||||
self.get_balance_with_commitment(address, CommitmentLevel::default())
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_transaction_status(
|
||||
&mut self,
|
||||
signature: Signature,
|
||||
) -> io::Result<Option<TransactionStatus>> {
|
||||
self.get_transaction_status_with_context(context::current(), signature)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_transaction_statuses(
|
||||
&mut self,
|
||||
signatures: Vec<Signature>,
|
||||
) -> io::Result<Vec<Option<TransactionStatus>>> {
|
||||
@@ -295,30 +219,22 @@ impl BanksClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_client<C>(transport: C) -> io::Result<BanksClient>
|
||||
where
|
||||
C: Transport<ClientMessage<BanksRequest>, Response<BanksResponse>> + Send + 'static,
|
||||
{
|
||||
Ok(BanksClient {
|
||||
inner: TarpcClient::new(client::Config::default(), transport).spawn()?,
|
||||
})
|
||||
pub async fn start_client(
|
||||
transport: UnboundedChannel<Response<BanksResponse>, ClientMessage<BanksRequest>>,
|
||||
) -> io::Result<BanksClient> {
|
||||
BanksClient::new(client::Config::default(), transport).spawn()
|
||||
}
|
||||
|
||||
pub async fn start_tcp_client<T: ToSocketAddrs>(addr: T) -> io::Result<BanksClient> {
|
||||
let transport = tcp::connect(addr, Bincode::default).await?;
|
||||
Ok(BanksClient {
|
||||
inner: TarpcClient::new(client::Config::default(), transport).spawn()?,
|
||||
})
|
||||
BanksClient::new(client::Config::default(), transport).spawn()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_banks_server::banks_server::start_local_server;
|
||||
use solana_runtime::{
|
||||
bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache,
|
||||
genesis_utils::create_genesis_config,
|
||||
};
|
||||
use solana_runtime::{bank::Bank, bank_forks::BankForks, genesis_utils::create_genesis_config};
|
||||
use solana_sdk::{message::Message, signature::Signer, system_instruction};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use tarpc::transport;
|
||||
@@ -337,12 +253,9 @@ mod tests {
|
||||
// `runtime.block_on()` just once, to run all the async code.
|
||||
|
||||
let genesis = create_genesis_config(10);
|
||||
let bank = Bank::new(&genesis.genesis_config);
|
||||
let slot = bank.slot();
|
||||
let block_commitment_cache = Arc::new(RwLock::new(
|
||||
BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
|
||||
));
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::new(
|
||||
&genesis.genesis_config,
|
||||
))));
|
||||
|
||||
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let mint_pubkey = genesis.mint_keypair.pubkey();
|
||||
@@ -350,8 +263,9 @@ mod tests {
|
||||
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
||||
|
||||
Runtime::new()?.block_on(async {
|
||||
let client_transport = start_local_server(bank_forks, block_commitment_cache).await;
|
||||
let mut banks_client = start_client(client_transport).await?;
|
||||
let client_transport = start_local_server(&bank_forks).await;
|
||||
let mut banks_client =
|
||||
BanksClient::new(client::Config::default(), client_transport).spawn()?;
|
||||
|
||||
let recent_blockhash = banks_client.get_recent_blockhash().await?;
|
||||
let transaction = Transaction::new(&[&genesis.mint_keypair], message, recent_blockhash);
|
||||
@@ -368,12 +282,9 @@ mod tests {
|
||||
// server-side functionality is available to the client.
|
||||
|
||||
let genesis = create_genesis_config(10);
|
||||
let bank = Bank::new(&genesis.genesis_config);
|
||||
let slot = bank.slot();
|
||||
let block_commitment_cache = Arc::new(RwLock::new(
|
||||
BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
|
||||
));
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::new(
|
||||
&genesis.genesis_config,
|
||||
))));
|
||||
|
||||
let mint_pubkey = &genesis.mint_keypair.pubkey();
|
||||
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
||||
@@ -381,8 +292,9 @@ mod tests {
|
||||
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
||||
|
||||
Runtime::new()?.block_on(async {
|
||||
let client_transport = start_local_server(bank_forks, block_commitment_cache).await;
|
||||
let mut banks_client = start_client(client_transport).await?;
|
||||
let client_transport = start_local_server(&bank_forks).await;
|
||||
let mut banks_client =
|
||||
BanksClient::new(client::Config::default(), client_transport).spawn()?;
|
||||
let (_, recent_blockhash, last_valid_slot) = banks_client.get_fees().await?;
|
||||
let transaction = Transaction::new(&[&genesis.mint_keypair], message, recent_blockhash);
|
||||
let signature = transaction.signatures[0];
|
||||
|
@@ -1,22 +1,17 @@
|
||||
[package]
|
||||
name = "solana-banks-interface"
|
||||
version = "1.7.2"
|
||||
version = "1.4.14"
|
||||
description = "Solana banks RPC interface"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-banks-interface"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
mio = "0.7.6"
|
||||
serde = { version = "1.0.122", features = ["derive"] }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.2" }
|
||||
tarpc = { version = "0.24.1", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1.0.112", features = ["derive"] }
|
||||
solana-sdk = { path = "../sdk", version = "1.4.14" }
|
||||
tarpc = { version = "0.23.0", features = ["full"] }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@@ -10,19 +10,11 @@ use solana_sdk::{
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum TransactionConfirmationStatus {
|
||||
Processed,
|
||||
Confirmed,
|
||||
Finalized,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TransactionStatus {
|
||||
pub slot: Slot,
|
||||
pub confirmations: Option<usize>, // None = rooted
|
||||
pub err: Option<TransactionError>,
|
||||
pub confirmation_status: Option<TransactionConfirmationStatus>,
|
||||
}
|
||||
|
||||
#[tarpc::service]
|
||||
|
@@ -1,27 +1,24 @@
|
||||
[package]
|
||||
name = "solana-banks-server"
|
||||
version = "1.7.2"
|
||||
version = "1.4.14"
|
||||
description = "Solana banks server"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-banks-server"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.1"
|
||||
futures = "0.3"
|
||||
log = "0.4.11"
|
||||
mio = "0.7.6"
|
||||
solana-banks-interface = { path = "../banks-interface", version = "=1.7.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.2" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.7.2" }
|
||||
tarpc = { version = "0.24.1", features = ["full"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-serde = { version = "0.8", features = ["bincode"] }
|
||||
tokio-stream = "0.1"
|
||||
log = "0.4.8"
|
||||
solana-banks-interface = { path = "../banks-interface", version = "1.4.14" }
|
||||
solana-runtime = { path = "../runtime", version = "1.4.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.4.14" }
|
||||
solana-metrics = { path = "../metrics", version = "1.4.14" }
|
||||
tarpc = { version = "0.23.0", features = ["full"] }
|
||||
tokio = { version = "0.3", features = ["full"] }
|
||||
tokio-serde = { version = "0.6", features = ["bincode"] }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@@ -4,9 +4,7 @@ use futures::{
|
||||
future,
|
||||
prelude::stream::{self, StreamExt},
|
||||
};
|
||||
use solana_banks_interface::{
|
||||
Banks, BanksRequest, BanksResponse, TransactionConfirmationStatus, TransactionStatus,
|
||||
};
|
||||
use solana_banks_interface::{Banks, BanksRequest, BanksResponse, TransactionStatus};
|
||||
use solana_runtime::{bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache};
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
@@ -62,7 +60,7 @@ impl BanksServer {
|
||||
}
|
||||
}
|
||||
|
||||
fn run(bank_forks: Arc<RwLock<BankForks>>, transaction_receiver: Receiver<TransactionInfo>) {
|
||||
fn run(bank: &Bank, transaction_receiver: Receiver<TransactionInfo>) {
|
||||
while let Ok(info) = transaction_receiver.recv() {
|
||||
let mut transaction_infos = vec![info];
|
||||
while let Ok(info) = transaction_receiver.try_recv() {
|
||||
@@ -72,28 +70,21 @@ impl BanksServer {
|
||||
.into_iter()
|
||||
.map(|info| deserialize(&info.wire_transaction).unwrap())
|
||||
.collect();
|
||||
let bank = bank_forks.read().unwrap().working_bank();
|
||||
let _ = bank.process_transactions(&transactions);
|
||||
}
|
||||
}
|
||||
|
||||
/// Useful for unit-testing
|
||||
fn new_loopback(
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
) -> Self {
|
||||
fn new_loopback(bank_forks: Arc<RwLock<BankForks>>) -> Self {
|
||||
let (transaction_sender, transaction_receiver) = channel();
|
||||
let bank = bank_forks.read().unwrap().working_bank();
|
||||
let slot = bank.slot();
|
||||
{
|
||||
// ensure that the commitment cache and bank are synced
|
||||
let mut w_block_commitment_cache = block_commitment_cache.write().unwrap();
|
||||
w_block_commitment_cache.set_all_slots(slot, slot);
|
||||
}
|
||||
let server_bank_forks = bank_forks.clone();
|
||||
let block_commitment_cache = Arc::new(RwLock::new(
|
||||
BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
|
||||
));
|
||||
Builder::new()
|
||||
.name("solana-bank-forks-client".to_string())
|
||||
.spawn(move || Self::run(server_bank_forks, transaction_receiver))
|
||||
.spawn(move || Self::run(&bank, transaction_receiver))
|
||||
.unwrap();
|
||||
Self::new(bank_forks, block_commitment_cache, transaction_sender)
|
||||
}
|
||||
@@ -131,16 +122,6 @@ impl BanksServer {
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_transaction(transaction: &Transaction) -> transaction::Result<()> {
|
||||
if let Err(err) = transaction.verify() {
|
||||
Err(err)
|
||||
} else if let Err(err) = transaction.verify_precompiles() {
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tarpc::server]
|
||||
impl Banks for BanksServer {
|
||||
async fn send_transaction_with_context(self, _: Context, transaction: Transaction) {
|
||||
@@ -174,17 +155,11 @@ impl Banks for BanksServer {
|
||||
_: Context,
|
||||
signature: Signature,
|
||||
) -> Option<TransactionStatus> {
|
||||
let bank = self.bank(CommitmentLevel::Processed);
|
||||
let bank = self.bank(CommitmentLevel::Recent);
|
||||
let (slot, status) = bank.get_signature_status_slot(&signature)?;
|
||||
let r_block_commitment_cache = self.block_commitment_cache.read().unwrap();
|
||||
|
||||
let optimistically_confirmed_bank = self.bank(CommitmentLevel::Confirmed);
|
||||
let optimistically_confirmed =
|
||||
optimistically_confirmed_bank.get_signature_status_slot(&signature);
|
||||
|
||||
let confirmations = if r_block_commitment_cache.root() >= slot
|
||||
&& r_block_commitment_cache.highest_confirmed_root() >= slot
|
||||
{
|
||||
let confirmations = if r_block_commitment_cache.root() >= slot {
|
||||
None
|
||||
} else {
|
||||
r_block_commitment_cache
|
||||
@@ -195,13 +170,6 @@ impl Banks for BanksServer {
|
||||
slot,
|
||||
confirmations,
|
||||
err: status.err(),
|
||||
confirmation_status: if confirmations.is_none() {
|
||||
Some(TransactionConfirmationStatus::Finalized)
|
||||
} else if optimistically_confirmed.is_some() {
|
||||
Some(TransactionConfirmationStatus::Confirmed)
|
||||
} else {
|
||||
Some(TransactionConfirmationStatus::Processed)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -215,10 +183,6 @@ impl Banks for BanksServer {
|
||||
transaction: Transaction,
|
||||
commitment: CommitmentLevel,
|
||||
) -> Option<transaction::Result<()>> {
|
||||
if let Err(err) = verify_transaction(&transaction) {
|
||||
return Some(Err(err));
|
||||
}
|
||||
|
||||
let blockhash = &transaction.message.recent_blockhash;
|
||||
let last_valid_slot = self
|
||||
.bank_forks
|
||||
@@ -242,15 +206,14 @@ impl Banks for BanksServer {
|
||||
commitment: CommitmentLevel,
|
||||
) -> Option<Account> {
|
||||
let bank = self.bank(commitment);
|
||||
bank.get_account(&address).map(Account::from)
|
||||
bank.get_account(&address)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_local_server(
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
) -> UnboundedChannel<Response<BanksResponse>, ClientMessage<BanksRequest>> {
|
||||
let banks_server = BanksServer::new_loopback(bank_forks, block_commitment_cache);
|
||||
let banks_server = BanksServer::new_loopback(bank_forks.clone());
|
||||
let (client_transport, server_transport) = transport::channel::unbounded();
|
||||
let server = server::new(server::Config::default())
|
||||
.incoming(stream::once(future::ready(server_transport)))
|
||||
|
@@ -1,4 +1,3 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
pub mod banks_server;
|
||||
pub mod rpc_banks_service;
|
||||
pub mod send_transaction_service;
|
||||
|
@@ -15,7 +15,6 @@ use tokio::{
|
||||
runtime::Runtime,
|
||||
time::{self, Duration},
|
||||
};
|
||||
use tokio_stream::wrappers::IntervalStream;
|
||||
|
||||
pub struct RpcBanksService {
|
||||
thread_hdl: JoinHandle<()>,
|
||||
@@ -36,7 +35,7 @@ async fn start_abortable_tcp_server(
|
||||
block_commitment_cache.clone(),
|
||||
)
|
||||
.fuse();
|
||||
let interval = IntervalStream::new(time::interval(Duration::from_millis(100))).fuse();
|
||||
let interval = time::interval(Duration::from_millis(100)).fuse();
|
||||
pin_mut!(server, interval);
|
||||
loop {
|
||||
select! {
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "1.7.2"
|
||||
version = "1.4.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -11,29 +11,28 @@ publish = false
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
itertools = "0.9.0"
|
||||
log = "0.4.11"
|
||||
log = "0.4.8"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.5.0"
|
||||
rayon = "1.4.0"
|
||||
serde_json = "1.0.56"
|
||||
serde_yaml = "0.8.13"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.2" }
|
||||
solana-core = { path = "../core", version = "=1.7.2" }
|
||||
solana-genesis = { path = "../genesis", version = "=1.7.2" }
|
||||
solana-client = { path = "../client", version = "=1.7.2" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "=1.7.2" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.2" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.2" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.2" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.7.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.2" }
|
||||
solana-version = { path = "../version", version = "=1.7.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.4.14" }
|
||||
solana-core = { path = "../core", version = "1.4.14" }
|
||||
solana-genesis = { path = "../genesis", version = "1.4.14" }
|
||||
solana-client = { path = "../client", version = "1.4.14" }
|
||||
solana-faucet = { path = "../faucet", version = "1.4.14" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.4.14" }
|
||||
solana-logger = { path = "../logger", version = "1.4.14" }
|
||||
solana-metrics = { path = "../metrics", version = "1.4.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.4.14" }
|
||||
solana-runtime = { path = "../runtime", version = "1.4.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.4.14" }
|
||||
solana-version = { path = "../version", version = "1.4.14" }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.7.2" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.4.14" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -1,5 +1,4 @@
|
||||
#![allow(clippy::useless_attribute)]
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
|
||||
use crate::order_book::*;
|
||||
use itertools::izip;
|
||||
@@ -391,7 +390,7 @@ fn swapper<T>(
|
||||
while client
|
||||
.get_balance_with_commitment(
|
||||
&trade_infos[trade_index].trade_account,
|
||||
CommitmentConfig::processed(),
|
||||
CommitmentConfig::recent(),
|
||||
)
|
||||
.unwrap_or(0)
|
||||
== 0
|
||||
@@ -446,7 +445,7 @@ fn swapper<T>(
|
||||
account_group = (account_group + 1) % account_groups as usize;
|
||||
|
||||
let (blockhash, _fee_calculator, _last_valid_slot) = client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.expect("Failed to get blockhash");
|
||||
let to_swap_txs: Vec<_> = to_swap
|
||||
.par_iter()
|
||||
@@ -572,7 +571,7 @@ fn trader<T>(
|
||||
account_group = (account_group + 1) % account_groups as usize;
|
||||
|
||||
let (blockhash, _fee_calculator, _last_valid_slot) = client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.expect("Failed to get blockhash");
|
||||
|
||||
trades.chunks(chunk_size).for_each(|chunk| {
|
||||
@@ -659,7 +658,7 @@ where
|
||||
{
|
||||
for s in &tx.signatures {
|
||||
if let Ok(Some(r)) =
|
||||
sync_client.get_signature_status_with_commitment(s, CommitmentConfig::processed())
|
||||
sync_client.get_signature_status_with_commitment(s, CommitmentConfig::recent())
|
||||
{
|
||||
match r {
|
||||
Ok(_) => {
|
||||
@@ -682,7 +681,7 @@ fn verify_funding_transfer<T: SyncClient + ?Sized>(
|
||||
if verify_transaction(client, tx) {
|
||||
for a in &tx.message().account_keys[1..] {
|
||||
if client
|
||||
.get_balance_with_commitment(a, CommitmentConfig::processed())
|
||||
.get_balance_with_commitment(a, CommitmentConfig::recent())
|
||||
.unwrap_or(0)
|
||||
>= amount
|
||||
{
|
||||
@@ -765,7 +764,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
|
||||
);
|
||||
|
||||
let (blockhash, _fee_calculator, _last_valid_slot) = client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.expect("blockhash");
|
||||
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
|
||||
tx.sign(&[*k], blockhash);
|
||||
@@ -804,7 +803,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
|
||||
funded.append(&mut new_funded);
|
||||
funded.retain(|(k, b)| {
|
||||
client
|
||||
.get_balance_with_commitment(&k.pubkey(), CommitmentConfig::processed())
|
||||
.get_balance_with_commitment(&k.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap_or(0)
|
||||
> lamports
|
||||
&& *b > lamports
|
||||
@@ -858,7 +857,7 @@ pub fn create_token_accounts<T: Client>(
|
||||
let mut retries = 0;
|
||||
while !to_create_txs.is_empty() {
|
||||
let (blockhash, _fee_calculator, _last_valid_slot) = client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.expect("Failed to get blockhash");
|
||||
to_create_txs
|
||||
.par_iter_mut()
|
||||
@@ -904,7 +903,7 @@ pub fn create_token_accounts<T: Client>(
|
||||
let mut new_notfunded: Vec<(&Arc<Keypair>, &Keypair)> = vec![];
|
||||
for f in ¬funded {
|
||||
if client
|
||||
.get_balance_with_commitment(&f.1.pubkey(), CommitmentConfig::processed())
|
||||
.get_balance_with_commitment(&f.1.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap_or(0)
|
||||
== 0
|
||||
{
|
||||
@@ -969,7 +968,7 @@ pub fn airdrop_lamports<T: Client>(
|
||||
id: &Keypair,
|
||||
amount: u64,
|
||||
) {
|
||||
let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::processed());
|
||||
let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent());
|
||||
let balance = balance.unwrap_or(0);
|
||||
if balance >= amount {
|
||||
return;
|
||||
@@ -987,7 +986,7 @@ pub fn airdrop_lamports<T: Client>(
|
||||
let mut tries = 0;
|
||||
loop {
|
||||
let (blockhash, _fee_calculator, _last_valid_slot) = client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.expect("Failed to get blockhash");
|
||||
match request_airdrop_transaction(&faucet_addr, &id.pubkey(), amount_to_drop, blockhash) {
|
||||
Ok(transaction) => {
|
||||
@@ -996,14 +995,14 @@ pub fn airdrop_lamports<T: Client>(
|
||||
for _ in 0..30 {
|
||||
if let Ok(Some(_)) = client.get_signature_status_with_commitment(
|
||||
&signature,
|
||||
CommitmentConfig::processed(),
|
||||
CommitmentConfig::recent(),
|
||||
) {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(100));
|
||||
}
|
||||
if client
|
||||
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::processed())
|
||||
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap_or(0)
|
||||
>= amount
|
||||
{
|
||||
|
@@ -163,8 +163,7 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
pub fn extract_args(matches: &ArgMatches) -> Config {
|
||||
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
let mut args = Config::default();
|
||||
|
||||
args.entrypoint_addr = solana_net_utils::parse_host_port(
|
||||
|
@@ -1,11 +1,10 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
pub mod bench;
|
||||
mod cli;
|
||||
pub mod order_book;
|
||||
|
||||
use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_exchange, Config};
|
||||
use log::*;
|
||||
use solana_gossip::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_core::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_sdk::signature::Signer;
|
||||
|
||||
fn main() {
|
||||
|
@@ -1,21 +1,19 @@
|
||||
use log::*;
|
||||
use solana_bench_exchange::bench::{airdrop_lamports, do_bench_exchange, Config};
|
||||
use solana_core::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_core::validator::ValidatorConfig;
|
||||
use solana_exchange_program::{
|
||||
exchange_processor::process_instruction, id, solana_exchange_program,
|
||||
};
|
||||
use solana_faucet::faucet::run_local_faucet_with_port;
|
||||
use solana_gossip::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_local_cluster::{
|
||||
local_cluster::{ClusterConfig, LocalCluster},
|
||||
validator_configs::make_identical_validator_configs,
|
||||
};
|
||||
use solana_runtime::{bank::Bank, bank_client::BankClient};
|
||||
use solana_sdk::{
|
||||
genesis_config::create_genesis_config,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use std::{process::exit, sync::mpsc::channel, time::Duration};
|
||||
use solana_exchange_program::exchange_processor::process_instruction;
|
||||
use solana_exchange_program::id;
|
||||
use solana_exchange_program::solana_exchange_program;
|
||||
use solana_faucet::faucet::run_local_faucet;
|
||||
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_runtime::bank_client::BankClient;
|
||||
use solana_sdk::genesis_config::create_genesis_config;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use std::process::exit;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
@@ -24,17 +22,15 @@ fn test_exchange_local_cluster() {
|
||||
|
||||
const NUM_NODES: usize = 1;
|
||||
|
||||
let config = Config {
|
||||
identity: Keypair::new(),
|
||||
duration: Duration::from_secs(1),
|
||||
fund_amount: 100_000,
|
||||
threads: 1,
|
||||
transfer_delay: 20, // 15
|
||||
batch_size: 100, // 1000
|
||||
chunk_size: 10, // 200
|
||||
account_groups: 1, // 10
|
||||
..Config::default()
|
||||
};
|
||||
let mut config = Config::default();
|
||||
config.identity = Keypair::new();
|
||||
config.duration = Duration::from_secs(1);
|
||||
config.fund_amount = 100_000;
|
||||
config.threads = 1;
|
||||
config.transfer_delay = 20; // 15
|
||||
config.batch_size = 100; // 1000;
|
||||
config.chunk_size = 10; // 200;
|
||||
config.account_groups = 1; // 10;
|
||||
let Config {
|
||||
fund_amount,
|
||||
batch_size,
|
||||
@@ -46,7 +42,7 @@ fn test_exchange_local_cluster() {
|
||||
let cluster = LocalCluster::new(&mut ClusterConfig {
|
||||
node_stakes: vec![100_000; NUM_NODES],
|
||||
cluster_lamports: 100_000_000_000_000,
|
||||
validator_configs: make_identical_validator_configs(&ValidatorConfig::default(), NUM_NODES),
|
||||
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
|
||||
native_instruction_processors: [solana_exchange_program!()].to_vec(),
|
||||
..ClusterConfig::default()
|
||||
});
|
||||
@@ -59,11 +55,8 @@ fn test_exchange_local_cluster() {
|
||||
);
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
run_local_faucet_with_port(faucet_keypair, addr_sender, Some(1_000_000_000_000), 0);
|
||||
let faucet_addr = addr_receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("run_local_faucet")
|
||||
.expect("faucet_addr");
|
||||
run_local_faucet(faucet_keypair, addr_sender, Some(1_000_000_000_000));
|
||||
let faucet_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
|
||||
info!("Connecting to the cluster");
|
||||
let nodes =
|
||||
@@ -96,18 +89,15 @@ fn test_exchange_bank_client() {
|
||||
bank.add_builtin("exchange_program", id(), process_instruction);
|
||||
let clients = vec![BankClient::new(bank)];
|
||||
|
||||
do_bench_exchange(
|
||||
clients,
|
||||
Config {
|
||||
identity,
|
||||
duration: Duration::from_secs(1),
|
||||
fund_amount: 100_000,
|
||||
threads: 1,
|
||||
transfer_delay: 20, // 0;
|
||||
batch_size: 100, // 1500;
|
||||
chunk_size: 10, // 1500;
|
||||
account_groups: 1, // 50;
|
||||
..Config::default()
|
||||
},
|
||||
);
|
||||
let mut config = Config::default();
|
||||
config.identity = identity;
|
||||
config.duration = Duration::from_secs(1);
|
||||
config.fund_amount = 100_000;
|
||||
config.threads = 1;
|
||||
config.transfer_delay = 20; // 0;
|
||||
config.batch_size = 100; // 1500;
|
||||
config.chunk_size = 10; // 1500;
|
||||
config.account_groups = 1; // 50;
|
||||
|
||||
do_bench_exchange(clients, config);
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "1.7.2"
|
||||
version = "1.4.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -10,11 +10,11 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.2" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.7.2" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.2" }
|
||||
solana-version = { path = "../version", version = "=1.7.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.4.14" }
|
||||
solana-streamer = { path = "../streamer", version = "1.4.14" }
|
||||
solana-logger = { path = "../logger", version = "1.4.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.4.14" }
|
||||
solana-version = { path = "../version", version = "1.4.14" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -1,4 +1,3 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use clap::{crate_description, crate_name, App, Arg};
|
||||
use solana_streamer::packet::{Packet, Packets, PacketsRecycler, PACKET_DATA_SIZE};
|
||||
use solana_streamer::streamer::{receiver, PacketReceiver};
|
||||
@@ -91,8 +90,6 @@ fn main() -> Result<()> {
|
||||
s_reader,
|
||||
recycler.clone(),
|
||||
"bench-streamer-test",
|
||||
1,
|
||||
true,
|
||||
));
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "1.7.2"
|
||||
version = "1.4.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -11,27 +11,27 @@ publish = false
|
||||
[dependencies]
|
||||
bincode = "1.3.1"
|
||||
clap = "2.33.1"
|
||||
log = "0.4.11"
|
||||
rayon = "1.5.0"
|
||||
log = "0.4.8"
|
||||
rayon = "1.4.0"
|
||||
serde_json = "1.0.56"
|
||||
serde_yaml = "0.8.13"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.2" }
|
||||
solana-core = { path = "../core", version = "=1.7.2" }
|
||||
solana-genesis = { path = "../genesis", version = "=1.7.2" }
|
||||
solana-client = { path = "../client", version = "=1.7.2" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.2" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.2" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.2" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.7.2" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.2" }
|
||||
solana-version = { path = "../version", version = "=1.7.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.4.14" }
|
||||
solana-core = { path = "../core", version = "1.4.14" }
|
||||
solana-genesis = { path = "../genesis", version = "1.4.14" }
|
||||
solana-client = { path = "../client", version = "1.4.14" }
|
||||
solana-faucet = { path = "../faucet", version = "1.4.14" }
|
||||
solana-logger = { path = "../logger", version = "1.4.14" }
|
||||
solana-metrics = { path = "../metrics", version = "1.4.14" }
|
||||
solana-measure = { path = "../measure", version = "1.4.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.4.14" }
|
||||
solana-runtime = { path = "../runtime", version = "1.4.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.4.14" }
|
||||
solana-version = { path = "../version", version = "1.4.14" }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.4.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.7.2" }
|
||||
serial_test_derive = "0.4.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.4.14" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -8,7 +8,7 @@ use solana_measure::measure::Measure;
|
||||
use solana_metrics::{self, datapoint_info};
|
||||
use solana_sdk::{
|
||||
client::Client,
|
||||
clock::{DEFAULT_S_PER_SLOT, MAX_PROCESSING_AGE},
|
||||
clock::{DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE},
|
||||
commitment_config::CommitmentConfig,
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::Hash,
|
||||
@@ -32,7 +32,8 @@ use std::{
|
||||
};
|
||||
|
||||
// The point at which transactions become "too old", in seconds.
|
||||
const MAX_TX_QUEUE_AGE: u64 = (MAX_PROCESSING_AGE as f64 * DEFAULT_S_PER_SLOT) as u64;
|
||||
const MAX_TX_QUEUE_AGE: u64 =
|
||||
MAX_PROCESSING_AGE as u64 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND;
|
||||
|
||||
pub const MAX_SPENDS_PER_TX: u64 = 4;
|
||||
|
||||
@@ -47,7 +48,7 @@ pub type SharedTransactions = Arc<RwLock<VecDeque<Vec<(Transaction, u64)>>>>;
|
||||
|
||||
fn get_recent_blockhash<T: Client>(client: &T) -> (Hash, FeeCalculator) {
|
||||
loop {
|
||||
match client.get_recent_blockhash_with_commitment(CommitmentConfig::processed()) {
|
||||
match client.get_recent_blockhash_with_commitment(CommitmentConfig::recent()) {
|
||||
Ok((blockhash, fee_calculator, _last_valid_slot)) => {
|
||||
return (blockhash, fee_calculator)
|
||||
}
|
||||
@@ -496,7 +497,7 @@ fn do_tx_transfers<T: Client>(
|
||||
|
||||
fn verify_funding_transfer<T: Client>(client: &Arc<T>, tx: &Transaction, amount: u64) -> bool {
|
||||
for a in &tx.message().account_keys[1..] {
|
||||
match client.get_balance_with_commitment(a, CommitmentConfig::processed()) {
|
||||
match client.get_balance_with_commitment(a, CommitmentConfig::recent()) {
|
||||
Ok(balance) => return balance >= amount,
|
||||
Err(err) => error!("failed to get balance {:?}", err),
|
||||
}
|
||||
@@ -761,7 +762,7 @@ pub fn airdrop_lamports<T: Client>(
|
||||
};
|
||||
|
||||
let current_balance = client
|
||||
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::processed())
|
||||
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap_or_else(|e| {
|
||||
info!("airdrop error {}", e);
|
||||
starting_balance
|
||||
@@ -937,12 +938,10 @@ mod tests {
|
||||
let bank = Bank::new(&genesis_config);
|
||||
let client = Arc::new(BankClient::new(bank));
|
||||
|
||||
let config = Config {
|
||||
id,
|
||||
tx_count: 10,
|
||||
duration: Duration::from_secs(5),
|
||||
..Config::default()
|
||||
};
|
||||
let mut config = Config::default();
|
||||
config.id = id;
|
||||
config.tx_count = 10;
|
||||
config.duration = Duration::from_secs(5);
|
||||
|
||||
let keypair_count = config.tx_count * config.keypair_multiplier;
|
||||
let keypairs =
|
||||
@@ -966,7 +965,7 @@ mod tests {
|
||||
for kp in &keypairs {
|
||||
assert_eq!(
|
||||
client
|
||||
.get_balance_with_commitment(&kp.pubkey(), CommitmentConfig::processed())
|
||||
.get_balance_with_commitment(&kp.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap(),
|
||||
lamports
|
||||
);
|
||||
|
@@ -196,7 +196,7 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
|
||||
/// * `matches` - command line arguments parsed by clap
|
||||
/// # Panics
|
||||
/// Panics if there is trouble parsing any of the arguments
|
||||
pub fn extract_args(matches: &ArgMatches) -> Config {
|
||||
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
let mut args = Config::default();
|
||||
|
||||
if let Some(addr) = matches.value_of("entrypoint") {
|
||||
|
@@ -1,3 +1,2 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
pub mod bench;
|
||||
pub mod cli;
|
||||
|
@@ -1,9 +1,8 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use log::*;
|
||||
use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs, generate_keypairs};
|
||||
use solana_bench_tps::cli;
|
||||
use solana_core::gossip_service::{discover_cluster, get_client, get_multi_client};
|
||||
use solana_genesis::Base64Account;
|
||||
use solana_gossip::gossip_service::{discover_cluster, get_client, get_multi_client};
|
||||
use solana_sdk::fee_calculator::FeeRateGovernor;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::system_program;
|
||||
|
@@ -1,22 +1,14 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use serial_test::serial;
|
||||
use solana_bench_tps::{
|
||||
bench::{do_bench_tps, generate_and_fund_keypairs},
|
||||
cli::Config,
|
||||
};
|
||||
use serial_test_derive::serial;
|
||||
use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs};
|
||||
use solana_bench_tps::cli::Config;
|
||||
use solana_client::thin_client::create_client;
|
||||
use solana_core::cluster_info::VALIDATOR_PORT_RANGE;
|
||||
use solana_core::validator::ValidatorConfig;
|
||||
use solana_faucet::faucet::run_local_faucet_with_port;
|
||||
use solana_gossip::cluster_info::VALIDATOR_PORT_RANGE;
|
||||
use solana_local_cluster::{
|
||||
local_cluster::{ClusterConfig, LocalCluster},
|
||||
validator_configs::make_identical_validator_configs,
|
||||
};
|
||||
use solana_faucet::faucet::run_local_faucet;
|
||||
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use std::{
|
||||
sync::{mpsc::channel, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
use std::sync::{mpsc::channel, Arc};
|
||||
use std::time::Duration;
|
||||
|
||||
fn test_bench_tps_local_cluster(config: Config) {
|
||||
let native_instruction_processors = vec![];
|
||||
@@ -26,7 +18,7 @@ fn test_bench_tps_local_cluster(config: Config) {
|
||||
let cluster = LocalCluster::new(&mut ClusterConfig {
|
||||
node_stakes: vec![999_990; NUM_NODES],
|
||||
cluster_lamports: 200_000_000,
|
||||
validator_configs: make_identical_validator_configs(&ValidatorConfig::default(), NUM_NODES),
|
||||
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
|
||||
native_instruction_processors,
|
||||
..ClusterConfig::default()
|
||||
});
|
||||
@@ -44,11 +36,8 @@ fn test_bench_tps_local_cluster(config: Config) {
|
||||
));
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
run_local_faucet_with_port(faucet_keypair, addr_sender, None, 0);
|
||||
let faucet_addr = addr_receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("run_local_faucet")
|
||||
.expect("faucet_addr");
|
||||
run_local_faucet(faucet_keypair, addr_sender, None);
|
||||
let faucet_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
|
||||
let lamports_per_account = 100;
|
||||
|
||||
@@ -71,9 +60,9 @@ fn test_bench_tps_local_cluster(config: Config) {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_bench_tps_local_cluster_solana() {
|
||||
test_bench_tps_local_cluster(Config {
|
||||
tx_count: 100,
|
||||
duration: Duration::from_secs(10),
|
||||
..Config::default()
|
||||
});
|
||||
let mut config = Config::default();
|
||||
config.tx_count = 100;
|
||||
config.duration = Duration::from_secs(10);
|
||||
|
||||
test_bench_tps_local_cluster(config);
|
||||
}
|
||||
|
9
cargo
9
cargo
@@ -3,22 +3,25 @@
|
||||
# shellcheck source=ci/rust-version.sh
|
||||
here=$(dirname "$0")
|
||||
|
||||
source "${here}"/ci/rust-version.sh all
|
||||
|
||||
toolchain=
|
||||
case "$1" in
|
||||
stable)
|
||||
source "${here}"/ci/rust-version.sh stable
|
||||
# shellcheck disable=SC2054 # rust_stable is sourced from rust-version.sh
|
||||
toolchain="$rust_stable"
|
||||
shift
|
||||
;;
|
||||
nightly)
|
||||
source "${here}"/ci/rust-version.sh nightly
|
||||
# shellcheck disable=SC2054 # rust_nightly is sourced from rust-version.sh
|
||||
toolchain="$rust_nightly"
|
||||
shift
|
||||
;;
|
||||
+*)
|
||||
toolchain="${1#+}"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
source "${here}"/ci/rust-version.sh stable
|
||||
# shellcheck disable=SC2054 # rust_stable is sourced from rust-version.sh
|
||||
toolchain="$rust_stable"
|
||||
;;
|
||||
|
@@ -47,8 +47,6 @@ sudo ./setup-new-buildkite-agent/setup-buildkite.sh
|
||||
```
|
||||
- Copy the pubkey contents from `~buildkite-agent/.ssh/id_ecdsa.pub` and
|
||||
add the pubkey as an authorized SSH key on github.
|
||||
- In net/scripts/solana-user-authorized_keys.sh
|
||||
- Bug mvines to add it to the "solana-grimes" github user
|
||||
- Edit `/etc/buildkite-agent/buildkite-agent.cfg` and/or `/etc/systemd/system/buildkite-agent@*` to the desired configuration of the agent(s)
|
||||
- Copy `ejson` keys from another CI node at `/opt/ejson/keys/`
|
||||
to the same location on the new node.
|
||||
|
@@ -263,7 +263,7 @@ if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
|
||||
annotate --style info --context pr-backlink \
|
||||
"Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH"
|
||||
|
||||
if [[ $GITHUB_USER = "dependabot[bot]" ]]; then
|
||||
if [[ $GITHUB_USER = "dependabot-preview[bot]" ]]; then
|
||||
command_step dependabot "ci/dependabot-pr.sh" 5
|
||||
wait_step
|
||||
fi
|
||||
|
@@ -5,6 +5,9 @@ steps:
|
||||
- command: "ci/publish-tarball.sh"
|
||||
timeout_in_minutes: 60
|
||||
name: "publish tarball"
|
||||
- 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
|
||||
|
@@ -105,18 +105,11 @@ if [[ -z "$CHANNEL" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $CHANNEL = beta ]]; then
|
||||
CHANNEL_LATEST_TAG="$BETA_CHANNEL_LATEST_TAG"
|
||||
elif [[ $CHANNEL = stable ]]; then
|
||||
CHANNEL_LATEST_TAG="$STABLE_CHANNEL_LATEST_TAG"
|
||||
fi
|
||||
|
||||
echo EDGE_CHANNEL="$EDGE_CHANNEL"
|
||||
echo BETA_CHANNEL="$BETA_CHANNEL"
|
||||
echo BETA_CHANNEL_LATEST_TAG="$BETA_CHANNEL_LATEST_TAG"
|
||||
echo STABLE_CHANNEL="$STABLE_CHANNEL"
|
||||
echo STABLE_CHANNEL_LATEST_TAG="$STABLE_CHANNEL_LATEST_TAG"
|
||||
echo CHANNEL="$CHANNEL"
|
||||
echo CHANNEL_LATEST_TAG="$CHANNEL_LATEST_TAG"
|
||||
|
||||
exit 0
|
||||
|
@@ -6,13 +6,13 @@ source ci/_
|
||||
|
||||
commit_range="$(git merge-base HEAD origin/master)..HEAD"
|
||||
parsed_update_args="$(
|
||||
git log "$commit_range" --author "dependabot\[bot\]" --oneline -n1 |
|
||||
git log "$commit_range" --author "dependabot-preview" --oneline -n1 |
|
||||
grep -o '[Bb]ump.*$' |
|
||||
sed -r 's/[Bb]ump ([^ ]+) from ([^ ]+) to ([^ ]+)/-p \1:\2 --precise \3/'
|
||||
)"
|
||||
# relaxed_parsed_update_args is temporal measure...
|
||||
relaxed_parsed_update_args="$(
|
||||
git log "$commit_range" --author "dependabot\[bot\]" --oneline -n1 |
|
||||
git log "$commit_range" --author "dependabot-preview" --oneline -n1 |
|
||||
grep -o '[Bb]ump.*$' |
|
||||
sed -r 's/[Bb]ump ([^ ]+) from [^ ]+ to ([^ ]+)/-p \1 --precise \2/'
|
||||
)"
|
||||
|
@@ -1,43 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
here="$(dirname "$0")"
|
||||
src_root="$(readlink -f "${here}/..")"
|
||||
|
||||
cd "${src_root}"
|
||||
|
||||
cargo_audit_ignores=(
|
||||
# failure is officially deprecated/unmaintained
|
||||
#
|
||||
# Blocked on multiple upstream crates removing their `failure` dependency.
|
||||
--ignore RUSTSEC-2020-0036
|
||||
|
||||
# `net2` crate has been deprecated; use `socket2` instead
|
||||
#
|
||||
# Blocked on https://github.com/paritytech/jsonrpc/issues/575
|
||||
--ignore RUSTSEC-2020-0016
|
||||
|
||||
# stdweb is unmaintained
|
||||
#
|
||||
# Blocked on multiple upstream crates removing their `stdweb` dependency.
|
||||
--ignore RUSTSEC-2020-0056
|
||||
|
||||
# Potential segfault in the time crate
|
||||
#
|
||||
# Blocked on multiple crates updating `time` to >= 0.2.23
|
||||
--ignore RUSTSEC-2020-0071
|
||||
|
||||
# difference is unmaintained
|
||||
#
|
||||
# Blocked on predicates v1.0.6 removing its dependency on `difference`
|
||||
--ignore RUSTSEC-2020-0095
|
||||
|
||||
# generic-array: arr! macro erases lifetimes
|
||||
#
|
||||
# Blocked on libsecp256k1 releasing with upgraded dependencies
|
||||
# https://github.com/paritytech/libsecp256k1/issues/66
|
||||
--ignore RUSTSEC-2020-0146
|
||||
|
||||
)
|
||||
scripts/cargo-for-all-lock-files.sh stable audit "${cargo_audit_ignores[@]}"
|
@@ -1,4 +1,4 @@
|
||||
FROM solanalabs/rust:1.52.1
|
||||
FROM solanalabs/rust:1.46.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.52.1
|
||||
FROM rust:1.46.0
|
||||
|
||||
# Add Google Protocol Buffers for Libra's metrics library.
|
||||
ENV PROTOC_VERSION 3.8.0
|
||||
|
@@ -70,7 +70,7 @@ done
|
||||
|
||||
source ci/upload-ci-artifact.sh
|
||||
source scripts/configure-metrics.sh
|
||||
source multinode-demo/common.sh --prebuild
|
||||
source multinode-demo/common.sh
|
||||
|
||||
nodes=(
|
||||
"multinode-demo/bootstrap-validator.sh \
|
||||
@@ -78,6 +78,7 @@ nodes=(
|
||||
--init-complete-file init-complete-node0.log \
|
||||
--dynamic-port-range 8000-8050"
|
||||
"multinode-demo/validator.sh \
|
||||
--enable-rpc-exit \
|
||||
--no-restart \
|
||||
--dynamic-port-range 8050-8100
|
||||
--init-complete-file init-complete-node1.log \
|
||||
@@ -200,10 +201,17 @@ killNodes() {
|
||||
[[ ${#pids[@]} -gt 0 ]] || return
|
||||
|
||||
# Try to use the RPC exit API to cleanly exit the first two nodes
|
||||
# (dynamic nodes, -x, are just killed)
|
||||
# (dynamic nodes, -x, are just killed since their RPC port is not known)
|
||||
echo "--- RPC exit"
|
||||
$solana_validator --ledger "$SOLANA_CONFIG_DIR"/bootstrap-validator exit --force || true
|
||||
$solana_validator --ledger "$SOLANA_CONFIG_DIR"/validator exit --force || true
|
||||
for port in 8899 18899; do
|
||||
(
|
||||
set -x
|
||||
curl --retry 5 --retry-delay 2 --retry-connrefused \
|
||||
-X POST -H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":1, "method":"validatorExit"}' \
|
||||
http://localhost:$port
|
||||
)
|
||||
done
|
||||
|
||||
# Give the nodes a splash of time to cleanly exit before killing them
|
||||
sleep 2
|
||||
|
@@ -19,7 +19,6 @@ declare prints=(
|
||||
# Parts of the tree that are expected to be print free
|
||||
declare print_free_tree=(
|
||||
':core/src/**.rs'
|
||||
':^core/src/validator.rs'
|
||||
':faucet/src/**.rs'
|
||||
':ledger/src/**.rs'
|
||||
':metrics/src/**.rs'
|
||||
|
36
ci/publish-bpf-sdk.sh
Executable file
36
ci/publish-bpf-sdk.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
eval "$(ci/channel-info.sh)"
|
||||
|
||||
if [[ -n "$CI_TAG" ]]; then
|
||||
CHANNEL_OR_TAG=$CI_TAG
|
||||
else
|
||||
CHANNEL_OR_TAG=$CHANNEL
|
||||
fi
|
||||
|
||||
(
|
||||
set -x
|
||||
sdk/bpf/scripts/package.sh
|
||||
[[ -f bpf-sdk.tar.bz2 ]]
|
||||
)
|
||||
|
||||
echo --- AWS S3 Store
|
||||
if [[ -z $CHANNEL_OR_TAG ]]; then
|
||||
echo Skipped
|
||||
else
|
||||
(
|
||||
set -x
|
||||
docker run \
|
||||
--rm \
|
||||
--env AWS_ACCESS_KEY_ID \
|
||||
--env AWS_SECRET_ACCESS_KEY \
|
||||
--volume "$PWD:/solana" \
|
||||
eremite/aws-cli:2018.12.18 \
|
||||
/usr/bin/s3cmd --acl-public put /solana/bpf-sdk.tar.bz2 \
|
||||
s3://solana-sdk/"$CHANNEL_OR_TAG"/bpf-sdk.tar.bz2
|
||||
)
|
||||
fi
|
||||
|
||||
exit 0
|
@@ -83,7 +83,7 @@ echo --- Creating release tarball
|
||||
export CHANNEL
|
||||
|
||||
source ci/rust-version.sh stable
|
||||
scripts/cargo-install-all.sh stable "${RELEASE_BASENAME}"
|
||||
scripts/cargo-install-all.sh +"$rust_stable" "${RELEASE_BASENAME}"
|
||||
|
||||
tar cvf "${TARBALL_BASENAME}"-$TARGET.tar "${RELEASE_BASENAME}"
|
||||
bzip2 "${TARBALL_BASENAME}"-$TARGET.tar
|
||||
@@ -113,10 +113,19 @@ for file in "${TARBALL_BASENAME}"-$TARGET.tar.bz2 "${TARBALL_BASENAME}"-$TARGET.
|
||||
|
||||
if [[ -n $BUILDKITE ]]; then
|
||||
echo --- AWS S3 Store: "$file"
|
||||
upload-s3-artifact "/solana/$file" s3://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
|
||||
(
|
||||
set -x
|
||||
$DRYRUN docker run \
|
||||
--rm \
|
||||
--env AWS_ACCESS_KEY_ID \
|
||||
--env AWS_SECRET_ACCESS_KEY \
|
||||
--volume "$PWD:/solana" \
|
||||
eremite/aws-cli:2018.12.18 \
|
||||
/usr/bin/s3cmd --acl-public put /solana/"$file" s3://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
|
||||
|
||||
echo Published to:
|
||||
$DRYRUN ci/format-url.sh https://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
|
||||
echo Published to:
|
||||
$DRYRUN ci/format-url.sh https://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
|
||||
)
|
||||
|
||||
if [[ -n $TAG ]]; then
|
||||
ci/upload-github-release-asset.sh "$file"
|
||||
@@ -140,9 +149,7 @@ done
|
||||
|
||||
|
||||
# Create install wrapper for release.solana.com
|
||||
if [[ -n $DO_NOT_PUBLISH_TAR ]]; then
|
||||
echo "Skipping publishing install wrapper"
|
||||
elif [[ -n $BUILDKITE ]]; then
|
||||
if [[ -n $BUILDKITE ]]; then
|
||||
cat > release.solana.com-install <<EOF
|
||||
SOLANA_RELEASE=$CHANNEL_OR_TAG
|
||||
SOLANA_INSTALL_INIT_ARGS=$CHANNEL_OR_TAG
|
||||
@@ -151,9 +158,19 @@ EOF
|
||||
cat install/solana-install-init.sh >> release.solana.com-install
|
||||
|
||||
echo --- AWS S3 Store: "install"
|
||||
$DRYRUN upload-s3-artifact "/solana/release.solana.com-install" "s3://release.solana.com/$CHANNEL_OR_TAG/install"
|
||||
echo Published to:
|
||||
$DRYRUN ci/format-url.sh https://release.solana.com/"$CHANNEL_OR_TAG"/install
|
||||
(
|
||||
set -x
|
||||
$DRYRUN docker run \
|
||||
--rm \
|
||||
--env AWS_ACCESS_KEY_ID \
|
||||
--env AWS_SECRET_ACCESS_KEY \
|
||||
--volume "$PWD:/solana" \
|
||||
eremite/aws-cli:2018.12.18 \
|
||||
/usr/bin/s3cmd --acl-public put /solana/release.solana.com-install s3://release.solana.com/"$CHANNEL_OR_TAG"/install
|
||||
|
||||
echo Published to:
|
||||
$DRYRUN ci/format-url.sh https://release.solana.com/"$CHANNEL_OR_TAG"/install
|
||||
)
|
||||
fi
|
||||
|
||||
echo --- ok
|
||||
|
@@ -7,7 +7,7 @@ source multinode-demo/common.sh
|
||||
|
||||
rm -rf config/run/init-completed config/ledger config/snapshot-ledger
|
||||
|
||||
SOLANA_RUN_SH_VALIDATOR_ARGS="--snapshot-interval-slots 200" timeout 120 ./run.sh &
|
||||
timeout 120 ./run.sh &
|
||||
pid=$!
|
||||
|
||||
attempts=20
|
||||
@@ -16,20 +16,16 @@ while [[ ! -f config/run/init-completed ]]; do
|
||||
if ((--attempts == 0)); then
|
||||
echo "Error: validator failed to boot"
|
||||
exit 1
|
||||
else
|
||||
echo "Checking init"
|
||||
fi
|
||||
done
|
||||
|
||||
snapshot_slot=1
|
||||
|
||||
# wait a bit longer than snapshot_slot
|
||||
while [[ $($solana_cli --url http://localhost:8899 slot --commitment processed) -le $((snapshot_slot + 1)) ]]; do
|
||||
while [[ $($solana_cli --url http://localhost:8899 slot --commitment recent) -le $((snapshot_slot + 1)) ]]; do
|
||||
sleep 1
|
||||
echo "Checking slot"
|
||||
done
|
||||
|
||||
$solana_validator --ledger config/ledger exit --force || true
|
||||
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"validatorExit"}' http://localhost:8899
|
||||
|
||||
wait $pid
|
||||
|
||||
|
@@ -18,13 +18,13 @@
|
||||
if [[ -n $RUST_STABLE_VERSION ]]; then
|
||||
stable_version="$RUST_STABLE_VERSION"
|
||||
else
|
||||
stable_version=1.52.1
|
||||
stable_version=1.46.0
|
||||
fi
|
||||
|
||||
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
||||
nightly_version="$RUST_NIGHTLY_VERSION"
|
||||
else
|
||||
nightly_version=2021-05-18
|
||||
nightly_version=2020-08-17
|
||||
fi
|
||||
|
||||
|
||||
|
@@ -76,7 +76,7 @@ RestartForceExitStatus=SIGPIPE
|
||||
TimeoutStartSec=10
|
||||
TimeoutStopSec=0
|
||||
KillMode=process
|
||||
LimitNOFILE=700000
|
||||
LimitNOFILE=500000
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
@@ -8,5 +8,5 @@ source "$HERE"/utils.sh
|
||||
ensure_env || exit 1
|
||||
|
||||
# Allow more files to be opened by a user
|
||||
echo "* - nofile 700000" > /etc/security/limits.d/90-solana-nofiles.conf
|
||||
echo "* - nofile 500000" > /etc/security/limits.d/90-solana-nofiles.conf
|
||||
|
||||
|
@@ -7,7 +7,6 @@ SOLANA_ROOT="$HERE"/../..
|
||||
source "$HERE"/utils.sh
|
||||
|
||||
ensure_env || exit 1
|
||||
check_ssh_authorized_keys || exit 1
|
||||
|
||||
set -ex
|
||||
|
||||
|
@@ -6,11 +6,6 @@ HERE="$(dirname "$0")"
|
||||
source "$HERE"/utils.sh
|
||||
|
||||
ensure_env || exit 1
|
||||
# This is a last ditch effort to prevent the caller from locking themselves
|
||||
# out of the machine. Exiting here will likely leave the system in some
|
||||
# half-configured state. To prevent this, duplicate the next line at the top
|
||||
# of the entrypoint script.
|
||||
check_ssh_authorized_keys || exit 1
|
||||
|
||||
set -xe
|
||||
# Setup sshd
|
||||
|
@@ -14,33 +14,3 @@ ensure_env() {
|
||||
$RC
|
||||
}
|
||||
|
||||
# Some scripts disable SSH password logins. If no one hash setup authorized_keys
|
||||
# this will result in the machine being remotely inaccessible. Check that the
|
||||
# user running this script has setup their keys
|
||||
check_ssh_authorized_keys() {
|
||||
declare rc=false
|
||||
declare user_home=
|
||||
if [[ -n "$SUDO_USER" ]]; then
|
||||
declare user uid gid home
|
||||
declare passwd_entry
|
||||
passwd_entry="$(grep "$SUDO_USER:[^:]*:$SUDO_UID:$SUDO_GID" /etc/passwd)"
|
||||
IFS=: read -r user _ uid gid _ home _ <<<"$passwd_entry"
|
||||
if [[ "$user" == "$SUDO_USER" && "$uid" == "$SUDO_UID" && "$gid" == "$SUDO_GID" ]]; then
|
||||
user_home="$home"
|
||||
fi
|
||||
else
|
||||
user_home="$HOME"
|
||||
fi
|
||||
declare authorized_keys="${user_home}/.ssh/authorized_keys"
|
||||
if [[ -n "$user_home" ]]; then
|
||||
[[ -s "$authorized_keys" ]] && rc=true
|
||||
fi
|
||||
if ! $rc; then
|
||||
echo "ERROR! This script will disable SSH password logins and you don't"
|
||||
echo "appear to have set up any authorized keys. Please add you SSH"
|
||||
echo "public key to ${authorized_keys} before continuing!"
|
||||
fi
|
||||
$rc
|
||||
}
|
||||
|
||||
check_ssh_authorized_keys
|
||||
|
@@ -23,11 +23,7 @@ fi
|
||||
BENCH_FILE=bench_output.log
|
||||
BENCH_ARTIFACT=current_bench_results.log
|
||||
|
||||
# solana-keygen required when building C programs
|
||||
_ "$cargo" build --manifest-path=keygen/Cargo.toml
|
||||
export PATH="$PWD/target/debug":$PATH
|
||||
|
||||
# Clear the C dependency files, if dependency moves these files are not regenerated
|
||||
# Clear the C dependency files, if dependeny moves these files are not regenerated
|
||||
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
|
||||
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
|
||||
|
||||
@@ -45,14 +41,6 @@ _ "$cargo" nightly bench --manifest-path sdk/Cargo.toml ${V:+--verbose} \
|
||||
_ "$cargo" nightly bench --manifest-path runtime/Cargo.toml ${V:+--verbose} \
|
||||
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
|
||||
|
||||
# Run gossip benches
|
||||
_ "$cargo" nightly bench --manifest-path gossip/Cargo.toml ${V:+--verbose} \
|
||||
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
|
||||
|
||||
# Run poh benches
|
||||
_ "$cargo" nightly bench --manifest-path poh/Cargo.toml ${V:+--verbose} \
|
||||
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
|
||||
|
||||
# Run core benches
|
||||
_ "$cargo" nightly bench --manifest-path core/Cargo.toml ${V:+--verbose} \
|
||||
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
|
||||
|
@@ -12,16 +12,6 @@ cargo="$(readlink -f "./cargo")"
|
||||
|
||||
scripts/increment-cargo-version.sh check
|
||||
|
||||
# Disallow uncommitted Cargo.lock changes
|
||||
(
|
||||
_ scripts/cargo-for-all-lock-files.sh tree
|
||||
set +e
|
||||
if ! _ git diff --exit-code; then
|
||||
echo -e "\nError: Uncommitted Cargo.lock changes" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
|
||||
echo --- build environment
|
||||
(
|
||||
set -x
|
||||
@@ -45,7 +35,7 @@ export RUSTFLAGS="-D warnings -A incomplete_features"
|
||||
# Only force up-to-date lock files on edge
|
||||
if [[ $CI_BASE_BRANCH = "$EDGE_CHANNEL" ]]; then
|
||||
# Exclude --benches as it's not available in rust stable yet
|
||||
if _ scripts/cargo-for-all-lock-files.sh stable check --locked --tests --bins --examples; then
|
||||
if _ scripts/cargo-for-all-lock-files.sh +"$rust_stable" check --locked --tests --bins --examples; then
|
||||
true
|
||||
else
|
||||
check_status=$?
|
||||
@@ -56,29 +46,55 @@ if [[ $CI_BASE_BRANCH = "$EDGE_CHANNEL" ]]; then
|
||||
fi
|
||||
|
||||
# Ensure nightly and --benches
|
||||
_ scripts/cargo-for-all-lock-files.sh nightly check --locked --all-targets
|
||||
_ scripts/cargo-for-all-lock-files.sh +"$rust_nightly" check --locked --all-targets
|
||||
else
|
||||
echo "Note: cargo-for-all-lock-files.sh skipped because $CI_BASE_BRANCH != $EDGE_CHANNEL"
|
||||
fi
|
||||
|
||||
_ ci/order-crates-for-publishing.py
|
||||
_ "$cargo" stable fmt --all -- --check
|
||||
|
||||
# -Z... is needed because of clippy bug: https://github.com/rust-lang/rust-clippy/issues/4612
|
||||
# run nightly clippy for `sdk/` as there's a moderate amount of nightly-only code there
|
||||
_ "$cargo" nightly clippy -Zunstable-options --workspace --all-targets -- --deny=warnings --deny=clippy::integer_arithmetic
|
||||
_ "$cargo" nightly clippy \
|
||||
-Zunstable-options --workspace --all-targets \
|
||||
-- --deny=warnings --allow=clippy::stable_sort_primitive
|
||||
|
||||
_ "$cargo" stable fmt --all -- --check
|
||||
cargo_audit_ignores=(
|
||||
# failure is officially deprecated/unmaintained
|
||||
#
|
||||
# Blocked on multiple upstream crates removing their `failure` dependency.
|
||||
--ignore RUSTSEC-2020-0036
|
||||
|
||||
_ ci/do-audit.sh
|
||||
# `net2` crate has been deprecated; use `socket2` instead
|
||||
#
|
||||
# Blocked on https://github.com/paritytech/jsonrpc/issues/575
|
||||
--ignore RUSTSEC-2020-0016
|
||||
|
||||
# stdweb is unmaintained
|
||||
#
|
||||
# Blocked on multiple upstream crates removing their `stdweb` dependency.
|
||||
--ignore RUSTSEC-2020-0056
|
||||
|
||||
# Potential segfault in the time crate
|
||||
#
|
||||
# Blocked on multiple crates updating `time` to >= 0.2.23
|
||||
--ignore RUSTSEC-2020-0071
|
||||
)
|
||||
_ scripts/cargo-for-all-lock-files.sh +"$rust_stable" audit "${cargo_audit_ignores[@]}"
|
||||
|
||||
{
|
||||
cd programs/bpf
|
||||
_ "$cargo" stable audit
|
||||
for project in rust/*/ ; do
|
||||
echo "+++ do_bpf_checks $project"
|
||||
(
|
||||
cd "$project"
|
||||
_ "$cargo" nightly clippy -- --deny=warnings --allow=clippy::missing_safety_doc
|
||||
_ "$cargo" stable fmt -- --check
|
||||
_ "$cargo" nightly test
|
||||
_ "$cargo" nightly clippy -- --deny=warnings \
|
||||
--allow=clippy::missing_safety_doc \
|
||||
--allow=clippy::stable_sort_primitive
|
||||
)
|
||||
done
|
||||
}
|
||||
|
@@ -8,16 +8,10 @@ source ci/_
|
||||
(
|
||||
echo --- git diff --check
|
||||
set -x
|
||||
|
||||
if [[ -n $CI_BASE_BRANCH ]]
|
||||
then branch="$CI_BASE_BRANCH"
|
||||
else branch="master"
|
||||
fi
|
||||
|
||||
# Look for failed mergify.io backports by searching leftover conflict markers
|
||||
# Also check for any trailing whitespaces!
|
||||
git fetch origin "$branch"
|
||||
git diff "$(git merge-base HEAD "origin/$branch")" --check --oneline
|
||||
git fetch origin "$CI_BASE_BRANCH"
|
||||
git diff "$(git merge-base HEAD "origin/$CI_BASE_BRANCH")..HEAD" --check --oneline
|
||||
)
|
||||
|
||||
echo
|
||||
@@ -25,29 +19,4 @@ echo
|
||||
_ ci/nits.sh
|
||||
_ ci/check-ssh-keys.sh
|
||||
|
||||
|
||||
# Ensure the current channel version is not equal ("greater") than
|
||||
# the version of the latest tag
|
||||
if [[ -z $CI_TAG ]]; then
|
||||
echo "--- channel version check"
|
||||
(
|
||||
eval "$(ci/channel-info.sh)"
|
||||
|
||||
if [[ -n $CHANNEL_LATEST_TAG ]]; then
|
||||
source scripts/read-cargo-variable.sh
|
||||
|
||||
version=$(readCargoVariable version "version/Cargo.toml")
|
||||
echo "version: v$version"
|
||||
echo "latest channel tag: $CHANNEL_LATEST_TAG"
|
||||
|
||||
if [[ $CHANNEL_LATEST_TAG = v$version ]]; then
|
||||
echo "Error: please run ./scripts/increment-cargo-version.sh"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Skipped. CHANNEL_LATEST_TAG (CHANNEL=$CHANNEL) unset"
|
||||
fi
|
||||
)
|
||||
fi
|
||||
|
||||
echo --- ok
|
||||
|
@@ -21,10 +21,16 @@ export RUST_BACKTRACE=1
|
||||
export RUSTFLAGS="-D warnings"
|
||||
source scripts/ulimit-n.sh
|
||||
|
||||
# Clear cached json keypair files
|
||||
rm -rf "$HOME/.config/solana"
|
||||
|
||||
# Clear the C dependency files, if dependency moves these files are not regenerated
|
||||
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
|
||||
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
|
||||
|
||||
# Clear the BPF sysroot files, they are not automatically rebuilt
|
||||
rm -rf target/xargo # Issue #3105
|
||||
|
||||
# Limit compiler jobs to reduce memory usage
|
||||
# on machines with 2gb/thread of memory
|
||||
NPROC=$(nproc)
|
||||
@@ -36,18 +42,10 @@ test-stable)
|
||||
_ "$cargo" stable test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
;;
|
||||
test-stable-perf)
|
||||
# solana-keygen required when building C programs
|
||||
_ "$cargo" build --manifest-path=keygen/Cargo.toml
|
||||
export PATH="$PWD/target/debug":$PATH
|
||||
|
||||
# BPF solana-sdk legacy compile test
|
||||
./cargo-build-bpf --manifest-path sdk/Cargo.toml
|
||||
|
||||
# BPF Program unit tests
|
||||
"$cargo" test --manifest-path programs/bpf/Cargo.toml
|
||||
cargo-build-bpf --manifest-path programs/bpf/Cargo.toml --bpf-sdk sdk/bpf
|
||||
|
||||
# BPF program system tests
|
||||
# BPF program tests
|
||||
_ make -C programs/bpf/c tests
|
||||
_ "$cargo" stable test \
|
||||
--manifest-path programs/bpf/Cargo.toml \
|
||||
|
@@ -16,16 +16,3 @@ upload-ci-artifact() {
|
||||
fi
|
||||
}
|
||||
|
||||
upload-s3-artifact() {
|
||||
echo "--- artifact: $1 to $2"
|
||||
(
|
||||
set -x
|
||||
docker run \
|
||||
--rm \
|
||||
--env AWS_ACCESS_KEY_ID \
|
||||
--env AWS_SECRET_ACCESS_KEY \
|
||||
--volume "$PWD:/solana" \
|
||||
eremite/aws-cli:2018.12.18 \
|
||||
/usr/bin/s3cmd --acl-public put "$1" "$2"
|
||||
)
|
||||
}
|
||||
|
@@ -1,28 +1,23 @@
|
||||
[package]
|
||||
name = "solana-clap-utils"
|
||||
version = "1.7.2"
|
||||
version = "1.4.14"
|
||||
description = "Solana utilities for the clap"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-clap-utils"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
rpassword = "4.0"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "=1.7.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.2" }
|
||||
thiserror = "1.0.21"
|
||||
tiny-bip39 = "0.8.0"
|
||||
uriparse = "0.6.3"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.4.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.4.14" }
|
||||
thiserror = "1.0.20"
|
||||
tiny-bip39 = "0.7.0"
|
||||
url = "2.1.0"
|
||||
chrono = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[lib]
|
||||
name = "solana_clap_utils"
|
||||
|
||||
|
22
clap-utils/src/commitment.rs
Normal file
22
clap-utils/src/commitment.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
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", "single", "singleGossip", "root", "max"])
|
||||
.default_value(default_value)
|
||||
.value_name("COMMITMENT_LEVEL")
|
||||
.help(COMMITMENT_ARG.help)
|
||||
}
|
@@ -1,7 +1,5 @@
|
||||
use {
|
||||
crate::{input_validators, ArgConstant},
|
||||
clap::Arg,
|
||||
};
|
||||
use crate::{input_validators, ArgConstant};
|
||||
use clap::Arg;
|
||||
|
||||
pub const FEE_PAYER_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "fee_payer",
|
||||
|
@@ -1,24 +1,19 @@
|
||||
use {
|
||||
crate::keypair::{
|
||||
keypair_from_seed_phrase, pubkey_from_path, resolve_signer_from_path, signer_from_path,
|
||||
ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG,
|
||||
},
|
||||
chrono::DateTime,
|
||||
clap::ArgMatches,
|
||||
solana_remote_wallet::remote_wallet::RemoteWalletManager,
|
||||
solana_sdk::{
|
||||
clock::UnixTimestamp,
|
||||
commitment_config::CommitmentConfig,
|
||||
genesis_config::ClusterType,
|
||||
native_token::sol_to_lamports,
|
||||
pubkey::Pubkey,
|
||||
signature::{read_keypair_file, Keypair, Signature, Signer},
|
||||
},
|
||||
std::{str::FromStr, sync::Arc},
|
||||
use crate::keypair::{
|
||||
keypair_from_seed_phrase, pubkey_from_path, resolve_signer_from_path, signer_from_path,
|
||||
ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG,
|
||||
};
|
||||
|
||||
// Sentinel value used to indicate to write to screen instead of file
|
||||
pub const STDOUT_OUTFILE_TOKEN: &str = "-";
|
||||
use chrono::DateTime;
|
||||
use clap::ArgMatches;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
clock::UnixTimestamp,
|
||||
commitment_config::CommitmentConfig,
|
||||
genesis_config::ClusterType,
|
||||
native_token::sol_to_lamports,
|
||||
pubkey::Pubkey,
|
||||
signature::{read_keypair_file, Keypair, Signature, Signer},
|
||||
};
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
|
||||
// Return parsed values from matches at `name`
|
||||
pub fn values_of<T>(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<T>>
|
||||
@@ -60,7 +55,7 @@ pub fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option<Keypair> {
|
||||
if let Some(value) = matches.value_of(name) {
|
||||
if value == ASK_KEYWORD {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
keypair_from_seed_phrase(name, skip_validation, true, None, true).ok()
|
||||
keypair_from_seed_phrase(name, skip_validation, true).ok()
|
||||
} else {
|
||||
read_keypair_file(value).ok()
|
||||
}
|
||||
@@ -75,7 +70,7 @@ pub fn keypairs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Keypair>>
|
||||
.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, None, true).ok()
|
||||
keypair_from_seed_phrase(name, skip_validation, true).ok()
|
||||
} else {
|
||||
read_keypair_file(value).ok()
|
||||
}
|
||||
@@ -172,12 +167,12 @@ pub fn resolve_signer(
|
||||
name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
resolve_signer_from_path(
|
||||
Ok(resolve_signer_from_path(
|
||||
matches,
|
||||
matches.value_of(name).unwrap(),
|
||||
name,
|
||||
wallet_manager,
|
||||
)
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> {
|
||||
@@ -189,9 +184,14 @@ pub fn cluster_type_of(matches: &ArgMatches<'_>, name: &str) -> Option<ClusterTy
|
||||
}
|
||||
|
||||
pub fn commitment_of(matches: &ArgMatches<'_>, name: &str) -> Option<CommitmentConfig> {
|
||||
matches
|
||||
.value_of(name)
|
||||
.map(|value| CommitmentConfig::from_str(value).unwrap_or_default())
|
||||
matches.value_of(name).map(|value| match value {
|
||||
"max" => CommitmentConfig::max(),
|
||||
"recent" => CommitmentConfig::recent(),
|
||||
"root" => CommitmentConfig::root(),
|
||||
"single" => CommitmentConfig::single(),
|
||||
"singleGossip" => CommitmentConfig::single_gossip(),
|
||||
_ => CommitmentConfig::default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@@ -1,15 +1,13 @@
|
||||
use {
|
||||
crate::keypair::{parse_signer_source, SignerSourceKind, ASK_KEYWORD},
|
||||
chrono::DateTime,
|
||||
solana_sdk::{
|
||||
clock::{Epoch, Slot},
|
||||
hash::Hash,
|
||||
pubkey::{Pubkey, MAX_SEED_LEN},
|
||||
signature::{read_keypair_file, Signature},
|
||||
},
|
||||
std::fmt::Display,
|
||||
std::str::FromStr,
|
||||
use crate::keypair::{parse_keypair_path, KeypairUrl, ASK_KEYWORD};
|
||||
use chrono::DateTime;
|
||||
use solana_sdk::{
|
||||
clock::Slot,
|
||||
hash::Hash,
|
||||
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
|
||||
@@ -34,29 +32,6 @@ where
|
||||
is_parsable_generic::<T, String>(string)
|
||||
}
|
||||
|
||||
// Return an error if string cannot be parsed as numeric type T, and value not within specified
|
||||
// range
|
||||
pub fn is_within_range<T>(string: String, range_min: T, range_max: T) -> Result<(), String>
|
||||
where
|
||||
T: FromStr + Copy + std::fmt::Debug + PartialOrd + std::ops::Add<Output = T> + From<usize>,
|
||||
T::Err: Display,
|
||||
{
|
||||
match string.parse::<T>() {
|
||||
Ok(input) => {
|
||||
let range = range_min..range_max + 1.into();
|
||||
if !range.contains(&input) {
|
||||
Err(format!(
|
||||
"input '{:?}' out of range ({:?}..{:?}]",
|
||||
input, range_min, range_max
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Err(err) => Err(format!("error parsing '{}': {}", string, err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Return an error if a pubkey cannot be parsed.
|
||||
pub fn is_pubkey<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
@@ -96,26 +71,6 @@ where
|
||||
.map_err(|err| format!("{}", err))
|
||||
}
|
||||
|
||||
// Return an error if a `SignerSourceKind::Prompt` cannot be parsed
|
||||
pub fn is_prompt_signer_source<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
if string.as_ref() == ASK_KEYWORD {
|
||||
return Ok(());
|
||||
}
|
||||
match parse_signer_source(string.as_ref())
|
||||
.map_err(|err| format!("{}", err))?
|
||||
.kind
|
||||
{
|
||||
SignerSourceKind::Prompt => Ok(()),
|
||||
_ => Err(format!(
|
||||
"Unable to parse input as `prompt:` URI scheme or `ASK` keyword: {}",
|
||||
string
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -130,11 +85,8 @@ pub fn is_valid_pubkey<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
match parse_signer_source(string.as_ref())
|
||||
.map_err(|err| format!("{}", err))?
|
||||
.kind
|
||||
{
|
||||
SignerSourceKind::Filepath(path) => is_keypair(path),
|
||||
match parse_keypair_path(string.as_ref()) {
|
||||
KeypairUrl::Filepath(path) => is_keypair(path),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
@@ -196,40 +148,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_url_or_moniker<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
match url::Url::parse(&normalize_to_url_if_moniker(string.as_ref())) {
|
||||
Ok(url) => {
|
||||
if url.has_host() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("no host provided".to_string())
|
||||
}
|
||||
}
|
||||
Err(err) => Err(format!("{}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_to_url_if_moniker<T: AsRef<str>>(url_or_moniker: T) -> String {
|
||||
match url_or_moniker.as_ref() {
|
||||
"m" | "mainnet-beta" => "https://api.mainnet-beta.solana.com",
|
||||
"t" | "testnet" => "https://api.testnet.solana.com",
|
||||
"d" | "devnet" => "https://api.devnet.solana.com",
|
||||
"l" | "localhost" => "http://localhost:8899",
|
||||
url => url,
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn is_epoch<T>(epoch: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
is_parsable_generic::<Epoch, _>(epoch)
|
||||
}
|
||||
|
||||
pub fn is_slot<T>(slot: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
@@ -339,21 +257,6 @@ where
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub fn is_derived_address_seed<T>(value: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let value = value.as_ref();
|
||||
if value.len() > MAX_SEED_LEN {
|
||||
Err(format!(
|
||||
"Address seed must not be longer than {} bytes",
|
||||
MAX_SEED_LEN
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@@ -1,44 +1,33 @@
|
||||
use {
|
||||
crate::{
|
||||
input_parsers::{pubkeys_sigs_of, STDOUT_OUTFILE_TOKEN},
|
||||
offline::{SIGNER_ARG, SIGN_ONLY_ARG},
|
||||
ArgConstant,
|
||||
use crate::{
|
||||
input_parsers::pubkeys_sigs_of,
|
||||
offline::{SIGNER_ARG, SIGN_ONLY_ARG},
|
||||
ArgConstant,
|
||||
};
|
||||
use bip39::{Language, Mnemonic, Seed};
|
||||
use clap::ArgMatches;
|
||||
use rpassword::prompt_password_stderr;
|
||||
use solana_remote_wallet::{
|
||||
remote_keypair::generate_remote_keypair,
|
||||
remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager},
|
||||
};
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{
|
||||
keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair,
|
||||
read_keypair_file, Keypair, NullSigner, Presigner, Signature, Signer,
|
||||
},
|
||||
bip39::{Language, Mnemonic, Seed},
|
||||
clap::ArgMatches,
|
||||
rpassword::prompt_password_stderr,
|
||||
solana_remote_wallet::{
|
||||
locator::{Locator as RemoteWalletLocator, LocatorError as RemoteWalletLocatorError},
|
||||
remote_keypair::generate_remote_keypair,
|
||||
remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager},
|
||||
},
|
||||
solana_sdk::{
|
||||
derivation_path::{DerivationPath, DerivationPathError},
|
||||
hash::Hash,
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
signature::{
|
||||
generate_seed_from_seed_phrase_and_passphrase, keypair_from_seed,
|
||||
keypair_from_seed_and_derivation_path, keypair_from_seed_phrase_and_passphrase,
|
||||
read_keypair, read_keypair_file, Keypair, NullSigner, Presigner, Signature, Signer,
|
||||
},
|
||||
},
|
||||
std::{
|
||||
cell::RefCell,
|
||||
convert::TryFrom,
|
||||
error,
|
||||
io::{stdin, stdout, Write},
|
||||
ops::Deref,
|
||||
process::exit,
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
},
|
||||
thiserror::Error,
|
||||
};
|
||||
use std::{
|
||||
error,
|
||||
io::{stdin, stdout, Write},
|
||||
process::exit,
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub struct SignOnly {
|
||||
pub blockhash: Hash,
|
||||
pub message: Option<String>,
|
||||
pub present_signers: Vec<(Pubkey, Signature)>,
|
||||
pub absent_signers: Vec<Pubkey>,
|
||||
pub bad_signers: Vec<Pubkey>,
|
||||
@@ -69,71 +58,14 @@ impl CliSignerInfo {
|
||||
Some(0)
|
||||
}
|
||||
}
|
||||
pub fn index_of_or_none(&self, pubkey: Option<Pubkey>) -> Option<usize> {
|
||||
if let Some(pubkey) = pubkey {
|
||||
self.signers
|
||||
.iter()
|
||||
.position(|signer| signer.pubkey() == pubkey)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn signers_for_message(&self, message: &Message) -> Vec<&dyn Signer> {
|
||||
self.signers
|
||||
.iter()
|
||||
.filter_map(|k| {
|
||||
if message.signer_keys().contains(&&k.pubkey()) {
|
||||
Some(k.as_ref())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DefaultSigner {
|
||||
pub arg_name: String,
|
||||
pub path: String,
|
||||
is_path_checked: RefCell<bool>,
|
||||
}
|
||||
|
||||
impl DefaultSigner {
|
||||
pub fn new<AN: AsRef<str>, P: AsRef<str>>(arg_name: AN, path: P) -> Self {
|
||||
let arg_name = arg_name.as_ref().to_string();
|
||||
let path = path.as_ref().to_string();
|
||||
Self {
|
||||
arg_name,
|
||||
path,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> Result<&str, Box<dyn std::error::Error>> {
|
||||
if !self.is_path_checked.borrow().deref() {
|
||||
parse_signer_source(&self.path)
|
||||
.and_then(|s| {
|
||||
if let SignerSourceKind::Filepath(path) = &s.kind {
|
||||
std::fs::metadata(path).map(|_| ()).map_err(|e| e.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.map_err(|_| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"No default signer found, run \"solana-keygen new -o {}\" to create a new one",
|
||||
self.path
|
||||
),
|
||||
)
|
||||
})?;
|
||||
*self.is_path_checked.borrow_mut() = true;
|
||||
}
|
||||
Ok(&self.path)
|
||||
}
|
||||
|
||||
pub fn generate_unique_signers(
|
||||
&self,
|
||||
bulk_signers: Vec<Option<Box<dyn Signer>>>,
|
||||
@@ -148,9 +80,11 @@ impl DefaultSigner {
|
||||
unique_signers.push(default_signer);
|
||||
}
|
||||
|
||||
for signer in bulk_signers.into_iter().flatten() {
|
||||
if !unique_signers.iter().any(|s| s == &signer) {
|
||||
unique_signers.push(signer);
|
||||
for signer in bulk_signers.into_iter() {
|
||||
if let Some(signer) = signer {
|
||||
if !unique_signers.iter().any(|s| s == &signer) {
|
||||
unique_signers.push(signer);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(CliSignerInfo {
|
||||
@@ -163,151 +97,29 @@ impl DefaultSigner {
|
||||
matches: &ArgMatches,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Box<dyn Signer>, Box<dyn std::error::Error>> {
|
||||
signer_from_path(matches, self.path()?, &self.arg_name, wallet_manager)
|
||||
}
|
||||
|
||||
pub fn signer_from_path_with_config(
|
||||
&self,
|
||||
matches: &ArgMatches,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
config: &SignerFromPathConfig,
|
||||
) -> Result<Box<dyn Signer>, Box<dyn std::error::Error>> {
|
||||
signer_from_path_with_config(
|
||||
matches,
|
||||
self.path()?,
|
||||
&self.arg_name,
|
||||
wallet_manager,
|
||||
config,
|
||||
)
|
||||
signer_from_path(matches, &self.path, &self.arg_name, wallet_manager)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SignerSource {
|
||||
pub kind: SignerSourceKind,
|
||||
pub derivation_path: Option<DerivationPath>,
|
||||
pub legacy: bool,
|
||||
}
|
||||
|
||||
impl SignerSource {
|
||||
fn new(kind: SignerSourceKind) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
derivation_path: None,
|
||||
legacy: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_legacy(kind: SignerSourceKind) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
derivation_path: None,
|
||||
legacy: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SIGNER_SOURCE_PROMPT: &str = "prompt";
|
||||
const SIGNER_SOURCE_FILEPATH: &str = "file";
|
||||
const SIGNER_SOURCE_USB: &str = "usb";
|
||||
const SIGNER_SOURCE_STDIN: &str = "stdin";
|
||||
const SIGNER_SOURCE_PUBKEY: &str = "pubkey";
|
||||
|
||||
pub(crate) enum SignerSourceKind {
|
||||
Prompt,
|
||||
pub enum KeypairUrl {
|
||||
Ask,
|
||||
Filepath(String),
|
||||
Usb(RemoteWalletLocator),
|
||||
Usb(String),
|
||||
Stdin,
|
||||
Pubkey(Pubkey),
|
||||
}
|
||||
|
||||
impl AsRef<str> for SignerSourceKind {
|
||||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
Self::Prompt => SIGNER_SOURCE_PROMPT,
|
||||
Self::Filepath(_) => SIGNER_SOURCE_FILEPATH,
|
||||
Self::Usb(_) => SIGNER_SOURCE_USB,
|
||||
Self::Stdin => SIGNER_SOURCE_STDIN,
|
||||
Self::Pubkey(_) => SIGNER_SOURCE_PUBKEY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SignerSourceKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let s: &str = self.as_ref();
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum SignerSourceError {
|
||||
#[error("unrecognized signer source")]
|
||||
UnrecognizedSource,
|
||||
#[error(transparent)]
|
||||
RemoteWalletLocatorError(#[from] RemoteWalletLocatorError),
|
||||
#[error(transparent)]
|
||||
DerivationPathError(#[from] DerivationPathError),
|
||||
#[error(transparent)]
|
||||
IoError(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
pub(crate) fn parse_signer_source<S: AsRef<str>>(
|
||||
source: S,
|
||||
) -> Result<SignerSource, SignerSourceError> {
|
||||
let source = source.as_ref();
|
||||
let source = {
|
||||
#[cfg(target_family = "windows")]
|
||||
{
|
||||
source.replace("\\", "/")
|
||||
}
|
||||
#[cfg(not(target_family = "windows"))]
|
||||
{
|
||||
source.to_string()
|
||||
}
|
||||
};
|
||||
match uriparse::URIReference::try_from(source.as_str()) {
|
||||
Err(_) => Err(SignerSourceError::UnrecognizedSource),
|
||||
Ok(uri) => {
|
||||
if let Some(scheme) = uri.scheme() {
|
||||
let scheme = scheme.as_str().to_ascii_lowercase();
|
||||
match scheme.as_str() {
|
||||
SIGNER_SOURCE_PROMPT => Ok(SignerSource {
|
||||
kind: SignerSourceKind::Prompt,
|
||||
derivation_path: DerivationPath::from_uri_any_query(&uri)?,
|
||||
legacy: false,
|
||||
}),
|
||||
SIGNER_SOURCE_FILEPATH => Ok(SignerSource::new(SignerSourceKind::Filepath(
|
||||
uri.path().to_string(),
|
||||
))),
|
||||
SIGNER_SOURCE_USB => Ok(SignerSource {
|
||||
kind: SignerSourceKind::Usb(RemoteWalletLocator::new_from_uri(&uri)?),
|
||||
derivation_path: DerivationPath::from_uri_key_query(&uri)?,
|
||||
legacy: false,
|
||||
}),
|
||||
SIGNER_SOURCE_STDIN => Ok(SignerSource::new(SignerSourceKind::Stdin)),
|
||||
_ => {
|
||||
#[cfg(target_family = "windows")]
|
||||
// On Windows, an absolute path's drive letter will be parsed as the URI
|
||||
// scheme. Assume a filepath source in case of a single character shceme.
|
||||
if scheme.len() == 1 {
|
||||
return Ok(SignerSource::new(SignerSourceKind::Filepath(source)));
|
||||
}
|
||||
Err(SignerSourceError::UnrecognizedSource)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match source.as_str() {
|
||||
STDOUT_OUTFILE_TOKEN => Ok(SignerSource::new(SignerSourceKind::Stdin)),
|
||||
ASK_KEYWORD => Ok(SignerSource::new_legacy(SignerSourceKind::Prompt)),
|
||||
_ => match Pubkey::from_str(source.as_str()) {
|
||||
Ok(pubkey) => Ok(SignerSource::new(SignerSourceKind::Pubkey(pubkey))),
|
||||
Err(_) => std::fs::metadata(source.as_str())
|
||||
.map(|_| SignerSource::new(SignerSourceKind::Filepath(source)))
|
||||
.map_err(|err| err.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn parse_keypair_path(path: &str) -> KeypairUrl {
|
||||
if path == "-" {
|
||||
KeypairUrl::Stdin
|
||||
} else if path == ASK_KEYWORD {
|
||||
KeypairUrl::Ask
|
||||
} else if path.starts_with("usb://") {
|
||||
KeypairUrl::Usb(path.to_string())
|
||||
} else if let Ok(pubkey) = Pubkey::from_str(path) {
|
||||
KeypairUrl::Pubkey(pubkey)
|
||||
} else {
|
||||
KeypairUrl::Filepath(path.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,53 +136,22 @@ pub fn presigner_from_pubkey_sigs(
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SignerFromPathConfig {
|
||||
pub allow_null_signer: bool,
|
||||
}
|
||||
|
||||
impl Default for SignerFromPathConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
allow_null_signer: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signer_from_path(
|
||||
matches: &ArgMatches,
|
||||
path: &str,
|
||||
keypair_name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Box<dyn Signer>, Box<dyn error::Error>> {
|
||||
let config = SignerFromPathConfig::default();
|
||||
signer_from_path_with_config(matches, path, keypair_name, wallet_manager, &config)
|
||||
}
|
||||
|
||||
pub fn signer_from_path_with_config(
|
||||
matches: &ArgMatches,
|
||||
path: &str,
|
||||
keypair_name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
config: &SignerFromPathConfig,
|
||||
) -> Result<Box<dyn Signer>, Box<dyn error::Error>> {
|
||||
let SignerSource {
|
||||
kind,
|
||||
derivation_path,
|
||||
legacy,
|
||||
} = parse_signer_source(path)?;
|
||||
match kind {
|
||||
SignerSourceKind::Prompt => {
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Ask => {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
Ok(Box::new(keypair_from_seed_phrase(
|
||||
keypair_name,
|
||||
skip_validation,
|
||||
false,
|
||||
derivation_path,
|
||||
legacy,
|
||||
)?))
|
||||
}
|
||||
SignerSourceKind::Filepath(path) => match read_keypair_file(&path) {
|
||||
KeypairUrl::Filepath(path) => match read_keypair_file(&path) {
|
||||
Err(e) => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("could not read keypair file \"{}\". Run \"solana-keygen new\" to create a keypair file: {}", path, e),
|
||||
@@ -378,18 +159,17 @@ pub fn signer_from_path_with_config(
|
||||
.into()),
|
||||
Ok(file) => Ok(Box::new(file)),
|
||||
},
|
||||
SignerSourceKind::Stdin => {
|
||||
KeypairUrl::Stdin => {
|
||||
let mut stdin = std::io::stdin();
|
||||
Ok(Box::new(read_keypair(&mut stdin)?))
|
||||
}
|
||||
SignerSourceKind::Usb(locator) => {
|
||||
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(
|
||||
locator,
|
||||
derivation_path.unwrap_or_default(),
|
||||
path,
|
||||
wallet_manager,
|
||||
matches.is_present("confirm_key"),
|
||||
keypair_name,
|
||||
@@ -398,13 +178,13 @@ pub fn signer_from_path_with_config(
|
||||
Err(RemoteWalletError::NoDeviceFound.into())
|
||||
}
|
||||
}
|
||||
SignerSourceKind::Pubkey(pubkey) => {
|
||||
KeypairUrl::Pubkey(pubkey) => {
|
||||
let presigner = pubkeys_sigs_of(matches, SIGNER_ARG.name)
|
||||
.as_ref()
|
||||
.and_then(|presigners| presigner_from_pubkey_sigs(&pubkey, presigners));
|
||||
if let Some(presigner) = presigner {
|
||||
Ok(Box::new(presigner))
|
||||
} else if config.allow_null_signer || matches.is_present(SIGN_ONLY_ARG.name) {
|
||||
} else if matches.is_present(SIGN_ONLY_ARG.name) {
|
||||
Ok(Box::new(NullSigner::new(&pubkey)))
|
||||
} else {
|
||||
Err(std::io::Error::new(
|
||||
@@ -423,9 +203,8 @@ pub fn pubkey_from_path(
|
||||
keypair_name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Pubkey, Box<dyn error::Error>> {
|
||||
let SignerSource { kind, .. } = parse_signer_source(path)?;
|
||||
match kind {
|
||||
SignerSourceKind::Pubkey(pubkey) => Ok(pubkey),
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Pubkey(pubkey) => Ok(pubkey),
|
||||
_ => Ok(signer_from_path(matches, path, keypair_name, wallet_manager)?.pubkey()),
|
||||
}
|
||||
}
|
||||
@@ -436,51 +215,34 @@ pub fn resolve_signer_from_path(
|
||||
keypair_name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<String>, Box<dyn error::Error>> {
|
||||
let SignerSource {
|
||||
kind,
|
||||
derivation_path,
|
||||
legacy,
|
||||
} = parse_signer_source(path)?;
|
||||
match kind {
|
||||
SignerSourceKind::Prompt => {
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Ask => {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
// This method validates the seed phrase, but returns `None` because there is no path
|
||||
// on disk or to a device
|
||||
keypair_from_seed_phrase(
|
||||
keypair_name,
|
||||
skip_validation,
|
||||
false,
|
||||
derivation_path,
|
||||
legacy,
|
||||
)
|
||||
.map(|_| None)
|
||||
keypair_from_seed_phrase(keypair_name, skip_validation, false).map(|_| None)
|
||||
}
|
||||
SignerSourceKind::Filepath(path) => match read_keypair_file(&path) {
|
||||
KeypairUrl::Filepath(path) => match read_keypair_file(&path) {
|
||||
Err(e) => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"could not read keypair file \"{}\". \
|
||||
Run \"solana-keygen new\" to create a keypair file: {}",
|
||||
path, e
|
||||
),
|
||||
format!("could not read keypair file \"{}\". Run \"solana-keygen new\" to create a keypair file: {}", path, e),
|
||||
)
|
||||
.into()),
|
||||
Ok(_) => Ok(Some(path.to_string())),
|
||||
},
|
||||
SignerSourceKind::Stdin => {
|
||||
KeypairUrl::Stdin => {
|
||||
let mut stdin = std::io::stdin();
|
||||
// This method validates the keypair from stdin, but returns `None` because there is no
|
||||
// path on disk or to a device
|
||||
read_keypair(&mut stdin).map(|_| None)
|
||||
}
|
||||
SignerSourceKind::Usb(locator) => {
|
||||
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(
|
||||
locator,
|
||||
derivation_path.unwrap_or_default(),
|
||||
path,
|
||||
wallet_manager,
|
||||
matches.is_present("confirm_key"),
|
||||
keypair_name,
|
||||
@@ -495,7 +257,7 @@ pub fn resolve_signer_from_path(
|
||||
}
|
||||
}
|
||||
|
||||
// Keyword used to indicate that the user should be prompted for a keypair seed phrase
|
||||
// Keyword used to indicate that the user should be asked for a keypair seed phrase
|
||||
pub const ASK_KEYWORD: &str = "ASK";
|
||||
|
||||
pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant {
|
||||
@@ -516,56 +278,6 @@ pub fn prompt_passphrase(prompt: &str) -> Result<String, Box<dyn error::Error>>
|
||||
Ok(passphrase)
|
||||
}
|
||||
|
||||
/// Parses a path into a SignerSource and returns a Keypair for supporting SignerSourceKinds
|
||||
pub fn keypair_from_path(
|
||||
matches: &ArgMatches,
|
||||
path: &str,
|
||||
keypair_name: &str,
|
||||
confirm_pubkey: bool,
|
||||
) -> Result<Keypair, Box<dyn error::Error>> {
|
||||
let SignerSource {
|
||||
kind,
|
||||
derivation_path,
|
||||
legacy,
|
||||
} = parse_signer_source(path)?;
|
||||
match kind {
|
||||
SignerSourceKind::Prompt => {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
Ok(keypair_from_seed_phrase(
|
||||
keypair_name,
|
||||
skip_validation,
|
||||
confirm_pubkey,
|
||||
derivation_path,
|
||||
legacy,
|
||||
)?)
|
||||
}
|
||||
SignerSourceKind::Filepath(path) => match read_keypair_file(&path) {
|
||||
Err(e) => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"could not read keypair file \"{}\". \
|
||||
Run \"solana-keygen new\" to create a keypair file: {}",
|
||||
path, e
|
||||
),
|
||||
)
|
||||
.into()),
|
||||
Ok(file) => Ok(file),
|
||||
},
|
||||
SignerSourceKind::Stdin => {
|
||||
let mut stdin = std::io::stdin();
|
||||
Ok(read_keypair(&mut stdin)?)
|
||||
}
|
||||
_ => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"signer of type `{:?}` does not support Keypair output",
|
||||
kind
|
||||
),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads user input from stdin to retrieve a seed phrase and passphrase for keypair derivation
|
||||
/// Optionally skips validation of seed phrase
|
||||
/// Optionally confirms recovered public key
|
||||
@@ -573,8 +285,6 @@ pub fn keypair_from_seed_phrase(
|
||||
keypair_name: &str,
|
||||
skip_validation: bool,
|
||||
confirm_pubkey: bool,
|
||||
derivation_path: Option<DerivationPath>,
|
||||
legacy: bool,
|
||||
) -> Result<Keypair, Box<dyn error::Error>> {
|
||||
let seed_phrase = prompt_password_stderr(&format!("[{}] seed phrase: ", keypair_name))?;
|
||||
let seed_phrase = seed_phrase.trim();
|
||||
@@ -585,12 +295,7 @@ pub fn keypair_from_seed_phrase(
|
||||
|
||||
let keypair = if skip_validation {
|
||||
let passphrase = prompt_passphrase(&passphrase_prompt)?;
|
||||
if legacy {
|
||||
keypair_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase)?
|
||||
} else {
|
||||
let seed = generate_seed_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase);
|
||||
keypair_from_seed_and_derivation_path(&seed, derivation_path)?
|
||||
}
|
||||
keypair_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase)?
|
||||
} else {
|
||||
let sanitized = sanitize_seed_phrase(seed_phrase);
|
||||
let parse_language_fn = || {
|
||||
@@ -613,11 +318,7 @@ pub fn keypair_from_seed_phrase(
|
||||
let mnemonic = parse_language_fn()?;
|
||||
let passphrase = prompt_passphrase(&passphrase_prompt)?;
|
||||
let seed = Seed::new(&mnemonic, &passphrase);
|
||||
if legacy {
|
||||
keypair_from_seed(seed.as_bytes())?
|
||||
} else {
|
||||
keypair_from_seed_and_derivation_path(&seed.as_bytes(), derivation_path)?
|
||||
}
|
||||
keypair_from_seed(seed.as_bytes())?
|
||||
};
|
||||
|
||||
if confirm_pubkey {
|
||||
@@ -645,9 +346,6 @@ fn sanitize_seed_phrase(seed_phrase: &str) -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_remote_wallet::locator::Manufacturer;
|
||||
use solana_sdk::system_instruction;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_seed_phrase() {
|
||||
@@ -657,153 +355,4 @@ mod tests {
|
||||
sanitize_seed_phrase(seed_phrase)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signer_info_signers_for_message() {
|
||||
let source = Keypair::new();
|
||||
let fee_payer = Keypair::new();
|
||||
let nonsigner1 = Keypair::new();
|
||||
let nonsigner2 = Keypair::new();
|
||||
let recipient = Pubkey::new_unique();
|
||||
let message = Message::new(
|
||||
&[system_instruction::transfer(
|
||||
&source.pubkey(),
|
||||
&recipient,
|
||||
42,
|
||||
)],
|
||||
Some(&fee_payer.pubkey()),
|
||||
);
|
||||
let signers = vec![
|
||||
Box::new(fee_payer) as Box<dyn Signer>,
|
||||
Box::new(source) as Box<dyn Signer>,
|
||||
Box::new(nonsigner1) as Box<dyn Signer>,
|
||||
Box::new(nonsigner2) as Box<dyn Signer>,
|
||||
];
|
||||
let signer_info = CliSignerInfo { signers };
|
||||
let msg_signers = signer_info.signers_for_message(&message);
|
||||
let signer_pubkeys = msg_signers.iter().map(|s| s.pubkey()).collect::<Vec<_>>();
|
||||
let expect = vec![
|
||||
signer_info.signers[0].pubkey(),
|
||||
signer_info.signers[1].pubkey(),
|
||||
];
|
||||
assert_eq!(signer_pubkeys, expect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_signer_source() {
|
||||
assert!(matches!(
|
||||
parse_signer_source(STDOUT_OUTFILE_TOKEN).unwrap(),
|
||||
SignerSource {
|
||||
kind: SignerSourceKind::Stdin,
|
||||
derivation_path: None,
|
||||
legacy: false,
|
||||
}
|
||||
));
|
||||
let stdin = "stdin:".to_string();
|
||||
assert!(matches!(
|
||||
parse_signer_source(&stdin).unwrap(),
|
||||
SignerSource {
|
||||
kind: SignerSourceKind::Stdin,
|
||||
derivation_path: None,
|
||||
legacy: false,
|
||||
}
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_signer_source(ASK_KEYWORD).unwrap(),
|
||||
SignerSource {
|
||||
kind: SignerSourceKind::Prompt,
|
||||
derivation_path: None,
|
||||
legacy: true,
|
||||
}
|
||||
));
|
||||
let pubkey = Pubkey::new_unique();
|
||||
assert!(
|
||||
matches!(parse_signer_source(&pubkey.to_string()).unwrap(), SignerSource {
|
||||
kind: SignerSourceKind::Pubkey(p),
|
||||
derivation_path: None,
|
||||
legacy: false,
|
||||
}
|
||||
if p == pubkey)
|
||||
);
|
||||
|
||||
// Set up absolute and relative path strs
|
||||
let file0 = NamedTempFile::new().unwrap();
|
||||
let path = file0.path();
|
||||
assert!(path.is_absolute());
|
||||
let absolute_path_str = path.to_str().unwrap();
|
||||
|
||||
let file1 = NamedTempFile::new_in(std::env::current_dir().unwrap()).unwrap();
|
||||
let path = file1.path().file_name().unwrap().to_str().unwrap();
|
||||
let path = std::path::Path::new(path);
|
||||
assert!(path.is_relative());
|
||||
let relative_path_str = path.to_str().unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(parse_signer_source(absolute_path_str).unwrap(), SignerSource {
|
||||
kind: SignerSourceKind::Filepath(p),
|
||||
derivation_path: None,
|
||||
legacy: false,
|
||||
} if p == absolute_path_str)
|
||||
);
|
||||
assert!(
|
||||
matches!(parse_signer_source(&relative_path_str).unwrap(), SignerSource {
|
||||
kind: SignerSourceKind::Filepath(p),
|
||||
derivation_path: None,
|
||||
legacy: false,
|
||||
} if p == relative_path_str)
|
||||
);
|
||||
|
||||
let usb = "usb://ledger".to_string();
|
||||
let expected_locator = RemoteWalletLocator {
|
||||
manufacturer: Manufacturer::Ledger,
|
||||
pubkey: None,
|
||||
};
|
||||
assert!(matches!(parse_signer_source(&usb).unwrap(), SignerSource {
|
||||
kind: SignerSourceKind::Usb(u),
|
||||
derivation_path: None,
|
||||
legacy: false,
|
||||
} if u == expected_locator));
|
||||
let usb = "usb://ledger?key=0/0".to_string();
|
||||
let expected_locator = RemoteWalletLocator {
|
||||
manufacturer: Manufacturer::Ledger,
|
||||
pubkey: None,
|
||||
};
|
||||
let expected_derivation_path = Some(DerivationPath::new_bip44(Some(0), Some(0)));
|
||||
assert!(matches!(parse_signer_source(&usb).unwrap(), SignerSource {
|
||||
kind: SignerSourceKind::Usb(u),
|
||||
derivation_path: d,
|
||||
legacy: false,
|
||||
} if u == expected_locator && d == expected_derivation_path));
|
||||
// Catchall into SignerSource::Filepath fails
|
||||
let junk = "sometextthatisnotapubkeyorfile".to_string();
|
||||
assert!(Pubkey::from_str(&junk).is_err());
|
||||
assert!(matches!(
|
||||
parse_signer_source(&junk),
|
||||
Err(SignerSourceError::IoError(_))
|
||||
));
|
||||
|
||||
let prompt = "prompt:".to_string();
|
||||
assert!(matches!(
|
||||
parse_signer_source(&prompt).unwrap(),
|
||||
SignerSource {
|
||||
kind: SignerSourceKind::Prompt,
|
||||
derivation_path: None,
|
||||
legacy: false,
|
||||
}
|
||||
));
|
||||
assert!(
|
||||
matches!(parse_signer_source(&format!("file:{}", absolute_path_str)).unwrap(), SignerSource {
|
||||
kind: SignerSourceKind::Filepath(p),
|
||||
derivation_path: None,
|
||||
legacy: false,
|
||||
} if p == absolute_path_str)
|
||||
);
|
||||
assert!(
|
||||
matches!(parse_signer_source(&format!("file:{}", relative_path_str)).unwrap(), SignerSource {
|
||||
kind: SignerSourceKind::Filepath(p),
|
||||
derivation_path: None,
|
||||
legacy: false,
|
||||
} if p == relative_path_str)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -23,10 +23,10 @@ impl std::fmt::Debug for DisplayError {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod commitment;
|
||||
pub mod fee_payer;
|
||||
pub mod input_parsers;
|
||||
pub mod input_validators;
|
||||
pub mod keypair;
|
||||
pub mod memo;
|
||||
pub mod nonce;
|
||||
pub mod offline;
|
||||
|
@@ -1,15 +0,0 @@
|
||||
use {crate::ArgConstant, clap::Arg};
|
||||
|
||||
pub const MEMO_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "memo",
|
||||
long: "--with-memo",
|
||||
help: "Specify a memo string to include in the transaction.",
|
||||
};
|
||||
|
||||
pub fn memo_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(MEMO_ARG.name)
|
||||
.long(MEMO_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("MEMO")
|
||||
.help(MEMO_ARG.help)
|
||||
}
|
@@ -1,7 +1,5 @@
|
||||
use {
|
||||
crate::{input_validators::*, offline::BLOCKHASH_ARG, ArgConstant},
|
||||
clap::{App, Arg},
|
||||
};
|
||||
use crate::{input_validators::*, offline::BLOCKHASH_ARG, ArgConstant};
|
||||
use clap::{App, Arg};
|
||||
|
||||
pub const NONCE_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "nonce",
|
||||
|
@@ -1,7 +1,5 @@
|
||||
use {
|
||||
crate::{input_validators::*, ArgConstant},
|
||||
clap::{App, Arg},
|
||||
};
|
||||
use crate::{input_validators::*, ArgConstant};
|
||||
use clap::{App, Arg};
|
||||
|
||||
pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "blockhash",
|
||||
@@ -21,12 +19,6 @@ pub const SIGNER_ARG: ArgConstant<'static> = ArgConstant {
|
||||
help: "Provide a public-key/signature pair for the transaction",
|
||||
};
|
||||
|
||||
pub const DUMP_TRANSACTION_MESSAGE: ArgConstant<'static> = ArgConstant {
|
||||
name: "dump_transaction_message",
|
||||
long: "dump-transaction-message",
|
||||
help: "Display the base64 encoded binary transaction message in sign-only mode",
|
||||
};
|
||||
|
||||
pub fn blockhash_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(BLOCKHASH_ARG.name)
|
||||
.long(BLOCKHASH_ARG.long)
|
||||
@@ -55,14 +47,6 @@ fn signer_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
.help(SIGNER_ARG.help)
|
||||
}
|
||||
|
||||
pub fn dump_transaction_message<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(DUMP_TRANSACTION_MESSAGE.name)
|
||||
.long(DUMP_TRANSACTION_MESSAGE.long)
|
||||
.takes_value(false)
|
||||
.requires(SIGN_ONLY_ARG.name)
|
||||
.help(DUMP_TRANSACTION_MESSAGE.help)
|
||||
}
|
||||
|
||||
pub trait ArgsConfig {
|
||||
fn blockhash_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
|
||||
arg
|
||||
@@ -73,9 +57,6 @@ pub trait ArgsConfig {
|
||||
fn signer_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
|
||||
arg
|
||||
}
|
||||
fn dump_transaction_message_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
|
||||
arg
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OfflineArgs {
|
||||
@@ -88,7 +69,6 @@ impl OfflineArgs for App<'_, '_> {
|
||||
self.arg(config.blockhash_arg(blockhash_arg()))
|
||||
.arg(config.sign_only_arg(sign_only_arg()))
|
||||
.arg(config.signer_arg(signer_arg()))
|
||||
.arg(config.dump_transaction_message_arg(dump_transaction_message()))
|
||||
}
|
||||
fn offline_args(self) -> Self {
|
||||
struct NullArgsConfig {}
|
||||
|
@@ -3,16 +3,15 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-config"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.7.2"
|
||||
version = "1.4.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-cli-config"
|
||||
|
||||
[dependencies]
|
||||
dirs-next = "2.0.0"
|
||||
lazy_static = "1.4.0"
|
||||
serde = "1.0.122"
|
||||
serde = "1.0.112"
|
||||
serde_derive = "1.0.103"
|
||||
serde_yaml = "0.8.13"
|
||||
url = "2.1.1"
|
||||
|
@@ -17,10 +17,9 @@ pub struct Config {
|
||||
pub json_rpc_url: String,
|
||||
pub websocket_url: String,
|
||||
pub keypair_path: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub address_labels: HashMap<String, String>,
|
||||
#[serde(default)]
|
||||
pub commitment: String,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -42,14 +41,11 @@ impl Default for Config {
|
||||
"System Program".to_string(),
|
||||
);
|
||||
|
||||
let commitment = "confirmed".to_string();
|
||||
|
||||
Self {
|
||||
json_rpc_url,
|
||||
websocket_url,
|
||||
keypair_path,
|
||||
address_labels,
|
||||
commitment,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,8 +71,7 @@ impl Config {
|
||||
.set_scheme(if is_secure { "wss" } else { "ws" })
|
||||
.expect("unable to set scheme");
|
||||
if let Some(port) = json_rpc_url.port() {
|
||||
let port = port.checked_add(1).expect("port out of range");
|
||||
ws_url.set_port(Some(port)).expect("unable to set port");
|
||||
ws_url.set_port(Some(port + 1)).expect("unable to set port");
|
||||
}
|
||||
ws_url.to_string()
|
||||
}
|
||||
@@ -107,13 +102,13 @@ mod test {
|
||||
#[test]
|
||||
fn compute_websocket_url() {
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"http://api.devnet.solana.com"),
|
||||
"ws://api.devnet.solana.com/".to_string()
|
||||
Config::compute_websocket_url(&"http://devnet.solana.com"),
|
||||
"ws://devnet.solana.com/".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"https://api.devnet.solana.com"),
|
||||
"wss://api.devnet.solana.com/".to_string()
|
||||
Config::compute_websocket_url(&"https://devnet.solana.com"),
|
||||
"wss://devnet.solana.com/".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
@@ -3,29 +3,27 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-output"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.7.2"
|
||||
version = "1.4.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-cli-output"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.13.0"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
console = "0.14.1"
|
||||
console = "0.11.3"
|
||||
humantime = "2.0.1"
|
||||
Inflector = "0.11.4"
|
||||
indicatif = "0.15.0"
|
||||
serde = "1.0.122"
|
||||
serde = "1.0.112"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.7.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.2" }
|
||||
solana-client = { path = "../client", version = "=1.7.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.2" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.2" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.7.2" }
|
||||
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.4.14" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.4.14" }
|
||||
solana-client = { path = "../client", version = "1.4.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.4.14" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.4.14" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.4.14" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.4.14" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,85 +1,33 @@
|
||||
use {
|
||||
crate::cli_output::CliSignatureVerificationStatus,
|
||||
chrono::{DateTime, Local, NaiveDateTime, SecondsFormat, TimeZone, Utc},
|
||||
console::style,
|
||||
indicatif::{ProgressBar, ProgressStyle},
|
||||
solana_sdk::{
|
||||
clock::UnixTimestamp, hash::Hash, message::Message, native_token::lamports_to_sol,
|
||||
program_utils::limited_deserialize, pubkey::Pubkey, stake, transaction::Transaction,
|
||||
},
|
||||
solana_transaction_status::UiTransactionStatusMeta,
|
||||
spl_memo::id as spl_memo_id,
|
||||
spl_memo::v1::id as spl_memo_v1_id,
|
||||
std::{collections::HashMap, fmt, io},
|
||||
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::UiTransactionStatusMeta;
|
||||
use std::{collections::HashMap, fmt, io};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BuildBalanceMessageConfig {
|
||||
pub use_lamports_unit: bool,
|
||||
pub show_unit: bool,
|
||||
pub trim_trailing_zeros: bool,
|
||||
}
|
||||
|
||||
impl Default for BuildBalanceMessageConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
use_lamports_unit: false,
|
||||
show_unit: true,
|
||||
trim_trailing_zeros: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_memo_program(k: &Pubkey) -> bool {
|
||||
let k_str = k.to_string();
|
||||
(k_str == spl_memo_v1_id().to_string()) || (k_str == spl_memo_id().to_string())
|
||||
}
|
||||
|
||||
pub fn build_balance_message_with_config(
|
||||
lamports: u64,
|
||||
config: &BuildBalanceMessageConfig,
|
||||
) -> String {
|
||||
let value = if config.use_lamports_unit {
|
||||
lamports.to_string()
|
||||
pub fn build_balance_message(lamports: u64, use_lamports_unit: bool, show_unit: bool) -> String {
|
||||
if use_lamports_unit {
|
||||
let ess = if lamports == 1 { "" } else { "s" };
|
||||
let unit = if show_unit {
|
||||
format!(" lamport{}", ess)
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
format!("{:?}{}", lamports, unit)
|
||||
} else {
|
||||
let sol = lamports_to_sol(lamports);
|
||||
let sol_str = format!("{:.9}", sol);
|
||||
if config.trim_trailing_zeros {
|
||||
sol_str
|
||||
.trim_end_matches('0')
|
||||
.trim_end_matches('.')
|
||||
.to_string()
|
||||
} else {
|
||||
sol_str
|
||||
}
|
||||
};
|
||||
let unit = if config.show_unit {
|
||||
if config.use_lamports_unit {
|
||||
let ess = if lamports == 1 { "" } else { "s" };
|
||||
format!(" lamport{}", ess)
|
||||
} else {
|
||||
" SOL".to_string()
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
format!("{}{}", value, unit)
|
||||
}
|
||||
|
||||
pub fn build_balance_message(lamports: u64, use_lamports_unit: bool, show_unit: bool) -> String {
|
||||
build_balance_message_with_config(
|
||||
lamports,
|
||||
&BuildBalanceMessageConfig {
|
||||
use_lamports_unit,
|
||||
show_unit,
|
||||
..BuildBalanceMessageConfig::default()
|
||||
},
|
||||
)
|
||||
let pretty_sol = sol_str.trim_end_matches('0').trim_end_matches('.');
|
||||
let unit = if show_unit { " SOL" } else { "" };
|
||||
format!("{}{}", pretty_sol, unit)
|
||||
}
|
||||
}
|
||||
|
||||
// Pretty print a "name value"
|
||||
pub fn println_name_value(name: &str, value: &str) {
|
||||
let styled_value = if value.is_empty() {
|
||||
let styled_value = if value == "" {
|
||||
style("(not set)").italic()
|
||||
} else {
|
||||
style(value)
|
||||
@@ -87,8 +35,8 @@ pub fn println_name_value(name: &str, value: &str) {
|
||||
println!("{} {}", style(name).bold(), styled_value);
|
||||
}
|
||||
|
||||
pub fn writeln_name_value(f: &mut dyn fmt::Write, name: &str, value: &str) -> fmt::Result {
|
||||
let styled_value = if value.is_empty() {
|
||||
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)
|
||||
@@ -132,98 +80,35 @@ pub fn println_signers(
|
||||
println!();
|
||||
}
|
||||
|
||||
fn format_account_mode(message: &Message, index: usize) -> String {
|
||||
format!(
|
||||
"{}r{}{}", // accounts are always readable...
|
||||
if message.is_signer(index) {
|
||||
"s" // stands for signer
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if message.is_writable(index, /*demote_sysvar_write_locks=*/ true) {
|
||||
"w" // comment for consistent rust fmt (no joking; lol)
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
// account may be executable on-chain while not being
|
||||
// designated as a program-id in the message
|
||||
if message.maybe_executable(index) {
|
||||
"x"
|
||||
} else {
|
||||
// programs to be executed via CPI cannot be identified as
|
||||
// executable from the message
|
||||
"-"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn write_transaction<W: io::Write>(
|
||||
w: &mut W,
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<UiTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
||||
block_time: Option<UnixTimestamp>,
|
||||
) -> io::Result<()> {
|
||||
let message = &transaction.message;
|
||||
if let Some(block_time) = block_time {
|
||||
writeln!(
|
||||
w,
|
||||
"{}Block Time: {:?}",
|
||||
prefix,
|
||||
Local.timestamp(block_time, 0)
|
||||
)?;
|
||||
}
|
||||
writeln!(
|
||||
w,
|
||||
"{}Recent Blockhash: {:?}",
|
||||
prefix, message.recent_blockhash
|
||||
)?;
|
||||
let sigverify_statuses = if let Some(sigverify_status) = sigverify_status {
|
||||
sigverify_status
|
||||
.iter()
|
||||
.map(|s| format!(" ({})", s))
|
||||
.collect()
|
||||
} else {
|
||||
vec!["".to_string(); transaction.signatures.len()]
|
||||
};
|
||||
for (signature_index, (signature, sigverify_status)) in transaction
|
||||
.signatures
|
||||
.iter()
|
||||
.zip(&sigverify_statuses)
|
||||
.enumerate()
|
||||
{
|
||||
for (signature_index, signature) in transaction.signatures.iter().enumerate() {
|
||||
writeln!(
|
||||
w,
|
||||
"{}Signature {}: {:?}{}",
|
||||
prefix, signature_index, signature, sigverify_status,
|
||||
"{}Signature {}: {:?}",
|
||||
prefix, signature_index, signature
|
||||
)?;
|
||||
}
|
||||
let mut fee_payer_index = None;
|
||||
writeln!(w, "{}{:?}", prefix, message.header)?;
|
||||
for (account_index, account) in message.account_keys.iter().enumerate() {
|
||||
if fee_payer_index.is_none() && message.is_non_loader_key(account, account_index) {
|
||||
fee_payer_index = Some(account_index)
|
||||
}
|
||||
writeln!(
|
||||
w,
|
||||
"{}Account {}: {} {}{}",
|
||||
prefix,
|
||||
account_index,
|
||||
format_account_mode(message, account_index),
|
||||
account,
|
||||
if Some(account_index) == fee_payer_index {
|
||||
" (fee payer)"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
)?;
|
||||
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: {} ({})",
|
||||
"{} Program: {} ({})",
|
||||
prefix, program_pubkey, instruction.program_id_index
|
||||
)?;
|
||||
for (account_index, account) in instruction.accounts.iter().enumerate() {
|
||||
@@ -244,9 +129,10 @@ pub fn write_transaction<W: io::Write>(
|
||||
writeln!(w, "{} {:?}", prefix, vote_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
} else if program_pubkey == stake::program::id() {
|
||||
if let Ok(stake_instruction) =
|
||||
limited_deserialize::<stake::instruction::StakeInstruction>(&instruction.data)
|
||||
} 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;
|
||||
@@ -259,11 +145,6 @@ pub fn write_transaction<W: io::Write>(
|
||||
writeln!(w, "{} {:?}", prefix, system_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
} else if is_memo_program(&program_pubkey) {
|
||||
if let Ok(s) = std::str::from_utf8(&instruction.data) {
|
||||
writeln!(w, "{} Data: \"{}\"", prefix, s)?;
|
||||
raw = false;
|
||||
}
|
||||
}
|
||||
|
||||
if raw {
|
||||
@@ -321,38 +202,7 @@ pub fn write_transaction<W: io::Write>(
|
||||
if !log_messages.is_empty() {
|
||||
writeln!(w, "{}Log Messages:", prefix,)?;
|
||||
for log_message in log_messages {
|
||||
writeln!(w, "{} {}", prefix, log_message)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rewards) = &transaction_status.rewards {
|
||||
if !rewards.is_empty() {
|
||||
writeln!(w, "{}Rewards:", prefix,)?;
|
||||
writeln!(
|
||||
w,
|
||||
"{} {:<44} {:^15} {:<15} {:<20}",
|
||||
prefix, "Address", "Type", "Amount", "New Balance"
|
||||
)?;
|
||||
for reward in rewards {
|
||||
let sign = if reward.lamports < 0 { "-" } else { "" };
|
||||
writeln!(
|
||||
w,
|
||||
"{} {:<44} {:^15} {:<15} {}",
|
||||
prefix,
|
||||
reward.pubkey,
|
||||
if let Some(reward_type) = reward.reward_type {
|
||||
format!("{}", reward_type)
|
||||
} else {
|
||||
"-".to_string()
|
||||
},
|
||||
format!(
|
||||
"{}◎{:<14.9}",
|
||||
sign,
|
||||
lamports_to_sol(reward.lamports.abs() as u64)
|
||||
),
|
||||
format!("◎{:<18.9}", lamports_to_sol(reward.post_balance),)
|
||||
)?;
|
||||
writeln!(w, "{} {}", prefix, log_message,)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -367,52 +217,15 @@ pub fn println_transaction(
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<UiTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
||||
block_time: Option<UnixTimestamp>,
|
||||
) {
|
||||
let mut w = Vec::new();
|
||||
if write_transaction(
|
||||
&mut w,
|
||||
transaction,
|
||||
transaction_status,
|
||||
prefix,
|
||||
sigverify_status,
|
||||
block_time,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
if write_transaction(&mut w, transaction, transaction_status, prefix).is_ok() {
|
||||
if let Ok(s) = String::from_utf8(w) {
|
||||
print!("{}", s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writeln_transaction(
|
||||
f: &mut dyn fmt::Write,
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<UiTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
||||
block_time: Option<UnixTimestamp>,
|
||||
) -> fmt::Result {
|
||||
let mut w = Vec::new();
|
||||
if write_transaction(
|
||||
&mut w,
|
||||
transaction,
|
||||
transaction_status,
|
||||
prefix,
|
||||
sigverify_status,
|
||||
block_time,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
if let Ok(s) = String::from_utf8(w) {
|
||||
write!(f, "{}", s)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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);
|
||||
@@ -422,13 +235,6 @@ pub fn new_spinner_progress_bar() -> ProgressBar {
|
||||
progress_bar
|
||||
}
|
||||
|
||||
pub fn unix_timestamp_to_string(unix_timestamp: UnixTimestamp) -> String {
|
||||
match NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) {
|
||||
Some(ndt) => DateTime::<Utc>::from_utc(ndt, Utc).to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
None => format!("UnixTimestamp {}", unix_timestamp),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@@ -1,4 +1,3 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
mod cli_output;
|
||||
pub mod display;
|
||||
pub use cli_output::*;
|
||||
|
@@ -3,11 +3,10 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.7.2"
|
||||
version = "1.4.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-cli"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.1"
|
||||
@@ -16,41 +15,42 @@ chrono = { version = "0.4.11", features = ["serde"] }
|
||||
clap = "2.33.1"
|
||||
criterion-stats = "0.3.0"
|
||||
ctrlc = { version = "3.1.5", features = ["termination"] }
|
||||
console = "0.14.1"
|
||||
console = "0.11.3"
|
||||
dirs-next = "2.0.0"
|
||||
log = "0.4.11"
|
||||
log = "0.4.8"
|
||||
Inflector = "0.11.4"
|
||||
indicatif = "0.15.0"
|
||||
humantime = "2.0.1"
|
||||
num-traits = "0.2"
|
||||
pretty-hex = "0.2.1"
|
||||
reqwest = { version = "0.11.2", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serde = "1.0.122"
|
||||
pretty-hex = "0.1.1"
|
||||
reqwest = { version = "0.10.8", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serde = "1.0.112"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.7.2" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.7.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.2" }
|
||||
solana-cli-config = { path = "../cli-config", version = "=1.7.2" }
|
||||
solana-cli-output = { path = "../cli-output", version = "=1.7.2" }
|
||||
solana-client = { path = "../client", version = "=1.7.2" }
|
||||
solana-config-program = { path = "../programs/config", version = "=1.7.2" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.2" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.2" }
|
||||
solana_rbpf = "=0.2.11"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "=1.7.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.2" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.2" }
|
||||
solana-version = { path = "../version", version = "=1.7.2" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.7.2" }
|
||||
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
|
||||
thiserror = "1.0.21"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.4.14" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.4.14" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.4.14" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.4.14" }
|
||||
solana-cli-output = { path = "../cli-output", version = "1.4.14" }
|
||||
solana-client = { path = "../client", version = "1.4.14" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.4.14" }
|
||||
solana-faucet = { path = "../faucet", version = "1.4.14" }
|
||||
solana-logger = { path = "../logger", version = "1.4.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.4.14" }
|
||||
solana_rbpf = "=0.1.34"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.4.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.4.14" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.4.14" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.4.14" }
|
||||
solana-version = { path = "../version", version = "1.4.14" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.4.14" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.4.14" }
|
||||
thiserror = "1.0.20"
|
||||
tiny-bip39 = "0.7.0"
|
||||
url = "2.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-core = { path = "../core", version = "=1.7.2" }
|
||||
solana-core = { path = "../core", version = "1.4.14" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[[bin]]
|
||||
|
@@ -86,13 +86,9 @@ pub fn check_account_for_spend_multiple_fees_with_commitment(
|
||||
return Err(CliError::InsufficientFundsForSpendAndFee(
|
||||
lamports_to_sol(balance),
|
||||
lamports_to_sol(fee),
|
||||
*account_pubkey,
|
||||
));
|
||||
} else {
|
||||
return Err(CliError::InsufficientFundsForFee(
|
||||
lamports_to_sol(fee),
|
||||
*account_pubkey,
|
||||
));
|
||||
return Err(CliError::InsufficientFundsForFee(lamports_to_sol(fee)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -231,9 +227,18 @@ mod tests {
|
||||
mocks.insert(RpcRequest::GetBalance, account_balance_response);
|
||||
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
||||
|
||||
assert!(check_account_for_balance(&rpc_client, &pubkey, 1).unwrap());
|
||||
assert!(check_account_for_balance(&rpc_client, &pubkey, account_balance).unwrap());
|
||||
assert!(!check_account_for_balance(&rpc_client, &pubkey, account_balance + 1).unwrap());
|
||||
assert_eq!(
|
||||
check_account_for_balance(&rpc_client, &pubkey, 1).unwrap(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
check_account_for_balance(&rpc_client, &pubkey, account_balance).unwrap(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
check_account_for_balance(&rpc_client, &pubkey, account_balance + 1).unwrap(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
1404
cli/src/cli.rs
1404
cli/src/cli.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -19,22 +19,10 @@ use solana_sdk::{
|
||||
};
|
||||
use std::{collections::HashMap, fmt, sync::Arc};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum ForceActivation {
|
||||
No,
|
||||
Almost,
|
||||
Yes,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum FeatureCliCommand {
|
||||
Status {
|
||||
features: Vec<Pubkey>,
|
||||
},
|
||||
Activate {
|
||||
feature: Pubkey,
|
||||
force: ForceActivation,
|
||||
},
|
||||
Status { features: Vec<Pubkey> },
|
||||
Activate { feature: Pubkey },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -70,8 +58,8 @@ impl fmt::Display for CliFeatures {
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
"{:<44} | {:<27} | {}",
|
||||
"Feature", "Status", "Description"
|
||||
"{:<44} {:<40} {}",
|
||||
"Feature", "Description", "Status"
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
@@ -79,15 +67,15 @@ impl fmt::Display for CliFeatures {
|
||||
for feature in &self.features {
|
||||
writeln!(
|
||||
f,
|
||||
"{:<44} | {:<27} | {}",
|
||||
"{:<44} {:<40} {}",
|
||||
feature.id,
|
||||
feature.description,
|
||||
match feature.status {
|
||||
CliFeatureStatus::Inactive => style("inactive".to_string()).red(),
|
||||
CliFeatureStatus::Pending => style("activation pending".to_string()).yellow(),
|
||||
CliFeatureStatus::Active(activation_slot) =>
|
||||
style(format!("active since slot {}", activation_slot)).green(),
|
||||
},
|
||||
feature.description,
|
||||
}
|
||||
)?;
|
||||
}
|
||||
if self.inactive && !self.feature_activation_allowed {
|
||||
@@ -138,13 +126,6 @@ impl FeatureSubCommands for App<'_, '_> {
|
||||
.index(1)
|
||||
.required(true)
|
||||
.help("The signer for the feature to activate"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("force")
|
||||
.long("yolo")
|
||||
.hidden(true)
|
||||
.multiple(true)
|
||||
.help("Override activation sanity checks. Don't use this flag"),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -171,20 +152,13 @@ pub fn parse_feature_subcommand(
|
||||
("activate", Some(matches)) => {
|
||||
let (feature_signer, feature) = signer_of(matches, "feature", wallet_manager)?;
|
||||
let mut signers = vec![default_signer.signer_from_path(matches, wallet_manager)?];
|
||||
|
||||
let force = match matches.occurrences_of("force") {
|
||||
2 => ForceActivation::Yes,
|
||||
1 => ForceActivation::Almost,
|
||||
_ => ForceActivation::No,
|
||||
};
|
||||
|
||||
signers.push(feature_signer.unwrap());
|
||||
let feature = feature.unwrap();
|
||||
|
||||
known_feature(&feature)?;
|
||||
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Feature(FeatureCliCommand::Activate { feature, force }),
|
||||
command: CliCommand::Feature(FeatureCliCommand::Activate { feature }),
|
||||
signers,
|
||||
}
|
||||
}
|
||||
@@ -215,13 +189,11 @@ pub fn process_feature_subcommand(
|
||||
) -> ProcessResult {
|
||||
match feature_subcommand {
|
||||
FeatureCliCommand::Status { features } => process_status(rpc_client, config, features),
|
||||
FeatureCliCommand::Activate { feature, force } => {
|
||||
process_activate(rpc_client, config, *feature, *force)
|
||||
}
|
||||
FeatureCliCommand::Activate { feature } => process_activate(rpc_client, config, *feature),
|
||||
}
|
||||
}
|
||||
|
||||
fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, f64>, ClientError> {
|
||||
fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, u64>, ClientError> {
|
||||
// Validator identity -> feature set
|
||||
let feature_set_map = rpc_client
|
||||
.get_cluster_nodes()?
|
||||
@@ -239,7 +211,7 @@ fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, f6
|
||||
.sum();
|
||||
|
||||
// Sum all active stake by feature set
|
||||
let mut active_stake_by_feature_set: HashMap<u32, u64> = HashMap::new();
|
||||
let mut active_stake_by_feature_set = HashMap::new();
|
||||
for vote_account in vote_accounts.current {
|
||||
if let Some(Some(feature_set)) = feature_set_map.get(&vote_account.node_pubkey) {
|
||||
*active_stake_by_feature_set.entry(*feature_set).or_default() +=
|
||||
@@ -251,15 +223,11 @@ fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, f6
|
||||
}
|
||||
}
|
||||
|
||||
Ok(active_stake_by_feature_set
|
||||
.into_iter()
|
||||
.map(|(feature_set, active_stake)| {
|
||||
(
|
||||
feature_set,
|
||||
active_stake as f64 * 100. / total_active_stake as f64,
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
// Convert active stake to a percentage so the caller doesn't need `total_active_stake`
|
||||
for (_, val) in active_stake_by_feature_set.iter_mut() {
|
||||
*val = *val * 100 / total_active_stake;
|
||||
}
|
||||
Ok(active_stake_by_feature_set)
|
||||
}
|
||||
|
||||
// Feature activation is only allowed when 95% of the active stake is on the current feature set
|
||||
@@ -270,7 +238,7 @@ fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<boo
|
||||
|
||||
let feature_activation_allowed = active_stake_by_feature_set
|
||||
.get(&my_feature_set)
|
||||
.map(|percentage| *percentage >= 95.)
|
||||
.map(|percentage| *percentage >= 95)
|
||||
.unwrap_or(false);
|
||||
|
||||
if !feature_activation_allowed && !quiet {
|
||||
@@ -287,15 +255,15 @@ fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<boo
|
||||
}
|
||||
println!(
|
||||
"{}",
|
||||
style(format!("Tool Feature Set: {}", my_feature_set)).bold()
|
||||
style(format!("Tool Feture Set: {}", my_feature_set)).bold()
|
||||
);
|
||||
println!("{}", style("Cluster Feature Sets and Stakes:").bold());
|
||||
for (feature_set, percentage) in active_stake_by_feature_set.iter() {
|
||||
if *feature_set == 0 {
|
||||
println!(" unknown - {:.2}%", percentage);
|
||||
println!("unknown - {}%", percentage);
|
||||
} else {
|
||||
println!(
|
||||
" {:<10} - {:.2}% {}",
|
||||
"{} - {}% {}",
|
||||
feature_set,
|
||||
percentage,
|
||||
if *feature_set == my_feature_set {
|
||||
@@ -361,14 +329,12 @@ fn process_activate(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
feature_id: Pubkey,
|
||||
force: ForceActivation,
|
||||
) -> ProcessResult {
|
||||
let account = rpc_client
|
||||
.get_multiple_accounts(&[feature_id])?
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
|
||||
if let Some(account) = account {
|
||||
if feature::from_account(&account).is_some() {
|
||||
return Err(format!("{} has already been activated", feature_id).into());
|
||||
@@ -376,13 +342,7 @@ fn process_activate(
|
||||
}
|
||||
|
||||
if !feature_activation_allowed(rpc_client, false)? {
|
||||
match force {
|
||||
ForceActivation::Almost =>
|
||||
return Err("Add force argument once more to override the sanity check to force feature activation ".into()),
|
||||
ForceActivation::Yes => println!("FEATURE ACTIVATION FORCED"),
|
||||
ForceActivation::No =>
|
||||
return Err("Feature activation is not allowed at this time".into()),
|
||||
}
|
||||
return Err("Feature activation is not allowed at this time".into());
|
||||
}
|
||||
|
||||
let rent = rpc_client.get_minimum_balance_for_rent_exemption(Feature::size_of())?;
|
||||
|
@@ -1,22 +1,14 @@
|
||||
use crate::cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult};
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{
|
||||
input_parsers::{pubkeys_of, value_of},
|
||||
input_validators::is_valid_pubkey,
|
||||
keypair::*,
|
||||
};
|
||||
use solana_cli_output::{
|
||||
CliEpochRewardshMetadata, CliInflation, CliKeyedEpochReward, CliKeyedEpochRewards,
|
||||
};
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use console::style;
|
||||
use solana_clap_utils::keypair::*;
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{clock::Epoch, pubkey::Pubkey};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum InflationCliCommand {
|
||||
Show,
|
||||
Rewards(Vec<Pubkey>, Option<Epoch>),
|
||||
}
|
||||
|
||||
pub trait InflationSubCommands {
|
||||
@@ -25,118 +17,73 @@ pub trait InflationSubCommands {
|
||||
|
||||
impl InflationSubCommands for App<'_, '_> {
|
||||
fn inflation_subcommands(self) -> Self {
|
||||
self.subcommand(
|
||||
SubCommand::with_name("inflation")
|
||||
.about("Show inflation information")
|
||||
.subcommand(
|
||||
SubCommand::with_name("rewards")
|
||||
.about("Show inflation rewards for a set of addresses")
|
||||
.arg(pubkey!(
|
||||
Arg::with_name("addresses")
|
||||
.value_name("ADDRESS")
|
||||
.index(1)
|
||||
.multiple(true)
|
||||
.required(true),
|
||||
"Address of account to query for rewards. "
|
||||
))
|
||||
.arg(
|
||||
Arg::with_name("rewards_epoch")
|
||||
.long("rewards-epoch")
|
||||
.takes_value(true)
|
||||
.value_name("EPOCH")
|
||||
.help("Display rewards for specific epoch [default: latest epoch]"),
|
||||
),
|
||||
),
|
||||
)
|
||||
self.subcommand(SubCommand::with_name("inflation").about("Show inflation information"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_inflation_subcommand(
|
||||
matches: &ArgMatches<'_>,
|
||||
_matches: &ArgMatches<'_>,
|
||||
_default_signer: &DefaultSigner,
|
||||
_wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let command = match matches.subcommand() {
|
||||
("rewards", Some(matches)) => {
|
||||
let addresses = pubkeys_of(matches, "addresses").unwrap();
|
||||
let rewards_epoch = value_of(matches, "rewards_epoch");
|
||||
InflationCliCommand::Rewards(addresses, rewards_epoch)
|
||||
}
|
||||
_ => InflationCliCommand::Show,
|
||||
};
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::Inflation(command),
|
||||
command: CliCommand::Inflation(InflationCliCommand::Show),
|
||||
signers: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process_inflation_subcommand(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
_config: &CliConfig,
|
||||
inflation_subcommand: &InflationCliCommand,
|
||||
) -> ProcessResult {
|
||||
match inflation_subcommand {
|
||||
InflationCliCommand::Show => process_show(rpc_client, config),
|
||||
InflationCliCommand::Rewards(ref addresses, rewards_epoch) => {
|
||||
process_rewards(rpc_client, config, addresses, *rewards_epoch)
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(*inflation_subcommand, InflationCliCommand::Show);
|
||||
|
||||
fn process_show(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
|
||||
let governor = rpc_client.get_inflation_governor()?;
|
||||
let current_rate = rpc_client.get_inflation_rate()?;
|
||||
let current_inflation_rate = rpc_client.get_inflation_rate()?;
|
||||
|
||||
let inflation = CliInflation {
|
||||
governor,
|
||||
current_rate,
|
||||
};
|
||||
|
||||
Ok(config.output_format.formatted_string(&inflation))
|
||||
}
|
||||
|
||||
fn process_rewards(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
addresses: &[Pubkey],
|
||||
rewards_epoch: Option<Epoch>,
|
||||
) -> ProcessResult {
|
||||
let rewards = rpc_client
|
||||
.get_inflation_reward(&addresses, rewards_epoch)
|
||||
.map_err(|err| {
|
||||
if let Some(epoch) = rewards_epoch {
|
||||
format!("Rewards not available for epoch {}", epoch)
|
||||
} else {
|
||||
format!("Rewards not available {}", err)
|
||||
}
|
||||
})?;
|
||||
let epoch_schedule = rpc_client.get_epoch_schedule()?;
|
||||
|
||||
let mut epoch_rewards: Vec<CliKeyedEpochReward> = vec![];
|
||||
let epoch_metadata = if let Some(Some(first_reward)) = rewards.iter().find(|&v| v.is_some()) {
|
||||
let (epoch_start_time, epoch_end_time) =
|
||||
crate::stake::get_epoch_boundary_timestamps(rpc_client, first_reward, &epoch_schedule)?;
|
||||
for (reward, address) in rewards.iter().zip(addresses) {
|
||||
let cli_reward = reward.as_ref().and_then(|reward| {
|
||||
crate::stake::make_cli_reward(reward, epoch_start_time, epoch_end_time)
|
||||
});
|
||||
epoch_rewards.push(CliKeyedEpochReward {
|
||||
address: address.to_string(),
|
||||
reward: cli_reward,
|
||||
});
|
||||
}
|
||||
let block_time = rpc_client.get_block_time(first_reward.effective_slot)?;
|
||||
Some(CliEpochRewardshMetadata {
|
||||
epoch: first_reward.epoch,
|
||||
effective_slot: first_reward.effective_slot,
|
||||
block_time,
|
||||
})
|
||||
println!("{}", style("Inflation Governor:").bold());
|
||||
if (governor.initial - governor.terminal).abs() < f64::EPSILON {
|
||||
println!(
|
||||
"Fixed APR: {:>5.2}%",
|
||||
governor.terminal * 100.
|
||||
);
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let cli_rewards = CliKeyedEpochRewards {
|
||||
epoch_metadata,
|
||||
rewards: epoch_rewards,
|
||||
};
|
||||
Ok(config.output_format.formatted_string(&cli_rewards))
|
||||
println!("Initial APR: {:>5.2}%", governor.initial * 100.);
|
||||
println!(
|
||||
"Terminal APR: {:>5.2}%",
|
||||
governor.terminal * 100.
|
||||
);
|
||||
println!("Rate reduction per year: {:>5.2}%", governor.taper * 100.);
|
||||
}
|
||||
if governor.foundation_term > 0. {
|
||||
println!("Foundation percentage: {:>5.2}%", governor.foundation);
|
||||
println!(
|
||||
"Foundation term: {:.1} years",
|
||||
governor.foundation_term
|
||||
);
|
||||
}
|
||||
|
||||
println!(
|
||||
"\n{}",
|
||||
style(format!(
|
||||
"Inflation for Epoch {}:",
|
||||
current_inflation_rate.epoch
|
||||
))
|
||||
.bold()
|
||||
);
|
||||
println!(
|
||||
"Total APR: {:>5.2}%",
|
||||
current_inflation_rate.total * 100.
|
||||
);
|
||||
println!(
|
||||
"Staking APR: {:>5.2}%",
|
||||
current_inflation_rate.validator * 100.
|
||||
);
|
||||
println!(
|
||||
"Foundation APR: {:>5.2}%",
|
||||
current_inflation_rate.foundation * 100.
|
||||
);
|
||||
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
macro_rules! ACCOUNT_STRING {
|
||||
() => {
|
||||
r#", one of:
|
||||
@@ -10,6 +9,7 @@ macro_rules! ACCOUNT_STRING {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
macro_rules! pubkey {
|
||||
($arg:expr, $help:expr) => {
|
||||
$arg.takes_value(true)
|
||||
@@ -25,9 +25,8 @@ pub mod cli;
|
||||
pub mod cluster_query;
|
||||
pub mod feature;
|
||||
pub mod inflation;
|
||||
pub mod memo;
|
||||
pub mod nonce;
|
||||
pub mod program;
|
||||
pub mod send_tpu;
|
||||
pub mod spend_utils;
|
||||
pub mod stake;
|
||||
pub mod test_utils;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user