Compare commits
649 Commits
error-type
...
v1.10.2
Author | SHA1 | Date | |
---|---|---|---|
|
11be3fb7fa | ||
|
e8a8f4e9e2 | ||
|
accc64ebcf | ||
|
bc9882a78b | ||
|
01b6f97f0b | ||
|
7758c32035 | ||
|
bf57252298 | ||
|
0d369616e7 | ||
|
48d4c8eb5c | ||
|
e2e54ef64b | ||
|
1e20bd8f9a | ||
|
b1da7cff66 | ||
|
4e02ec342c | ||
|
3cf31fa9b8 | ||
|
ccff006948 | ||
|
3eca66a1f8 | ||
|
4d08234603 | ||
|
5d75ef4766 | ||
|
7ee7fc6f58 | ||
|
d20dd21600 | ||
|
2bff36dfba | ||
|
4f18d73281 | ||
|
043086081f | ||
|
3d021cffa3 | ||
|
9b591286d7 | ||
|
99f1a22b9d | ||
|
e6a67cd091 | ||
|
631be1ffdd | ||
|
021135978d | ||
|
b444836a97 | ||
|
83f5f8bfc3 | ||
|
ddd9d5a5a5 | ||
|
ead8cc4366 | ||
|
7b238b3645 | ||
|
35d1235ed0 | ||
|
3c6840050c | ||
|
58c0db9704 | ||
|
37189f20c5 | ||
|
588414a776 | ||
|
72af687aa6 | ||
|
f68c5a274d | ||
|
9f71958d7d | ||
|
17b00ad3a4 | ||
|
1fe0d6eeeb | ||
|
e60c9b97c9 | ||
|
ea1bcd3d59 | ||
|
1eddb6d1e9 | ||
|
26ef6111bb | ||
|
9bbccbe27c | ||
|
fb974489a5 | ||
|
ba54b30101 | ||
|
a1c45d5acb | ||
|
176fd23002 | ||
|
3688ac4eae | ||
|
cc55684f5f | ||
|
e2bc326d58 | ||
|
8abaa5d350 | ||
|
949006b5a2 | ||
|
afc41c7b11 | ||
|
7a9884c831 | ||
|
8a4b019ded | ||
|
249d926d1b | ||
|
65e2d9b2f2 | ||
|
5c722519cf | ||
|
5a0cd05866 | ||
|
9acbfa5eb1 | ||
|
c878c9e2cb | ||
|
0a17edcc1f | ||
|
cc4d75a16f | ||
|
b719d6a2ad | ||
|
8438366d1b | ||
|
46ec5d563b | ||
|
9b80452c7c | ||
|
c2ec294401 | ||
|
536a99705b | ||
|
00558227be | ||
|
5599bd9442 | ||
|
9cfa21f7d1 | ||
|
12337d8daf | ||
|
3114c199bd | ||
|
e790d0fc53 | ||
|
7933c7fc24 | ||
|
ddbf5c782f | ||
|
38d8bbb19c | ||
|
181fffb916 | ||
|
08c9a650db | ||
|
3a0271c113 | ||
|
463cd564cf | ||
|
b8b7163b66 | ||
|
ba771cdc45 | ||
|
d2b23da9ea | ||
|
09b58e1cfb | ||
|
e23c6ce62b | ||
|
38db1dead4 | ||
|
36ad59673c | ||
|
0d33b54d74 | ||
|
93c8e04d51 | ||
|
b28acd2d4d | ||
|
360f6466a3 | ||
|
aad73f1f2e | ||
|
afda8c4020 | ||
|
62d2a4cd88 | ||
|
8d53ea81e9 | ||
|
f2fa49a771 | ||
|
7b7160448b | ||
|
7f608965ef | ||
|
ddfd4f86f3 | ||
|
a99fd09c16 | ||
|
4b59bfe6d8 | ||
|
5ac3466f26 | ||
|
2e750722c7 | ||
|
a9fd807f61 | ||
|
011472a8e8 | ||
|
b4480e6b70 | ||
|
61d7bdd66f | ||
|
43347f3da6 | ||
|
634f4eb37d | ||
|
39387e8446 | ||
|
79a515e88e | ||
|
e87b941a51 | ||
|
fe7604589d | ||
|
e9912744ef | ||
|
8184f755ae | ||
|
97d40ba3da | ||
|
82cb61dc36 | ||
|
1a99251498 | ||
|
41ab690a61 | ||
|
7dbde2247d | ||
|
da00d29de0 | ||
|
e88da2ec0a | ||
|
26aa18b3f3 | ||
|
e630eb73d7 | ||
|
ef8b7d9c62 | ||
|
d909b7c80b | ||
|
8eefe60c44 | ||
|
d43786edcf | ||
|
9ec514f6c5 | ||
|
3ddd018452 | ||
|
41f78b9925 | ||
|
4f0070a5c6 | ||
|
8de88d0a55 | ||
|
86e2f728c3 | ||
|
7b7fdb42d9 | ||
|
c8cb940b4e | ||
|
a4f4ac5279 | ||
|
d3ebe8d8f5 | ||
|
7d1a090cfb | ||
|
c69e3b73ff | ||
|
2a17a661e6 | ||
|
a0d68ef60e | ||
|
7d1810bbcc | ||
|
6c56eb9663 | ||
|
d0ba914d2b | ||
|
7943e8a1c3 | ||
|
3e48cc4e00 | ||
|
ce4d579499 | ||
|
f6a06826d8 | ||
|
93c5642f9f | ||
|
1282277126 | ||
|
454e82683e | ||
|
6b2683f7da | ||
|
ec798f5aad | ||
|
3b5b71ce44 | ||
|
19448ba078 | ||
|
e3fa55f88d | ||
|
5877e38baa | ||
|
0de7b757d0 | ||
|
6dfd1b9883 | ||
|
6666f23c01 | ||
|
f0a235d16f | ||
|
4eeb9f4648 | ||
|
f814c4a082 | ||
|
911c5a8362 | ||
|
22d2a40133 | ||
|
611d745241 | ||
|
ee3fc39f1c | ||
|
fe18ea35a2 | ||
|
30dafc7135 | ||
|
186bb19965 | ||
|
87b76aeeb0 | ||
|
a57c7ba5df | ||
|
2efb909051 | ||
|
418076ab3e | ||
|
36484f4f08 | ||
|
569d531573 | ||
|
0ad4757159 | ||
|
e2fa6a0f7a | ||
|
533eca3b4c | ||
|
ff04a5b989 | ||
|
cf18292fd3 | ||
|
92216c01ff | ||
|
0e5a58b883 | ||
|
d4292774c5 | ||
|
97b5a71ceb | ||
|
804fac8ea9 | ||
|
985af71241 | ||
|
98f059e89c | ||
|
274a6a5ccb | ||
|
2d55a8e1c3 | ||
|
3f35d7fad9 | ||
|
7111918596 | ||
|
5e0086c1ee | ||
|
d1f141484e | ||
|
db12d90735 | ||
|
2207e49633 | ||
|
39c86c6c44 | ||
|
6c53f7f588 | ||
|
2b0f16e7c3 | ||
|
017170c99d | ||
|
a14c7c37ee | ||
|
9d2232306e | ||
|
12bddbc4ec | ||
|
fcaf01e243 | ||
|
856d29c7b7 | ||
|
6a0d2fcfa7 | ||
|
4bc440666a | ||
|
c0d0724be0 | ||
|
7ee549e5ae | ||
|
227df52213 | ||
|
99a057927c | ||
|
cafc18c3f9 | ||
|
0dd36f3201 | ||
|
3ea9ca35fa | ||
|
a245efe83d | ||
|
c81dd602c4 | ||
|
084fb79ad8 | ||
|
2e3eafaa11 | ||
|
2996f1f783 | ||
|
05f04a22b7 | ||
|
d0e85c293f | ||
|
6872fc79ba | ||
|
0a3a18744f | ||
|
09d064c090 | ||
|
ff604efc44 | ||
|
20d031e2b8 | ||
|
5766567e9f | ||
|
88e1192c6b | ||
|
cadc2de77d | ||
|
8b32e80ee2 | ||
|
7ebf398ed7 | ||
|
ac70070e5b | ||
|
f00016b647 | ||
|
bcda74f42f | ||
|
c97f34a0fd | ||
|
72c68695b5 | ||
|
d0d256ee9a | ||
|
7e08ae1d0c | ||
|
ebe3d2d59d | ||
|
04d23a1597 | ||
|
42bdf1d864 | ||
|
8c872e9ce0 | ||
|
70ebab2c82 | ||
|
1add82aa9e | ||
|
e2d0ede630 | ||
|
dcd0a39cb6 | ||
|
1719d2349f | ||
|
ee7e411d68 | ||
|
970f543ef6 | ||
|
1351c1bbcf | ||
|
c696944d36 | ||
|
5726f42a7c | ||
|
ae7fedf0b1 | ||
|
b4100a9b5d | ||
|
1a68f81f89 | ||
|
da00b39f4f | ||
|
619335df1a | ||
|
fa680a35ea | ||
|
b66a304e7b | ||
|
3c235503de | ||
|
a102453bae | ||
|
bca1d51735 | ||
|
6e28a03c73 | ||
|
813725dfec | ||
|
de30699fa5 | ||
|
aeda27433f | ||
|
4213ece7bd | ||
|
f7118258d6 | ||
|
aaf657297f | ||
|
83d31c9e65 | ||
|
115d71536b | ||
|
c2435363f3 | ||
|
7939fdc3e5 | ||
|
577fa4ec0c | ||
|
88b66ae3a8 | ||
|
a6d736572c | ||
|
6ae87109d2 | ||
|
56e23432b0 | ||
|
64f5e57666 | ||
|
527f62c744 | ||
|
917113914d | ||
|
43393ea45d | ||
|
b44f40ee3a | ||
|
03bf66a51b | ||
|
ab92578b02 | ||
|
bb50259956 | ||
|
bf689303d4 | ||
|
197f42d346 | ||
|
a43d04d295 | ||
|
98707baec2 | ||
|
c42b80f099 | ||
|
d2a407a9a7 | ||
|
36167b032c | ||
|
21656c26e7 | ||
|
20bc37c9c9 | ||
|
89f5145f64 | ||
|
d15d495f17 | ||
|
d6927365ed | ||
|
22a2a4252a | ||
|
05b387a852 | ||
|
824446710b | ||
|
0855cef76c | ||
|
69b47915b1 | ||
|
c04438be4b | ||
|
817f47d970 | ||
|
3d9874b95a | ||
|
12dffc105a | ||
|
f7753ce85f | ||
|
a75c9378f2 | ||
|
6399647e64 | ||
|
78089941ff | ||
|
ac13d14e30 | ||
|
c078ca3fb3 | ||
|
34443a238e | ||
|
2f9e30a1f7 | ||
|
4bd6a231d2 | ||
|
e4a1799334 | ||
|
59e64d5e99 | ||
|
9e87eb5f71 | ||
|
9213fcb11b | ||
|
0a1ab945bc | ||
|
d5dec989b9 | ||
|
2ca7ae8e30 | ||
|
47e4291303 | ||
|
9d036be61b | ||
|
a7598b6d78 | ||
|
57a9146fa1 | ||
|
67f6787f7a | ||
|
226a71f073 | ||
|
e545828fc5 | ||
|
205cd4609b | ||
|
ae175a026b | ||
|
86d465c531 | ||
|
f67a27eeea | ||
|
1b287f1b59 | ||
|
f334931903 | ||
|
fb47c1f0a3 | ||
|
c7aa7fb66b | ||
|
869cfc9a1c | ||
|
ba2d83f580 | ||
|
7aa1fb4e24 | ||
|
6587dbfa47 | ||
|
a25ac1c988 | ||
|
c899685cb2 | ||
|
541b5a4826 | ||
|
dcd4ea9111 | ||
|
7873175764 | ||
|
86cf226395 | ||
|
f0f4042680 | ||
|
3c65fd7ba3 | ||
|
e52e48076e | ||
|
f2d406ad5d | ||
|
5acf0f6331 | ||
|
d7fcfee4db | ||
|
b2e475b5c4 | ||
|
a160fc30f2 | ||
|
514aab46d9 | ||
|
a146f2d853 | ||
|
c5d8560cdb | ||
|
dfef68f985 | ||
|
4de14e530b | ||
|
37afdd1a65 | ||
|
c7ca2f41f5 | ||
|
eaf2df99c6 | ||
|
86c3990c25 | ||
|
d2c89213ff | ||
|
9548ea61e5 | ||
|
ba215e94f6 | ||
|
e05cf4bf97 | ||
|
4d877567dd | ||
|
96c88d1a5e | ||
|
27aaf9df85 | ||
|
5a230f418d | ||
|
a47b76afcc | ||
|
f73b470ec0 | ||
|
812b2fff04 | ||
|
a9d9a5095b | ||
|
4bec182b32 | ||
|
28442aa922 | ||
|
660f6981c6 | ||
|
c16cf9cf8a | ||
|
60af1a4cce | ||
|
cc94a93b56 | ||
|
bd1850df25 | ||
|
c62f9839a2 | ||
|
ab02dba96f | ||
|
75563f6c7b | ||
|
58a70d76a3 | ||
|
dccbddad80 | ||
|
e3b137066d | ||
|
2fda90e414 | ||
|
eac4a6df68 | ||
|
fb0e71946f | ||
|
4bc3a1195f | ||
|
65f8f43665 | ||
|
c631a3e0e4 | ||
|
45e09664b8 | ||
|
93789ca5e5 | ||
|
545c97f903 | ||
|
22f6db2e4f | ||
|
551c24da57 | ||
|
9f1f7aff2b | ||
|
29bf1e2529 | ||
|
d60dac9749 | ||
|
fb95fa68a2 | ||
|
bc800a8d5a | ||
|
6a0c45fa2e | ||
|
604ca9316c | ||
|
17b4563a6f | ||
|
bf8ac87a78 | ||
|
13e631dcf7 | ||
|
ed61d249e0 | ||
|
24de5506c2 | ||
|
af8970ad7a | ||
|
9cc456937c | ||
|
aaae5b3ba6 | ||
|
fde5b77f6e | ||
|
41cc596559 | ||
|
1b7bc8b284 | ||
|
40a49081b9 | ||
|
cc74693176 | ||
|
f7da18a4a0 | ||
|
aab70b3aee | ||
|
4cce058e07 | ||
|
5b1d9739ce | ||
|
ee9ccaf414 | ||
|
220aa6ada0 | ||
|
94a5aee484 | ||
|
5cef4c0a4c | ||
|
89c42ebcbe | ||
|
a71f05f86c | ||
|
fa51e5b704 | ||
|
350207b05f | ||
|
7376efe8ea | ||
|
e3570a060a | ||
|
bd86459a94 | ||
|
c0638439be | ||
|
18d69c8d2b | ||
|
75658e2a96 | ||
|
331b953551 | ||
|
9d477d45c7 | ||
|
8c376f58cb | ||
|
a6a8a712e5 | ||
|
3fab5a3b14 | ||
|
322304f1d0 | ||
|
021c1f4f24 | ||
|
4e739bf7c4 | ||
|
1bac1bd58e | ||
|
2839266543 | ||
|
c1b543c74d | ||
|
3ab75fc8de | ||
|
c924b1250a | ||
|
e2de4fbfe7 | ||
|
aa49802119 | ||
|
bee586c6fd | ||
|
85e8bece2e | ||
|
db481e1799 | ||
|
115b488807 | ||
|
3993cd765c | ||
|
d9c259a231 | ||
|
071e97053f | ||
|
aea8f0df16 | ||
|
66b44b48a4 | ||
|
8b1cde83c1 | ||
|
1cf6c97779 | ||
|
1192e760a4 | ||
|
8b90084ebc | ||
|
f366e0f890 | ||
|
52045c761c | ||
|
fc21af4e6e | ||
|
0562426661 | ||
|
1c10677f82 | ||
|
2e56c59bcb | ||
|
7569f282c6 | ||
|
9666f4a8be | ||
|
fd0f5e4d12 | ||
|
714a344937 | ||
|
2b111cd631 | ||
|
1240217a73 | ||
|
31ed4c18f9 | ||
|
92e43df266 | ||
|
dec21524e1 | ||
|
8cb0b1ce9a | ||
|
b0486f0aaa | ||
|
ba5faa2582 | ||
|
e42316ba5a | ||
|
a300e2d2dc | ||
|
1ee6707bdd | ||
|
091929da12 | ||
|
90689585ef | ||
|
477cb539d0 | ||
|
88bf3c7063 | ||
|
d6011ba14d | ||
|
6d5bbca630 | ||
|
ce4f7601af | ||
|
d8cbb2a952 | ||
|
8dd62854fa | ||
|
a7f2fff219 | ||
|
0cf886302d | ||
|
fc8130955a | ||
|
4d00995d2d | ||
|
ae95540387 | ||
|
0e587488b5 | ||
|
aca1d577a4 | ||
|
7803e1be3d | ||
|
d137acf74d | ||
|
7be533a770 | ||
|
95bbb70c91 | ||
|
252b0c42d5 | ||
|
e3915e4b7a | ||
|
4abf7a7f88 | ||
|
38b02bbcc0 | ||
|
9977396d8f | ||
|
7d34a7acac | ||
|
373f200ab8 | ||
|
0eb488580d | ||
|
41fb98c771 | ||
|
a2d251ce1e | ||
|
e7777281d6 | ||
|
cca3dbc76d | ||
|
7ba57e7a7c | ||
|
f8db314134 | ||
|
fe7543c31a | ||
|
7f20c6149e | ||
|
d343713f61 | ||
|
b448472037 | ||
|
60850d71ce | ||
|
dcf44d2523 | ||
|
650882217c | ||
|
e616a7ebfc | ||
|
8dc6f9f589 | ||
|
49443406fd | ||
|
2d94e6e5d3 | ||
|
cc76a73c49 | ||
|
5e5cdf397c | ||
|
2c38a9213f | ||
|
901b2881fb | ||
|
b27976626a | ||
|
c17e54e3f6 | ||
|
65f1e0fcc2 | ||
|
3bd5a89d6f | ||
|
1a855aa6f7 | ||
|
eb461a5442 | ||
|
6edeed888d | ||
|
f34ade7610 | ||
|
7171b3a3ac | ||
|
70633b5c2f | ||
|
a724fa2347 | ||
|
2aa113fd8c | ||
|
1309a9cea0 | ||
|
e14ae33e86 | ||
|
871fd291f3 | ||
|
3b9654771d | ||
|
7d7228c356 | ||
|
56ac26f90e | ||
|
9c9f2dd5bd | ||
|
cddab635ff | ||
|
ae6c511f13 | ||
|
93a7b94507 | ||
|
f12a8fcd73 | ||
|
f804ccdece | ||
|
4c577d7f8c | ||
|
4ab7d6c23e | ||
|
eca8d21249 | ||
|
d3b95b9226 | ||
|
7711cd74c3 | ||
|
66a97bdde0 | ||
|
b635073829 | ||
|
e291342c4a | ||
|
b82d71d22a | ||
|
1632ee03da | ||
|
2756abce39 | ||
|
9c3144e286 | ||
|
ba046bd4dd | ||
|
2370e61431 | ||
|
f2908ed475 | ||
|
6614727be8 | ||
|
b211f839cb | ||
|
eaae2f3538 | ||
|
7171c95bdd | ||
|
b27333e52d | ||
|
4854fadc44 | ||
|
3e8ee27d9d | ||
|
8161cee70f | ||
|
de2a7fdc6c | ||
|
3ca16de851 | ||
|
c45dde6164 | ||
|
35a5dd9c45 | ||
|
157f165a3d | ||
|
637e366b18 | ||
|
a49ef49f87 | ||
|
8b66625c95 | ||
|
3c44d405c7 | ||
|
d064c40617 | ||
|
4214c69694 | ||
|
ec364cc737 | ||
|
49da347d84 | ||
|
2cc6f863bf | ||
|
0869f644fc | ||
|
04a2c19c87 | ||
|
5f1f4dcbdd | ||
|
aadf4b9b63 | ||
|
9bc2592da1 | ||
|
eeec1ce2ad | ||
|
39327ec753 | ||
|
28275a33d6 | ||
|
dd6b4ac221 | ||
|
428575f9ae | ||
|
3b4aad9df1 | ||
|
eaa52bc935 | ||
|
d8ec8ca2e6 | ||
|
0f94e1d3a2 | ||
|
d90d5ee9b6 | ||
|
2f29ff1a3f | ||
|
4a9f4e2505 | ||
|
ad3cb0bc93 | ||
|
4ce48307bb | ||
|
2759cb860b | ||
|
813006b33b | ||
|
81a10e649f | ||
|
6d76db1de5 | ||
|
5771c36d3f | ||
|
b11d3b5abf | ||
|
c2389fc209 | ||
|
e2aa932e97 | ||
|
0eb0d62e95 | ||
|
9f1f64e384 | ||
|
207825d30b | ||
|
52d12cc802 | ||
|
08e64c88ed | ||
|
b2687b7e70 | ||
|
9cb27613c3 | ||
|
5f611723a5 | ||
|
0459f0a4c0 | ||
|
f1e2598baa | ||
|
e0c091a9f4 | ||
|
100293c4b5 | ||
|
705084a25b | ||
|
f81f926a0c | ||
|
bc654bf865 |
19
.buildkite/coverage-in-disk.sh
Normal file
19
.buildkite/coverage-in-disk.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# This script is used to upload the full buildkite pipeline. The steps defined
|
||||
# in the buildkite UI should simply be:
|
||||
#
|
||||
# steps:
|
||||
# - command: ".buildkite/pipeline-upload.sh"
|
||||
#
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
source ci/_
|
||||
sudo chmod 0777 ci/buildkite-pipeline-in-disk.sh
|
||||
|
||||
_ ci/buildkite-pipeline-in-disk.sh pipeline.yml
|
||||
echo +++ pipeline
|
||||
cat pipeline.yml
|
||||
|
||||
_ buildkite-agent pipeline upload pipeline.yml
|
@@ -12,7 +12,14 @@ export PS4="++"
|
||||
# Restore target/ from the previous CI build on this machine
|
||||
#
|
||||
eval "$(ci/channel-info.sh)"
|
||||
export CARGO_TARGET_CACHE=$HOME/cargo-target-cache/"$CHANNEL"-"$BUILDKITE_LABEL"
|
||||
eval "$(ci/sbf-tools-info.sh)"
|
||||
source "ci/rust-version.sh"
|
||||
HOST_RUST_VERSION="$rust_stable"
|
||||
pattern='^[0-9]+\.[0-9]+\.[0-9]+$'
|
||||
if [[ ${HOST_RUST_VERSION} =~ ${pattern} ]]; then
|
||||
HOST_RUST_VERSION="${rust_stable%.*}"
|
||||
fi
|
||||
export CARGO_TARGET_CACHE=$HOME/cargo-target-cache/"$CHANNEL"-"$BUILDKITE_LABEL"-"$SBF_TOOLS_VERSION"-"$HOST_RUST_VERSION"
|
||||
(
|
||||
set -x
|
||||
MAX_CACHE_SIZE=18 # gigabytes
|
||||
|
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,5 +1,9 @@
|
||||
#### Problem
|
||||
|
||||
|
||||
|
||||
#### Summary of Changes
|
||||
|
||||
|
||||
|
||||
Fixes #
|
||||
|
66
.github/workflows/client-targets.yml
vendored
Normal file
66
.github/workflows/client-targets.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: client_targets
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- "client/**"
|
||||
- "sdk/**"
|
||||
- ".github/workflows/client-targets.yml"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
check_compilation:
|
||||
name: Client compilation
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
target: [aarch64-apple-ios, x86_64-apple-ios, aarch64-apple-darwin, x86_64-apple-darwin, aarch64-linux-android, armv7-linux-androideabi, i686-linux-android, x86_64-linux-android]
|
||||
include:
|
||||
- target: aarch64-apple-ios
|
||||
platform: ios
|
||||
os: macos-latest
|
||||
- target: x86_64-apple-ios
|
||||
platform: ios
|
||||
os: macos-latest
|
||||
- target: aarch64-apple-darwin
|
||||
platform: ios
|
||||
os: macos-latest
|
||||
- target: x86_64-apple-darwin
|
||||
platform: ios
|
||||
os: macos-latest
|
||||
- target: aarch64-linux-android
|
||||
platform: android
|
||||
os: ubuntu-latest
|
||||
- target: armv7-linux-androideabi
|
||||
platform: android
|
||||
os: ubuntu-latest
|
||||
- target: i686-linux-android
|
||||
platform: android
|
||||
os: ubuntu-latest
|
||||
- target: x86_64-linux-android
|
||||
platform: android
|
||||
os: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.target }}
|
||||
- name: Install cargo-ndk
|
||||
if: ${{ matrix.platform == 'android' }}
|
||||
run: cargo install cargo-ndk
|
||||
- uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.platform == 'android' }}
|
||||
with:
|
||||
command: ndk
|
||||
args: --target ${{ matrix.target }} build -p solana-client
|
||||
- uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.platform == 'ios' }}
|
||||
with:
|
||||
command: build
|
||||
args: -p solana-client --target ${{ matrix.target }}
|
40
.github/workflows/explorer_preview.yml
vendored
40
.github/workflows/explorer_preview.yml
vendored
@@ -17,6 +17,42 @@ jobs:
|
||||
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }} #Optional
|
||||
vercel-org-id: ${{ secrets.ORG_ID}} #Required
|
||||
vercel-project-id: ${{ secrets.PROJECT_ID}} #Required
|
||||
working-directory: ./explorer
|
||||
vercel-project-id: ${{ secrets.PROJECT_ID}} #Required
|
||||
scope: ${{ secrets.TEAM_ID }}
|
||||
|
||||
- name: vercel url
|
||||
run : |
|
||||
touch vercelfile.txt
|
||||
vercel --token ${{secrets.VERCEL_TOKEN}} ls explorer --scope team_8A2WD7p4uR7tmKX9M68loHXI > vercelfile.txt
|
||||
touch vercelfile1.txt
|
||||
head -n 2 vercelfile.txt > vercelfile1.txt
|
||||
touch vercelfile2.txt
|
||||
tail -n 1 vercelfile1.txt > vercelfile2.txt
|
||||
filtered_url7=$(cut -f7 -d" " vercelfile2.txt)
|
||||
echo "filtered_url7 is: $filtered_url7"
|
||||
touch .env.preview1
|
||||
echo "$filtered_url7" > .env.preview1
|
||||
#filtered_url=$(cat vercelfile2.txt )
|
||||
#echo "$filtered_url" >> .env.preview1
|
||||
|
||||
- name: Fetching Vercel Preview Deployment Link
|
||||
uses: mathiasvr/command-output@v1
|
||||
id: test1
|
||||
with:
|
||||
run: |
|
||||
echo "$(cat .env.preview1)"
|
||||
- name: Fetching PR commit URL
|
||||
uses: mathiasvr/command-output@v1
|
||||
id: test2
|
||||
with:
|
||||
run: |
|
||||
HEAD_SHA=$(curl -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/solana-labs/solana/pulls | jq .[0] | jq -r .head.sha)
|
||||
USER_NAME=$(curl -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/solana-labs/solana/pulls | jq .[0] | jq -r .head.user.login)
|
||||
echo "github.com/$USER_NAME/solana/commit/$HEAD_SHA"
|
||||
|
||||
- name: Slack Notification4
|
||||
uses: rtCamp/action-slack-notify@master
|
||||
env:
|
||||
SLACK_MESSAGE: ' Vercel Link: ${{ steps.test1.outputs.stdout }} PR Commit: ${{steps.test2.outputs.stdout}}'
|
||||
SLACK_TITLE: Vercel "Explorer" Preview Deployment Link , PR Commit
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
|
||||
|
6
.github/workflows/explorer_production.yml
vendored
6
.github/workflows/explorer_production.yml
vendored
@@ -30,7 +30,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: explorer
|
||||
working-directory: explorer
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
@@ -38,9 +39,8 @@ jobs:
|
||||
- uses: amondnet/vercel-action@v20
|
||||
with:
|
||||
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }} #Optional
|
||||
github-token: ${{ secrets.PAT }} #Optional
|
||||
vercel-args: '--prod' #for production
|
||||
vercel-org-id: ${{ secrets.ORG_ID}} #Required
|
||||
vercel-project-id: ${{ secrets.PROJECT_ID}} #Required
|
||||
working-directory: ./explorer
|
||||
scope: ${{ secrets.TEAM_ID }}
|
||||
|
19
.github/workflows/web3.yml
vendored
19
.github/workflows/web3.yml
vendored
@@ -65,20 +65,7 @@ jobs:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: web3.js/package-lock.json
|
||||
- run: npm i -g npm@7
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: |
|
||||
npm run build
|
||||
ls -l lib
|
||||
test -r lib/index.iife.js
|
||||
test -r lib/index.cjs.js
|
||||
test -r lib/index.esm.js
|
||||
- run: npm run doc
|
||||
- run: npm run codecov
|
||||
- run: |
|
||||
sh -c "$(curl -sSfL https://release.solana.com/edge/install)"
|
||||
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
|
||||
PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
|
||||
solana --version
|
||||
- run: npm run test:live-with-test-validator
|
||||
source .travis/before_install.sh
|
||||
npm install
|
||||
source .travis/script.sh
|
||||
|
@@ -113,3 +113,10 @@ pull_request_rules:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.9
|
||||
|
||||
commands_restrictions:
|
||||
# The author of copied PRs is the Mergify user.
|
||||
# Restrict `copy` access to Core Contributors
|
||||
copy:
|
||||
conditions:
|
||||
- author=@core-contributors
|
||||
|
19
.travis.yml
19
.travis.yml
@@ -78,7 +78,24 @@ jobs:
|
||||
# - sudo apt-get install libssl-dev libudev-dev
|
||||
|
||||
# docs pull request
|
||||
|
||||
# - name: "explorer"
|
||||
# if: type = pull_request AND branch = master
|
||||
|
||||
# language: node_js
|
||||
# node_js:
|
||||
# - "lts/*"
|
||||
|
||||
# cache:
|
||||
# directories:
|
||||
# - ~/.npm
|
||||
|
||||
# before_install:
|
||||
# - .travis/affects.sh explorer/ .travis || travis_terminate 0
|
||||
# - cd explorer
|
||||
|
||||
# script:
|
||||
# - npm run build
|
||||
# - npm run format
|
||||
- name: "docs"
|
||||
if: type IN (push, pull_request) OR tag IS present
|
||||
language: node_js
|
||||
|
2457
Cargo.lock
generated
2457
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
45
Cargo.toml
45
Cargo.toml
@@ -1,29 +1,30 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"account-decoder",
|
||||
"accounts-bench",
|
||||
"accounts-cluster-bench",
|
||||
"accountsdb-plugin-interface",
|
||||
"accountsdb-plugin-manager",
|
||||
"accountsdb-plugin-postgres",
|
||||
"accounts-cluster-bench",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"accounts-bench",
|
||||
"banking-bench",
|
||||
"banks-client",
|
||||
"banks-interface",
|
||||
"banks-server",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"bloom",
|
||||
"bucket_map",
|
||||
"clap-utils",
|
||||
"cli",
|
||||
"cli-config",
|
||||
"cli-output",
|
||||
"client",
|
||||
"client-test",
|
||||
"core",
|
||||
"dos",
|
||||
"download-utils",
|
||||
"entry",
|
||||
"faucet",
|
||||
"frozen-abi",
|
||||
"perf",
|
||||
"validator",
|
||||
"genesis",
|
||||
"genesis-utils",
|
||||
"gossip",
|
||||
@@ -32,17 +33,16 @@ members = [
|
||||
"ledger",
|
||||
"ledger-tool",
|
||||
"local-cluster",
|
||||
"logger",
|
||||
"log-analyzer",
|
||||
"logger",
|
||||
"measure",
|
||||
"merkle-root-bench",
|
||||
"merkle-tree",
|
||||
"storage-bigtable",
|
||||
"storage-proto",
|
||||
"streamer",
|
||||
"measure",
|
||||
"metrics",
|
||||
"net-shaper",
|
||||
"net-utils",
|
||||
"notifier",
|
||||
"perf",
|
||||
"poh",
|
||||
"poh-bench",
|
||||
"program-test",
|
||||
@@ -52,11 +52,17 @@ members = [
|
||||
"programs/bpf_loader/gen-syscall-list",
|
||||
"programs/compute-budget",
|
||||
"programs/config",
|
||||
"programs/ed25519-tests",
|
||||
"programs/stake",
|
||||
"programs/vote",
|
||||
"programs/zk-token-proof",
|
||||
"rayon-threadlimit",
|
||||
"rbpf-cli",
|
||||
"remote-wallet",
|
||||
"replica-lib",
|
||||
"replica-node",
|
||||
"rpc",
|
||||
"rpc-test",
|
||||
"runtime",
|
||||
"runtime/store-tool",
|
||||
"sdk",
|
||||
@@ -64,24 +70,19 @@ members = [
|
||||
"sdk/cargo-test-bpf",
|
||||
"send-transaction-service",
|
||||
"stake-accounts",
|
||||
"storage-bigtable",
|
||||
"storage-proto",
|
||||
"streamer",
|
||||
"sys-tuner",
|
||||
"test-validator",
|
||||
"tokens",
|
||||
"transaction-dos",
|
||||
"transaction-status",
|
||||
"account-decoder",
|
||||
"upload-perf",
|
||||
"net-utils",
|
||||
"validator",
|
||||
"version",
|
||||
"cli",
|
||||
"rayon-threadlimit",
|
||||
"watchtower",
|
||||
"replica-node",
|
||||
"replica-lib",
|
||||
"test-validator",
|
||||
"rpc-test",
|
||||
"client-test",
|
||||
"zk-token-sdk",
|
||||
"programs/zk-token-proof",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
|
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright 2020 Solana Foundation.
|
||||
Copyright 2022 Solana Foundation.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-account-decoder"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
description = "Solana account decoder"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,21 +10,21 @@ license = "Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.12.3"
|
||||
Inflector = "0.11.4"
|
||||
base64 = "0.13.0"
|
||||
bincode = "1.3.3"
|
||||
bs58 = "0.4.0"
|
||||
bv = "0.11.1"
|
||||
Inflector = "0.11.4"
|
||||
lazy_static = "1.4.0"
|
||||
serde = "1.0.133"
|
||||
serde = "1.0.136"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.74"
|
||||
solana-config-program = { path = "../programs/config", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.10.0" }
|
||||
serde_json = "1.0.79"
|
||||
solana-config-program = { path = "../programs/config", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.10.2" }
|
||||
spl-token = { version = "=3.2.0", features = ["no-entrypoint"] }
|
||||
thiserror = "1.0"
|
||||
zstd = "0.9.1"
|
||||
zstd = "0.11.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -33,7 +33,7 @@ pub type StringDecimals = String;
|
||||
pub const MAX_BASE58_BYTES: usize = 128;
|
||||
|
||||
/// A duplicate representation of an Account for pretty JSON serialization
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiAccount {
|
||||
pub lamports: u64,
|
||||
@@ -136,16 +136,13 @@ impl UiAccount {
|
||||
UiAccountData::Binary(blob, encoding) => match encoding {
|
||||
UiAccountEncoding::Base58 => bs58::decode(blob).into_vec().ok(),
|
||||
UiAccountEncoding::Base64 => base64::decode(blob).ok(),
|
||||
UiAccountEncoding::Base64Zstd => base64::decode(blob)
|
||||
.ok()
|
||||
.map(|zstd_data| {
|
||||
let mut data = vec![];
|
||||
zstd::stream::read::Decoder::new(zstd_data.as_slice())
|
||||
.and_then(|mut reader| reader.read_to_end(&mut data))
|
||||
.map(|_| data)
|
||||
.ok()
|
||||
})
|
||||
.flatten(),
|
||||
UiAccountEncoding::Base64Zstd => base64::decode(blob).ok().and_then(|zstd_data| {
|
||||
let mut data = vec![];
|
||||
zstd::stream::read::Decoder::new(zstd_data.as_slice())
|
||||
.and_then(|mut reader| reader.read_to_end(&mut data))
|
||||
.map(|_| data)
|
||||
.ok()
|
||||
}),
|
||||
UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None,
|
||||
},
|
||||
}?;
|
||||
|
@@ -5,7 +5,7 @@ use {
|
||||
parse_nonce::parse_nonce,
|
||||
parse_stake::parse_stake,
|
||||
parse_sysvar::parse_sysvar,
|
||||
parse_token::{parse_token, spl_token_id},
|
||||
parse_token::{parse_token, spl_token_ids},
|
||||
parse_vote::parse_vote,
|
||||
},
|
||||
inflector::Inflector,
|
||||
@@ -21,7 +21,6 @@ lazy_static! {
|
||||
static ref STAKE_PROGRAM_ID: Pubkey = stake::program::id();
|
||||
static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
|
||||
static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id();
|
||||
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id();
|
||||
static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id();
|
||||
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
|
||||
let mut m = HashMap::new();
|
||||
@@ -31,7 +30,9 @@ lazy_static! {
|
||||
);
|
||||
m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config);
|
||||
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
|
||||
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken);
|
||||
for spl_token_id in spl_token_ids() {
|
||||
m.insert(spl_token_id, ParsableAccount::SplToken);
|
||||
}
|
||||
m.insert(*STAKE_PROGRAM_ID, ParsableAccount::Stake);
|
||||
m.insert(*SYSVAR_PROGRAM_ID, ParsableAccount::Sysvar);
|
||||
m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
|
||||
|
@@ -15,16 +15,31 @@ use {
|
||||
|
||||
// A helper function to convert spl_token::id() as spl_sdk::pubkey::Pubkey to
|
||||
// solana_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_id() -> Pubkey {
|
||||
fn spl_token_id() -> Pubkey {
|
||||
Pubkey::new_from_array(spl_token::id().to_bytes())
|
||||
}
|
||||
|
||||
// Returns all known SPL Token program ids
|
||||
pub fn spl_token_ids() -> Vec<Pubkey> {
|
||||
vec![spl_token_id()]
|
||||
}
|
||||
|
||||
// Check if the provided program id as a known SPL Token program id
|
||||
pub fn is_known_spl_token_id(program_id: &Pubkey) -> bool {
|
||||
*program_id == spl_token_id()
|
||||
}
|
||||
|
||||
// A helper function to convert spl_token::native_mint::id() as spl_sdk::pubkey::Pubkey to
|
||||
// solana_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_native_mint() -> Pubkey {
|
||||
Pubkey::new_from_array(spl_token::native_mint::id().to_bytes())
|
||||
}
|
||||
|
||||
// The program id of the `spl_token_native_mint` account
|
||||
pub fn spl_token_native_mint_program_id() -> Pubkey {
|
||||
spl_token_id()
|
||||
}
|
||||
|
||||
// A helper function to convert a solana_sdk::pubkey::Pubkey to spl_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_pubkey(pubkey: &Pubkey) -> SplTokenPubkey {
|
||||
SplTokenPubkey::new_from_array(pubkey.to_bytes())
|
||||
|
@@ -2,21 +2,21 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2021"
|
||||
name = "solana-accounts-bench"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
log = "0.4.14"
|
||||
rayon = "1.5.1"
|
||||
solana-logger = { path = "../logger", version = "=1.10.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-version = { path = "../version", version = "=1.10.0" }
|
||||
clap = "2.33.1"
|
||||
solana-logger = { path = "../logger", version = "=1.10.2" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
solana-version = { path = "../version", version = "=1.10.2" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2021"
|
||||
name = "solana-accounts-cluster-bench"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -13,25 +13,25 @@ clap = "2.33.1"
|
||||
log = "0.4.14"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.5.1"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.10.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.10.0" }
|
||||
solana-client = { path = "../client", version = "=1.10.0" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.10.0" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.10.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.10.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.10.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.0" }
|
||||
solana-version = { path = "../version", version = "=1.10.0" }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.10.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.10.2" }
|
||||
solana-client = { path = "../client", version = "=1.10.2" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.10.2" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.10.2" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.2" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.10.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.10.2" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.2" }
|
||||
solana-version = { path = "../version", version = "=1.10.2" }
|
||||
spl-token = { version = "=3.2.0", features = ["no-entrypoint"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-core = { path = "../core", version = "=1.10.0" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.10.0" }
|
||||
solana-test-validator = { path = "../test-validator", version = "=1.10.0" }
|
||||
solana-core = { path = "../core", version = "=1.10.2" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.10.2" }
|
||||
solana-test-validator = { path = "../test-validator", version = "=1.10.2" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -116,7 +116,7 @@ fn make_create_message(
|
||||
|
||||
let instructions: Vec<_> = (0..num_instructions)
|
||||
.into_iter()
|
||||
.map(|_| {
|
||||
.flat_map(|_| {
|
||||
let program_id = if mint.is_some() {
|
||||
inline_spl_token::id()
|
||||
} else {
|
||||
@@ -148,7 +148,6 @@ fn make_create_message(
|
||||
|
||||
instructions
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
Message::new(&instructions, Some(&keypair.pubkey()))
|
||||
@@ -674,7 +673,7 @@ pub mod test {
|
||||
#[test]
|
||||
fn test_accounts_cluster_bench() {
|
||||
solana_logger::setup();
|
||||
let validator_config = ValidatorConfig::default();
|
||||
let validator_config = ValidatorConfig::default_for_test();
|
||||
let num_nodes = 1;
|
||||
let mut config = ClusterConfig {
|
||||
cluster_lamports: 10_000_000,
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2021"
|
||||
name = "solana-accountsdb-plugin-interface"
|
||||
description = "The Solana AccountsDb plugin interface."
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -11,9 +11,9 @@ documentation = "https://docs.rs/solana-accountsdb-plugin-interface"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.11"
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.2" }
|
||||
thiserror = "1.0.30"
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.0" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -9,9 +9,7 @@ use {
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
impl Eq for ReplicaAccountInfo<'_> {}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// Information about an account being updated
|
||||
pub struct ReplicaAccountInfo<'a> {
|
||||
/// The Pubkey for the account
|
||||
@@ -112,7 +110,7 @@ pub enum AccountsDbPluginError {
|
||||
}
|
||||
|
||||
/// The current status of a slot
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SlotStatus {
|
||||
/// The highest slot of the heaviest fork processed by the node. Ledger state at this slot is
|
||||
/// not derived from a confirmed or finalized block, but if multiple forks are present, is from
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2021"
|
||||
name = "solana-accountsdb-plugin-manager"
|
||||
description = "The Solana AccountsDb plugin manager."
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -12,18 +12,17 @@ documentation = "https://docs.rs/solana-validator"
|
||||
[dependencies]
|
||||
bs58 = "0.4.0"
|
||||
crossbeam-channel = "0.5"
|
||||
libloading = "0.7.2"
|
||||
json5 = "0.4.1"
|
||||
libloading = "0.7.3"
|
||||
log = "0.4.11"
|
||||
serde = "1.0.133"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.74"
|
||||
solana-accountsdb-plugin-interface = { path = "../accountsdb-plugin-interface", version = "=1.10.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.0" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.10.0" }
|
||||
solana-rpc = { path = "../rpc", version = "=1.10.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.0" }
|
||||
serde_json = "1.0.79"
|
||||
solana-accountsdb-plugin-interface = { path = "../accountsdb-plugin-interface", version = "=1.10.2" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.2" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.10.2" }
|
||||
solana-rpc = { path = "../rpc", version = "=1.10.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.2" }
|
||||
thiserror = "1.0.30"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
@@ -9,7 +9,6 @@ use {
|
||||
},
|
||||
crossbeam_channel::Receiver,
|
||||
log::*,
|
||||
serde_json,
|
||||
solana_rpc::{
|
||||
optimistically_confirmed_bank_tracker::BankNotification,
|
||||
transaction_notifier_interface::TransactionNotifierLock,
|
||||
@@ -156,12 +155,12 @@ impl AccountsDbPluginService {
|
||||
)));
|
||||
}
|
||||
|
||||
let result: serde_json::Value = match serde_json::from_str(&contents) {
|
||||
let result: serde_json::Value = match json5::from_str(&contents) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
return Err(AccountsdbPluginServiceError::InvalidConfigFileFormat(
|
||||
format!(
|
||||
"The config file {:?} is not in a valid Json format, error: {:?}",
|
||||
"The config file {:?} is not in a valid Json5 format, error: {:?}",
|
||||
accountsdb_plugin_config_file, err
|
||||
),
|
||||
));
|
||||
@@ -171,13 +170,24 @@ impl AccountsDbPluginService {
|
||||
let libpath = result["libpath"]
|
||||
.as_str()
|
||||
.ok_or(AccountsdbPluginServiceError::LibPathNotSet)?;
|
||||
let mut libpath = PathBuf::from(libpath);
|
||||
if libpath.is_relative() {
|
||||
let config_dir = accountsdb_plugin_config_file.parent().ok_or_else(|| {
|
||||
AccountsdbPluginServiceError::CannotOpenConfigFile(format!(
|
||||
"Failed to resolve parent of {:?}",
|
||||
accountsdb_plugin_config_file,
|
||||
))
|
||||
})?;
|
||||
libpath = config_dir.join(libpath);
|
||||
}
|
||||
|
||||
let config_file = accountsdb_plugin_config_file
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.ok_or(AccountsdbPluginServiceError::InvalidPluginPath)?;
|
||||
|
||||
unsafe {
|
||||
let result = plugin_manager.load_plugin(libpath, config_file);
|
||||
let result = plugin_manager.load_plugin(libpath.to_str().unwrap(), config_file);
|
||||
if let Err(err) = result {
|
||||
let msg = format!(
|
||||
"Failed to load the plugin library: {:?}, error: {:?}",
|
||||
|
@@ -52,7 +52,7 @@ impl SlotStatusNotifierImpl {
|
||||
|
||||
for plugin in plugin_manager.plugins.iter_mut() {
|
||||
let mut measure = Measure::start("accountsdb-plugin-update-slot");
|
||||
match plugin.update_slot_status(slot, parent, slot_status.clone()) {
|
||||
match plugin.update_slot_status(slot, parent, slot_status) {
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Failed to update slot status at slot {}, error: {} to plugin {}",
|
||||
|
@@ -8,7 +8,6 @@ use {
|
||||
solana_measure::measure::Measure,
|
||||
solana_metrics::*,
|
||||
solana_rpc::transaction_notifier_interface::TransactionNotifier,
|
||||
solana_runtime::bank,
|
||||
solana_sdk::{clock::Slot, signature::Signature, transaction::SanitizedTransaction},
|
||||
solana_transaction_status::TransactionStatusMeta,
|
||||
std::sync::{Arc, RwLock},
|
||||
@@ -85,7 +84,7 @@ impl TransactionNotifierImpl {
|
||||
) -> ReplicaTransactionInfo<'a> {
|
||||
ReplicaTransactionInfo {
|
||||
signature,
|
||||
is_vote: bank::is_simple_vote_transaction(transaction),
|
||||
is_vote: transaction.is_simple_vote_transaction(),
|
||||
transaction,
|
||||
transaction_status_meta,
|
||||
}
|
||||
|
@@ -1,39 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2021"
|
||||
name = "solana-accountsdb-plugin-postgres"
|
||||
description = "The Solana AccountsDb plugin for PostgreSQL database."
|
||||
version = "1.10.0"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-validator"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.4.0"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
crossbeam-channel = "0.5"
|
||||
log = "0.4.14"
|
||||
postgres = { version = "0.19.2", features = ["with-chrono-0_4"] }
|
||||
postgres-types = { version = "0.2.2", features = ["derive"] }
|
||||
serde = "1.0.133"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.74"
|
||||
solana-accountsdb-plugin-interface = { path = "../accountsdb-plugin-interface", version = "=1.10.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.0" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.10.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.0" }
|
||||
thiserror = "1.0.30"
|
||||
tokio-postgres = "0.7.4"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.10.0" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
@@ -1,5 +0,0 @@
|
||||
This is an example implementing the AccountsDb plugin for PostgreSQL database.
|
||||
Please see the `src/accountsdb_plugin_postgres.rs` for the format of the plugin's configuration file.
|
||||
|
||||
To create the schema objects for the database, please use `scripts/create_schema.sql`.
|
||||
`scripts/drop_schema.sql` can be used to tear down the schema objects.
|
@@ -1,195 +0,0 @@
|
||||
/**
|
||||
* This plugin implementation for PostgreSQL requires the following tables
|
||||
*/
|
||||
-- The table storing accounts
|
||||
|
||||
|
||||
CREATE TABLE account (
|
||||
pubkey BYTEA PRIMARY KEY,
|
||||
owner BYTEA,
|
||||
lamports BIGINT NOT NULL,
|
||||
slot BIGINT NOT NULL,
|
||||
executable BOOL NOT NULL,
|
||||
rent_epoch BIGINT NOT NULL,
|
||||
data BYTEA,
|
||||
write_version BIGINT NOT NULL,
|
||||
updated_on TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
-- The table storing slot information
|
||||
CREATE TABLE slot (
|
||||
slot BIGINT PRIMARY KEY,
|
||||
parent BIGINT,
|
||||
status VARCHAR(16) NOT NULL,
|
||||
updated_on TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
-- Types for Transactions
|
||||
|
||||
Create TYPE "TransactionErrorCode" AS ENUM (
|
||||
'AccountInUse',
|
||||
'AccountLoadedTwice',
|
||||
'AccountNotFound',
|
||||
'ProgramAccountNotFound',
|
||||
'InsufficientFundsForFee',
|
||||
'InvalidAccountForFee',
|
||||
'AlreadyProcessed',
|
||||
'BlockhashNotFound',
|
||||
'InstructionError',
|
||||
'CallChainTooDeep',
|
||||
'MissingSignatureForFee',
|
||||
'InvalidAccountIndex',
|
||||
'SignatureFailure',
|
||||
'InvalidProgramForExecution',
|
||||
'SanitizeFailure',
|
||||
'ClusterMaintenance',
|
||||
'AccountBorrowOutstanding',
|
||||
'WouldExceedMaxAccountCostLimit',
|
||||
'WouldExceedMaxBlockCostLimit',
|
||||
'UnsupportedVersion',
|
||||
'InvalidWritableAccount',
|
||||
'WouldExceedMaxAccountDataCostLimit'
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionError" AS (
|
||||
error_code "TransactionErrorCode",
|
||||
error_detail VARCHAR(256)
|
||||
);
|
||||
|
||||
CREATE TYPE "CompiledInstruction" AS (
|
||||
program_id_index SMALLINT,
|
||||
accounts SMALLINT[],
|
||||
data BYTEA
|
||||
);
|
||||
|
||||
CREATE TYPE "InnerInstructions" AS (
|
||||
index SMALLINT,
|
||||
instructions "CompiledInstruction"[]
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionTokenBalance" AS (
|
||||
account_index SMALLINT,
|
||||
mint VARCHAR(44),
|
||||
ui_token_amount DOUBLE PRECISION,
|
||||
owner VARCHAR(44)
|
||||
);
|
||||
|
||||
Create TYPE "RewardType" AS ENUM (
|
||||
'Fee',
|
||||
'Rent',
|
||||
'Staking',
|
||||
'Voting'
|
||||
);
|
||||
|
||||
CREATE TYPE "Reward" AS (
|
||||
pubkey VARCHAR(44),
|
||||
lamports BIGINT,
|
||||
post_balance BIGINT,
|
||||
reward_type "RewardType",
|
||||
commission SMALLINT
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionStatusMeta" AS (
|
||||
error "TransactionError",
|
||||
fee BIGINT,
|
||||
pre_balances BIGINT[],
|
||||
post_balances BIGINT[],
|
||||
inner_instructions "InnerInstructions"[],
|
||||
log_messages TEXT[],
|
||||
pre_token_balances "TransactionTokenBalance"[],
|
||||
post_token_balances "TransactionTokenBalance"[],
|
||||
rewards "Reward"[]
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionMessageHeader" AS (
|
||||
num_required_signatures SMALLINT,
|
||||
num_readonly_signed_accounts SMALLINT,
|
||||
num_readonly_unsigned_accounts SMALLINT
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionMessage" AS (
|
||||
header "TransactionMessageHeader",
|
||||
account_keys BYTEA[],
|
||||
recent_blockhash BYTEA,
|
||||
instructions "CompiledInstruction"[]
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionMessageAddressTableLookup" AS (
|
||||
account_key BYTEA,
|
||||
writable_indexes SMALLINT[],
|
||||
readonly_indexes SMALLINT[]
|
||||
);
|
||||
|
||||
CREATE TYPE "TransactionMessageV0" AS (
|
||||
header "TransactionMessageHeader",
|
||||
account_keys BYTEA[],
|
||||
recent_blockhash BYTEA,
|
||||
instructions "CompiledInstruction"[],
|
||||
address_table_lookups "TransactionMessageAddressTableLookup"[]
|
||||
);
|
||||
|
||||
CREATE TYPE "LoadedAddresses" AS (
|
||||
writable BYTEA[],
|
||||
readonly BYTEA[]
|
||||
);
|
||||
|
||||
CREATE TYPE "LoadedMessageV0" AS (
|
||||
message "TransactionMessageV0",
|
||||
loaded_addresses "LoadedAddresses"
|
||||
);
|
||||
|
||||
-- The table storing transactions
|
||||
CREATE TABLE transaction (
|
||||
slot BIGINT NOT NULL,
|
||||
signature BYTEA NOT NULL,
|
||||
is_vote BOOL NOT NULL,
|
||||
message_type SMALLINT, -- 0: legacy, 1: v0 message
|
||||
legacy_message "TransactionMessage",
|
||||
v0_loaded_message "LoadedMessageV0",
|
||||
signatures BYTEA[],
|
||||
message_hash BYTEA,
|
||||
meta "TransactionStatusMeta",
|
||||
updated_on TIMESTAMP NOT NULL,
|
||||
CONSTRAINT transaction_pk PRIMARY KEY (slot, signature)
|
||||
);
|
||||
|
||||
-- The table storing block metadata
|
||||
CREATE TABLE block (
|
||||
slot BIGINT PRIMARY KEY,
|
||||
blockhash VARCHAR(44),
|
||||
rewards "Reward"[],
|
||||
block_time BIGINT,
|
||||
block_height BIGINT,
|
||||
updated_on TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
/**
|
||||
* The following is for keeping historical data for accounts and is not required for plugin to work.
|
||||
*/
|
||||
-- The table storing historical data for accounts
|
||||
CREATE TABLE account_audit (
|
||||
pubkey BYTEA,
|
||||
owner BYTEA,
|
||||
lamports BIGINT NOT NULL,
|
||||
slot BIGINT NOT NULL,
|
||||
executable BOOL NOT NULL,
|
||||
rent_epoch BIGINT NOT NULL,
|
||||
data BYTEA,
|
||||
write_version BIGINT NOT NULL,
|
||||
updated_on TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX account_audit_account_key ON account_audit (pubkey, write_version);
|
||||
|
||||
CREATE FUNCTION audit_account_update() RETURNS trigger AS $audit_account_update$
|
||||
BEGIN
|
||||
INSERT INTO account_audit (pubkey, owner, lamports, slot, executable, rent_epoch, data, write_version, updated_on)
|
||||
VALUES (OLD.pubkey, OLD.owner, OLD.lamports, OLD.slot,
|
||||
OLD.executable, OLD.rent_epoch, OLD.data, OLD.write_version, OLD.updated_on);
|
||||
RETURN NEW;
|
||||
END;
|
||||
|
||||
$audit_account_update$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER account_update_trigger AFTER UPDATE OR DELETE ON account
|
||||
FOR EACH ROW EXECUTE PROCEDURE audit_account_update();
|
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Script for cleaning up the schema for PostgreSQL used for the AccountsDb plugin.
|
||||
*/
|
||||
|
||||
DROP TRIGGER account_update_trigger ON account;
|
||||
DROP FUNCTION audit_account_update;
|
||||
DROP TABLE account_audit;
|
||||
DROP TABLE account;
|
||||
DROP TABLE slot;
|
||||
DROP TABLE transaction;
|
||||
DROP TABLE block;
|
||||
|
||||
DROP TYPE "TransactionError" CASCADE;
|
||||
DROP TYPE "TransactionErrorCode" CASCADE;
|
||||
DROP TYPE "LoadedMessageV0" CASCADE;
|
||||
DROP TYPE "LoadedAddresses" CASCADE;
|
||||
DROP TYPE "TransactionMessageV0" CASCADE;
|
||||
DROP TYPE "TransactionMessage" CASCADE;
|
||||
DROP TYPE "TransactionMessageHeader" CASCADE;
|
||||
DROP TYPE "TransactionMessageAddressTableLookup" CASCADE;
|
||||
DROP TYPE "TransactionStatusMeta" CASCADE;
|
||||
DROP TYPE "RewardType" CASCADE;
|
||||
DROP TYPE "Reward" CASCADE;
|
||||
DROP TYPE "TransactionTokenBalance" CASCADE;
|
||||
DROP TYPE "InnerInstructions" CASCADE;
|
||||
DROP TYPE "CompiledInstruction" CASCADE;
|
@@ -1,802 +0,0 @@
|
||||
# This a reference configuration file for the PostgreSQL database version 14.
|
||||
|
||||
# -----------------------------
|
||||
# PostgreSQL configuration file
|
||||
# -----------------------------
|
||||
#
|
||||
# This file consists of lines of the form:
|
||||
#
|
||||
# name = value
|
||||
#
|
||||
# (The "=" is optional.) Whitespace may be used. Comments are introduced with
|
||||
# "#" anywhere on a line. The complete list of parameter names and allowed
|
||||
# values can be found in the PostgreSQL documentation.
|
||||
#
|
||||
# The commented-out settings shown in this file represent the default values.
|
||||
# Re-commenting a setting is NOT sufficient to revert it to the default value;
|
||||
# you need to reload the server.
|
||||
#
|
||||
# This file is read on server startup and when the server receives a SIGHUP
|
||||
# signal. If you edit the file on a running system, you have to SIGHUP the
|
||||
# server for the changes to take effect, run "pg_ctl reload", or execute
|
||||
# "SELECT pg_reload_conf()". Some parameters, which are marked below,
|
||||
# require a server shutdown and restart to take effect.
|
||||
#
|
||||
# Any parameter can also be given as a command-line option to the server, e.g.,
|
||||
# "postgres -c log_connections=on". Some parameters can be changed at run time
|
||||
# with the "SET" SQL command.
|
||||
#
|
||||
# Memory units: B = bytes Time units: us = microseconds
|
||||
# kB = kilobytes ms = milliseconds
|
||||
# MB = megabytes s = seconds
|
||||
# GB = gigabytes min = minutes
|
||||
# TB = terabytes h = hours
|
||||
# d = days
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# FILE LOCATIONS
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# The default values of these variables are driven from the -D command-line
|
||||
# option or PGDATA environment variable, represented here as ConfigDir.
|
||||
|
||||
data_directory = '/var/lib/postgresql/14/main' # use data in another directory
|
||||
# (change requires restart)
|
||||
|
||||
hba_file = '/etc/postgresql/14/main/pg_hba.conf' # host-based authentication file
|
||||
# (change requires restart)
|
||||
ident_file = '/etc/postgresql/14/main/pg_ident.conf' # ident configuration file
|
||||
# (change requires restart)
|
||||
|
||||
# If external_pid_file is not explicitly set, no extra PID file is written.
|
||||
external_pid_file = '/var/run/postgresql/14-main.pid' # write an extra PID file
|
||||
# (change requires restart)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# CONNECTIONS AND AUTHENTICATION
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Connection Settings -
|
||||
|
||||
#listen_addresses = 'localhost' # what IP address(es) to listen on;
|
||||
# comma-separated list of addresses;
|
||||
# defaults to 'localhost'; use '*' for all
|
||||
# (change requires restart)
|
||||
listen_addresses = '*'
|
||||
port = 5433 # (change requires restart)
|
||||
max_connections = 200 # (change requires restart)
|
||||
#superuser_reserved_connections = 3 # (change requires restart)
|
||||
unix_socket_directories = '/var/run/postgresql' # comma-separated list of directories
|
||||
# (change requires restart)
|
||||
#unix_socket_group = '' # (change requires restart)
|
||||
#unix_socket_permissions = 0777 # begin with 0 to use octal notation
|
||||
# (change requires restart)
|
||||
#bonjour = off # advertise server via Bonjour
|
||||
# (change requires restart)
|
||||
#bonjour_name = '' # defaults to the computer name
|
||||
# (change requires restart)
|
||||
|
||||
# - TCP settings -
|
||||
# see "man tcp" for details
|
||||
|
||||
#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds;
|
||||
# 0 selects the system default
|
||||
#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds;
|
||||
# 0 selects the system default
|
||||
#tcp_keepalives_count = 0 # TCP_KEEPCNT;
|
||||
# 0 selects the system default
|
||||
#tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds;
|
||||
# 0 selects the system default
|
||||
|
||||
#client_connection_check_interval = 0 # time between checks for client
|
||||
# disconnection while running queries;
|
||||
# 0 for never
|
||||
|
||||
# - Authentication -
|
||||
|
||||
#authentication_timeout = 1min # 1s-600s
|
||||
#password_encryption = scram-sha-256 # scram-sha-256 or md5
|
||||
#db_user_namespace = off
|
||||
|
||||
# GSSAPI using Kerberos
|
||||
#krb_server_keyfile = 'FILE:${sysconfdir}/krb5.keytab'
|
||||
#krb_caseins_users = off
|
||||
|
||||
# - SSL -
|
||||
|
||||
ssl = on
|
||||
#ssl_ca_file = ''
|
||||
ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem'
|
||||
#ssl_crl_file = ''
|
||||
#ssl_crl_dir = ''
|
||||
ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'
|
||||
#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
|
||||
#ssl_prefer_server_ciphers = on
|
||||
#ssl_ecdh_curve = 'prime256v1'
|
||||
#ssl_min_protocol_version = 'TLSv1.2'
|
||||
#ssl_max_protocol_version = ''
|
||||
#ssl_dh_params_file = ''
|
||||
#ssl_passphrase_command = ''
|
||||
#ssl_passphrase_command_supports_reload = off
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# RESOURCE USAGE (except WAL)
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Memory -
|
||||
|
||||
shared_buffers = 1GB # min 128kB
|
||||
# (change requires restart)
|
||||
#huge_pages = try # on, off, or try
|
||||
# (change requires restart)
|
||||
#huge_page_size = 0 # zero for system default
|
||||
# (change requires restart)
|
||||
#temp_buffers = 8MB # min 800kB
|
||||
#max_prepared_transactions = 0 # zero disables the feature
|
||||
# (change requires restart)
|
||||
# Caution: it is not advisable to set max_prepared_transactions nonzero unless
|
||||
# you actively intend to use prepared transactions.
|
||||
#work_mem = 4MB # min 64kB
|
||||
#hash_mem_multiplier = 1.0 # 1-1000.0 multiplier on hash table work_mem
|
||||
#maintenance_work_mem = 64MB # min 1MB
|
||||
#autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem
|
||||
#logical_decoding_work_mem = 64MB # min 64kB
|
||||
#max_stack_depth = 2MB # min 100kB
|
||||
#shared_memory_type = mmap # the default is the first option
|
||||
# supported by the operating system:
|
||||
# mmap
|
||||
# sysv
|
||||
# windows
|
||||
# (change requires restart)
|
||||
dynamic_shared_memory_type = posix # the default is the first option
|
||||
# supported by the operating system:
|
||||
# posix
|
||||
# sysv
|
||||
# windows
|
||||
# mmap
|
||||
# (change requires restart)
|
||||
#min_dynamic_shared_memory = 0MB # (change requires restart)
|
||||
|
||||
# - Disk -
|
||||
|
||||
#temp_file_limit = -1 # limits per-process temp file space
|
||||
# in kilobytes, or -1 for no limit
|
||||
|
||||
# - Kernel Resources -
|
||||
|
||||
#max_files_per_process = 1000 # min 64
|
||||
# (change requires restart)
|
||||
|
||||
# - Cost-Based Vacuum Delay -
|
||||
|
||||
#vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables)
|
||||
#vacuum_cost_page_hit = 1 # 0-10000 credits
|
||||
#vacuum_cost_page_miss = 2 # 0-10000 credits
|
||||
#vacuum_cost_page_dirty = 20 # 0-10000 credits
|
||||
#vacuum_cost_limit = 200 # 1-10000 credits
|
||||
|
||||
# - Background Writer -
|
||||
|
||||
#bgwriter_delay = 200ms # 10-10000ms between rounds
|
||||
#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables
|
||||
#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round
|
||||
#bgwriter_flush_after = 512kB # measured in pages, 0 disables
|
||||
|
||||
# - Asynchronous Behavior -
|
||||
|
||||
#backend_flush_after = 0 # measured in pages, 0 disables
|
||||
effective_io_concurrency = 1000 # 1-1000; 0 disables prefetching
|
||||
#maintenance_io_concurrency = 10 # 1-1000; 0 disables prefetching
|
||||
#max_worker_processes = 8 # (change requires restart)
|
||||
#max_parallel_workers_per_gather = 2 # taken from max_parallel_workers
|
||||
#max_parallel_maintenance_workers = 2 # taken from max_parallel_workers
|
||||
#max_parallel_workers = 8 # maximum number of max_worker_processes that
|
||||
# can be used in parallel operations
|
||||
#parallel_leader_participation = on
|
||||
#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate
|
||||
# (change requires restart)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# WRITE-AHEAD LOG
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Settings -
|
||||
|
||||
wal_level = minimal # minimal, replica, or logical
|
||||
# (change requires restart)
|
||||
fsync = off # flush data to disk for crash safety
|
||||
# (turning this off can cause
|
||||
# unrecoverable data corruption)
|
||||
synchronous_commit = off # synchronization level;
|
||||
# off, local, remote_write, remote_apply, or on
|
||||
#wal_sync_method = fsync # the default is the first option
|
||||
# supported by the operating system:
|
||||
# open_datasync
|
||||
# fdatasync (default on Linux and FreeBSD)
|
||||
# fsync
|
||||
# fsync_writethrough
|
||||
# open_sync
|
||||
full_page_writes = off # recover from partial page writes
|
||||
#wal_log_hints = off # also do full page writes of non-critical updates
|
||||
# (change requires restart)
|
||||
#wal_compression = off # enable compression of full-page writes
|
||||
#wal_init_zero = on # zero-fill new WAL files
|
||||
#wal_recycle = on # recycle WAL files
|
||||
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
|
||||
# (change requires restart)
|
||||
#wal_writer_delay = 200ms # 1-10000 milliseconds
|
||||
#wal_writer_flush_after = 1MB # measured in pages, 0 disables
|
||||
#wal_skip_threshold = 2MB
|
||||
|
||||
#commit_delay = 0 # range 0-100000, in microseconds
|
||||
#commit_siblings = 5 # range 1-1000
|
||||
|
||||
# - Checkpoints -
|
||||
|
||||
#checkpoint_timeout = 5min # range 30s-1d
|
||||
#checkpoint_completion_target = 0.9 # checkpoint target duration, 0.0 - 1.0
|
||||
#checkpoint_flush_after = 256kB # measured in pages, 0 disables
|
||||
#checkpoint_warning = 30s # 0 disables
|
||||
max_wal_size = 1GB
|
||||
min_wal_size = 80MB
|
||||
|
||||
# - Archiving -
|
||||
|
||||
#archive_mode = off # enables archiving; off, on, or always
|
||||
# (change requires restart)
|
||||
#archive_command = '' # command to use to archive a logfile segment
|
||||
# placeholders: %p = path of file to archive
|
||||
# %f = file name only
|
||||
# e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'
|
||||
#archive_timeout = 0 # force a logfile segment switch after this
|
||||
# number of seconds; 0 disables
|
||||
|
||||
# - Archive Recovery -
|
||||
|
||||
# These are only used in recovery mode.
|
||||
|
||||
#restore_command = '' # command to use to restore an archived logfile segment
|
||||
# placeholders: %p = path of file to restore
|
||||
# %f = file name only
|
||||
# e.g. 'cp /mnt/server/archivedir/%f %p'
|
||||
#archive_cleanup_command = '' # command to execute at every restartpoint
|
||||
#recovery_end_command = '' # command to execute at completion of recovery
|
||||
|
||||
# - Recovery Target -
|
||||
|
||||
# Set these only when performing a targeted recovery.
|
||||
|
||||
#recovery_target = '' # 'immediate' to end recovery as soon as a
|
||||
# consistent state is reached
|
||||
# (change requires restart)
|
||||
#recovery_target_name = '' # the named restore point to which recovery will proceed
|
||||
# (change requires restart)
|
||||
#recovery_target_time = '' # the time stamp up to which recovery will proceed
|
||||
# (change requires restart)
|
||||
#recovery_target_xid = '' # the transaction ID up to which recovery will proceed
|
||||
# (change requires restart)
|
||||
#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed
|
||||
# (change requires restart)
|
||||
#recovery_target_inclusive = on # Specifies whether to stop:
|
||||
# just after the specified recovery target (on)
|
||||
# just before the recovery target (off)
|
||||
# (change requires restart)
|
||||
#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID
|
||||
# (change requires restart)
|
||||
#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown'
|
||||
# (change requires restart)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# REPLICATION
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Sending Servers -
|
||||
|
||||
# Set these on the primary and on any standby that will send replication data.
|
||||
|
||||
max_wal_senders = 0 # max number of walsender processes
|
||||
# (change requires restart)
|
||||
#max_replication_slots = 10 # max number of replication slots
|
||||
# (change requires restart)
|
||||
#wal_keep_size = 0 # in megabytes; 0 disables
|
||||
#max_slot_wal_keep_size = -1 # in megabytes; -1 disables
|
||||
#wal_sender_timeout = 60s # in milliseconds; 0 disables
|
||||
#track_commit_timestamp = off # collect timestamp of transaction commit
|
||||
# (change requires restart)
|
||||
|
||||
# - Primary Server -
|
||||
|
||||
# These settings are ignored on a standby server.
|
||||
|
||||
#synchronous_standby_names = '' # standby servers that provide sync rep
|
||||
# method to choose sync standbys, number of sync standbys,
|
||||
# and comma-separated list of application_name
|
||||
# from standby(s); '*' = all
|
||||
#vacuum_defer_cleanup_age = 0 # number of xacts by which cleanup is delayed
|
||||
|
||||
# - Standby Servers -
|
||||
|
||||
# These settings are ignored on a primary server.
|
||||
|
||||
#primary_conninfo = '' # connection string to sending server
|
||||
#primary_slot_name = '' # replication slot on sending server
|
||||
#promote_trigger_file = '' # file name whose presence ends recovery
|
||||
#hot_standby = on # "off" disallows queries during recovery
|
||||
# (change requires restart)
|
||||
#max_standby_archive_delay = 30s # max delay before canceling queries
|
||||
# when reading WAL from archive;
|
||||
# -1 allows indefinite delay
|
||||
#max_standby_streaming_delay = 30s # max delay before canceling queries
|
||||
# when reading streaming WAL;
|
||||
# -1 allows indefinite delay
|
||||
#wal_receiver_create_temp_slot = off # create temp slot if primary_slot_name
|
||||
# is not set
|
||||
#wal_receiver_status_interval = 10s # send replies at least this often
|
||||
# 0 disables
|
||||
#hot_standby_feedback = off # send info from standby to prevent
|
||||
# query conflicts
|
||||
#wal_receiver_timeout = 60s # time that receiver waits for
|
||||
# communication from primary
|
||||
# in milliseconds; 0 disables
|
||||
#wal_retrieve_retry_interval = 5s # time to wait before retrying to
|
||||
# retrieve WAL after a failed attempt
|
||||
#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery
|
||||
|
||||
# - Subscribers -
|
||||
|
||||
# These settings are ignored on a publisher.
|
||||
|
||||
#max_logical_replication_workers = 4 # taken from max_worker_processes
|
||||
# (change requires restart)
|
||||
#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# QUERY TUNING
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Planner Method Configuration -
|
||||
|
||||
#enable_async_append = on
|
||||
#enable_bitmapscan = on
|
||||
#enable_gathermerge = on
|
||||
#enable_hashagg = on
|
||||
#enable_hashjoin = on
|
||||
#enable_incremental_sort = on
|
||||
#enable_indexscan = on
|
||||
#enable_indexonlyscan = on
|
||||
#enable_material = on
|
||||
#enable_memoize = on
|
||||
#enable_mergejoin = on
|
||||
#enable_nestloop = on
|
||||
#enable_parallel_append = on
|
||||
#enable_parallel_hash = on
|
||||
#enable_partition_pruning = on
|
||||
#enable_partitionwise_join = off
|
||||
#enable_partitionwise_aggregate = off
|
||||
#enable_seqscan = on
|
||||
#enable_sort = on
|
||||
#enable_tidscan = on
|
||||
|
||||
# - Planner Cost Constants -
|
||||
|
||||
#seq_page_cost = 1.0 # measured on an arbitrary scale
|
||||
#random_page_cost = 4.0 # same scale as above
|
||||
#cpu_tuple_cost = 0.01 # same scale as above
|
||||
#cpu_index_tuple_cost = 0.005 # same scale as above
|
||||
#cpu_operator_cost = 0.0025 # same scale as above
|
||||
#parallel_setup_cost = 1000.0 # same scale as above
|
||||
#parallel_tuple_cost = 0.1 # same scale as above
|
||||
#min_parallel_table_scan_size = 8MB
|
||||
#min_parallel_index_scan_size = 512kB
|
||||
#effective_cache_size = 4GB
|
||||
|
||||
#jit_above_cost = 100000 # perform JIT compilation if available
|
||||
# and query more expensive than this;
|
||||
# -1 disables
|
||||
#jit_inline_above_cost = 500000 # inline small functions if query is
|
||||
# more expensive than this; -1 disables
|
||||
#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if
|
||||
# query is more expensive than this;
|
||||
# -1 disables
|
||||
|
||||
# - Genetic Query Optimizer -
|
||||
|
||||
#geqo = on
|
||||
#geqo_threshold = 12
|
||||
#geqo_effort = 5 # range 1-10
|
||||
#geqo_pool_size = 0 # selects default based on effort
|
||||
#geqo_generations = 0 # selects default based on effort
|
||||
#geqo_selection_bias = 2.0 # range 1.5-2.0
|
||||
#geqo_seed = 0.0 # range 0.0-1.0
|
||||
|
||||
# - Other Planner Options -
|
||||
|
||||
#default_statistics_target = 100 # range 1-10000
|
||||
#constraint_exclusion = partition # on, off, or partition
|
||||
#cursor_tuple_fraction = 0.1 # range 0.0-1.0
|
||||
#from_collapse_limit = 8
|
||||
#jit = on # allow JIT compilation
|
||||
#join_collapse_limit = 8 # 1 disables collapsing of explicit
|
||||
# JOIN clauses
|
||||
#plan_cache_mode = auto # auto, force_generic_plan or
|
||||
# force_custom_plan
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# REPORTING AND LOGGING
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Where to Log -
|
||||
|
||||
#log_destination = 'stderr' # Valid values are combinations of
|
||||
# stderr, csvlog, syslog, and eventlog,
|
||||
# depending on platform. csvlog
|
||||
# requires logging_collector to be on.
|
||||
|
||||
# This is used when logging to stderr:
|
||||
#logging_collector = off # Enable capturing of stderr and csvlog
|
||||
# into log files. Required to be on for
|
||||
# csvlogs.
|
||||
# (change requires restart)
|
||||
|
||||
# These are only used if logging_collector is on:
|
||||
#log_directory = 'log' # directory where log files are written,
|
||||
# can be absolute or relative to PGDATA
|
||||
#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern,
|
||||
# can include strftime() escapes
|
||||
#log_file_mode = 0600 # creation mode for log files,
|
||||
# begin with 0 to use octal notation
|
||||
#log_rotation_age = 1d # Automatic rotation of logfiles will
|
||||
# happen after that time. 0 disables.
|
||||
#log_rotation_size = 10MB # Automatic rotation of logfiles will
|
||||
# happen after that much log output.
|
||||
# 0 disables.
|
||||
#log_truncate_on_rotation = off # If on, an existing log file with the
|
||||
# same name as the new log file will be
|
||||
# truncated rather than appended to.
|
||||
# But such truncation only occurs on
|
||||
# time-driven rotation, not on restarts
|
||||
# or size-driven rotation. Default is
|
||||
# off, meaning append to existing files
|
||||
# in all cases.
|
||||
|
||||
# These are relevant when logging to syslog:
|
||||
#syslog_facility = 'LOCAL0'
|
||||
#syslog_ident = 'postgres'
|
||||
#syslog_sequence_numbers = on
|
||||
#syslog_split_messages = on
|
||||
|
||||
# This is only relevant when logging to eventlog (Windows):
|
||||
# (change requires restart)
|
||||
#event_source = 'PostgreSQL'
|
||||
|
||||
# - When to Log -
|
||||
|
||||
#log_min_messages = warning # values in order of decreasing detail:
|
||||
# debug5
|
||||
# debug4
|
||||
# debug3
|
||||
# debug2
|
||||
# debug1
|
||||
# info
|
||||
# notice
|
||||
# warning
|
||||
# error
|
||||
# log
|
||||
# fatal
|
||||
# panic
|
||||
|
||||
#log_min_error_statement = error # values in order of decreasing detail:
|
||||
# debug5
|
||||
# debug4
|
||||
# debug3
|
||||
# debug2
|
||||
# debug1
|
||||
# info
|
||||
# notice
|
||||
# warning
|
||||
# error
|
||||
# log
|
||||
# fatal
|
||||
# panic (effectively off)
|
||||
|
||||
#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements
|
||||
# and their durations, > 0 logs only
|
||||
# statements running at least this number
|
||||
# of milliseconds
|
||||
|
||||
#log_min_duration_sample = -1 # -1 is disabled, 0 logs a sample of statements
|
||||
# and their durations, > 0 logs only a sample of
|
||||
# statements running at least this number
|
||||
# of milliseconds;
|
||||
# sample fraction is determined by log_statement_sample_rate
|
||||
|
||||
#log_statement_sample_rate = 1.0 # fraction of logged statements exceeding
|
||||
# log_min_duration_sample to be logged;
|
||||
# 1.0 logs all such statements, 0.0 never logs
|
||||
|
||||
|
||||
#log_transaction_sample_rate = 0.0 # fraction of transactions whose statements
|
||||
# are logged regardless of their duration; 1.0 logs all
|
||||
# statements from all transactions, 0.0 never logs
|
||||
|
||||
# - What to Log -
|
||||
|
||||
#debug_print_parse = off
|
||||
#debug_print_rewritten = off
|
||||
#debug_print_plan = off
|
||||
#debug_pretty_print = on
|
||||
#log_autovacuum_min_duration = -1 # log autovacuum activity;
|
||||
# -1 disables, 0 logs all actions and
|
||||
# their durations, > 0 logs only
|
||||
# actions running at least this number
|
||||
# of milliseconds.
|
||||
#log_checkpoints = off
|
||||
#log_connections = off
|
||||
#log_disconnections = off
|
||||
#log_duration = off
|
||||
#log_error_verbosity = default # terse, default, or verbose messages
|
||||
#log_hostname = off
|
||||
log_line_prefix = '%m [%p] %q%u@%d ' # special values:
|
||||
# %a = application name
|
||||
# %u = user name
|
||||
# %d = database name
|
||||
# %r = remote host and port
|
||||
# %h = remote host
|
||||
# %b = backend type
|
||||
# %p = process ID
|
||||
# %P = process ID of parallel group leader
|
||||
# %t = timestamp without milliseconds
|
||||
# %m = timestamp with milliseconds
|
||||
# %n = timestamp with milliseconds (as a Unix epoch)
|
||||
# %Q = query ID (0 if none or not computed)
|
||||
# %i = command tag
|
||||
# %e = SQL state
|
||||
# %c = session ID
|
||||
# %l = session line number
|
||||
# %s = session start timestamp
|
||||
# %v = virtual transaction ID
|
||||
# %x = transaction ID (0 if none)
|
||||
# %q = stop here in non-session
|
||||
# processes
|
||||
# %% = '%'
|
||||
# e.g. '<%u%%%d> '
|
||||
#log_lock_waits = off # log lock waits >= deadlock_timeout
|
||||
#log_recovery_conflict_waits = off # log standby recovery conflict waits
|
||||
# >= deadlock_timeout
|
||||
#log_parameter_max_length = -1 # when logging statements, limit logged
|
||||
# bind-parameter values to N bytes;
|
||||
# -1 means print in full, 0 disables
|
||||
#log_parameter_max_length_on_error = 0 # when logging an error, limit logged
|
||||
# bind-parameter values to N bytes;
|
||||
# -1 means print in full, 0 disables
|
||||
#log_statement = 'none' # none, ddl, mod, all
|
||||
#log_replication_commands = off
|
||||
#log_temp_files = -1 # log temporary files equal or larger
|
||||
# than the specified size in kilobytes;
|
||||
# -1 disables, 0 logs all temp files
|
||||
log_timezone = 'Etc/UTC'
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# PROCESS TITLE
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
cluster_name = '14/main' # added to process titles if nonempty
|
||||
# (change requires restart)
|
||||
#update_process_title = on
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# STATISTICS
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Query and Index Statistics Collector -
|
||||
|
||||
#track_activities = on
|
||||
#track_activity_query_size = 1024 # (change requires restart)
|
||||
#track_counts = on
|
||||
#track_io_timing = off
|
||||
#track_wal_io_timing = off
|
||||
#track_functions = none # none, pl, all
|
||||
stats_temp_directory = '/var/run/postgresql/14-main.pg_stat_tmp'
|
||||
|
||||
|
||||
# - Monitoring -
|
||||
|
||||
#compute_query_id = auto
|
||||
#log_statement_stats = off
|
||||
#log_parser_stats = off
|
||||
#log_planner_stats = off
|
||||
#log_executor_stats = off
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# AUTOVACUUM
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
#autovacuum = on # Enable autovacuum subprocess? 'on'
|
||||
# requires track_counts to also be on.
|
||||
#autovacuum_max_workers = 3 # max number of autovacuum subprocesses
|
||||
# (change requires restart)
|
||||
#autovacuum_naptime = 1min # time between autovacuum runs
|
||||
#autovacuum_vacuum_threshold = 50 # min number of row updates before
|
||||
# vacuum
|
||||
#autovacuum_vacuum_insert_threshold = 1000 # min number of row inserts
|
||||
# before vacuum; -1 disables insert
|
||||
# vacuums
|
||||
#autovacuum_analyze_threshold = 50 # min number of row updates before
|
||||
# analyze
|
||||
#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum
|
||||
#autovacuum_vacuum_insert_scale_factor = 0.2 # fraction of inserts over table
|
||||
# size before insert vacuum
|
||||
#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze
|
||||
#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum
|
||||
# (change requires restart)
|
||||
#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age
|
||||
# before forced vacuum
|
||||
# (change requires restart)
|
||||
#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for
|
||||
# autovacuum, in milliseconds;
|
||||
# -1 means use vacuum_cost_delay
|
||||
#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for
|
||||
# autovacuum, -1 means use
|
||||
# vacuum_cost_limit
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# CLIENT CONNECTION DEFAULTS
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Statement Behavior -
|
||||
|
||||
#client_min_messages = notice # values in order of decreasing detail:
|
||||
# debug5
|
||||
# debug4
|
||||
# debug3
|
||||
# debug2
|
||||
# debug1
|
||||
# log
|
||||
# notice
|
||||
# warning
|
||||
# error
|
||||
#search_path = '"$user", public' # schema names
|
||||
#row_security = on
|
||||
#default_table_access_method = 'heap'
|
||||
#default_tablespace = '' # a tablespace name, '' uses the default
|
||||
#default_toast_compression = 'pglz' # 'pglz' or 'lz4'
|
||||
#temp_tablespaces = '' # a list of tablespace names, '' uses
|
||||
# only default tablespace
|
||||
#check_function_bodies = on
|
||||
#default_transaction_isolation = 'read committed'
|
||||
#default_transaction_read_only = off
|
||||
#default_transaction_deferrable = off
|
||||
#session_replication_role = 'origin'
|
||||
#statement_timeout = 0 # in milliseconds, 0 is disabled
|
||||
#lock_timeout = 0 # in milliseconds, 0 is disabled
|
||||
#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled
|
||||
#idle_session_timeout = 0 # in milliseconds, 0 is disabled
|
||||
#vacuum_freeze_table_age = 150000000
|
||||
#vacuum_freeze_min_age = 50000000
|
||||
#vacuum_failsafe_age = 1600000000
|
||||
#vacuum_multixact_freeze_table_age = 150000000
|
||||
#vacuum_multixact_freeze_min_age = 5000000
|
||||
#vacuum_multixact_failsafe_age = 1600000000
|
||||
#bytea_output = 'hex' # hex, escape
|
||||
#xmlbinary = 'base64'
|
||||
#xmloption = 'content'
|
||||
#gin_pending_list_limit = 4MB
|
||||
|
||||
# - Locale and Formatting -
|
||||
|
||||
datestyle = 'iso, mdy'
|
||||
#intervalstyle = 'postgres'
|
||||
timezone = 'Etc/UTC'
|
||||
#timezone_abbreviations = 'Default' # Select the set of available time zone
|
||||
# abbreviations. Currently, there are
|
||||
# Default
|
||||
# Australia (historical usage)
|
||||
# India
|
||||
# You can create your own file in
|
||||
# share/timezonesets/.
|
||||
#extra_float_digits = 1 # min -15, max 3; any value >0 actually
|
||||
# selects precise output mode
|
||||
#client_encoding = sql_ascii # actually, defaults to database
|
||||
# encoding
|
||||
|
||||
# These settings are initialized by initdb, but they can be changed.
|
||||
lc_messages = 'C.UTF-8' # locale for system error message
|
||||
# strings
|
||||
lc_monetary = 'C.UTF-8' # locale for monetary formatting
|
||||
lc_numeric = 'C.UTF-8' # locale for number formatting
|
||||
lc_time = 'C.UTF-8' # locale for time formatting
|
||||
|
||||
# default configuration for text search
|
||||
default_text_search_config = 'pg_catalog.english'
|
||||
|
||||
# - Shared Library Preloading -
|
||||
|
||||
#local_preload_libraries = ''
|
||||
#session_preload_libraries = ''
|
||||
#shared_preload_libraries = '' # (change requires restart)
|
||||
#jit_provider = 'llvmjit' # JIT library to use
|
||||
|
||||
# - Other Defaults -
|
||||
|
||||
#dynamic_library_path = '$libdir'
|
||||
#extension_destdir = '' # prepend path when loading extensions
|
||||
# and shared objects (added by Debian)
|
||||
#gin_fuzzy_search_limit = 0
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# LOCK MANAGEMENT
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
#deadlock_timeout = 1s
|
||||
#max_locks_per_transaction = 64 # min 10
|
||||
# (change requires restart)
|
||||
#max_pred_locks_per_transaction = 64 # min 10
|
||||
# (change requires restart)
|
||||
#max_pred_locks_per_relation = -2 # negative values mean
|
||||
# (max_pred_locks_per_transaction
|
||||
# / -max_pred_locks_per_relation) - 1
|
||||
#max_pred_locks_per_page = 2 # min 0
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# VERSION AND PLATFORM COMPATIBILITY
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# - Previous PostgreSQL Versions -
|
||||
|
||||
#array_nulls = on
|
||||
#backslash_quote = safe_encoding # on, off, or safe_encoding
|
||||
#escape_string_warning = on
|
||||
#lo_compat_privileges = off
|
||||
#quote_all_identifiers = off
|
||||
#standard_conforming_strings = on
|
||||
#synchronize_seqscans = on
|
||||
|
||||
# - Other Platforms and Clients -
|
||||
|
||||
#transform_null_equals = off
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# ERROR HANDLING
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
#exit_on_error = off # terminate session on any error?
|
||||
#restart_after_crash = on # reinitialize after backend crash?
|
||||
#data_sync_retry = off # retry or panic on failure to fsync
|
||||
# data?
|
||||
# (change requires restart)
|
||||
#recovery_init_sync_method = fsync # fsync, syncfs (Linux 5.8+)
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# CONFIG FILE INCLUDES
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# These options allow settings to be loaded from files other than the
|
||||
# default postgresql.conf. Note that these are directives, not variable
|
||||
# assignments, so they can usefully be given more than once.
|
||||
|
||||
include_dir = 'conf.d' # include files ending in '.conf' from
|
||||
# a directory, e.g., 'conf.d'
|
||||
#include_if_exists = '...' # include file only if it exists
|
||||
#include = '...' # include file
|
||||
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# CUSTOMIZED OPTIONS
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# Add settings for extensions here
|
@@ -1,74 +0,0 @@
|
||||
use {log::*, std::collections::HashSet};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AccountsSelector {
|
||||
pub accounts: HashSet<Vec<u8>>,
|
||||
pub owners: HashSet<Vec<u8>>,
|
||||
pub select_all_accounts: bool,
|
||||
}
|
||||
|
||||
impl AccountsSelector {
|
||||
pub fn default() -> Self {
|
||||
AccountsSelector {
|
||||
accounts: HashSet::default(),
|
||||
owners: HashSet::default(),
|
||||
select_all_accounts: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(accounts: &[String], owners: &[String]) -> Self {
|
||||
info!(
|
||||
"Creating AccountsSelector from accounts: {:?}, owners: {:?}",
|
||||
accounts, owners
|
||||
);
|
||||
|
||||
let select_all_accounts = accounts.iter().any(|key| key == "*");
|
||||
if select_all_accounts {
|
||||
return AccountsSelector {
|
||||
accounts: HashSet::default(),
|
||||
owners: HashSet::default(),
|
||||
select_all_accounts,
|
||||
};
|
||||
}
|
||||
let accounts = accounts
|
||||
.iter()
|
||||
.map(|key| bs58::decode(key).into_vec().unwrap())
|
||||
.collect();
|
||||
let owners = owners
|
||||
.iter()
|
||||
.map(|key| bs58::decode(key).into_vec().unwrap())
|
||||
.collect();
|
||||
AccountsSelector {
|
||||
accounts,
|
||||
owners,
|
||||
select_all_accounts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_account_selected(&self, account: &[u8], owner: &[u8]) -> bool {
|
||||
self.select_all_accounts || self.accounts.contains(account) || self.owners.contains(owner)
|
||||
}
|
||||
|
||||
/// Check if any account is of interested at all
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.select_all_accounts || !self.accounts.is_empty() || !self.owners.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_create_accounts_selector() {
|
||||
AccountsSelector::new(
|
||||
&["9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin".to_string()],
|
||||
&[],
|
||||
);
|
||||
|
||||
AccountsSelector::new(
|
||||
&[],
|
||||
&["9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin".to_string()],
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,466 +0,0 @@
|
||||
use solana_measure::measure::Measure;
|
||||
/// Main entry for the PostgreSQL plugin
|
||||
use {
|
||||
crate::{
|
||||
accounts_selector::AccountsSelector,
|
||||
postgres_client::{ParallelPostgresClient, PostgresClientBuilder},
|
||||
transaction_selector::TransactionSelector,
|
||||
},
|
||||
bs58,
|
||||
log::*,
|
||||
serde_derive::{Deserialize, Serialize},
|
||||
serde_json,
|
||||
solana_accountsdb_plugin_interface::accountsdb_plugin_interface::{
|
||||
AccountsDbPlugin, AccountsDbPluginError, ReplicaAccountInfoVersions,
|
||||
ReplicaBlockInfoVersions, ReplicaTransactionInfoVersions, Result, SlotStatus,
|
||||
},
|
||||
solana_metrics::*,
|
||||
std::{fs::File, io::Read},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AccountsDbPluginPostgres {
|
||||
client: Option<ParallelPostgresClient>,
|
||||
accounts_selector: Option<AccountsSelector>,
|
||||
transaction_selector: Option<TransactionSelector>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AccountsDbPluginPostgres {
|
||||
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct AccountsDbPluginPostgresConfig {
|
||||
pub host: Option<String>,
|
||||
pub user: Option<String>,
|
||||
pub port: Option<u16>,
|
||||
pub connection_str: Option<String>,
|
||||
pub threads: Option<usize>,
|
||||
pub batch_size: Option<usize>,
|
||||
pub panic_on_db_errors: Option<bool>,
|
||||
/// Indicates if to store historical data for accounts
|
||||
pub store_account_historical_data: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AccountsDbPluginPostgresError {
|
||||
#[error("Error connecting to the backend data store. Error message: ({msg})")]
|
||||
DataStoreConnectionError { msg: String },
|
||||
|
||||
#[error("Error preparing data store schema. Error message: ({msg})")]
|
||||
DataSchemaError { msg: String },
|
||||
|
||||
#[error("Error preparing data store schema. Error message: ({msg})")]
|
||||
ConfigurationError { msg: String },
|
||||
}
|
||||
|
||||
impl AccountsDbPlugin for AccountsDbPluginPostgres {
|
||||
fn name(&self) -> &'static str {
|
||||
"AccountsDbPluginPostgres"
|
||||
}
|
||||
|
||||
/// Do initialization for the PostgreSQL plugin.
|
||||
///
|
||||
/// # Format of the config file:
|
||||
/// * The `accounts_selector` section allows the user to controls accounts selections.
|
||||
/// "accounts_selector" : {
|
||||
/// "accounts" : \["pubkey-1", "pubkey-2", ..., "pubkey-n"\],
|
||||
/// }
|
||||
/// or:
|
||||
/// "accounts_selector" = {
|
||||
/// "owners" : \["pubkey-1", "pubkey-2", ..., "pubkey-m"\]
|
||||
/// }
|
||||
/// Accounts either satisyfing the accounts condition or owners condition will be selected.
|
||||
/// When only owners is specified,
|
||||
/// all accounts belonging to the owners will be streamed.
|
||||
/// The accounts field supports wildcard to select all accounts:
|
||||
/// "accounts_selector" : {
|
||||
/// "accounts" : \["*"\],
|
||||
/// }
|
||||
/// * "host", optional, specifies the PostgreSQL server.
|
||||
/// * "user", optional, specifies the PostgreSQL user.
|
||||
/// * "port", optional, specifies the PostgreSQL server's port.
|
||||
/// * "connection_str", optional, the custom PostgreSQL connection string.
|
||||
/// Please refer to https://docs.rs/postgres/0.19.2/postgres/config/struct.Config.html for the connection configuration.
|
||||
/// When `connection_str` is set, the values in "host", "user" and "port" are ignored. If `connection_str` is not given,
|
||||
/// `host` and `user` must be given.
|
||||
/// "store_account_historical_data", optional, set it to 'true', to store historical account data to account_audit
|
||||
/// table.
|
||||
/// * "threads" optional, specifies the number of worker threads for the plugin. A thread
|
||||
/// maintains a PostgreSQL connection to the server. The default is '10'.
|
||||
/// * "batch_size" optional, specifies the batch size of bulk insert when the AccountsDb is created
|
||||
/// from restoring a snapshot. The default is '10'.
|
||||
/// * "panic_on_db_errors", optional, contols if to panic when there are errors replicating data to the
|
||||
/// PostgreSQL database. The default is 'false'.
|
||||
/// * "transaction_selector", optional, controls if and what transaction to store. If this field is missing
|
||||
/// None of the transction is stored.
|
||||
/// "transaction_selector" : {
|
||||
/// "mentions" : \["pubkey-1", "pubkey-2", ..., "pubkey-n"\],
|
||||
/// }
|
||||
/// The `mentions` field support wildcard to select all transaction or all 'vote' transactions:
|
||||
/// For example, to select all transactions:
|
||||
/// "transaction_selector" : {
|
||||
/// "mentions" : \["*"\],
|
||||
/// }
|
||||
/// To select all vote transactions:
|
||||
/// "transaction_selector" : {
|
||||
/// "mentions" : \["all_votes"\],
|
||||
/// }
|
||||
/// # Examples
|
||||
///
|
||||
/// {
|
||||
/// "libpath": "/home/solana/target/release/libsolana_accountsdb_plugin_postgres.so",
|
||||
/// "host": "host_foo",
|
||||
/// "user": "solana",
|
||||
/// "threads": 10,
|
||||
/// "accounts_selector" : {
|
||||
/// "owners" : ["9oT9R5ZyRovSVnt37QvVoBttGpNqR3J7unkb567NP8k3"]
|
||||
/// }
|
||||
/// }
|
||||
|
||||
fn on_load(&mut self, config_file: &str) -> Result<()> {
|
||||
solana_logger::setup_with_default("info");
|
||||
info!(
|
||||
"Loading plugin {:?} from config_file {:?}",
|
||||
self.name(),
|
||||
config_file
|
||||
);
|
||||
let mut file = File::open(config_file)?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
|
||||
let result: serde_json::Value = serde_json::from_str(&contents).unwrap();
|
||||
self.accounts_selector = Some(Self::create_accounts_selector_from_config(&result));
|
||||
self.transaction_selector = Some(Self::create_transaction_selector_from_config(&result));
|
||||
|
||||
let result: serde_json::Result<AccountsDbPluginPostgresConfig> =
|
||||
serde_json::from_str(&contents);
|
||||
match result {
|
||||
Err(err) => {
|
||||
return Err(AccountsDbPluginError::ConfigFileReadError {
|
||||
msg: format!(
|
||||
"The config file is not in the JSON format expected: {:?}",
|
||||
err
|
||||
),
|
||||
})
|
||||
}
|
||||
Ok(config) => {
|
||||
let client = PostgresClientBuilder::build_pararallel_postgres_client(&config)?;
|
||||
self.client = Some(client);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_unload(&mut self) {
|
||||
info!("Unloading plugin: {:?}", self.name());
|
||||
|
||||
match &mut self.client {
|
||||
None => {}
|
||||
Some(client) => {
|
||||
client.join().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_account(
|
||||
&mut self,
|
||||
account: ReplicaAccountInfoVersions,
|
||||
slot: u64,
|
||||
is_startup: bool,
|
||||
) -> Result<()> {
|
||||
let mut measure_all = Measure::start("accountsdb-plugin-postgres-update-account-main");
|
||||
match account {
|
||||
ReplicaAccountInfoVersions::V0_0_1(account) => {
|
||||
let mut measure_select =
|
||||
Measure::start("accountsdb-plugin-postgres-update-account-select");
|
||||
if let Some(accounts_selector) = &self.accounts_selector {
|
||||
if !accounts_selector.is_account_selected(account.pubkey, account.owner) {
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
measure_select.stop();
|
||||
inc_new_counter_debug!(
|
||||
"accountsdb-plugin-postgres-update-account-select-us",
|
||||
measure_select.as_us() as usize,
|
||||
100000,
|
||||
100000
|
||||
);
|
||||
|
||||
debug!(
|
||||
"Updating account {:?} with owner {:?} at slot {:?} using account selector {:?}",
|
||||
bs58::encode(account.pubkey).into_string(),
|
||||
bs58::encode(account.owner).into_string(),
|
||||
slot,
|
||||
self.accounts_selector.as_ref().unwrap()
|
||||
);
|
||||
|
||||
match &mut self.client {
|
||||
None => {
|
||||
return Err(AccountsDbPluginError::Custom(Box::new(
|
||||
AccountsDbPluginPostgresError::DataStoreConnectionError {
|
||||
msg: "There is no connection to the PostgreSQL database."
|
||||
.to_string(),
|
||||
},
|
||||
)));
|
||||
}
|
||||
Some(client) => {
|
||||
let mut measure_update =
|
||||
Measure::start("accountsdb-plugin-postgres-update-account-client");
|
||||
let result = { client.update_account(account, slot, is_startup) };
|
||||
measure_update.stop();
|
||||
|
||||
inc_new_counter_debug!(
|
||||
"accountsdb-plugin-postgres-update-account-client-us",
|
||||
measure_update.as_us() as usize,
|
||||
100000,
|
||||
100000
|
||||
);
|
||||
|
||||
if let Err(err) = result {
|
||||
return Err(AccountsDbPluginError::AccountsUpdateError {
|
||||
msg: format!("Failed to persist the update of account to the PostgreSQL database. Error: {:?}", err)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
measure_all.stop();
|
||||
|
||||
inc_new_counter_debug!(
|
||||
"accountsdb-plugin-postgres-update-account-main-us",
|
||||
measure_all.as_us() as usize,
|
||||
100000,
|
||||
100000
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_slot_status(
|
||||
&mut self,
|
||||
slot: u64,
|
||||
parent: Option<u64>,
|
||||
status: SlotStatus,
|
||||
) -> Result<()> {
|
||||
info!("Updating slot {:?} at with status {:?}", slot, status);
|
||||
|
||||
match &mut self.client {
|
||||
None => {
|
||||
return Err(AccountsDbPluginError::Custom(Box::new(
|
||||
AccountsDbPluginPostgresError::DataStoreConnectionError {
|
||||
msg: "There is no connection to the PostgreSQL database.".to_string(),
|
||||
},
|
||||
)));
|
||||
}
|
||||
Some(client) => {
|
||||
let result = client.update_slot_status(slot, parent, status);
|
||||
|
||||
if let Err(err) = result {
|
||||
return Err(AccountsDbPluginError::SlotStatusUpdateError{
|
||||
msg: format!("Failed to persist the update of slot to the PostgreSQL database. Error: {:?}", err)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notify_end_of_startup(&mut self) -> Result<()> {
|
||||
info!("Notifying the end of startup for accounts notifications");
|
||||
match &mut self.client {
|
||||
None => {
|
||||
return Err(AccountsDbPluginError::Custom(Box::new(
|
||||
AccountsDbPluginPostgresError::DataStoreConnectionError {
|
||||
msg: "There is no connection to the PostgreSQL database.".to_string(),
|
||||
},
|
||||
)));
|
||||
}
|
||||
Some(client) => {
|
||||
let result = client.notify_end_of_startup();
|
||||
|
||||
if let Err(err) = result {
|
||||
return Err(AccountsDbPluginError::SlotStatusUpdateError{
|
||||
msg: format!("Failed to notify the end of startup for accounts notifications. Error: {:?}", err)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notify_transaction(
|
||||
&mut self,
|
||||
transaction_info: ReplicaTransactionInfoVersions,
|
||||
slot: u64,
|
||||
) -> Result<()> {
|
||||
match &mut self.client {
|
||||
None => {
|
||||
return Err(AccountsDbPluginError::Custom(Box::new(
|
||||
AccountsDbPluginPostgresError::DataStoreConnectionError {
|
||||
msg: "There is no connection to the PostgreSQL database.".to_string(),
|
||||
},
|
||||
)));
|
||||
}
|
||||
Some(client) => match transaction_info {
|
||||
ReplicaTransactionInfoVersions::V0_0_1(transaction_info) => {
|
||||
if let Some(transaction_selector) = &self.transaction_selector {
|
||||
if !transaction_selector.is_transaction_selected(
|
||||
transaction_info.is_vote,
|
||||
transaction_info.transaction.message().account_keys_iter(),
|
||||
) {
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let result = client.log_transaction_info(transaction_info, slot);
|
||||
|
||||
if let Err(err) = result {
|
||||
return Err(AccountsDbPluginError::SlotStatusUpdateError{
|
||||
msg: format!("Failed to persist the transaction info to the PostgreSQL database. Error: {:?}", err)
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn notify_block_metadata(&mut self, block_info: ReplicaBlockInfoVersions) -> Result<()> {
|
||||
match &mut self.client {
|
||||
None => {
|
||||
return Err(AccountsDbPluginError::Custom(Box::new(
|
||||
AccountsDbPluginPostgresError::DataStoreConnectionError {
|
||||
msg: "There is no connection to the PostgreSQL database.".to_string(),
|
||||
},
|
||||
)));
|
||||
}
|
||||
Some(client) => match block_info {
|
||||
ReplicaBlockInfoVersions::V0_0_1(block_info) => {
|
||||
let result = client.update_block_metadata(block_info);
|
||||
|
||||
if let Err(err) = result {
|
||||
return Err(AccountsDbPluginError::SlotStatusUpdateError{
|
||||
msg: format!("Failed to persist the update of block metadata to the PostgreSQL database. Error: {:?}", err)
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if the plugin is interested in account data
|
||||
/// Default is true -- if the plugin is not interested in
|
||||
/// account data, please return false.
|
||||
fn account_data_notifications_enabled(&self) -> bool {
|
||||
self.accounts_selector
|
||||
.as_ref()
|
||||
.map_or_else(|| false, |selector| selector.is_enabled())
|
||||
}
|
||||
|
||||
/// Check if the plugin is interested in transaction data
|
||||
fn transaction_notifications_enabled(&self) -> bool {
|
||||
self.transaction_selector
|
||||
.as_ref()
|
||||
.map_or_else(|| false, |selector| selector.is_enabled())
|
||||
}
|
||||
}
|
||||
|
||||
impl AccountsDbPluginPostgres {
|
||||
fn create_accounts_selector_from_config(config: &serde_json::Value) -> AccountsSelector {
|
||||
let accounts_selector = &config["accounts_selector"];
|
||||
|
||||
if accounts_selector.is_null() {
|
||||
AccountsSelector::default()
|
||||
} else {
|
||||
let accounts = &accounts_selector["accounts"];
|
||||
let accounts: Vec<String> = if accounts.is_array() {
|
||||
accounts
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| val.as_str().unwrap().to_string())
|
||||
.collect()
|
||||
} else {
|
||||
Vec::default()
|
||||
};
|
||||
let owners = &accounts_selector["owners"];
|
||||
let owners: Vec<String> = if owners.is_array() {
|
||||
owners
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| val.as_str().unwrap().to_string())
|
||||
.collect()
|
||||
} else {
|
||||
Vec::default()
|
||||
};
|
||||
AccountsSelector::new(&accounts, &owners)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_transaction_selector_from_config(config: &serde_json::Value) -> TransactionSelector {
|
||||
let transaction_selector = &config["transaction_selector"];
|
||||
|
||||
if transaction_selector.is_null() {
|
||||
TransactionSelector::default()
|
||||
} else {
|
||||
let accounts = &transaction_selector["mentions"];
|
||||
let accounts: Vec<String> = if accounts.is_array() {
|
||||
accounts
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| val.as_str().unwrap().to_string())
|
||||
.collect()
|
||||
} else {
|
||||
Vec::default()
|
||||
};
|
||||
TransactionSelector::new(&accounts)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(improper_ctypes_definitions)]
|
||||
/// # Safety
|
||||
///
|
||||
/// This function returns the AccountsDbPluginPostgres pointer as trait AccountsDbPlugin.
|
||||
pub unsafe extern "C" fn _create_plugin() -> *mut dyn AccountsDbPlugin {
|
||||
let plugin = AccountsDbPluginPostgres::new();
|
||||
let plugin: Box<dyn AccountsDbPlugin> = Box::new(plugin);
|
||||
Box::into_raw(plugin)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use {super::*, serde_json};
|
||||
|
||||
#[test]
|
||||
fn test_accounts_selector_from_config() {
|
||||
let config = "{\"accounts_selector\" : { \
|
||||
\"owners\" : [\"9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin\"] \
|
||||
}}";
|
||||
|
||||
let config: serde_json::Value = serde_json::from_str(config).unwrap();
|
||||
AccountsDbPluginPostgres::create_accounts_selector_from_config(&config);
|
||||
}
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
pub mod accounts_selector;
|
||||
pub mod accountsdb_plugin_postgres;
|
||||
pub mod postgres_client;
|
||||
pub mod transaction_selector;
|
File diff suppressed because it is too large
Load Diff
@@ -1,97 +0,0 @@
|
||||
use {
|
||||
crate::{
|
||||
accountsdb_plugin_postgres::{
|
||||
AccountsDbPluginPostgresConfig, AccountsDbPluginPostgresError,
|
||||
},
|
||||
postgres_client::{
|
||||
postgres_client_transaction::DbReward, SimplePostgresClient, UpdateBlockMetadataRequest,
|
||||
},
|
||||
},
|
||||
chrono::Utc,
|
||||
log::*,
|
||||
postgres::{Client, Statement},
|
||||
solana_accountsdb_plugin_interface::accountsdb_plugin_interface::{
|
||||
AccountsDbPluginError, ReplicaBlockInfo,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DbBlockInfo {
|
||||
pub slot: i64,
|
||||
pub blockhash: String,
|
||||
pub rewards: Vec<DbReward>,
|
||||
pub block_time: Option<i64>,
|
||||
pub block_height: Option<i64>,
|
||||
}
|
||||
|
||||
impl<'a> From<&ReplicaBlockInfo<'a>> for DbBlockInfo {
|
||||
fn from(block_info: &ReplicaBlockInfo) -> Self {
|
||||
Self {
|
||||
slot: block_info.slot as i64,
|
||||
blockhash: block_info.blockhash.to_string(),
|
||||
rewards: block_info.rewards.iter().map(DbReward::from).collect(),
|
||||
block_time: block_info.block_time,
|
||||
block_height: block_info
|
||||
.block_height
|
||||
.map(|block_height| block_height as i64),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SimplePostgresClient {
|
||||
pub(crate) fn build_block_metadata_upsert_statement(
|
||||
client: &mut Client,
|
||||
config: &AccountsDbPluginPostgresConfig,
|
||||
) -> Result<Statement, AccountsDbPluginError> {
|
||||
let stmt =
|
||||
"INSERT INTO block (slot, blockhash, rewards, block_time, block_height, updated_on) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6)";
|
||||
|
||||
let stmt = client.prepare(stmt);
|
||||
|
||||
match stmt {
|
||||
Err(err) => {
|
||||
return Err(AccountsDbPluginError::Custom(Box::new(AccountsDbPluginPostgresError::DataSchemaError {
|
||||
msg: format!(
|
||||
"Error in preparing for the block metadata update PostgreSQL database: ({}) host: {:?} user: {:?} config: {:?}",
|
||||
err, config.host, config.user, config
|
||||
),
|
||||
})));
|
||||
}
|
||||
Ok(stmt) => Ok(stmt),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_block_metadata_impl(
|
||||
&mut self,
|
||||
block_info: UpdateBlockMetadataRequest,
|
||||
) -> Result<(), AccountsDbPluginError> {
|
||||
let client = self.client.get_mut().unwrap();
|
||||
let statement = &client.update_block_metadata_stmt;
|
||||
let client = &mut client.client;
|
||||
let updated_on = Utc::now().naive_utc();
|
||||
|
||||
let block_info = block_info.block_info;
|
||||
let result = client.query(
|
||||
statement,
|
||||
&[
|
||||
&block_info.slot,
|
||||
&block_info.blockhash,
|
||||
&block_info.rewards,
|
||||
&block_info.block_time,
|
||||
&block_info.block_height,
|
||||
&updated_on,
|
||||
],
|
||||
);
|
||||
|
||||
if let Err(err) = result {
|
||||
let msg = format!(
|
||||
"Failed to persist the update of block metadata to the PostgreSQL database. Error: {:?}",
|
||||
err);
|
||||
error!("{}", msg);
|
||||
return Err(AccountsDbPluginError::AccountsUpdateError { msg });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,194 +0,0 @@
|
||||
/// The transaction selector is responsible for filtering transactions
|
||||
/// in the plugin framework.
|
||||
use {log::*, solana_sdk::pubkey::Pubkey, std::collections::HashSet};
|
||||
|
||||
pub(crate) struct TransactionSelector {
|
||||
pub mentioned_addresses: HashSet<Vec<u8>>,
|
||||
pub select_all_transactions: bool,
|
||||
pub select_all_vote_transactions: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl TransactionSelector {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
mentioned_addresses: HashSet::default(),
|
||||
select_all_transactions: false,
|
||||
select_all_vote_transactions: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a selector based on the mentioned addresses
|
||||
/// To select all transactions use ["*"] or ["all"]
|
||||
/// To select all vote transactions, use ["all_votes"]
|
||||
/// To select transactions mentioning specific addresses use ["<pubkey1>", "<pubkey2>", ...]
|
||||
pub fn new(mentioned_addresses: &[String]) -> Self {
|
||||
info!(
|
||||
"Creating TransactionSelector from addresses: {:?}",
|
||||
mentioned_addresses
|
||||
);
|
||||
|
||||
let select_all_transactions = mentioned_addresses
|
||||
.iter()
|
||||
.any(|key| key == "*" || key == "all");
|
||||
if select_all_transactions {
|
||||
return Self {
|
||||
mentioned_addresses: HashSet::default(),
|
||||
select_all_transactions,
|
||||
select_all_vote_transactions: true,
|
||||
};
|
||||
}
|
||||
let select_all_vote_transactions = mentioned_addresses.iter().any(|key| key == "all_votes");
|
||||
if select_all_vote_transactions {
|
||||
return Self {
|
||||
mentioned_addresses: HashSet::default(),
|
||||
select_all_transactions,
|
||||
select_all_vote_transactions: true,
|
||||
};
|
||||
}
|
||||
|
||||
let mentioned_addresses = mentioned_addresses
|
||||
.iter()
|
||||
.map(|key| bs58::decode(key).into_vec().unwrap())
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
mentioned_addresses,
|
||||
select_all_transactions: false,
|
||||
select_all_vote_transactions: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a transaction is of interest.
|
||||
pub fn is_transaction_selected(
|
||||
&self,
|
||||
is_vote: bool,
|
||||
mentioned_addresses: Box<dyn Iterator<Item = &Pubkey> + '_>,
|
||||
) -> bool {
|
||||
if !self.is_enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.select_all_transactions || (self.select_all_vote_transactions && is_vote) {
|
||||
return true;
|
||||
}
|
||||
for address in mentioned_addresses {
|
||||
if self.mentioned_addresses.contains(address.as_ref()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if any transaction is of interest at all
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.select_all_transactions
|
||||
|| self.select_all_vote_transactions
|
||||
|| !self.mentioned_addresses.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_select_transaction() {
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
|
||||
let selector = TransactionSelector::new(&[pubkey1.to_string()]);
|
||||
|
||||
assert!(selector.is_enabled());
|
||||
|
||||
let addresses = [pubkey1];
|
||||
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey2];
|
||||
assert!(!selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey1, pubkey2];
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_all_transaction_using_wildcard() {
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
|
||||
let selector = TransactionSelector::new(&["*".to_string()]);
|
||||
|
||||
assert!(selector.is_enabled());
|
||||
|
||||
let addresses = [pubkey1];
|
||||
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey2];
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey1, pubkey2];
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_all_transaction_all() {
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
|
||||
let selector = TransactionSelector::new(&["all".to_string()]);
|
||||
|
||||
assert!(selector.is_enabled());
|
||||
|
||||
let addresses = [pubkey1];
|
||||
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey2];
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey1, pubkey2];
|
||||
assert!(selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_all_vote_transaction() {
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
|
||||
let selector = TransactionSelector::new(&["all_votes".to_string()]);
|
||||
|
||||
assert!(selector.is_enabled());
|
||||
|
||||
let addresses = [pubkey1];
|
||||
|
||||
assert!(!selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey2];
|
||||
assert!(selector.is_transaction_selected(true, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey1, pubkey2];
|
||||
assert!(selector.is_transaction_selected(true, Box::new(addresses.iter())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_no_transaction() {
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
|
||||
let selector = TransactionSelector::new(&[]);
|
||||
|
||||
assert!(!selector.is_enabled());
|
||||
|
||||
let addresses = [pubkey1];
|
||||
|
||||
assert!(!selector.is_transaction_selected(false, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey2];
|
||||
assert!(!selector.is_transaction_selected(true, Box::new(addresses.iter())));
|
||||
|
||||
let addresses = [pubkey1, pubkey2];
|
||||
assert!(!selector.is_transaction_selected(true, Box::new(addresses.iter())));
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2021"
|
||||
name = "solana-banking-bench"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -14,17 +14,17 @@ crossbeam-channel = "0.5"
|
||||
log = "0.4.14"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.5.1"
|
||||
solana-core = { path = "../core", version = "=1.10.0" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.10.0" }
|
||||
solana-ledger = { path = "../ledger", version = "=1.10.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.0" }
|
||||
solana-perf = { path = "../perf", version = "=1.10.0" }
|
||||
solana-poh = { path = "../poh", version = "=1.10.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.0" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-version = { path = "../version", version = "=1.10.0" }
|
||||
solana-core = { path = "../core", version = "=1.10.2" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.10.2" }
|
||||
solana-ledger = { path = "../ledger", version = "=1.10.2" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.2" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.2" }
|
||||
solana-perf = { path = "../perf", version = "=1.10.2" }
|
||||
solana-poh = { path = "../poh", version = "=1.10.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.10.2" }
|
||||
solana-version = { path = "../version", version = "=1.10.2" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -1,16 +1,17 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use {
|
||||
clap::{crate_description, crate_name, value_t, App, Arg},
|
||||
crossbeam_channel::unbounded,
|
||||
crossbeam_channel::{unbounded, Receiver},
|
||||
log::*,
|
||||
rand::{thread_rng, Rng},
|
||||
rayon::prelude::*,
|
||||
solana_core::{banking_stage::BankingStage, packet_deduper::PacketDeduper},
|
||||
solana_core::banking_stage::BankingStage,
|
||||
solana_gossip::cluster_info::{ClusterInfo, Node},
|
||||
solana_ledger::{
|
||||
blockstore::Blockstore,
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
get_tmp_ledger_path,
|
||||
leader_schedule_cache::LeaderScheduleCache,
|
||||
},
|
||||
solana_measure::measure::Measure,
|
||||
solana_perf::packet::to_packet_batches,
|
||||
@@ -28,7 +29,7 @@ use {
|
||||
},
|
||||
solana_streamer::socket::SocketAddrSpace,
|
||||
std::{
|
||||
sync::{atomic::Ordering, mpsc::Receiver, Arc, Mutex, RwLock},
|
||||
sync::{atomic::Ordering, Arc, Mutex, RwLock},
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
},
|
||||
@@ -174,6 +175,11 @@ fn main() {
|
||||
let mut bank_forks = BankForks::new(bank0);
|
||||
let mut bank = bank_forks.working_bank();
|
||||
|
||||
// set cost tracker limits to MAX so it will not filter out TXs
|
||||
bank.write_cost_tracker()
|
||||
.unwrap()
|
||||
.set_limits(std::u64::MAX, std::u64::MAX, std::u64::MAX);
|
||||
|
||||
info!("threads: {} txs: {}", num_threads, total_num_transactions);
|
||||
|
||||
let same_payer = matches.is_present("same_payer");
|
||||
@@ -218,15 +224,19 @@ fn main() {
|
||||
let blockstore = Arc::new(
|
||||
Blockstore::open(&ledger_path).expect("Expected to be able to open database ledger"),
|
||||
);
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, None);
|
||||
let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank));
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) = create_test_recorder(
|
||||
&bank,
|
||||
&blockstore,
|
||||
None,
|
||||
Some(leader_schedule_cache.clone()),
|
||||
);
|
||||
let cluster_info = ClusterInfo::new(
|
||||
Node::new_localhost().info,
|
||||
Arc::new(Keypair::new()),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let packet_deduper = PacketDeduper::default();
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
@@ -236,7 +246,6 @@ fn main() {
|
||||
None,
|
||||
replay_vote_sender,
|
||||
Arc::new(RwLock::new(CostModel::default())),
|
||||
packet_deduper.clone(),
|
||||
);
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
|
||||
@@ -331,9 +340,17 @@ fn main() {
|
||||
bank = bank_forks.working_bank();
|
||||
insert_time.stop();
|
||||
|
||||
// set cost tracker limits to MAX so it will not filter out TXs
|
||||
bank.write_cost_tracker().unwrap().set_limits(
|
||||
std::u64::MAX,
|
||||
std::u64::MAX,
|
||||
std::u64::MAX,
|
||||
);
|
||||
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
assert!(poh_recorder.lock().unwrap().bank().is_some());
|
||||
if bank.slot() > 32 {
|
||||
leader_schedule_cache.set_root(&bank);
|
||||
bank_forks.set_root(root, &AbsRequestSender::default(), None);
|
||||
root += 1;
|
||||
}
|
||||
@@ -351,7 +368,6 @@ fn main() {
|
||||
// in this chunk, but since we rotate between CHUNKS then
|
||||
// we should clear them by the time we come around again to re-use that chunk.
|
||||
bank.clear_signatures();
|
||||
packet_deduper.reset();
|
||||
total_us += duration_as_us(&now.elapsed());
|
||||
debug!(
|
||||
"time: {} us checked: {} sent: {}",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-banks-client"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
description = "Solana banks client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,19 +10,19 @@ documentation = "https://docs.rs/solana-banks-client"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
borsh = "0.9.1"
|
||||
borsh = "0.9.3"
|
||||
futures = "0.3"
|
||||
solana-banks-interface = { path = "../banks-interface", version = "=1.10.0" }
|
||||
solana-program = { path = "../sdk/program", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-banks-interface = { path = "../banks-interface", version = "=1.10.2" }
|
||||
solana-program = { path = "../sdk/program", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
tarpc = { version = "0.27.2", features = ["full"] }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-serde = { version = "0.8", features = ["bincode"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.0" }
|
||||
solana-banks-server = { path = "../banks-server", version = "=1.10.0" }
|
||||
solana-banks-server = { path = "../banks-server", version = "=1.10.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.2" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@@ -5,9 +5,11 @@
|
||||
//! but they are undocumented, may change over time, and are generally more
|
||||
//! cumbersome to use.
|
||||
|
||||
pub use solana_banks_interface::{BanksClient as TarpcClient, TransactionStatus};
|
||||
use {
|
||||
pub use {
|
||||
crate::error::BanksClientError,
|
||||
solana_banks_interface::{BanksClient as TarpcClient, TransactionStatus},
|
||||
};
|
||||
use {
|
||||
borsh::BorshDeserialize,
|
||||
futures::{future::join_all, Future, FutureExt, TryFutureExt},
|
||||
solana_banks_interface::{BanksRequest, BanksResponse, BanksTransactionResultWithSimulation},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-banks-interface"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
description = "Solana banks RPC interface"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,8 +10,8 @@ documentation = "https://docs.rs/solana-banks-interface"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.133", features = ["derive"] }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
tarpc = { version = "0.27.2", features = ["full"] }
|
||||
|
||||
[lib]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-banks-server"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
description = "Solana banks server"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -11,11 +11,12 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.3"
|
||||
crossbeam-channel = "0.5"
|
||||
futures = "0.3"
|
||||
solana-banks-interface = { path = "../banks-interface", version = "=1.10.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-send-transaction-service = { path = "../send-transaction-service", version = "=1.10.0" }
|
||||
solana-banks-interface = { path = "../banks-interface", version = "=1.10.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
solana-send-transaction-service = { path = "../send-transaction-service", version = "=1.10.2" }
|
||||
tarpc = { version = "0.27.2", features = ["full"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-serde = { version = "0.8", features = ["bincode"] }
|
||||
|
@@ -1,5 +1,6 @@
|
||||
use {
|
||||
bincode::{deserialize, serialize},
|
||||
crossbeam_channel::{unbounded, Receiver, Sender},
|
||||
futures::{future, prelude::stream::StreamExt},
|
||||
solana_banks_interface::{
|
||||
Banks, BanksRequest, BanksResponse, BanksTransactionResultWithSimulation,
|
||||
@@ -30,10 +31,7 @@ use {
|
||||
convert::TryFrom,
|
||||
io,
|
||||
net::{Ipv4Addr, SocketAddr},
|
||||
sync::{
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
Arc, RwLock,
|
||||
},
|
||||
sync::{Arc, RwLock},
|
||||
thread::Builder,
|
||||
time::Duration,
|
||||
},
|
||||
@@ -96,7 +94,7 @@ impl BanksServer {
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
poll_signature_status_sleep_duration: Duration,
|
||||
) -> Self {
|
||||
let (transaction_sender, transaction_receiver) = channel();
|
||||
let (transaction_sender, transaction_receiver) = unbounded();
|
||||
let bank = bank_forks.read().unwrap().working_bank();
|
||||
let slot = bank.slot();
|
||||
{
|
||||
@@ -392,7 +390,7 @@ pub async fn start_tcp_server(
|
||||
// serve is generated by the service attribute. It takes as input any type implementing
|
||||
// the generated Banks trait.
|
||||
.map(move |chan| {
|
||||
let (sender, receiver) = channel();
|
||||
let (sender, receiver) = unbounded();
|
||||
|
||||
SendTransactionService::new::<NullTpuInfo>(
|
||||
tpu_addr,
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2021"
|
||||
name = "solana-bench-streamer"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -10,9 +10,10 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
solana-streamer = { path = "../streamer", version = "=1.10.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.10.0" }
|
||||
solana-version = { path = "../version", version = "=1.10.0" }
|
||||
crossbeam-channel = "0.5"
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.10.2" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.10.2" }
|
||||
solana-version = { path = "../version", version = "=1.10.2" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use {
|
||||
clap::{crate_description, crate_name, App, Arg},
|
||||
clap::{crate_description, crate_name, value_t, App, Arg},
|
||||
crossbeam_channel::unbounded,
|
||||
solana_streamer::{
|
||||
packet::{Packet, PacketBatch, PacketBatchRecycler, PACKET_DATA_SIZE},
|
||||
streamer::{receiver, PacketBatchReceiver},
|
||||
@@ -10,7 +11,6 @@ use {
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
mpsc::channel,
|
||||
Arc,
|
||||
},
|
||||
thread::{sleep, spawn, JoinHandle, Result},
|
||||
@@ -67,13 +67,22 @@ fn main() -> Result<()> {
|
||||
.takes_value(true)
|
||||
.help("Use NUM receive sockets"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num-producers")
|
||||
.long("num-producers")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("Use this many producer threads."),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if let Some(n) = matches.value_of("num-recv-sockets") {
|
||||
num_sockets = max(num_sockets, n.to_string().parse().expect("integer"));
|
||||
}
|
||||
|
||||
let mut port = 0;
|
||||
let num_producers = value_t!(matches, "num_producers", u64).unwrap_or(4);
|
||||
|
||||
let port = 0;
|
||||
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
||||
let mut addr = SocketAddr::new(ip_addr, 0);
|
||||
|
||||
@@ -82,14 +91,17 @@ fn main() -> Result<()> {
|
||||
let mut read_channels = Vec::new();
|
||||
let mut read_threads = Vec::new();
|
||||
let recycler = PacketBatchRecycler::default();
|
||||
for _ in 0..num_sockets {
|
||||
let read = solana_net_utils::bind_to(ip_addr, port, false).unwrap();
|
||||
let (_port, read_sockets) = solana_net_utils::multi_bind_in_range(
|
||||
ip_addr,
|
||||
(port, port + num_sockets as u16),
|
||||
num_sockets,
|
||||
)
|
||||
.unwrap();
|
||||
for read in read_sockets {
|
||||
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
|
||||
|
||||
addr = read.local_addr().unwrap();
|
||||
port = addr.port();
|
||||
|
||||
let (s_reader, r_reader) = channel();
|
||||
let (s_reader, r_reader) = unbounded();
|
||||
read_channels.push(r_reader);
|
||||
read_threads.push(receiver(
|
||||
Arc::new(read),
|
||||
@@ -102,9 +114,10 @@ fn main() -> Result<()> {
|
||||
));
|
||||
}
|
||||
|
||||
let t_producer1 = producer(&addr, exit.clone());
|
||||
let t_producer2 = producer(&addr, exit.clone());
|
||||
let t_producer3 = producer(&addr, exit.clone());
|
||||
let producer_threads: Vec<_> = (0..num_producers)
|
||||
.into_iter()
|
||||
.map(|_| producer(&addr, exit.clone()))
|
||||
.collect();
|
||||
|
||||
let rvs = Arc::new(AtomicUsize::new(0));
|
||||
let sink_threads: Vec<_> = read_channels
|
||||
@@ -124,9 +137,9 @@ fn main() -> Result<()> {
|
||||
for t_reader in read_threads {
|
||||
t_reader.join()?;
|
||||
}
|
||||
t_producer1.join()?;
|
||||
t_producer2.join()?;
|
||||
t_producer3.join()?;
|
||||
for t_producer in producer_threads {
|
||||
t_producer.join()?;
|
||||
}
|
||||
for t_sink in sink_threads {
|
||||
t_sink.join()?;
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2021"
|
||||
name = "solana-bench-tps"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -10,27 +10,28 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
crossbeam-channel = "0.5"
|
||||
log = "0.4.14"
|
||||
rayon = "1.5.1"
|
||||
serde_json = "1.0.74"
|
||||
serde_json = "1.0.79"
|
||||
serde_yaml = "0.8.23"
|
||||
solana-core = { path = "../core", version = "=1.10.0" }
|
||||
solana-genesis = { path = "../genesis", version = "=1.10.0" }
|
||||
solana-client = { path = "../client", version = "=1.10.0" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.10.0" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.10.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.0" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.10.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.10.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.10.0" }
|
||||
solana-version = { path = "../version", version = "=1.10.0" }
|
||||
solana-client = { path = "../client", version = "=1.10.2" }
|
||||
solana-core = { path = "../core", version = "=1.10.2" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.10.2" }
|
||||
solana-genesis = { path = "../genesis", version = "=1.10.2" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.10.2" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.2" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.2" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.10.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.10.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.10.2" }
|
||||
solana-version = { path = "../version", version = "=1.10.2" }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.5.1"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.10.0" }
|
||||
serial_test = "0.6.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.10.2" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -110,7 +110,7 @@ fn generate_chunked_transfers(
|
||||
shared_txs: &SharedTransactions,
|
||||
shared_tx_active_thread_count: Arc<AtomicIsize>,
|
||||
source_keypair_chunks: Vec<Vec<&Keypair>>,
|
||||
dest_keypair_chunks: &mut Vec<VecDeque<&Keypair>>,
|
||||
dest_keypair_chunks: &mut [VecDeque<&Keypair>],
|
||||
threads: usize,
|
||||
duration: Duration,
|
||||
sustained: bool,
|
||||
@@ -475,6 +475,7 @@ fn do_tx_transfers<T: Client>(
|
||||
let tx_len = txs0.len();
|
||||
let transfer_start = Instant::now();
|
||||
let mut old_transactions = false;
|
||||
let mut transactions = Vec::<_>::new();
|
||||
for tx in txs0 {
|
||||
let now = timestamp();
|
||||
// Transactions that are too old will be rejected by the cluster Don't bother
|
||||
@@ -483,10 +484,13 @@ fn do_tx_transfers<T: Client>(
|
||||
old_transactions = true;
|
||||
continue;
|
||||
}
|
||||
client
|
||||
.async_send_transaction(tx.0)
|
||||
.expect("async_send_transaction in do_tx_transfers");
|
||||
transactions.push(tx.0);
|
||||
}
|
||||
|
||||
if let Err(error) = client.async_send_batch(transactions) {
|
||||
warn!("send_batch_sync in do_tx_transfers failed: {}", error);
|
||||
}
|
||||
|
||||
if old_transactions {
|
||||
let mut shared_txs_wl = shared_txs.write().expect("write lock in do_tx_transfers");
|
||||
shared_txs_wl.clear();
|
||||
|
@@ -21,7 +21,7 @@ pub const NUM_SIGNATURES_FOR_TXS: u64 = 100_000 * 60 * 60 * 24 * 7;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup_with_default("solana=info");
|
||||
solana_metrics::set_panic_hook("bench-tps");
|
||||
solana_metrics::set_panic_hook("bench-tps", /*version:*/ None);
|
||||
|
||||
let matches = cli::build_args(solana_version::version!()).get_matches();
|
||||
let cli_config = cli::extract_args(&matches);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use {
|
||||
crossbeam_channel::unbounded,
|
||||
serial_test::serial,
|
||||
solana_bench_tps::{
|
||||
bench::{do_bench_tps, generate_and_fund_keypairs},
|
||||
@@ -15,10 +16,7 @@ use {
|
||||
},
|
||||
solana_sdk::signature::{Keypair, Signer},
|
||||
solana_streamer::socket::SocketAddrSpace,
|
||||
std::{
|
||||
sync::{mpsc::channel, Arc},
|
||||
time::Duration,
|
||||
},
|
||||
std::{sync::Arc, time::Duration},
|
||||
};
|
||||
|
||||
fn test_bench_tps_local_cluster(config: Config) {
|
||||
@@ -31,7 +29,7 @@ fn test_bench_tps_local_cluster(config: Config) {
|
||||
node_stakes: vec![999_990; NUM_NODES],
|
||||
cluster_lamports: 200_000_000,
|
||||
validator_configs: make_identical_validator_configs(
|
||||
&ValidatorConfig::default(),
|
||||
&ValidatorConfig::default_for_test(),
|
||||
NUM_NODES,
|
||||
),
|
||||
native_instruction_processors,
|
||||
@@ -52,7 +50,7 @@ fn test_bench_tps_local_cluster(config: Config) {
|
||||
VALIDATOR_PORT_RANGE,
|
||||
));
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
let (addr_sender, addr_receiver) = unbounded();
|
||||
run_local_faucet_with_port(faucet_keypair, addr_sender, None, 0);
|
||||
let faucet_addr = addr_receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
|
32
bloom/Cargo.toml
Normal file
32
bloom/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "solana-bloom"
|
||||
version = "1.10.2"
|
||||
description = "Solana bloom filter"
|
||||
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-bloom"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bv = { version = "0.11.1", features = ["serde"] }
|
||||
fnv = "1.0.7"
|
||||
log = "0.4.14"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.5.1"
|
||||
serde = { version = "1.0.136", features = ["rc"] }
|
||||
serde_derive = "1.0.103"
|
||||
solana-frozen-abi = { path = "../frozen-abi", version = "=1.10.2" }
|
||||
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
name = "solana_bloom"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.4"
|
@@ -5,7 +5,7 @@ use {
|
||||
bv::BitVec,
|
||||
fnv::FnvHasher,
|
||||
rand::Rng,
|
||||
solana_runtime::bloom::{AtomicBloom, Bloom, BloomHashIndex},
|
||||
solana_bloom::bloom::{AtomicBloom, Bloom, BloomHashIndex},
|
||||
solana_sdk::{
|
||||
hash::{hash, Hash},
|
||||
signature::Signature,
|
1
bloom/build.rs
Symbolic link
1
bloom/build.rs
Symbolic link
@@ -0,0 +1 @@
|
||||
../frozen-abi/build.rs
|
@@ -101,7 +101,7 @@ impl<T: BloomHashIndex> Bloom<T> {
|
||||
}
|
||||
}
|
||||
fn pos(&self, key: &T, k: u64) -> u64 {
|
||||
key.hash_at_index(k) % self.bits.len()
|
||||
key.hash_at_index(k).wrapping_rem(self.bits.len())
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
self.bits = BitVec::new_fill(false, self.bits.len());
|
||||
@@ -111,7 +111,7 @@ impl<T: BloomHashIndex> Bloom<T> {
|
||||
for k in &self.keys {
|
||||
let pos = self.pos(key, *k);
|
||||
if !self.bits.get(pos) {
|
||||
self.num_bits_set += 1;
|
||||
self.num_bits_set = self.num_bits_set.saturating_add(1);
|
||||
self.bits.set(pos, true);
|
||||
}
|
||||
}
|
||||
@@ -164,21 +164,26 @@ impl<T: BloomHashIndex> From<Bloom<T>> for AtomicBloom<T> {
|
||||
|
||||
impl<T: BloomHashIndex> AtomicBloom<T> {
|
||||
fn pos(&self, key: &T, hash_index: u64) -> (usize, u64) {
|
||||
let pos = key.hash_at_index(hash_index) % self.num_bits;
|
||||
let pos = key.hash_at_index(hash_index).wrapping_rem(self.num_bits);
|
||||
// Divide by 64 to figure out which of the
|
||||
// AtomicU64 bit chunks we need to modify.
|
||||
let index = pos >> 6;
|
||||
let index = pos.wrapping_shr(6);
|
||||
// (pos & 63) is equivalent to mod 64 so that we can find
|
||||
// the index of the bit within the AtomicU64 to modify.
|
||||
let mask = 1u64 << (pos & 63);
|
||||
let mask = 1u64.wrapping_shl(u32::try_from(pos & 63).unwrap());
|
||||
(index as usize, mask)
|
||||
}
|
||||
|
||||
pub fn add(&self, key: &T) {
|
||||
/// Adds an item to the bloom filter and returns true if the item
|
||||
/// was not in the filter before.
|
||||
pub fn add(&self, key: &T) -> bool {
|
||||
let mut added = false;
|
||||
for k in &self.keys {
|
||||
let (index, mask) = self.pos(key, *k);
|
||||
self.bits[index].fetch_or(mask, Ordering::Relaxed);
|
||||
let prev_val = self.bits[index].fetch_or(mask, Ordering::Relaxed);
|
||||
added = added || prev_val & mask == 0u64;
|
||||
}
|
||||
added
|
||||
}
|
||||
|
||||
pub fn contains(&self, key: &T) -> bool {
|
||||
@@ -189,6 +194,12 @@ impl<T: BloomHashIndex> AtomicBloom<T> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn clear_for_tests(&mut self) {
|
||||
self.bits.iter().for_each(|bit| {
|
||||
bit.store(0u64, Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
|
||||
// Only for tests and simulations.
|
||||
pub fn mock_clone(&self) -> Self {
|
||||
Self {
|
||||
@@ -320,7 +331,9 @@ mod test {
|
||||
assert_eq!(bloom.keys.len(), 3);
|
||||
assert_eq!(bloom.num_bits, 6168);
|
||||
assert_eq!(bloom.bits.len(), 97);
|
||||
hash_values.par_iter().for_each(|v| bloom.add(v));
|
||||
hash_values.par_iter().for_each(|v| {
|
||||
bloom.add(v);
|
||||
});
|
||||
let bloom: Bloom<Hash> = bloom.into();
|
||||
assert_eq!(bloom.keys.len(), 3);
|
||||
assert_eq!(bloom.bits.len(), 6168);
|
||||
@@ -362,7 +375,9 @@ mod test {
|
||||
}
|
||||
// Round trip, re-inserting the same hash values.
|
||||
let bloom: AtomicBloom<_> = bloom.into();
|
||||
hash_values.par_iter().for_each(|v| bloom.add(v));
|
||||
hash_values.par_iter().for_each(|v| {
|
||||
bloom.add(v);
|
||||
});
|
||||
for hash_value in &hash_values {
|
||||
assert!(bloom.contains(hash_value));
|
||||
}
|
||||
@@ -380,7 +395,9 @@ mod test {
|
||||
let bloom: AtomicBloom<_> = bloom.into();
|
||||
assert_eq!(bloom.num_bits, 9731);
|
||||
assert_eq!(bloom.bits.len(), (9731 + 63) / 64);
|
||||
more_hash_values.par_iter().for_each(|v| bloom.add(v));
|
||||
more_hash_values.par_iter().for_each(|v| {
|
||||
bloom.add(v);
|
||||
});
|
||||
for hash_value in &hash_values {
|
||||
assert!(bloom.contains(hash_value));
|
||||
}
|
5
bloom/src/lib.rs
Normal file
5
bloom/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))]
|
||||
pub mod bloom;
|
||||
|
||||
#[macro_use]
|
||||
extern crate solana_frozen_abi_macro;
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-bucket-map"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
description = "solana-bucket-map"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-bucket-map"
|
||||
@@ -11,17 +11,18 @@ license = "Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
memmap2 = "0.5.0"
|
||||
log = { version = "0.4.11" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.0" }
|
||||
memmap2 = "0.5.3"
|
||||
modular-bitfield = "0.11.2"
|
||||
rand = "0.7.0"
|
||||
tempfile = "3.2.0"
|
||||
solana-measure = { path = "../measure", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
tempfile = "3.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
fs_extra = "1.2.0"
|
||||
rayon = "1.5.0"
|
||||
solana-logger = { path = "../logger", version = "=1.10.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.2" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@@ -1,9 +1,11 @@
|
||||
#![allow(dead_code)]
|
||||
use {
|
||||
crate::{
|
||||
bucket::Bucket,
|
||||
bucket_storage::{BucketStorage, Uid},
|
||||
RefCount,
|
||||
},
|
||||
modular_bitfield::prelude::*,
|
||||
solana_sdk::{clock::Slot, pubkey::Pubkey},
|
||||
std::{
|
||||
collections::hash_map::DefaultHasher,
|
||||
@@ -19,18 +21,25 @@ use {
|
||||
pub struct IndexEntry {
|
||||
pub key: Pubkey, // can this be smaller if we have reduced the keys into buckets already?
|
||||
pub ref_count: RefCount, // can this be smaller? Do we ever need more than 4B refcounts?
|
||||
storage_offset: u64, // smaller? since these are variably sized, this could get tricky. well, actually accountinfo is not variable sized...
|
||||
storage_cap_and_offset: PackedStorage,
|
||||
// if the bucket doubled, the index can be recomputed using create_bucket_capacity_pow2
|
||||
storage_capacity_when_created_pow2: u8, // see data_location
|
||||
pub num_slots: Slot, // can this be smaller? epoch size should ~ be the max len. this is the num elements in the slot list
|
||||
}
|
||||
|
||||
/// Pack the storage offset and capacity-when-crated-pow2 fields into a single u64
|
||||
#[bitfield(bits = 64)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
struct PackedStorage {
|
||||
capacity_when_created_pow2: B8,
|
||||
offset: B56,
|
||||
}
|
||||
|
||||
impl IndexEntry {
|
||||
pub fn init(&mut self, pubkey: &Pubkey) {
|
||||
self.key = *pubkey;
|
||||
self.ref_count = 0;
|
||||
self.storage_offset = 0;
|
||||
self.storage_capacity_when_created_pow2 = 0;
|
||||
self.storage_cap_and_offset = PackedStorage::default();
|
||||
self.num_slots = 0;
|
||||
}
|
||||
|
||||
@@ -38,11 +47,14 @@ impl IndexEntry {
|
||||
&mut self,
|
||||
storage_capacity_when_created_pow2: u8,
|
||||
) {
|
||||
self.storage_capacity_when_created_pow2 = storage_capacity_when_created_pow2;
|
||||
self.storage_cap_and_offset
|
||||
.set_capacity_when_created_pow2(storage_capacity_when_created_pow2)
|
||||
}
|
||||
|
||||
pub fn set_storage_offset(&mut self, storage_offset: u64) {
|
||||
self.storage_offset = storage_offset;
|
||||
self.storage_cap_and_offset
|
||||
.set_offset_checked(storage_offset)
|
||||
.expect("New storage offset must fit into 7 bytes!")
|
||||
}
|
||||
|
||||
pub fn data_bucket_from_num_slots(num_slots: Slot) -> u64 {
|
||||
@@ -57,10 +69,18 @@ impl IndexEntry {
|
||||
self.ref_count
|
||||
}
|
||||
|
||||
fn storage_capacity_when_created_pow2(&self) -> u8 {
|
||||
self.storage_cap_and_offset.capacity_when_created_pow2()
|
||||
}
|
||||
|
||||
fn storage_offset(&self) -> u64 {
|
||||
self.storage_cap_and_offset.offset()
|
||||
}
|
||||
|
||||
// This function maps the original data location into an index in the current bucket storage.
|
||||
// This is coupled with how we resize bucket storages.
|
||||
pub fn data_loc(&self, storage: &BucketStorage) -> u64 {
|
||||
self.storage_offset << (storage.capacity_pow2 - self.storage_capacity_when_created_pow2)
|
||||
self.storage_offset() << (storage.capacity_pow2 - self.storage_capacity_when_created_pow2())
|
||||
}
|
||||
|
||||
pub fn read_value<'a, T>(&self, bucket: &'a Bucket<T>) -> Option<(&'a [T], RefCount)> {
|
||||
@@ -78,9 +98,59 @@ impl IndexEntry {
|
||||
};
|
||||
Some((slice, self.ref_count))
|
||||
}
|
||||
|
||||
pub fn key_uid(key: &Pubkey) -> Uid {
|
||||
let mut s = DefaultHasher::new();
|
||||
key.hash(&mut s);
|
||||
s.finish().max(1u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
impl IndexEntry {
|
||||
pub fn new(key: Pubkey) -> Self {
|
||||
IndexEntry {
|
||||
key,
|
||||
ref_count: 0,
|
||||
storage_cap_and_offset: PackedStorage::default(),
|
||||
num_slots: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// verify that accessors for storage_offset and capacity_when_created are
|
||||
/// correct and independent
|
||||
#[test]
|
||||
fn test_api() {
|
||||
for offset in [0, 1, u32::MAX as u64] {
|
||||
let mut index = IndexEntry::new(solana_sdk::pubkey::new_rand());
|
||||
if offset != 0 {
|
||||
index.set_storage_offset(offset);
|
||||
}
|
||||
assert_eq!(index.storage_offset(), offset);
|
||||
assert_eq!(index.storage_capacity_when_created_pow2(), 0);
|
||||
for pow in [1, 255, 0] {
|
||||
index.set_storage_capacity_when_created_pow2(pow);
|
||||
assert_eq!(index.storage_offset(), offset);
|
||||
assert_eq!(index.storage_capacity_when_created_pow2(), pow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_size() {
|
||||
assert_eq!(std::mem::size_of::<PackedStorage>(), 1 + 7);
|
||||
assert_eq!(std::mem::size_of::<IndexEntry>(), 32 + 8 + 8 + 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "New storage offset must fit into 7 bytes!")]
|
||||
fn test_set_storage_offset_value_too_large() {
|
||||
let too_big = 1 << 56;
|
||||
let mut index = IndexEntry::new(Pubkey::new_unique());
|
||||
index.set_storage_offset(too_big);
|
||||
}
|
||||
}
|
||||
|
365
ci/buildkite-pipeline-in-disk.sh
Normal file
365
ci/buildkite-pipeline-in-disk.sh
Normal file
@@ -0,0 +1,365 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Builds a buildkite pipeline based on the environment variables
|
||||
#
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
output_file=${1:-/dev/stderr}
|
||||
|
||||
if [[ -n $CI_PULL_REQUEST ]]; then
|
||||
IFS=':' read -ra affected_files <<< "$(buildkite-agent meta-data get affected_files)"
|
||||
if [[ ${#affected_files[*]} -eq 0 ]]; then
|
||||
echo "Unable to determine the files affected by this PR"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
affected_files=()
|
||||
fi
|
||||
|
||||
annotate() {
|
||||
if [[ -n $BUILDKITE ]]; then
|
||||
buildkite-agent annotate "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Checks if a CI pull request affects one or more path patterns. Each
|
||||
# pattern argument is checked in series. If one of them found to be affected,
|
||||
# return immediately as such.
|
||||
#
|
||||
# Bash regular expressions are permitted in the pattern:
|
||||
# affects .rs$ -- any file or directory ending in .rs
|
||||
# affects .rs -- also matches foo.rs.bar
|
||||
# affects ^snap/ -- anything under the snap/ subdirectory
|
||||
# affects snap/ -- also matches foo/snap/
|
||||
# Any pattern starting with the ! character will be negated:
|
||||
# affects !^docs/ -- anything *not* under the docs/ subdirectory
|
||||
#
|
||||
affects() {
|
||||
if [[ -z $CI_PULL_REQUEST ]]; then
|
||||
# affected_files metadata is not currently available for non-PR builds so assume
|
||||
# the worse (affected)
|
||||
return 0
|
||||
fi
|
||||
# Assume everyting needs to be tested when any Dockerfile changes
|
||||
for pattern in ^ci/docker-rust/Dockerfile ^ci/docker-rust-nightly/Dockerfile "$@"; do
|
||||
if [[ ${pattern:0:1} = "!" ]]; then
|
||||
for file in "${affected_files[@]}"; do
|
||||
if [[ ! $file =~ ${pattern:1} ]]; then
|
||||
return 0 # affected
|
||||
fi
|
||||
done
|
||||
else
|
||||
for file in "${affected_files[@]}"; do
|
||||
if [[ $file =~ $pattern ]]; then
|
||||
return 0 # affected
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
return 1 # not affected
|
||||
}
|
||||
|
||||
|
||||
# Checks if a CI pull request affects anything other than the provided path patterns
|
||||
#
|
||||
# Syntax is the same as `affects()` except that the negation prefix is not
|
||||
# supported
|
||||
#
|
||||
affects_other_than() {
|
||||
if [[ -z $CI_PULL_REQUEST ]]; then
|
||||
# affected_files metadata is not currently available for non-PR builds so assume
|
||||
# the worse (affected)
|
||||
return 0
|
||||
fi
|
||||
|
||||
for file in "${affected_files[@]}"; do
|
||||
declare matched=false
|
||||
for pattern in "$@"; do
|
||||
if [[ $file =~ $pattern ]]; then
|
||||
matched=true
|
||||
fi
|
||||
done
|
||||
if ! $matched; then
|
||||
return 0 # affected
|
||||
fi
|
||||
done
|
||||
|
||||
return 1 # not affected
|
||||
}
|
||||
|
||||
|
||||
start_pipeline() {
|
||||
echo "# $*" > "$output_file"
|
||||
echo "steps:" >> "$output_file"
|
||||
}
|
||||
|
||||
command_step() {
|
||||
cat >> "$output_file" <<EOF
|
||||
- name: "$1"
|
||||
command: "$2"
|
||||
timeout_in_minutes: $3
|
||||
artifact_paths: "log-*.txt"
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
trigger_secondary_step() {
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- trigger: "solana-secondary"
|
||||
branches: "!pull/*"
|
||||
async: true
|
||||
build:
|
||||
message: "${BUILDKITE_MESSAGE}"
|
||||
commit: "${BUILDKITE_COMMIT}"
|
||||
branch: "${BUILDKITE_BRANCH}"
|
||||
env:
|
||||
TRIGGERED_BUILDKITE_TAG: "${BUILDKITE_TAG}"
|
||||
EOF
|
||||
}
|
||||
|
||||
wait_step() {
|
||||
echo " - wait" >> "$output_file"
|
||||
}
|
||||
|
||||
all_test_steps() {
|
||||
command_step checks ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_nightly_docker_image ci/test-checks.sh" 20
|
||||
wait_step
|
||||
|
||||
# Coverage...
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-coverage.sh \
|
||||
^scripts/coverage.sh \
|
||||
; then
|
||||
command_step coverage ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_nightly_docker_image ci/test-coverage.sh" 40
|
||||
wait_step
|
||||
else
|
||||
annotate --style info --context test-coverage \
|
||||
"Coverage skipped as no .rs files were modified"
|
||||
fi
|
||||
# Coverage in disk...
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-coverage.sh \
|
||||
^scripts/coverage-in-disk.sh \
|
||||
; then
|
||||
command_step coverage-in-disk ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_nightly_docker_image ci/test-coverage.sh" 40
|
||||
wait_step
|
||||
else
|
||||
annotate --style info --context test-coverage \
|
||||
"Coverage skipped as no .rs files were modified"
|
||||
fi
|
||||
# Full test suite
|
||||
command_step stable ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-stable.sh" 60
|
||||
wait_step
|
||||
|
||||
# BPF test suite
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-stable-bpf.sh \
|
||||
^ci/test-stable.sh \
|
||||
^ci/test-local-cluster.sh \
|
||||
^core/build.rs \
|
||||
^fetch-perf-libs.sh \
|
||||
^programs/ \
|
||||
^sdk/ \
|
||||
; then
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- command: "ci/test-stable-bpf.sh"
|
||||
name: "stable-bpf"
|
||||
timeout_in_minutes: 20
|
||||
artifact_paths: "bpf-dumps.tar.bz2"
|
||||
agents:
|
||||
- "queue=default"
|
||||
EOF
|
||||
else
|
||||
annotate --style info \
|
||||
"Stable-BPF skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Perf test suite
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-stable-perf.sh \
|
||||
^ci/test-stable.sh \
|
||||
^ci/test-local-cluster.sh \
|
||||
^core/build.rs \
|
||||
^fetch-perf-libs.sh \
|
||||
^programs/ \
|
||||
^sdk/ \
|
||||
; then
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- command: "ci/test-stable-perf.sh"
|
||||
name: "stable-perf"
|
||||
timeout_in_minutes: 20
|
||||
artifact_paths: "log-*.txt"
|
||||
agents:
|
||||
- "queue=cuda"
|
||||
EOF
|
||||
else
|
||||
annotate --style info \
|
||||
"Stable-perf skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Downstream backwards compatibility
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-stable-perf.sh \
|
||||
^ci/test-stable.sh \
|
||||
^ci/test-local-cluster.sh \
|
||||
^core/build.rs \
|
||||
^fetch-perf-libs.sh \
|
||||
^programs/ \
|
||||
^sdk/ \
|
||||
^scripts/build-downstream-projects.sh \
|
||||
; then
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- command: "scripts/build-downstream-projects.sh"
|
||||
name: "downstream-projects"
|
||||
timeout_in_minutes: 30
|
||||
EOF
|
||||
else
|
||||
annotate --style info \
|
||||
"downstream-projects skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Downstream Anchor projects backwards compatibility
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-stable-perf.sh \
|
||||
^ci/test-stable.sh \
|
||||
^ci/test-local-cluster.sh \
|
||||
^core/build.rs \
|
||||
^fetch-perf-libs.sh \
|
||||
^programs/ \
|
||||
^sdk/ \
|
||||
^scripts/build-downstream-anchor-projects.sh \
|
||||
; then
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- command: "scripts/build-downstream-anchor-projects.sh"
|
||||
name: "downstream-anchor-projects"
|
||||
timeout_in_minutes: 10
|
||||
EOF
|
||||
else
|
||||
annotate --style info \
|
||||
"downstream-anchor-projects skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Wasm support
|
||||
if affects \
|
||||
^ci/test-wasm.sh \
|
||||
^ci/test-stable.sh \
|
||||
^sdk/ \
|
||||
; then
|
||||
command_step wasm ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-wasm.sh" 20
|
||||
else
|
||||
annotate --style info \
|
||||
"wasm skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Benches...
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-coverage.sh \
|
||||
^ci/test-bench.sh \
|
||||
; then
|
||||
command_step bench "ci/test-bench.sh" 30
|
||||
else
|
||||
annotate --style info --context test-bench \
|
||||
"Bench skipped as no .rs files were modified"
|
||||
fi
|
||||
|
||||
command_step "local-cluster" \
|
||||
". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-local-cluster.sh" \
|
||||
40
|
||||
|
||||
command_step "local-cluster-flakey" \
|
||||
". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-local-cluster-flakey.sh" \
|
||||
10
|
||||
|
||||
command_step "local-cluster-slow" \
|
||||
". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-local-cluster-slow.sh" \
|
||||
30
|
||||
}
|
||||
|
||||
pull_or_push_steps() {
|
||||
command_step sanity "ci/test-sanity.sh" 5
|
||||
wait_step
|
||||
|
||||
# Check for any .sh file changes
|
||||
if affects .sh$; then
|
||||
command_step shellcheck "ci/shellcheck.sh" 5
|
||||
wait_step
|
||||
fi
|
||||
|
||||
# Run the full test suite by default, skipping only if modifications are local
|
||||
# to some particular areas of the tree
|
||||
if affects_other_than ^.buildkite ^.mergify .md$ ^docs/ ^web3.js/ ^explorer/ ^.gitbook; then
|
||||
all_test_steps
|
||||
fi
|
||||
|
||||
# web3.js, explorer and docs changes run on Travis or Github actions...
|
||||
}
|
||||
|
||||
|
||||
if [[ -n $BUILDKITE_TAG ]]; then
|
||||
start_pipeline "Tag pipeline for $BUILDKITE_TAG"
|
||||
|
||||
annotate --style info --context release-tag \
|
||||
"https://github.com/solana-labs/solana/releases/$BUILDKITE_TAG"
|
||||
|
||||
# Jump directly to the secondary build to publish release artifacts quickly
|
||||
trigger_secondary_step
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
|
||||
echo "+++ Affected files in this PR"
|
||||
for file in "${affected_files[@]}"; do
|
||||
echo "- $file"
|
||||
done
|
||||
|
||||
start_pipeline "Pull request pipeline for $BUILDKITE_BRANCH"
|
||||
|
||||
# Add helpful link back to the corresponding Github Pull Request
|
||||
annotate --style info --context pr-backlink \
|
||||
"Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH"
|
||||
|
||||
if [[ $GITHUB_USER = "dependabot[bot]" ]]; then
|
||||
command_step dependabot "ci/dependabot-pr.sh" 5
|
||||
wait_step
|
||||
fi
|
||||
pull_or_push_steps
|
||||
exit 0
|
||||
fi
|
||||
|
||||
start_pipeline "Push pipeline for ${BUILDKITE_BRANCH:-?unknown branch?}"
|
||||
pull_or_push_steps
|
||||
wait_step
|
||||
trigger_secondary_step
|
||||
exit 0
|
@@ -102,6 +102,8 @@ command_step() {
|
||||
command: "$2"
|
||||
timeout_in_minutes: $3
|
||||
artifact_paths: "log-*.txt"
|
||||
agents:
|
||||
- "queue=solana"
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -168,7 +170,7 @@ all_test_steps() {
|
||||
timeout_in_minutes: 20
|
||||
artifact_paths: "bpf-dumps.tar.bz2"
|
||||
agents:
|
||||
- "queue=default"
|
||||
- "queue=solana"
|
||||
EOF
|
||||
else
|
||||
annotate --style info \
|
||||
@@ -221,12 +223,41 @@ EOF
|
||||
- command: "scripts/build-downstream-projects.sh"
|
||||
name: "downstream-projects"
|
||||
timeout_in_minutes: 30
|
||||
agents:
|
||||
- "queue=solana"
|
||||
EOF
|
||||
else
|
||||
annotate --style info \
|
||||
"downstream-projects skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Downstream Anchor projects backwards compatibility
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-stable-perf.sh \
|
||||
^ci/test-stable.sh \
|
||||
^ci/test-local-cluster.sh \
|
||||
^core/build.rs \
|
||||
^fetch-perf-libs.sh \
|
||||
^programs/ \
|
||||
^sdk/ \
|
||||
^scripts/build-downstream-anchor-projects.sh \
|
||||
; then
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- command: "scripts/build-downstream-anchor-projects.sh"
|
||||
name: "downstream-anchor-projects"
|
||||
timeout_in_minutes: 10
|
||||
agents:
|
||||
- "queue=solana"
|
||||
EOF
|
||||
else
|
||||
annotate --style info \
|
||||
"downstream-anchor-projects skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Wasm support
|
||||
if affects \
|
||||
^ci/test-wasm.sh \
|
||||
@@ -264,7 +295,7 @@ EOF
|
||||
|
||||
command_step "local-cluster-slow" \
|
||||
". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-local-cluster-slow.sh" \
|
||||
25
|
||||
30
|
||||
}
|
||||
|
||||
pull_or_push_steps() {
|
||||
|
@@ -19,3 +19,8 @@ steps:
|
||||
timeout_in_minutes: 240
|
||||
name: "publish crate"
|
||||
branches: "!master"
|
||||
- command: "ci/publish-tarball.sh"
|
||||
agents:
|
||||
- "queue=release-build-aarch64-apple-darwin"
|
||||
timeout_in_minutes: 60
|
||||
name: "publish tarball (aarch64-apple-darwin)"
|
||||
|
@@ -8,11 +8,6 @@ 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
|
||||
@@ -30,22 +25,10 @@ cargo_audit_ignores=(
|
||||
|
||||
# generic-array: arr! macro erases lifetimes
|
||||
#
|
||||
# Blocked on libsecp256k1 releasing with upgraded dependencies
|
||||
# https://github.com/paritytech/libsecp256k1/issues/66
|
||||
# Blocked on new spl dependencies on solana-program v1.9
|
||||
# due to curve25519-dalek dependency
|
||||
--ignore RUSTSEC-2020-0146
|
||||
|
||||
# hyper: Lenient `hyper` header parsing of `Content-Length` could allow request smuggling
|
||||
#
|
||||
# Blocked on jsonrpc removing dependency on unmaintained `websocket`
|
||||
# https://github.com/paritytech/jsonrpc/issues/605
|
||||
--ignore RUSTSEC-2021-0078
|
||||
|
||||
# hyper: Integer overflow in `hyper`'s parsing of the `Transfer-Encoding` header leads to data loss
|
||||
#
|
||||
# Blocked on jsonrpc removing dependency on unmaintained `websocket`
|
||||
# https://github.com/paritytech/jsonrpc/issues/605
|
||||
--ignore RUSTSEC-2021-0079
|
||||
|
||||
# chrono: Potential segfault in `localtime_r` invocations
|
||||
#
|
||||
# Blocked due to no safe upgrade
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM solanalabs/rust:1.57.0
|
||||
FROM solanalabs/rust:1.59.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.57.0
|
||||
FROM rust:1.59.0
|
||||
|
||||
# Add Google Protocol Buffers for Libra's metrics library.
|
||||
ENV PROTOC_VERSION 3.8.0
|
||||
|
@@ -150,7 +150,7 @@ elif [[ -n $BUILDKITE ]]; then
|
||||
cat > release.solana.com-install <<EOF
|
||||
SOLANA_RELEASE=$CHANNEL_OR_TAG
|
||||
SOLANA_INSTALL_INIT_ARGS=$CHANNEL_OR_TAG
|
||||
SOLANA_DOWNLOAD_ROOT=http://release.solana.com
|
||||
SOLANA_DOWNLOAD_ROOT=https://release.solana.com
|
||||
EOF
|
||||
cat install/solana-install-init.sh >> release.solana.com-install
|
||||
|
||||
|
@@ -7,7 +7,7 @@ source multinode-demo/common.sh
|
||||
|
||||
rm -rf config/run/init-completed config/ledger config/snapshot-ledger
|
||||
|
||||
SOLANA_RUN_SH_VALIDATOR_ARGS="--snapshot-interval-slots 200" timeout 120 ./scripts/run.sh &
|
||||
SOLANA_RUN_SH_VALIDATOR_ARGS="--full-snapshot-interval-slots 200" timeout 120 ./scripts/run.sh &
|
||||
pid=$!
|
||||
|
||||
attempts=20
|
||||
|
@@ -18,13 +18,13 @@
|
||||
if [[ -n $RUST_STABLE_VERSION ]]; then
|
||||
stable_version="$RUST_STABLE_VERSION"
|
||||
else
|
||||
stable_version=1.57.0
|
||||
stable_version=1.59.0
|
||||
fi
|
||||
|
||||
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
||||
nightly_version="$RUST_NIGHTLY_VERSION"
|
||||
else
|
||||
nightly_version=2021-12-03
|
||||
nightly_version=2022-02-24
|
||||
fi
|
||||
|
||||
|
||||
|
24
ci/sbf-tools-info.sh
Executable file
24
ci/sbf-tools-info.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Finds the version of sbf-tools used by this source tree.
|
||||
#
|
||||
# stdout of this script may be eval-ed.
|
||||
#
|
||||
|
||||
here="$(dirname "$0")"
|
||||
|
||||
SBF_TOOLS_VERSION=unknown
|
||||
|
||||
cargo_build_bpf_main="${here}/../sdk/cargo-build-bpf/src/main.rs"
|
||||
if [[ -f "${cargo_build_bpf_main}" ]]; then
|
||||
version=$(sed -e 's/^.*bpf_tools_version\s*=\s*"\(v[0-9.]\+\)".*/\1/;t;d' "${cargo_build_bpf_main}")
|
||||
if [[ ${version} != '' ]]; then
|
||||
SBF_TOOLS_VERSION="${version}"
|
||||
else
|
||||
echo '--- unable to parse SBF_TOOLS_VERSION'
|
||||
fi
|
||||
else
|
||||
echo "--- '${cargo_build_bpf_main}' not present"
|
||||
fi
|
||||
|
||||
echo SBF_TOOLS_VERSION="${SBF_TOOLS_VERSION}"
|
@@ -57,32 +57,20 @@ if [[ $CI_BASE_BRANCH = "$EDGE_CHANNEL" ]]; then
|
||||
exit "$check_status"
|
||||
fi
|
||||
|
||||
# Ensure nightly and --benches
|
||||
# Ensure nightly and --benches
|
||||
_ 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
|
||||
_ ci/order-crates-for-publishing.py
|
||||
|
||||
# -Z... is needed because of clippy bug: https://github.com/rust-lang/rust-clippy/issues/4612
|
||||
# run nightly clippy for `sdk/` as there's a moderate amount of nightly-only code there
|
||||
_ "$cargo" nightly clippy -Zunstable-options --workspace --all-targets -- --deny=warnings --deny=clippy::integer_arithmetic
|
||||
_ scripts/cargo-for-all-lock-files.sh -- nightly clippy -Zunstable-options --all-targets -- --deny=warnings --deny=clippy::integer_arithmetic
|
||||
|
||||
_ "$cargo" stable fmt --all -- --check
|
||||
_ scripts/cargo-for-all-lock-files.sh -- nightly fmt --all -- --check
|
||||
|
||||
_ ci/do-audit.sh
|
||||
|
||||
{
|
||||
cd programs/bpf
|
||||
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
|
||||
)
|
||||
done
|
||||
}
|
||||
_ ci/do-audit.sh
|
||||
|
||||
echo --- ok
|
||||
|
@@ -21,15 +21,16 @@ export RUST_BACKTRACE=1
|
||||
export RUSTFLAGS="-D warnings"
|
||||
source scripts/ulimit-n.sh
|
||||
|
||||
# Limit compiler jobs to reduce memory usage
|
||||
# on machines with 2gb/thread of memory
|
||||
# limit jobs to 4gb/thread
|
||||
JOBS=$(grep MemTotal /proc/meminfo | awk '{printf "%.0f", ($2 / (4 * 1024 * 1024))}')
|
||||
NPROC=$(nproc)
|
||||
NPROC=$((NPROC>14 ? 14 : NPROC))
|
||||
JOBS=$((JOBS>NPROC ? NPROC : JOBS))
|
||||
|
||||
|
||||
echo "Executing $testName"
|
||||
case $testName in
|
||||
test-stable)
|
||||
_ "$cargo" stable test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
_ "$cargo" stable test --jobs "$JOBS" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
;;
|
||||
test-stable-bpf)
|
||||
# Clear the C dependency files, if dependency moves these files are not regenerated
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-clap-utils"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
description = "Solana utilities for the clap"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,19 +10,19 @@ documentation = "https://docs.rs/solana-clap-utils"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
clap = "2.33.0"
|
||||
rpassword = "5.0"
|
||||
solana-perf = { path = "../perf", version = "=1.10.0" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-perf = { path = "../perf", version = "=1.10.2" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "=1.10.2", default-features = false }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
thiserror = "1.0.30"
|
||||
tiny-bip39 = "0.8.2"
|
||||
uriparse = "0.6.3"
|
||||
url = "2.2.2"
|
||||
chrono = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.2.0"
|
||||
tempfile = "3.3.0"
|
||||
|
||||
[lib]
|
||||
name = "solana_clap_utils"
|
||||
|
@@ -328,7 +328,7 @@ pub fn is_derivation<T>(value: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let value = value.as_ref().replace("'", "");
|
||||
let value = value.as_ref().replace('\'', "");
|
||||
let mut parts = value.split('/');
|
||||
let account = parts.next().unwrap();
|
||||
account
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2021"
|
||||
name = "solana-cli-config"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -12,13 +12,13 @@ documentation = "https://docs.rs/solana-cli-config"
|
||||
[dependencies]
|
||||
dirs-next = "2.0.0"
|
||||
lazy_static = "1.4.0"
|
||||
serde = "1.0.133"
|
||||
serde = "1.0.136"
|
||||
serde_derive = "1.0.103"
|
||||
serde_yaml = "0.8.23"
|
||||
url = "2.2.2"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.52"
|
||||
anyhow = "1.0.56"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -3,29 +3,32 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2021"
|
||||
name = "solana-cli-output"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-cli-output"
|
||||
|
||||
[dependencies]
|
||||
Inflector = "0.11.4"
|
||||
base64 = "0.13.0"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
clap = "2.33.0"
|
||||
console = "0.15.0"
|
||||
humantime = "2.0.1"
|
||||
Inflector = "0.11.4"
|
||||
indicatif = "0.16.2"
|
||||
serde = "1.0.133"
|
||||
serde_json = "1.0.74"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.10.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.10.0" }
|
||||
solana-client = { path = "../client", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.10.0" }
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.10.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.10.2" }
|
||||
solana-client = { path = "../client", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.2" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.10.2" }
|
||||
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
|
||||
|
||||
[dev-dependencies]
|
||||
ed25519-dalek = "=1.0.1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -46,6 +46,8 @@ use {
|
||||
},
|
||||
};
|
||||
|
||||
static CHECK_MARK: Emoji = Emoji("✅ ", "");
|
||||
static CROSS_MARK: Emoji = Emoji("❌ ", "");
|
||||
static WARNING: Emoji = Emoji("⚠️", "!");
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
@@ -391,19 +393,19 @@ impl fmt::Display for CliValidators {
|
||||
) -> fmt::Result {
|
||||
fn non_zero_or_dash(v: u64, max_v: u64) -> String {
|
||||
if v == 0 {
|
||||
"- ".into()
|
||||
" - ".into()
|
||||
} else if v == max_v {
|
||||
format!("{:>8} ( 0)", v)
|
||||
format!("{:>9} ( 0)", v)
|
||||
} else if v > max_v.saturating_sub(100) {
|
||||
format!("{:>8} ({:>3})", v, -(max_v.saturating_sub(v) as isize))
|
||||
format!("{:>9} ({:>3})", v, -(max_v.saturating_sub(v) as isize))
|
||||
} else {
|
||||
format!("{:>8} ", v)
|
||||
format!("{:>9} ", v)
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"{} {:<44} {:<44} {:>3}% {:>14} {:>14} {:>7} {:>8} {:>7} {}",
|
||||
"{} {:<44} {:<44} {:>3}% {:>14} {:>14} {:>7} {:>8} {:>7} {:>22} ({:.2}%)",
|
||||
if validator.delinquent {
|
||||
WARNING.to_string()
|
||||
} else {
|
||||
@@ -417,19 +419,19 @@ impl fmt::Display for CliValidators {
|
||||
if let Some(skip_rate) = validator.skip_rate {
|
||||
format!("{:.2}%", skip_rate)
|
||||
} else {
|
||||
"- ".to_string()
|
||||
"- ".to_string()
|
||||
},
|
||||
validator.epoch_credits,
|
||||
validator.version,
|
||||
if validator.activated_stake > 0 {
|
||||
format!(
|
||||
"{} ({:.2}%)",
|
||||
build_balance_message(validator.activated_stake, use_lamports_unit, true),
|
||||
100. * validator.activated_stake as f64 / total_active_stake as f64,
|
||||
)
|
||||
} else {
|
||||
"-".into()
|
||||
},
|
||||
build_balance_message_with_config(
|
||||
validator.activated_stake,
|
||||
&BuildBalanceMessageConfig {
|
||||
use_lamports_unit,
|
||||
trim_trailing_zeros: false,
|
||||
..BuildBalanceMessageConfig::default()
|
||||
}
|
||||
),
|
||||
100. * validator.activated_stake as f64 / total_active_stake as f64,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -439,13 +441,13 @@ impl fmt::Display for CliValidators {
|
||||
0
|
||||
};
|
||||
let header = style(format!(
|
||||
"{:padding$} {:<44} {:<38} {} {} {} {} {} {} {}",
|
||||
"{:padding$} {:<44} {:<38} {} {} {} {} {} {} {}",
|
||||
" ",
|
||||
"Identity",
|
||||
"Vote Account",
|
||||
"Commission",
|
||||
"Last Vote ",
|
||||
"Root Slot ",
|
||||
"Last Vote ",
|
||||
"Root Slot ",
|
||||
"Skip Rate",
|
||||
"Credits",
|
||||
"Version",
|
||||
@@ -2285,6 +2287,7 @@ impl fmt::Display for CliBlock {
|
||||
let sign = if reward.lamports < 0 { "-" } else { "" };
|
||||
|
||||
total_rewards += reward.lamports;
|
||||
#[allow(clippy::format_in_format_args)]
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} {:^15} {:>15} {} {}",
|
||||
@@ -2332,7 +2335,7 @@ impl fmt::Display for CliBlock {
|
||||
writeln_transaction(
|
||||
f,
|
||||
&transaction_with_meta.transaction.decode().unwrap(),
|
||||
&transaction_with_meta.meta,
|
||||
transaction_with_meta.meta.as_ref(),
|
||||
" ",
|
||||
None,
|
||||
None,
|
||||
@@ -2366,7 +2369,7 @@ impl fmt::Display for CliTransaction {
|
||||
writeln_transaction(
|
||||
f,
|
||||
&self.decoded_transaction,
|
||||
&self.meta,
|
||||
self.meta.as_ref(),
|
||||
&self.prefix,
|
||||
if !self.sigverify_status.is_empty() {
|
||||
Some(&self.sigverify_status)
|
||||
@@ -2448,6 +2451,8 @@ pub struct CliGossipNode {
|
||||
pub rpc_host: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feature_set: Option<u32>,
|
||||
}
|
||||
|
||||
impl CliGossipNode {
|
||||
@@ -2460,6 +2465,7 @@ impl CliGossipNode {
|
||||
tpu_port: info.tpu.map(|addr| addr.port()),
|
||||
rpc_host: info.rpc.map(|addr| addr.to_string()),
|
||||
version: info.version,
|
||||
feature_set: info.feature_set,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2485,7 +2491,7 @@ impl fmt::Display for CliGossipNode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{:15} | {:44} | {:6} | {:5} | {:21} | {}",
|
||||
"{:15} | {:44} | {:6} | {:5} | {:21} | {:8}| {}",
|
||||
unwrap_to_string_or_none(self.ip_address.as_ref()),
|
||||
self.identity_label
|
||||
.as_ref()
|
||||
@@ -2494,6 +2500,7 @@ impl fmt::Display for CliGossipNode {
|
||||
unwrap_to_string_or_none(self.tpu_port.as_ref()),
|
||||
unwrap_to_string_or_none(self.rpc_host.as_ref()),
|
||||
unwrap_to_string_or_default(self.version.as_ref(), "unknown"),
|
||||
unwrap_to_string_or_default(self.feature_set.as_ref(), "unknown"),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2508,10 +2515,10 @@ impl fmt::Display for CliGossipNodes {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"IP Address | Node identifier \
|
||||
| Gossip | TPU | RPC Address | Version\n\
|
||||
"IP Address | Identity \
|
||||
| Gossip | TPU | RPC Address | Version | Feature Set\n\
|
||||
----------------+----------------------------------------------+\
|
||||
--------+-------+-----------------------+----------------",
|
||||
--------+-------+-----------------------+---------+----------------",
|
||||
)?;
|
||||
for node in self.0.iter() {
|
||||
writeln!(f, "{}", node)?;
|
||||
@@ -2523,6 +2530,172 @@ impl fmt::Display for CliGossipNodes {
|
||||
impl QuietDisplay for CliGossipNodes {}
|
||||
impl VerboseDisplay for CliGossipNodes {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliPing {
|
||||
pub source_pubkey: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub fixed_blockhash: Option<String>,
|
||||
#[serde(skip_serializing)]
|
||||
pub blockhash_from_cluster: bool,
|
||||
pub pings: Vec<CliPingData>,
|
||||
pub transaction_stats: CliPingTxStats,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub confirmation_stats: Option<CliPingConfirmationStats>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliPing {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Source Account:", &self.source_pubkey)?;
|
||||
if let Some(fixed_blockhash) = &self.fixed_blockhash {
|
||||
let blockhash_origin = if self.blockhash_from_cluster {
|
||||
"fetched from cluster"
|
||||
} else {
|
||||
"supplied from cli arguments"
|
||||
};
|
||||
writeln!(
|
||||
f,
|
||||
"Fixed blockhash is used: {} ({})",
|
||||
fixed_blockhash, blockhash_origin
|
||||
)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
for ping in &self.pings {
|
||||
write!(f, "{}", ping)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
writeln!(f, "--- transaction statistics ---")?;
|
||||
write!(f, "{}", self.transaction_stats)?;
|
||||
if let Some(confirmation_stats) = &self.confirmation_stats {
|
||||
write!(f, "{}", confirmation_stats)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliPing {}
|
||||
impl VerboseDisplay for CliPing {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliPingData {
|
||||
pub success: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub signature: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ms: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<String>,
|
||||
#[serde(skip_serializing)]
|
||||
pub print_timestamp: bool,
|
||||
pub timestamp: String,
|
||||
pub sequence: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub lamports: Option<u64>,
|
||||
}
|
||||
impl fmt::Display for CliPingData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let (mark, msg) = if let Some(signature) = &self.signature {
|
||||
if self.success {
|
||||
(
|
||||
CHECK_MARK,
|
||||
format!(
|
||||
"{} lamport(s) transferred: seq={:<3} time={:>4}ms signature={}",
|
||||
self.lamports.unwrap(),
|
||||
self.sequence,
|
||||
self.ms.unwrap(),
|
||||
signature
|
||||
),
|
||||
)
|
||||
} else if let Some(error) = &self.error {
|
||||
(
|
||||
CROSS_MARK,
|
||||
format!(
|
||||
"Transaction failed: seq={:<3} error={:?} signature={}",
|
||||
self.sequence, error, signature
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
CROSS_MARK,
|
||||
format!(
|
||||
"Confirmation timeout: seq={:<3} signature={}",
|
||||
self.sequence, signature
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(
|
||||
CROSS_MARK,
|
||||
format!(
|
||||
"Submit failed: seq={:<3} error={:?}",
|
||||
self.sequence,
|
||||
self.error.as_ref().unwrap(),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"{}{}{}",
|
||||
if self.print_timestamp {
|
||||
&self.timestamp
|
||||
} else {
|
||||
""
|
||||
},
|
||||
mark,
|
||||
msg
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliPingData {}
|
||||
impl VerboseDisplay for CliPingData {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliPingTxStats {
|
||||
pub num_transactions: u32,
|
||||
pub num_transaction_confirmed: u32,
|
||||
}
|
||||
impl fmt::Display for CliPingTxStats {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"{} transactions submitted, {} transactions confirmed, {:.1}% transaction loss",
|
||||
self.num_transactions,
|
||||
self.num_transaction_confirmed,
|
||||
(100.
|
||||
- f64::from(self.num_transaction_confirmed) / f64::from(self.num_transactions)
|
||||
* 100.)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliPingTxStats {}
|
||||
impl VerboseDisplay for CliPingTxStats {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliPingConfirmationStats {
|
||||
pub min: f64,
|
||||
pub mean: f64,
|
||||
pub max: f64,
|
||||
pub std_dev: f64,
|
||||
}
|
||||
impl fmt::Display for CliPingConfirmationStats {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"confirmation min/mean/max/stddev = {:.0}/{:.0}/{:.0}/{:.0} ms",
|
||||
self.min, self.mean, self.max, self.std_dev,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl QuietDisplay for CliPingConfirmationStats {}
|
||||
impl VerboseDisplay for CliPingConfirmationStats {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {
|
||||
|
@@ -4,10 +4,17 @@ use {
|
||||
console::style,
|
||||
indicatif::{ProgressBar, ProgressStyle},
|
||||
solana_sdk::{
|
||||
clock::UnixTimestamp, hash::Hash, message::Message, native_token::lamports_to_sol,
|
||||
program_utils::limited_deserialize, pubkey::Pubkey, stake, transaction::Transaction,
|
||||
clock::UnixTimestamp,
|
||||
hash::Hash,
|
||||
instruction::CompiledInstruction,
|
||||
native_token::lamports_to_sol,
|
||||
program_utils::limited_deserialize,
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
stake,
|
||||
transaction::{Transaction, TransactionError},
|
||||
},
|
||||
solana_transaction_status::UiTransactionStatusMeta,
|
||||
solana_transaction_status::{Rewards, UiTransactionStatusMeta},
|
||||
spl_memo::{id as spl_memo_id, v1::id as spl_memo_v1_id},
|
||||
std::{collections::HashMap, fmt, io},
|
||||
};
|
||||
@@ -131,22 +138,28 @@ pub fn println_signers(
|
||||
println!();
|
||||
}
|
||||
|
||||
fn format_account_mode(message: &Message, index: usize) -> String {
|
||||
struct CliAccountMeta {
|
||||
is_signer: bool,
|
||||
is_writable: bool,
|
||||
is_invoked: bool,
|
||||
}
|
||||
|
||||
fn format_account_mode(meta: CliAccountMeta) -> String {
|
||||
format!(
|
||||
"{}r{}{}", // accounts are always readable...
|
||||
if message.is_signer(index) {
|
||||
if meta.is_signer {
|
||||
"s" // stands for signer
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if message.is_writable(index) {
|
||||
if meta.is_writable {
|
||||
"w" // comment for consistent rust fmt (no joking; lol)
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
// account may be executable on-chain while not being
|
||||
// designated as a program-id in the message
|
||||
if message.maybe_executable(index) {
|
||||
if meta.is_invoked {
|
||||
"x"
|
||||
} else {
|
||||
// programs to be executed via CPI cannot be identified as
|
||||
@@ -156,202 +169,66 @@ fn format_account_mode(message: &Message, index: usize) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn write_transaction<W: io::Write>(
|
||||
fn write_transaction<W: io::Write>(
|
||||
w: &mut W,
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<UiTransactionStatusMeta>,
|
||||
transaction_status: Option<&UiTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
||||
block_time: Option<UnixTimestamp>,
|
||||
timezone: CliTimezone,
|
||||
) -> io::Result<()> {
|
||||
write_block_time(w, block_time, timezone, prefix)?;
|
||||
|
||||
let message = &transaction.message;
|
||||
if let Some(block_time) = block_time {
|
||||
writeln!(
|
||||
w,
|
||||
"{}Block Time: {:?}",
|
||||
prefix,
|
||||
Local.timestamp(block_time, 0)
|
||||
)?;
|
||||
}
|
||||
writeln!(
|
||||
w,
|
||||
"{}Recent Blockhash: {:?}",
|
||||
prefix, message.recent_blockhash
|
||||
)?;
|
||||
let sigverify_statuses = if let Some(sigverify_status) = sigverify_status {
|
||||
sigverify_status
|
||||
.iter()
|
||||
.map(|s| format!(" ({})", s))
|
||||
.collect()
|
||||
} else {
|
||||
vec!["".to_string(); transaction.signatures.len()]
|
||||
};
|
||||
for (signature_index, (signature, sigverify_status)) in transaction
|
||||
.signatures
|
||||
.iter()
|
||||
.zip(&sigverify_statuses)
|
||||
.enumerate()
|
||||
{
|
||||
writeln!(
|
||||
w,
|
||||
"{}Signature {}: {:?}{}",
|
||||
prefix, signature_index, signature, sigverify_status,
|
||||
)?;
|
||||
}
|
||||
write_recent_blockhash(w, &message.recent_blockhash, prefix)?;
|
||||
write_signatures(w, &transaction.signatures, sigverify_status, prefix)?;
|
||||
|
||||
let mut fee_payer_index = None;
|
||||
for (account_index, account) in message.account_keys.iter().enumerate() {
|
||||
if fee_payer_index.is_none() && message.is_non_loader_key(account_index) {
|
||||
fee_payer_index = Some(account_index)
|
||||
}
|
||||
writeln!(
|
||||
|
||||
let account_meta = CliAccountMeta {
|
||||
is_signer: message.is_signer(account_index),
|
||||
is_writable: message.is_writable(account_index),
|
||||
is_invoked: message.maybe_executable(account_index),
|
||||
};
|
||||
|
||||
write_account(
|
||||
w,
|
||||
"{}Account {}: {} {}{}",
|
||||
prefix,
|
||||
account_index,
|
||||
format_account_mode(message, account_index),
|
||||
account,
|
||||
if Some(account_index) == fee_payer_index {
|
||||
" (fee payer)"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
format_account_mode(account_meta),
|
||||
Some(account_index) == fee_payer_index,
|
||||
prefix,
|
||||
)?;
|
||||
}
|
||||
|
||||
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
||||
let program_pubkey = message.account_keys[instruction.program_id_index as usize];
|
||||
writeln!(w, "{}Instruction {}", prefix, instruction_index)?;
|
||||
writeln!(
|
||||
let instruction_accounts = instruction.accounts.iter().map(|account_index| {
|
||||
let account_pubkey = &message.account_keys[*account_index as usize];
|
||||
(account_pubkey, *account_index)
|
||||
});
|
||||
|
||||
write_instruction(
|
||||
w,
|
||||
"{} Program: {} ({})",
|
||||
prefix, program_pubkey, instruction.program_id_index
|
||||
instruction_index,
|
||||
&program_pubkey,
|
||||
instruction,
|
||||
instruction_accounts,
|
||||
prefix,
|
||||
)?;
|
||||
for (account_index, account) in instruction.accounts.iter().enumerate() {
|
||||
let account_pubkey = message.account_keys[*account as usize];
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {}: {} ({})",
|
||||
prefix, account_index, account_pubkey, account
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut raw = true;
|
||||
if program_pubkey == solana_vote_program::id() {
|
||||
if let Ok(vote_instruction) = limited_deserialize::<
|
||||
solana_vote_program::vote_instruction::VoteInstruction,
|
||||
>(&instruction.data)
|
||||
{
|
||||
writeln!(w, "{} {:?}", prefix, vote_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
} else if program_pubkey == stake::program::id() {
|
||||
if let Ok(stake_instruction) =
|
||||
limited_deserialize::<stake::instruction::StakeInstruction>(&instruction.data)
|
||||
{
|
||||
writeln!(w, "{} {:?}", prefix, stake_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
} else if program_pubkey == solana_sdk::system_program::id() {
|
||||
if let Ok(system_instruction) = limited_deserialize::<
|
||||
solana_sdk::system_instruction::SystemInstruction,
|
||||
>(&instruction.data)
|
||||
{
|
||||
writeln!(w, "{} {:?}", prefix, system_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
} else if is_memo_program(&program_pubkey) {
|
||||
if let Ok(s) = std::str::from_utf8(&instruction.data) {
|
||||
writeln!(w, "{} Data: \"{}\"", prefix, s)?;
|
||||
raw = false;
|
||||
}
|
||||
}
|
||||
|
||||
if raw {
|
||||
writeln!(w, "{} Data: {:?}", prefix, instruction.data)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(transaction_status) = transaction_status {
|
||||
writeln!(
|
||||
w,
|
||||
"{}Status: {}",
|
||||
prefix,
|
||||
match &transaction_status.status {
|
||||
Ok(_) => "Ok".into(),
|
||||
Err(err) => err.to_string(),
|
||||
}
|
||||
)?;
|
||||
writeln!(
|
||||
w,
|
||||
"{} Fee: ◎{}",
|
||||
prefix,
|
||||
lamports_to_sol(transaction_status.fee)
|
||||
)?;
|
||||
assert_eq!(
|
||||
transaction_status.pre_balances.len(),
|
||||
transaction_status.post_balances.len()
|
||||
);
|
||||
for (i, (pre, post)) in transaction_status
|
||||
.pre_balances
|
||||
.iter()
|
||||
.zip(transaction_status.post_balances.iter())
|
||||
.enumerate()
|
||||
{
|
||||
if pre == post {
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {} balance: ◎{}",
|
||||
prefix,
|
||||
i,
|
||||
lamports_to_sol(*pre)
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {} balance: ◎{} -> ◎{}",
|
||||
prefix,
|
||||
i,
|
||||
lamports_to_sol(*pre),
|
||||
lamports_to_sol(*post)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(log_messages) = &transaction_status.log_messages {
|
||||
if !log_messages.is_empty() {
|
||||
writeln!(w, "{}Log Messages:", prefix,)?;
|
||||
for log_message in log_messages {
|
||||
writeln!(w, "{} {}", prefix, log_message)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rewards) = &transaction_status.rewards {
|
||||
if !rewards.is_empty() {
|
||||
writeln!(w, "{}Rewards:", prefix,)?;
|
||||
writeln!(
|
||||
w,
|
||||
"{} {:<44} {:^15} {:<15} {:<20}",
|
||||
prefix, "Address", "Type", "Amount", "New Balance"
|
||||
)?;
|
||||
for reward in rewards {
|
||||
let sign = if reward.lamports < 0 { "-" } else { "" };
|
||||
writeln!(
|
||||
w,
|
||||
"{} {:<44} {:^15} {}◎{:<14.9} ◎{:<18.9}",
|
||||
prefix,
|
||||
reward.pubkey,
|
||||
if let Some(reward_type) = reward.reward_type {
|
||||
format!("{}", reward_type)
|
||||
} else {
|
||||
"-".to_string()
|
||||
},
|
||||
sign,
|
||||
lamports_to_sol(reward.lamports.abs() as u64),
|
||||
lamports_to_sol(reward.post_balance)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
write_status(w, &transaction_status.status, prefix)?;
|
||||
write_fees(w, transaction_status.fee, prefix)?;
|
||||
write_balances(w, transaction_status, prefix)?;
|
||||
write_log_messages(w, transaction_status.log_messages.as_ref(), prefix)?;
|
||||
write_rewards(w, transaction_status.rewards.as_ref(), prefix)?;
|
||||
} else {
|
||||
writeln!(w, "{}Status: Unavailable", prefix)?;
|
||||
}
|
||||
@@ -359,9 +236,252 @@ pub fn write_transaction<W: io::Write>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
enum CliTimezone {
|
||||
Local,
|
||||
#[allow(dead_code)]
|
||||
Utc,
|
||||
}
|
||||
|
||||
fn write_block_time<W: io::Write>(
|
||||
w: &mut W,
|
||||
block_time: Option<UnixTimestamp>,
|
||||
timezone: CliTimezone,
|
||||
prefix: &str,
|
||||
) -> io::Result<()> {
|
||||
if let Some(block_time) = block_time {
|
||||
let block_time_output = match timezone {
|
||||
CliTimezone::Local => format!("{:?}", Local.timestamp(block_time, 0)),
|
||||
CliTimezone::Utc => format!("{:?}", Utc.timestamp(block_time, 0)),
|
||||
};
|
||||
writeln!(w, "{}Block Time: {}", prefix, block_time_output,)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_recent_blockhash<W: io::Write>(
|
||||
w: &mut W,
|
||||
recent_blockhash: &Hash,
|
||||
prefix: &str,
|
||||
) -> io::Result<()> {
|
||||
writeln!(w, "{}Recent Blockhash: {:?}", prefix, recent_blockhash)
|
||||
}
|
||||
|
||||
fn write_signatures<W: io::Write>(
|
||||
w: &mut W,
|
||||
signatures: &[Signature],
|
||||
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
||||
prefix: &str,
|
||||
) -> io::Result<()> {
|
||||
let sigverify_statuses = if let Some(sigverify_status) = sigverify_status {
|
||||
sigverify_status
|
||||
.iter()
|
||||
.map(|s| format!(" ({})", s))
|
||||
.collect()
|
||||
} else {
|
||||
vec!["".to_string(); signatures.len()]
|
||||
};
|
||||
for (signature_index, (signature, sigverify_status)) in
|
||||
signatures.iter().zip(&sigverify_statuses).enumerate()
|
||||
{
|
||||
writeln!(
|
||||
w,
|
||||
"{}Signature {}: {:?}{}",
|
||||
prefix, signature_index, signature, sigverify_status,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_account<W: io::Write>(
|
||||
w: &mut W,
|
||||
account_index: usize,
|
||||
account_address: &Pubkey,
|
||||
account_mode: String,
|
||||
is_fee_payer: bool,
|
||||
prefix: &str,
|
||||
) -> io::Result<()> {
|
||||
writeln!(
|
||||
w,
|
||||
"{}Account {}: {} {}{}",
|
||||
prefix,
|
||||
account_index,
|
||||
account_mode,
|
||||
account_address,
|
||||
if is_fee_payer { " (fee payer)" } else { "" },
|
||||
)
|
||||
}
|
||||
|
||||
fn write_instruction<'a, W: io::Write>(
|
||||
w: &mut W,
|
||||
instruction_index: usize,
|
||||
program_pubkey: &Pubkey,
|
||||
instruction: &CompiledInstruction,
|
||||
instruction_accounts: impl Iterator<Item = (&'a Pubkey, u8)>,
|
||||
prefix: &str,
|
||||
) -> io::Result<()> {
|
||||
writeln!(w, "{}Instruction {}", prefix, instruction_index)?;
|
||||
writeln!(
|
||||
w,
|
||||
"{} Program: {} ({})",
|
||||
prefix, program_pubkey, instruction.program_id_index
|
||||
)?;
|
||||
for (index, (account_address, account_index)) in instruction_accounts.enumerate() {
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {}: {} ({})",
|
||||
prefix, index, account_address, account_index
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut raw = true;
|
||||
if program_pubkey == &solana_vote_program::id() {
|
||||
if let Ok(vote_instruction) = limited_deserialize::<
|
||||
solana_vote_program::vote_instruction::VoteInstruction,
|
||||
>(&instruction.data)
|
||||
{
|
||||
writeln!(w, "{} {:?}", prefix, vote_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
} else if program_pubkey == &stake::program::id() {
|
||||
if let Ok(stake_instruction) =
|
||||
limited_deserialize::<stake::instruction::StakeInstruction>(&instruction.data)
|
||||
{
|
||||
writeln!(w, "{} {:?}", prefix, stake_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
} else if program_pubkey == &solana_sdk::system_program::id() {
|
||||
if let Ok(system_instruction) = limited_deserialize::<
|
||||
solana_sdk::system_instruction::SystemInstruction,
|
||||
>(&instruction.data)
|
||||
{
|
||||
writeln!(w, "{} {:?}", prefix, system_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
} else if is_memo_program(program_pubkey) {
|
||||
if let Ok(s) = std::str::from_utf8(&instruction.data) {
|
||||
writeln!(w, "{} Data: \"{}\"", prefix, s)?;
|
||||
raw = false;
|
||||
}
|
||||
}
|
||||
|
||||
if raw {
|
||||
writeln!(w, "{} Data: {:?}", prefix, instruction.data)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_rewards<W: io::Write>(
|
||||
w: &mut W,
|
||||
rewards: Option<&Rewards>,
|
||||
prefix: &str,
|
||||
) -> io::Result<()> {
|
||||
if let Some(rewards) = rewards {
|
||||
if !rewards.is_empty() {
|
||||
writeln!(w, "{}Rewards:", prefix,)?;
|
||||
writeln!(
|
||||
w,
|
||||
"{} {:<44} {:^15} {:<16} {:<20}",
|
||||
prefix, "Address", "Type", "Amount", "New Balance"
|
||||
)?;
|
||||
for reward in rewards {
|
||||
let sign = if reward.lamports < 0 { "-" } else { "" };
|
||||
writeln!(
|
||||
w,
|
||||
"{} {:<44} {:^15} {}◎{:<14.9} ◎{:<18.9}",
|
||||
prefix,
|
||||
reward.pubkey,
|
||||
if let Some(reward_type) = reward.reward_type {
|
||||
format!("{}", reward_type)
|
||||
} else {
|
||||
"-".to_string()
|
||||
},
|
||||
sign,
|
||||
lamports_to_sol(reward.lamports.abs() as u64),
|
||||
lamports_to_sol(reward.post_balance)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_status<W: io::Write>(
|
||||
w: &mut W,
|
||||
transaction_status: &Result<(), TransactionError>,
|
||||
prefix: &str,
|
||||
) -> io::Result<()> {
|
||||
writeln!(
|
||||
w,
|
||||
"{}Status: {}",
|
||||
prefix,
|
||||
match transaction_status {
|
||||
Ok(_) => "Ok".into(),
|
||||
Err(err) => err.to_string(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn write_fees<W: io::Write>(w: &mut W, transaction_fee: u64, prefix: &str) -> io::Result<()> {
|
||||
writeln!(w, "{} Fee: ◎{}", prefix, lamports_to_sol(transaction_fee))
|
||||
}
|
||||
|
||||
fn write_balances<W: io::Write>(
|
||||
w: &mut W,
|
||||
transaction_status: &UiTransactionStatusMeta,
|
||||
prefix: &str,
|
||||
) -> io::Result<()> {
|
||||
assert_eq!(
|
||||
transaction_status.pre_balances.len(),
|
||||
transaction_status.post_balances.len()
|
||||
);
|
||||
for (i, (pre, post)) in transaction_status
|
||||
.pre_balances
|
||||
.iter()
|
||||
.zip(transaction_status.post_balances.iter())
|
||||
.enumerate()
|
||||
{
|
||||
if pre == post {
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {} balance: ◎{}",
|
||||
prefix,
|
||||
i,
|
||||
lamports_to_sol(*pre)
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {} balance: ◎{} -> ◎{}",
|
||||
prefix,
|
||||
i,
|
||||
lamports_to_sol(*pre),
|
||||
lamports_to_sol(*post)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_log_messages<W: io::Write>(
|
||||
w: &mut W,
|
||||
log_messages: Option<&Vec<String>>,
|
||||
prefix: &str,
|
||||
) -> io::Result<()> {
|
||||
if let Some(log_messages) = log_messages {
|
||||
if !log_messages.is_empty() {
|
||||
writeln!(w, "{}Log Messages:", prefix,)?;
|
||||
for log_message in log_messages {
|
||||
writeln!(w, "{} {}", prefix, log_message)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn println_transaction(
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<UiTransactionStatusMeta>,
|
||||
transaction_status: Option<&UiTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
||||
block_time: Option<UnixTimestamp>,
|
||||
@@ -374,6 +494,7 @@ pub fn println_transaction(
|
||||
prefix,
|
||||
sigverify_status,
|
||||
block_time,
|
||||
CliTimezone::Local,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
@@ -386,22 +507,23 @@ pub fn println_transaction(
|
||||
pub fn writeln_transaction(
|
||||
f: &mut dyn fmt::Write,
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<UiTransactionStatusMeta>,
|
||||
transaction_status: Option<&UiTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
||||
block_time: Option<UnixTimestamp>,
|
||||
) -> fmt::Result {
|
||||
let mut w = Vec::new();
|
||||
if write_transaction(
|
||||
let write_result = write_transaction(
|
||||
&mut w,
|
||||
transaction,
|
||||
transaction_status,
|
||||
prefix,
|
||||
sigverify_status,
|
||||
block_time,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
CliTimezone::Local,
|
||||
);
|
||||
|
||||
if write_result.is_ok() {
|
||||
if let Ok(s) = String::from_utf8(w) {
|
||||
write!(f, "{}", s)?;
|
||||
}
|
||||
@@ -427,7 +549,102 @@ pub fn unix_timestamp_to_string(unix_timestamp: UnixTimestamp) -> String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use {super::*, solana_sdk::pubkey::Pubkey};
|
||||
use {
|
||||
super::*,
|
||||
solana_sdk::{
|
||||
message::{v0::LoadedAddresses, Message as LegacyMessage, MessageHeader},
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
},
|
||||
solana_transaction_status::{Reward, RewardType, TransactionStatusMeta},
|
||||
std::io::BufWriter,
|
||||
};
|
||||
|
||||
fn test_keypair() -> Keypair {
|
||||
let secret = ed25519_dalek::SecretKey::from_bytes(&[0u8; 32]).unwrap();
|
||||
let public = ed25519_dalek::PublicKey::from(&secret);
|
||||
let keypair = ed25519_dalek::Keypair { secret, public };
|
||||
Keypair::from_bytes(&keypair.to_bytes()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_transaction() {
|
||||
let keypair = test_keypair();
|
||||
let account_key = Pubkey::new_from_array([1u8; 32]);
|
||||
let transaction = Transaction::new(
|
||||
&[&keypair],
|
||||
LegacyMessage {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 1,
|
||||
num_readonly_signed_accounts: 0,
|
||||
num_readonly_unsigned_accounts: 1,
|
||||
},
|
||||
recent_blockhash: Hash::default(),
|
||||
account_keys: vec![keypair.pubkey(), account_key],
|
||||
instructions: vec![CompiledInstruction::new_from_raw_parts(1, vec![], vec![0])],
|
||||
},
|
||||
Hash::default(),
|
||||
);
|
||||
|
||||
let sigverify_status = CliSignatureVerificationStatus::verify_transaction(&transaction);
|
||||
let meta = TransactionStatusMeta {
|
||||
status: Ok(()),
|
||||
fee: 5000,
|
||||
pre_balances: vec![5000, 10_000],
|
||||
post_balances: vec![0, 9_900],
|
||||
inner_instructions: None,
|
||||
log_messages: Some(vec!["Test message".to_string()]),
|
||||
pre_token_balances: None,
|
||||
post_token_balances: None,
|
||||
rewards: Some(vec![Reward {
|
||||
pubkey: account_key.to_string(),
|
||||
lamports: -100,
|
||||
post_balance: 9_900,
|
||||
reward_type: Some(RewardType::Rent),
|
||||
commission: None,
|
||||
}]),
|
||||
loaded_addresses: LoadedAddresses::default(),
|
||||
};
|
||||
|
||||
let output = {
|
||||
let mut write_buffer = BufWriter::new(Vec::new());
|
||||
write_transaction(
|
||||
&mut write_buffer,
|
||||
&transaction,
|
||||
Some(&meta.into()),
|
||||
"",
|
||||
Some(&sigverify_status),
|
||||
Some(1628633791),
|
||||
CliTimezone::Utc,
|
||||
)
|
||||
.unwrap();
|
||||
let bytes = write_buffer.into_inner().unwrap();
|
||||
String::from_utf8(bytes).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
r#"Block Time: 2021-08-10T22:16:31Z
|
||||
Recent Blockhash: 11111111111111111111111111111111
|
||||
Signature 0: 5pkjrE4VBa3Bu9CMKXgh1U345cT1gGo8QBVRTzHAo6gHeiPae5BTbShP15g6NgqRMNqu8Qrhph1ATmrfC1Ley3rx (pass)
|
||||
Account 0: srw- 4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS (fee payer)
|
||||
Account 1: -r-x 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi
|
||||
Instruction 0
|
||||
Program: 4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi (1)
|
||||
Account 0: 4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS (0)
|
||||
Data: []
|
||||
Status: Ok
|
||||
Fee: ◎0.000005
|
||||
Account 0 balance: ◎0.000005 -> ◎0
|
||||
Account 1 balance: ◎0.00001 -> ◎0.0000099
|
||||
Log Messages:
|
||||
Test message
|
||||
Rewards:
|
||||
Address Type Amount New Balance \0
|
||||
4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi rent -◎0.000000100 ◎0.000009900 \0
|
||||
"#.replace("\\0", "") // replace marker used to subvert trailing whitespace linter on CI
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_labeled_address() {
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2021"
|
||||
name = "solana-cli"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -13,43 +13,44 @@ documentation = "https://docs.rs/solana-cli"
|
||||
bincode = "1.3.3"
|
||||
bs58 = "0.4.0"
|
||||
clap = "2.33.1"
|
||||
criterion-stats = "0.3.0"
|
||||
ctrlc = { version = "3.2.1", features = ["termination"] }
|
||||
console = "0.15.0"
|
||||
const_format = "0.2.22"
|
||||
log = "0.4.14"
|
||||
criterion-stats = "0.3.0"
|
||||
crossbeam-channel = "0.5"
|
||||
ctrlc = { version = "3.2.1", features = ["termination"] }
|
||||
humantime = "2.0.1"
|
||||
log = "0.4.14"
|
||||
num-traits = "0.2"
|
||||
pretty-hex = "0.2.1"
|
||||
reqwest = { version = "0.11.6", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
semver = "1.0.4"
|
||||
serde = "1.0.133"
|
||||
reqwest = { version = "0.11.9", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
semver = "1.0.6"
|
||||
serde = "1.0.136"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.74"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.10.0" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.10.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.10.0" }
|
||||
solana-cli-config = { path = "../cli-config", version = "=1.10.0" }
|
||||
solana-cli-output = { path = "../cli-output", version = "=1.10.0" }
|
||||
solana-client = { path = "../client", version = "=1.10.0" }
|
||||
solana-config-program = { path = "../programs/config", version = "=1.10.0" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.10.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.0" }
|
||||
solana-program-runtime = { path = "../program-runtime", version = "=1.10.0" }
|
||||
solana_rbpf = "=0.2.21"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.0" }
|
||||
solana-version = { path = "../version", version = "=1.10.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.10.0" }
|
||||
serde_json = "1.0.79"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.10.2" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.10.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.10.2" }
|
||||
solana-cli-config = { path = "../cli-config", version = "=1.10.2" }
|
||||
solana-cli-output = { path = "../cli-output", version = "=1.10.2" }
|
||||
solana-client = { path = "../client", version = "=1.10.2" }
|
||||
solana-config-program = { path = "../programs/config", version = "=1.10.2" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.10.2" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.2" }
|
||||
solana-program-runtime = { path = "../program-runtime", version = "=1.10.2" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.2" }
|
||||
solana-version = { path = "../version", version = "=1.10.2" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.10.2" }
|
||||
solana_rbpf = "=0.2.24"
|
||||
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
|
||||
thiserror = "1.0.30"
|
||||
tiny-bip39 = "0.8.2"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-streamer = { path = "../streamer", version = "=1.10.0" }
|
||||
solana-test-validator = { path = "../test-validator", version = "=1.10.0" }
|
||||
tempfile = "3.2.0"
|
||||
solana-streamer = { path = "../streamer", version = "=1.10.2" }
|
||||
solana-test-validator = { path = "../test-validator", version = "=1.10.2" }
|
||||
tempfile = "3.3.0"
|
||||
|
||||
[[bin]]
|
||||
name = "solana"
|
||||
|
@@ -83,12 +83,12 @@ pub enum CliCommand {
|
||||
filter: RpcTransactionLogsFilter,
|
||||
},
|
||||
Ping {
|
||||
lamports: u64,
|
||||
interval: Duration,
|
||||
count: Option<u64>,
|
||||
timeout: Duration,
|
||||
blockhash: Option<Hash>,
|
||||
print_timestamp: bool,
|
||||
additional_fee: Option<u32>,
|
||||
},
|
||||
Rent {
|
||||
data_length: usize,
|
||||
@@ -973,21 +973,21 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
CliCommand::LiveSlots => process_live_slots(config),
|
||||
CliCommand::Logs { filter } => process_logs(config, filter),
|
||||
CliCommand::Ping {
|
||||
lamports,
|
||||
interval,
|
||||
count,
|
||||
timeout,
|
||||
blockhash,
|
||||
print_timestamp,
|
||||
additional_fee,
|
||||
} => process_ping(
|
||||
&rpc_client,
|
||||
config,
|
||||
*lamports,
|
||||
interval,
|
||||
count,
|
||||
timeout,
|
||||
blockhash,
|
||||
*print_timestamp,
|
||||
additional_fee,
|
||||
),
|
||||
CliCommand::Rent {
|
||||
data_length,
|
||||
@@ -1709,7 +1709,7 @@ mod tests {
|
||||
serde_json::{json, Value},
|
||||
solana_client::{
|
||||
blockhash_query,
|
||||
mock_sender::SIGNATURE,
|
||||
mock_sender_for_cli::SIGNATURE,
|
||||
rpc_request::RpcRequest,
|
||||
rpc_response::{Response, RpcResponseContext},
|
||||
},
|
||||
|
@@ -4,7 +4,8 @@ use {
|
||||
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
||||
},
|
||||
clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand},
|
||||
console::{style, Emoji},
|
||||
console::style,
|
||||
crossbeam_channel::unbounded,
|
||||
serde::{Deserialize, Serialize},
|
||||
solana_clap_utils::{
|
||||
input_parsers::*,
|
||||
@@ -15,7 +16,7 @@ use {
|
||||
solana_cli_output::{
|
||||
display::{
|
||||
build_balance_message, format_labeled_address, new_spinner_progress_bar,
|
||||
println_name_value, println_transaction, unix_timestamp_to_string, writeln_name_value,
|
||||
println_transaction, unix_timestamp_to_string, writeln_name_value,
|
||||
},
|
||||
*,
|
||||
},
|
||||
@@ -32,24 +33,26 @@ use {
|
||||
rpc_request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
|
||||
rpc_response::SlotInfo,
|
||||
},
|
||||
solana_program_runtime::compute_budget::ComputeBudget,
|
||||
solana_remote_wallet::remote_wallet::RemoteWalletManager,
|
||||
solana_sdk::{
|
||||
account::from_account,
|
||||
account_utils::StateMut,
|
||||
clock::{self, Clock, Slot},
|
||||
commitment_config::CommitmentConfig,
|
||||
compute_budget::ComputeBudgetInstruction,
|
||||
epoch_schedule::Epoch,
|
||||
hash::Hash,
|
||||
message::Message,
|
||||
native_token::lamports_to_sol,
|
||||
nonce::State as NonceState,
|
||||
pubkey::{self, Pubkey},
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
rpc_port::DEFAULT_RPC_PORT_STR,
|
||||
signature::Signature,
|
||||
slot_history,
|
||||
stake::{self, state::StakeState},
|
||||
system_instruction, system_program,
|
||||
system_instruction,
|
||||
sysvar::{
|
||||
self,
|
||||
slot_history::SlotHistory,
|
||||
@@ -74,9 +77,6 @@ use {
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
static CHECK_MARK: Emoji = Emoji("✅ ", "");
|
||||
static CROSS_MARK: Emoji = Emoji("❌ ", "");
|
||||
|
||||
pub trait ClusterQuerySubCommands {
|
||||
fn cluster_query_subcommands(self) -> Self;
|
||||
}
|
||||
@@ -262,15 +262,6 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.takes_value(false)
|
||||
.help("Print timestamp (unix time + microseconds as in gettimeofday) before each line"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("lamports")
|
||||
.long("lamports")
|
||||
.value_name("NUMBER")
|
||||
.takes_value(true)
|
||||
.default_value("1")
|
||||
.validator(is_amount)
|
||||
.help("Number of lamports to transfer for each transaction"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("timeout")
|
||||
.short("t")
|
||||
@@ -280,6 +271,13 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.default_value("15")
|
||||
.help("Wait up to timeout seconds for transaction confirmation"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("additional_fee")
|
||||
.long("additional-fee")
|
||||
.value_name("NUMBER")
|
||||
.takes_value(true)
|
||||
.help("Request additional-fee for transaction"),
|
||||
)
|
||||
.arg(blockhash_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
@@ -515,7 +513,6 @@ pub fn parse_cluster_ping(
|
||||
default_signer: &DefaultSigner,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let lamports = value_t_or_exit!(matches, "lamports", u64);
|
||||
let interval = Duration::from_secs(value_t_or_exit!(matches, "interval", u64));
|
||||
let count = if matches.is_present("count") {
|
||||
Some(value_t_or_exit!(matches, "count", u64))
|
||||
@@ -525,14 +522,15 @@ pub fn parse_cluster_ping(
|
||||
let timeout = Duration::from_secs(value_t_or_exit!(matches, "timeout", u64));
|
||||
let blockhash = value_of(matches, BLOCKHASH_ARG.name);
|
||||
let print_timestamp = matches.is_present("print_timestamp");
|
||||
let additional_fee = value_of(matches, "additional_fee");
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::Ping {
|
||||
lamports,
|
||||
interval,
|
||||
count,
|
||||
timeout,
|
||||
blockhash,
|
||||
print_timestamp,
|
||||
additional_fee,
|
||||
},
|
||||
signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
|
||||
})
|
||||
@@ -1358,60 +1356,63 @@ pub fn process_get_transaction_count(rpc_client: &RpcClient, _config: &CliConfig
|
||||
pub fn process_ping(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
lamports: u64,
|
||||
interval: &Duration,
|
||||
count: &Option<u64>,
|
||||
timeout: &Duration,
|
||||
fixed_blockhash: &Option<Hash>,
|
||||
print_timestamp: bool,
|
||||
additional_fee: &Option<u32>,
|
||||
) -> ProcessResult {
|
||||
println_name_value("Source Account:", &config.signers[0].pubkey().to_string());
|
||||
println!();
|
||||
|
||||
let (signal_sender, signal_receiver) = std::sync::mpsc::channel();
|
||||
let (signal_sender, signal_receiver) = unbounded();
|
||||
ctrlc::set_handler(move || {
|
||||
let _ = signal_sender.send(());
|
||||
})
|
||||
.expect("Error setting Ctrl-C handler");
|
||||
|
||||
let mut cli_pings = vec![];
|
||||
|
||||
let mut submit_count = 0;
|
||||
let mut confirmed_count = 0;
|
||||
let mut confirmation_time: VecDeque<u64> = VecDeque::with_capacity(1024);
|
||||
|
||||
let mut blockhash = rpc_client.get_latest_blockhash()?;
|
||||
let mut blockhash_transaction_count = 0;
|
||||
let mut lamports = 0;
|
||||
let mut blockhash_acquired = Instant::now();
|
||||
let mut blockhash_from_cluster = false;
|
||||
if let Some(fixed_blockhash) = fixed_blockhash {
|
||||
let blockhash_origin = if *fixed_blockhash != Hash::default() {
|
||||
if *fixed_blockhash != Hash::default() {
|
||||
blockhash = *fixed_blockhash;
|
||||
"supplied from cli arguments"
|
||||
} else {
|
||||
"fetched from cluster"
|
||||
};
|
||||
println!(
|
||||
"Fixed blockhash is used: {} ({})",
|
||||
blockhash, blockhash_origin
|
||||
);
|
||||
blockhash_from_cluster = true;
|
||||
}
|
||||
}
|
||||
|
||||
'mainloop: for seq in 0..count.unwrap_or(std::u64::MAX) {
|
||||
let now = Instant::now();
|
||||
if fixed_blockhash.is_none() && now.duration_since(blockhash_acquired).as_secs() > 60 {
|
||||
// Fetch a new blockhash every minute
|
||||
let new_blockhash = rpc_client.get_new_latest_blockhash(&blockhash)?;
|
||||
blockhash = new_blockhash;
|
||||
blockhash_transaction_count = 0;
|
||||
lamports = 0;
|
||||
blockhash_acquired = Instant::now();
|
||||
}
|
||||
|
||||
let seed =
|
||||
&format!("{}{}", blockhash_transaction_count, blockhash)[0..pubkey::MAX_SEED_LEN];
|
||||
let to = Pubkey::create_with_seed(&config.signers[0].pubkey(), seed, &system_program::id())
|
||||
.unwrap();
|
||||
blockhash_transaction_count += 1;
|
||||
let to = config.signers[0].pubkey();
|
||||
lamports += 1;
|
||||
|
||||
let build_message = |lamports| {
|
||||
let ix = system_instruction::transfer(&config.signers[0].pubkey(), &to, lamports);
|
||||
Message::new(&[ix], Some(&config.signers[0].pubkey()))
|
||||
let mut ixs = vec![system_instruction::transfer(
|
||||
&config.signers[0].pubkey(),
|
||||
&to,
|
||||
lamports,
|
||||
)];
|
||||
if let Some(additional_fee) = additional_fee {
|
||||
ixs.push(ComputeBudgetInstruction::request_units(
|
||||
ComputeBudget::new(false).max_units as u32,
|
||||
*additional_fee,
|
||||
));
|
||||
}
|
||||
Message::new(&ixs, Some(&config.signers[0].pubkey()))
|
||||
};
|
||||
let (message, _) = resolve_spend_tx_and_check_account_balance(
|
||||
rpc_client,
|
||||
@@ -1430,11 +1431,7 @@ pub fn process_ping(
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_micros();
|
||||
if print_timestamp {
|
||||
format!("[{}.{:06}] ", micros / 1_000_000, micros % 1_000_000)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
format!("[{}.{:06}] ", micros / 1_000_000, micros % 1_000_000)
|
||||
};
|
||||
|
||||
match rpc_client.send_transaction(&tx) {
|
||||
@@ -1448,35 +1445,51 @@ pub fn process_ping(
|
||||
Ok(()) => {
|
||||
let elapsed_time_millis = elapsed_time.as_millis() as u64;
|
||||
confirmation_time.push_back(elapsed_time_millis);
|
||||
println!(
|
||||
"{}{}{} lamport(s) transferred: seq={:<3} time={:>4}ms signature={}",
|
||||
timestamp(),
|
||||
CHECK_MARK, lamports, seq, elapsed_time_millis, signature
|
||||
);
|
||||
let cli_ping_data = CliPingData {
|
||||
success: true,
|
||||
signature: Some(signature.to_string()),
|
||||
ms: Some(elapsed_time_millis),
|
||||
error: None,
|
||||
timestamp: timestamp(),
|
||||
print_timestamp,
|
||||
sequence: seq,
|
||||
lamports: Some(lamports),
|
||||
};
|
||||
eprint!("{}", cli_ping_data);
|
||||
cli_pings.push(cli_ping_data);
|
||||
confirmed_count += 1;
|
||||
}
|
||||
Err(err) => {
|
||||
println!(
|
||||
"{}{}Transaction failed: seq={:<3} error={:?} signature={}",
|
||||
timestamp(),
|
||||
CROSS_MARK,
|
||||
seq,
|
||||
err,
|
||||
signature
|
||||
);
|
||||
let cli_ping_data = CliPingData {
|
||||
success: false,
|
||||
signature: Some(signature.to_string()),
|
||||
ms: None,
|
||||
error: Some(err.to_string()),
|
||||
timestamp: timestamp(),
|
||||
print_timestamp,
|
||||
sequence: seq,
|
||||
lamports: None,
|
||||
};
|
||||
eprint!("{}", cli_ping_data);
|
||||
cli_pings.push(cli_ping_data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if elapsed_time >= *timeout {
|
||||
println!(
|
||||
"{}{}Confirmation timeout: seq={:<3} signature={}",
|
||||
timestamp(),
|
||||
CROSS_MARK,
|
||||
seq,
|
||||
signature
|
||||
);
|
||||
let cli_ping_data = CliPingData {
|
||||
success: false,
|
||||
signature: Some(signature.to_string()),
|
||||
ms: None,
|
||||
error: None,
|
||||
timestamp: timestamp(),
|
||||
print_timestamp,
|
||||
sequence: seq,
|
||||
lamports: None,
|
||||
};
|
||||
eprint!("{}", cli_ping_data);
|
||||
cli_pings.push(cli_ping_data);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1490,13 +1503,18 @@ pub fn process_ping(
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
println!(
|
||||
"{}{}Submit failed: seq={:<3} error={:?}",
|
||||
timestamp(),
|
||||
CROSS_MARK,
|
||||
seq,
|
||||
err
|
||||
);
|
||||
let cli_ping_data = CliPingData {
|
||||
success: false,
|
||||
signature: None,
|
||||
ms: None,
|
||||
error: Some(err.to_string()),
|
||||
timestamp: timestamp(),
|
||||
print_timestamp,
|
||||
sequence: seq,
|
||||
lamports: None,
|
||||
};
|
||||
eprint!("{}", cli_ping_data);
|
||||
cli_pings.push(cli_ping_data);
|
||||
}
|
||||
}
|
||||
submit_count += 1;
|
||||
@@ -1506,28 +1524,34 @@ pub fn process_ping(
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("--- transaction statistics ---");
|
||||
println!(
|
||||
"{} transactions submitted, {} transactions confirmed, {:.1}% transaction loss",
|
||||
submit_count,
|
||||
confirmed_count,
|
||||
(100. - f64::from(confirmed_count) / f64::from(submit_count) * 100.)
|
||||
);
|
||||
if !confirmation_time.is_empty() {
|
||||
let transaction_stats = CliPingTxStats {
|
||||
num_transactions: submit_count,
|
||||
num_transaction_confirmed: confirmed_count,
|
||||
};
|
||||
let confirmation_stats = if !confirmation_time.is_empty() {
|
||||
let samples: Vec<f64> = confirmation_time.iter().map(|t| *t as f64).collect();
|
||||
let dist = criterion_stats::Distribution::from(samples.into_boxed_slice());
|
||||
let mean = dist.mean();
|
||||
println!(
|
||||
"confirmation min/mean/max/stddev = {:.0}/{:.0}/{:.0}/{:.0} ms",
|
||||
dist.min(),
|
||||
Some(CliPingConfirmationStats {
|
||||
min: dist.min(),
|
||||
mean,
|
||||
dist.max(),
|
||||
dist.std_dev(Some(mean))
|
||||
);
|
||||
}
|
||||
max: dist.max(),
|
||||
std_dev: dist.std_dev(Some(mean)),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok("".to_string())
|
||||
let cli_ping = CliPing {
|
||||
source_pubkey: config.signers[0].pubkey().to_string(),
|
||||
fixed_blockhash: fixed_blockhash.map(|_| blockhash.to_string()),
|
||||
blockhash_from_cluster,
|
||||
pings: cli_pings,
|
||||
transaction_stats,
|
||||
confirmation_stats,
|
||||
};
|
||||
|
||||
Ok(config.output_format.formatted_string(&cli_ping))
|
||||
}
|
||||
|
||||
pub fn parse_logs(
|
||||
@@ -2018,6 +2042,7 @@ pub fn process_transaction_history(
|
||||
RpcTransactionConfig {
|
||||
encoding: Some(UiTransactionEncoding::Base64),
|
||||
commitment: Some(CommitmentConfig::confirmed()),
|
||||
max_supported_transaction_version: None,
|
||||
},
|
||||
) {
|
||||
Ok(confirmed_transaction) => {
|
||||
@@ -2027,7 +2052,7 @@ pub fn process_transaction_history(
|
||||
.transaction
|
||||
.decode()
|
||||
.expect("Successful decode"),
|
||||
&confirmed_transaction.transaction.meta,
|
||||
confirmed_transaction.transaction.meta.as_ref(),
|
||||
" ",
|
||||
None,
|
||||
None,
|
||||
@@ -2128,7 +2153,7 @@ pub fn process_calculate_rent(
|
||||
timing::years_as_slots(1.0, &seconds_per_tick, clock::DEFAULT_TICKS_PER_SLOT);
|
||||
let slots_per_epoch = epoch_schedule.slots_per_epoch as f64;
|
||||
let years_per_epoch = slots_per_epoch / slots_per_year;
|
||||
let (lamports_per_epoch, _) = rent.due(0, data_length, years_per_epoch);
|
||||
let lamports_per_epoch = rent.due(0, data_length, years_per_epoch).lamports();
|
||||
let cli_rent_calculation = CliRentCalculation {
|
||||
lamports_per_byte_year: rent.lamports_per_byte_year,
|
||||
lamports_per_epoch,
|
||||
@@ -2304,7 +2329,6 @@ mod tests {
|
||||
parse_command(&test_ping, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Ping {
|
||||
lamports: 1,
|
||||
interval: Duration::from_secs(1),
|
||||
count: Some(2),
|
||||
timeout: Duration::from_secs(3),
|
||||
@@ -2312,6 +2336,7 @@ mod tests {
|
||||
Hash::from_str("4CCNp28j6AhGq7PkjPDP4wbQWBS8LLbQin2xV5n8frKX").unwrap()
|
||||
),
|
||||
print_timestamp: true,
|
||||
additional_fee: None,
|
||||
},
|
||||
signers: vec![default_keypair.into()],
|
||||
}
|
||||
|
@@ -334,9 +334,17 @@ pub fn check_nonce_account(
|
||||
match state_from_account(nonce_account)? {
|
||||
State::Initialized(ref data) => {
|
||||
if &data.blockhash != nonce_hash {
|
||||
Err(Error::InvalidHash.into())
|
||||
Err(Error::InvalidHash {
|
||||
provided: *nonce_hash,
|
||||
expected: data.blockhash,
|
||||
}
|
||||
.into())
|
||||
} else if nonce_authority != &data.authority {
|
||||
Err(Error::InvalidAuthority.into())
|
||||
Err(Error::InvalidAuthority {
|
||||
provided: *nonce_authority,
|
||||
expected: data.authority,
|
||||
}
|
||||
.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -946,15 +954,22 @@ mod tests {
|
||||
hash(b"invalid"),
|
||||
0,
|
||||
)));
|
||||
let invalid_hash = Account::new_data(1, &data, &system_program::ID);
|
||||
let invalid_hash = Account::new_data(1, &data, &system_program::ID).unwrap();
|
||||
if let CliError::InvalidNonce(err) =
|
||||
check_nonce_account(&invalid_hash.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
|
||||
check_nonce_account(&invalid_hash, &nonce_pubkey, &blockhash).unwrap_err()
|
||||
{
|
||||
assert_eq!(err, Error::InvalidHash,);
|
||||
assert_eq!(
|
||||
err,
|
||||
Error::InvalidHash {
|
||||
provided: blockhash,
|
||||
expected: hash(b"invalid"),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let new_nonce_authority = solana_sdk::pubkey::new_rand();
|
||||
let data = Versions::new_current(State::Initialized(nonce::state::Data::new(
|
||||
solana_sdk::pubkey::new_rand(),
|
||||
new_nonce_authority,
|
||||
blockhash,
|
||||
0,
|
||||
)));
|
||||
@@ -962,7 +977,13 @@ mod tests {
|
||||
if let CliError::InvalidNonce(err) =
|
||||
check_nonce_account(&invalid_authority.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
|
||||
{
|
||||
assert_eq!(err, Error::InvalidAuthority,);
|
||||
assert_eq!(
|
||||
err,
|
||||
Error::InvalidAuthority {
|
||||
provided: nonce_pubkey,
|
||||
expected: new_nonce_authority,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let data = Versions::new_current(State::Uninitialized);
|
||||
|
@@ -1991,7 +1991,7 @@ fn read_and_verify_elf(program_location: &str) -> Result<Vec<u8>, Box<dyn std::e
|
||||
let mut program_data = Vec::new();
|
||||
file.read_to_end(&mut program_data)
|
||||
.map_err(|err| format!("Unable to read program file: {}", err))?;
|
||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1);
|
||||
let mut transaction_context = TransactionContext::new(Vec::new(), 1, 1);
|
||||
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
|
||||
|
||||
// Verify the program
|
||||
@@ -1999,10 +1999,7 @@ fn read_and_verify_elf(program_location: &str) -> Result<Vec<u8>, Box<dyn std::e
|
||||
&program_data,
|
||||
Some(verifier::check),
|
||||
Config {
|
||||
reject_unresolved_syscalls: true,
|
||||
verify_mul64_imm_nonzero: false,
|
||||
verify_shift32_imm: true,
|
||||
reject_section_virtual_address_file_offset_mismatch: true,
|
||||
reject_broken_elfs: true,
|
||||
..Config::default()
|
||||
},
|
||||
register_syscalls(&mut invoke_context).unwrap(),
|
||||
|
@@ -1384,7 +1384,13 @@ pub fn process_stake_authorize(
|
||||
if let Some(authorized) = authorized {
|
||||
match authorization_type {
|
||||
StakeAuthorize::Staker => {
|
||||
check_current_authority(&authorized.staker, &authority.pubkey())?;
|
||||
// first check authorized withdrawer
|
||||
check_current_authority(&authorized.withdrawer, &authority.pubkey())
|
||||
.or_else(|_| {
|
||||
// ...then check authorized staker. If neither matches, error will
|
||||
// print the stake key as `expected`
|
||||
check_current_authority(&authorized.staker, &authority.pubkey())
|
||||
})?;
|
||||
}
|
||||
StakeAuthorize::Withdrawer => {
|
||||
check_current_authority(&authorized.withdrawer, &authority.pubkey())?;
|
||||
|
@@ -1,23 +1,29 @@
|
||||
use {
|
||||
solana_client::rpc_client::RpcClient,
|
||||
solana_sdk::{clock::DEFAULT_MS_PER_SLOT, commitment_config::CommitmentConfig, pubkey::Pubkey},
|
||||
solana_sdk::{clock::DEFAULT_MS_PER_SLOT, commitment_config::CommitmentConfig},
|
||||
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::processed())
|
||||
.unwrap()
|
||||
.value;
|
||||
if balance == expected_balance {
|
||||
return;
|
||||
}
|
||||
if tries == 4 {
|
||||
assert_eq!(balance, expected_balance);
|
||||
}
|
||||
sleep(Duration::from_millis(500));
|
||||
});
|
||||
#[macro_export]
|
||||
macro_rules! check_balance {
|
||||
($expected_balance:expr, $client:expr, $pubkey:expr) => {
|
||||
(0..5).for_each(|tries| {
|
||||
let balance = $client
|
||||
.get_balance_with_commitment($pubkey, CommitmentConfig::processed())
|
||||
.unwrap()
|
||||
.value;
|
||||
if balance == $expected_balance {
|
||||
return;
|
||||
}
|
||||
if tries == 4 {
|
||||
assert_eq!(balance, $expected_balance);
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
});
|
||||
};
|
||||
($expected_balance:expr, $client:expr, $pubkey:expr,) => {
|
||||
check_balance!($expected_balance, $client, $pubkey)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn check_ready(rpc_client: &RpcClient) {
|
||||
|
@@ -35,7 +35,8 @@ use {
|
||||
transaction::Transaction,
|
||||
},
|
||||
solana_vote_program::{
|
||||
vote_instruction::{self, withdraw, VoteError},
|
||||
vote_error::VoteError,
|
||||
vote_instruction::{self, withdraw},
|
||||
vote_state::{VoteAuthorize, VoteInit, VoteState},
|
||||
},
|
||||
std::sync::Arc,
|
||||
|
@@ -39,7 +39,9 @@ use {
|
||||
system_program,
|
||||
transaction::Transaction,
|
||||
},
|
||||
solana_transaction_status::{Encodable, EncodedTransaction, UiTransactionEncoding},
|
||||
solana_transaction_status::{
|
||||
Encodable, EncodedTransaction, TransactionBinaryEncoding, UiTransactionEncoding,
|
||||
},
|
||||
std::{fmt::Write as FmtWrite, fs::File, io::Write, sync::Arc},
|
||||
};
|
||||
|
||||
@@ -189,7 +191,7 @@ impl WalletSubCommands for App<'_, '_> {
|
||||
Arg::with_name("encoding")
|
||||
.index(2)
|
||||
.value_name("ENCODING")
|
||||
.possible_values(&["base58", "base64"]) // Subset of `UiTransactionEncoding` enum
|
||||
.possible_values(&["base58", "base64"]) // Variants of `TransactionBinaryEncoding` enum
|
||||
.default_value("base58")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
@@ -273,11 +275,14 @@ impl WalletSubCommands for App<'_, '_> {
|
||||
}
|
||||
|
||||
fn resolve_derived_address_program_id(matches: &ArgMatches<'_>, arg_name: &str) -> Option<Pubkey> {
|
||||
matches.value_of(arg_name).and_then(|v| match v {
|
||||
"NONCE" => Some(system_program::id()),
|
||||
"STAKE" => Some(stake::program::id()),
|
||||
"VOTE" => Some(solana_vote_program::id()),
|
||||
_ => pubkey_of(matches, arg_name),
|
||||
matches.value_of(arg_name).and_then(|v| {
|
||||
let upper = v.to_ascii_uppercase();
|
||||
match upper.as_str() {
|
||||
"NONCE" | "SYSTEM" => Some(system_program::id()),
|
||||
"STAKE" => Some(stake::program::id()),
|
||||
"VOTE" => Some(solana_vote_program::id()),
|
||||
_ => pubkey_of(matches, arg_name),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -338,13 +343,13 @@ pub fn parse_balance(
|
||||
|
||||
pub fn parse_decode_transaction(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||
let blob = value_t_or_exit!(matches, "transaction", String);
|
||||
let encoding = match matches.value_of("encoding").unwrap() {
|
||||
"base58" => UiTransactionEncoding::Base58,
|
||||
"base64" => UiTransactionEncoding::Base64,
|
||||
let binary_encoding = match matches.value_of("encoding").unwrap() {
|
||||
"base58" => TransactionBinaryEncoding::Base58,
|
||||
"base64" => TransactionBinaryEncoding::Base64,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let encoded_transaction = EncodedTransaction::Binary(blob, encoding);
|
||||
let encoded_transaction = EncodedTransaction::Binary(blob, binary_encoding);
|
||||
if let Some(transaction) = encoded_transaction.decode() {
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::DecodeTransaction(transaction),
|
||||
@@ -556,6 +561,7 @@ pub fn process_confirm(
|
||||
RpcTransactionConfig {
|
||||
encoding: Some(UiTransactionEncoding::Base64),
|
||||
commitment: Some(CommitmentConfig::confirmed()),
|
||||
max_supported_transaction_version: None,
|
||||
},
|
||||
) {
|
||||
Ok(confirmed_transaction) => {
|
||||
|
@@ -1,8 +1,10 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use {
|
||||
solana_cli::{
|
||||
check_balance,
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
spend_utils::SpendAmount,
|
||||
test_utils::{check_ready, check_recent_balance},
|
||||
test_utils::check_ready,
|
||||
},
|
||||
solana_cli_output::{parse_sign_only_reply_string, OutputFormat},
|
||||
solana_client::{
|
||||
@@ -14,6 +16,7 @@ use {
|
||||
solana_sdk::{
|
||||
commitment_config::CommitmentConfig,
|
||||
hash::Hash,
|
||||
native_token::sol_to_lamports,
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
system_program,
|
||||
@@ -73,10 +76,14 @@ fn full_battery_tests(
|
||||
&rpc_client,
|
||||
&config_payer,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
2000,
|
||||
sol_to_lamports(2000.0),
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(2000, &rpc_client, &config_payer.signers[0].pubkey());
|
||||
check_balance!(
|
||||
sol_to_lamports(2000.0),
|
||||
&rpc_client,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
);
|
||||
|
||||
let mut config_nonce = CliConfig::recent_for_tests();
|
||||
config_nonce.json_rpc_url = json_rpc_url;
|
||||
@@ -108,12 +115,16 @@ fn full_battery_tests(
|
||||
seed,
|
||||
nonce_authority: optional_authority,
|
||||
memo: None,
|
||||
amount: SpendAmount::Some(1000),
|
||||
amount: SpendAmount::Some(sol_to_lamports(1000.0)),
|
||||
};
|
||||
|
||||
process_command(&config_payer).unwrap();
|
||||
check_recent_balance(1000, &rpc_client, &config_payer.signers[0].pubkey());
|
||||
check_recent_balance(1000, &rpc_client, &nonce_account);
|
||||
check_balance!(
|
||||
sol_to_lamports(1000.0),
|
||||
&rpc_client,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
);
|
||||
check_balance!(sol_to_lamports(1000.0), &rpc_client, &nonce_account);
|
||||
|
||||
// Get nonce
|
||||
config_payer.signers.pop();
|
||||
@@ -161,12 +172,16 @@ fn full_battery_tests(
|
||||
nonce_authority: index,
|
||||
memo: None,
|
||||
destination_account_pubkey: payee_pubkey,
|
||||
lamports: 100,
|
||||
lamports: sol_to_lamports(100.0),
|
||||
};
|
||||
process_command(&config_payer).unwrap();
|
||||
check_recent_balance(1000, &rpc_client, &config_payer.signers[0].pubkey());
|
||||
check_recent_balance(900, &rpc_client, &nonce_account);
|
||||
check_recent_balance(100, &rpc_client, &payee_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(1000.0),
|
||||
&rpc_client,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
);
|
||||
check_balance!(sol_to_lamports(900.0), &rpc_client, &nonce_account);
|
||||
check_balance!(sol_to_lamports(100.0), &rpc_client, &payee_pubkey);
|
||||
|
||||
// Show nonce account
|
||||
config_payer.command = CliCommand::ShowNonceAccount {
|
||||
@@ -208,17 +223,22 @@ fn full_battery_tests(
|
||||
nonce_authority: 1,
|
||||
memo: None,
|
||||
destination_account_pubkey: payee_pubkey,
|
||||
lamports: 100,
|
||||
lamports: sol_to_lamports(100.0),
|
||||
};
|
||||
process_command(&config_payer).unwrap();
|
||||
check_recent_balance(1000, &rpc_client, &config_payer.signers[0].pubkey());
|
||||
check_recent_balance(800, &rpc_client, &nonce_account);
|
||||
check_recent_balance(200, &rpc_client, &payee_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(1000.0),
|
||||
&rpc_client,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
);
|
||||
check_balance!(sol_to_lamports(800.0), &rpc_client, &nonce_account);
|
||||
check_balance!(sol_to_lamports(200.0), &rpc_client, &payee_pubkey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::redundant_closure)]
|
||||
fn test_create_account_with_seed() {
|
||||
const ONE_SIG_FEE: f64 = 0.000005;
|
||||
solana_logger::setup();
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
@@ -241,19 +261,27 @@ fn test_create_account_with_seed() {
|
||||
&rpc_client,
|
||||
&CliConfig::recent_for_tests(),
|
||||
&offline_nonce_authority_signer.pubkey(),
|
||||
42,
|
||||
sol_to_lamports(42.0),
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&CliConfig::recent_for_tests(),
|
||||
&online_nonce_creator_signer.pubkey(),
|
||||
4242,
|
||||
sol_to_lamports(4242.0),
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(42, &rpc_client, &offline_nonce_authority_signer.pubkey());
|
||||
check_recent_balance(4242, &rpc_client, &online_nonce_creator_signer.pubkey());
|
||||
check_recent_balance(0, &rpc_client, &to_address);
|
||||
check_balance!(
|
||||
sol_to_lamports(42.0),
|
||||
&rpc_client,
|
||||
&offline_nonce_authority_signer.pubkey(),
|
||||
);
|
||||
check_balance!(
|
||||
sol_to_lamports(4242.0),
|
||||
&rpc_client,
|
||||
&online_nonce_creator_signer.pubkey(),
|
||||
);
|
||||
check_balance!(0, &rpc_client, &to_address);
|
||||
|
||||
check_ready(&rpc_client);
|
||||
|
||||
@@ -263,7 +291,7 @@ fn test_create_account_with_seed() {
|
||||
let seed = authority_pubkey.to_string()[0..32].to_string();
|
||||
let nonce_address =
|
||||
Pubkey::create_with_seed(&creator_pubkey, &seed, &system_program::id()).unwrap();
|
||||
check_recent_balance(0, &rpc_client, &nonce_address);
|
||||
check_balance!(0, &rpc_client, &nonce_address);
|
||||
|
||||
let mut creator_config = CliConfig::recent_for_tests();
|
||||
creator_config.json_rpc_url = test_validator.rpc_url();
|
||||
@@ -273,13 +301,21 @@ fn test_create_account_with_seed() {
|
||||
seed: Some(seed),
|
||||
nonce_authority: Some(authority_pubkey),
|
||||
memo: None,
|
||||
amount: SpendAmount::Some(241),
|
||||
amount: SpendAmount::Some(sol_to_lamports(241.0)),
|
||||
};
|
||||
process_command(&creator_config).unwrap();
|
||||
check_recent_balance(241, &rpc_client, &nonce_address);
|
||||
check_recent_balance(42, &rpc_client, &offline_nonce_authority_signer.pubkey());
|
||||
check_recent_balance(4000, &rpc_client, &online_nonce_creator_signer.pubkey());
|
||||
check_recent_balance(0, &rpc_client, &to_address);
|
||||
check_balance!(sol_to_lamports(241.0), &rpc_client, &nonce_address);
|
||||
check_balance!(
|
||||
sol_to_lamports(42.0),
|
||||
&rpc_client,
|
||||
&offline_nonce_authority_signer.pubkey(),
|
||||
);
|
||||
check_balance!(
|
||||
sol_to_lamports(4001.0 - ONE_SIG_FEE),
|
||||
&rpc_client,
|
||||
&online_nonce_creator_signer.pubkey(),
|
||||
);
|
||||
check_balance!(0, &rpc_client, &to_address);
|
||||
|
||||
// Fetch nonce hash
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
@@ -299,7 +335,7 @@ fn test_create_account_with_seed() {
|
||||
authority_config.command = CliCommand::ClusterVersion;
|
||||
process_command(&authority_config).unwrap_err();
|
||||
authority_config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(10),
|
||||
amount: SpendAmount::Some(sol_to_lamports(10.0)),
|
||||
to: to_address,
|
||||
from: 0,
|
||||
sign_only: true,
|
||||
@@ -325,7 +361,7 @@ fn test_create_account_with_seed() {
|
||||
submit_config.json_rpc_url = test_validator.rpc_url();
|
||||
submit_config.signers = vec![&authority_presigner];
|
||||
submit_config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(10),
|
||||
amount: SpendAmount::Some(sol_to_lamports(10.0)),
|
||||
to: to_address,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
@@ -344,8 +380,16 @@ fn test_create_account_with_seed() {
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&submit_config).unwrap();
|
||||
check_recent_balance(241, &rpc_client, &nonce_address);
|
||||
check_recent_balance(31, &rpc_client, &offline_nonce_authority_signer.pubkey());
|
||||
check_recent_balance(4000, &rpc_client, &online_nonce_creator_signer.pubkey());
|
||||
check_recent_balance(10, &rpc_client, &to_address);
|
||||
check_balance!(sol_to_lamports(241.0), &rpc_client, &nonce_address);
|
||||
check_balance!(
|
||||
sol_to_lamports(32.0 - ONE_SIG_FEE),
|
||||
&rpc_client,
|
||||
&offline_nonce_authority_signer.pubkey(),
|
||||
);
|
||||
check_balance!(
|
||||
sol_to_lamports(4001.0 - ONE_SIG_FEE),
|
||||
&rpc_client,
|
||||
&online_nonce_creator_signer.pubkey(),
|
||||
);
|
||||
check_balance!(sol_to_lamports(10.0), &rpc_client, &to_address);
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use {
|
||||
serde_json::Value,
|
||||
solana_cli::{
|
||||
@@ -77,7 +78,7 @@ fn test_cli_program_deploy_non_upgradeable() {
|
||||
assert_eq!(account0.lamports, minimum_balance_for_rent_exemption);
|
||||
assert_eq!(account0.owner, bpf_loader::id());
|
||||
assert!(account0.executable);
|
||||
let mut file = File::open(noop_path.to_str().unwrap().to_string()).unwrap();
|
||||
let mut file = File::open(noop_path.to_str().unwrap()).unwrap();
|
||||
let mut elf = Vec::new();
|
||||
file.read_to_end(&mut elf).unwrap();
|
||||
assert_eq!(account0.data, elf);
|
||||
|
@@ -1,9 +1,11 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use {
|
||||
solana_cli::cli::{process_command, CliCommand, CliConfig},
|
||||
solana_client::rpc_client::RpcClient,
|
||||
solana_faucet::faucet::run_local_faucet,
|
||||
solana_sdk::{
|
||||
commitment_config::CommitmentConfig,
|
||||
native_token::sol_to_lamports,
|
||||
signature::{Keypair, Signer},
|
||||
},
|
||||
solana_streamer::socket::SocketAddrSpace,
|
||||
@@ -22,7 +24,7 @@ fn test_cli_request_airdrop() {
|
||||
bob_config.json_rpc_url = test_validator.rpc_url();
|
||||
bob_config.command = CliCommand::Airdrop {
|
||||
pubkey: None,
|
||||
lamports: 50,
|
||||
lamports: sol_to_lamports(50.0),
|
||||
};
|
||||
let keypair = Keypair::new();
|
||||
bob_config.signers = vec![&keypair];
|
||||
@@ -36,5 +38,5 @@ fn test_cli_request_airdrop() {
|
||||
let balance = rpc_client
|
||||
.get_balance(&bob_config.signers[0].pubkey())
|
||||
.unwrap();
|
||||
assert_eq!(balance, 50);
|
||||
assert_eq!(balance, sol_to_lamports(50.0));
|
||||
}
|
||||
|
@@ -1,10 +1,12 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#![allow(clippy::redundant_closure)]
|
||||
use {
|
||||
solana_cli::{
|
||||
check_balance,
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
spend_utils::SpendAmount,
|
||||
stake::StakeAuthorizationIndexed,
|
||||
test_utils::{check_ready, check_recent_balance},
|
||||
test_utils::check_ready,
|
||||
},
|
||||
solana_cli_output::{parse_sign_only_reply_string, OutputFormat},
|
||||
solana_client::{
|
||||
@@ -16,6 +18,7 @@ use {
|
||||
solana_sdk::{
|
||||
account_utils::StateMut,
|
||||
commitment_config::CommitmentConfig,
|
||||
fee::FeeStructure,
|
||||
nonce::State as NonceState,
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
@@ -150,7 +153,7 @@ fn test_seed_stake_delegation_and_deactivation() {
|
||||
100_000,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
check_balance!(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
|
||||
let stake_address = Pubkey::create_with_seed(
|
||||
&config_validator.signers[0].pubkey(),
|
||||
@@ -239,7 +242,7 @@ fn test_stake_delegation_and_deactivation() {
|
||||
100_000,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
check_balance!(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
|
||||
// Create stake account
|
||||
config_validator.signers.push(&stake_keypair);
|
||||
@@ -333,7 +336,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
100_000,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
check_balance!(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
@@ -342,7 +345,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
100_000,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &config_offline.signers[0].pubkey());
|
||||
check_balance!(100_000, &rpc_client, &config_offline.signers[0].pubkey());
|
||||
|
||||
// Create stake account
|
||||
config_validator.signers.push(&stake_keypair);
|
||||
@@ -874,14 +877,15 @@ fn test_stake_authorize() {
|
||||
#[test]
|
||||
fn test_stake_authorize_with_fee_payer() {
|
||||
solana_logger::setup();
|
||||
const SIG_FEE: u64 = 42;
|
||||
let fee_one_sig = FeeStructure::default().get_max_fee(1, 0);
|
||||
let fee_two_sig = FeeStructure::default().get_max_fee(2, 0);
|
||||
|
||||
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,
|
||||
SIG_FEE,
|
||||
1,
|
||||
Some(faucet_addr),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
@@ -910,14 +914,14 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
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, &config, &default_pubkey, 5_000_000).unwrap();
|
||||
check_balance!(5_000_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
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, &config_payer, &payer_pubkey, 5_000_000).unwrap();
|
||||
check_balance!(5_000_000, &rpc_client, &payer_pubkey);
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 5_000_000).unwrap();
|
||||
check_balance!(5_000_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
check_ready(&rpc_client);
|
||||
|
||||
@@ -932,7 +936,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
amount: SpendAmount::Some(1_000_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
@@ -943,8 +947,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
from: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
// `config` balance should be 50,000 - 1 stake account sig - 1 fee sig
|
||||
check_recent_balance(50_000 - SIG_FEE - SIG_FEE, &rpc_client, &default_pubkey);
|
||||
check_balance!(4_000_000 - fee_two_sig, &rpc_client, &default_pubkey);
|
||||
|
||||
// Assign authority with separate fee payer
|
||||
config.signers = vec![&default_signer, &payer_keypair];
|
||||
@@ -968,10 +971,10 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
// `config` balance has not changed, despite submitting the TX
|
||||
check_recent_balance(50_000 - SIG_FEE - SIG_FEE, &rpc_client, &default_pubkey);
|
||||
check_balance!(4_000_000 - fee_two_sig, &rpc_client, &default_pubkey);
|
||||
// `config_payer` however has paid `config`'s authority sig
|
||||
// and `config_payer`'s fee sig
|
||||
check_recent_balance(100_000 - SIG_FEE - SIG_FEE, &rpc_client, &payer_pubkey);
|
||||
check_balance!(5_000_000 - fee_two_sig, &rpc_client, &payer_pubkey);
|
||||
|
||||
// Assign authority with offline fee payer
|
||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||
@@ -1019,10 +1022,10 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
// `config`'s balance again has not changed
|
||||
check_recent_balance(50_000 - SIG_FEE - SIG_FEE, &rpc_client, &default_pubkey);
|
||||
check_balance!(4_000_000 - fee_two_sig, &rpc_client, &default_pubkey);
|
||||
// `config_offline` however has paid 1 sig due to being both authority
|
||||
// and fee payer
|
||||
check_recent_balance(100_000 - SIG_FEE, &rpc_client, &offline_pubkey);
|
||||
check_balance!(5_000_000 - fee_one_sig, &rpc_client, &offline_pubkey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1056,12 +1059,17 @@ fn test_stake_split() {
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
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,
|
||||
&config,
|
||||
&config.signers[0].pubkey(),
|
||||
50_000_000,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance!(50_000_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 1_000_000).unwrap();
|
||||
check_balance!(1_000_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create stake account, identity is authority
|
||||
let minimum_stake_balance = rpc_client
|
||||
@@ -1088,7 +1096,7 @@ fn test_stake_split() {
|
||||
from: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(
|
||||
check_balance!(
|
||||
10 * minimum_stake_balance,
|
||||
&rpc_client,
|
||||
&stake_account_pubkey,
|
||||
@@ -1108,7 +1116,7 @@ fn test_stake_split() {
|
||||
amount: SpendAmount::Some(minimum_nonce_balance),
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(minimum_nonce_balance, &rpc_client, &nonce_account.pubkey());
|
||||
check_balance!(minimum_nonce_balance, &rpc_client, &nonce_account.pubkey());
|
||||
|
||||
// Fetch nonce hash
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
@@ -1122,7 +1130,7 @@ fn test_stake_split() {
|
||||
|
||||
// Nonced offline split
|
||||
let split_account = keypair_from_seed(&[2u8; 32]).unwrap();
|
||||
check_recent_balance(0, &rpc_client, &split_account.pubkey());
|
||||
check_balance!(0, &rpc_client, &split_account.pubkey());
|
||||
config_offline.signers.push(&split_account);
|
||||
config_offline.command = CliCommand::SplitStake {
|
||||
stake_account_pubkey,
|
||||
@@ -1162,12 +1170,12 @@ fn test_stake_split() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(
|
||||
check_balance!(
|
||||
8 * minimum_stake_balance,
|
||||
&rpc_client,
|
||||
&stake_account_pubkey,
|
||||
);
|
||||
check_recent_balance(
|
||||
check_balance!(
|
||||
2 * minimum_stake_balance,
|
||||
&rpc_client,
|
||||
&split_account.pubkey(),
|
||||
@@ -1205,12 +1213,12 @@ fn test_stake_set_lockup() {
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 500_000)
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 5_000_000)
|
||||
.unwrap();
|
||||
check_recent_balance(500_000, &rpc_client, &config.signers[0].pubkey());
|
||||
check_balance!(5_000_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 1_000_000).unwrap();
|
||||
check_balance!(1_000_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create stake account, identity is authority
|
||||
let minimum_stake_balance = rpc_client
|
||||
@@ -1244,7 +1252,12 @@ fn test_stake_set_lockup() {
|
||||
from: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(
|
||||
check_balance!(
|
||||
10 * minimum_stake_balance,
|
||||
&rpc_client,
|
||||
&stake_account_pubkey,
|
||||
);
|
||||
check_balance!(
|
||||
10 * minimum_stake_balance,
|
||||
&rpc_client,
|
||||
&stake_account_pubkey,
|
||||
@@ -1377,7 +1390,7 @@ fn test_stake_set_lockup() {
|
||||
amount: SpendAmount::Some(minimum_nonce_balance),
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(minimum_nonce_balance, &rpc_client, &nonce_account_pubkey);
|
||||
check_balance!(minimum_nonce_balance, &rpc_client, &nonce_account_pubkey);
|
||||
|
||||
// Fetch nonce hash
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
@@ -1473,10 +1486,10 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
|
||||
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());
|
||||
check_balance!(200_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
check_balance!(100_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create nonce account
|
||||
let minimum_nonce_balance = rpc_client
|
||||
@@ -1553,7 +1566,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
from: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(50_000, &rpc_client, &stake_pubkey);
|
||||
check_balance!(50_000, &rpc_client, &stake_pubkey);
|
||||
|
||||
// Fetch nonce hash
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
@@ -1572,7 +1585,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
config_offline.command = CliCommand::WithdrawStake {
|
||||
stake_account_pubkey: stake_pubkey,
|
||||
destination_account_pubkey: recipient_pubkey,
|
||||
amount: SpendAmount::Some(42),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
withdraw_authority: 0,
|
||||
custodian: None,
|
||||
sign_only: true,
|
||||
@@ -1591,7 +1604,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
config.command = CliCommand::WithdrawStake {
|
||||
stake_account_pubkey: stake_pubkey,
|
||||
destination_account_pubkey: recipient_pubkey,
|
||||
amount: SpendAmount::Some(42),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
withdraw_authority: 0,
|
||||
custodian: None,
|
||||
sign_only: false,
|
||||
@@ -1607,7 +1620,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(42, &rpc_client, &recipient_pubkey);
|
||||
check_balance!(50_000, &rpc_client, &recipient_pubkey);
|
||||
|
||||
// Fetch nonce hash
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
@@ -1667,7 +1680,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
process_command(&config).unwrap();
|
||||
let seed_address =
|
||||
Pubkey::create_with_seed(&stake_pubkey, seed, &stake::program::id()).unwrap();
|
||||
check_recent_balance(50_000, &rpc_client, &seed_address);
|
||||
check_balance!(50_000, &rpc_client, &seed_address);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@@ -1,9 +1,11 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#![allow(clippy::redundant_closure)]
|
||||
use {
|
||||
solana_cli::{
|
||||
check_balance,
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
spend_utils::SpendAmount,
|
||||
test_utils::{check_ready, check_recent_balance},
|
||||
test_utils::check_ready,
|
||||
},
|
||||
solana_cli_output::{parse_sign_only_reply_string, OutputFormat},
|
||||
solana_client::{
|
||||
@@ -14,6 +16,8 @@ use {
|
||||
solana_faucet::faucet::run_local_faucet,
|
||||
solana_sdk::{
|
||||
commitment_config::CommitmentConfig,
|
||||
fee::FeeStructure,
|
||||
native_token::sol_to_lamports,
|
||||
nonce::State as NonceState,
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, Keypair, NullSigner, Signer},
|
||||
@@ -26,6 +30,8 @@ use {
|
||||
#[test]
|
||||
fn test_transfer() {
|
||||
solana_logger::setup();
|
||||
let fee_one_sig = FeeStructure::default().get_max_fee(1, 0);
|
||||
let fee_two_sig = FeeStructure::default().get_max_fee(2, 0);
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
@@ -49,15 +55,16 @@ fn test_transfer() {
|
||||
let sender_pubkey = config.signers[0].pubkey();
|
||||
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
|
||||
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);
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, sol_to_lamports(5.0))
|
||||
.unwrap();
|
||||
check_balance!(sol_to_lamports(5.0), &rpc_client, &sender_pubkey);
|
||||
check_balance!(0, &rpc_client, &recipient_pubkey);
|
||||
|
||||
check_ready(&rpc_client);
|
||||
|
||||
// Plain ole transfer
|
||||
config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(10),
|
||||
amount: SpendAmount::Some(sol_to_lamports(1.0)),
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
@@ -73,12 +80,16 @@ fn test_transfer() {
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(49_989, &rpc_client, &sender_pubkey);
|
||||
check_recent_balance(10, &rpc_client, &recipient_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(4.0) - fee_one_sig,
|
||||
&rpc_client,
|
||||
&sender_pubkey
|
||||
);
|
||||
check_balance!(sol_to_lamports(1.0), &rpc_client, &recipient_pubkey);
|
||||
|
||||
// Plain ole transfer, failure due to InsufficientFundsForSpendAndFee
|
||||
config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(49_989),
|
||||
amount: SpendAmount::Some(sol_to_lamports(4.0)),
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
@@ -94,8 +105,12 @@ fn test_transfer() {
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
assert!(process_command(&config).is_err());
|
||||
check_recent_balance(49_989, &rpc_client, &sender_pubkey);
|
||||
check_recent_balance(10, &rpc_client, &recipient_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(4.0) - fee_one_sig,
|
||||
&rpc_client,
|
||||
&sender_pubkey
|
||||
);
|
||||
check_balance!(sol_to_lamports(1.0), &rpc_client, &recipient_pubkey);
|
||||
|
||||
let mut offline = CliConfig::recent_for_tests();
|
||||
offline.json_rpc_url = String::default();
|
||||
@@ -105,13 +120,14 @@ fn test_transfer() {
|
||||
process_command(&offline).unwrap_err();
|
||||
|
||||
let offline_pubkey = offline.signers[0].pubkey();
|
||||
request_and_confirm_airdrop(&rpc_client, &offline, &offline_pubkey, 50).unwrap();
|
||||
check_recent_balance(50, &rpc_client, &offline_pubkey);
|
||||
request_and_confirm_airdrop(&rpc_client, &offline, &offline_pubkey, sol_to_lamports(1.0))
|
||||
.unwrap();
|
||||
check_balance!(sol_to_lamports(1.0), &rpc_client, &offline_pubkey);
|
||||
|
||||
// Offline transfer
|
||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||
offline.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(10),
|
||||
amount: SpendAmount::Some(sol_to_lamports(0.5)),
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: true,
|
||||
@@ -133,7 +149,7 @@ fn test_transfer() {
|
||||
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
||||
config.signers = vec![&offline_presigner];
|
||||
config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(10),
|
||||
amount: SpendAmount::Some(sol_to_lamports(0.5)),
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
@@ -149,8 +165,12 @@ fn test_transfer() {
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(39, &rpc_client, &offline_pubkey);
|
||||
check_recent_balance(20, &rpc_client, &recipient_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(0.5) - fee_one_sig,
|
||||
&rpc_client,
|
||||
&offline_pubkey
|
||||
);
|
||||
check_balance!(sol_to_lamports(1.5), &rpc_client, &recipient_pubkey);
|
||||
|
||||
// Create nonce account
|
||||
let nonce_account = keypair_from_seed(&[3u8; 32]).unwrap();
|
||||
@@ -166,7 +186,11 @@ fn test_transfer() {
|
||||
amount: SpendAmount::Some(minimum_nonce_balance),
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(49_987 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(4.0) - fee_one_sig - fee_two_sig - minimum_nonce_balance,
|
||||
&rpc_client,
|
||||
&sender_pubkey,
|
||||
);
|
||||
|
||||
// Fetch nonce hash
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
@@ -181,7 +205,7 @@ fn test_transfer() {
|
||||
// Nonced transfer
|
||||
config.signers = vec![&default_signer];
|
||||
config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(10),
|
||||
amount: SpendAmount::Some(sol_to_lamports(1.0)),
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
@@ -200,8 +224,12 @@ fn test_transfer() {
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(49_976 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
|
||||
check_recent_balance(30, &rpc_client, &recipient_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(3.0) - 2 * fee_one_sig - fee_two_sig - minimum_nonce_balance,
|
||||
&rpc_client,
|
||||
&sender_pubkey,
|
||||
);
|
||||
check_balance!(sol_to_lamports(2.5), &rpc_client, &recipient_pubkey);
|
||||
let new_nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
@@ -221,7 +249,11 @@ fn test_transfer() {
|
||||
new_authority: offline_pubkey,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(49_975 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(3.0) - 3 * fee_one_sig - fee_two_sig - minimum_nonce_balance,
|
||||
&rpc_client,
|
||||
&sender_pubkey,
|
||||
);
|
||||
|
||||
// Fetch nonce hash
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
@@ -236,7 +268,7 @@ fn test_transfer() {
|
||||
// Offline, nonced transfer
|
||||
offline.signers = vec![&default_offline_signer];
|
||||
offline.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(10),
|
||||
amount: SpendAmount::Some(sol_to_lamports(0.4)),
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: true,
|
||||
@@ -257,7 +289,7 @@ fn test_transfer() {
|
||||
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
||||
config.signers = vec![&offline_presigner];
|
||||
config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(10),
|
||||
amount: SpendAmount::Some(sol_to_lamports(0.4)),
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
@@ -276,13 +308,18 @@ fn test_transfer() {
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(28, &rpc_client, &offline_pubkey);
|
||||
check_recent_balance(40, &rpc_client, &recipient_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(0.1) - 2 * fee_one_sig,
|
||||
&rpc_client,
|
||||
&offline_pubkey
|
||||
);
|
||||
check_balance!(sol_to_lamports(2.9), &rpc_client, &recipient_pubkey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transfer_multisession_signing() {
|
||||
solana_logger::setup();
|
||||
let fee = FeeStructure::default().get_max_fee(2, 0);
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
@@ -305,19 +342,27 @@ fn test_transfer_multisession_signing() {
|
||||
&rpc_client,
|
||||
&CliConfig::recent_for_tests(),
|
||||
&offline_from_signer.pubkey(),
|
||||
43,
|
||||
sol_to_lamports(43.0),
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&CliConfig::recent_for_tests(),
|
||||
&offline_fee_payer_signer.pubkey(),
|
||||
3,
|
||||
sol_to_lamports(1.0) + 2 * fee,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(43, &rpc_client, &offline_from_signer.pubkey());
|
||||
check_recent_balance(3, &rpc_client, &offline_fee_payer_signer.pubkey());
|
||||
check_recent_balance(0, &rpc_client, &to_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(43.0),
|
||||
&rpc_client,
|
||||
&offline_from_signer.pubkey(),
|
||||
);
|
||||
check_balance!(
|
||||
sol_to_lamports(1.0) + 2 * fee,
|
||||
&rpc_client,
|
||||
&offline_fee_payer_signer.pubkey(),
|
||||
);
|
||||
check_balance!(0, &rpc_client, &to_pubkey);
|
||||
|
||||
check_ready(&rpc_client);
|
||||
|
||||
@@ -331,7 +376,7 @@ fn test_transfer_multisession_signing() {
|
||||
fee_payer_config.command = CliCommand::ClusterVersion;
|
||||
process_command(&fee_payer_config).unwrap_err();
|
||||
fee_payer_config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(42),
|
||||
amount: SpendAmount::Some(sol_to_lamports(42.0)),
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: true,
|
||||
@@ -362,7 +407,7 @@ fn test_transfer_multisession_signing() {
|
||||
from_config.command = CliCommand::ClusterVersion;
|
||||
process_command(&from_config).unwrap_err();
|
||||
from_config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(42),
|
||||
amount: SpendAmount::Some(sol_to_lamports(42.0)),
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: true,
|
||||
@@ -390,7 +435,7 @@ fn test_transfer_multisession_signing() {
|
||||
config.json_rpc_url = test_validator.rpc_url();
|
||||
config.signers = vec![&fee_payer_presigner, &from_presigner];
|
||||
config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(42),
|
||||
amount: SpendAmount::Some(sol_to_lamports(42.0)),
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: false,
|
||||
@@ -407,14 +452,23 @@ fn test_transfer_multisession_signing() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
|
||||
check_recent_balance(1, &rpc_client, &offline_from_signer.pubkey());
|
||||
check_recent_balance(1, &rpc_client, &offline_fee_payer_signer.pubkey());
|
||||
check_recent_balance(42, &rpc_client, &to_pubkey);
|
||||
check_balance!(
|
||||
sol_to_lamports(1.0),
|
||||
&rpc_client,
|
||||
&offline_from_signer.pubkey(),
|
||||
);
|
||||
check_balance!(
|
||||
sol_to_lamports(1.0) + fee,
|
||||
&rpc_client,
|
||||
&offline_fee_payer_signer.pubkey(),
|
||||
);
|
||||
check_balance!(sol_to_lamports(42.0), &rpc_client, &to_pubkey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transfer_all() {
|
||||
solana_logger::setup();
|
||||
let fee = FeeStructure::default().get_max_fee(1, 0);
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
@@ -437,9 +491,9 @@ fn test_transfer_all() {
|
||||
let sender_pubkey = config.signers[0].pubkey();
|
||||
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
|
||||
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);
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 500_000).unwrap();
|
||||
check_balance!(500_000, &rpc_client, &sender_pubkey);
|
||||
check_balance!(0, &rpc_client, &recipient_pubkey);
|
||||
|
||||
check_ready(&rpc_client);
|
||||
|
||||
@@ -461,8 +515,8 @@ fn test_transfer_all() {
|
||||
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);
|
||||
check_balance!(0, &rpc_client, &sender_pubkey);
|
||||
check_balance!(500_000 - fee, &rpc_client, &recipient_pubkey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -491,8 +545,8 @@ fn test_transfer_unfunded_recipient() {
|
||||
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
|
||||
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);
|
||||
check_balance!(50_000, &rpc_client, &sender_pubkey);
|
||||
check_balance!(0, &rpc_client, &recipient_pubkey);
|
||||
|
||||
check_ready(&rpc_client);
|
||||
|
||||
@@ -521,6 +575,7 @@ fn test_transfer_unfunded_recipient() {
|
||||
#[test]
|
||||
fn test_transfer_with_seed() {
|
||||
solana_logger::setup();
|
||||
let fee = FeeStructure::default().get_max_fee(1, 0);
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
@@ -551,17 +606,19 @@ fn test_transfer_with_seed() {
|
||||
)
|
||||
.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);
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, sol_to_lamports(1.0))
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &derived_address, sol_to_lamports(5.0))
|
||||
.unwrap();
|
||||
check_balance!(sol_to_lamports(1.0), &rpc_client, &sender_pubkey);
|
||||
check_balance!(sol_to_lamports(5.0), &rpc_client, &derived_address);
|
||||
check_balance!(0, &rpc_client, &recipient_pubkey);
|
||||
|
||||
check_ready(&rpc_client);
|
||||
|
||||
// Transfer with seed
|
||||
config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(50_000),
|
||||
amount: SpendAmount::Some(sol_to_lamports(5.0)),
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
@@ -577,7 +634,7 @@ fn test_transfer_with_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);
|
||||
check_balance!(sol_to_lamports(1.0) - fee, &rpc_client, &sender_pubkey);
|
||||
check_balance!(sol_to_lamports(5.0), &rpc_client, &recipient_pubkey);
|
||||
check_balance!(0, &rpc_client, &derived_address);
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use {
|
||||
solana_cli::{
|
||||
check_balance,
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
spend_utils::SpendAmount,
|
||||
test_utils::check_recent_balance,
|
||||
},
|
||||
solana_cli_output::{parse_sign_only_reply_string, OutputFormat},
|
||||
solana_client::{
|
||||
@@ -69,12 +70,12 @@ fn test_vote_authorize_and_withdraw() {
|
||||
.get_minimum_balance_for_rent_exemption(VoteState::size_of())
|
||||
.unwrap()
|
||||
.max(1);
|
||||
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
check_balance!(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
|
||||
// Transfer in some more SOL
|
||||
config.signers = vec![&default_signer];
|
||||
config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(1_000),
|
||||
amount: SpendAmount::Some(10_000),
|
||||
to: vote_account_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
@@ -90,8 +91,8 @@ fn test_vote_authorize_and_withdraw() {
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let expected_balance = expected_balance + 1_000;
|
||||
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
let expected_balance = expected_balance + 10_000;
|
||||
check_balance!(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
|
||||
// Authorize vote account withdrawal to another signer
|
||||
let first_withdraw_authority = Keypair::new();
|
||||
@@ -169,7 +170,7 @@ fn test_vote_authorize_and_withdraw() {
|
||||
config.command = CliCommand::WithdrawFromVoteAccount {
|
||||
vote_account_pubkey,
|
||||
withdraw_authority: 1,
|
||||
withdraw_amount: SpendAmount::Some(100),
|
||||
withdraw_amount: SpendAmount::Some(1_000),
|
||||
destination_account_pubkey: destination_account,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
@@ -180,9 +181,9 @@ fn test_vote_authorize_and_withdraw() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let expected_balance = expected_balance - 100;
|
||||
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
check_recent_balance(100, &rpc_client, &destination_account);
|
||||
let expected_balance = expected_balance - 1_000;
|
||||
check_balance!(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
check_balance!(1_000, &rpc_client, &destination_account);
|
||||
|
||||
// Re-assign validator identity
|
||||
let new_identity_keypair = Keypair::new();
|
||||
@@ -212,8 +213,8 @@ fn test_vote_authorize_and_withdraw() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(0, &rpc_client, &vote_account_pubkey);
|
||||
check_recent_balance(expected_balance, &rpc_client, &destination_account);
|
||||
check_balance!(0, &rpc_client, &vote_account_pubkey);
|
||||
check_balance!(expected_balance, &rpc_client, &destination_account);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -247,7 +248,7 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||
100_000,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &config_payer.signers[0].pubkey());
|
||||
check_balance!(100_000, &rpc_client, &config_payer.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
@@ -256,7 +257,7 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||
100_000,
|
||||
)
|
||||
.unwrap();
|
||||
check_recent_balance(100_000, &rpc_client, &config_offline.signers[0].pubkey());
|
||||
check_balance!(100_000, &rpc_client, &config_offline.signers[0].pubkey());
|
||||
|
||||
// Create vote account with specific withdrawer
|
||||
let vote_account_keypair = Keypair::new();
|
||||
@@ -288,12 +289,12 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||
.get_minimum_balance_for_rent_exemption(VoteState::size_of())
|
||||
.unwrap()
|
||||
.max(1);
|
||||
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
check_balance!(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
|
||||
// Transfer in some more SOL
|
||||
config_payer.signers = vec![&default_signer];
|
||||
config_payer.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(1_000),
|
||||
amount: SpendAmount::Some(10_000),
|
||||
to: vote_account_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
@@ -309,8 +310,8 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config_payer).unwrap();
|
||||
let expected_balance = expected_balance + 1_000;
|
||||
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
let expected_balance = expected_balance + 10_000;
|
||||
check_balance!(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
|
||||
// Authorize vote account withdrawal to another signer, offline
|
||||
let withdraw_authority = Keypair::new();
|
||||
@@ -367,7 +368,7 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||
config_offline.command = CliCommand::WithdrawFromVoteAccount {
|
||||
vote_account_pubkey,
|
||||
withdraw_authority: 1,
|
||||
withdraw_amount: SpendAmount::Some(100),
|
||||
withdraw_amount: SpendAmount::Some(1_000),
|
||||
destination_account_pubkey: destination_account,
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
@@ -387,7 +388,7 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||
config_payer.command = CliCommand::WithdrawFromVoteAccount {
|
||||
vote_account_pubkey,
|
||||
withdraw_authority: 1,
|
||||
withdraw_amount: SpendAmount::Some(100),
|
||||
withdraw_amount: SpendAmount::Some(1_000),
|
||||
destination_account_pubkey: destination_account,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
@@ -398,9 +399,9 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config_payer).unwrap();
|
||||
let expected_balance = expected_balance - 100;
|
||||
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
check_recent_balance(100, &rpc_client, &destination_account);
|
||||
let expected_balance = expected_balance - 1_000;
|
||||
check_balance!(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
check_balance!(1_000, &rpc_client, &destination_account);
|
||||
|
||||
// Re-assign validator identity offline
|
||||
let blockhash = rpc_client.get_latest_blockhash().unwrap();
|
||||
@@ -483,9 +484,7 @@ fn test_offline_vote_authorize_and_withdraw() {
|
||||
memo: None,
|
||||
fee_payer: 0,
|
||||
};
|
||||
let result = process_command(&config_payer).unwrap();
|
||||
println!("{:?}", result);
|
||||
check_recent_balance(0, &rpc_client, &vote_account_pubkey);
|
||||
println!("what");
|
||||
check_recent_balance(expected_balance, &rpc_client, &destination_account);
|
||||
process_command(&config_payer).unwrap();
|
||||
check_balance!(0, &rpc_client, &vote_account_pubkey);
|
||||
check_balance!(expected_balance, &rpc_client, &destination_account);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-client-test"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
description = "Solana RPC Test"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -8,28 +8,31 @@ license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-client-test"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0.74"
|
||||
serial_test = "0.5.1"
|
||||
solana-client = { path = "../client", version = "=1.10.0" }
|
||||
solana-ledger = { path = "../ledger", version = "=1.10.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.0" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "=1.10.0" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.10.0" }
|
||||
solana-perf = { path = "../perf", version = "=1.10.0" }
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.10.0" }
|
||||
solana-rpc = { path = "../rpc", version = "=1.10.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.10.0" }
|
||||
solana-test-validator = { path = "../test-validator", version = "=1.10.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.0" }
|
||||
solana-version = { path = "../version", version = "=1.10.0" }
|
||||
futures-util = "0.3.21"
|
||||
serde_json = "1.0.79"
|
||||
serial_test = "0.6.0"
|
||||
solana-client = { path = "../client", version = "=1.10.2" }
|
||||
solana-ledger = { path = "../ledger", version = "=1.10.2" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.2" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "=1.10.2" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.10.2" }
|
||||
solana-perf = { path = "../perf", version = "=1.10.2" }
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.10.2" }
|
||||
solana-rpc = { path = "../rpc", version = "=1.10.2" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.10.2" }
|
||||
solana-test-validator = { path = "../test-validator", version = "=1.10.2" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.2" }
|
||||
solana-version = { path = "../version", version = "=1.10.2" }
|
||||
systemstat = "0.1.10"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-logger = { path = "../logger", version = "=1.10.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.2" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -1,19 +1,21 @@
|
||||
use {
|
||||
futures_util::StreamExt,
|
||||
serde_json::{json, Value},
|
||||
serial_test::serial,
|
||||
solana_client::{
|
||||
nonblocking,
|
||||
pubsub_client::PubsubClient,
|
||||
rpc_client::RpcClient,
|
||||
rpc_config::{
|
||||
RpcAccountInfoConfig, RpcBlockSubscribeConfig, RpcBlockSubscribeFilter,
|
||||
RpcProgramAccountsConfig,
|
||||
},
|
||||
rpc_response::{RpcBlockUpdate, SlotInfo},
|
||||
rpc_response::SlotInfo,
|
||||
},
|
||||
solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path},
|
||||
solana_rpc::{
|
||||
optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
|
||||
rpc::create_test_transactions_and_populate_blockstore,
|
||||
rpc::{create_test_transaction_entries, populate_blockstore_for_tests},
|
||||
rpc_pubsub_service::{PubSubConfig, PubSubService},
|
||||
rpc_subscriptions::RpcSubscriptions,
|
||||
},
|
||||
@@ -34,7 +36,9 @@ use {
|
||||
},
|
||||
solana_streamer::socket::SocketAddrSpace,
|
||||
solana_test_validator::TestValidator,
|
||||
solana_transaction_status::{TransactionDetails, UiTransactionEncoding},
|
||||
solana_transaction_status::{
|
||||
BlockEncodingOptions, ConfirmedBlock, TransactionDetails, UiTransactionEncoding,
|
||||
},
|
||||
std::{
|
||||
collections::HashSet,
|
||||
net::{IpAddr, SocketAddr},
|
||||
@@ -212,6 +216,7 @@ fn test_block_subscription() {
|
||||
..
|
||||
} = create_genesis_config(10_000);
|
||||
let bank = Bank::new_for_tests(&genesis_config);
|
||||
let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0);
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
|
||||
// setup Blockstore
|
||||
@@ -225,9 +230,14 @@ fn test_block_subscription() {
|
||||
let keypair2 = Keypair::new();
|
||||
let keypair3 = Keypair::new();
|
||||
let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(blockstore.max_root()));
|
||||
let _confirmed_block_signatures = create_test_transactions_and_populate_blockstore(
|
||||
vec![&alice, &keypair1, &keypair2, &keypair3],
|
||||
0,
|
||||
bank.transfer(rent_exempt_amount, &alice, &keypair2.pubkey())
|
||||
.unwrap();
|
||||
populate_blockstore_for_tests(
|
||||
create_test_transaction_entries(
|
||||
vec![&alice, &keypair1, &keypair2, &keypair3],
|
||||
bank.clone(),
|
||||
)
|
||||
.0,
|
||||
bank,
|
||||
blockstore.clone(),
|
||||
max_complete_transaction_status_slot,
|
||||
@@ -265,6 +275,7 @@ fn test_block_subscription() {
|
||||
encoding: Some(UiTransactionEncoding::Json),
|
||||
transaction_details: Some(TransactionDetails::Signatures),
|
||||
show_rewards: None,
|
||||
max_supported_transaction_version: None,
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -275,23 +286,19 @@ fn test_block_subscription() {
|
||||
let maybe_actual = receiver.recv_timeout(Duration::from_millis(400));
|
||||
match maybe_actual {
|
||||
Ok(actual) => {
|
||||
let complete_block = blockstore.get_complete_block(slot, false).unwrap();
|
||||
let block = complete_block.clone().configure(
|
||||
UiTransactionEncoding::Json,
|
||||
TransactionDetails::Signatures,
|
||||
false,
|
||||
);
|
||||
let expected = RpcBlockUpdate {
|
||||
slot,
|
||||
block: Some(block),
|
||||
err: None,
|
||||
};
|
||||
let block = complete_block.configure(
|
||||
UiTransactionEncoding::Json,
|
||||
TransactionDetails::Signatures,
|
||||
false,
|
||||
);
|
||||
assert_eq!(actual.value.slot, expected.slot);
|
||||
let versioned_block = blockstore.get_complete_block(slot, false).unwrap();
|
||||
let confirmed_block = ConfirmedBlock::from(versioned_block);
|
||||
let block = confirmed_block
|
||||
.encode_with_options(
|
||||
UiTransactionEncoding::Json,
|
||||
BlockEncodingOptions {
|
||||
transaction_details: TransactionDetails::Signatures,
|
||||
show_rewards: false,
|
||||
max_supported_transaction_version: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(actual.value.slot, slot);
|
||||
assert!(block.eq(&actual.value.block.unwrap()));
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -513,3 +520,86 @@ fn test_slot_subscription() {
|
||||
|
||||
assert_eq!(errors, [].to_vec());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_slot_subscription_async() {
|
||||
let sync_service = Arc::new(AtomicU64::new(0));
|
||||
let sync_client = Arc::clone(&sync_service);
|
||||
fn wait_until(atomic: &Arc<AtomicU64>, value: u64) {
|
||||
while atomic.load(Ordering::Relaxed) != value {
|
||||
sleep(Duration::from_millis(1))
|
||||
}
|
||||
}
|
||||
|
||||
let pubsub_addr = SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
||||
rpc_port::DEFAULT_RPC_PUBSUB_PORT,
|
||||
);
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
|
||||
let bank = Bank::new_for_tests(&genesis_config);
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
let optimistically_confirmed_bank =
|
||||
OptimisticallyConfirmedBank::locked_from_bank_forks_root(&bank_forks);
|
||||
let max_complete_transaction_status_slot = Arc::new(AtomicU64::default());
|
||||
let subscriptions = Arc::new(RpcSubscriptions::new_for_tests(
|
||||
&exit,
|
||||
max_complete_transaction_status_slot,
|
||||
bank_forks,
|
||||
Arc::new(RwLock::new(BlockCommitmentCache::default())),
|
||||
optimistically_confirmed_bank,
|
||||
));
|
||||
let (trigger, pubsub_service) =
|
||||
PubSubService::new(PubSubConfig::default(), &subscriptions, pubsub_addr);
|
||||
sleep(Duration::from_millis(100));
|
||||
sync_service.store(1, Ordering::Relaxed);
|
||||
|
||||
wait_until(&sync_service, 2);
|
||||
subscriptions.notify_slot(1, 0, 0);
|
||||
sync_service.store(3, Ordering::Relaxed);
|
||||
|
||||
wait_until(&sync_service, 4);
|
||||
subscriptions.notify_slot(2, 1, 1);
|
||||
sync_service.store(5, Ordering::Relaxed);
|
||||
|
||||
wait_until(&sync_service, 6);
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
trigger.cancel();
|
||||
pubsub_service.close().unwrap();
|
||||
});
|
||||
|
||||
wait_until(&sync_client, 1);
|
||||
let url = format!("ws://0.0.0.0:{}/", pubsub_addr.port());
|
||||
let pubsub_client = nonblocking::pubsub_client::PubsubClient::new(&url)
|
||||
.await
|
||||
.unwrap();
|
||||
let (mut notifications, unsubscribe) = pubsub_client.slot_subscribe().await.unwrap();
|
||||
sync_client.store(2, Ordering::Relaxed);
|
||||
|
||||
wait_until(&sync_client, 3);
|
||||
assert_eq!(
|
||||
tokio::time::timeout(Duration::from_millis(25), notifications.next()).await,
|
||||
Ok(Some(SlotInfo {
|
||||
slot: 1,
|
||||
parent: 0,
|
||||
root: 0,
|
||||
}))
|
||||
);
|
||||
sync_client.store(4, Ordering::Relaxed);
|
||||
|
||||
wait_until(&sync_client, 5);
|
||||
assert_eq!(
|
||||
tokio::time::timeout(Duration::from_millis(25), notifications.next()).await,
|
||||
Ok(Some(SlotInfo {
|
||||
slot: 2,
|
||||
parent: 1,
|
||||
root: 1,
|
||||
}))
|
||||
);
|
||||
sync_client.store(6, Ordering::Relaxed);
|
||||
|
||||
unsubscribe().await;
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-client"
|
||||
version = "1.10.0"
|
||||
version = "1.10.2"
|
||||
description = "Solana Client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,37 +10,48 @@ license = "Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
async-mutex = "1.4.0"
|
||||
async-trait = "0.1.52"
|
||||
base64 = "0.13.0"
|
||||
bincode = "1.3.3"
|
||||
bs58 = "0.4.0"
|
||||
bytes = "1.1.0"
|
||||
clap = "2.33.0"
|
||||
crossbeam-channel = "0.5"
|
||||
futures = "0.3"
|
||||
futures-util = "0.3.21"
|
||||
indicatif = "0.16.2"
|
||||
itertools = "0.10.2"
|
||||
jsonrpc-core = "18.0.0"
|
||||
log = "0.4.14"
|
||||
quinn = "0.8.0"
|
||||
rayon = "1.5.1"
|
||||
reqwest = { version = "0.11.6", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
semver = "1.0.4"
|
||||
serde = "1.0.133"
|
||||
reqwest = { version = "0.11.9", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
rustls = { version = "0.20.2", features = ["dangerous_configuration"] }
|
||||
semver = "1.0.6"
|
||||
serde = "1.0.136"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.74"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.10.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.10.0" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.10.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.10.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.0" }
|
||||
solana-version = { path = "../version", version = "=1.10.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.10.0" }
|
||||
serde_json = "1.0.79"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.10.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.10.2" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.10.2" }
|
||||
solana-measure = { path = "../measure", version = "=1.10.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.10.2" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.10.2" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.10.2" }
|
||||
solana-version = { path = "../version", version = "=1.10.2" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.10.2" }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tungstenite = { version = "0.16.0", features = ["rustls-tls-webpki-roots"] }
|
||||
tokio-stream = "0.1.8"
|
||||
tokio-tungstenite = { version = "0.17.1", features = ["rustls-tls-webpki-roots"] }
|
||||
tungstenite = { version = "0.17.2", features = ["rustls-tls-webpki-roots"] }
|
||||
url = "2.2.2"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5.0"
|
||||
jsonrpc-http-server = "18.0.0"
|
||||
solana-logger = { path = "../logger", version = "=1.10.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.10.2" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -1,6 +1,7 @@
|
||||
pub use reqwest;
|
||||
use {
|
||||
crate::{rpc_request, rpc_response},
|
||||
quinn::{ConnectError, WriteError},
|
||||
solana_faucet::faucet::FaucetError,
|
||||
solana_sdk::{
|
||||
signature::SignerError, transaction::TransactionError, transport::TransportError,
|
||||
@@ -72,6 +73,18 @@ impl From<ClientErrorKind> for TransportError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WriteError> for ClientErrorKind {
|
||||
fn from(write_error: WriteError) -> Self {
|
||||
Self::Custom(format!("{:?}", write_error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConnectError> for ClientErrorKind {
|
||||
fn from(connect_error: ConnectError) -> Self {
|
||||
Self::Custom(format!("{:?}", connect_error))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{kind}")]
|
||||
pub struct ClientError {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//! The standard [`RpcSender`] over HTTP.
|
||||
//! Nonblocking [`RpcSender`] over HTTP.
|
||||
|
||||
use {
|
||||
crate::{
|
||||
@@ -8,6 +8,7 @@ use {
|
||||
rpc_response::RpcSimulateTransactionResult,
|
||||
rpc_sender::*,
|
||||
},
|
||||
async_trait::async_trait,
|
||||
log::*,
|
||||
reqwest::{
|
||||
self,
|
||||
@@ -25,40 +26,36 @@ use {
|
||||
};
|
||||
|
||||
pub struct HttpSender {
|
||||
client: Arc<reqwest::blocking::Client>,
|
||||
client: Arc<reqwest::Client>,
|
||||
url: String,
|
||||
request_id: AtomicU64,
|
||||
stats: RwLock<RpcTransportStats>,
|
||||
}
|
||||
|
||||
/// The standard [`RpcSender`] over HTTP.
|
||||
/// Nonblocking [`RpcSender`] over HTTP.
|
||||
impl HttpSender {
|
||||
/// Create an HTTP RPC sender.
|
||||
///
|
||||
/// The URL is an HTTP URL, usually for port 8899, as in
|
||||
/// "http://localhost:8899". The sender has a default timeout of 30 seconds.
|
||||
pub fn new(url: String) -> Self {
|
||||
pub fn new<U: ToString>(url: U) -> Self {
|
||||
Self::new_with_timeout(url, Duration::from_secs(30))
|
||||
}
|
||||
|
||||
/// Create an HTTP RPC sender.
|
||||
///
|
||||
/// The URL is an HTTP URL, usually for port 8899.
|
||||
pub fn new_with_timeout(url: String, timeout: Duration) -> Self {
|
||||
// `reqwest::blocking::Client` panics if run in a tokio async context. Shuttle the
|
||||
// request to a different tokio thread to avoid this
|
||||
pub fn new_with_timeout<U: ToString>(url: U, timeout: Duration) -> Self {
|
||||
let client = Arc::new(
|
||||
tokio::task::block_in_place(move || {
|
||||
reqwest::blocking::Client::builder()
|
||||
.timeout(timeout)
|
||||
.build()
|
||||
})
|
||||
.expect("build rpc client"),
|
||||
reqwest::Client::builder()
|
||||
.timeout(timeout)
|
||||
.build()
|
||||
.expect("build rpc client"),
|
||||
);
|
||||
|
||||
Self {
|
||||
client,
|
||||
url,
|
||||
url: url.to_string(),
|
||||
request_id: AtomicU64::new(0),
|
||||
stats: RwLock::new(RpcTransportStats::default()),
|
||||
}
|
||||
@@ -66,9 +63,9 @@ impl HttpSender {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct RpcErrorObject {
|
||||
code: i64,
|
||||
message: String,
|
||||
pub(crate) struct RpcErrorObject {
|
||||
pub code: i64,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
struct StatsUpdater<'a> {
|
||||
@@ -100,12 +97,17 @@ impl<'a> Drop for StatsUpdater<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RpcSender for HttpSender {
|
||||
fn get_transport_stats(&self) -> RpcTransportStats {
|
||||
self.stats.read().unwrap().clone()
|
||||
}
|
||||
|
||||
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value> {
|
||||
async fn send(
|
||||
&self,
|
||||
request: RpcRequest,
|
||||
params: serde_json::Value,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut stats_updater = StatsUpdater::new(&self.stats);
|
||||
|
||||
let request_id = self.request_id.fetch_add(1, Ordering::Relaxed);
|
||||
@@ -113,18 +115,15 @@ impl RpcSender for HttpSender {
|
||||
|
||||
let mut too_many_requests_retries = 5;
|
||||
loop {
|
||||
// `reqwest::blocking::Client` panics if run in a tokio async context. Shuttle the
|
||||
// request to a different tokio thread to avoid this
|
||||
let response = {
|
||||
let client = self.client.clone();
|
||||
let request_json = request_json.clone();
|
||||
tokio::task::block_in_place(move || {
|
||||
client
|
||||
.post(&self.url)
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.body(request_json)
|
||||
.send()
|
||||
})
|
||||
client
|
||||
.post(&self.url)
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.body(request_json)
|
||||
.send()
|
||||
.await
|
||||
}?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
@@ -155,8 +154,7 @@ impl RpcSender for HttpSender {
|
||||
return Err(response.error_for_status().unwrap_err().into());
|
||||
}
|
||||
|
||||
let mut json =
|
||||
tokio::task::block_in_place(move || response.json::<serde_json::Value>())?;
|
||||
let mut json = response.json::<serde_json::Value>().await?;
|
||||
if json["error"].is_object() {
|
||||
return match serde_json::from_value::<RpcErrorObject>(json["error"].clone()) {
|
||||
Ok(rpc_error_object) => {
|
||||
@@ -208,14 +206,16 @@ mod tests {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn http_sender_on_tokio_multi_thread() {
|
||||
let http_sender = HttpSender::new("http://localhost:1234".to_string());
|
||||
let _ = http_sender.send(RpcRequest::GetVersion, serde_json::Value::Null);
|
||||
let _ = http_sender
|
||||
.send(RpcRequest::GetVersion, serde_json::Value::Null)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
#[should_panic(expected = "can call blocking only when running on the multi-threaded runtime")]
|
||||
async fn http_sender_ontokio_current_thread_should_panic() {
|
||||
// RpcClient::new() will panic in the tokio current-thread runtime due to `tokio::task::block_in_place()` usage, and there
|
||||
// doesn't seem to be a way to detect whether the tokio runtime is multi_thread or current_thread...
|
||||
let _ = HttpSender::new("http://localhost:1234".to_string());
|
||||
async fn http_sender_on_tokio_current_thread() {
|
||||
let http_sender = HttpSender::new("http://localhost:1234".to_string());
|
||||
let _ = http_sender
|
||||
.send(RpcRequest::GetVersion, serde_json::Value::Null)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
@@ -4,11 +4,13 @@ extern crate serde_derive;
|
||||
|
||||
pub mod blockhash_query;
|
||||
pub mod client_error;
|
||||
pub mod http_sender;
|
||||
pub mod mock_sender;
|
||||
pub(crate) mod http_sender;
|
||||
pub(crate) mod mock_sender;
|
||||
pub mod nonblocking;
|
||||
pub mod nonce_utils;
|
||||
pub mod perf_utils;
|
||||
pub mod pubsub_client;
|
||||
pub mod quic_client;
|
||||
pub mod rpc_cache;
|
||||
pub mod rpc_client;
|
||||
pub mod rpc_config;
|
||||
@@ -21,4 +23,13 @@ pub mod rpc_sender;
|
||||
pub mod spinner;
|
||||
pub mod thin_client;
|
||||
pub mod tpu_client;
|
||||
pub mod tpu_connection;
|
||||
pub mod transaction_executor;
|
||||
pub mod udp_client;
|
||||
|
||||
pub mod mock_sender_for_cli {
|
||||
/// Magic `SIGNATURE` value used by `solana-cli` unit tests.
|
||||
/// Please don't use this constant.
|
||||
pub const SIGNATURE: &str =
|
||||
"43yNSFC6fYTuPgTNFFhF4axw7AfWxB2BPdurme8yrsWEYwm8299xh8n6TAHjGymiSub1XtyxTNyd9GBfY2hxoBw8";
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//! An [`RpcSender`] used for unit testing [`RpcClient`](crate::rpc_client::RpcClient).
|
||||
//! A nonblocking [`RpcSender`] used for unit testing [`RpcClient`](crate::rpc_client::RpcClient).
|
||||
|
||||
use {
|
||||
crate::{
|
||||
@@ -15,6 +15,7 @@ use {
|
||||
},
|
||||
rpc_sender::*,
|
||||
},
|
||||
async_trait::async_trait,
|
||||
serde_json::{json, Number, Value},
|
||||
solana_account_decoder::{UiAccount, UiAccountEncoding},
|
||||
solana_sdk::{
|
||||
@@ -27,21 +28,19 @@ use {
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
sysvar::epoch_schedule::EpochSchedule,
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
transaction::{self, Transaction, TransactionError, TransactionVersion},
|
||||
},
|
||||
solana_transaction_status::{
|
||||
EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction,
|
||||
EncodedTransactionWithStatusMeta, Rewards, TransactionConfirmationStatus,
|
||||
TransactionStatus, UiCompiledInstruction, UiMessage, UiRawMessage, UiTransaction,
|
||||
UiTransactionEncoding, UiTransactionStatusMeta,
|
||||
EncodedTransactionWithStatusMeta, Rewards, TransactionBinaryEncoding,
|
||||
TransactionConfirmationStatus, TransactionStatus, UiCompiledInstruction, UiMessage,
|
||||
UiRawMessage, UiTransaction, UiTransactionStatusMeta,
|
||||
},
|
||||
solana_version::Version,
|
||||
std::{collections::HashMap, net::SocketAddr, str::FromStr, sync::RwLock},
|
||||
};
|
||||
|
||||
pub const PUBKEY: &str = "7RoSF9fUmdphVCpabEoefH81WwrW7orsWonXWqTXkKV8";
|
||||
pub const SIGNATURE: &str =
|
||||
"43yNSFC6fYTuPgTNFFhF4axw7AfWxB2BPdurme8yrsWEYwm8299xh8n6TAHjGymiSub1XtyxTNyd9GBfY2hxoBw8";
|
||||
|
||||
pub type Mocks = HashMap<RpcRequest, Value>;
|
||||
pub struct MockSender {
|
||||
@@ -75,24 +74,29 @@ pub struct MockSender {
|
||||
/// from [`RpcRequest`] to a JSON [`Value`] response, Any entries in this map
|
||||
/// override the default behavior for the given request.
|
||||
impl MockSender {
|
||||
pub fn new(url: String) -> Self {
|
||||
pub fn new<U: ToString>(url: U) -> Self {
|
||||
Self::new_with_mocks(url, Mocks::default())
|
||||
}
|
||||
|
||||
pub fn new_with_mocks(url: String, mocks: Mocks) -> Self {
|
||||
pub fn new_with_mocks<U: ToString>(url: U, mocks: Mocks) -> Self {
|
||||
Self {
|
||||
url,
|
||||
url: url.to_string(),
|
||||
mocks: RwLock::new(mocks),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl RpcSender for MockSender {
|
||||
fn get_transport_stats(&self) -> RpcTransportStats {
|
||||
RpcTransportStats::default()
|
||||
}
|
||||
|
||||
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value> {
|
||||
async fn send(
|
||||
&self,
|
||||
request: RpcRequest,
|
||||
params: serde_json::Value,
|
||||
) -> Result<serde_json::Value> {
|
||||
if let Some(value) = self.mocks.write().unwrap().remove(&request) {
|
||||
return Ok(value);
|
||||
}
|
||||
@@ -188,6 +192,7 @@ impl RpcSender for MockSender {
|
||||
"getTransaction" => serde_json::to_value(EncodedConfirmedTransactionWithStatusMeta {
|
||||
slot: 2,
|
||||
transaction: EncodedTransactionWithStatusMeta {
|
||||
version: Some(TransactionVersion::LEGACY),
|
||||
transaction: EncodedTransaction::Json(
|
||||
UiTransaction {
|
||||
signatures: vec!["3AsdoALgZFuq2oUVWrDYhg2pNeaLJKPLf8hU2mQ6U8qJxeJ6hsrPVpMn9ma39DtfYCrDQSvngWRP8NnTpEhezJpE".to_string()],
|
||||
@@ -209,6 +214,7 @@ impl RpcSender for MockSender {
|
||||
accounts: vec![0, 1],
|
||||
data: "3Bxs49DitAvXtoDR".to_string(),
|
||||
}],
|
||||
address_table_lookups: None,
|
||||
})
|
||||
}),
|
||||
meta: Some(UiTransactionStatusMeta {
|
||||
@@ -222,6 +228,7 @@ impl RpcSender for MockSender {
|
||||
pre_token_balances: None,
|
||||
post_token_balances: None,
|
||||
rewards: None,
|
||||
loaded_addresses: None,
|
||||
}),
|
||||
},
|
||||
block_time: Some(1628633791),
|
||||
@@ -374,9 +381,10 @@ impl RpcSender for MockSender {
|
||||
pLHxcaShD81xBNaFDgnA2nkkdHnKtZt4hVSfKAmw3VRZbjrZ7L2fKZBx21CwsG\
|
||||
hD6onjM2M3qZW5C8J6d1pj41MxKmZgPBSha3MyKkNLkAGFASK"
|
||||
.to_string(),
|
||||
UiTransactionEncoding::Base58,
|
||||
TransactionBinaryEncoding::Base58,
|
||||
),
|
||||
meta: None,
|
||||
version: Some(TransactionVersion::LEGACY),
|
||||
}],
|
||||
rewards: Rewards::new(),
|
||||
block_time: None,
|
||||
@@ -386,7 +394,7 @@ impl RpcSender for MockSender {
|
||||
"getBlocksWithLimit" => serde_json::to_value(vec![1, 2, 3])?,
|
||||
"getSignaturesForAddress" => {
|
||||
serde_json::to_value(vec![RpcConfirmedTransactionStatusWithSignature {
|
||||
signature: SIGNATURE.to_string(),
|
||||
signature: crate::mock_sender_for_cli::SIGNATURE.to_string(),
|
||||
slot: 123,
|
||||
err: None,
|
||||
memo: None,
|
||||
@@ -435,7 +443,7 @@ impl RpcSender for MockSender {
|
||||
value: vec![Value::Null, Value::Null]
|
||||
})?,
|
||||
"getProgramAccounts" => {
|
||||
let pubkey = Pubkey::from_str(&PUBKEY.to_string()).unwrap();
|
||||
let pubkey = Pubkey::from_str(PUBKEY).unwrap();
|
||||
let account = Account {
|
||||
lamports: 1_000_000,
|
||||
data: vec![],
|
||||
|
2
client/src/nonblocking/mod.rs
Normal file
2
client/src/nonblocking/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod pubsub_client;
|
||||
pub mod rpc_client;
|
341
client/src/nonblocking/pubsub_client.rs
Normal file
341
client/src/nonblocking/pubsub_client.rs
Normal file
@@ -0,0 +1,341 @@
|
||||
use {
|
||||
crate::{
|
||||
http_sender::RpcErrorObject,
|
||||
rpc_config::{
|
||||
RpcAccountInfoConfig, RpcBlockSubscribeConfig, RpcBlockSubscribeFilter,
|
||||
RpcProgramAccountsConfig, RpcSignatureSubscribeConfig, RpcTransactionLogsConfig,
|
||||
RpcTransactionLogsFilter,
|
||||
},
|
||||
rpc_response::{
|
||||
Response as RpcResponse, RpcBlockUpdate, RpcKeyedAccount, RpcLogsResponse,
|
||||
RpcSignatureResult, RpcVote, SlotInfo, SlotUpdate,
|
||||
},
|
||||
},
|
||||
futures_util::{
|
||||
future::{ready, BoxFuture, FutureExt},
|
||||
sink::SinkExt,
|
||||
stream::{BoxStream, StreamExt},
|
||||
},
|
||||
log::*,
|
||||
serde::de::DeserializeOwned,
|
||||
serde_json::{json, Map, Value},
|
||||
solana_account_decoder::UiAccount,
|
||||
solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature},
|
||||
std::collections::BTreeMap,
|
||||
thiserror::Error,
|
||||
tokio::{
|
||||
net::TcpStream,
|
||||
sync::{mpsc, oneshot},
|
||||
task::JoinHandle,
|
||||
time::{sleep, Duration},
|
||||
},
|
||||
tokio_stream::wrappers::UnboundedReceiverStream,
|
||||
tokio_tungstenite::{
|
||||
connect_async,
|
||||
tungstenite::{
|
||||
protocol::frame::{coding::CloseCode, CloseFrame},
|
||||
Message,
|
||||
},
|
||||
MaybeTlsStream, WebSocketStream,
|
||||
},
|
||||
url::Url,
|
||||
};
|
||||
|
||||
pub type PubsubClientResult<T = ()> = Result<T, PubsubClientError>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PubsubClientError {
|
||||
#[error("url parse error")]
|
||||
UrlParseError(#[from] url::ParseError),
|
||||
|
||||
#[error("unable to connect to server")]
|
||||
ConnectionError(tokio_tungstenite::tungstenite::Error),
|
||||
|
||||
#[error("websocket error")]
|
||||
WsError(#[from] tokio_tungstenite::tungstenite::Error),
|
||||
|
||||
#[error("connection closed (({0})")]
|
||||
ConnectionClosed(String),
|
||||
|
||||
#[error("json parse error")]
|
||||
JsonParseError(#[from] serde_json::error::Error),
|
||||
|
||||
#[error("subscribe failed: {reason}")]
|
||||
SubscribeFailed { reason: String, message: String },
|
||||
}
|
||||
|
||||
type UnsubscribeFn = Box<dyn FnOnce() -> BoxFuture<'static, ()> + Send>;
|
||||
type SubscribeResponseMsg =
|
||||
Result<(mpsc::UnboundedReceiver<Value>, UnsubscribeFn), PubsubClientError>;
|
||||
type SubscribeRequestMsg = (String, Value, oneshot::Sender<SubscribeResponseMsg>);
|
||||
type SubscribeResult<'a, T> = PubsubClientResult<(BoxStream<'a, T>, UnsubscribeFn)>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PubsubClient {
|
||||
subscribe_tx: mpsc::UnboundedSender<SubscribeRequestMsg>,
|
||||
shutdown_tx: oneshot::Sender<()>,
|
||||
ws: JoinHandle<PubsubClientResult>,
|
||||
}
|
||||
|
||||
impl PubsubClient {
|
||||
pub async fn new(url: &str) -> PubsubClientResult<Self> {
|
||||
let url = Url::parse(url)?;
|
||||
let (ws, _response) = connect_async(url)
|
||||
.await
|
||||
.map_err(PubsubClientError::ConnectionError)?;
|
||||
|
||||
let (subscribe_tx, subscribe_rx) = mpsc::unbounded_channel();
|
||||
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||
|
||||
Ok(Self {
|
||||
subscribe_tx,
|
||||
shutdown_tx,
|
||||
ws: tokio::spawn(PubsubClient::run_ws(ws, subscribe_rx, shutdown_rx)),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn shutdown(self) -> PubsubClientResult {
|
||||
let _ = self.shutdown_tx.send(());
|
||||
self.ws.await.unwrap() // WS future should not be cancelled or panicked
|
||||
}
|
||||
|
||||
async fn subscribe<'a, T>(&self, operation: &str, params: Value) -> SubscribeResult<'a, T>
|
||||
where
|
||||
T: DeserializeOwned + Send + 'a,
|
||||
{
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
self.subscribe_tx
|
||||
.send((operation.to_string(), params, response_tx))
|
||||
.map_err(|err| PubsubClientError::ConnectionClosed(err.to_string()))?;
|
||||
|
||||
let (notifications, unsubscribe) = response_rx
|
||||
.await
|
||||
.map_err(|err| PubsubClientError::ConnectionClosed(err.to_string()))??;
|
||||
Ok((
|
||||
UnboundedReceiverStream::new(notifications)
|
||||
.filter_map(|value| ready(serde_json::from_value::<T>(value).ok()))
|
||||
.boxed(),
|
||||
unsubscribe,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn account_subscribe(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
config: Option<RpcAccountInfoConfig>,
|
||||
) -> SubscribeResult<'_, RpcResponse<UiAccount>> {
|
||||
let params = json!([pubkey.to_string(), config]);
|
||||
self.subscribe("account", params).await
|
||||
}
|
||||
|
||||
pub async fn block_subscribe(
|
||||
&self,
|
||||
filter: RpcBlockSubscribeFilter,
|
||||
config: Option<RpcBlockSubscribeConfig>,
|
||||
) -> SubscribeResult<'_, RpcResponse<RpcBlockUpdate>> {
|
||||
self.subscribe("block", json!([filter, config])).await
|
||||
}
|
||||
|
||||
pub async fn logs_subscribe(
|
||||
&self,
|
||||
filter: RpcTransactionLogsFilter,
|
||||
config: RpcTransactionLogsConfig,
|
||||
) -> SubscribeResult<'_, RpcResponse<RpcLogsResponse>> {
|
||||
self.subscribe("logs", json!([filter, config])).await
|
||||
}
|
||||
|
||||
pub async fn program_subscribe(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
config: Option<RpcProgramAccountsConfig>,
|
||||
) -> SubscribeResult<'_, RpcResponse<RpcKeyedAccount>> {
|
||||
let params = json!([pubkey.to_string(), config]);
|
||||
self.subscribe("program", params).await
|
||||
}
|
||||
|
||||
pub async fn vote_subscribe(&self) -> SubscribeResult<'_, RpcVote> {
|
||||
self.subscribe("vote", json!([])).await
|
||||
}
|
||||
|
||||
pub async fn root_subscribe(&self) -> SubscribeResult<'_, Slot> {
|
||||
self.subscribe("root", json!([])).await
|
||||
}
|
||||
|
||||
pub async fn signature_subscribe(
|
||||
&self,
|
||||
signature: &Signature,
|
||||
config: Option<RpcSignatureSubscribeConfig>,
|
||||
) -> SubscribeResult<'_, RpcResponse<RpcSignatureResult>> {
|
||||
let params = json!([signature.to_string(), config]);
|
||||
self.subscribe("signature", params).await
|
||||
}
|
||||
|
||||
pub async fn slot_subscribe(&self) -> SubscribeResult<'_, SlotInfo> {
|
||||
self.subscribe("slot", json!([])).await
|
||||
}
|
||||
|
||||
pub async fn slot_updates_subscribe(&self) -> SubscribeResult<'_, SlotUpdate> {
|
||||
self.subscribe("slotsUpdates", json!([])).await
|
||||
}
|
||||
|
||||
async fn run_ws(
|
||||
mut ws: WebSocketStream<MaybeTlsStream<TcpStream>>,
|
||||
mut subscribe_rx: mpsc::UnboundedReceiver<SubscribeRequestMsg>,
|
||||
mut shutdown_rx: oneshot::Receiver<()>,
|
||||
) -> PubsubClientResult {
|
||||
let mut request_id: u64 = 0;
|
||||
|
||||
let mut requests_subscribe = BTreeMap::new();
|
||||
let mut requests_unsubscribe = BTreeMap::<u64, oneshot::Sender<()>>::new();
|
||||
let mut subscriptions = BTreeMap::new();
|
||||
let (unsubscribe_tx, mut unsubscribe_rx) = mpsc::unbounded_channel();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// Send close on shutdown signal
|
||||
_ = (&mut shutdown_rx) => {
|
||||
let frame = CloseFrame { code: CloseCode::Normal, reason: "".into() };
|
||||
ws.send(Message::Close(Some(frame))).await?;
|
||||
ws.flush().await?;
|
||||
break;
|
||||
},
|
||||
// Send `Message::Ping` each 10s if no any other communication
|
||||
() = sleep(Duration::from_secs(10)) => {
|
||||
ws.send(Message::Ping(Vec::new())).await?;
|
||||
},
|
||||
// Read message for subscribe
|
||||
Some((operation, params, response_tx)) = subscribe_rx.recv() => {
|
||||
request_id += 1;
|
||||
let method = format!("{}Subscribe", operation);
|
||||
let text = json!({"jsonrpc":"2.0","id":request_id,"method":method,"params":params}).to_string();
|
||||
ws.send(Message::Text(text)).await?;
|
||||
requests_subscribe.insert(request_id, (operation, response_tx));
|
||||
},
|
||||
// Read message for unsubscribe
|
||||
Some((operation, sid, response_tx)) = unsubscribe_rx.recv() => {
|
||||
subscriptions.remove(&sid);
|
||||
request_id += 1;
|
||||
let method = format!("{}Unsubscribe", operation);
|
||||
let text = json!({"jsonrpc":"2.0","id":request_id,"method":method,"params":[sid]}).to_string();
|
||||
ws.send(Message::Text(text)).await?;
|
||||
requests_unsubscribe.insert(request_id, response_tx);
|
||||
},
|
||||
// Read incoming WebSocket message
|
||||
next_msg = ws.next() => {
|
||||
let msg = match next_msg {
|
||||
Some(msg) => msg?,
|
||||
None => break,
|
||||
};
|
||||
trace!("ws.next(): {:?}", &msg);
|
||||
|
||||
// Get text from the message
|
||||
let text = match msg {
|
||||
Message::Text(text) => text,
|
||||
Message::Binary(_data) => continue, // Ignore
|
||||
Message::Ping(data) => {
|
||||
ws.send(Message::Pong(data)).await?;
|
||||
continue
|
||||
},
|
||||
Message::Pong(_data) => continue,
|
||||
Message::Close(_frame) => break,
|
||||
Message::Frame(_frame) => continue,
|
||||
};
|
||||
|
||||
|
||||
let mut json: Map<String, Value> = serde_json::from_str(&text)?;
|
||||
|
||||
// Subscribe/Unsubscribe response, example:
|
||||
// `{"jsonrpc":"2.0","result":5308752,"id":1}`
|
||||
if let Some(id) = json.get("id") {
|
||||
let id = id.as_u64().ok_or_else(|| {
|
||||
PubsubClientError::SubscribeFailed { reason: "invalid `id` field".into(), message: text.clone() }
|
||||
})?;
|
||||
|
||||
let err = json.get("error").map(|error_object| {
|
||||
match serde_json::from_value::<RpcErrorObject>(error_object.clone()) {
|
||||
Ok(rpc_error_object) => {
|
||||
format!("{} ({})", rpc_error_object.message, rpc_error_object.code)
|
||||
}
|
||||
Err(err) => format!(
|
||||
"Failed to deserialize RPC error response: {} [{}]",
|
||||
serde_json::to_string(error_object).unwrap(),
|
||||
err
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(response_tx) = requests_unsubscribe.remove(&id) {
|
||||
let _ = response_tx.send(()); // do not care if receiver is closed
|
||||
} else if let Some((operation, response_tx)) = requests_subscribe.remove(&id) {
|
||||
match err {
|
||||
Some(reason) => {
|
||||
let _ = response_tx.send(Err(PubsubClientError::SubscribeFailed { reason, message: text.clone()}));
|
||||
},
|
||||
None => {
|
||||
// Subscribe Id
|
||||
let sid = json.get("result").and_then(Value::as_u64).ok_or_else(|| {
|
||||
PubsubClientError::SubscribeFailed { reason: "invalid `result` field".into(), message: text.clone() }
|
||||
})?;
|
||||
|
||||
// Create notifications channel and unsubscribe function
|
||||
let (notifications_tx, notifications_rx) = mpsc::unbounded_channel();
|
||||
let unsubscribe_tx = unsubscribe_tx.clone();
|
||||
let unsubscribe = Box::new(move || async move {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
// do nothing if ws already closed
|
||||
if unsubscribe_tx.send((operation, sid, response_tx)).is_ok() {
|
||||
let _ = response_rx.await; // channel can be closed only if ws is closed
|
||||
}
|
||||
}.boxed());
|
||||
|
||||
if response_tx.send(Ok((notifications_rx, unsubscribe))).is_err() {
|
||||
break;
|
||||
}
|
||||
subscriptions.insert(sid, notifications_tx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!("Unknown request id: {}", id);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Notification, example:
|
||||
// `{"jsonrpc":"2.0","method":"logsNotification","params":{"result":{...},"subscription":3114862}}`
|
||||
if let Some(Value::Object(params)) = json.get_mut("params") {
|
||||
if let Some(sid) = params.get("subscription").and_then(Value::as_u64) {
|
||||
let mut unsubscribe_required = false;
|
||||
|
||||
if let Some(notifications_tx) = subscriptions.get(&sid) {
|
||||
if let Some(result) = params.remove("result") {
|
||||
if notifications_tx.send(result).is_err() {
|
||||
unsubscribe_required = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unsubscribe_required = true;
|
||||
}
|
||||
|
||||
if unsubscribe_required {
|
||||
if let Some(Value::String(method)) = json.remove("method") {
|
||||
if let Some(operation) = method.strip_suffix("Notification") {
|
||||
let (response_tx, _response_rx) = oneshot::channel();
|
||||
let _ = unsubscribe_tx.send((operation.to_string(), sid, response_tx));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// see client-test/test/client.rs
|
||||
}
|
5128
client/src/nonblocking/rpc_client.rs
Normal file
5128
client/src/nonblocking/rpc_client.rs
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user