Compare commits
588 Commits
Author | SHA1 | Date | |
---|---|---|---|
347ff770bd | |||
41ab35fc9b | |||
9cca79090c | |||
9541973377 | |||
0bfe517cfb | |||
bd8e3182ae | |||
e5460f97ad | |||
0de08eab3b | |||
80d6a5aa9d | |||
7f2977a3b0 | |||
936ff7424e | |||
23e114e077 | |||
09445506e2 | |||
6268fa32c6 | |||
7df2525972 | |||
90543940bd | |||
dd21f20555 | |||
20a282f03f | |||
b5894515e8 | |||
f6bf48e3c8 | |||
63224bd11e | |||
4790b0a5cd | |||
8beaa43e6e | |||
151d9bd28c | |||
0c2faa3168 | |||
525421a669 | |||
2e5ad16b62 | |||
87494812a7 | |||
b555845ca2 | |||
98867430d5 | |||
d7a8420d9a | |||
ef3c33ad06 | |||
02762ff785 | |||
07336e3e43 | |||
e5a76572d9 | |||
0d3756b4d8 | |||
a6b346d876 | |||
4276591eb9 | |||
127e7407e4 | |||
11ab894256 | |||
f10f57e89f | |||
592a13b5f8 | |||
5702744399 | |||
c7629d595b | |||
cfc824d38d | |||
492a02d737 | |||
624f9790bd | |||
a23fb497ec | |||
6107de87c0 | |||
0c0b65e9ab | |||
9b04b634bd | |||
aca3534e89 | |||
b4bb062a2e | |||
194a07862f | |||
e043825de2 | |||
3e91fa313e | |||
9ac4b11185 | |||
c32bd40aa4 | |||
7e480df9fa | |||
d1da10f512 | |||
e55d5385bd | |||
708a45e5ed | |||
afd5b9f09b | |||
a9a7f21f51 | |||
f69ee59e21 | |||
aad1e4ee4a | |||
97a58d7320 | |||
ac4722afd7 | |||
9ded0bdc03 | |||
b2cee2753d | |||
09843a28d9 | |||
72ff8973a2 | |||
cc7342a8ef | |||
ffe2f96a58 | |||
fb08b41513 | |||
579575fe84 | |||
0bb95b470f | |||
4e6d175697 | |||
173ca7b448 | |||
fc36a0c5ba | |||
3317a14bd5 | |||
64a754f610 | |||
734a4e39f7 | |||
f356a05e96 | |||
c3c4ce48af | |||
452663a99a | |||
c0d27503ce | |||
57833a36e3 | |||
e58365a160 | |||
a5f8635fdc | |||
de8df24203 | |||
ea1312ab0b | |||
e0119e7de7 | |||
9c596cfd6c | |||
e4032ec87f | |||
e3bab5a987 | |||
5f426ee2dc | |||
a76426514e | |||
4c48740647 | |||
d9c7f78717 | |||
305fbe38e9 | |||
a6b7cc202a | |||
3584a764f9 | |||
eed62bc408 | |||
6a334a8bef | |||
be05c8b121 | |||
9bd3773934 | |||
999f81c56d | |||
1967b90489 | |||
22ca850ce7 | |||
824daab8f6 | |||
191e51b01d | |||
0de081c776 | |||
9a151259b2 | |||
3da1f67d40 | |||
39a2fbe2bf | |||
7a08d47588 | |||
3f3f3bb443 | |||
e2f893d743 | |||
1a66968126 | |||
437f0bb8c6 | |||
16f8fcd711 | |||
f511625887 | |||
4c3bfcaedf | |||
f44eeb4165 | |||
29730b5a19 | |||
6e214bbc04 | |||
a79ba512a8 | |||
e777436cfd | |||
b4f5627a5b | |||
d4790be689 | |||
58a9639810 | |||
74b13605c0 | |||
9fbc03d517 | |||
7f1368e792 | |||
c60e21b900 | |||
30781bb7b1 | |||
2114864626 | |||
fc29ee7001 | |||
166fa33433 | |||
2ba5828a75 | |||
98f5f58975 | |||
5f7258640b | |||
e9d04b5517 | |||
b856b99487 | |||
d3672ca23b | |||
6242809a07 | |||
1958bb1169 | |||
5e2de5648d | |||
16ded2115c | |||
b775e8748c | |||
9d00220d88 | |||
a00cbb55b9 | |||
7ae3b55dde | |||
280437bad2 | |||
722879b96c | |||
c22b83aa6c | |||
9387ee6f3b | |||
211a42fb98 | |||
247fa529b0 | |||
b3bfe7b6ad | |||
109c15c97c | |||
32b05e7ba0 | |||
1da88658c3 | |||
77d8468df2 | |||
163efc3bdf | |||
e7aa6cd5ea | |||
eb12d29683 | |||
979e07501a | |||
297c08310f | |||
f0afbf4948 | |||
c82c750091 | |||
a7d1223583 | |||
b550f5bc19 | |||
5d341d9bf4 | |||
82ba19488e | |||
fa7067950a | |||
d69f09f152 | |||
0b510ac9b4 | |||
96e587c83e | |||
fc6e7a5e2a | |||
087c43ad33 | |||
7d67f5b18c | |||
80f5127dfa | |||
bb5a69aa4d | |||
23e6ff3e94 | |||
5e0ca65100 | |||
6500fed8b7 | |||
a80ac11b68 | |||
5f03a17a6e | |||
a52a22f558 | |||
dfd09a5c13 | |||
37eb205b54 | |||
e4a68f7d99 | |||
f0be7032cb | |||
241fb938c1 | |||
11190259d5 | |||
7f9d5ac383 | |||
abfae5d46a | |||
081f1cd118 | |||
6f31373b21 | |||
b231fb2c18 | |||
e255c85bef | |||
e5bb1597a4 | |||
a97d89fb5a | |||
1f1dd58c78 | |||
b21ce376fb | |||
451d974f26 | |||
f254bf85eb | |||
8b80628b38 | |||
2ac95a3ebc | |||
ddef2fb7fa | |||
de4d2e640e | |||
c857467262 | |||
671fb3519d | |||
9aed0b0952 | |||
767c89526a | |||
2bfe545438 | |||
804a284a52 | |||
51e5189ffb | |||
5216f51ff2 | |||
0c55add96b | |||
291f81d5b0 | |||
5c8a878f1b | |||
7148aaa30c | |||
7a3c4c184f | |||
bc5f434e48 | |||
569edbb241 | |||
abf2d71f4c | |||
1a8b57fcd0 | |||
723c03dfbd | |||
0a1fcfa08b | |||
8820933287 | |||
460c643f8e | |||
65600f9a1f | |||
ef61dc9780 | |||
477e5d4bff | |||
d54632da00 | |||
26b420bd39 | |||
c3dda3ce0c | |||
c527e1f2e5 | |||
135f47b6be | |||
6656b3965f | |||
3068572bb9 | |||
f48236837c | |||
59beb8e548 | |||
543f7e7ec1 | |||
efe563201f | |||
603cae4a5c | |||
73fb9695bc | |||
1aec2102d4 | |||
99012f022e | |||
20afb912cd | |||
563231132f | |||
32ec9147bb | |||
4be8842925 | |||
b1ef9045ec | |||
dbe4d87e60 | |||
d2efa3aa15 | |||
ccd2c6cc13 | |||
e976b1547a | |||
03ac807756 | |||
067871cc39 | |||
e9ceb99460 | |||
cd994f0162 | |||
01e4d0a1e9 | |||
7f0f43cb28 | |||
d0bf97d25d | |||
c3056734e3 | |||
5e2b9e595d | |||
9be3e00546 | |||
0c2dcd759c | |||
b711476811 | |||
e4fe7dfbbd | |||
40e62c60d3 | |||
a93d37b804 | |||
65e6df2b0d | |||
f02bd10d4a | |||
d7d8a751d9 | |||
f6f4193d4d | |||
fe0077d88a | |||
8016f61ce8 | |||
0b6366da9c | |||
d567a62cc7 | |||
eccea2b1ea | |||
bfd93cc13f | |||
b21c89e494 | |||
253b68abf1 | |||
49034b8016 | |||
3838fb62d4 | |||
9f267fc5e7 | |||
5a82265650 | |||
77d2ed95ff | |||
858ca752e2 | |||
fea0bd234c | |||
7af7d5f22c | |||
de4cccd977 | |||
9f74136632 | |||
21484acc20 | |||
36ad03af1f | |||
e255ee52b1 | |||
972540907b | |||
cfeed09f1f | |||
dadebb2bba | |||
169403a15e | |||
e2a874370b | |||
baf7713744 | |||
573304cf73 | |||
ec6d5933de | |||
30b815e7bc | |||
f463ebfde2 | |||
ba0aa706e4 | |||
eacf9209f7 | |||
ba12a14494 | |||
4e60f95854 | |||
a7193ce834 | |||
f12a467177 | |||
45a92337f4 | |||
bfa6e9bdf6 | |||
a7d9a52690 | |||
4b3391f1d8 | |||
19b203f344 | |||
6150faafb6 | |||
49e608295e | |||
636be95e2a | |||
0db5a74bb9 | |||
08fd4905db | |||
3610b6f31c | |||
b35f35a7e8 | |||
790a6b7550 | |||
30d2e15945 | |||
7b3c7a075a | |||
f534698618 | |||
fe1347b441 | |||
8f6c01f0f8 | |||
066ff36175 | |||
5da9e7cb8a | |||
09b68f2fbb | |||
d9fcd84bc2 | |||
86703384dc | |||
580b0859e8 | |||
5d8c5254b0 | |||
ea83292daa | |||
f1c3e6dc36 | |||
bdd19c09d1 | |||
95cbfce900 | |||
a404a9d802 | |||
15cd1283e8 | |||
512a193674 | |||
de37795ca1 | |||
cb7871347d | |||
b4b9ea7771 | |||
62b7bf5365 | |||
91c57cd70d | |||
cb35fc185b | |||
cca9009f23 | |||
d8d73ff56c | |||
34504797b4 | |||
1767e4fbde | |||
39515cae5e | |||
116d67e1e3 | |||
08bda35fd6 | |||
ba1d0927e6 | |||
555df9f96c | |||
99166a4a59 | |||
92534849db | |||
5ba8b4884b | |||
cb878f2ea8 | |||
b1d5bf30d2 | |||
481c60e287 | |||
86242dc3ba | |||
71899deb53 | |||
7e2e0d4a86 | |||
d4cc7c6b66 | |||
b62349f081 | |||
d16638dc90 | |||
b97fc31fcd | |||
4378634970 | |||
10e12d14e1 | |||
c5cfbee853 | |||
c276670a94 | |||
676da0a836 | |||
0021cf924f | |||
d593ee187c | |||
a07bfc2d76 | |||
f0e9843dd4 | |||
a2f643e7c7 | |||
7ebaf1c192 | |||
6a61e7a01e | |||
1c23f135bf | |||
3c67f71695 | |||
d380b9cef7 | |||
7415821156 | |||
4a6c3a9331 | |||
0cd1cce588 | |||
f762b4a730 | |||
08f7f2546e | |||
cb701fbbd9 | |||
231ff7d70d | |||
a154414e65 | |||
673c679523 | |||
65a7621b17 | |||
c9da25836a | |||
b48dd58fda | |||
40f32fd37d | |||
fef4100f5f | |||
68bf58aac0 | |||
5677662d61 | |||
6755fd0c96 | |||
dfbe38b859 | |||
480a35d678 | |||
e6b53c262b | |||
e127631f8d | |||
2d246581ed | |||
952a5b11c1 | |||
c49a8cb67c | |||
733a1c85cf | |||
c8f7719c9e | |||
c89f5e28b7 | |||
38e4c34d18 | |||
afa7343bc2 | |||
8ea584e01f | |||
239dc9b0b7 | |||
6e6a55b7d6 | |||
815bad8a6c | |||
74f813574f | |||
07648f43db | |||
99f0d29e65 | |||
87825f3beb | |||
8e38f90e54 | |||
d72c90e475 | |||
459ae81655 | |||
a2ce22f11b | |||
44ad4a1ecd | |||
fcd8dd75c5 | |||
ca262fdeb9 | |||
3d6bb95932 | |||
e5d36fcfb3 | |||
061965e291 | |||
e56681a2f6 | |||
8cf23903ce | |||
7ac2aae730 | |||
6f5b9331bd | |||
a04375e204 | |||
7b19d26a6e | |||
3c3a3f0b50 | |||
0367b32533 | |||
09392ee562 | |||
1a848e22ea | |||
3d8cadebc0 | |||
47b8d518c5 | |||
011d2dd41d | |||
bdfffd0151 | |||
722cebc4c3 | |||
771b98a168 | |||
1b02ec4f6e | |||
aae51925c1 | |||
00626fbf4c | |||
14ffc05fd4 | |||
6e5c9a1b1c | |||
1276b92462 | |||
89241cedba | |||
0e3e3a03cc | |||
25fe93e9fb | |||
4440a8d9fa | |||
b737e562ab | |||
9cb32b2105 | |||
f46ad1b7b7 | |||
b15603b4eb | |||
c7267799ce | |||
ed0a083cd9 | |||
484bd48b35 | |||
ae73cc8d05 | |||
fc59a08f0e | |||
15da7968c5 | |||
b58a6e2b6e | |||
ec15ea079f | |||
4b5a05bf38 | |||
7dd7141307 | |||
e5175c843d | |||
d5ff64b0d7 | |||
0fbdc7e152 | |||
49aca9ecd8 | |||
fcc147b4f2 | |||
c455d1b1c5 | |||
e9b29fc697 | |||
fdea6fad26 | |||
a4bc31341a | |||
4af797c0a2 | |||
a1e06df4a8 | |||
1f2480fd9f | |||
8587bd0d69 | |||
0063a58e95 | |||
9aeb3bc5d6 | |||
97665b977e | |||
c45ed29cf4 | |||
635afbabff | |||
98afdad1dd | |||
c085b94b43 | |||
a53946c485 | |||
f6de92c346 | |||
46f9822d62 | |||
6dad84d228 | |||
3582607aa0 | |||
f051077350 | |||
ffd6f3e6bf | |||
c6b2eb07ee | |||
7a3e1f9826 | |||
8a690b6cf7 | |||
8688efa89b | |||
3b047e5b99 | |||
3cddc731b2 | |||
1d29a583c6 | |||
b5335edb35 | |||
abee1e83eb | |||
6c5be574c8 | |||
c1f993d2fc | |||
e2ddb2f0ea | |||
f3faba5ca9 | |||
3a6fd91739 | |||
2d2b3d8287 | |||
6e47b88399 | |||
941e56c6c7 | |||
d1adc2a446 | |||
02da7dfedf | |||
eb0fd3625a | |||
b87e606626 | |||
1c91376f78 | |||
10067ad07b | |||
eb76289107 | |||
8926736e1c | |||
bf4c169703 | |||
0020e43476 | |||
a9a2c76221 | |||
4754b4e871 | |||
52ffb9a64a | |||
bd0b1503c6 | |||
10e7fa40ac | |||
198ed407b7 | |||
d96af2dd23 | |||
192cca8f98 | |||
ee716e1c55 | |||
6dd3c7c2dd | |||
582b4c9edf | |||
f15add2a74 | |||
74d48910e2 | |||
c53e8ee3ad | |||
c5e5fedc47 | |||
b9929dcd67 | |||
554a158443 | |||
b7fa4b7ee1 | |||
fd44cee8cc | |||
c6a362cce2 | |||
252180c244 | |||
e02b4e698e | |||
4811afe8eb | |||
bc4568b10f | |||
d59c131e90 | |||
825027f9f7 | |||
9b8f0bee99 | |||
fc13c1d654 | |||
a57758e9c9 | |||
564590462a | |||
269f6af97e | |||
57b8a59632 | |||
4289f52d2b | |||
573f68620b | |||
4bfe64688b | |||
50034848a5 | |||
981294cbc6 | |||
ff728e5e56 | |||
9aaf41bef2 | |||
271eec656c | |||
13d071607f | |||
ffe35d9a10 | |||
bb2fb07b39 | |||
85fc51dc61 | |||
0276b6c4c2 | |||
c481e4fe7f | |||
76a3b3ad11 | |||
356c663e88 | |||
015bbc1e12 | |||
454a9f3175 | |||
485b3d64a1 | |||
5d170d83c0 | |||
f54d8ea3ab | |||
ef9f54b3d4 | |||
8d0b102b44 |
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:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:3K68mE38LJ2RB98VWmjuNLFBNn1XTGR4:cR4r05/TOZQKmEZp1v4CSgUJtC6QJiOaL85QjXW0qZ061fMnsBA8AtAPMDoDq4WCGOZM1A==]"
|
||||
"CODECOV_TOKEN": "EJ[1:KToenD1Sr3w82lHGxz1n+j3hwNlLk/1pYrjZHlvY6kE=:hN1Q25omtJ+4yYVn+qzIsPLKT3O6J9XN:DMLNLXi/pkWgvwF6gNIcNF222sgsRR9LnwLZYj0P0wGj7q6w8YQnd1Rskj+sRroI/z5pQg==]"
|
||||
}
|
||||
}
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,7 +1,3 @@
|
||||
/docs/html/
|
||||
/docs/src/tests.ok
|
||||
/docs/src/cli/usage.md
|
||||
/docs/src/.gitbook/assets/*.svg
|
||||
/farf/
|
||||
/solana-release/
|
||||
/solana-release.tar.bz2
|
||||
|
1029
Cargo.lock
generated
1029
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -28,6 +28,7 @@ members = [
|
||||
"local-cluster",
|
||||
"logger",
|
||||
"log-analyzer",
|
||||
"merkle-root-bench",
|
||||
"merkle-tree",
|
||||
"stake-o-matic",
|
||||
"storage-bigtable",
|
||||
|
155
SECURITY.md
Normal file
155
SECURITY.md
Normal file
@ -0,0 +1,155 @@
|
||||
# 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 bounty 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.
|
||||
|
||||
Notes:
|
||||
* All locked tokens can be staked during the lockup period
|
||||
|
||||
<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,10 +1,11 @@
|
||||
[package]
|
||||
name = "solana-account-decoder"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
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"
|
||||
|
||||
@ -15,14 +16,14 @@ bs58 = "0.3.1"
|
||||
bv = "0.11.1"
|
||||
Inflector = "0.11.4"
|
||||
lazy_static = "1.4.0"
|
||||
serde = "1.0.112"
|
||||
serde = "1.0.118"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-config-program = { path = "../programs/config", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.0" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.0.0", features = ["no-entrypoint"] }
|
||||
solana-config-program = { path = "../programs/config", version = "=1.5.20" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.5.20" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "=1.5.20" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.5.20" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.1.0", features = ["no-entrypoint"] }
|
||||
thiserror = "1.0"
|
||||
zstd = "0.5.1"
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
#![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;
|
||||
@ -22,6 +24,7 @@ use {
|
||||
};
|
||||
|
||||
pub type StringAmount = String;
|
||||
pub type StringDecimals = String;
|
||||
|
||||
/// A duplicate representation of an Account for pretty JSON serialization
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::{
|
||||
parse_bpf_loader::parse_bpf_upgradeable_loader,
|
||||
parse_config::parse_config,
|
||||
parse_nonce::parse_nonce,
|
||||
parse_stake::parse_stake,
|
||||
@ -13,6 +14,7 @@ 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 = solana_stake_program::id();
|
||||
static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
|
||||
@ -21,6 +23,10 @@ lazy_static! {
|
||||
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);
|
||||
@ -60,6 +66,7 @@ pub struct ParsedAccount {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ParsableAccount {
|
||||
BpfUpgradeableLoader,
|
||||
Config,
|
||||
Nonce,
|
||||
SplToken,
|
||||
@ -84,6 +91,9 @@ pub fn parse_account_data(
|
||||
.ok_or(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 => {
|
||||
@ -118,7 +128,7 @@ mod test {
|
||||
|
||||
let vote_state = VoteState::default();
|
||||
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
|
||||
let versioned = VoteStateVersions::Current(Box::new(vote_state));
|
||||
let versioned = VoteStateVersions::new_current(vote_state);
|
||||
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
|
||||
let parsed = parse_account_data(
|
||||
&account_pubkey,
|
||||
|
181
account-decoder/src/parse_bpf_loader.rs
Normal file
181
account-decoder/src/parse_bpf_loader.rs
Normal file
@ -0,0 +1,181 @@
|
||||
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),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -9,7 +9,13 @@ 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 {
|
||||
State::Uninitialized => Ok(UiNonceState::Uninitialized),
|
||||
// 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::Initialized(data) => Ok(UiNonceState::Initialized(UiNonceData {
|
||||
authority: data.authority.to_string(),
|
||||
blockhash: data.blockhash.to_string(),
|
||||
|
@ -214,13 +214,13 @@ pub struct UiStakeHistoryEntry {
|
||||
mod test {
|
||||
use super::*;
|
||||
use solana_sdk::{
|
||||
account::create_account, fee_calculator::FeeCalculator, hash::Hash,
|
||||
account::create_account_for_test, fee_calculator::FeeCalculator, hash::Hash,
|
||||
sysvar::recent_blockhashes::IterItem,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_parse_sysvars() {
|
||||
let clock_sysvar = create_account(&Clock::default(), 1);
|
||||
let clock_sysvar = create_account_for_test(&Clock::default());
|
||||
assert_eq!(
|
||||
parse_sysvar(&clock_sysvar.data, &sysvar::clock::id()).unwrap(),
|
||||
SysvarAccountType::Clock(UiClock::default()),
|
||||
@ -233,13 +233,13 @@ mod test {
|
||||
first_normal_epoch: 1,
|
||||
first_normal_slot: 12,
|
||||
};
|
||||
let epoch_schedule_sysvar = create_account(&epoch_schedule, 1);
|
||||
let epoch_schedule_sysvar = create_account_for_test(&epoch_schedule);
|
||||
assert_eq!(
|
||||
parse_sysvar(&epoch_schedule_sysvar.data, &sysvar::epoch_schedule::id()).unwrap(),
|
||||
SysvarAccountType::EpochSchedule(epoch_schedule),
|
||||
);
|
||||
|
||||
let fees_sysvar = create_account(&Fees::default(), 1);
|
||||
let fees_sysvar = create_account_for_test(&Fees::default());
|
||||
assert_eq!(
|
||||
parse_sysvar(&fees_sysvar.data, &sysvar::fees::id()).unwrap(),
|
||||
SysvarAccountType::Fees(UiFees::default()),
|
||||
@ -252,7 +252,7 @@ mod test {
|
||||
let recent_blockhashes: RecentBlockhashes = vec![IterItem(0, &hash, &fee_calculator)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let recent_blockhashes_sysvar = create_account(&recent_blockhashes, 1);
|
||||
let recent_blockhashes_sysvar = create_account_for_test(&recent_blockhashes);
|
||||
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(&rent, 1);
|
||||
let rent_sysvar = create_account_for_test(&rent);
|
||||
assert_eq!(
|
||||
parse_sysvar(&rent_sysvar.data, &sysvar::rent::id()).unwrap(),
|
||||
SysvarAccountType::Rent(rent.into()),
|
||||
);
|
||||
|
||||
let rewards_sysvar = create_account(&Rewards::default(), 1);
|
||||
let rewards_sysvar = create_account_for_test(&Rewards::default());
|
||||
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(&slot_hashes, 1);
|
||||
let slot_hashes_sysvar = create_account_for_test(&slot_hashes);
|
||||
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(&slot_history, 1);
|
||||
let slot_history_sysvar = create_account_for_test(&slot_history);
|
||||
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(&stake_history, 1);
|
||||
let stake_history_sysvar = create_account_for_test(&stake_history);
|
||||
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,
|
||||
StringAmount, StringDecimals,
|
||||
};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use spl_token_v2_0::{
|
||||
@ -158,46 +158,66 @@ 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: f64,
|
||||
pub ui_amount: Option<f64>,
|
||||
pub decimals: u8,
|
||||
pub amount: StringAmount,
|
||||
pub ui_amount_string: StringDecimals,
|
||||
}
|
||||
|
||||
impl UiTokenAmount {
|
||||
pub fn real_number_string(&self) -> String {
|
||||
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()
|
||||
}
|
||||
real_number_string(
|
||||
u64::from_str(&self.amount).unwrap_or_default(),
|
||||
self.decimals as u8,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn real_number_string_trimmed(&self) -> String {
|
||||
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()
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount {
|
||||
// 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;
|
||||
let amount_decimals = 10_usize
|
||||
.checked_pow(decimals as u32)
|
||||
.map(|dividend| amount as f64 / dividend as f64);
|
||||
UiTokenAmount {
|
||||
ui_amount: amount_decimals,
|
||||
decimals,
|
||||
amount: amount.to_string(),
|
||||
ui_amount_string: real_number_string_trimmed(amount, decimals),
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,9 +273,10 @@ mod test {
|
||||
mint: mint_pubkey.to_string(),
|
||||
owner: owner_pubkey.to_string(),
|
||||
token_amount: UiTokenAmount {
|
||||
ui_amount: 0.42,
|
||||
ui_amount: Some(0.42),
|
||||
decimals: 2,
|
||||
amount: "42".to_string()
|
||||
amount: "42".to_string(),
|
||||
ui_amount_string: "0.42".to_string()
|
||||
},
|
||||
delegate: None,
|
||||
state: UiAccountState::Initialized,
|
||||
@ -336,17 +357,87 @@ 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.real_number_string(), "1");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "1");
|
||||
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");
|
||||
let token_amount = token_amount_to_ui_amount(1, 9);
|
||||
assert_eq!(&token_amount.real_number_string(), "0.000000001");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "0.000000001");
|
||||
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");
|
||||
let token_amount = token_amount_to_ui_amount(1_000_000_000, 9);
|
||||
assert_eq!(&token_amount.real_number_string(), "1.000000000");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "1");
|
||||
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");
|
||||
let token_amount = token_amount_to_ui_amount(1_234_567_890, 3);
|
||||
assert_eq!(&token_amount.real_number_string(), "1234567.890");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "1234567.89");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ 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::Current(Box::new(vote_state));
|
||||
let versioned = VoteStateVersions::new_current(vote_state);
|
||||
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
|
||||
let expected_vote_state = UiVoteState {
|
||||
node_pubkey: Pubkey::default().to_string(),
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-accounts-bench"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -11,11 +11,11 @@ publish = false
|
||||
[dependencies]
|
||||
log = "0.4.11"
|
||||
rayon = "1.4.0"
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.0" }
|
||||
solana-measure = { path = "../measure", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.5.20" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.5.20" }
|
||||
solana-measure = { path = "../measure", version = "=1.5.20" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.5.20" }
|
||||
solana-version = { path = "../version", version = "=1.5.20" }
|
||||
rand = "0.7.0"
|
||||
clap = "2.33.1"
|
||||
crossbeam-channel = "0.4"
|
||||
|
@ -1,14 +1,15 @@
|
||||
#![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, Accounts},
|
||||
accounts::{create_test_accounts, update_accounts_bench, Accounts},
|
||||
accounts_index::Ancestors,
|
||||
};
|
||||
use solana_sdk::{genesis_config::ClusterType, pubkey::Pubkey};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::{collections::HashSet, env, fs, path::PathBuf};
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
@ -53,10 +54,12 @@ 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(vec![path], &ClusterType::Testnet);
|
||||
let accounts =
|
||||
Accounts::new_with_config(vec![path], &ClusterType::Testnet, HashSet::new(), false);
|
||||
println!("Creating {} accounts", num_accounts);
|
||||
let mut create_time = Measure::start("create accounts");
|
||||
let pubkeys: Vec<_> = (0..num_slots)
|
||||
@ -85,6 +88,8 @@ fn main() {
|
||||
ancestors.insert(i as u64, i - 1);
|
||||
accounts.add_root(i as u64);
|
||||
}
|
||||
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");
|
||||
@ -92,19 +97,43 @@ fn main() {
|
||||
time.stop();
|
||||
println!("{}", time);
|
||||
for slot in 0..num_slots {
|
||||
update_accounts(&accounts, &pubkeys, ((x + 1) * num_slots + slot) as u64);
|
||||
update_accounts_bench(&accounts, &pubkeys, ((x + 1) * num_slots + slot) as u64);
|
||||
accounts.add_root((x * num_slots + slot) as u64);
|
||||
}
|
||||
} else {
|
||||
let mut pubkeys: Vec<Pubkey> = vec![];
|
||||
let mut time = Measure::start("hash");
|
||||
let hash = accounts
|
||||
.accounts_db
|
||||
.update_accounts_hash(0, &ancestors, true)
|
||||
.0;
|
||||
let results = accounts.accounts_db.update_accounts_hash(0, &ancestors);
|
||||
time.stop();
|
||||
println!("hash: {} {}", hash, time);
|
||||
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,
|
||||
);
|
||||
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
|
||||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -14,16 +14,16 @@ crossbeam-channel = "0.4"
|
||||
log = "0.4.11"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.4.0"
|
||||
solana-core = { path = "../core", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
|
||||
solana-streamer = { path = "../streamer", version = "1.5.0" }
|
||||
solana-perf = { path = "../perf", version = "1.5.0" }
|
||||
solana-ledger = { path = "../ledger", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.0" }
|
||||
solana-measure = { path = "../measure", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-core = { path = "../core", version = "=1.5.20" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.5.20" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.5.20" }
|
||||
solana-perf = { path = "../perf", version = "=1.5.20" }
|
||||
solana-ledger = { path = "../ledger", version = "=1.5.20" }
|
||||
solana-logger = { path = "../logger", version = "=1.5.20" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.5.20" }
|
||||
solana-measure = { path = "../measure", version = "=1.5.20" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.5.20" }
|
||||
solana-version = { path = "../version", version = "=1.5.20" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use clap::{crate_description, crate_name, value_t, App, Arg};
|
||||
use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
@ -18,7 +19,7 @@ use solana_ledger::{
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_runtime::{
|
||||
accounts_background_service::ABSRequestSender, bank::Bank, bank_forks::BankForks,
|
||||
accounts_background_service::AbsRequestSender, bank::Bank, bank_forks::BankForks,
|
||||
};
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
@ -325,7 +326,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, &AbsRequestSender::default(), None);
|
||||
root += 1;
|
||||
}
|
||||
debug!(
|
||||
|
@ -1,26 +1,30 @@
|
||||
[package]
|
||||
name = "solana-banks-client"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
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]
|
||||
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.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-banks-interface = { path = "../banks-interface", version = "=1.5.20" }
|
||||
solana-program = { path = "../sdk/program", version = "=1.5.20" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.5.20" }
|
||||
tarpc = { version = "0.23.0", features = ["full"] }
|
||||
tokio = { version = "0.3", features = ["full"] }
|
||||
tokio = { version = "0.3.5", features = ["full"] }
|
||||
tokio-serde = { version = "0.6", features = ["bincode"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-runtime = { path = "../runtime", version = "1.5.0" }
|
||||
solana-banks-server = { path = "../banks-server", version = "1.5.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.5.20" }
|
||||
solana-banks-server = { path = "../banks-server", version = "=1.5.20" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@ -5,19 +5,18 @@
|
||||
//! 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 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,
|
||||
sysvar,
|
||||
transaction::{self, Transaction},
|
||||
transport,
|
||||
};
|
||||
@ -122,7 +121,7 @@ impl BanksClient {
|
||||
pub fn get_fees(
|
||||
&mut self,
|
||||
) -> impl Future<Output = io::Result<(FeeCalculator, Hash, Slot)>> + '_ {
|
||||
self.get_fees_with_commitment_and_context(context::current(), CommitmentLevel::Root)
|
||||
self.get_fees_with_commitment_and_context(context::current(), CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the cluster rent
|
||||
@ -196,7 +195,7 @@ impl BanksClient {
|
||||
/// 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::Root)
|
||||
self.get_slot_with_context(context::current(), CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the account at the given address at the slot corresponding to the given
|
||||
@ -218,6 +217,33 @@ impl BanksClient {
|
||||
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(
|
||||
@ -289,7 +315,10 @@ pub async fn start_tcp_client<T: ToSocketAddrs>(addr: T) -> io::Result<BanksClie
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_banks_server::banks_server::start_local_server;
|
||||
use solana_runtime::{bank::Bank, bank_forks::BankForks, genesis_utils::create_genesis_config};
|
||||
use solana_runtime::{
|
||||
bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache,
|
||||
genesis_utils::create_genesis_config,
|
||||
};
|
||||
use solana_sdk::{message::Message, signature::Signer, system_instruction};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use tarpc::transport;
|
||||
@ -308,9 +337,12 @@ mod tests {
|
||||
// `runtime.block_on()` just once, to run all the async code.
|
||||
|
||||
let genesis = create_genesis_config(10);
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::new(
|
||||
&genesis.genesis_config,
|
||||
))));
|
||||
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 bob_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let mint_pubkey = genesis.mint_keypair.pubkey();
|
||||
@ -318,7 +350,7 @@ mod tests {
|
||||
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
||||
|
||||
Runtime::new()?.block_on(async {
|
||||
let client_transport = start_local_server(&bank_forks).await;
|
||||
let client_transport = start_local_server(bank_forks, block_commitment_cache).await;
|
||||
let mut banks_client = start_client(client_transport).await?;
|
||||
|
||||
let recent_blockhash = banks_client.get_recent_blockhash().await?;
|
||||
@ -336,9 +368,12 @@ mod tests {
|
||||
// server-side functionality is available to the client.
|
||||
|
||||
let genesis = create_genesis_config(10);
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::new(
|
||||
&genesis.genesis_config,
|
||||
))));
|
||||
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 mint_pubkey = &genesis.mint_keypair.pubkey();
|
||||
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
||||
@ -346,7 +381,7 @@ mod tests {
|
||||
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
||||
|
||||
Runtime::new()?.block_on(async {
|
||||
let client_transport = start_local_server(&bank_forks).await;
|
||||
let client_transport = start_local_server(bank_forks, block_commitment_cache).await;
|
||||
let mut banks_client = start_client(client_transport).await?;
|
||||
let (_, recent_blockhash, last_valid_slot) = banks_client.get_fees().await?;
|
||||
let transaction = Transaction::new(&[&genesis.mint_keypair], message, recent_blockhash);
|
||||
|
@ -1,21 +1,22 @@
|
||||
[package]
|
||||
name = "solana-banks-interface"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
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.112", features = ["derive"] }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
serde = { version = "1.0.118", features = ["derive"] }
|
||||
solana-sdk = { path = "../sdk", version = "=1.5.20" }
|
||||
tarpc = { version = "0.23.0", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.3", features = ["full"] }
|
||||
tokio = { version = "0.3.5", features = ["full"] }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@ -10,11 +10,19 @@ 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,11 +1,12 @@
|
||||
[package]
|
||||
name = "solana-banks-server"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
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]
|
||||
@ -13,10 +14,10 @@ bincode = "1.3.1"
|
||||
futures = "0.3"
|
||||
log = "0.4.11"
|
||||
mio = "0.7.6"
|
||||
solana-banks-interface = { path = "../banks-interface", version = "1.5.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.0" }
|
||||
solana-banks-interface = { path = "../banks-interface", version = "=1.5.20" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.5.20" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.5.20" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.5.20" }
|
||||
tarpc = { version = "0.23.0", features = ["full"] }
|
||||
tokio = { version = "0.3", features = ["full"] }
|
||||
tokio-serde = { version = "0.6", features = ["bincode"] }
|
||||
|
@ -4,7 +4,9 @@ use futures::{
|
||||
future,
|
||||
prelude::stream::{self, StreamExt},
|
||||
};
|
||||
use solana_banks_interface::{Banks, BanksRequest, BanksResponse, TransactionStatus};
|
||||
use solana_banks_interface::{
|
||||
Banks, BanksRequest, BanksResponse, TransactionConfirmationStatus, TransactionStatus,
|
||||
};
|
||||
use solana_runtime::{bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache};
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
@ -60,7 +62,7 @@ impl BanksServer {
|
||||
}
|
||||
}
|
||||
|
||||
fn run(bank: &Bank, transaction_receiver: Receiver<TransactionInfo>) {
|
||||
fn run(bank_forks: Arc<RwLock<BankForks>>, 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() {
|
||||
@ -70,21 +72,28 @@ 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>>) -> Self {
|
||||
fn new_loopback(
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
) -> Self {
|
||||
let (transaction_sender, transaction_receiver) = channel();
|
||||
let bank = bank_forks.read().unwrap().working_bank();
|
||||
let slot = bank.slot();
|
||||
let block_commitment_cache = Arc::new(RwLock::new(
|
||||
BlockCommitmentCache::new_for_tests_with_slots(slot, 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();
|
||||
Builder::new()
|
||||
.name("solana-bank-forks-client".to_string())
|
||||
.spawn(move || Self::run(&bank, transaction_receiver))
|
||||
.spawn(move || Self::run(server_bank_forks, transaction_receiver))
|
||||
.unwrap();
|
||||
Self::new(bank_forks, block_commitment_cache, transaction_sender)
|
||||
}
|
||||
@ -165,11 +174,17 @@ impl Banks for BanksServer {
|
||||
_: Context,
|
||||
signature: Signature,
|
||||
) -> Option<TransactionStatus> {
|
||||
let bank = self.bank(CommitmentLevel::Recent);
|
||||
let bank = self.bank(CommitmentLevel::Processed);
|
||||
let (slot, status) = bank.get_signature_status_slot(&signature)?;
|
||||
let r_block_commitment_cache = self.block_commitment_cache.read().unwrap();
|
||||
|
||||
let confirmations = if r_block_commitment_cache.root() >= slot {
|
||||
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
|
||||
{
|
||||
None
|
||||
} else {
|
||||
r_block_commitment_cache
|
||||
@ -180,6 +195,13 @@ 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)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -225,9 +247,10 @@ impl Banks for BanksServer {
|
||||
}
|
||||
|
||||
pub async fn start_local_server(
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
) -> UnboundedChannel<Response<BanksResponse>, ClientMessage<BanksRequest>> {
|
||||
let banks_server = BanksServer::new_loopback(bank_forks.clone());
|
||||
let banks_server = BanksServer::new_loopback(bank_forks, block_commitment_cache);
|
||||
let (client_transport, server_transport) = transport::channel::unbounded();
|
||||
let server = server::new(server::Config::default())
|
||||
.incoming(stream::once(future::ready(server_transport)))
|
||||
|
@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
pub mod banks_server;
|
||||
pub mod rpc_banks_service;
|
||||
pub mod send_transaction_service;
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -18,21 +18,21 @@ rand = "0.7.0"
|
||||
rayon = "1.4.0"
|
||||
serde_json = "1.0.56"
|
||||
serde_yaml = "0.8.13"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
|
||||
solana-core = { path = "../core", version = "1.5.0" }
|
||||
solana-genesis = { path = "../genesis", version = "1.5.0" }
|
||||
solana-client = { path = "../client", version = "1.5.0" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.0" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.5.20" }
|
||||
solana-core = { path = "../core", version = "=1.5.20" }
|
||||
solana-genesis = { path = "../genesis", version = "=1.5.20" }
|
||||
solana-client = { path = "../client", version = "=1.5.20" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.5.20" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "=1.5.20" }
|
||||
solana-logger = { path = "../logger", version = "=1.5.20" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.5.20" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.5.20" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.5.20" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.5.20" }
|
||||
solana-version = { path = "../version", version = "=1.5.20" }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.5.0" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.5.20" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -1,4 +1,5 @@
|
||||
#![allow(clippy::useless_attribute)]
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
|
||||
use crate::order_book::*;
|
||||
use itertools::izip;
|
||||
@ -390,7 +391,7 @@ fn swapper<T>(
|
||||
while client
|
||||
.get_balance_with_commitment(
|
||||
&trade_infos[trade_index].trade_account,
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.unwrap_or(0)
|
||||
== 0
|
||||
@ -445,7 +446,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::recent())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.expect("Failed to get blockhash");
|
||||
let to_swap_txs: Vec<_> = to_swap
|
||||
.par_iter()
|
||||
@ -571,7 +572,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::recent())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.expect("Failed to get blockhash");
|
||||
|
||||
trades.chunks(chunk_size).for_each(|chunk| {
|
||||
@ -658,7 +659,7 @@ where
|
||||
{
|
||||
for s in &tx.signatures {
|
||||
if let Ok(Some(r)) =
|
||||
sync_client.get_signature_status_with_commitment(s, CommitmentConfig::recent())
|
||||
sync_client.get_signature_status_with_commitment(s, CommitmentConfig::processed())
|
||||
{
|
||||
match r {
|
||||
Ok(_) => {
|
||||
@ -681,7 +682,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::recent())
|
||||
.get_balance_with_commitment(a, CommitmentConfig::processed())
|
||||
.unwrap_or(0)
|
||||
>= amount
|
||||
{
|
||||
@ -764,7 +765,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::recent())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.expect("blockhash");
|
||||
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
|
||||
tx.sign(&[*k], blockhash);
|
||||
@ -803,7 +804,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::recent())
|
||||
.get_balance_with_commitment(&k.pubkey(), CommitmentConfig::processed())
|
||||
.unwrap_or(0)
|
||||
> lamports
|
||||
&& *b > lamports
|
||||
@ -857,7 +858,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::recent())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.expect("Failed to get blockhash");
|
||||
to_create_txs
|
||||
.par_iter_mut()
|
||||
@ -903,7 +904,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::recent())
|
||||
.get_balance_with_commitment(&f.1.pubkey(), CommitmentConfig::processed())
|
||||
.unwrap_or(0)
|
||||
== 0
|
||||
{
|
||||
@ -968,7 +969,7 @@ pub fn airdrop_lamports<T: Client>(
|
||||
id: &Keypair,
|
||||
amount: u64,
|
||||
) {
|
||||
let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent());
|
||||
let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::processed());
|
||||
let balance = balance.unwrap_or(0);
|
||||
if balance >= amount {
|
||||
return;
|
||||
@ -986,7 +987,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::recent())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.expect("Failed to get blockhash");
|
||||
match request_airdrop_transaction(&faucet_addr, &id.pubkey(), amount_to_drop, blockhash) {
|
||||
Ok(transaction) => {
|
||||
@ -995,14 +996,14 @@ pub fn airdrop_lamports<T: Client>(
|
||||
for _ in 0..30 {
|
||||
if let Ok(Some(_)) = client.get_signature_status_with_commitment(
|
||||
&signature,
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
) {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(100));
|
||||
}
|
||||
if client
|
||||
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent())
|
||||
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::processed())
|
||||
.unwrap_or(0)
|
||||
>= amount
|
||||
{
|
||||
|
@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
pub mod bench;
|
||||
mod cli;
|
||||
pub mod order_book;
|
||||
|
@ -5,7 +5,7 @@ use solana_core::validator::ValidatorConfig;
|
||||
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_faucet::faucet::run_local_faucet_with_port;
|
||||
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_runtime::bank_client::BankClient;
|
||||
@ -57,8 +57,11 @@ fn test_exchange_local_cluster() {
|
||||
);
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
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();
|
||||
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");
|
||||
|
||||
info!("Connecting to the cluster");
|
||||
let nodes =
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
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.5.0" }
|
||||
solana-streamer = { path = "../streamer", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.5.20" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.5.20" }
|
||||
solana-logger = { path = "../logger", version = "=1.5.20" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.5.20" }
|
||||
solana-version = { path = "../version", version = "=1.5.20" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -1,3 +1,4 @@
|
||||
#![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};
|
||||
@ -74,7 +75,7 @@ fn main() -> Result<()> {
|
||||
|
||||
let mut read_channels = Vec::new();
|
||||
let mut read_threads = Vec::new();
|
||||
let recycler = PacketsRecycler::default();
|
||||
let recycler = PacketsRecycler::new_without_limit("bench-streamer-recycler-shrink-stats");
|
||||
for _ in 0..num_sockets {
|
||||
let read = solana_net_utils::bind_to(ip_addr, port, false).unwrap();
|
||||
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
|
||||
@ -90,6 +91,7 @@ fn main() -> Result<()> {
|
||||
s_reader,
|
||||
recycler.clone(),
|
||||
"bench-streamer-test",
|
||||
1,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -15,23 +15,23 @@ log = "0.4.11"
|
||||
rayon = "1.4.0"
|
||||
serde_json = "1.0.56"
|
||||
serde_yaml = "0.8.13"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
|
||||
solana-core = { path = "../core", version = "1.5.0" }
|
||||
solana-genesis = { path = "../genesis", version = "1.5.0" }
|
||||
solana-client = { path = "../client", version = "1.5.0" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.0" }
|
||||
solana-measure = { path = "../measure", version = "1.5.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.5.20" }
|
||||
solana-core = { path = "../core", version = "=1.5.20" }
|
||||
solana-genesis = { path = "../genesis", version = "=1.5.20" }
|
||||
solana-client = { path = "../client", version = "=1.5.20" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.5.20" }
|
||||
solana-logger = { path = "../logger", version = "=1.5.20" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.5.20" }
|
||||
solana-measure = { path = "../measure", version = "=1.5.20" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.5.20" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.5.20" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.5.20" }
|
||||
solana-version = { path = "../version", version = "=1.5.20" }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.4.0"
|
||||
serial_test_derive = "0.4.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.5.0" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.5.20" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -48,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::recent()) {
|
||||
match client.get_recent_blockhash_with_commitment(CommitmentConfig::processed()) {
|
||||
Ok((blockhash, fee_calculator, _last_valid_slot)) => {
|
||||
return (blockhash, fee_calculator)
|
||||
}
|
||||
@ -497,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::recent()) {
|
||||
match client.get_balance_with_commitment(a, CommitmentConfig::processed()) {
|
||||
Ok(balance) => return balance >= amount,
|
||||
Err(err) => error!("failed to get balance {:?}", err),
|
||||
}
|
||||
@ -762,7 +762,7 @@ pub fn airdrop_lamports<T: Client>(
|
||||
};
|
||||
|
||||
let current_balance = client
|
||||
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent())
|
||||
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::processed())
|
||||
.unwrap_or_else(|e| {
|
||||
info!("airdrop error {}", e);
|
||||
starting_balance
|
||||
@ -967,7 +967,7 @@ mod tests {
|
||||
for kp in &keypairs {
|
||||
assert_eq!(
|
||||
client
|
||||
.get_balance_with_commitment(&kp.pubkey(), CommitmentConfig::recent())
|
||||
.get_balance_with_commitment(&kp.pubkey(), CommitmentConfig::processed())
|
||||
.unwrap(),
|
||||
lamports
|
||||
);
|
||||
|
@ -1,2 +1,3 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
pub mod bench;
|
||||
pub mod cli;
|
||||
|
@ -1,3 +1,4 @@
|
||||
#![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;
|
||||
|
@ -1,10 +1,11 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
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;
|
||||
use solana_faucet::faucet::run_local_faucet_with_port;
|
||||
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use std::sync::{mpsc::channel, Arc};
|
||||
@ -36,8 +37,11 @@ fn test_bench_tps_local_cluster(config: Config) {
|
||||
));
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
run_local_faucet(faucet_keypair, addr_sender, None);
|
||||
let faucet_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
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");
|
||||
|
||||
let lamports_per_account = 100;
|
||||
|
||||
|
9
cargo
9
cargo
@ -3,25 +3,22 @@
|
||||
# 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"
|
||||
;;
|
||||
|
@ -105,11 +105,18 @@ 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
|
||||
|
45
ci/do-audit.sh
Executable file
45
ci/do-audit.sh
Executable file
@ -0,0 +1,45 @@
|
||||
#!/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
|
||||
|
||||
# hyper is upgraded on master/v1.6 but not for v1.5
|
||||
--ignore RUSTSEC-2021-0020
|
||||
|
||||
# generic-array: arr! macro erases lifetimes
|
||||
#
|
||||
# ed25519-dalek and libsecp256k1 not upgraded for v1.5
|
||||
--ignore RUSTSEC-2020-0146
|
||||
|
||||
)
|
||||
scripts/cargo-for-all-lock-files.sh stable audit "${cargo_audit_ignores[@]}"
|
@ -1,4 +1,4 @@
|
||||
FROM solanalabs/rust:1.48.0
|
||||
FROM solanalabs/rust:1.49.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.48.0
|
||||
FROM rust:1.49.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
|
||||
source multinode-demo/common.sh --prebuild
|
||||
|
||||
nodes=(
|
||||
"multinode-demo/bootstrap-validator.sh \
|
||||
@ -80,7 +80,7 @@ nodes=(
|
||||
"multinode-demo/validator.sh \
|
||||
--enable-rpc-exit \
|
||||
--no-restart \
|
||||
--dynamic-port-range 8050-8100
|
||||
--dynamic-port-range 8050-8100 \
|
||||
--init-complete-file init-complete-node1.log \
|
||||
--rpc-port 18899"
|
||||
)
|
||||
|
@ -19,6 +19,7 @@ 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'
|
||||
|
@ -83,7 +83,7 @@ echo --- Creating release tarball
|
||||
export CHANNEL
|
||||
|
||||
source ci/rust-version.sh stable
|
||||
scripts/cargo-install-all.sh +"$rust_stable" "${RELEASE_BASENAME}"
|
||||
scripts/cargo-install-all.sh stable "${RELEASE_BASENAME}"
|
||||
|
||||
tar cvf "${TARBALL_BASENAME}"-$TARGET.tar "${RELEASE_BASENAME}"
|
||||
bzip2 "${TARBALL_BASENAME}"-$TARGET.tar
|
||||
|
@ -18,13 +18,13 @@
|
||||
if [[ -n $RUST_STABLE_VERSION ]]; then
|
||||
stable_version="$RUST_STABLE_VERSION"
|
||||
else
|
||||
stable_version=1.48.0
|
||||
stable_version=1.49.0
|
||||
fi
|
||||
|
||||
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
||||
nightly_version="$RUST_NIGHTLY_VERSION"
|
||||
else
|
||||
nightly_version=2020-12-13
|
||||
nightly_version=2021-01-23
|
||||
fi
|
||||
|
||||
|
||||
|
@ -76,7 +76,7 @@ RestartForceExitStatus=SIGPIPE
|
||||
TimeoutStartSec=10
|
||||
TimeoutStopSec=0
|
||||
KillMode=process
|
||||
LimitNOFILE=500000
|
||||
LimitNOFILE=700000
|
||||
|
||||
[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 500000" > /etc/security/limits.d/90-solana-nofiles.conf
|
||||
echo "* - nofile 700000" > /etc/security/limits.d/90-solana-nofiles.conf
|
||||
|
||||
|
@ -23,6 +23,10 @@ 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 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
|
||||
|
@ -12,6 +12,16 @@ 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
|
||||
@ -35,7 +45,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 +"$rust_stable" check --locked --tests --bins --examples; then
|
||||
if _ scripts/cargo-for-all-lock-files.sh stable check --locked --tests --bins --examples; then
|
||||
true
|
||||
else
|
||||
check_status=$?
|
||||
@ -46,56 +56,30 @@ if [[ $CI_BASE_BRANCH = "$EDGE_CHANNEL" ]]; then
|
||||
fi
|
||||
|
||||
# Ensure nightly and --benches
|
||||
_ scripts/cargo-for-all-lock-files.sh +"$rust_nightly" check --locked --all-targets
|
||||
_ scripts/cargo-for-all-lock-files.sh 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
|
||||
_ "$cargo" nightly clippy -Zunstable-options --workspace --all-targets -- --deny=warnings --deny=clippy::integer_arithmetic
|
||||
|
||||
cargo_audit_ignores=(
|
||||
# failure is officially deprecated/unmaintained
|
||||
#
|
||||
# Blocked on multiple upstream crates removing their `failure` dependency.
|
||||
--ignore RUSTSEC-2020-0036
|
||||
_ "$cargo" stable fmt --all -- --check
|
||||
|
||||
# `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
|
||||
|
||||
# memmap crate is unmaintained
|
||||
#
|
||||
# Blocked on us releasing new solana crates
|
||||
--ignore RUSTSEC-2020-0077
|
||||
)
|
||||
_ scripts/cargo-for-all-lock-files.sh +"$rust_stable" audit "${cargo_audit_ignores[@]}"
|
||||
_ ci/do-audit.sh
|
||||
|
||||
{
|
||||
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
|
||||
)
|
||||
done
|
||||
}
|
||||
|
@ -25,4 +25,29 @@ 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
|
||||
|
@ -39,6 +39,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
|
||||
|
||||
|
@ -1,23 +1,28 @@
|
||||
[package]
|
||||
name = "solana-clap-utils"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
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.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "=1.5.20" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.5.20" }
|
||||
thiserror = "1.0.21"
|
||||
tiny-bip39 = "0.7.0"
|
||||
uriparse = "0.6.3"
|
||||
url = "2.1.0"
|
||||
chrono = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[lib]
|
||||
name = "solana_clap_utils"
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
use crate::ArgConstant;
|
||||
use clap::Arg;
|
||||
|
||||
pub const COMMITMENT_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "commitment",
|
||||
long: "commitment",
|
||||
help: "Return information at the selected commitment level",
|
||||
};
|
||||
|
||||
pub fn commitment_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
commitment_arg_with_default("recent")
|
||||
}
|
||||
|
||||
pub fn commitment_arg_with_default<'a, 'b>(default_value: &'static str) -> Arg<'a, 'b> {
|
||||
Arg::with_name(COMMITMENT_ARG.name)
|
||||
.long(COMMITMENT_ARG.long)
|
||||
.takes_value(true)
|
||||
.possible_values(&["recent", "single", "singleGossip", "root", "max"])
|
||||
.default_value(default_value)
|
||||
.value_name("COMMITMENT_LEVEL")
|
||||
.help(COMMITMENT_ARG.help)
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
use crate::{input_validators, ArgConstant};
|
||||
use clap::Arg;
|
||||
use {
|
||||
crate::{input_validators, ArgConstant},
|
||||
clap::Arg,
|
||||
};
|
||||
|
||||
pub const FEE_PAYER_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "fee_payer",
|
||||
|
@ -1,19 +1,21 @@
|
||||
use crate::keypair::{
|
||||
keypair_from_seed_phrase, pubkey_from_path, resolve_signer_from_path, signer_from_path,
|
||||
ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG,
|
||||
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 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>>
|
||||
@ -167,12 +169,12 @@ pub fn resolve_signer(
|
||||
name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
Ok(resolve_signer_from_path(
|
||||
resolve_signer_from_path(
|
||||
matches,
|
||||
matches.value_of(name).unwrap(),
|
||||
name,
|
||||
wallet_manager,
|
||||
)?)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> {
|
||||
@ -184,14 +186,9 @@ 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| match value {
|
||||
"max" => CommitmentConfig::max(),
|
||||
"recent" => CommitmentConfig::recent(),
|
||||
"root" => CommitmentConfig::root(),
|
||||
"single" => CommitmentConfig::single(),
|
||||
"singleGossip" => CommitmentConfig::single_gossip(),
|
||||
_ => CommitmentConfig::default(),
|
||||
})
|
||||
matches
|
||||
.value_of(name)
|
||||
.map(|value| CommitmentConfig::from_str(value).unwrap_or_default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,13 +1,15 @@
|
||||
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 {
|
||||
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 std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
fn is_parsable_generic<U, T>(string: T) -> Result<(), String>
|
||||
where
|
||||
@ -32,6 +34,29 @@ 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
|
||||
@ -85,8 +110,11 @@ pub fn is_valid_pubkey<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
match parse_keypair_path(string.as_ref()) {
|
||||
KeypairUrl::Filepath(path) => is_keypair(path),
|
||||
match parse_signer_source(string.as_ref())
|
||||
.map_err(|err| format!("{}", err))?
|
||||
.kind
|
||||
{
|
||||
SignerSourceKind::Filepath(path) => is_keypair(path),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
@ -148,6 +176,40 @@ 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://testnet.solana.com",
|
||||
"d" | "devnet" => "https://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,
|
||||
@ -257,6 +319,21 @@ 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,33 +1,41 @@
|
||||
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,
|
||||
use {
|
||||
crate::{
|
||||
input_parsers::pubkeys_sigs_of,
|
||||
offline::{SIGNER_ARG, SIGN_ONLY_ARG},
|
||||
ArgConstant,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
error,
|
||||
io::{stdin, stdout, Write},
|
||||
process::exit,
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
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::{
|
||||
keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair,
|
||||
read_keypair_file, Keypair, NullSigner, Presigner, Signature, Signer,
|
||||
},
|
||||
},
|
||||
std::{
|
||||
convert::TryFrom,
|
||||
error,
|
||||
io::{stdin, stdout, Write},
|
||||
process::exit,
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
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>,
|
||||
@ -58,6 +66,27 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultSigner {
|
||||
@ -99,27 +128,87 @@ impl DefaultSigner {
|
||||
) -> 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)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum KeypairUrl {
|
||||
pub(crate) struct SignerSource {
|
||||
pub kind: SignerSourceKind,
|
||||
pub derivation_path: Option<DerivationPath>,
|
||||
}
|
||||
|
||||
impl SignerSource {
|
||||
fn new(kind: SignerSourceKind) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
derivation_path: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum SignerSourceKind {
|
||||
Ask,
|
||||
Filepath(String),
|
||||
Usb(String),
|
||||
Usb(RemoteWalletLocator),
|
||||
Stdin,
|
||||
Pubkey(Pubkey),
|
||||
}
|
||||
|
||||
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())
|
||||
#[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();
|
||||
match uriparse::URIReference::try_from(source) {
|
||||
Err(_) => Err(SignerSourceError::UnrecognizedSource),
|
||||
Ok(uri) => {
|
||||
if let Some(scheme) = uri.scheme() {
|
||||
let scheme = scheme.as_str().to_ascii_lowercase();
|
||||
match scheme.as_str() {
|
||||
"ask" => Ok(SignerSource::new(SignerSourceKind::Ask)),
|
||||
"file" => Ok(SignerSource::new(SignerSourceKind::Filepath(
|
||||
uri.path().to_string(),
|
||||
))),
|
||||
"stdin" => Ok(SignerSource::new(SignerSourceKind::Stdin)),
|
||||
"usb" => Ok(SignerSource {
|
||||
kind: SignerSourceKind::Usb(RemoteWalletLocator::new_from_uri(&uri)?),
|
||||
derivation_path: DerivationPath::from_uri(&uri)?,
|
||||
}),
|
||||
_ => Err(SignerSourceError::UnrecognizedSource),
|
||||
}
|
||||
} else {
|
||||
match source {
|
||||
"-" => Ok(SignerSource::new(SignerSourceKind::Stdin)),
|
||||
ASK_KEYWORD => Ok(SignerSource::new(SignerSourceKind::Ask)),
|
||||
_ => match Pubkey::from_str(source) {
|
||||
Ok(pubkey) => Ok(SignerSource::new(SignerSourceKind::Pubkey(pubkey))),
|
||||
Err(_) => std::fs::metadata(source)
|
||||
.map(|_| {
|
||||
SignerSource::new(SignerSourceKind::Filepath(source.to_string()))
|
||||
})
|
||||
.map_err(|err| err.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,14 +225,42 @@ 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>> {
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Ask => {
|
||||
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,
|
||||
} = parse_signer_source(path)?;
|
||||
match kind {
|
||||
SignerSourceKind::Ask => {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
Ok(Box::new(keypair_from_seed_phrase(
|
||||
keypair_name,
|
||||
@ -151,7 +268,7 @@ pub fn signer_from_path(
|
||||
false,
|
||||
)?))
|
||||
}
|
||||
KeypairUrl::Filepath(path) => match read_keypair_file(&path) {
|
||||
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),
|
||||
@ -159,17 +276,18 @@ pub fn signer_from_path(
|
||||
.into()),
|
||||
Ok(file) => Ok(Box::new(file)),
|
||||
},
|
||||
KeypairUrl::Stdin => {
|
||||
SignerSourceKind::Stdin => {
|
||||
let mut stdin = std::io::stdin();
|
||||
Ok(Box::new(read_keypair(&mut stdin)?))
|
||||
}
|
||||
KeypairUrl::Usb(path) => {
|
||||
SignerSourceKind::Usb(locator) => {
|
||||
if wallet_manager.is_none() {
|
||||
*wallet_manager = maybe_wallet_manager()?;
|
||||
}
|
||||
if let Some(wallet_manager) = wallet_manager {
|
||||
Ok(Box::new(generate_remote_keypair(
|
||||
path,
|
||||
locator,
|
||||
derivation_path.unwrap_or_default(),
|
||||
wallet_manager,
|
||||
matches.is_present("confirm_key"),
|
||||
keypair_name,
|
||||
@ -178,13 +296,13 @@ pub fn signer_from_path(
|
||||
Err(RemoteWalletError::NoDeviceFound.into())
|
||||
}
|
||||
}
|
||||
KeypairUrl::Pubkey(pubkey) => {
|
||||
SignerSourceKind::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 matches.is_present(SIGN_ONLY_ARG.name) {
|
||||
} else if config.allow_null_signer || matches.is_present(SIGN_ONLY_ARG.name) {
|
||||
Ok(Box::new(NullSigner::new(&pubkey)))
|
||||
} else {
|
||||
Err(std::io::Error::new(
|
||||
@ -203,8 +321,9 @@ pub fn pubkey_from_path(
|
||||
keypair_name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Pubkey, Box<dyn error::Error>> {
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Pubkey(pubkey) => Ok(pubkey),
|
||||
let SignerSource { kind, .. } = parse_signer_source(path)?;
|
||||
match kind {
|
||||
SignerSourceKind::Pubkey(pubkey) => Ok(pubkey),
|
||||
_ => Ok(signer_from_path(matches, path, keypair_name, wallet_manager)?.pubkey()),
|
||||
}
|
||||
}
|
||||
@ -215,14 +334,18 @@ pub fn resolve_signer_from_path(
|
||||
keypair_name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<String>, Box<dyn error::Error>> {
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Ask => {
|
||||
let SignerSource {
|
||||
kind,
|
||||
derivation_path,
|
||||
} = parse_signer_source(path)?;
|
||||
match kind {
|
||||
SignerSourceKind::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).map(|_| None)
|
||||
}
|
||||
KeypairUrl::Filepath(path) => match read_keypair_file(&path) {
|
||||
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),
|
||||
@ -230,19 +353,20 @@ pub fn resolve_signer_from_path(
|
||||
.into()),
|
||||
Ok(_) => Ok(Some(path.to_string())),
|
||||
},
|
||||
KeypairUrl::Stdin => {
|
||||
SignerSourceKind::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)
|
||||
}
|
||||
KeypairUrl::Usb(path) => {
|
||||
SignerSourceKind::Usb(locator) => {
|
||||
if wallet_manager.is_none() {
|
||||
*wallet_manager = maybe_wallet_manager()?;
|
||||
}
|
||||
if let Some(wallet_manager) = wallet_manager {
|
||||
let path = generate_remote_keypair(
|
||||
path,
|
||||
locator,
|
||||
derivation_path.unwrap_or_default(),
|
||||
wallet_manager,
|
||||
matches.is_present("confirm_key"),
|
||||
keypair_name,
|
||||
@ -346,6 +470,9 @@ 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() {
|
||||
@ -355,4 +482,142 @@ 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("-").unwrap(),
|
||||
SignerSource {
|
||||
kind: SignerSourceKind::Stdin,
|
||||
derivation_path: None,
|
||||
}
|
||||
));
|
||||
let ask = "stdin:".to_string();
|
||||
assert!(matches!(
|
||||
parse_signer_source(&ask).unwrap(),
|
||||
SignerSource {
|
||||
kind: SignerSourceKind::Stdin,
|
||||
derivation_path: None,
|
||||
}
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_signer_source(ASK_KEYWORD).unwrap(),
|
||||
SignerSource {
|
||||
kind: SignerSourceKind::Ask,
|
||||
derivation_path: None,
|
||||
}
|
||||
));
|
||||
let pubkey = Pubkey::new_unique();
|
||||
assert!(
|
||||
matches!(parse_signer_source(&pubkey.to_string()).unwrap(), SignerSource {
|
||||
kind: SignerSourceKind::Pubkey(p),
|
||||
derivation_path: None,
|
||||
}
|
||||
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,
|
||||
} if p == absolute_path_str)
|
||||
);
|
||||
assert!(
|
||||
matches!(parse_signer_source(&relative_path_str).unwrap(), SignerSource {
|
||||
kind: SignerSourceKind::Filepath(p),
|
||||
derivation_path: None,
|
||||
} 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,
|
||||
} 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,
|
||||
} 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 ask = "ask:".to_string();
|
||||
assert!(matches!(
|
||||
parse_signer_source(&ask).unwrap(),
|
||||
SignerSource {
|
||||
kind: SignerSourceKind::Ask,
|
||||
derivation_path: None,
|
||||
}
|
||||
));
|
||||
assert!(
|
||||
matches!(parse_signer_source(&format!("file:{}", absolute_path_str)).unwrap(), SignerSource {
|
||||
kind: SignerSourceKind::Filepath(p),
|
||||
derivation_path: None,
|
||||
} if p == absolute_path_str)
|
||||
);
|
||||
assert!(
|
||||
matches!(parse_signer_source(&format!("file:{}", relative_path_str)).unwrap(), SignerSource {
|
||||
kind: SignerSourceKind::Filepath(p),
|
||||
derivation_path: None,
|
||||
} if p == relative_path_str)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ impl std::fmt::Debug for DisplayError {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod commitment;
|
||||
pub mod fee_payer;
|
||||
pub mod input_parsers;
|
||||
pub mod input_validators;
|
||||
|
15
clap-utils/src/memo.rs
Normal file
15
clap-utils/src/memo.rs
Normal file
@ -0,0 +1,15 @@
|
||||
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,5 +1,7 @@
|
||||
use crate::{input_validators::*, offline::BLOCKHASH_ARG, ArgConstant};
|
||||
use clap::{App, Arg};
|
||||
use {
|
||||
crate::{input_validators::*, offline::BLOCKHASH_ARG, ArgConstant},
|
||||
clap::{App, Arg},
|
||||
};
|
||||
|
||||
pub const NONCE_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "nonce",
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::{input_validators::*, ArgConstant};
|
||||
use clap::{App, Arg};
|
||||
use {
|
||||
crate::{input_validators::*, ArgConstant},
|
||||
clap::{App, Arg},
|
||||
};
|
||||
|
||||
pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "blockhash",
|
||||
@ -19,6 +21,12 @@ 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)
|
||||
@ -47,6 +55,14 @@ 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
|
||||
@ -57,6 +73,9 @@ 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 {
|
||||
@ -69,6 +88,7 @@ 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,15 +3,16 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-config"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
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.112"
|
||||
serde = "1.0.118"
|
||||
serde_derive = "1.0.103"
|
||||
serde_yaml = "0.8.13"
|
||||
url = "2.1.1"
|
||||
|
@ -17,9 +17,10 @@ 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 {
|
||||
@ -41,11 +42,14 @@ impl Default for Config {
|
||||
"System Program".to_string(),
|
||||
);
|
||||
|
||||
let commitment = "confirmed".to_string();
|
||||
|
||||
Self {
|
||||
json_rpc_url,
|
||||
websocket_url,
|
||||
keypair_path,
|
||||
address_labels,
|
||||
commitment,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
|
@ -3,27 +3,29 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-output"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
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.11.3"
|
||||
humantime = "2.0.1"
|
||||
Inflector = "0.11.4"
|
||||
indicatif = "0.15.0"
|
||||
serde = "1.0.112"
|
||||
serde = "1.0.118"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
|
||||
solana-client = { path = "../client", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.5.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.0" }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.5.20" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.5.20" }
|
||||
solana-client = { path = "../client", version = "=1.5.20" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.5.20" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "=1.5.20" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.5.20" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.5.20" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,28 +1,73 @@
|
||||
use console::style;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use solana_sdk::{
|
||||
hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize,
|
||||
transaction::Transaction,
|
||||
use {
|
||||
crate::cli_output::CliSignatureVerificationStatus,
|
||||
chrono::{DateTime, Local, NaiveDateTime, SecondsFormat, TimeZone, Utc},
|
||||
console::style,
|
||||
indicatif::{ProgressBar, ProgressStyle},
|
||||
solana_sdk::{
|
||||
clock::UnixTimestamp, hash::Hash, native_token::lamports_to_sol,
|
||||
program_utils::limited_deserialize, transaction::Transaction,
|
||||
},
|
||||
solana_transaction_status::UiTransactionStatusMeta,
|
||||
std::{collections::HashMap, fmt, io},
|
||||
};
|
||||
use solana_transaction_status::UiTransactionStatusMeta;
|
||||
use std::{collections::HashMap, fmt, io};
|
||||
|
||||
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)
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_balance_message_with_config(
|
||||
lamports: u64,
|
||||
config: &BuildBalanceMessageConfig,
|
||||
) -> String {
|
||||
let value = if config.use_lamports_unit {
|
||||
lamports.to_string()
|
||||
} else {
|
||||
let sol = lamports_to_sol(lamports);
|
||||
let sol_str = format!("{:.9}", sol);
|
||||
let pretty_sol = sol_str.trim_end_matches('0').trim_end_matches('.');
|
||||
let unit = if show_unit { " SOL" } else { "" };
|
||||
format!("{}{}", pretty_sol, unit)
|
||||
}
|
||||
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()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Pretty print a "name value"
|
||||
@ -35,7 +80,7 @@ pub fn println_name_value(name: &str, value: &str) {
|
||||
println!("{} {}", style(name).bold(), styled_value);
|
||||
}
|
||||
|
||||
pub fn writeln_name_value(f: &mut fmt::Formatter, name: &str, value: &str) -> fmt::Result {
|
||||
pub fn writeln_name_value(f: &mut dyn fmt::Write, name: &str, value: &str) -> fmt::Result {
|
||||
let styled_value = if value.is_empty() {
|
||||
style("(not set)").italic()
|
||||
} else {
|
||||
@ -85,18 +130,41 @@ pub fn write_transaction<W: io::Write>(
|
||||
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
|
||||
)?;
|
||||
for (signature_index, signature) in transaction.signatures.iter().enumerate() {
|
||||
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()
|
||||
{
|
||||
writeln!(
|
||||
w,
|
||||
"{}Signature {}: {:?}",
|
||||
prefix, signature_index, signature
|
||||
"{}Signature {}: {:?}{}",
|
||||
prefix, signature_index, signature, sigverify_status,
|
||||
)?;
|
||||
}
|
||||
writeln!(w, "{}{:?}", prefix, message.header)?;
|
||||
@ -217,15 +285,52 @@ 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).is_ok() {
|
||||
if write_transaction(
|
||||
&mut w,
|
||||
transaction,
|
||||
transaction_status,
|
||||
prefix,
|
||||
sigverify_status,
|
||||
block_time,
|
||||
)
|
||||
.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);
|
||||
@ -235,6 +340,13 @@ 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,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
mod cli_output;
|
||||
pub mod display;
|
||||
pub use cli_output::*;
|
||||
|
@ -3,10 +3,11 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
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"
|
||||
@ -24,32 +25,32 @@ humantime = "2.0.1"
|
||||
num-traits = "0.2"
|
||||
pretty-hex = "0.2.1"
|
||||
reqwest = { version = "0.10.8", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serde = "1.0.112"
|
||||
serde = "1.0.118"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.5.0" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.5.0" }
|
||||
solana-cli-output = { path = "../cli-output", version = "1.5.0" }
|
||||
solana-client = { path = "../client", version = "1.5.0" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.5.0" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.0" }
|
||||
solana_rbpf = "=0.2.2"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.0" }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.5.20" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.5.20" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.5.20" }
|
||||
solana-cli-config = { path = "../cli-config", version = "=1.5.20" }
|
||||
solana-cli-output = { path = "../cli-output", version = "=1.5.20" }
|
||||
solana-client = { path = "../client", version = "=1.5.20" }
|
||||
solana-config-program = { path = "../programs/config", version = "=1.5.20" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.5.20" }
|
||||
solana-logger = { path = "../logger", version = "=1.5.20" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.5.20" }
|
||||
solana_rbpf = "=0.2.7"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "=1.5.20" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.5.20" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "=1.5.20" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.5.20" }
|
||||
solana-version = { path = "../version", version = "=1.5.20" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.5.20" }
|
||||
thiserror = "1.0.21"
|
||||
tiny-bip39 = "0.7.0"
|
||||
url = "2.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-core = { path = "../core", version = "1.5.0" }
|
||||
solana-core = { path = "../core", version = "=1.5.20" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[[bin]]
|
||||
|
@ -86,9 +86,13 @@ 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)));
|
||||
return Err(CliError::InsufficientFundsForFee(
|
||||
lamports_to_sol(fee),
|
||||
*account_pubkey,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
2015
cli/src/cli.rs
2015
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,10 +19,22 @@ 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 },
|
||||
Status {
|
||||
features: Vec<Pubkey>,
|
||||
},
|
||||
Activate {
|
||||
feature: Pubkey,
|
||||
force: ForceActivation,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@ -58,8 +70,8 @@ impl fmt::Display for CliFeatures {
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
"{:<44} {:<40} {}",
|
||||
"Feature", "Description", "Status"
|
||||
"{:<44} | {:<27} | {}",
|
||||
"Feature", "Status", "Description"
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
@ -67,15 +79,15 @@ impl fmt::Display for CliFeatures {
|
||||
for feature in &self.features {
|
||||
writeln!(
|
||||
f,
|
||||
"{:<44} {:<40} {}",
|
||||
"{:<44} | {:<27} | {}",
|
||||
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 {
|
||||
@ -126,6 +138,13 @@ 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"),
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -152,13 +171,20 @@ 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 }),
|
||||
command: CliCommand::Feature(FeatureCliCommand::Activate { feature, force }),
|
||||
signers,
|
||||
}
|
||||
}
|
||||
@ -189,11 +215,13 @@ pub fn process_feature_subcommand(
|
||||
) -> ProcessResult {
|
||||
match feature_subcommand {
|
||||
FeatureCliCommand::Status { features } => process_status(rpc_client, config, features),
|
||||
FeatureCliCommand::Activate { feature } => process_activate(rpc_client, config, *feature),
|
||||
FeatureCliCommand::Activate { feature, force } => {
|
||||
process_activate(rpc_client, config, *feature, *force)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, u64>, ClientError> {
|
||||
fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, f64>, ClientError> {
|
||||
// Validator identity -> feature set
|
||||
let feature_set_map = rpc_client
|
||||
.get_cluster_nodes()?
|
||||
@ -211,7 +239,7 @@ fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, u6
|
||||
.sum();
|
||||
|
||||
// Sum all active stake by feature set
|
||||
let mut active_stake_by_feature_set = HashMap::new();
|
||||
let mut active_stake_by_feature_set: HashMap<u32, u64> = 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() +=
|
||||
@ -223,11 +251,15 @@ fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, u6
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
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())
|
||||
}
|
||||
|
||||
// Feature activation is only allowed when 95% of the active stake is on the current feature set
|
||||
@ -238,7 +270,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 {
|
||||
@ -255,15 +287,15 @@ fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<boo
|
||||
}
|
||||
println!(
|
||||
"{}",
|
||||
style(format!("Tool Feture Set: {}", my_feature_set)).bold()
|
||||
style(format!("Tool Feature 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 - {}%", percentage);
|
||||
println!(" unknown - {:.2}%", percentage);
|
||||
} else {
|
||||
println!(
|
||||
"{} - {}% {}",
|
||||
" {:<10} - {:.2}% {}",
|
||||
feature_set,
|
||||
percentage,
|
||||
if *feature_set == my_feature_set {
|
||||
@ -329,12 +361,14 @@ 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());
|
||||
@ -342,7 +376,13 @@ fn process_activate(
|
||||
}
|
||||
|
||||
if !feature_activation_allowed(rpc_client, false)? {
|
||||
return Err("Feature activation is not allowed at this time".into());
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
let rent = rpc_client.get_minimum_balance_for_rent_exemption(Feature::size_of())?;
|
||||
|
@ -1,14 +1,22 @@
|
||||
use crate::cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult};
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use console::style;
|
||||
use solana_clap_utils::keypair::*;
|
||||
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 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 {
|
||||
@ -17,73 +25,118 @@ pub trait InflationSubCommands {
|
||||
|
||||
impl InflationSubCommands for App<'_, '_> {
|
||||
fn inflation_subcommands(self) -> Self {
|
||||
self.subcommand(SubCommand::with_name("inflation").about("Show inflation information"))
|
||||
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]"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(InflationCliCommand::Show),
|
||||
command: CliCommand::Inflation(command),
|
||||
signers: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process_inflation_subcommand(
|
||||
rpc_client: &RpcClient,
|
||||
_config: &CliConfig,
|
||||
config: &CliConfig,
|
||||
inflation_subcommand: &InflationCliCommand,
|
||||
) -> ProcessResult {
|
||||
assert_eq!(*inflation_subcommand, InflationCliCommand::Show);
|
||||
|
||||
let governor = rpc_client.get_inflation_governor()?;
|
||||
let current_inflation_rate = rpc_client.get_inflation_rate()?;
|
||||
|
||||
println!("{}", style("Inflation Governor:").bold());
|
||||
if (governor.initial - governor.terminal).abs() < f64::EPSILON {
|
||||
println!(
|
||||
"Fixed APR: {:>5.2}%",
|
||||
governor.terminal * 100.
|
||||
);
|
||||
} else {
|
||||
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.);
|
||||
match inflation_subcommand {
|
||||
InflationCliCommand::Show => process_show(rpc_client, config),
|
||||
InflationCliCommand::Rewards(ref addresses, rewards_epoch) => {
|
||||
process_rewards(rpc_client, config, addresses, *rewards_epoch)
|
||||
}
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
||||
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 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,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let cli_rewards = CliKeyedEpochRewards {
|
||||
epoch_metadata,
|
||||
rewards: epoch_rewards,
|
||||
};
|
||||
Ok(config.output_format.formatted_string(&cli_rewards))
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
macro_rules! ACCOUNT_STRING {
|
||||
() => {
|
||||
r#", one of:
|
||||
@ -26,6 +27,7 @@ pub mod cluster_query;
|
||||
pub mod feature;
|
||||
pub mod inflation;
|
||||
pub mod nonce;
|
||||
pub mod program;
|
||||
pub mod send_tpu;
|
||||
pub mod spend_utils;
|
||||
pub mod stake;
|
||||
|
@ -3,11 +3,8 @@ use clap::{
|
||||
SubCommand,
|
||||
};
|
||||
use console::style;
|
||||
|
||||
use solana_clap_utils::{
|
||||
commitment::COMMITMENT_ARG,
|
||||
input_parsers::commitment_of,
|
||||
input_validators::is_url,
|
||||
input_validators::{is_url, is_url_or_moniker, normalize_to_url_if_moniker},
|
||||
keypair::{CliSigners, DefaultSigner, SKIP_SEED_PHRASE_VALIDATION_ARG},
|
||||
DisplayError,
|
||||
};
|
||||
@ -63,12 +60,19 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
);
|
||||
let (keypair_setting_type, keypair_path) =
|
||||
CliConfig::compute_keypair_path_setting("", &config.keypair_path);
|
||||
let (commitment_setting_type, commitment) =
|
||||
CliConfig::compute_commitment_config("", &config.commitment);
|
||||
|
||||
if let Some(field) = subcommand_matches.value_of("specific_setting") {
|
||||
let (field_name, value, setting_type) = match field {
|
||||
"json_rpc_url" => ("RPC URL", json_rpc_url, url_setting_type),
|
||||
"websocket_url" => ("WebSocket URL", websocket_url, ws_setting_type),
|
||||
"keypair" => ("Key Path", keypair_path, keypair_setting_type),
|
||||
"commitment" => (
|
||||
"Commitment",
|
||||
commitment.commitment.to_string(),
|
||||
commitment_setting_type,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
println_name_value_or(&format!("{}:", field_name), &value, setting_type);
|
||||
@ -77,11 +81,16 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
|
||||
println_name_value_or("WebSocket URL:", &websocket_url, ws_setting_type);
|
||||
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
|
||||
println_name_value_or(
|
||||
"Commitment:",
|
||||
&commitment.commitment.to_string(),
|
||||
commitment_setting_type,
|
||||
);
|
||||
}
|
||||
}
|
||||
("set", Some(subcommand_matches)) => {
|
||||
if let Some(url) = subcommand_matches.value_of("json_rpc_url") {
|
||||
config.json_rpc_url = url.to_string();
|
||||
config.json_rpc_url = normalize_to_url_if_moniker(url);
|
||||
// Revert to a computed `websocket_url` value when `json_rpc_url` is
|
||||
// changed
|
||||
config.websocket_url = "".to_string();
|
||||
@ -92,6 +101,9 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
if let Some(keypair) = subcommand_matches.value_of("keypair") {
|
||||
config.keypair_path = keypair.to_string();
|
||||
}
|
||||
if let Some(commitment) = subcommand_matches.value_of("commitment") {
|
||||
config.commitment = commitment.to_string();
|
||||
}
|
||||
|
||||
config.save(config_file)?;
|
||||
|
||||
@ -105,11 +117,18 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
);
|
||||
let (keypair_setting_type, keypair_path) =
|
||||
CliConfig::compute_keypair_path_setting("", &config.keypair_path);
|
||||
let (commitment_setting_type, commitment) =
|
||||
CliConfig::compute_commitment_config("", &config.commitment);
|
||||
|
||||
println_name_value("Config File:", config_file);
|
||||
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
|
||||
println_name_value_or("WebSocket URL:", &websocket_url, ws_setting_type);
|
||||
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
|
||||
println_name_value_or(
|
||||
"Commitment:",
|
||||
&commitment.commitment.to_string(),
|
||||
commitment_setting_type,
|
||||
);
|
||||
}
|
||||
("import-address-labels", Some(subcommand_matches)) => {
|
||||
let filename = value_t_or_exit!(subcommand_matches, "filename", PathBuf);
|
||||
@ -165,8 +184,18 @@ pub fn parse_args<'a>(
|
||||
path: default_signer_path.clone(),
|
||||
};
|
||||
|
||||
let CliCommandInfo { command, signers } =
|
||||
parse_command(&matches, &default_signer, &mut wallet_manager)?;
|
||||
let CliCommandInfo {
|
||||
command,
|
||||
mut signers,
|
||||
} = parse_command(&matches, &default_signer, &mut wallet_manager)?;
|
||||
|
||||
if signers.is_empty() {
|
||||
if let Ok(signer_info) =
|
||||
default_signer.generate_unique_signers(vec![None], matches, &mut wallet_manager)
|
||||
{
|
||||
signers.extend(signer_info.signers);
|
||||
}
|
||||
}
|
||||
|
||||
let verbose = matches.is_present("verbose");
|
||||
let output_format = matches
|
||||
@ -182,11 +211,10 @@ pub fn parse_args<'a>(
|
||||
OutputFormat::Display
|
||||
});
|
||||
|
||||
let commitment = matches
|
||||
.subcommand_name()
|
||||
.and_then(|name| matches.subcommand_matches(name))
|
||||
.and_then(|sub_matches| commitment_of(sub_matches, COMMITMENT_ARG.long))
|
||||
.unwrap_or_default();
|
||||
let (_, commitment) = CliConfig::compute_commitment_config(
|
||||
matches.value_of("commitment").unwrap_or(""),
|
||||
&config.commitment,
|
||||
);
|
||||
|
||||
let address_labels = if matches.is_present("no_address_labels") {
|
||||
HashMap::new()
|
||||
@ -206,7 +234,10 @@ pub fn parse_args<'a>(
|
||||
verbose,
|
||||
output_format,
|
||||
commitment,
|
||||
send_transaction_config: RpcSendTransactionConfig::default(),
|
||||
send_transaction_config: RpcSendTransactionConfig {
|
||||
preflight_commitment: Some(commitment.commitment),
|
||||
..RpcSendTransactionConfig::default()
|
||||
},
|
||||
address_labels,
|
||||
},
|
||||
signers,
|
||||
@ -214,7 +245,7 @@ pub fn parse_args<'a>(
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
solana_logger::setup();
|
||||
solana_logger::setup_with_default("off");
|
||||
let matches = app(
|
||||
crate_name!(),
|
||||
crate_description!(),
|
||||
@ -238,11 +269,14 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
Arg::with_name("json_rpc_url")
|
||||
.short("u")
|
||||
.long("url")
|
||||
.value_name("URL")
|
||||
.value_name("URL_OR_MONIKER")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
.validator(is_url)
|
||||
.help("JSON RPC URL for the solana cluster"),
|
||||
.validator(is_url_or_moniker)
|
||||
.help(
|
||||
"URL for Solana's JSON RPC or moniker (or their first letter): \
|
||||
[mainnet-beta, testnet, devnet, localhost]",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("websocket_url")
|
||||
@ -262,6 +296,25 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.takes_value(true)
|
||||
.help("Filepath or URL to a keypair"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("commitment")
|
||||
.long("commitment")
|
||||
.takes_value(true)
|
||||
.possible_values(&[
|
||||
"processed",
|
||||
"confirmed",
|
||||
"finalized",
|
||||
"recent", // Deprecated as of v1.5.5
|
||||
"single", // Deprecated as of v1.5.5
|
||||
"singleGossip", // Deprecated as of v1.5.5
|
||||
"root", // Deprecated as of v1.5.5
|
||||
"max", // Deprecated as of v1.5.5
|
||||
])
|
||||
.value_name("COMMITMENT_LEVEL")
|
||||
.hide_possible_values(true)
|
||||
.global(true)
|
||||
.help("Return information at the selected commitment level [possible values: processed, confirmed, finalized]"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("verbose")
|
||||
.long("verbose")
|
||||
@ -313,7 +366,12 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.index(1)
|
||||
.value_name("CONFIG_FIELD")
|
||||
.takes_value(true)
|
||||
.possible_values(&["json_rpc_url", "websocket_url", "keypair"])
|
||||
.possible_values(&[
|
||||
"json_rpc_url",
|
||||
"websocket_url",
|
||||
"keypair",
|
||||
"commitment",
|
||||
])
|
||||
.help("Return a specific config setting"),
|
||||
),
|
||||
)
|
||||
@ -322,7 +380,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.about("Set a config setting")
|
||||
.group(
|
||||
ArgGroup::with_name("config_settings")
|
||||
.args(&["json_rpc_url", "websocket_url", "keypair"])
|
||||
.args(&["json_rpc_url", "websocket_url", "keypair", "commitment"])
|
||||
.multiple(true)
|
||||
.required(true),
|
||||
),
|
||||
|
@ -332,9 +332,7 @@ pub fn process_authorize_nonce_account(
|
||||
nonce_authority: SignerIndex,
|
||||
new_authority: &Pubkey,
|
||||
) -> ProcessResult {
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
|
||||
let nonce_authority = config.signers[nonce_authority];
|
||||
let ix = authorize_nonce_account(nonce_account, &nonce_authority.pubkey(), new_authority);
|
||||
@ -349,11 +347,7 @@ pub fn process_authorize_nonce_account(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<NonceError>(result, &config)
|
||||
}
|
||||
|
||||
@ -400,9 +394,7 @@ pub fn process_create_nonce_account(
|
||||
Message::new(&ixs, Some(&config.signers[0].pubkey()))
|
||||
};
|
||||
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
|
||||
let (message, lamports) = resolve_spend_tx_and_check_account_balance(
|
||||
rpc_client,
|
||||
@ -414,9 +406,7 @@ pub fn process_create_nonce_account(
|
||||
config.commitment,
|
||||
)?;
|
||||
|
||||
if let Ok(nonce_account) =
|
||||
get_account_with_commitment(rpc_client, &nonce_account_address, config.commitment)
|
||||
{
|
||||
if let Ok(nonce_account) = get_account(rpc_client, &nonce_account_address) {
|
||||
let err_msg = if state_from_account(&nonce_account).is_ok() {
|
||||
format!("Nonce account {} already exists", nonce_account_address)
|
||||
} else {
|
||||
@ -439,11 +429,7 @@ pub fn process_create_nonce_account(
|
||||
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
|
||||
@ -471,20 +457,17 @@ pub fn process_new_nonce(
|
||||
(&nonce_account, "nonce_account_pubkey".to_string()),
|
||||
)?;
|
||||
|
||||
let nonce_account_check =
|
||||
rpc_client.get_account_with_commitment(&nonce_account, config.commitment);
|
||||
if nonce_account_check.is_err() || nonce_account_check.unwrap().value.is_none() {
|
||||
return Err(CliError::BadParameter(
|
||||
"Unable to create new nonce, no nonce account found".to_string(),
|
||||
)
|
||||
if let Err(err) = rpc_client.get_account(&nonce_account) {
|
||||
return Err(CliError::BadParameter(format!(
|
||||
"Unable to advance nonce account {}. error: {}",
|
||||
nonce_account, err
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
let nonce_authority = config.signers[nonce_authority];
|
||||
let ix = advance_nonce_account(&nonce_account, &nonce_authority.pubkey());
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let message = Message::new(&[ix], Some(&config.signers[0].pubkey()));
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
@ -495,11 +478,7 @@ pub fn process_new_nonce(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
|
||||
@ -541,9 +520,7 @@ pub fn process_withdraw_from_nonce_account(
|
||||
destination_account_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
) -> ProcessResult {
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
|
||||
let nonce_authority = config.signers[nonce_authority];
|
||||
let ix = withdraw_nonce_account(
|
||||
@ -562,11 +539,7 @@ pub fn process_withdraw_from_nonce_account(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<NonceError>(result, &config)
|
||||
}
|
||||
|
||||
|
2967
cli/src/program.rs
Normal file
2967
cli/src/program.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,21 +1,38 @@
|
||||
use log::*;
|
||||
use solana_client::rpc_response::{RpcContactInfo, RpcLeaderSchedule};
|
||||
use solana_sdk::clock::NUM_CONSECUTIVE_LEADER_SLOTS;
|
||||
use std::net::{SocketAddr, UdpSocket};
|
||||
|
||||
pub fn get_leader_tpu(
|
||||
pub fn get_leader_tpus(
|
||||
slot_index: u64,
|
||||
num_leaders: u64,
|
||||
leader_schedule: Option<&RpcLeaderSchedule>,
|
||||
cluster_nodes: Option<&Vec<RpcContactInfo>>,
|
||||
) -> Option<SocketAddr> {
|
||||
leader_schedule?
|
||||
.iter()
|
||||
.find(|(_pubkey, slots)| slots.iter().any(|slot| *slot as u64 == slot_index))
|
||||
.and_then(|(pubkey, _)| {
|
||||
cluster_nodes?
|
||||
) -> Vec<SocketAddr> {
|
||||
let leaders: Vec<_> = (0..num_leaders)
|
||||
.filter_map(|i| {
|
||||
leader_schedule?
|
||||
.iter()
|
||||
.find(|contact_info| contact_info.pubkey == *pubkey)
|
||||
.and_then(|contact_info| contact_info.tpu)
|
||||
.find(|(_pubkey, slots)| {
|
||||
slots.iter().any(|slot| {
|
||||
*slot as u64 == (slot_index + (i * NUM_CONSECUTIVE_LEADER_SLOTS))
|
||||
})
|
||||
})
|
||||
.and_then(|(pubkey, _)| {
|
||||
cluster_nodes?
|
||||
.iter()
|
||||
.find(|contact_info| contact_info.pubkey == *pubkey)
|
||||
.and_then(|contact_info| contact_info.tpu)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let mut unique_leaders = vec![];
|
||||
for leader in leaders.into_iter() {
|
||||
if !unique_leaders.contains(&leader) {
|
||||
unique_leaders.push(leader);
|
||||
}
|
||||
}
|
||||
unique_leaders
|
||||
}
|
||||
|
||||
pub fn send_transaction_tpu(
|
||||
|
@ -107,15 +107,22 @@ where
|
||||
return Err(CliError::InsufficientFundsForSpendAndFee(
|
||||
lamports_to_sol(spend),
|
||||
lamports_to_sol(fee),
|
||||
*from_pubkey,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if from_balance < spend {
|
||||
return Err(CliError::InsufficientFundsForSpend(lamports_to_sol(spend)));
|
||||
return Err(CliError::InsufficientFundsForSpend(
|
||||
lamports_to_sol(spend),
|
||||
*from_pubkey,
|
||||
));
|
||||
}
|
||||
if !check_account_for_balance_with_commitment(rpc_client, fee_pubkey, fee, commitment)?
|
||||
{
|
||||
return Err(CliError::InsufficientFundsForFee(lamports_to_sol(fee)));
|
||||
return Err(CliError::InsufficientFundsForFee(
|
||||
lamports_to_sol(fee),
|
||||
*fee_pubkey,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok((message, spend))
|
||||
|
508
cli/src/stake.rs
508
cli/src/stake.rs
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@ use std::{thread::sleep, time::Duration};
|
||||
pub fn check_recent_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) {
|
||||
(0..5).for_each(|tries| {
|
||||
let balance = client
|
||||
.get_balance_with_commitment(pubkey, CommitmentConfig::recent())
|
||||
.get_balance_with_commitment(pubkey, CommitmentConfig::processed())
|
||||
.unwrap()
|
||||
.value;
|
||||
if balance == expected_balance {
|
||||
@ -20,7 +20,7 @@ pub fn check_recent_balance(expected_balance: u64, client: &RpcClient, pubkey: &
|
||||
|
||||
pub fn check_ready(rpc_client: &RpcClient) {
|
||||
while rpc_client
|
||||
.get_slot_with_commitment(CommitmentConfig::recent())
|
||||
.get_slot_with_commitment(CommitmentConfig::processed())
|
||||
.unwrap()
|
||||
< 5
|
||||
{
|
||||
|
@ -20,7 +20,6 @@ use solana_config_program::{config_instruction, get_config_data, ConfigKeys, Con
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
commitment_config::CommitmentConfig,
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
@ -288,9 +287,7 @@ pub fn process_set_validator_info(
|
||||
};
|
||||
|
||||
// Check existence of validator-info account
|
||||
let balance = rpc_client
|
||||
.poll_get_balance_with_commitment(&info_pubkey, CommitmentConfig::default())
|
||||
.unwrap_or(0);
|
||||
let balance = rpc_client.get_balance(&info_pubkey).unwrap_or(0);
|
||||
|
||||
let lamports =
|
||||
rpc_client.get_minimum_balance_for_rent_exemption(ValidatorInfo::max_space() as usize)?;
|
||||
|
@ -8,7 +8,6 @@ use crate::{
|
||||
};
|
||||
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{
|
||||
commitment::commitment_arg,
|
||||
input_parsers::*,
|
||||
input_validators::*,
|
||||
keypair::{DefaultSigner, SignerIndex},
|
||||
@ -209,7 +208,22 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.takes_value(false)
|
||||
.help("Display balance in lamports instead of SOL"),
|
||||
)
|
||||
.arg(commitment_arg()),
|
||||
.arg(
|
||||
Arg::with_name("with_rewards")
|
||||
.long("with-rewards")
|
||||
.takes_value(false)
|
||||
.help("Display inflation rewards"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num_rewards_epochs")
|
||||
.long("num-rewards-epochs")
|
||||
.takes_value(true)
|
||||
.value_name("NUM")
|
||||
.validator(|s| is_within_range(s, 1, 10))
|
||||
.default_value_if("with_rewards", None, "1")
|
||||
.requires("with_rewards")
|
||||
.help("Display rewards for NUM recent epochs, max 10 [default: latest epoch only]"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("withdraw-from-vote-account")
|
||||
@ -375,10 +389,16 @@ pub fn parse_vote_get_account_command(
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
let use_lamports_unit = matches.is_present("lamports");
|
||||
let with_rewards = if matches.is_present("with_rewards") {
|
||||
Some(value_of(matches, "num_rewards_epochs").unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::ShowVoteAccount {
|
||||
pubkey: vote_account_pubkey,
|
||||
use_lamports_unit,
|
||||
with_rewards,
|
||||
},
|
||||
signers: vec![],
|
||||
})
|
||||
@ -494,9 +514,7 @@ pub fn process_create_vote_account(
|
||||
}
|
||||
}
|
||||
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
|
||||
let (message, _) = resolve_spend_tx_and_check_account_balance(
|
||||
rpc_client,
|
||||
@ -509,11 +527,7 @@ pub fn process_create_vote_account(
|
||||
)?;
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
|
||||
@ -536,9 +550,7 @@ pub fn process_vote_authorize(
|
||||
(&authorized.pubkey(), "authorized_account".to_string()),
|
||||
(new_authorized_pubkey, "new_authorized_pubkey".to_string()),
|
||||
)?;
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let ixs = vec![vote_instruction::authorize(
|
||||
vote_account_pubkey, // vote account to update
|
||||
&authorized.pubkey(), // current authorized
|
||||
@ -556,11 +568,7 @@ pub fn process_vote_authorize(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
}
|
||||
|
||||
@ -578,9 +586,7 @@ pub fn process_vote_update_validator(
|
||||
(vote_account_pubkey, "vote_account_pubkey".to_string()),
|
||||
(&new_identity_pubkey, "new_identity_account".to_string()),
|
||||
)?;
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let ixs = vec![vote_instruction::update_validator_identity(
|
||||
vote_account_pubkey,
|
||||
&authorized_withdrawer.pubkey(),
|
||||
@ -597,11 +603,7 @@ pub fn process_vote_update_validator(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
}
|
||||
|
||||
@ -613,9 +615,7 @@ pub fn process_vote_update_commission(
|
||||
withdraw_authority: SignerIndex,
|
||||
) -> ProcessResult {
|
||||
let authorized_withdrawer = config.signers[withdraw_authority];
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let ixs = vec![vote_instruction::update_commission(
|
||||
vote_account_pubkey,
|
||||
&authorized_withdrawer.pubkey(),
|
||||
@ -632,11 +632,7 @@ pub fn process_vote_update_commission(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
}
|
||||
|
||||
@ -673,6 +669,7 @@ pub fn process_show_vote_account(
|
||||
config: &CliConfig,
|
||||
vote_account_address: &Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
with_rewards: Option<usize>,
|
||||
) -> ProcessResult {
|
||||
let (vote_account, vote_state) =
|
||||
get_vote_account(rpc_client, vote_account_address, config.commitment)?;
|
||||
@ -698,14 +695,16 @@ pub fn process_show_vote_account(
|
||||
}
|
||||
}
|
||||
|
||||
let epoch_rewards = match crate::stake::fetch_epoch_rewards(rpc_client, vote_account_address, 1)
|
||||
{
|
||||
Ok(rewards) => Some(rewards),
|
||||
Err(error) => {
|
||||
eprintln!("Failed to fetch epoch rewards: {:?}", error);
|
||||
None
|
||||
}
|
||||
};
|
||||
let epoch_rewards =
|
||||
with_rewards.and_then(|num_epochs| {
|
||||
match crate::stake::fetch_epoch_rewards(rpc_client, vote_account_address, num_epochs) {
|
||||
Ok(rewards) => Some(rewards),
|
||||
Err(error) => {
|
||||
eprintln!("Failed to fetch epoch rewards: {:?}", error);
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let vote_account_data = CliVoteAccount {
|
||||
account_balance: vote_account.lamports,
|
||||
@ -733,14 +732,10 @@ pub fn process_withdraw_from_vote_account(
|
||||
withdraw_amount: SpendAmount,
|
||||
destination_account_pubkey: &Pubkey,
|
||||
) -> ProcessResult {
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let withdraw_authority = config.signers[withdraw_authority];
|
||||
|
||||
let current_balance = rpc_client
|
||||
.get_balance_with_commitment(&vote_account_pubkey, config.commitment)?
|
||||
.value;
|
||||
let current_balance = rpc_client.get_balance(&vote_account_pubkey)?;
|
||||
let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(VoteState::size_of())?;
|
||||
|
||||
let lamports = match withdraw_amount {
|
||||
@ -773,11 +768,7 @@ pub fn process_withdraw_from_vote_account(
|
||||
&transaction.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&transaction,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&transaction);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
}
|
||||
|
||||
|
@ -1,422 +0,0 @@
|
||||
use serde_json::Value;
|
||||
use solana_cli::cli::{process_command, CliCommand, CliConfig};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_core::test_validator::TestValidator;
|
||||
use solana_faucet::faucet::run_local_faucet;
|
||||
use solana_sdk::{
|
||||
bpf_loader,
|
||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||
commitment_config::CommitmentConfig,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use std::{fs::File, io::Read, path::PathBuf, str::FromStr, sync::mpsc::channel};
|
||||
|
||||
#[test]
|
||||
fn test_cli_deploy_program() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mut pathbuf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
pathbuf.push("tests");
|
||||
pathbuf.push("fixtures");
|
||||
pathbuf.push("noop");
|
||||
pathbuf.set_extension("so");
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
|
||||
let mut file = File::open(pathbuf.to_str().unwrap()).unwrap();
|
||||
let mut program_data = Vec::new();
|
||||
file.read_to_end(&mut program_data).unwrap();
|
||||
let minimum_balance_for_rent_exemption = rpc_client
|
||||
.get_minimum_balance_for_rent_exemption(program_data.len())
|
||||
.unwrap();
|
||||
|
||||
let mut config = CliConfig::recent_for_tests();
|
||||
let keypair = Keypair::new();
|
||||
config.json_rpc_url = test_validator.rpc_url();
|
||||
config.command = CliCommand::Airdrop {
|
||||
faucet_host: None,
|
||||
faucet_port: faucet_addr.port(),
|
||||
pubkey: None,
|
||||
lamports: 4 * minimum_balance_for_rent_exemption, // min balance for rent exemption for three programs + leftover for tx processing
|
||||
};
|
||||
config.signers = vec![&keypair];
|
||||
process_command(&config).unwrap();
|
||||
|
||||
config.command = CliCommand::ProgramDeploy {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
buffer: None,
|
||||
use_deprecated_loader: false,
|
||||
use_upgradeable_loader: false,
|
||||
allow_excessive_balance: false,
|
||||
upgrade_authority: None,
|
||||
max_len: None,
|
||||
};
|
||||
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let program_id_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("programId")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_id = Pubkey::from_str(&program_id_str).unwrap();
|
||||
let account0 = rpc_client
|
||||
.get_account_with_commitment(&program_id, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(account0.lamports, minimum_balance_for_rent_exemption);
|
||||
assert_eq!(account0.owner, bpf_loader::id());
|
||||
assert_eq!(account0.executable, true);
|
||||
|
||||
let mut file = File::open(pathbuf.to_str().unwrap().to_string()).unwrap();
|
||||
let mut elf = Vec::new();
|
||||
file.read_to_end(&mut elf).unwrap();
|
||||
|
||||
assert_eq!(account0.data, elf);
|
||||
|
||||
// Test custom address
|
||||
let custom_address_keypair = Keypair::new();
|
||||
config.signers = vec![&keypair, &custom_address_keypair];
|
||||
config.command = CliCommand::ProgramDeploy {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
buffer: Some(1),
|
||||
use_deprecated_loader: false,
|
||||
use_upgradeable_loader: false,
|
||||
allow_excessive_balance: false,
|
||||
upgrade_authority: None,
|
||||
max_len: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let account1 = rpc_client
|
||||
.get_account_with_commitment(&custom_address_keypair.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(account1.lamports, minimum_balance_for_rent_exemption);
|
||||
assert_eq!(account1.owner, bpf_loader::id());
|
||||
assert_eq!(account1.executable, true);
|
||||
assert_eq!(account0.data, account1.data);
|
||||
|
||||
// Attempt to redeploy to the same address
|
||||
process_command(&config).unwrap_err();
|
||||
|
||||
// Attempt to deploy to account with excess balance
|
||||
let custom_address_keypair = Keypair::new();
|
||||
config.command = CliCommand::Airdrop {
|
||||
faucet_host: None,
|
||||
faucet_port: faucet_addr.port(),
|
||||
pubkey: None,
|
||||
lamports: 2 * minimum_balance_for_rent_exemption, // Anything over minimum_balance_for_rent_exemption should trigger err
|
||||
};
|
||||
config.signers = vec![&custom_address_keypair];
|
||||
process_command(&config).unwrap();
|
||||
|
||||
config.signers = vec![&keypair, &custom_address_keypair];
|
||||
config.command = CliCommand::ProgramDeploy {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
buffer: Some(1),
|
||||
use_deprecated_loader: false,
|
||||
use_upgradeable_loader: false,
|
||||
allow_excessive_balance: false,
|
||||
upgrade_authority: None,
|
||||
max_len: None,
|
||||
};
|
||||
process_command(&config).unwrap_err();
|
||||
|
||||
// Use forcing parameter to deploy to account with excess balance
|
||||
config.command = CliCommand::ProgramDeploy {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
buffer: Some(1),
|
||||
use_deprecated_loader: false,
|
||||
use_upgradeable_loader: false,
|
||||
allow_excessive_balance: true,
|
||||
upgrade_authority: None,
|
||||
max_len: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let account2 = rpc_client
|
||||
.get_account_with_commitment(&custom_address_keypair.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(account2.lamports, 2 * minimum_balance_for_rent_exemption);
|
||||
assert_eq!(account2.owner, bpf_loader::id());
|
||||
assert_eq!(account2.executable, true);
|
||||
assert_eq!(account0.data, account2.data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_deploy_upgradeable_program() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mut pathbuf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
pathbuf.push("tests");
|
||||
pathbuf.push("fixtures");
|
||||
pathbuf.push("noop");
|
||||
pathbuf.set_extension("so");
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
|
||||
let mut file = File::open(pathbuf.to_str().unwrap()).unwrap();
|
||||
let mut program_data = Vec::new();
|
||||
file.read_to_end(&mut program_data).unwrap();
|
||||
let max_len = program_data.len();
|
||||
println!(
|
||||
"max_len {:?} {:?}",
|
||||
max_len,
|
||||
UpgradeableLoaderState::programdata_len(max_len)
|
||||
);
|
||||
let minimum_balance_for_programdata = rpc_client
|
||||
.get_minimum_balance_for_rent_exemption(
|
||||
UpgradeableLoaderState::programdata_len(max_len).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let minimum_balance_for_program = rpc_client
|
||||
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::program_len().unwrap())
|
||||
.unwrap();
|
||||
let upgrade_authority = Keypair::new();
|
||||
|
||||
let mut config = CliConfig::recent_for_tests();
|
||||
let keypair = Keypair::new();
|
||||
config.json_rpc_url = test_validator.rpc_url();
|
||||
config.command = CliCommand::Airdrop {
|
||||
faucet_host: None,
|
||||
faucet_port: faucet_addr.port(),
|
||||
pubkey: None,
|
||||
lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
|
||||
};
|
||||
config.signers = vec![&keypair];
|
||||
process_command(&config).unwrap();
|
||||
|
||||
// Deploy and attempt to upgrade a non-upgradeable program
|
||||
config.command = CliCommand::ProgramDeploy {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
buffer: None,
|
||||
use_deprecated_loader: false,
|
||||
use_upgradeable_loader: true,
|
||||
allow_excessive_balance: false,
|
||||
upgrade_authority: None,
|
||||
max_len: Some(max_len),
|
||||
};
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let program_id_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("programId")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_id = Pubkey::from_str(&program_id_str).unwrap();
|
||||
|
||||
config.signers = vec![&keypair, &upgrade_authority];
|
||||
config.command = CliCommand::ProgramUpgrade {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
program: program_id,
|
||||
buffer: None,
|
||||
upgrade_authority: 1,
|
||||
};
|
||||
process_command(&config).unwrap_err();
|
||||
|
||||
// Deploy the upgradeable program
|
||||
config.command = CliCommand::ProgramDeploy {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
buffer: None,
|
||||
use_deprecated_loader: false,
|
||||
use_upgradeable_loader: true,
|
||||
allow_excessive_balance: false,
|
||||
upgrade_authority: Some(upgrade_authority.pubkey()),
|
||||
max_len: Some(max_len),
|
||||
};
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let program_id_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("programId")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_id = Pubkey::from_str(&program_id_str).unwrap();
|
||||
let program_account = rpc_client
|
||||
.get_account_with_commitment(&program_id, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(program_account.lamports, minimum_balance_for_program);
|
||||
assert_eq!(program_account.owner, bpf_loader_upgradeable::id());
|
||||
assert_eq!(program_account.executable, true);
|
||||
let (programdata_pubkey, _) =
|
||||
Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader_upgradeable::id());
|
||||
let programdata_account = rpc_client
|
||||
.get_account_with_commitment(&programdata_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
programdata_account.lamports,
|
||||
minimum_balance_for_programdata
|
||||
);
|
||||
assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id());
|
||||
assert_eq!(programdata_account.executable, false);
|
||||
assert_eq!(
|
||||
programdata_account.data[UpgradeableLoaderState::programdata_data_offset().unwrap()..],
|
||||
program_data[..]
|
||||
);
|
||||
|
||||
// Upgrade the program
|
||||
config.signers = vec![&keypair, &upgrade_authority];
|
||||
config.command = CliCommand::ProgramUpgrade {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
program: program_id,
|
||||
buffer: None,
|
||||
upgrade_authority: 1,
|
||||
};
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let program_id_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("programId")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_id = Pubkey::from_str(&program_id_str).unwrap();
|
||||
let program_account = rpc_client
|
||||
.get_account_with_commitment(&program_id, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(program_account.lamports, minimum_balance_for_program);
|
||||
assert_eq!(program_account.owner, bpf_loader_upgradeable::id());
|
||||
assert_eq!(program_account.executable, true);
|
||||
let (programdata_pubkey, _) =
|
||||
Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader_upgradeable::id());
|
||||
let programdata_account = rpc_client
|
||||
.get_account_with_commitment(&programdata_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
programdata_account.lamports,
|
||||
minimum_balance_for_programdata
|
||||
);
|
||||
assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id());
|
||||
assert_eq!(programdata_account.executable, false);
|
||||
assert_eq!(
|
||||
programdata_account.data[UpgradeableLoaderState::programdata_data_offset().unwrap()..],
|
||||
program_data[..]
|
||||
);
|
||||
|
||||
// Set a new authority
|
||||
let new_upgrade_authority = Keypair::new();
|
||||
config.signers = vec![&keypair, &upgrade_authority];
|
||||
config.command = CliCommand::SetProgramUpgradeAuthority {
|
||||
program: program_id,
|
||||
upgrade_authority: 1,
|
||||
new_upgrade_authority: Some(new_upgrade_authority.pubkey()),
|
||||
};
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let new_upgrade_authority_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("UpgradeAuthority")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Pubkey::from_str(&new_upgrade_authority_str).unwrap(),
|
||||
new_upgrade_authority.pubkey()
|
||||
);
|
||||
|
||||
// Upgrade with new authority
|
||||
config.signers = vec![&keypair, &new_upgrade_authority];
|
||||
config.command = CliCommand::ProgramUpgrade {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
program: program_id,
|
||||
buffer: None,
|
||||
upgrade_authority: 1,
|
||||
};
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let program_id_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("programId")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_id = Pubkey::from_str(&program_id_str).unwrap();
|
||||
let program_account = rpc_client
|
||||
.get_account_with_commitment(&program_id, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(program_account.lamports, minimum_balance_for_program);
|
||||
assert_eq!(program_account.owner, bpf_loader_upgradeable::id());
|
||||
assert_eq!(program_account.executable, true);
|
||||
let (programdata_pubkey, _) =
|
||||
Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader_upgradeable::id());
|
||||
let programdata_account = rpc_client
|
||||
.get_account_with_commitment(&programdata_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
programdata_account.lamports,
|
||||
minimum_balance_for_programdata
|
||||
);
|
||||
assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id());
|
||||
assert_eq!(programdata_account.executable, false);
|
||||
assert_eq!(
|
||||
programdata_account.data[UpgradeableLoaderState::programdata_data_offset().unwrap()..],
|
||||
program_data[..]
|
||||
);
|
||||
|
||||
// Set a no authority
|
||||
config.signers = vec![&keypair, &new_upgrade_authority];
|
||||
config.command = CliCommand::SetProgramUpgradeAuthority {
|
||||
program: program_id,
|
||||
upgrade_authority: 1,
|
||||
new_upgrade_authority: None,
|
||||
};
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let new_upgrade_authority_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("UpgradeAuthority")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
assert_eq!(new_upgrade_authority_str, "None");
|
||||
|
||||
// Upgrade with no authority
|
||||
config.signers = vec![&keypair, &new_upgrade_authority];
|
||||
config.command = CliCommand::ProgramUpgrade {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
program: program_id,
|
||||
buffer: None,
|
||||
upgrade_authority: 1,
|
||||
};
|
||||
process_command(&config).unwrap_err();
|
||||
}
|
@ -18,52 +18,44 @@ use solana_sdk::{
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
system_program,
|
||||
};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
fn test_nonce() {
|
||||
let mint_keypair = Keypair::new();
|
||||
full_battery_tests(
|
||||
TestValidator::with_no_fees(mint_keypair.pubkey()),
|
||||
mint_keypair,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
|
||||
full_battery_tests(test_validator, None, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonce_with_seed() {
|
||||
let mint_keypair = Keypair::new();
|
||||
full_battery_tests(
|
||||
TestValidator::with_no_fees(mint_keypair.pubkey()),
|
||||
mint_keypair,
|
||||
Some(String::from("seed")),
|
||||
false,
|
||||
);
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
|
||||
full_battery_tests(test_validator, Some(String::from("seed")), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonce_with_authority() {
|
||||
let mint_keypair = Keypair::new();
|
||||
full_battery_tests(
|
||||
TestValidator::with_no_fees(mint_keypair.pubkey()),
|
||||
mint_keypair,
|
||||
None,
|
||||
true,
|
||||
);
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
|
||||
full_battery_tests(test_validator, None, true);
|
||||
}
|
||||
|
||||
fn full_battery_tests(
|
||||
test_validator: TestValidator,
|
||||
mint_keypair: Keypair,
|
||||
seed: Option<String>,
|
||||
use_nonce_authority: bool,
|
||||
) {
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let json_rpc_url = test_validator.rpc_url();
|
||||
|
||||
let mut config_payer = CliConfig::recent_for_tests();
|
||||
@ -73,10 +65,9 @@ fn full_battery_tests(
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config_payer,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
2000,
|
||||
&config_payer,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(2000, &rpc_client, &config_payer.signers[0].pubkey());
|
||||
@ -216,33 +207,29 @@ fn full_battery_tests(
|
||||
fn test_create_account_with_seed() {
|
||||
solana_logger::setup();
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
|
||||
let offline_nonce_authority_signer = keypair_from_seed(&[1u8; 32]).unwrap();
|
||||
let online_nonce_creator_signer = keypair_from_seed(&[2u8; 32]).unwrap();
|
||||
let to_address = Pubkey::new(&[3u8; 32]);
|
||||
let config = CliConfig::recent_for_tests();
|
||||
|
||||
// Setup accounts
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&CliConfig::recent_for_tests(),
|
||||
&offline_nonce_authority_signer.pubkey(),
|
||||
42,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&CliConfig::recent_for_tests(),
|
||||
&online_nonce_creator_signer.pubkey(),
|
||||
4242,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(42, &rpc_client, &offline_nonce_authority_signer.pubkey());
|
||||
@ -278,7 +265,7 @@ fn test_create_account_with_seed() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_address,
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@ -296,11 +283,14 @@ fn test_create_account_with_seed() {
|
||||
to: to_address,
|
||||
from: 0,
|
||||
sign_only: true,
|
||||
dump_transaction_message: true,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_address),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
authority_config.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&authority_config).unwrap();
|
||||
@ -317,6 +307,7 @@ fn test_create_account_with_seed() {
|
||||
to: to_address,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: true,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_address),
|
||||
@ -325,6 +316,8 @@ fn test_create_account_with_seed() {
|
||||
nonce_account: Some(nonce_address),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&submit_config).unwrap();
|
||||
check_recent_balance(241, &rpc_client, &nonce_address);
|
||||
|
1282
cli/tests/program.rs
Normal file
1282
cli/tests/program.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,22 +6,17 @@ use solana_sdk::{
|
||||
commitment_config::CommitmentConfig,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
fn test_cli_request_airdrop() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
|
||||
let mut bob_config = CliConfig::recent_for_tests();
|
||||
bob_config.json_rpc_url = test_validator.rpc_url();
|
||||
bob_config.command = CliCommand::Airdrop {
|
||||
faucet_host: None,
|
||||
faucet_port: faucet_addr.port(),
|
||||
pubkey: None,
|
||||
lamports: 50,
|
||||
};
|
||||
@ -31,11 +26,11 @@ fn test_cli_request_airdrop() {
|
||||
let sig_response = process_command(&bob_config);
|
||||
sig_response.unwrap();
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let balance = rpc_client
|
||||
.get_balance_with_commitment(&bob_config.signers[0].pubkey(), CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value;
|
||||
.get_balance(&bob_config.signers[0].pubkey())
|
||||
.unwrap();
|
||||
assert_eq!(balance, 50);
|
||||
}
|
||||
|
@ -22,31 +22,24 @@ use solana_stake_program::{
|
||||
stake_instruction::LockupArgs,
|
||||
stake_state::{Lockup, StakeAuthorize, StakeState},
|
||||
};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
fn test_stake_delegation_force() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let default_signer = Keypair::new();
|
||||
|
||||
let mut config = CliConfig::recent_for_tests();
|
||||
config.json_rpc_url = test_validator.rpc_url();
|
||||
config.signers = vec![&default_signer];
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 100_000)
|
||||
.unwrap();
|
||||
|
||||
// Create vote account
|
||||
let vote_keypair = Keypair::new();
|
||||
@ -72,6 +65,7 @@ fn test_stake_delegation_force() {
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -88,6 +82,7 @@ fn test_stake_delegation_force() {
|
||||
stake_authority: 0,
|
||||
force: false,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -102,6 +97,7 @@ fn test_stake_delegation_force() {
|
||||
stake_authority: 0,
|
||||
force: true,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -115,12 +111,12 @@ fn test_seed_stake_delegation_and_deactivation() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let validator_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
|
||||
let mut config_validator = CliConfig::recent_for_tests();
|
||||
@ -129,10 +125,9 @@ fn test_seed_stake_delegation_and_deactivation() {
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config_validator,
|
||||
&config_validator.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config_validator,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
@ -154,6 +149,7 @@ fn test_seed_stake_delegation_and_deactivation() {
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -169,6 +165,7 @@ fn test_seed_stake_delegation_and_deactivation() {
|
||||
stake_authority: 0,
|
||||
force: true,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -181,6 +178,7 @@ fn test_seed_stake_delegation_and_deactivation() {
|
||||
stake_account_pubkey: stake_address,
|
||||
stake_authority: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -194,12 +192,12 @@ fn test_stake_delegation_and_deactivation() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let validator_keypair = Keypair::new();
|
||||
|
||||
let mut config_validator = CliConfig::recent_for_tests();
|
||||
@ -210,10 +208,9 @@ fn test_stake_delegation_and_deactivation() {
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config_validator,
|
||||
&config_validator.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config_validator,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
@ -228,6 +225,7 @@ fn test_stake_delegation_and_deactivation() {
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -244,6 +242,7 @@ fn test_stake_delegation_and_deactivation() {
|
||||
stake_authority: 0,
|
||||
force: true,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -256,6 +255,7 @@ fn test_stake_delegation_and_deactivation() {
|
||||
stake_account_pubkey: stake_keypair.pubkey(),
|
||||
stake_authority: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -269,12 +269,12 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let mut config_validator = CliConfig::recent_for_tests();
|
||||
config_validator.json_rpc_url = test_validator.rpc_url();
|
||||
@ -291,25 +291,23 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
let offline_keypair = Keypair::new();
|
||||
config_offline.signers = vec![&offline_keypair];
|
||||
// Verfiy that we cannot reach the cluster
|
||||
// Verify that we cannot reach the cluster
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config_validator,
|
||||
&config_validator.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config_offline,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config_offline,
|
||||
&config_offline.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config_validator,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &config_offline.signers[0].pubkey());
|
||||
@ -324,6 +322,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -340,6 +339,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
stake_authority: 0,
|
||||
force: true,
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -359,6 +359,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
stake_authority: 0,
|
||||
force: true,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -372,6 +373,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
stake_account_pubkey: stake_keypair.pubkey(),
|
||||
stake_authority: 0,
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -388,6 +390,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
stake_account_pubkey: stake_keypair.pubkey(),
|
||||
stake_authority: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -401,12 +404,12 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let config_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
|
||||
let mut config = CliConfig::recent_for_tests();
|
||||
@ -417,14 +420,8 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
.get_minimum_balance_for_rent_exemption(NonceState::size())
|
||||
.unwrap();
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 100_000)
|
||||
.unwrap();
|
||||
|
||||
// Create stake account
|
||||
let stake_keypair = Keypair::new();
|
||||
@ -437,6 +434,7 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -460,7 +458,7 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@ -474,6 +472,7 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
stake_authority: 0,
|
||||
force: true,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
nonce_hash,
|
||||
@ -488,7 +487,7 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@ -499,6 +498,7 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
stake_account_pubkey: stake_keypair.pubkey(),
|
||||
stake_authority: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
nonce_hash,
|
||||
@ -515,26 +515,20 @@ fn test_stake_authorize() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let default_signer = Keypair::new();
|
||||
|
||||
let mut config = CliConfig::recent_for_tests();
|
||||
config.json_rpc_url = test_validator.rpc_url();
|
||||
config.signers = vec![&default_signer];
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 100_000)
|
||||
.unwrap();
|
||||
|
||||
let offline_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
|
||||
let mut config_offline = CliConfig::recent_for_tests();
|
||||
@ -542,15 +536,14 @@ fn test_stake_authorize() {
|
||||
config_offline.json_rpc_url = String::default();
|
||||
let offline_authority_pubkey = config_offline.signers[0].pubkey();
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
// Verfiy that we cannot reach the cluster
|
||||
// Verify that we cannot reach the cluster
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config_offline,
|
||||
&config_offline.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@ -566,6 +559,7 @@ fn test_stake_authorize() {
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -582,17 +576,15 @@ fn test_stake_authorize() {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 0)],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let current_authority = match stake_state {
|
||||
StakeState::Initialized(meta) => meta.authorized.staker,
|
||||
@ -613,17 +605,15 @@ fn test_stake_authorize() {
|
||||
(StakeAuthorize::Withdrawer, withdraw_authority_pubkey, 0),
|
||||
],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let (current_staker, current_withdrawer) = match stake_state {
|
||||
StakeState::Initialized(meta) => (meta.authorized.staker, meta.authorized.withdrawer),
|
||||
@ -639,17 +629,15 @@ fn test_stake_authorize() {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, offline_authority_pubkey, 1)],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let current_authority = match stake_state {
|
||||
StakeState::Initialized(meta) => meta.authorized.staker,
|
||||
@ -660,18 +648,17 @@ fn test_stake_authorize() {
|
||||
// Offline assignment of new nonced stake authority
|
||||
let nonced_authority = Keypair::new();
|
||||
let nonced_authority_pubkey = nonced_authority.pubkey();
|
||||
let (blockhash, _, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value;
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, nonced_authority_pubkey, 0)],
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sign_reply = process_command(&config_offline).unwrap();
|
||||
@ -683,17 +670,15 @@ fn test_stake_authorize() {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, nonced_authority_pubkey, 0)],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let current_authority = match stake_state {
|
||||
StakeState::Initialized(meta) => meta.authorized.staker,
|
||||
@ -719,7 +704,7 @@ fn test_stake_authorize() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@ -733,10 +718,12 @@ fn test_stake_authorize() {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 1)],
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
let sign_reply = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_reply);
|
||||
@ -749,6 +736,7 @@ fn test_stake_authorize() {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 1)],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
sign_only.blockhash,
|
||||
@ -756,13 +744,10 @@ fn test_stake_authorize() {
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let current_authority = match stake_state {
|
||||
StakeState::Initialized(meta) => meta.authorized.staker,
|
||||
@ -773,7 +758,7 @@ fn test_stake_authorize() {
|
||||
let new_nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@ -787,12 +772,12 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
const SIG_FEE: u64 = 42;
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), SIG_FEE);
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, SIG_FEE, Some(faucet_addr));
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let default_signer = Keypair::new();
|
||||
let default_pubkey = default_signer.pubkey();
|
||||
|
||||
@ -815,16 +800,13 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &default_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &default_pubkey, 100_000).unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &payer_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config_payer, &payer_pubkey, 100_000).unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &payer_pubkey);
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
check_ready(&rpc_client);
|
||||
@ -841,6 +823,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -857,10 +840,12 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, offline_pubkey, 0)],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 1,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
// `config` balance has not changed, despite submitting the TX
|
||||
@ -870,18 +855,17 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
check_recent_balance(100_000 - SIG_FEE - SIG_FEE, &rpc_client, &payer_pubkey);
|
||||
|
||||
// Assign authority with offline fee payer
|
||||
let (blockhash, _, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value;
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, payer_pubkey, 0)],
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sign_reply = process_command(&config_offline).unwrap();
|
||||
@ -893,10 +877,12 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, payer_pubkey, 0)],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
// `config`'s balance again has not changed
|
||||
@ -911,12 +897,12 @@ fn test_stake_split() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let default_signer = Keypair::new();
|
||||
let offline_signer = Keypair::new();
|
||||
|
||||
@ -932,18 +918,11 @@ fn test_stake_split() {
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
500_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 500_000)
|
||||
.unwrap();
|
||||
check_recent_balance(500_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create stake account, identity is authority
|
||||
@ -961,6 +940,7 @@ fn test_stake_split() {
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(10 * minimum_stake_balance),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -993,7 +973,7 @@ fn test_stake_split() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@ -1007,6 +987,7 @@ fn test_stake_split() {
|
||||
stake_account_pubkey,
|
||||
stake_authority: 0,
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
@ -1025,6 +1006,7 @@ fn test_stake_split() {
|
||||
stake_account_pubkey,
|
||||
stake_authority: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
sign_only.blockhash,
|
||||
@ -1054,12 +1036,12 @@ fn test_stake_set_lockup() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let default_signer = Keypair::new();
|
||||
let offline_signer = Keypair::new();
|
||||
|
||||
@ -1075,18 +1057,11 @@ fn test_stake_set_lockup() {
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
500_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 500_000)
|
||||
.unwrap();
|
||||
check_recent_balance(500_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create stake account, identity is authority
|
||||
@ -1111,6 +1086,7 @@ fn test_stake_set_lockup() {
|
||||
lockup,
|
||||
amount: SpendAmount::Some(10 * minimum_stake_balance),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -1136,17 +1112,14 @@ fn test_stake_set_lockup() {
|
||||
lockup,
|
||||
custodian: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let current_lockup = match stake_state {
|
||||
StakeState::Initialized(meta) => meta.lockup,
|
||||
@ -1173,6 +1146,7 @@ fn test_stake_set_lockup() {
|
||||
lockup,
|
||||
custodian: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -1191,17 +1165,14 @@ fn test_stake_set_lockup() {
|
||||
lockup,
|
||||
custodian: 1,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let current_lockup = match stake_state {
|
||||
StakeState::Initialized(meta) => meta.lockup,
|
||||
@ -1225,6 +1196,7 @@ fn test_stake_set_lockup() {
|
||||
lockup,
|
||||
custodian: 1,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@ -1252,7 +1224,7 @@ fn test_stake_set_lockup() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@ -1269,6 +1241,7 @@ fn test_stake_set_lockup() {
|
||||
lockup,
|
||||
custodian: 0,
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_account_pubkey),
|
||||
nonce_authority: 0,
|
||||
@ -1285,6 +1258,7 @@ fn test_stake_set_lockup() {
|
||||
lockup,
|
||||
custodian: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account_pubkey),
|
||||
sign_only.blockhash,
|
||||
@ -1294,11 +1268,7 @@ fn test_stake_set_lockup() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let current_lockup = match stake_state {
|
||||
StakeState::Initialized(meta) => meta.lockup,
|
||||
@ -1317,12 +1287,12 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let mut config = CliConfig::recent_for_tests();
|
||||
let default_signer = keypair_from_seed(&[1u8; 32]).unwrap();
|
||||
config.signers = vec![&default_signer];
|
||||
@ -1334,21 +1304,14 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
let offline_pubkey = config_offline.signers[0].pubkey();
|
||||
config_offline.json_rpc_url = String::default();
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
// Verfiy that we cannot reach the cluster
|
||||
// Verify that we cannot reach the cluster
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
200_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 200_000)
|
||||
.unwrap();
|
||||
check_recent_balance(200_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create nonce account
|
||||
@ -1370,7 +1333,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@ -1388,6 +1351,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_pubkey),
|
||||
nonce_authority: 0,
|
||||
@ -1409,6 +1373,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_pubkey),
|
||||
sign_only.blockhash,
|
||||
@ -1425,7 +1390,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@ -1442,6 +1407,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
withdraw_authority: 0,
|
||||
custodian: None,
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_pubkey),
|
||||
nonce_authority: 0,
|
||||
@ -1458,6 +1424,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
withdraw_authority: 0,
|
||||
custodian: None,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_pubkey),
|
||||
sign_only.blockhash,
|
||||
@ -1473,7 +1440,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@ -1490,6 +1457,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_pubkey),
|
||||
nonce_authority: 0,
|
||||
@ -1509,6 +1477,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_pubkey),
|
||||
sign_only.blockhash,
|
||||
|
@ -17,19 +17,17 @@ use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, Keypair, NullSigner, Signer},
|
||||
};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
fn test_transfer() {
|
||||
solana_logger::setup();
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let default_signer = Keypair::new();
|
||||
let default_offline_signer = Keypair::new();
|
||||
@ -41,8 +39,7 @@ fn test_transfer() {
|
||||
let sender_pubkey = config.signers[0].pubkey();
|
||||
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 50_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 50_000).unwrap();
|
||||
check_recent_balance(50_000, &rpc_client, &sender_pubkey);
|
||||
check_recent_balance(0, &rpc_client, &recipient_pubkey);
|
||||
|
||||
@ -54,11 +51,14 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(49_989, &rpc_client, &sender_pubkey);
|
||||
@ -70,11 +70,14 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
assert!(process_command(&config).is_err());
|
||||
check_recent_balance(49_989, &rpc_client, &sender_pubkey);
|
||||
@ -88,24 +91,24 @@ fn test_transfer() {
|
||||
process_command(&offline).unwrap_err();
|
||||
|
||||
let offline_pubkey = offline.signers[0].pubkey();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 50, &config).unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &offline, &offline_pubkey, 50).unwrap();
|
||||
check_recent_balance(50, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Offline transfer
|
||||
let (blockhash, _, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value;
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
offline.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(10),
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
offline.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&offline).unwrap();
|
||||
@ -118,11 +121,14 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(39, &rpc_client, &offline_pubkey);
|
||||
@ -147,7 +153,7 @@ fn test_transfer() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@ -160,6 +166,7 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
@ -168,6 +175,8 @@ fn test_transfer() {
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(49_976 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
|
||||
@ -175,7 +184,7 @@ fn test_transfer() {
|
||||
let new_nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@ -196,7 +205,7 @@ fn test_transfer() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@ -209,11 +218,14 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
let sign_only_reply = process_command(&offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
@ -225,6 +237,7 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
@ -233,6 +246,8 @@ fn test_transfer() {
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(28, &rpc_client, &offline_pubkey);
|
||||
@ -243,34 +258,30 @@ fn test_transfer() {
|
||||
fn test_transfer_multisession_signing() {
|
||||
solana_logger::setup();
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
|
||||
let to_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
let offline_from_signer = keypair_from_seed(&[2u8; 32]).unwrap();
|
||||
let offline_fee_payer_signer = keypair_from_seed(&[3u8; 32]).unwrap();
|
||||
let from_null_signer = NullSigner::new(&offline_from_signer.pubkey());
|
||||
let config = CliConfig::recent_for_tests();
|
||||
|
||||
// Setup accounts
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&CliConfig::recent_for_tests(),
|
||||
&offline_from_signer.pubkey(),
|
||||
43,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&CliConfig::recent_for_tests(),
|
||||
&offline_fee_payer_signer.pubkey(),
|
||||
3,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(43, &rpc_client, &offline_from_signer.pubkey());
|
||||
@ -279,10 +290,7 @@ fn test_transfer_multisession_signing() {
|
||||
|
||||
check_ready(&rpc_client);
|
||||
|
||||
let (blockhash, _, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value;
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
|
||||
// Offline fee-payer signs first
|
||||
let mut fee_payer_config = CliConfig::recent_for_tests();
|
||||
@ -296,11 +304,14 @@ fn test_transfer_multisession_signing() {
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
fee_payer_config.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&fee_payer_config).unwrap();
|
||||
@ -322,11 +333,14 @@ fn test_transfer_multisession_signing() {
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
from_config.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&from_config).unwrap();
|
||||
@ -345,11 +359,14 @@ fn test_transfer_multisession_signing() {
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
|
||||
@ -362,13 +379,12 @@ fn test_transfer_multisession_signing() {
|
||||
fn test_transfer_all() {
|
||||
solana_logger::setup();
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let default_signer = Keypair::new();
|
||||
|
||||
@ -379,8 +395,7 @@ fn test_transfer_all() {
|
||||
let sender_pubkey = config.signers[0].pubkey();
|
||||
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 50_000, &config)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 50_000).unwrap();
|
||||
check_recent_balance(50_000, &rpc_client, &sender_pubkey);
|
||||
check_recent_balance(0, &rpc_client, &recipient_pubkey);
|
||||
|
||||
@ -392,13 +407,73 @@ fn test_transfer_all() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(0, &rpc_client, &sender_pubkey);
|
||||
check_recent_balance(49_999, &rpc_client, &recipient_pubkey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transfer_with_seed() {
|
||||
solana_logger::setup();
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let default_signer = Keypair::new();
|
||||
|
||||
let mut config = CliConfig::recent_for_tests();
|
||||
config.json_rpc_url = test_validator.rpc_url();
|
||||
config.signers = vec![&default_signer];
|
||||
|
||||
let sender_pubkey = config.signers[0].pubkey();
|
||||
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
let derived_address_seed = "seed".to_string();
|
||||
let derived_address_program_id = solana_stake_program::id();
|
||||
let derived_address = Pubkey::create_with_seed(
|
||||
&sender_pubkey,
|
||||
&derived_address_seed,
|
||||
&derived_address_program_id,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 1).unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &derived_address, 50_000).unwrap();
|
||||
check_recent_balance(1, &rpc_client, &sender_pubkey);
|
||||
check_recent_balance(50_000, &rpc_client, &derived_address);
|
||||
check_recent_balance(0, &rpc_client, &recipient_pubkey);
|
||||
|
||||
check_ready(&rpc_client);
|
||||
|
||||
// Transfer with seed
|
||||
config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(50_000),
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: Some(derived_address_seed),
|
||||
derived_address_program_id: Some(derived_address_program_id),
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(0, &rpc_client, &sender_pubkey);
|
||||
check_recent_balance(50_000, &rpc_client, &recipient_pubkey);
|
||||
check_recent_balance(0, &rpc_client, &derived_address);
|
||||
}
|
||||
|
@ -15,31 +15,24 @@ use solana_sdk::{
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use solana_vote_program::vote_state::{VoteAuthorize, VoteState, VoteStateVersions};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
fn test_vote_authorize_and_withdraw() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let default_signer = Keypair::new();
|
||||
|
||||
let mut config = CliConfig::recent_for_tests();
|
||||
config.json_rpc_url = test_validator.rpc_url();
|
||||
config.signers = vec![&default_signer];
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 100_000)
|
||||
.unwrap();
|
||||
|
||||
// Create vote account
|
||||
let vote_account_keypair = Keypair::new();
|
||||
@ -55,9 +48,7 @@ fn test_vote_authorize_and_withdraw() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let vote_account = rpc_client
|
||||
.get_account_with_commitment(&vote_account_keypair.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.get_account(&vote_account_keypair.pubkey())
|
||||
.unwrap();
|
||||
let vote_state: VoteStateVersions = vote_account.state().unwrap();
|
||||
let authorized_withdrawer = vote_state.convert_to_current().authorized_withdrawer;
|
||||
@ -75,11 +66,14 @@ fn test_vote_authorize_and_withdraw() {
|
||||
to: vote_account_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let expected_balance = expected_balance + 1_000;
|
||||
@ -95,9 +89,7 @@ fn test_vote_authorize_and_withdraw() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let vote_account = rpc_client
|
||||
.get_account_with_commitment(&vote_account_keypair.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.get_account(&vote_account_keypair.pubkey())
|
||||
.unwrap();
|
||||
let vote_state: VoteStateVersions = vote_account.state().unwrap();
|
||||
let authorized_withdrawer = vote_state.convert_to_current().authorized_withdrawer;
|
||||
|
@ -1,10 +1,11 @@
|
||||
[package]
|
||||
name = "solana-client"
|
||||
version = "1.5.0"
|
||||
version = "1.5.20"
|
||||
description = "Solana Client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-client"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
@ -20,16 +21,17 @@ net2 = "0.2.37"
|
||||
rayon = "1.4.0"
|
||||
reqwest = { version = "0.10.8", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
semver = "0.11.0"
|
||||
serde = "1.0.112"
|
||||
serde = "1.0.118"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.0" }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.5.20" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.5.20" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.5.20" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.5.20" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.5.20" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.5.20" }
|
||||
solana-version = { path = "../version", version = "=1.5.20" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.5.20" }
|
||||
thiserror = "1.0"
|
||||
tungstenite = "0.10.1"
|
||||
url = "2.1.1"
|
||||
@ -38,7 +40,7 @@ url = "2.1.1"
|
||||
assert_matches = "1.3.0"
|
||||
jsonrpc-core = "15.0.0"
|
||||
jsonrpc-http-server = "15.0.0"
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.5.20" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -1,10 +1,13 @@
|
||||
use crate::rpc_request;
|
||||
use solana_faucet::faucet::FaucetError;
|
||||
use solana_sdk::{
|
||||
signature::SignerError, transaction::TransactionError, transport::TransportError,
|
||||
};
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use reqwest; // export `reqwest` for clients
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ClientErrorKind {
|
||||
#[error(transparent)]
|
||||
@ -19,6 +22,8 @@ pub enum ClientErrorKind {
|
||||
SigningError(#[from] SignerError),
|
||||
#[error(transparent)]
|
||||
TransactionError(#[from] TransactionError),
|
||||
#[error(transparent)]
|
||||
FaucetError(#[from] FaucetError),
|
||||
#[error("Custom: {0}")]
|
||||
Custom(String),
|
||||
}
|
||||
@ -33,16 +38,17 @@ impl From<TransportError> for ClientErrorKind {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<TransportError> for ClientErrorKind {
|
||||
fn into(self) -> TransportError {
|
||||
match self {
|
||||
Self::Io(err) => TransportError::IoError(err),
|
||||
Self::TransactionError(err) => TransportError::TransactionError(err),
|
||||
Self::Reqwest(err) => TransportError::Custom(format!("{:?}", err)),
|
||||
Self::RpcError(err) => TransportError::Custom(format!("{:?}", err)),
|
||||
Self::SerdeJson(err) => TransportError::Custom(format!("{:?}", err)),
|
||||
Self::SigningError(err) => TransportError::Custom(format!("{:?}", err)),
|
||||
Self::Custom(err) => TransportError::Custom(format!("{:?}", err)),
|
||||
impl From<ClientErrorKind> for TransportError {
|
||||
fn from(client_error_kind: ClientErrorKind) -> Self {
|
||||
match client_error_kind {
|
||||
ClientErrorKind::Io(err) => Self::IoError(err),
|
||||
ClientErrorKind::TransactionError(err) => Self::TransactionError(err),
|
||||
ClientErrorKind::Reqwest(err) => Self::Custom(format!("{:?}", err)),
|
||||
ClientErrorKind::RpcError(err) => Self::Custom(format!("{:?}", err)),
|
||||
ClientErrorKind::SerdeJson(err) => Self::Custom(format!("{:?}", err)),
|
||||
ClientErrorKind::SigningError(err) => Self::Custom(format!("{:?}", err)),
|
||||
ClientErrorKind::FaucetError(err) => Self::Custom(format!("{:?}", err)),
|
||||
ClientErrorKind::Custom(err) => Self::Custom(format!("{:?}", err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -98,9 +104,9 @@ impl From<TransportError> for ClientError {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<TransportError> for ClientError {
|
||||
fn into(self) -> TransportError {
|
||||
self.kind.into()
|
||||
impl From<ClientError> for TransportError {
|
||||
fn from(client_error: ClientError) -> Self {
|
||||
client_error.kind.into()
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,4 +164,13 @@ impl From<TransactionError> for ClientError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FaucetError> for ClientError {
|
||||
fn from(err: FaucetError) -> Self {
|
||||
Self {
|
||||
request: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, ClientError>;
|
||||
|
@ -85,6 +85,14 @@ impl RpcSender for HttpSender {
|
||||
}
|
||||
}
|
||||
},
|
||||
rpc_custom_error::JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY => {
|
||||
match serde_json::from_value::<rpc_custom_error::NodeUnhealthyErrorData>(json["error"]["data"].clone()) {
|
||||
Ok(rpc_custom_error::NodeUnhealthyErrorData {num_slots_behind}) => RpcResponseErrorData::NodeUnhealthy {num_slots_behind},
|
||||
Err(_err) => {
|
||||
RpcResponseErrorData::Empty
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => RpcResponseErrorData::Empty
|
||||
};
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
@ -8,6 +9,7 @@ pub mod mock_sender;
|
||||
pub mod nonce_utils;
|
||||
pub mod perf_utils;
|
||||
pub mod pubsub_client;
|
||||
pub mod rpc_cache;
|
||||
pub mod rpc_client;
|
||||
pub mod rpc_config;
|
||||
pub mod rpc_custom_error;
|
||||
|
@ -12,7 +12,7 @@ use solana_sdk::{
|
||||
signature::Signature,
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
};
|
||||
use solana_transaction_status::TransactionStatus;
|
||||
use solana_transaction_status::{TransactionConfirmationStatus, TransactionStatus};
|
||||
use solana_version::Version;
|
||||
use std::{collections::HashMap, sync::RwLock};
|
||||
|
||||
@ -48,6 +48,10 @@ impl RpcSender for MockSender {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
let val = match request {
|
||||
RpcRequest::GetAccountInfo => serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: Value::Null,
|
||||
})?,
|
||||
RpcRequest::GetBalance => serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: Value::Number(Number::from(50)),
|
||||
@ -65,6 +69,7 @@ impl RpcSender for MockSender {
|
||||
slots_in_epoch: 32,
|
||||
absolute_slot: 34,
|
||||
block_height: 34,
|
||||
transaction_count: Some(123),
|
||||
})?,
|
||||
RpcRequest::GetFeeCalculatorForBlockhash => {
|
||||
let value = if self.url == "blockhash_expired" {
|
||||
@ -101,6 +106,7 @@ impl RpcSender for MockSender {
|
||||
slot: 1,
|
||||
confirmations: None,
|
||||
err,
|
||||
confirmation_status: Some(TransactionConfirmationStatus::Finalized),
|
||||
})
|
||||
};
|
||||
let statuses: Vec<Option<TransactionStatus>> = params.as_array().unwrap()[0]
|
||||
@ -116,6 +122,7 @@ impl RpcSender for MockSender {
|
||||
}
|
||||
RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)),
|
||||
RpcRequest::GetSlot => Value::Number(Number::from(0)),
|
||||
RpcRequest::RequestAirdrop => Value::String(Signature::new(&[8; 64]).to_string()),
|
||||
RpcRequest::SendTransaction => {
|
||||
let signature = if self.url == "malicious" {
|
||||
Signature::new(&[8; 64]).to_string()
|
||||
|
@ -46,10 +46,7 @@ pub fn get_account_with_commitment(
|
||||
.value
|
||||
.ok_or_else(|| Error::Client(format!("AccountNotFound: pubkey={}", nonce_pubkey)))
|
||||
})
|
||||
.and_then(|a| match account_identity_ok(&a) {
|
||||
Ok(()) => Ok(a),
|
||||
Err(e) => Err(e),
|
||||
})
|
||||
.and_then(|a| account_identity_ok(&a).map(|()| a))
|
||||
}
|
||||
|
||||
pub fn account_identity_ok(account: &Account) -> Result<(), Error> {
|
||||
|
@ -33,7 +33,7 @@ pub fn sample_txs<T>(
|
||||
let mut now = Instant::now();
|
||||
let start_time = now;
|
||||
let initial_txs = client
|
||||
.get_transaction_count_with_commitment(CommitmentConfig::recent())
|
||||
.get_transaction_count_with_commitment(CommitmentConfig::processed())
|
||||
.expect("transaction count");
|
||||
let mut last_txs = initial_txs;
|
||||
|
||||
@ -42,7 +42,7 @@ pub fn sample_txs<T>(
|
||||
let elapsed = now.elapsed();
|
||||
now = Instant::now();
|
||||
let mut txs;
|
||||
match client.get_transaction_count_with_commitment(CommitmentConfig::recent()) {
|
||||
match client.get_transaction_count_with_commitment(CommitmentConfig::processed()) {
|
||||
Err(e) => {
|
||||
// ThinClient with multiple options should pick a better one now.
|
||||
info!("Couldn't get transaction count {:?}", e);
|
||||
|
@ -98,7 +98,7 @@ where
|
||||
}
|
||||
|
||||
pub fn send_unsubscribe(&self) -> Result<(), PubsubClientError> {
|
||||
let method = format!("{}Unubscribe", self.operation);
|
||||
let method = format!("{}Unsubscribe", self.operation);
|
||||
self.socket
|
||||
.write()
|
||||
.unwrap()
|
||||
|
75
client/src/rpc_cache.rs
Normal file
75
client/src/rpc_cache.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use crate::{rpc_config::RpcLargestAccountsFilter, rpc_response::RpcAccountBalance};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LargestAccountsCache {
|
||||
duration: u64,
|
||||
cache: HashMap<Option<RpcLargestAccountsFilter>, LargestAccountsCacheValue>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct LargestAccountsCacheValue {
|
||||
accounts: Vec<RpcAccountBalance>,
|
||||
slot: u64,
|
||||
cached_time: SystemTime,
|
||||
}
|
||||
|
||||
impl LargestAccountsCache {
|
||||
pub fn new(duration: u64) -> Self {
|
||||
Self {
|
||||
duration,
|
||||
cache: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_largest_accounts(
|
||||
&self,
|
||||
filter: &Option<RpcLargestAccountsFilter>,
|
||||
) -> Option<(u64, Vec<RpcAccountBalance>)> {
|
||||
self.cache.get(&filter).and_then(|value| {
|
||||
if let Ok(elapsed) = value.cached_time.elapsed() {
|
||||
if elapsed < Duration::from_secs(self.duration) {
|
||||
return Some((value.slot, value.accounts.clone()));
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_largest_accounts(
|
||||
&mut self,
|
||||
filter: &Option<RpcLargestAccountsFilter>,
|
||||
slot: u64,
|
||||
accounts: &[RpcAccountBalance],
|
||||
) {
|
||||
self.cache.insert(
|
||||
filter.clone(),
|
||||
LargestAccountsCacheValue {
|
||||
accounts: accounts.to_owned(),
|
||||
slot,
|
||||
cached_time: SystemTime::now(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_old_entries_expire() {
|
||||
let mut cache = LargestAccountsCache::new(1);
|
||||
|
||||
let filter = Some(RpcLargestAccountsFilter::Circulating);
|
||||
|
||||
let accounts: Vec<RpcAccountBalance> = Vec::new();
|
||||
|
||||
cache.set_largest_accounts(&filter, 1000, &accounts);
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
assert_eq!(cache.get_largest_accounts(&filter), None);
|
||||
}
|
||||
}
|
@ -4,9 +4,10 @@ use crate::{
|
||||
mock_sender::{MockSender, Mocks},
|
||||
rpc_config::RpcAccountInfoConfig,
|
||||
rpc_config::{
|
||||
RpcConfirmedBlockConfig, RpcConfirmedTransactionConfig, RpcEpochConfig,
|
||||
RpcGetConfirmedSignaturesForAddress2Config, RpcLargestAccountsConfig,
|
||||
RpcProgramAccountsConfig, RpcSendTransactionConfig, RpcSimulateTransactionConfig,
|
||||
RpcTokenAccountsFilter,
|
||||
RpcProgramAccountsConfig, RpcRequestAirdropConfig, RpcSendTransactionConfig,
|
||||
RpcSimulateTransactionConfig, RpcTokenAccountsFilter,
|
||||
},
|
||||
rpc_request::{RpcError, RpcRequest, RpcResponseErrorData, TokenAccountsFilter},
|
||||
rpc_response::*,
|
||||
@ -23,7 +24,7 @@ use solana_account_decoder::{
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
clock::{
|
||||
Slot, UnixTimestamp, DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT,
|
||||
Epoch, Slot, UnixTimestamp, DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT,
|
||||
MAX_HASH_AGE_IN_SECONDS,
|
||||
},
|
||||
commitment_config::{CommitmentConfig, CommitmentLevel},
|
||||
@ -36,11 +37,14 @@ use solana_sdk::{
|
||||
transaction::{self, uses_durable_nonce, Transaction},
|
||||
};
|
||||
use solana_transaction_status::{
|
||||
EncodedConfirmedBlock, EncodedConfirmedTransaction, TransactionStatus, UiTransactionEncoding,
|
||||
EncodedConfirmedBlock, EncodedConfirmedTransaction, TransactionStatus, UiConfirmedBlock,
|
||||
UiTransactionEncoding,
|
||||
};
|
||||
use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY;
|
||||
use std::{
|
||||
cmp::min,
|
||||
net::SocketAddr,
|
||||
str::FromStr,
|
||||
sync::RwLock,
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
@ -49,7 +53,7 @@ use std::{
|
||||
pub struct RpcClient {
|
||||
sender: Box<dyn RpcSender + Send + Sync + 'static>,
|
||||
commitment_config: CommitmentConfig,
|
||||
default_cluster_transaction_encoding: RwLock<Option<UiTransactionEncoding>>,
|
||||
node_version: RwLock<Option<semver::Version>>,
|
||||
}
|
||||
|
||||
fn serialize_encode_transaction(
|
||||
@ -79,7 +83,7 @@ impl RpcClient {
|
||||
) -> Self {
|
||||
Self {
|
||||
sender: Box::new(sender),
|
||||
default_cluster_transaction_encoding: RwLock::new(None),
|
||||
node_version: RwLock::new(None),
|
||||
commitment_config,
|
||||
}
|
||||
}
|
||||
@ -99,6 +103,17 @@ impl RpcClient {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_timeout_and_commitment(
|
||||
url: String,
|
||||
timeout: Duration,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> Self {
|
||||
Self::new_sender(
|
||||
HttpSender::new_with_timeout(url, timeout),
|
||||
commitment_config,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_mock(url: String) -> Self {
|
||||
Self::new_sender(MockSender::new(url), CommitmentConfig::default())
|
||||
}
|
||||
@ -119,16 +134,54 @@ impl RpcClient {
|
||||
Self::new_with_timeout(url, timeout)
|
||||
}
|
||||
|
||||
pub fn confirm_transaction(&self, signature: &Signature) -> ClientResult<bool> {
|
||||
Ok(self
|
||||
.confirm_transaction_with_commitment(signature, self.commitment_config)?
|
||||
.value)
|
||||
fn get_node_version(&self) -> Result<semver::Version, RpcError> {
|
||||
let r_node_version = self.node_version.read().unwrap();
|
||||
if let Some(version) = &*r_node_version {
|
||||
Ok(version.clone())
|
||||
} else {
|
||||
drop(r_node_version);
|
||||
let mut w_node_version = self.node_version.write().unwrap();
|
||||
let node_version = self.get_version().map_err(|e| {
|
||||
RpcError::RpcRequestError(format!("cluster version query failed: {}", e))
|
||||
})?;
|
||||
let node_version = semver::Version::parse(&node_version.solana_core).map_err(|e| {
|
||||
RpcError::RpcRequestError(format!("failed to parse cluster version: {}", e))
|
||||
})?;
|
||||
*w_node_version = Some(node_version.clone());
|
||||
Ok(node_version)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn commitment(&self) -> CommitmentConfig {
|
||||
self.commitment_config
|
||||
}
|
||||
|
||||
fn use_deprecated_commitment(&self) -> Result<bool, RpcError> {
|
||||
Ok(self.get_node_version()? < semver::Version::new(1, 5, 5))
|
||||
}
|
||||
|
||||
fn maybe_map_commitment(
|
||||
&self,
|
||||
requested_commitment: CommitmentConfig,
|
||||
) -> Result<CommitmentConfig, RpcError> {
|
||||
if matches!(
|
||||
requested_commitment.commitment,
|
||||
CommitmentLevel::Finalized | CommitmentLevel::Confirmed | CommitmentLevel::Processed
|
||||
) && self.use_deprecated_commitment()?
|
||||
{
|
||||
return Ok(CommitmentConfig::use_deprecated_commitment(
|
||||
requested_commitment,
|
||||
));
|
||||
}
|
||||
Ok(requested_commitment)
|
||||
}
|
||||
|
||||
pub fn confirm_transaction(&self, signature: &Signature) -> ClientResult<bool> {
|
||||
Ok(self
|
||||
.confirm_transaction_with_commitment(signature, self.commitment_config)?
|
||||
.value)
|
||||
}
|
||||
|
||||
pub fn confirm_transaction_with_commitment(
|
||||
&self,
|
||||
signature: &Signature,
|
||||
@ -150,34 +203,20 @@ impl RpcClient {
|
||||
self.send_transaction_with_config(
|
||||
transaction,
|
||||
RpcSendTransactionConfig {
|
||||
preflight_commitment: Some(self.commitment_config.commitment),
|
||||
preflight_commitment: Some(
|
||||
self.maybe_map_commitment(self.commitment_config)?
|
||||
.commitment,
|
||||
),
|
||||
..RpcSendTransactionConfig::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn default_cluster_transaction_encoding(&self) -> Result<UiTransactionEncoding, RpcError> {
|
||||
let default_cluster_transaction_encoding =
|
||||
self.default_cluster_transaction_encoding.read().unwrap();
|
||||
if let Some(encoding) = *default_cluster_transaction_encoding {
|
||||
Ok(encoding)
|
||||
if self.get_node_version()? < semver::Version::new(1, 3, 16) {
|
||||
Ok(UiTransactionEncoding::Base58)
|
||||
} else {
|
||||
drop(default_cluster_transaction_encoding);
|
||||
let cluster_version = self.get_version().map_err(|e| {
|
||||
RpcError::RpcRequestError(format!("cluster version query failed: {}", e))
|
||||
})?;
|
||||
let cluster_version =
|
||||
semver::Version::parse(&cluster_version.solana_core).map_err(|e| {
|
||||
RpcError::RpcRequestError(format!("failed to parse cluster version: {}", e))
|
||||
})?;
|
||||
// Prefer base64 since 1.3.16
|
||||
let encoding = if cluster_version < semver::Version::new(1, 3, 16) {
|
||||
UiTransactionEncoding::Base58
|
||||
} else {
|
||||
UiTransactionEncoding::Base64
|
||||
};
|
||||
*self.default_cluster_transaction_encoding.write().unwrap() = Some(encoding);
|
||||
Ok(encoding)
|
||||
Ok(UiTransactionEncoding::Base64)
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,8 +230,13 @@ impl RpcClient {
|
||||
} else {
|
||||
self.default_cluster_transaction_encoding()?
|
||||
};
|
||||
let preflight_commitment = CommitmentConfig {
|
||||
commitment: config.preflight_commitment.unwrap_or_default(),
|
||||
};
|
||||
let preflight_commitment = self.maybe_map_commitment(preflight_commitment)?;
|
||||
let config = RpcSendTransactionConfig {
|
||||
encoding: Some(encoding),
|
||||
preflight_commitment: Some(preflight_commitment.commitment),
|
||||
..config
|
||||
};
|
||||
let serialized_encoded = serialize_encode_transaction(transaction, encoding)?;
|
||||
@ -218,6 +262,7 @@ impl RpcClient {
|
||||
for (i, log) in logs.iter().enumerate() {
|
||||
debug!("{:>3}: {}", i + 1, log);
|
||||
}
|
||||
debug!("");
|
||||
}
|
||||
}
|
||||
return Err(err);
|
||||
@ -265,8 +310,11 @@ impl RpcClient {
|
||||
} else {
|
||||
self.default_cluster_transaction_encoding()?
|
||||
};
|
||||
let commitment = config.commitment.unwrap_or_default();
|
||||
let commitment = self.maybe_map_commitment(commitment)?;
|
||||
let config = RpcSimulateTransactionConfig {
|
||||
encoding: Some(encoding),
|
||||
commitment: Some(commitment),
|
||||
..config
|
||||
};
|
||||
let serialized_encoded = serialize_encode_transaction(transaction, encoding)?;
|
||||
@ -276,6 +324,10 @@ impl RpcClient {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_snapshot_slot(&self) -> ClientResult<Slot> {
|
||||
self.send(RpcRequest::GetSnapshotSlot, Value::Null)
|
||||
}
|
||||
|
||||
pub fn get_signature_status(
|
||||
&self,
|
||||
signature: &Signature,
|
||||
@ -345,31 +397,92 @@ impl RpcClient {
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ClientResult<Slot> {
|
||||
self.send(RpcRequest::GetSlot, json!([commitment_config]))
|
||||
self.send(
|
||||
RpcRequest::GetSlot,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_slot_leaders(&self, start_slot: Slot, limit: u64) -> ClientResult<Vec<Pubkey>> {
|
||||
self.send(RpcRequest::GetSlotLeaders, json!([start_slot, limit]))
|
||||
.and_then(|slot_leaders: Vec<String>| {
|
||||
slot_leaders
|
||||
.iter()
|
||||
.map(|slot_leader| {
|
||||
Pubkey::from_str(slot_leader).map_err(|err| {
|
||||
ClientErrorKind::Custom(format!(
|
||||
"pubkey deserialization failed: {}",
|
||||
err
|
||||
))
|
||||
.into()
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_stake_activation(
|
||||
&self,
|
||||
stake_account: Pubkey,
|
||||
epoch: Option<Epoch>,
|
||||
) -> ClientResult<RpcStakeActivation> {
|
||||
self.send(
|
||||
RpcRequest::GetStakeActivation,
|
||||
json!([
|
||||
stake_account.to_string(),
|
||||
RpcEpochConfig {
|
||||
epoch,
|
||||
commitment: Some(self.commitment_config),
|
||||
}
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn supply(&self) -> RpcResult<RpcSupply> {
|
||||
self.supply_with_commitment(self.commitment_config)
|
||||
}
|
||||
|
||||
pub fn supply_with_commitment(
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<RpcSupply> {
|
||||
self.send(RpcRequest::GetSupply, json!([commitment_config]))
|
||||
self.send(
|
||||
RpcRequest::GetSupply,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
#[deprecated(since = "1.5.19", note = "Please use RpcClient::supply() instead")]
|
||||
#[allow(deprecated)]
|
||||
pub fn total_supply(&self) -> ClientResult<u64> {
|
||||
self.total_supply_with_commitment(self.commitment_config)
|
||||
}
|
||||
|
||||
#[deprecated(
|
||||
since = "1.5.19",
|
||||
note = "Please use RpcClient::supply_with_commitment() instead"
|
||||
)]
|
||||
#[allow(deprecated)]
|
||||
pub fn total_supply_with_commitment(
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ClientResult<u64> {
|
||||
self.send(RpcRequest::GetTotalSupply, json!([commitment_config]))
|
||||
self.send(
|
||||
RpcRequest::GetTotalSupply,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_largest_accounts_with_config(
|
||||
&self,
|
||||
config: RpcLargestAccountsConfig,
|
||||
) -> RpcResult<Vec<RpcAccountBalance>> {
|
||||
let commitment = config.commitment.unwrap_or_default();
|
||||
let commitment = self.maybe_map_commitment(commitment)?;
|
||||
let config = RpcLargestAccountsConfig {
|
||||
commitment: Some(commitment),
|
||||
..config
|
||||
};
|
||||
self.send(RpcRequest::GetLargestAccounts, json!([config]))
|
||||
}
|
||||
|
||||
@ -381,7 +494,10 @@ impl RpcClient {
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ClientResult<RpcVoteAccountStatus> {
|
||||
self.send(RpcRequest::GetVoteAccounts, json!([commitment_config]))
|
||||
self.send(
|
||||
RpcRequest::GetVoteAccounts,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn wait_for_max_stake(
|
||||
@ -432,6 +548,14 @@ impl RpcClient {
|
||||
self.send(RpcRequest::GetConfirmedBlock, json!([slot, encoding]))
|
||||
}
|
||||
|
||||
pub fn get_confirmed_block_with_config(
|
||||
&self,
|
||||
slot: Slot,
|
||||
config: RpcConfirmedBlockConfig,
|
||||
) -> ClientResult<UiConfirmedBlock> {
|
||||
self.send(RpcRequest::GetConfirmedBlock, json!([slot, config]))
|
||||
}
|
||||
|
||||
pub fn get_confirmed_blocks(
|
||||
&self,
|
||||
start_slot: Slot,
|
||||
@ -443,6 +567,24 @@ impl RpcClient {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_confirmed_blocks_with_commitment(
|
||||
&self,
|
||||
start_slot: Slot,
|
||||
end_slot: Option<Slot>,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ClientResult<Vec<Slot>> {
|
||||
let json = if end_slot.is_some() {
|
||||
json!([
|
||||
start_slot,
|
||||
end_slot,
|
||||
self.maybe_map_commitment(commitment_config)?
|
||||
])
|
||||
} else {
|
||||
json!([start_slot, self.maybe_map_commitment(commitment_config)?])
|
||||
};
|
||||
self.send(RpcRequest::GetConfirmedBlocks, json)
|
||||
}
|
||||
|
||||
pub fn get_confirmed_blocks_with_limit(
|
||||
&self,
|
||||
start_slot: Slot,
|
||||
@ -454,6 +596,27 @@ impl RpcClient {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_confirmed_blocks_with_limit_and_commitment(
|
||||
&self,
|
||||
start_slot: Slot,
|
||||
limit: usize,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ClientResult<Vec<Slot>> {
|
||||
self.send(
|
||||
RpcRequest::GetConfirmedBlocksWithLimit,
|
||||
json!([
|
||||
start_slot,
|
||||
limit,
|
||||
self.maybe_map_commitment(commitment_config)?
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
#[deprecated(
|
||||
since = "1.5.19",
|
||||
note = "Please use RpcClient::get_confirmed_signatures_for_address2() instead"
|
||||
)]
|
||||
#[allow(deprecated)]
|
||||
pub fn get_confirmed_signatures_for_address(
|
||||
&self,
|
||||
address: &Pubkey,
|
||||
@ -495,6 +658,7 @@ impl RpcClient {
|
||||
before: config.before.map(|signature| signature.to_string()),
|
||||
until: config.until.map(|signature| signature.to_string()),
|
||||
limit: config.limit,
|
||||
commitment: config.commitment,
|
||||
};
|
||||
|
||||
let result: Vec<RpcConfirmedTransactionStatusWithSignature> = self.send(
|
||||
@ -516,6 +680,17 @@ impl RpcClient {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_confirmed_transaction_with_config(
|
||||
&self,
|
||||
signature: &Signature,
|
||||
config: RpcConfirmedTransactionConfig,
|
||||
) -> ClientResult<EncodedConfirmedTransaction> {
|
||||
self.send(
|
||||
RpcRequest::GetConfirmedTransaction,
|
||||
json!([signature.to_string(), config]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_block_time(&self, slot: Slot) -> ClientResult<UnixTimestamp> {
|
||||
let request = RpcRequest::GetBlockTime;
|
||||
let response = self.sender.send(request, json!([slot]));
|
||||
@ -541,7 +716,10 @@ impl RpcClient {
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ClientResult<EpochInfo> {
|
||||
self.send(RpcRequest::GetEpochInfo, json!([commitment_config]))
|
||||
self.send(
|
||||
RpcRequest::GetEpochInfo,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_leader_schedule(
|
||||
@ -558,7 +736,7 @@ impl RpcClient {
|
||||
) -> ClientResult<Option<RpcLeaderSchedule>> {
|
||||
self.send(
|
||||
RpcRequest::GetLeaderSchedule,
|
||||
json!([slot, commitment_config]),
|
||||
json!([slot, self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
@ -566,6 +744,13 @@ impl RpcClient {
|
||||
self.send(RpcRequest::GetEpochSchedule, Value::Null)
|
||||
}
|
||||
|
||||
pub fn get_recent_performance_samples(
|
||||
&self,
|
||||
limit: Option<usize>,
|
||||
) -> ClientResult<Vec<RpcPerfSample>> {
|
||||
self.send(RpcRequest::GetRecentPerformanceSamples, json!([limit]))
|
||||
}
|
||||
|
||||
pub fn get_identity(&self) -> ClientResult<Pubkey> {
|
||||
let rpc_identity: RpcIdentity = self.send(RpcRequest::GetIdentity, Value::Null)?;
|
||||
|
||||
@ -585,6 +770,27 @@ impl RpcClient {
|
||||
self.send(RpcRequest::GetInflationRate, Value::Null)
|
||||
}
|
||||
|
||||
pub fn get_inflation_reward(
|
||||
&self,
|
||||
addresses: &[Pubkey],
|
||||
epoch: Option<Epoch>,
|
||||
) -> ClientResult<Vec<Option<RpcInflationReward>>> {
|
||||
let addresses: Vec<_> = addresses
|
||||
.iter()
|
||||
.map(|address| address.to_string())
|
||||
.collect();
|
||||
self.send(
|
||||
RpcRequest::GetInflationReward,
|
||||
json!([
|
||||
addresses,
|
||||
RpcEpochConfig {
|
||||
epoch,
|
||||
commitment: Some(self.commitment_config),
|
||||
}
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_version(&self) -> ClientResult<RpcVersionInfo> {
|
||||
self.send(RpcRequest::GetVersion, Value::Null)
|
||||
}
|
||||
@ -599,7 +805,7 @@ impl RpcClient {
|
||||
) -> ClientResult<Signature> {
|
||||
let signature = self.send_transaction(transaction)?;
|
||||
let recent_blockhash = if uses_durable_nonce(transaction).is_some() {
|
||||
self.get_recent_blockhash_with_commitment(CommitmentConfig::recent())?
|
||||
self.get_recent_blockhash_with_commitment(CommitmentConfig::processed())?
|
||||
.value
|
||||
.0
|
||||
} else {
|
||||
@ -611,7 +817,7 @@ impl RpcClient {
|
||||
if self
|
||||
.get_fee_calculator_for_blockhash_with_commitment(
|
||||
&recent_blockhash,
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)?
|
||||
.value
|
||||
.is_none()
|
||||
@ -657,7 +863,7 @@ impl RpcClient {
|
||||
) -> RpcResult<Option<Account>> {
|
||||
let config = RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::Base64),
|
||||
commitment: Some(commitment_config),
|
||||
commitment: Some(self.maybe_map_commitment(commitment_config)?),
|
||||
data_slice: None,
|
||||
};
|
||||
let response = self.sender.send(
|
||||
@ -704,7 +910,7 @@ impl RpcClient {
|
||||
) -> RpcResult<Vec<Option<Account>>> {
|
||||
let config = RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::Base64),
|
||||
commitment: Some(commitment_config),
|
||||
commitment: Some(self.maybe_map_commitment(commitment_config)?),
|
||||
data_slice: None,
|
||||
};
|
||||
let pubkeys: Vec<_> = pubkeys.iter().map(|pubkey| pubkey.to_string()).collect();
|
||||
@ -758,7 +964,10 @@ impl RpcClient {
|
||||
) -> RpcResult<u64> {
|
||||
self.send(
|
||||
RpcRequest::GetBalance,
|
||||
json!([pubkey.to_string(), commitment_config]),
|
||||
json!([
|
||||
pubkey.to_string(),
|
||||
self.maybe_map_commitment(commitment_config)?
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
@ -769,6 +978,7 @@ impl RpcClient {
|
||||
filters: None,
|
||||
account_config: RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::Base64),
|
||||
commitment: Some(self.commitment_config),
|
||||
..RpcAccountInfoConfig::default()
|
||||
},
|
||||
},
|
||||
@ -780,6 +990,16 @@ impl RpcClient {
|
||||
pubkey: &Pubkey,
|
||||
config: RpcProgramAccountsConfig,
|
||||
) -> ClientResult<Vec<(Pubkey, Account)>> {
|
||||
let commitment = config.account_config.commitment.unwrap_or_default();
|
||||
let commitment = self.maybe_map_commitment(commitment)?;
|
||||
let account_config = RpcAccountInfoConfig {
|
||||
commitment: Some(commitment),
|
||||
..config.account_config
|
||||
};
|
||||
let config = RpcProgramAccountsConfig {
|
||||
account_config,
|
||||
..config
|
||||
};
|
||||
let accounts: Vec<RpcKeyedAccount> = self.send(
|
||||
RpcRequest::GetProgramAccounts,
|
||||
json!([pubkey.to_string(), config]),
|
||||
@ -796,7 +1016,10 @@ impl RpcClient {
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ClientResult<u64> {
|
||||
self.send(RpcRequest::GetTransactionCount, json!([commitment_config]))
|
||||
self.send(
|
||||
RpcRequest::GetTransactionCount,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_recent_blockhash(&self) -> ClientResult<(Hash, FeeCalculator)> {
|
||||
@ -818,9 +1041,11 @@ impl RpcClient {
|
||||
fee_calculator,
|
||||
last_valid_slot,
|
||||
},
|
||||
}) =
|
||||
self.send::<Response<RpcFees>>(RpcRequest::GetFees, json!([commitment_config]))
|
||||
{
|
||||
}) = self
|
||||
.send::<Response<RpcFees>>(
|
||||
RpcRequest::GetFees,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
) {
|
||||
(context, blockhash, fee_calculator, last_valid_slot)
|
||||
} else if let Ok(Response {
|
||||
context,
|
||||
@ -831,7 +1056,7 @@ impl RpcClient {
|
||||
},
|
||||
}) = self.send::<Response<RpcBlockhashFeeCalculator>>(
|
||||
RpcRequest::GetRecentBlockhash,
|
||||
json!([commitment_config]),
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
) {
|
||||
(context, blockhash, fee_calculator, 0)
|
||||
} else {
|
||||
@ -869,7 +1094,10 @@ impl RpcClient {
|
||||
) -> RpcResult<Option<FeeCalculator>> {
|
||||
let Response { context, value } = self.send::<Response<Option<RpcFeeCalculator>>>(
|
||||
RpcRequest::GetFeeCalculatorForBlockhash,
|
||||
json!([blockhash.to_string(), commitment_config]),
|
||||
json!([
|
||||
blockhash.to_string(),
|
||||
self.maybe_map_commitment(commitment_config)?
|
||||
]),
|
||||
)?;
|
||||
|
||||
Ok(Response {
|
||||
@ -932,6 +1160,11 @@ impl RpcClient {
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
pub fn get_health(&self) -> ClientResult<()> {
|
||||
self.send::<String>(RpcRequest::GetHealth, Value::Null)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub fn get_token_account(&self, pubkey: &Pubkey) -> ClientResult<Option<UiTokenAccount>> {
|
||||
Ok(self
|
||||
.get_token_account_with_commitment(pubkey, self.commitment_config)?
|
||||
@ -945,7 +1178,7 @@ impl RpcClient {
|
||||
) -> RpcResult<Option<UiTokenAccount>> {
|
||||
let config = RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::JsonParsed),
|
||||
commitment: Some(commitment_config),
|
||||
commitment: Some(self.maybe_map_commitment(commitment_config)?),
|
||||
data_slice: None,
|
||||
};
|
||||
let response = self.sender.send(
|
||||
@ -1006,7 +1239,10 @@ impl RpcClient {
|
||||
) -> RpcResult<UiTokenAmount> {
|
||||
self.send(
|
||||
RpcRequest::GetTokenAccountBalance,
|
||||
json!([pubkey.to_string(), commitment_config]),
|
||||
json!([
|
||||
pubkey.to_string(),
|
||||
self.maybe_map_commitment(commitment_config)?
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
@ -1039,7 +1275,7 @@ impl RpcClient {
|
||||
|
||||
let config = RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::JsonParsed),
|
||||
commitment: Some(commitment_config),
|
||||
commitment: Some(self.maybe_map_commitment(commitment_config)?),
|
||||
data_slice: None,
|
||||
};
|
||||
|
||||
@ -1078,7 +1314,7 @@ impl RpcClient {
|
||||
|
||||
let config = RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::JsonParsed),
|
||||
commitment: Some(commitment_config),
|
||||
commitment: Some(self.maybe_map_commitment(commitment_config)?),
|
||||
data_slice: None,
|
||||
};
|
||||
|
||||
@ -1101,10 +1337,71 @@ impl RpcClient {
|
||||
) -> RpcResult<UiTokenAmount> {
|
||||
self.send(
|
||||
RpcRequest::GetTokenSupply,
|
||||
json!([mint.to_string(), commitment_config]),
|
||||
json!([
|
||||
mint.to_string(),
|
||||
self.maybe_map_commitment(commitment_config)?
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn request_airdrop(&self, pubkey: &Pubkey, lamports: u64) -> ClientResult<Signature> {
|
||||
self.request_airdrop_with_config(
|
||||
pubkey,
|
||||
lamports,
|
||||
RpcRequestAirdropConfig {
|
||||
commitment: Some(self.commitment_config),
|
||||
..RpcRequestAirdropConfig::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn request_airdrop_with_blockhash(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
recent_blockhash: &Hash,
|
||||
) -> ClientResult<Signature> {
|
||||
self.request_airdrop_with_config(
|
||||
pubkey,
|
||||
lamports,
|
||||
RpcRequestAirdropConfig {
|
||||
commitment: Some(self.commitment_config),
|
||||
recent_blockhash: Some(recent_blockhash.to_string()),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn request_airdrop_with_config(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
config: RpcRequestAirdropConfig,
|
||||
) -> ClientResult<Signature> {
|
||||
let commitment = config.commitment.unwrap_or_default();
|
||||
let commitment = self.maybe_map_commitment(commitment)?;
|
||||
let config = RpcRequestAirdropConfig {
|
||||
commitment: Some(commitment),
|
||||
..config
|
||||
};
|
||||
self.send(
|
||||
RpcRequest::RequestAirdrop,
|
||||
json!([pubkey.to_string(), lamports, config]),
|
||||
)
|
||||
.and_then(|signature: String| {
|
||||
Signature::from_str(&signature).map_err(|err| {
|
||||
ClientErrorKind::Custom(format!("signature deserialization failed: {}", err)).into()
|
||||
})
|
||||
})
|
||||
.map_err(|_| {
|
||||
RpcError::ForUser(
|
||||
"airdrop request failed. \
|
||||
This can happen when the rate limit is reached."
|
||||
.to_string(),
|
||||
)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn poll_balance_with_timeout_and_commitment(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
@ -1306,9 +1603,28 @@ impl RpcClient {
|
||||
commitment: CommitmentConfig,
|
||||
config: RpcSendTransactionConfig,
|
||||
) -> ClientResult<Signature> {
|
||||
let desired_confirmations = match commitment.commitment {
|
||||
CommitmentLevel::Max | CommitmentLevel::Root => MAX_LOCKOUT_HISTORY + 1,
|
||||
_ => 1,
|
||||
let recent_blockhash = if uses_durable_nonce(transaction).is_some() {
|
||||
self.get_recent_blockhash_with_commitment(CommitmentConfig::processed())?
|
||||
.value
|
||||
.0
|
||||
} else {
|
||||
transaction.message.recent_blockhash
|
||||
};
|
||||
let signature = self.send_transaction_with_config(transaction, config)?;
|
||||
self.confirm_transaction_with_spinner(&signature, &recent_blockhash, commitment)?;
|
||||
Ok(signature)
|
||||
}
|
||||
|
||||
pub fn confirm_transaction_with_spinner(
|
||||
&self,
|
||||
signature: &Signature,
|
||||
recent_blockhash: &Hash,
|
||||
commitment: CommitmentConfig,
|
||||
) -> ClientResult<()> {
|
||||
let desired_confirmations = if commitment.is_finalized() {
|
||||
MAX_LOCKOUT_HISTORY + 1
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let mut confirmations = 0;
|
||||
|
||||
@ -1316,25 +1632,17 @@ impl RpcClient {
|
||||
|
||||
progress_bar.set_message(&format!(
|
||||
"[{}/{}] Finalizing transaction {}",
|
||||
confirmations, desired_confirmations, transaction.signatures[0],
|
||||
confirmations, desired_confirmations, signature,
|
||||
));
|
||||
let recent_blockhash = if uses_durable_nonce(transaction).is_some() {
|
||||
self.get_recent_blockhash_with_commitment(CommitmentConfig::recent())?
|
||||
.value
|
||||
.0
|
||||
} else {
|
||||
transaction.message.recent_blockhash
|
||||
};
|
||||
let signature = self.send_transaction_with_config(transaction, config)?;
|
||||
let (signature, status) = loop {
|
||||
// Get recent commitment in order to count confirmations for successful transactions
|
||||
let status =
|
||||
self.get_signature_status_with_commitment(&signature, CommitmentConfig::recent())?;
|
||||
let status = self
|
||||
.get_signature_status_with_commitment(&signature, CommitmentConfig::processed())?;
|
||||
if status.is_none() {
|
||||
if self
|
||||
.get_fee_calculator_for_blockhash_with_commitment(
|
||||
&recent_blockhash,
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)?
|
||||
.value
|
||||
.is_none()
|
||||
@ -1364,29 +1672,20 @@ impl RpcClient {
|
||||
}
|
||||
let now = Instant::now();
|
||||
loop {
|
||||
match commitment.commitment {
|
||||
CommitmentLevel::Max | CommitmentLevel::Root =>
|
||||
// Return when default (max) commitment is reached
|
||||
// Failed transactions have already been eliminated, `is_some` check is sufficient
|
||||
{
|
||||
if self.get_signature_status(&signature)?.is_some() {
|
||||
progress_bar.set_message("Transaction confirmed");
|
||||
progress_bar.finish_and_clear();
|
||||
return Ok(signature);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Return when one confirmation has been reached
|
||||
if confirmations >= desired_confirmations {
|
||||
progress_bar.set_message("Transaction reached commitment");
|
||||
progress_bar.finish_and_clear();
|
||||
return Ok(signature);
|
||||
}
|
||||
}
|
||||
// Return when specified commitment is reached
|
||||
// Failed transactions have already been eliminated, `is_some` check is sufficient
|
||||
if self
|
||||
.get_signature_status_with_commitment(&signature, commitment)?
|
||||
.is_some()
|
||||
{
|
||||
progress_bar.set_message("Transaction confirmed");
|
||||
progress_bar.finish_and_clear();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
progress_bar.set_message(&format!(
|
||||
"[{}/{}] Finalizing transaction {}",
|
||||
confirmations + 1,
|
||||
min(confirmations + 1, desired_confirmations),
|
||||
desired_confirmations,
|
||||
signature,
|
||||
));
|
||||
@ -1427,6 +1726,7 @@ pub struct GetConfirmedSignaturesForAddress2Config {
|
||||
pub before: Option<Signature>,
|
||||
pub until: Option<Signature>,
|
||||
pub limit: Option<usize>,
|
||||
pub commitment: Option<CommitmentConfig>,
|
||||
}
|
||||
|
||||
fn new_spinner_progress_bar() -> ProgressBar {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user