Compare commits
686 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cdd3f835f | ||
|
|
c4e04f70d0 | ||
|
|
5d971472b2 | ||
|
|
f1201502d4 | ||
|
|
fd5222ad21 | ||
|
|
768a5f2b40 | ||
|
|
87b57b53f9 | ||
|
|
55a64c8945 | ||
|
|
e8c6233c6e | ||
|
|
f51b214449 | ||
|
|
8fe8a5717e | ||
|
|
9adf8b4fc8 | ||
|
|
82772f95a1 | ||
|
|
0b5d3df251 | ||
|
|
e63fdba252 | ||
|
|
5e65b7cbd9 | ||
|
|
68d0fe2dbc | ||
|
|
3aad5f563e | ||
|
|
ccfe09e460 | ||
|
|
6fd57fafc8 | ||
|
|
c7d857583f | ||
|
|
e29b7876ad | ||
|
|
de479ebda9 | ||
|
|
d3447f2f41 | ||
|
|
d9e14b4a82 | ||
|
|
94b97e4b56 | ||
|
|
abd977b819 | ||
|
|
36eafa56a3 | ||
|
|
06a63549c1 | ||
|
|
a4047bb9c8 | ||
|
|
a235423000 | ||
|
|
726eadc64b | ||
|
|
4d18144232 | ||
|
|
342cf90ce1 | ||
|
|
3ec109a0e4 | ||
|
|
2634402fef | ||
|
|
997f317c23 | ||
|
|
7bc915c0d1 | ||
|
|
8651f058eb | ||
|
|
b6d6ff786a | ||
|
|
b9a80152df | ||
|
|
e9dda5ebd7 | ||
|
|
5f0be1793c | ||
|
|
2d8533075d | ||
|
|
bf382c6069 | ||
|
|
366e426f2b | ||
|
|
fa34e6e419 | ||
|
|
ab9fe5e9ad | ||
|
|
3474419111 | ||
|
|
ff21251416 | ||
|
|
7e6bbc7b77 | ||
|
|
82783b18ea | ||
|
|
b7c6f38665 | ||
|
|
11da07eca7 | ||
|
|
b6b779d2c4 | ||
|
|
1c85d62fe4 | ||
|
|
867a213cd3 | ||
|
|
c51a18a887 | ||
|
|
206ff02be9 | ||
|
|
8d7e90e9b8 | ||
|
|
eb11db3e3e | ||
|
|
8d8ad84527 | ||
|
|
fa059bb3c3 | ||
|
|
9652e832c2 | ||
|
|
52e27712e1 | ||
|
|
c00ec26a3b | ||
|
|
50eba96b58 | ||
|
|
e7c0629951 | ||
|
|
a08235da9a | ||
|
|
b213004157 | ||
|
|
92562b4349 | ||
|
|
01c490d354 | ||
|
|
cfdc0eb99e | ||
|
|
0b7b3c9f20 | ||
|
|
5cd685ed3a | ||
|
|
9498f11d46 | ||
|
|
558324b861 | ||
|
|
9a5fc3513a | ||
|
|
b7c6e139e6 | ||
|
|
a9d2fa6aad | ||
|
|
056a9952c3 | ||
|
|
fc21c857a3 | ||
|
|
614ad64ec1 | ||
|
|
f72c186004 | ||
|
|
59a2b05b44 | ||
|
|
bed6e566ef | ||
|
|
e85f9fcb73 | ||
|
|
8cb3953c9a | ||
|
|
d8e885f425 | ||
|
|
28fa5149b7 | ||
|
|
190acd7d15 | ||
|
|
909316bd53 | ||
|
|
000b763e95 | ||
|
|
9d00ff6624 | ||
|
|
fa254ff18f | ||
|
|
f78df36363 | ||
|
|
bae6fe17e9 | ||
|
|
4b1d338e04 | ||
|
|
8079359420 | ||
|
|
3aa52f95a2 | ||
|
|
3d88b9ac22 | ||
|
|
948487d9a7 | ||
|
|
d775855a23 | ||
|
|
3f41d60793 | ||
|
|
7a6543eb5b | ||
|
|
892abd2a24 | ||
|
|
8fef8eaed9 | ||
|
|
b7bd9d9fbb | ||
|
|
a8eb9357cb | ||
|
|
87601facf1 | ||
|
|
4facdb25d0 | ||
|
|
bef59c3bd7 | ||
|
|
307064949a | ||
|
|
40cb8d857b | ||
|
|
972381efff | ||
|
|
a2098c9ea9 | ||
|
|
5af9963ea9 | ||
|
|
69736a792c | ||
|
|
59446d5c50 | ||
|
|
ac538d0395 | ||
|
|
e79c910c41 | ||
|
|
0a7ef32ec7 | ||
|
|
1f6a7c174a | ||
|
|
109bfc3e7a | ||
|
|
682b700ec8 | ||
|
|
89bfe5fab0 | ||
|
|
fbcc107086 | ||
|
|
9c6f613f8c | ||
|
|
34f5f48e43 | ||
|
|
09cd6197c2 | ||
|
|
4a86a794ed | ||
|
|
7cd1c06a50 | ||
|
|
240433ef25 | ||
|
|
0afb058616 | ||
|
|
f1cda98aeb | ||
|
|
d5cbac41cb | ||
|
|
f19209b23d | ||
|
|
fd670d0ae0 | ||
|
|
948d869c49 | ||
|
|
2bab4ace8b | ||
|
|
1ddff68642 | ||
|
|
c0b250285a | ||
|
|
4509579e10 | ||
|
|
9b8d59cc2b | ||
|
|
bd91fc2985 | ||
|
|
9a3ebe0b20 | ||
|
|
6c08dc9c9d | ||
|
|
740c1df045 | ||
|
|
0a5905a02c | ||
|
|
237eceb6c1 | ||
|
|
dabbdcf988 | ||
|
|
3fceaf694c | ||
|
|
34df5ad364 | ||
|
|
573aed2b4b | ||
|
|
6ef65d8513 | ||
|
|
7c6fb3d554 | ||
|
|
886eaac211 | ||
|
|
6ca69a1525 | ||
|
|
1cc2f67391 | ||
|
|
5d547130f0 | ||
|
|
facb209720 | ||
|
|
5e215ac854 | ||
|
|
d3dd9ec6e2 | ||
|
|
12ed7c6845 | ||
|
|
f9d68b5d86 | ||
|
|
8f9f11e37f | ||
|
|
6a5f67f78c | ||
|
|
a505e92487 | ||
|
|
701300334a | ||
|
|
b9cf02fd6a | ||
|
|
71cb8de0dd | ||
|
|
13e5b479eb | ||
|
|
2ad435587a | ||
|
|
8f54d409e2 | ||
|
|
b4345c039a | ||
|
|
e61545ad18 | ||
|
|
961d1f0ee5 | ||
|
|
b260f686a3 | ||
|
|
3cfc38850b | ||
|
|
f12a933a54 | ||
|
|
135763e019 | ||
|
|
aaec7de881 | ||
|
|
420ea2f143 | ||
|
|
cb2dd56317 | ||
|
|
a420d1e91e | ||
|
|
0073448afc | ||
|
|
086cdd8ef7 | ||
|
|
dd57cbd6a4 | ||
|
|
8937a1db3b | ||
|
|
89a914f7c1 | ||
|
|
cf9936a314 | ||
|
|
6f95524be3 | ||
|
|
8021d368fe | ||
|
|
d7c43f0c0b | ||
|
|
6765453f8a | ||
|
|
adb0824da5 | ||
|
|
f86dcec94b | ||
|
|
8f28989520 | ||
|
|
1823d7bdec | ||
|
|
892a3b6dc4 | ||
|
|
cc987b8884 | ||
|
|
32d616da1e | ||
|
|
6d62d0cd42 | ||
|
|
c7d6e2b4a5 | ||
|
|
d6f1e4b10a | ||
|
|
73dad25d74 | ||
|
|
a895ce51ee | ||
|
|
3f95e7f055 | ||
|
|
a54042fc11 | ||
|
|
68525a961f | ||
|
|
45093c8092 | ||
|
|
c3227ab671 | ||
|
|
967c178f5d | ||
|
|
310aa1a63f | ||
|
|
d5ae850169 | ||
|
|
89f5153316 | ||
|
|
677008b6cc | ||
|
|
7936f34df8 | ||
|
|
65f0187324 | ||
|
|
8dc5d10f9c | ||
|
|
58d8c3ad70 | ||
|
|
7df45cf58a | ||
|
|
3379a8470d | ||
|
|
0969e87b08 | ||
|
|
7a0dcdd1a4 | ||
|
|
34893d2449 | ||
|
|
ec8d1c5e2b | ||
|
|
e1dbed25b6 | ||
|
|
3b08a2a116 | ||
|
|
7e42eca4b0 | ||
|
|
580304add4 | ||
|
|
b58ce6c740 | ||
|
|
0b27d0b363 | ||
|
|
6ea74c3d29 | ||
|
|
15631f8194 | ||
|
|
b87a1d2bc5 | ||
|
|
eae98ad8ab | ||
|
|
3a6c23e995 | ||
|
|
2e3db6aba8 | ||
|
|
f1e635d088 | ||
|
|
cc07c86aab | ||
|
|
543b6016ea | ||
|
|
f4e05909f7 | ||
|
|
5da1466d08 | ||
|
|
7a8528793e | ||
|
|
4a0338c902 | ||
|
|
11b4da4146 | ||
|
|
33c19130b5 | ||
|
|
0c7689206c | ||
|
|
756bc3b5bb | ||
|
|
571b2eb807 | ||
|
|
9819fe6684 | ||
|
|
ec7e44659d | ||
|
|
40d0f8da2d | ||
|
|
47ddb84078 | ||
|
|
4649378f95 | ||
|
|
3f6027055c | ||
|
|
d61a46476a | ||
|
|
c112f51f97 | ||
|
|
c1351d6b12 | ||
|
|
c1acfe4843 | ||
|
|
68a4288078 | ||
|
|
c4c96e1460 | ||
|
|
32ab57fa83 | ||
|
|
a33e8cc164 | ||
|
|
c8b4f616b0 | ||
|
|
380c3b0080 | ||
|
|
2d6847c27b | ||
|
|
d5b9899ac9 | ||
|
|
9817cd769a | ||
|
|
ec3d2fdbdc | ||
|
|
1f794fb1da | ||
|
|
89e1d7300d | ||
|
|
d239550e68 | ||
|
|
3dc336e1f1 | ||
|
|
220a369efa | ||
|
|
b079564a13 | ||
|
|
e8935aa99e | ||
|
|
016a342de0 | ||
|
|
47c6dfe1aa | ||
|
|
c66d528e85 | ||
|
|
8ba8deb933 | ||
|
|
587342d5e3 | ||
|
|
f31d2d9cc4 | ||
|
|
bc761c2c02 | ||
|
|
6f4bc3aaff | ||
|
|
070664ff94 | ||
|
|
61c2883de6 | ||
|
|
e32f7dbe49 | ||
|
|
c0b178db45 | ||
|
|
1027b0681b | ||
|
|
3ae6e0b8ab | ||
|
|
4b7da6e60d | ||
|
|
2863f8ec65 | ||
|
|
e2491c6322 | ||
|
|
4a8b1d9b2c | ||
|
|
74aed5cb58 | ||
|
|
b130c298df | ||
|
|
e5a6f8c2de | ||
|
|
87e5f8acbf | ||
|
|
c1a3b6ecc2 | ||
|
|
c242d66130 | ||
|
|
864d212c64 | ||
|
|
a9564d207b | ||
|
|
b82a9c832b | ||
|
|
5d9298543f | ||
|
|
4e9ae61044 | ||
|
|
d47262d233 | ||
|
|
8fdcf9f968 | ||
|
|
c82d37f6c3 | ||
|
|
5a8658283a | ||
|
|
4b97e58cba | ||
|
|
48031651a0 | ||
|
|
f3d556e3f9 | ||
|
|
8d4cecdb77 | ||
|
|
39a622f66e | ||
|
|
dae28b9cfe | ||
|
|
b7b4aa5d4d | ||
|
|
ed036b978d | ||
|
|
284920433f | ||
|
|
30bed18b77 | ||
|
|
6678dd10a5 | ||
|
|
296d740f83 | ||
|
|
b8fda9d730 | ||
|
|
2623c71ed3 | ||
|
|
e4472db33f | ||
|
|
076fef5e57 | ||
|
|
40eba48109 | ||
|
|
095c79e863 | ||
|
|
959c1ea857 | ||
|
|
ef3af104ae | ||
|
|
9dc69d9843 | ||
|
|
45348b2c83 | ||
|
|
c558db2a48 | ||
|
|
f987c18a7e | ||
|
|
5d3f43c10b | ||
|
|
216b01b224 | ||
|
|
35dd52e9ba | ||
|
|
b0c83921be | ||
|
|
e744b15ad2 | ||
|
|
1fd695d337 | ||
|
|
8f38bc7dc0 | ||
|
|
7d6ea6c17e | ||
|
|
56dc958116 | ||
|
|
19dfb87b1f | ||
|
|
a5287f56fc | ||
|
|
eed8087d87 | ||
|
|
4115d73b9a | ||
|
|
064b95c16a | ||
|
|
70c167182a | ||
|
|
fee002382e | ||
|
|
d75a470ffa | ||
|
|
c530fbd22b | ||
|
|
1b8f9e75dd | ||
|
|
1a5b01676d | ||
|
|
4b397d15b3 | ||
|
|
4d2b83d01f | ||
|
|
87096f13d2 | ||
|
|
a0ffcc61ae | ||
|
|
4b4819cd07 | ||
|
|
ca791a0378 | ||
|
|
b08f8d3103 | ||
|
|
88ba8439fc | ||
|
|
4dd0367136 | ||
|
|
ff2c183ac1 | ||
|
|
aa24181a53 | ||
|
|
1f83c56e05 | ||
|
|
2592894958 | ||
|
|
85027caf42 | ||
|
|
3ea556bc24 | ||
|
|
ca4a22d4ba | ||
|
|
18c1f0dfe9 | ||
|
|
734afee5e0 | ||
|
|
271e17547a | ||
|
|
e28368ff1b | ||
|
|
1aab959d4e | ||
|
|
bca769111f | ||
|
|
909321928c | ||
|
|
8b0a7f6838 | ||
|
|
5fa36bbab3 | ||
|
|
d65a7a3c30 | ||
|
|
453f5ce8f2 | ||
|
|
dc1db33ec9 | ||
|
|
c68e80c93b | ||
|
|
6b9a0935c1 | ||
|
|
b84468ecd3 | ||
|
|
ff4ba54553 | ||
|
|
f78a90bce2 | ||
|
|
24d871b529 | ||
|
|
e547f38589 | ||
|
|
6fb16f9879 | ||
|
|
2dc50cff5b | ||
|
|
98228c392e | ||
|
|
aeb7278b00 | ||
|
|
42d7609d54 | ||
|
|
a70008cc5c | ||
|
|
306a5c849e | ||
|
|
bb92184085 | ||
|
|
90c9462dd4 | ||
|
|
21b287ef0b | ||
|
|
b0c524765e | ||
|
|
6d0318cbe6 | ||
|
|
8f5ee6832f | ||
|
|
38fe766fa7 | ||
|
|
74866882f2 | ||
|
|
c638e83bf5 | ||
|
|
de6ef68571 | ||
|
|
c51049a59b | ||
|
|
9cedeb0a8d | ||
|
|
e37a4823f1 | ||
|
|
bf60345b7a | ||
|
|
cb29b8dd2a | ||
|
|
3a501ad69e | ||
|
|
e6e43d236f | ||
|
|
142601d4b6 | ||
|
|
f192e4f08f | ||
|
|
f020370ae7 | ||
|
|
24935af867 | ||
|
|
6a213bc8f5 | ||
|
|
f0414711b7 | ||
|
|
d087ed5bf6 | ||
|
|
d14dea4660 | ||
|
|
29abfebb68 | ||
|
|
668dfc40c7 | ||
|
|
61514e3b0e | ||
|
|
46fcab14dd | ||
|
|
2435c3ce0c | ||
|
|
55907b2167 | ||
|
|
a03eff51af | ||
|
|
10175618d2 | ||
|
|
4ff033852d | ||
|
|
2237f47b90 | ||
|
|
bfca226964 | ||
|
|
6077458ad8 | ||
|
|
7079559c2d | ||
|
|
0641244378 | ||
|
|
563da2bb18 | ||
|
|
dc347dd3d7 | ||
|
|
eab4fe50a3 | ||
|
|
ead6dc553a | ||
|
|
009c124fac | ||
|
|
7029c88305 | ||
|
|
9411fc00b8 | ||
|
|
5a93a4c466 | ||
|
|
9afc5da2e1 | ||
|
|
49706172f3 | ||
|
|
b2a0cdaa38 | ||
|
|
5481d1a039 | ||
|
|
dd5e320aa1 | ||
|
|
3c2aff2b5b | ||
|
|
c3c4c9326b | ||
|
|
ae70f4ea92 | ||
|
|
29fb79382c | ||
|
|
5c2cf04e10 | ||
|
|
9e0a26628b | ||
|
|
ce88602ced | ||
|
|
53b8d0d528 | ||
|
|
96a61cc4e4 | ||
|
|
b7b36bb0a4 | ||
|
|
52b254071c | ||
|
|
fbf2dd1672 | ||
|
|
4bbf09f582 | ||
|
|
952cd38b7b | ||
|
|
9a79be5ca0 | ||
|
|
2182521a8b | ||
|
|
fe65c2ae02 | ||
|
|
554d36c74b | ||
|
|
29ef0916db | ||
|
|
f93c8290f4 | ||
|
|
a69293df24 | ||
|
|
48ac038f7a | ||
|
|
5a7d2560c9 | ||
|
|
d91027f771 | ||
|
|
deaf3cb416 | ||
|
|
f95e1ea40f | ||
|
|
f64ab49307 | ||
|
|
fe1c99c0cf | ||
|
|
bdb7b73b8a | ||
|
|
293fff90d3 | ||
|
|
6eb4973780 | ||
|
|
5f5824d78d | ||
|
|
0ef9d79056 | ||
|
|
215650f6e7 | ||
|
|
a0d0d4c0e9 | ||
|
|
0422af2aae | ||
|
|
cef8e42938 | ||
|
|
0eeeec38fa | ||
|
|
75a84ecdae | ||
|
|
87c507fdbe | ||
|
|
3783ae823d | ||
|
|
f3ed00e28e | ||
|
|
307d023b2e | ||
|
|
775ce3a03f | ||
|
|
f655372b08 | ||
|
|
2c4079f4c8 | ||
|
|
ac1f90f1a9 | ||
|
|
4bb55b1622 | ||
|
|
23c5bb17c7 | ||
|
|
a0ed3261c9 | ||
|
|
261732f140 | ||
|
|
595c96b262 | ||
|
|
496999beba | ||
|
|
bb50881346 | ||
|
|
948902eae0 | ||
|
|
e41ff2df66 | ||
|
|
f88b79d42b | ||
|
|
1a0dd53450 | ||
|
|
9872430bd2 | ||
|
|
ae8badb141 | ||
|
|
36fa3a1a0a | ||
|
|
df8a69d15f | ||
|
|
fad08a19cc | ||
|
|
6527d05d77 | ||
|
|
d303e6b94e | ||
|
|
5fa397ceed | ||
|
|
c0fd017906 | ||
|
|
74e7da214a | ||
|
|
756ba07b16 | ||
|
|
5c236fd06c | ||
|
|
f671be814e | ||
|
|
e277437bd2 | ||
|
|
beead7e54d | ||
|
|
ea010be5cb | ||
|
|
97b6c41d42 | ||
|
|
6d0f3762b2 | ||
|
|
132a2a73af | ||
|
|
eab80d0aea | ||
|
|
88b1383eed | ||
|
|
ff74452ef3 | ||
|
|
bf8e9b3d71 | ||
|
|
de34187db0 | ||
|
|
acb23e8ef0 | ||
|
|
f992ee3140 | ||
|
|
97986a5241 | ||
|
|
a7d1346d51 | ||
|
|
983ec5debc | ||
|
|
cb28ac3aed | ||
|
|
a817a7c889 | ||
|
|
a5f2444ad2 | ||
|
|
cea8067219 | ||
|
|
4db074a5aa | ||
|
|
3eb00ef60f | ||
|
|
ca8bf8f964 | ||
|
|
39b3ce9bd3 | ||
|
|
4caa313aef | ||
|
|
a78a339407 | ||
|
|
0919b13c87 | ||
|
|
f2b0e2f418 | ||
|
|
cb6848aa80 | ||
|
|
542691c4e4 | ||
|
|
8ad6a8767f | ||
|
|
2242b1b4a5 | ||
|
|
8df4d8b905 | ||
|
|
7fad53b112 | ||
|
|
9d667db634 | ||
|
|
f47a789b15 | ||
|
|
5e3ce30d02 | ||
|
|
97c5fb8141 | ||
|
|
0e3a8fa6d9 | ||
|
|
5eae76c66e | ||
|
|
849f79e4ed | ||
|
|
ff7cf839d8 | ||
|
|
f3cbd243cc | ||
|
|
f146c92e88 | ||
|
|
fb2620b3a5 | ||
|
|
fd00e5cb35 | ||
|
|
44fde2d964 | ||
|
|
448b957a13 | ||
|
|
01607b9860 | ||
|
|
23d8c7ff0e | ||
|
|
b321da00b4 | ||
|
|
dec3da8f9d | ||
|
|
80aae18794 | ||
|
|
1f2aaf3f98 | ||
|
|
2534a028c0 | ||
|
|
fc409d9262 | ||
|
|
b70d195473 | ||
|
|
7eedff2714 | ||
|
|
6d9185d121 | ||
|
|
f89c22b5ee | ||
|
|
f23dc11a86 | ||
|
|
09a0325534 | ||
|
|
408d5da50f | ||
|
|
561808cf90 | ||
|
|
25df95be6f | ||
|
|
b85d7c1f70 | ||
|
|
642720a2fe | ||
|
|
1cc7131bb7 | ||
|
|
8f60f1093a | ||
|
|
d3b458dd9b | ||
|
|
a08e2cc434 | ||
|
|
b83a0434a4 | ||
|
|
b68b74ac32 | ||
|
|
b084c1d437 | ||
|
|
63ed892502 | ||
|
|
1cb6101c6a | ||
|
|
be0cc0273f | ||
|
|
abf33b3b3b | ||
|
|
d9b0490f72 | ||
|
|
caa70d2bca | ||
|
|
4f05f08f5d | ||
|
|
0c76b89e55 | ||
|
|
08ab4b93ea | ||
|
|
f0028b6972 | ||
|
|
b6553357f9 | ||
|
|
d86103383a | ||
|
|
1265afebbb | ||
|
|
306783c661 | ||
|
|
8ec8204a30 | ||
|
|
8cf3ef895d | ||
|
|
e4498adb1f | ||
|
|
42c5c59800 | ||
|
|
8ef8c9094a | ||
|
|
8dc4724340 | ||
|
|
13551885c2 | ||
|
|
d677e83ed4 | ||
|
|
5d9130a3c4 | ||
|
|
1ca4913328 | ||
|
|
b7614abb9e | ||
|
|
862a4a243f | ||
|
|
db291234ed | ||
|
|
2a5605db24 | ||
|
|
b4362cc18b | ||
|
|
6a5a6387e2 | ||
|
|
0f31adeafb | ||
|
|
ae817722d8 | ||
|
|
90bedd7e06 | ||
|
|
7d27be2a73 | ||
|
|
74da2de3b7 | ||
|
|
35db70a56c | ||
|
|
7dac8e2dde | ||
|
|
82c6992d6f | ||
|
|
4831c7b9af | ||
|
|
113db8d656 | ||
|
|
de6679ea95 | ||
|
|
0b66ae5c53 | ||
|
|
61a20febb9 | ||
|
|
29f81577e9 | ||
|
|
3acf956f6f | ||
|
|
87b13bef8e | ||
|
|
0d4cb252c4 | ||
|
|
fcabc6f799 | ||
|
|
848c43a9ab | ||
|
|
5f766cd20b | ||
|
|
8c07ba635e | ||
|
|
bb07aecfec | ||
|
|
27c5ec0149 | ||
|
|
4f01db0482 | ||
|
|
f2f8a7a90e | ||
|
|
e743414908 | ||
|
|
f6f0f94e17 | ||
|
|
d47a47924a | ||
|
|
7a2bf7e7eb | ||
|
|
d5a7867087 | ||
|
|
fbf78b83c4 | ||
|
|
2c63cf3cbd | ||
|
|
3b648e71e6 | ||
|
|
021d0a46f8 | ||
|
|
8839dbfe5b | ||
|
|
407d058611 | ||
|
|
c6a7f499ce | ||
|
|
d821fd29d6 | ||
|
|
6b99ab3a57 | ||
|
|
004f1d5aed | ||
|
|
1caeea8bc2 | ||
|
|
6ce4a1a18d | ||
|
|
0b48c8eb35 | ||
|
|
fef913085e | ||
|
|
2059af822d | ||
|
|
0fe74e95fe | ||
|
|
b7755123c1 | ||
|
|
39282be486 | ||
|
|
b18e4057bb | ||
|
|
12a9b5f35e | ||
|
|
89baa94002 | ||
|
|
1ef3478709 | ||
|
|
73063544bd | ||
|
|
90240bf11d | ||
|
|
5c5a06198c | ||
|
|
394933e53c | ||
|
|
b106d3ba60 | ||
|
|
947a339714 | ||
|
|
edb18349c9 | ||
|
|
9dcb965959 | ||
|
|
72ae82fe47 | ||
|
|
2d9d2f1e99 |
@@ -1,42 +0,0 @@
|
||||
version: '{build}'
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v[0-9.]+\.[0-9.]+/
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.cargo'
|
||||
- '%APPVEYOR_BUILD_FOLDER%\target'
|
||||
|
||||
clone_folder: d:\projects\solana
|
||||
|
||||
build_script:
|
||||
- bash ci/publish-tarball.sh
|
||||
|
||||
notifications:
|
||||
- provider: Slack
|
||||
incoming_webhook:
|
||||
secure: GJsBey+F5apAtUm86MHVJ68Uqa6WN1SImcuIc4TsTZrDhA8K1QWUNw9FFQPybUWDyOcS5dly3kubnUqlGt9ux6Ad2efsfRIQYWv0tOVXKeY=
|
||||
channel: ci-status
|
||||
on_build_success: false
|
||||
on_build_failure: true
|
||||
on_build_status_changed: true
|
||||
|
||||
deploy:
|
||||
- provider: S3
|
||||
access_key_id:
|
||||
secure: fTbJl6JpFebR40J7cOWZ2mXBa3kIvEiXgzxAj6L3N7A=
|
||||
secret_access_key:
|
||||
secure: vItsBXb2rEFLvkWtVn/Rcxu5a5+2EwC+b7GsA0waJy9hXh6XuBAD0lnHd9re3g/4
|
||||
bucket: release.solana.com
|
||||
region: us-west-1
|
||||
set_public: true
|
||||
|
||||
- provider: GitHub
|
||||
auth_token:
|
||||
secure: 81fEmPZ0cV1wLtNuUrcmtgxKF6ROQF1+/ft5m+fHX21z6PoeCbaNo8cTyLioWBj7
|
||||
draft: false
|
||||
prerelease: false
|
||||
on:
|
||||
appveyor_repo_tag: true
|
||||
2
.buildkite/env/secrets.ejson
vendored
2
.buildkite/env/secrets.ejson
vendored
@@ -7,6 +7,6 @@
|
||||
"GITHUB_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:Vq2dkGTOzfEpRht0BAGHFp/hDogMvXJe:tFXHg1epVt2mq9hkuc5sRHe+KAnVREi/p8S+IZu67XRyzdiA/nGak1k860FXYuuzuaE0QWekaEc=]",
|
||||
"INFLUX_DATABASE": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:5KI9WBkXx3R/W4m256mU5MJOE7N8aAT9:Cb8QFELZ9I60t5zhJ9h55Kcs]",
|
||||
"INFLUX_PASSWORD": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:hQRMpLCrav+OYkNphkeM4hagdVoZv5Iw:AUO76rr6+gF1OLJA8ZLSG8wHKXgYCPNk6gRCV8rBhZBJ4KwDaxpvOhMl7bxxXG6jol7v4aRa/Lk=]",
|
||||
"INFLUX_USERNAME": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R7BNmQjfeqoGDAFTJu9bYTGHol2NgnYN:Q2tOT/EBcFvhFk+DKLKmVU7tLCpVC3Ui]",
|
||||
"INFLUX_USERNAME": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R7BNmQjfeqoGDAFTJu9bYTGHol2NgnYN:Q2tOT/EBcFvhFk+DKLKmVU7tLCpVC3Ui]"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,3 +3,16 @@ root: ./docs/src
|
||||
structure:
|
||||
readme: introduction.md
|
||||
summary: SUMMARY.md
|
||||
|
||||
redirects:
|
||||
wallet: ./wallet-guide/README.md
|
||||
wallet/app-wallets: ./wallet-guide/apps.md
|
||||
wallet/app-wallets/trust-wallet: ./wallet-guide/trust-wallet.md
|
||||
wallet/app-wallets/ledger-live: ./wallet-guide/ledger-live.md
|
||||
wallet/cli-wallets: ./wallet-guide/cli.md
|
||||
wallet/cli-wallets/paper-wallet: ./paper-wallet/README.md
|
||||
wallet/cli-wallets/paper-wallet/paper-wallet-usage: ./paper-wallet/paper-wallet-usage.md
|
||||
wallet/cli-wallets/remote-wallet: ./hardware-wallets/README.md
|
||||
wallet/cli-wallets/remote-wallet/ledger: ./hardware-wallets/ledger.md
|
||||
wallet/cli-wallets/file-system-wallet: ./file-system-wallet/README.md
|
||||
wallet/support: ./wallet-guide/support.md
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
/docs/html/
|
||||
/docs/src/tests.ok
|
||||
/docs/src/cli/usage.md
|
||||
/docs/src/.gitbook/assets/*.svg
|
||||
/farf/
|
||||
/solana-release/
|
||||
|
||||
11
.mergify.yml
11
.mergify.yml
@@ -19,20 +19,13 @@ pull_request_rules:
|
||||
label:
|
||||
add:
|
||||
- automerge
|
||||
- name: v0.23 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.23
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.23
|
||||
- name: v1.0 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v1.0
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.0
|
||||
- name: v1.1 backport
|
||||
@@ -41,6 +34,7 @@ pull_request_rules:
|
||||
- label=v1.1
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.1
|
||||
- name: v1.2 backport
|
||||
@@ -49,5 +43,6 @@ pull_request_rules:
|
||||
- label=v1.2
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.2
|
||||
|
||||
@@ -45,7 +45,7 @@ $ git pull --rebase upstream master
|
||||
|
||||
If there are no functional changes, PRs can be very large and that's no
|
||||
problem. If, however, your changes are making meaningful changes or additions,
|
||||
then about 1000 lines of changes is about the most you should ask a Solana
|
||||
then about 1,000 lines of changes is about the most you should ask a Solana
|
||||
maintainer to review.
|
||||
|
||||
### Should I send small PRs as I develop large, new components?
|
||||
|
||||
6792
Cargo.lock
generated
6792
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ members = [
|
||||
"bench-exchange",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"accounts-bench",
|
||||
"banking-bench",
|
||||
"chacha",
|
||||
"chacha-cuda",
|
||||
@@ -10,6 +11,8 @@ members = [
|
||||
"cli-config",
|
||||
"client",
|
||||
"core",
|
||||
"dos",
|
||||
"download-utils",
|
||||
"faucet",
|
||||
"perf",
|
||||
"validator",
|
||||
@@ -24,6 +27,7 @@ members = [
|
||||
"logger",
|
||||
"log-analyzer",
|
||||
"merkle-tree",
|
||||
"streamer",
|
||||
"measure",
|
||||
"metrics",
|
||||
"net-shaper",
|
||||
@@ -48,9 +52,13 @@ members = [
|
||||
"sdk",
|
||||
"sdk-c",
|
||||
"scripts",
|
||||
"stake-accounts",
|
||||
"stake-monitor",
|
||||
"sys-tuner",
|
||||
"transaction-status",
|
||||
"upload-perf",
|
||||
"net-utils",
|
||||
"version",
|
||||
"vote-signer",
|
||||
"cli",
|
||||
"rayon-threadlimit",
|
||||
|
||||
41
README.md
41
README.md
@@ -9,46 +9,7 @@ Blockchain Rebuilt for Scale
|
||||
Solana™ is a new blockchain architecture built from the ground up for scale. The architecture supports
|
||||
up to 710 thousand transactions per second on a gigabit network.
|
||||
|
||||
Documentation
|
||||
===
|
||||
|
||||
Before you jump into the code, review the documentation [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com).
|
||||
|
||||
(The _latest_ development version of the docs is [available here](https://docs.solana.com/v/master).)
|
||||
|
||||
Release Binaries
|
||||
===
|
||||
Official release binaries are available at [Github Releases](https://github.com/solana-labs/solana/releases).
|
||||
|
||||
Additionally we provide pre-release binaries for the latest code on the edge and
|
||||
beta channels. Note that these pre-release binaries may be less stable than an
|
||||
official release.
|
||||
|
||||
### Edge channel
|
||||
#### Linux (x86_64-unknown-linux-gnu)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
|
||||
#### mac OS (x86_64-apple-darwin)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-apple-darwin.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
|
||||
#### Windows (x86_64-pc-windows-msvc)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-pc-windows-msvc.tar.bz2)
|
||||
* [solana-install-init.exe](http://release.solana.com/edge/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
|
||||
#### All platforms
|
||||
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/edge/solana-metrics.tar.bz2)
|
||||
|
||||
### Beta channel
|
||||
#### Linux (x86_64-unknown-linux-gnu)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
|
||||
#### mac OS (x86_64-apple-darwin)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-apple-darwin.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
|
||||
#### Windows (x86_64-pc-windows-msvc)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-pc-windows-msvc.tar.bz2)
|
||||
* [solana-install-init.exe](http://release.solana.com/beta/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
|
||||
#### All platforms
|
||||
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/beta/solana-metrics.tar.bz2)
|
||||
Read all about it at [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com/v/master).
|
||||
|
||||
Developing
|
||||
===
|
||||
|
||||
22
accounts-bench/Cargo.toml
Normal file
22
accounts-bench/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-accounts-bench"
|
||||
version = "1.1.15"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.6"
|
||||
rayon = "1.3.0"
|
||||
solana-logger = { path = "../logger", version = "1.1.15" }
|
||||
solana-runtime = { path = "../runtime", version = "1.1.15" }
|
||||
solana-measure = { path = "../measure", version = "1.1.15" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.15" }
|
||||
rand = "0.7.0"
|
||||
clap = "2.33.0"
|
||||
crossbeam-channel = "0.4"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
105
accounts-bench/src/main.rs
Normal file
105
accounts-bench/src/main.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use clap::{value_t, App, Arg};
|
||||
use rayon::prelude::*;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_runtime::{
|
||||
accounts::{create_test_accounts, update_accounts, Accounts},
|
||||
accounts_index::Ancestors,
|
||||
};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
|
||||
let matches = App::new("crate")
|
||||
.about("about")
|
||||
.version("version")
|
||||
.arg(
|
||||
Arg::with_name("num_slots")
|
||||
.long("num_slots")
|
||||
.takes_value(true)
|
||||
.value_name("SLOTS")
|
||||
.help("Number of slots to store to."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num_accounts")
|
||||
.long("num_accounts")
|
||||
.takes_value(true)
|
||||
.value_name("NUM_ACCOUNTS")
|
||||
.help("Total number of accounts"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("iterations")
|
||||
.long("iterations")
|
||||
.takes_value(true)
|
||||
.value_name("ITERATIONS")
|
||||
.help("Number of bench iterations"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("clean")
|
||||
.long("clean")
|
||||
.takes_value(false)
|
||||
.help("Run clean"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let num_slots = value_t!(matches, "num_slots", usize).unwrap_or(4);
|
||||
let num_accounts = value_t!(matches, "num_accounts", usize).unwrap_or(10_000);
|
||||
let iterations = value_t!(matches, "iterations", usize).unwrap_or(20);
|
||||
let clean = matches.is_present("clean");
|
||||
println!("clean: {:?}", clean);
|
||||
|
||||
let path = PathBuf::from("farf/accounts-bench");
|
||||
if fs::remove_dir_all(path.clone()).is_err() {
|
||||
println!("Warning: Couldn't remove {:?}", path);
|
||||
}
|
||||
let accounts = Accounts::new(vec![path]);
|
||||
println!("Creating {} accounts", num_accounts);
|
||||
let mut create_time = Measure::start("create accounts");
|
||||
let pubkeys: Vec<_> = (0..num_slots)
|
||||
.into_par_iter()
|
||||
.map(|slot| {
|
||||
let mut pubkeys: Vec<Pubkey> = vec![];
|
||||
create_test_accounts(
|
||||
&accounts,
|
||||
&mut pubkeys,
|
||||
num_accounts / num_slots,
|
||||
slot as u64,
|
||||
);
|
||||
pubkeys
|
||||
})
|
||||
.collect();
|
||||
let pubkeys: Vec<_> = pubkeys.into_iter().flatten().collect();
|
||||
create_time.stop();
|
||||
println!(
|
||||
"created {} accounts in {} slots {}",
|
||||
(num_accounts / num_slots) * num_slots,
|
||||
num_slots,
|
||||
create_time
|
||||
);
|
||||
let mut ancestors: Ancestors = vec![(0, 0)].into_iter().collect();
|
||||
for i in 1..num_slots {
|
||||
ancestors.insert(i as u64, i - 1);
|
||||
accounts.add_root(i as u64);
|
||||
}
|
||||
for x in 0..iterations {
|
||||
if clean {
|
||||
let mut time = Measure::start("clean");
|
||||
accounts.accounts_db.clean_accounts();
|
||||
time.stop();
|
||||
println!("{}", time);
|
||||
for slot in 0..num_slots {
|
||||
update_accounts(&accounts, &pubkeys, ((x + 1) * num_slots + slot) as u64);
|
||||
accounts.add_root((x * num_slots + slot) as u64);
|
||||
}
|
||||
} else {
|
||||
let mut pubkeys: Vec<Pubkey> = vec![];
|
||||
let mut time = Measure::start("hash");
|
||||
let hash = accounts.accounts_db.update_accounts_hash(0, &ancestors);
|
||||
time.stop();
|
||||
println!("hash: {} {}", hash, time);
|
||||
create_test_accounts(&accounts, &mut pubkeys, 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-archiver-lib"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
description = "Solana Archiver Library"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,30 +10,34 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.2.1"
|
||||
crossbeam-channel = "0.3"
|
||||
ed25519-dalek = "=1.0.0-pre.1"
|
||||
crossbeam-channel = "0.4"
|
||||
ed25519-dalek = "=1.0.0-pre.3"
|
||||
log = "0.4.8"
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
solana-client = { path = "../client", version = "1.0.9" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.9" }
|
||||
rand = "0.7.0"
|
||||
rand_chacha = "0.2.2"
|
||||
solana-client = { path = "../client", version = "1.1.15" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.1.15" }
|
||||
thiserror = "1.0"
|
||||
serde = "1.0.104"
|
||||
serde_json = "1.0.46"
|
||||
serde = "1.0.105"
|
||||
serde_json = "1.0.48"
|
||||
serde_derive = "1.0.103"
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.9" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.9" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-perf = { path = "../perf", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.9" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.15" }
|
||||
solana-chacha = { path = "../chacha", version = "1.1.15" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.1.15" }
|
||||
solana-ledger = { path = "../ledger", version = "1.1.15" }
|
||||
solana-logger = { path = "../logger", version = "1.1.15" }
|
||||
solana-perf = { path = "../perf", version = "1.1.15" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.15" }
|
||||
solana-core = { path = "../core", version = "1.1.15" }
|
||||
solana-streamer = { path = "../streamer", version = "1.1.15" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.1.15" }
|
||||
solana-metrics = { path = "../metrics", version = "1.1.15" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.0"
|
||||
hex = "0.4.2"
|
||||
|
||||
[lib]
|
||||
name = "solana_archiver_lib"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::result::ArchiverError;
|
||||
use crossbeam_channel::unbounded;
|
||||
use rand::{thread_rng, Rng, SeedableRng};
|
||||
use rand_chacha::ChaChaRng;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rand_chacha::{rand_core::SeedableRng, ChaChaRng};
|
||||
use solana_archiver_utils::sample_file;
|
||||
use solana_chacha::chacha::{chacha_cbc_encrypt_ledger, CHACHA_BLOCK_SIZE};
|
||||
use solana_client::{
|
||||
@@ -10,16 +10,14 @@ use solana_client::{
|
||||
};
|
||||
use solana_core::{
|
||||
cluster_info::{ClusterInfo, Node, VALIDATOR_PORT_RANGE},
|
||||
cluster_slots::ClusterSlots,
|
||||
contact_info::ContactInfo,
|
||||
gossip_service::GossipService,
|
||||
packet::{limited_deserialize, PACKET_DATA_SIZE},
|
||||
repair_service,
|
||||
repair_service::{RepairService, RepairSlotRange, RepairStrategy},
|
||||
repair_service::{self, RepairService, RepairSlotRange, RepairStats, RepairStrategy},
|
||||
serve_repair::ServeRepair,
|
||||
shred_fetch_stage::ShredFetchStage,
|
||||
sigverify_stage::{DisabledSigVerifier, SigVerifyStage},
|
||||
storage_stage::NUM_STORAGE_SAMPLES,
|
||||
streamer::{receiver, responder, PacketReceiver},
|
||||
window_service::WindowService,
|
||||
};
|
||||
use solana_ledger::{
|
||||
@@ -27,6 +25,7 @@ use solana_ledger::{
|
||||
};
|
||||
use solana_net_utils::bind_in_range;
|
||||
use solana_perf::packet::Packets;
|
||||
use solana_perf::packet::{limited_deserialize, PACKET_DATA_SIZE};
|
||||
use solana_perf::recycler::Recycler;
|
||||
use solana_sdk::packet::Packet;
|
||||
use solana_sdk::{
|
||||
@@ -45,6 +44,7 @@ use solana_storage_program::{
|
||||
storage_contract::StorageContract,
|
||||
storage_instruction::{self, StorageAccountType},
|
||||
};
|
||||
use solana_streamer::streamer::{receiver, responder, PacketReceiver};
|
||||
use std::{
|
||||
io::{self, ErrorKind},
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
||||
@@ -52,7 +52,7 @@ use std::{
|
||||
result,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
sync::mpsc::{channel, Receiver, Sender},
|
||||
sync::{Arc, RwLock},
|
||||
sync::Arc,
|
||||
thread::{sleep, spawn, JoinHandle},
|
||||
time::Duration,
|
||||
};
|
||||
@@ -184,10 +184,10 @@ impl Archiver {
|
||||
|
||||
info!("Archiver: id: {}", keypair.pubkey());
|
||||
info!("Creating cluster info....");
|
||||
let mut cluster_info = ClusterInfo::new(node.info.clone(), keypair.clone());
|
||||
let cluster_info = ClusterInfo::new(node.info.clone(), keypair.clone());
|
||||
cluster_info.set_entrypoint(cluster_entrypoint.clone());
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let cluster_slots = Arc::new(ClusterSlots::default());
|
||||
// Note for now, this ledger will not contain any of the existing entries
|
||||
// in the ledger located at ledger_path, and will only append on newly received
|
||||
// entries after being passed to window_service
|
||||
@@ -199,7 +199,7 @@ impl Archiver {
|
||||
|
||||
info!("Connecting to the cluster via {:?}", cluster_entrypoint);
|
||||
let (nodes, _) =
|
||||
match solana_core::gossip_service::discover_cluster(&cluster_entrypoint.gossip, 1) {
|
||||
match solana_core::gossip_service::discover_cluster(&cluster_entrypoint.gossip, 2) {
|
||||
Ok(nodes_and_archivers) => nodes_and_archivers,
|
||||
Err(e) => {
|
||||
//shutdown services before exiting
|
||||
@@ -262,6 +262,7 @@ impl Archiver {
|
||||
repair_socket,
|
||||
shred_fetch_receiver,
|
||||
slot_sender,
|
||||
cluster_slots,
|
||||
) {
|
||||
Ok(window_service) => window_service,
|
||||
Err(e) => {
|
||||
@@ -306,7 +307,7 @@ impl Archiver {
|
||||
fn run(
|
||||
meta: &mut ArchiverMeta,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: Arc<ClusterInfo>,
|
||||
archiver_keypair: &Arc<Keypair>,
|
||||
storage_keypair: &Arc<Keypair>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
@@ -363,12 +364,12 @@ impl Archiver {
|
||||
}
|
||||
|
||||
fn redeem_rewards(
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
archiver_keypair: &Arc<Keypair>,
|
||||
storage_keypair: &Arc<Keypair>,
|
||||
client_commitment: CommitmentConfig,
|
||||
) {
|
||||
let nodes = cluster_info.read().unwrap().tvu_peers();
|
||||
let nodes = cluster_info.tvu_peers();
|
||||
let client = solana_core::gossip_service::get_client(&nodes);
|
||||
|
||||
if let Ok(Some(account)) =
|
||||
@@ -400,9 +401,10 @@ impl Archiver {
|
||||
}
|
||||
|
||||
// Find a segment to replicate and download it.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn setup(
|
||||
meta: &mut ArchiverMeta,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: Arc<ClusterInfo>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
node_info: &ContactInfo,
|
||||
@@ -410,6 +412,7 @@ impl Archiver {
|
||||
repair_socket: Arc<UdpSocket>,
|
||||
shred_fetch_receiver: PacketReceiver,
|
||||
slot_sender: Sender<u64>,
|
||||
cluster_slots: Arc<ClusterSlots>,
|
||||
) -> Result<WindowService> {
|
||||
let slots_per_segment =
|
||||
match Self::get_segment_config(&cluster_info, meta.client_commitment) {
|
||||
@@ -467,6 +470,7 @@ impl Archiver {
|
||||
RepairStrategy::RepairRange(repair_slot_range),
|
||||
&Arc::new(LeaderScheduleCache::default()),
|
||||
|_, _, _, _| true,
|
||||
cluster_slots,
|
||||
);
|
||||
info!("waiting for ledger download");
|
||||
Self::wait_for_segment_download(
|
||||
@@ -486,7 +490,7 @@ impl Archiver {
|
||||
blockstore: &Arc<Blockstore>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
node_info: &ContactInfo,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: Arc<ClusterInfo>,
|
||||
) {
|
||||
info!(
|
||||
"window created, waiting for ledger download starting at slot {:?}",
|
||||
@@ -514,11 +518,8 @@ impl Archiver {
|
||||
contact_info.tvu = "0.0.0.0:0".parse().unwrap();
|
||||
contact_info.wallclock = timestamp();
|
||||
// copy over the adopted shred_version from the entrypoint
|
||||
contact_info.shred_version = cluster_info.read().unwrap().my_data().shred_version;
|
||||
{
|
||||
let mut cluster_info_w = cluster_info.write().unwrap();
|
||||
cluster_info_w.insert_self(contact_info);
|
||||
}
|
||||
contact_info.shred_version = cluster_info.my_shred_version();
|
||||
cluster_info.update_contact_info(|current| *current = contact_info);
|
||||
}
|
||||
|
||||
fn encrypt_ledger(meta: &mut ArchiverMeta, blockstore: &Arc<Blockstore>) -> Result<()> {
|
||||
@@ -621,12 +622,12 @@ impl Archiver {
|
||||
|
||||
fn submit_mining_proof(
|
||||
meta: &ArchiverMeta,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
archiver_keypair: &Arc<Keypair>,
|
||||
storage_keypair: &Arc<Keypair>,
|
||||
) {
|
||||
// No point if we've got no storage account...
|
||||
let nodes = cluster_info.read().unwrap().tvu_peers();
|
||||
let nodes = cluster_info.tvu_peers();
|
||||
let client = solana_core::gossip_service::get_client(&nodes);
|
||||
let storage_balance = client
|
||||
.poll_get_balance_with_commitment(&storage_keypair.pubkey(), meta.client_commitment);
|
||||
@@ -684,13 +685,10 @@ impl Archiver {
|
||||
}
|
||||
|
||||
fn get_segment_config(
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
client_commitment: CommitmentConfig,
|
||||
) -> Result<u64> {
|
||||
let rpc_peers = {
|
||||
let cluster_info = cluster_info.read().unwrap();
|
||||
cluster_info.all_rpc_peers()
|
||||
};
|
||||
let rpc_peers = cluster_info.all_rpc_peers();
|
||||
debug!("rpc peers: {:?}", rpc_peers);
|
||||
if !rpc_peers.is_empty() {
|
||||
let rpc_client = {
|
||||
@@ -698,16 +696,10 @@ impl Archiver {
|
||||
RpcClient::new_socket(rpc_peers[node_index].rpc)
|
||||
};
|
||||
Ok(rpc_client
|
||||
.send(
|
||||
&RpcRequest::GetSlotsPerSegment,
|
||||
.send::<u64>(
|
||||
RpcRequest::GetSlotsPerSegment,
|
||||
serde_json::json!([client_commitment]),
|
||||
0,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("Error while making rpc request {:?}", err);
|
||||
ArchiverError::ClientError(err)
|
||||
})?
|
||||
.as_u64()
|
||||
.unwrap())
|
||||
} else {
|
||||
Err(ArchiverError::NoRpcPeers)
|
||||
@@ -716,7 +708,7 @@ impl Archiver {
|
||||
|
||||
/// Waits until the first segment is ready, and returns the current segment
|
||||
fn poll_for_segment(
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
slots_per_segment: u64,
|
||||
previous_blockhash: &Hash,
|
||||
exit: &Arc<AtomicBool>,
|
||||
@@ -736,38 +728,24 @@ impl Archiver {
|
||||
|
||||
/// Poll for a different blockhash and associated max_slot than `previous_blockhash`
|
||||
fn poll_for_blockhash_and_slot(
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
slots_per_segment: u64,
|
||||
previous_blockhash: &Hash,
|
||||
exit: &Arc<AtomicBool>,
|
||||
) -> Result<(Hash, u64)> {
|
||||
info!("waiting for the next turn...");
|
||||
loop {
|
||||
let rpc_peers = {
|
||||
let cluster_info = cluster_info.read().unwrap();
|
||||
cluster_info.all_rpc_peers()
|
||||
};
|
||||
let rpc_peers = cluster_info.all_rpc_peers();
|
||||
debug!("rpc peers: {:?}", rpc_peers);
|
||||
if !rpc_peers.is_empty() {
|
||||
let rpc_client = {
|
||||
let node_index = thread_rng().gen_range(0, rpc_peers.len());
|
||||
RpcClient::new_socket(rpc_peers[node_index].rpc)
|
||||
};
|
||||
let response = rpc_client
|
||||
.send(
|
||||
&RpcRequest::GetStorageTurn,
|
||||
serde_json::value::Value::Null,
|
||||
0,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("Error while making rpc request {:?}", err);
|
||||
ArchiverError::ClientError(err)
|
||||
})?;
|
||||
let RpcStorageTurn {
|
||||
blockhash: storage_blockhash,
|
||||
slot: turn_slot,
|
||||
} = serde_json::from_value::<RpcStorageTurn>(response)
|
||||
.map_err(ArchiverError::JsonError)?;
|
||||
} = rpc_client.send(RpcRequest::GetStorageTurn, serde_json::value::Value::Null)?;
|
||||
let turn_blockhash = storage_blockhash.parse().map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
@@ -839,13 +817,14 @@ impl Archiver {
|
||||
repair_service::MAX_REPAIR_LENGTH,
|
||||
&repair_slot_range,
|
||||
);
|
||||
let mut repair_stats = RepairStats::default();
|
||||
//iter over the repairs and send them
|
||||
if let Ok(repairs) = repairs {
|
||||
let reqs: Vec<_> = repairs
|
||||
.into_iter()
|
||||
.filter_map(|repair_request| {
|
||||
serve_repair
|
||||
.map_repair_request(&repair_request)
|
||||
.map_repair_request(&repair_request, &mut repair_stats, Some(0))
|
||||
.map(|result| ((archiver_info.gossip, result), repair_request))
|
||||
.ok()
|
||||
})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use serde_json;
|
||||
use solana_client::client_error;
|
||||
use solana_ledger::blockstore;
|
||||
use solana_sdk::transport;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-archiver-utils"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
description = "Solana Archiver Utils"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,16 +10,19 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
rand = "0.6.5"
|
||||
solana-chacha = { path = "../chacha", version = "1.0.9" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.9" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-perf = { path = "../perf", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
rand = "0.7.0"
|
||||
solana-chacha = { path = "../chacha", version = "1.1.15" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.1.15" }
|
||||
solana-ledger = { path = "../ledger", version = "1.1.15" }
|
||||
solana-logger = { path = "../logger", version = "1.1.15" }
|
||||
solana-perf = { path = "../perf", version = "1.1.15" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.15" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.0"
|
||||
hex = "0.4.2"
|
||||
|
||||
[lib]
|
||||
name = "solana_archiver_utils"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
@@ -2,19 +2,22 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-archiver"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
console = "0.9.2"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.9" }
|
||||
solana-archiver-lib = { path = "../archiver-lib", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
console = "0.10.0"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.1.15" }
|
||||
solana-core = { path = "../core", version = "1.1.15" }
|
||||
solana-logger = { path = "../logger", version = "1.1.15" }
|
||||
solana-metrics = { path = "../metrics", version = "1.1.15" }
|
||||
solana-archiver-lib = { path = "../archiver-lib", version = "1.1.15" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.15" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.15" }
|
||||
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
@@ -2,19 +2,26 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
crossbeam-channel = "0.4"
|
||||
log = "0.4.6"
|
||||
rayon = "1.2.0"
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.9" }
|
||||
solana-measure = { path = "../measure", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
rand = "0.6.5"
|
||||
crossbeam-channel = "0.3"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.3.0"
|
||||
solana-core = { path = "../core", version = "1.1.15" }
|
||||
solana-streamer = { path = "../streamer", version = "1.1.15" }
|
||||
solana-perf = { path = "../perf", version = "1.1.15" }
|
||||
solana-ledger = { path = "../ledger", version = "1.1.15" }
|
||||
solana-logger = { path = "../logger", version = "1.1.15" }
|
||||
solana-runtime = { path = "../runtime", version = "1.1.15" }
|
||||
solana-measure = { path = "../measure", version = "1.1.15" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.15" }
|
||||
solana-version = { path = "../version", version = "1.1.15" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
use clap::{crate_description, crate_name, value_t, App, Arg};
|
||||
use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana_core::banking_stage::{create_test_recorder, BankingStage};
|
||||
use solana_core::cluster_info::ClusterInfo;
|
||||
use solana_core::cluster_info::Node;
|
||||
use solana_core::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_core::packet::to_packets_chunked;
|
||||
use solana_core::poh_recorder::PohRecorder;
|
||||
use solana_core::poh_recorder::WorkingBankEntry;
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path};
|
||||
use solana_core::{
|
||||
banking_stage::{create_test_recorder, BankingStage},
|
||||
cluster_info::ClusterInfo,
|
||||
cluster_info::Node,
|
||||
poh_recorder::PohRecorder,
|
||||
poh_recorder::WorkingBankEntry,
|
||||
};
|
||||
use solana_ledger::{
|
||||
bank_forks::BankForks,
|
||||
blockstore::Blockstore,
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
get_tmp_ledger_path,
|
||||
};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::Keypair;
|
||||
use solana_sdk::signature::Signature;
|
||||
use solana_sdk::system_transaction;
|
||||
use solana_sdk::timing::{duration_as_us, timestamp};
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::Keypair,
|
||||
signature::Signature,
|
||||
system_transaction,
|
||||
timing::{duration_as_us, timestamp},
|
||||
transaction::Transaction,
|
||||
};
|
||||
use std::{
|
||||
sync::{atomic::Ordering, mpsc::Receiver, Arc, Mutex},
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
fn check_txs(
|
||||
receiver: &Arc<Receiver<WorkingBankEntry>>,
|
||||
@@ -57,15 +65,22 @@ fn check_txs(
|
||||
no_bank
|
||||
}
|
||||
|
||||
fn make_accounts_txs(txes: usize, mint_keypair: &Keypair, hash: Hash) -> Vec<Transaction> {
|
||||
fn make_accounts_txs(
|
||||
total_num_transactions: usize,
|
||||
hash: Hash,
|
||||
same_payer: bool,
|
||||
) -> Vec<Transaction> {
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
let dummy = system_transaction::transfer(mint_keypair, &to_pubkey, 1, hash);
|
||||
(0..txes)
|
||||
let payer_key = Keypair::new();
|
||||
let dummy = system_transaction::transfer(&payer_key, &to_pubkey, 1, hash);
|
||||
(0..total_num_transactions)
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let mut new = dummy.clone();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
new.message.account_keys[0] = Pubkey::new_rand();
|
||||
if !same_payer {
|
||||
new.message.account_keys[0] = Pubkey::new_rand();
|
||||
}
|
||||
new.message.account_keys[1] = Pubkey::new_rand();
|
||||
new.signatures = vec![Signature::new(&sig[0..64])];
|
||||
new
|
||||
@@ -89,13 +104,61 @@ fn bytes_as_usize(bytes: &[u8]) -> usize {
|
||||
bytes[0] as usize | (bytes[1] as usize) << 8
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
let num_threads = BankingStage::num_threads() as usize;
|
||||
|
||||
let matches = App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(solana_version::version!())
|
||||
.arg(
|
||||
Arg::with_name("num_chunks")
|
||||
.long("num-chunks")
|
||||
.takes_value(true)
|
||||
.value_name("SIZE")
|
||||
.help("Number of transaction chunks."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("packets_per_chunk")
|
||||
.long("packets-per-chunk")
|
||||
.takes_value(true)
|
||||
.value_name("SIZE")
|
||||
.help("Packets per chunk"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("skip_sanity")
|
||||
.long("skip-sanity")
|
||||
.takes_value(false)
|
||||
.help("Skip transaction sanity execution"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("same_payer")
|
||||
.long("same-payer")
|
||||
.takes_value(false)
|
||||
.help("Use the same payer for transfers"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("iterations")
|
||||
.long("iterations")
|
||||
.takes_value(true)
|
||||
.help("Number of iterations"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num_threads")
|
||||
.long("num-threads")
|
||||
.takes_value(true)
|
||||
.help("Number of iterations"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let num_threads =
|
||||
value_t!(matches, "num_threads", usize).unwrap_or(BankingStage::num_threads() as usize);
|
||||
// a multiple of packet chunk duplicates to avoid races
|
||||
const CHUNKS: usize = 8 * 2;
|
||||
const PACKETS_PER_BATCH: usize = 192;
|
||||
let txes = PACKETS_PER_BATCH * num_threads * CHUNKS;
|
||||
let num_chunks = value_t!(matches, "num_chunks", usize).unwrap_or(16);
|
||||
let packets_per_chunk = value_t!(matches, "packets_per_chunk", usize).unwrap_or(192);
|
||||
let iterations = value_t!(matches, "iterations", usize).unwrap_or(1000);
|
||||
|
||||
let total_num_transactions = num_chunks * num_threads * packets_per_chunk;
|
||||
let mint_total = 1_000_000_000_000;
|
||||
let GenesisConfigInfo {
|
||||
genesis_config,
|
||||
@@ -109,34 +172,44 @@ fn main() {
|
||||
let mut bank_forks = BankForks::new(0, bank0);
|
||||
let mut bank = bank_forks.working_bank();
|
||||
|
||||
info!("threads: {} txs: {}", num_threads, txes);
|
||||
info!("threads: {} txs: {}", num_threads, total_num_transactions);
|
||||
|
||||
let mut transactions = make_accounts_txs(txes, &mint_keypair, genesis_config.hash());
|
||||
let same_payer = matches.is_present("same_payer");
|
||||
let mut transactions =
|
||||
make_accounts_txs(total_num_transactions, genesis_config.hash(), same_payer);
|
||||
|
||||
// fund all the accounts
|
||||
transactions.iter().for_each(|tx| {
|
||||
let fund = system_transaction::transfer(
|
||||
let mut fund = system_transaction::transfer(
|
||||
&mint_keypair,
|
||||
&tx.message.account_keys[0],
|
||||
mint_total / txes as u64,
|
||||
mint_total / total_num_transactions as u64,
|
||||
genesis_config.hash(),
|
||||
);
|
||||
// Ignore any pesky duplicate signature errors in the case we are using single-payer
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
fund.signatures = vec![Signature::new(&sig[0..64])];
|
||||
let x = bank.process_transaction(&fund);
|
||||
x.unwrap();
|
||||
});
|
||||
//sanity check, make sure all the transactions can execute sequentially
|
||||
transactions.iter().for_each(|tx| {
|
||||
let res = bank.process_transaction(&tx);
|
||||
assert!(res.is_ok(), "sanity test transactions");
|
||||
});
|
||||
bank.clear_signatures();
|
||||
//sanity check, make sure all the transactions can execute in parallel
|
||||
let res = bank.process_transactions(&transactions);
|
||||
for r in res {
|
||||
assert!(r.is_ok(), "sanity parallel execution");
|
||||
|
||||
let skip_sanity = matches.is_present("skip_sanity");
|
||||
if !skip_sanity {
|
||||
//sanity check, make sure all the transactions can execute sequentially
|
||||
transactions.iter().for_each(|tx| {
|
||||
let res = bank.process_transaction(&tx);
|
||||
assert!(res.is_ok(), "sanity test transactions error: {:?}", res);
|
||||
});
|
||||
bank.clear_signatures();
|
||||
//sanity check, make sure all the transactions can execute in parallel
|
||||
let res = bank.process_transactions(&transactions);
|
||||
for r in res {
|
||||
assert!(r.is_ok(), "sanity parallel execution error: {:?}", r);
|
||||
}
|
||||
bank.clear_signatures();
|
||||
}
|
||||
bank.clear_signatures();
|
||||
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
|
||||
|
||||
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), packets_per_chunk);
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
{
|
||||
let blockstore = Arc::new(
|
||||
@@ -145,7 +218,7 @@ fn main() {
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, None);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
@@ -155,7 +228,7 @@ fn main() {
|
||||
);
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
|
||||
let chunk_len = verified.len() / CHUNKS;
|
||||
let chunk_len = verified.len() / num_chunks;
|
||||
let mut start = 0;
|
||||
|
||||
// This is so that the signal_receiver does not go out of scope after the closure.
|
||||
@@ -164,17 +237,17 @@ fn main() {
|
||||
let signal_receiver = Arc::new(signal_receiver);
|
||||
let mut total_us = 0;
|
||||
let mut tx_total_us = 0;
|
||||
let base_tx_count = bank.transaction_count();
|
||||
let mut txs_processed = 0;
|
||||
let mut root = 1;
|
||||
let collector = Pubkey::new_rand();
|
||||
const ITERS: usize = 1_000;
|
||||
let config = Config {
|
||||
packets_per_batch: PACKETS_PER_BATCH,
|
||||
packets_per_batch: packets_per_chunk,
|
||||
chunk_len,
|
||||
num_threads,
|
||||
};
|
||||
let mut total_sent = 0;
|
||||
for _ in 0..ITERS {
|
||||
for _ in 0..iterations {
|
||||
let now = Instant::now();
|
||||
let mut sent = 0;
|
||||
|
||||
@@ -215,7 +288,11 @@ fn main() {
|
||||
sleep(Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
if check_txs(&signal_receiver, txes / CHUNKS, &poh_recorder) {
|
||||
if check_txs(
|
||||
&signal_receiver,
|
||||
total_num_transactions / num_chunks,
|
||||
&poh_recorder,
|
||||
) {
|
||||
debug!(
|
||||
"resetting bank {} tx count: {} txs_proc: {}",
|
||||
bank.slot(),
|
||||
@@ -246,7 +323,7 @@ fn main() {
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
assert!(poh_recorder.lock().unwrap().bank().is_some());
|
||||
if bank.slot() > 32 {
|
||||
bank_forks.set_root(root, &None);
|
||||
bank_forks.set_root(root, &None, None);
|
||||
root += 1;
|
||||
}
|
||||
debug!(
|
||||
@@ -267,7 +344,7 @@ fn main() {
|
||||
debug!(
|
||||
"time: {} us checked: {} sent: {}",
|
||||
duration_as_us(&now.elapsed()),
|
||||
txes / CHUNKS,
|
||||
total_num_transactions / num_chunks,
|
||||
sent,
|
||||
);
|
||||
total_sent += sent;
|
||||
@@ -278,20 +355,26 @@ fn main() {
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
tx.signatures[0] = Signature::new(&sig[0..64]);
|
||||
}
|
||||
verified = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
|
||||
verified = to_packets_chunked(&transactions.clone(), packets_per_chunk);
|
||||
}
|
||||
|
||||
start += chunk_len;
|
||||
start %= verified.len();
|
||||
}
|
||||
let txs_processed = bank_forks.working_bank().transaction_count();
|
||||
debug!("processed: {} base: {}", txs_processed, base_tx_count);
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_total', 'median': '{}'}}",
|
||||
"{{'name': 'banking_bench_total', 'median': '{:.2}'}}",
|
||||
(1000.0 * 1000.0 * total_sent as f64) / (total_us as f64),
|
||||
);
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_tx_total', 'median': '{}'}}",
|
||||
"{{'name': 'banking_bench_tx_total', 'median': '{:.2}'}}",
|
||||
(1000.0 * 1000.0 * total_sent as f64) / (tx_total_us as f64),
|
||||
);
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_success_tx_total', 'median': '{:.2}'}}",
|
||||
(1000.0 * 1000.0 * (txs_processed - base_tx_count) as f64) / (total_us as f64),
|
||||
);
|
||||
|
||||
drop(verified_sender);
|
||||
drop(vote_sender);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -10,25 +10,28 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
clap = "2.32.0"
|
||||
itertools = "0.8.2"
|
||||
itertools = "0.9.0"
|
||||
log = "0.4.8"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
rand = "0.6.5"
|
||||
rayon = "1.2.0"
|
||||
serde_json = "1.0.46"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.3.0"
|
||||
serde_json = "1.0.48"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.9" }
|
||||
solana-client = { path = "../client", version = "1.0.9" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.9" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.1.15" }
|
||||
solana-core = { path = "../core", version = "1.1.15" }
|
||||
solana-genesis = { path = "../genesis", version = "1.1.15" }
|
||||
solana-client = { path = "../client", version = "1.1.15" }
|
||||
solana-faucet = { path = "../faucet", version = "1.1.15" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.1.15" }
|
||||
solana-logger = { path = "../logger", version = "1.1.15" }
|
||||
solana-metrics = { path = "../metrics", version = "1.1.15" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.15" }
|
||||
solana-runtime = { path = "../runtime", version = "1.1.15" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.15" }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.9" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.1.15" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
@@ -86,7 +86,7 @@ fn test_exchange_bank_client() {
|
||||
solana_logger::setup();
|
||||
let (genesis_config, identity) = create_genesis_config(100_000_000_000_000);
|
||||
let mut bank = Bank::new(&genesis_config);
|
||||
bank.add_instruction_processor(id(), process_instruction);
|
||||
bank.add_static_program("exchange_program", id(), process_instruction);
|
||||
let clients = vec![BankClient::new(bank)];
|
||||
|
||||
let mut config = Config::default();
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.1.15" }
|
||||
solana-streamer = { path = "../streamer", version = "1.1.15" }
|
||||
solana-logger = { path = "../logger", version = "1.1.15" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.15" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use clap::{crate_description, crate_name, App, Arg};
|
||||
use solana_core::packet::{Packet, Packets, PacketsRecycler, PACKET_DATA_SIZE};
|
||||
use solana_core::streamer::{receiver, PacketReceiver};
|
||||
use solana_streamer::packet::{Packet, Packets, PacketsRecycler, PACKET_DATA_SIZE};
|
||||
use solana_streamer::streamer::{receiver, PacketReceiver};
|
||||
use std::cmp::max;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -11,27 +11,30 @@ homepage = "https://solana.com/"
|
||||
bincode = "1.2.1"
|
||||
clap = "2.33.0"
|
||||
log = "0.4.8"
|
||||
rayon = "1.2.0"
|
||||
serde_json = "1.0.46"
|
||||
rayon = "1.3.0"
|
||||
serde_json = "1.0.48"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.9" }
|
||||
solana-client = { path = "../client", version = "1.0.9" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.9" }
|
||||
solana-librapay = { path = "../programs/librapay", version = "1.0.9", optional = true }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.9" }
|
||||
solana-measure = { path = "../measure", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader", version = "1.0.9", optional = true }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.1.15" }
|
||||
solana-core = { path = "../core", version = "1.1.15" }
|
||||
solana-genesis = { path = "../genesis", version = "1.1.15" }
|
||||
solana-client = { path = "../client", version = "1.1.15" }
|
||||
solana-faucet = { path = "../faucet", version = "1.1.15" }
|
||||
#solana-librapay = { path = "../programs/librapay", version = "1.1.8", optional = true }
|
||||
solana-logger = { path = "../logger", version = "1.1.15" }
|
||||
solana-metrics = { path = "../metrics", version = "1.1.15" }
|
||||
solana-measure = { path = "../measure", version = "1.1.15" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.15" }
|
||||
solana-runtime = { path = "../runtime", version = "1.1.15" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.15" }
|
||||
#solana-move-loader-program = { path = "../programs/move_loader", version = "1.1.8", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.3.2"
|
||||
serial_test = "0.4.0"
|
||||
serial_test_derive = "0.4.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.9" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.1.15" }
|
||||
|
||||
[features]
|
||||
move = ["solana-librapay", "solana-move-loader-program"]
|
||||
#[features]
|
||||
#move = ["solana-librapay", "solana-move-loader-program"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-chacha-cuda"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
description = "Solana Chacha Cuda APIs"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,15 +10,18 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.9" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.9" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-perf = { path = "../perf", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.1.15" }
|
||||
solana-chacha = { path = "../chacha", version = "1.1.15" }
|
||||
solana-ledger = { path = "../ledger", version = "1.1.15" }
|
||||
solana-logger = { path = "../logger", version = "1.1.15" }
|
||||
solana-perf = { path = "../perf", version = "1.1.15" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.15" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2.1"
|
||||
|
||||
[lib]
|
||||
name = "solana_chacha_cuda"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-chacha-sys"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
description = "Solana chacha-sys"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,3 +10,6 @@ edition = "2018"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.49"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-chacha"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
description = "Solana Chacha APIs"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,16 +10,19 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.9" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-perf = { path = "../perf", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
rand = "0.7.0"
|
||||
rand_chacha = "0.2.2"
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.1.15" }
|
||||
solana-ledger = { path = "../ledger", version = "1.1.15" }
|
||||
solana-logger = { path = "../logger", version = "1.1.15" }
|
||||
solana-perf = { path = "../perf", version = "1.1.15" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.15" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2.1"
|
||||
|
||||
[lib]
|
||||
name = "solana_chacha"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
8
ci/_
8
ci/_
@@ -5,7 +5,13 @@
|
||||
# |source| me
|
||||
#
|
||||
|
||||
base_dir=$(realpath --strip "$(dirname "$0")/..")
|
||||
|
||||
_() {
|
||||
echo "--- $*"
|
||||
if [[ $(pwd) = $base_dir ]]; then
|
||||
echo "--- $*"
|
||||
else
|
||||
echo "--- $* (wd: $(pwd))"
|
||||
fi
|
||||
"$@"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,16 @@
|
||||
# Build steps that run after the primary pipeline on pushes and tags.
|
||||
# Pull requests to not run these steps.
|
||||
steps:
|
||||
- command: "ci/publish-tarball.sh"
|
||||
timeout_in_minutes: 60
|
||||
name: "publish tarball"
|
||||
- command: "ci/publish-docs.sh"
|
||||
timeout_in_minutes: 15
|
||||
name: "publish docs"
|
||||
- command: "ci/publish-bpf-sdk.sh"
|
||||
timeout_in_minutes: 5
|
||||
name: "publish bpf sdk"
|
||||
- wait
|
||||
- command: "sdk/docker-solana/build.sh"
|
||||
timeout_in_minutes: 60
|
||||
name: "publish docker"
|
||||
@@ -9,12 +19,6 @@ steps:
|
||||
timeout_in_minutes: 240
|
||||
name: "publish crate"
|
||||
branches: "!master"
|
||||
- command: "ci/publish-bpf-sdk.sh"
|
||||
timeout_in_minutes: 5
|
||||
name: "publish bpf sdk"
|
||||
- command: "ci/publish-tarball.sh"
|
||||
timeout_in_minutes: 60
|
||||
name: "publish tarball"
|
||||
- command: "ci/publish-docs.sh"
|
||||
timeout_in_minutes: 15
|
||||
name: "publish docs"
|
||||
# - command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-move.sh"
|
||||
# name: "move"
|
||||
# timeout_in_minutes: 20
|
||||
|
||||
26
ci/buildkite-tests.yml
Normal file
26
ci/buildkite-tests.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
# These steps are conditionally triggered by ci/buildkite.yml when files
|
||||
# other than those in docs/ are modified
|
||||
|
||||
steps:
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
|
||||
name: "coverage"
|
||||
timeout_in_minutes: 30
|
||||
- wait
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-stable.sh"
|
||||
name: "stable"
|
||||
timeout_in_minutes: 60
|
||||
artifact_paths: "log-*.txt"
|
||||
- wait
|
||||
- command: "ci/test-stable-perf.sh"
|
||||
name: "stable-perf"
|
||||
timeout_in_minutes: 40
|
||||
artifact_paths: "log-*.txt"
|
||||
agents:
|
||||
- "queue=cuda"
|
||||
- command: "ci/test-bench.sh"
|
||||
name: "bench"
|
||||
timeout_in_minutes: 30
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-local-cluster.sh"
|
||||
name: "local-cluster"
|
||||
timeout_in_minutes: 45
|
||||
artifact_paths: "log-*.txt"
|
||||
@@ -1,42 +1,25 @@
|
||||
# Build steps that run on pushes and pull requests.
|
||||
# If files other than those in docs/ were modified, this will be followed up by
|
||||
# ci/buildkite-tests.yml
|
||||
#
|
||||
# Release tags use buildkite-release.yml instead
|
||||
|
||||
steps:
|
||||
- command: "ci/shellcheck.sh"
|
||||
name: "shellcheck"
|
||||
timeout_in_minutes: 5
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-checks.sh"
|
||||
name: "checks"
|
||||
timeout_in_minutes: 20
|
||||
- command: "ci/shellcheck.sh"
|
||||
name: "shellcheck"
|
||||
timeout_in_minutes: 5
|
||||
|
||||
- wait
|
||||
- command: "ci/test-stable-perf.sh"
|
||||
name: "stable-perf"
|
||||
timeout_in_minutes: 40
|
||||
artifact_paths: "log-*.txt"
|
||||
agents:
|
||||
- "queue=cuda"
|
||||
- command: "ci/test-bench.sh"
|
||||
name: "bench"
|
||||
timeout_in_minutes: 30
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-stable.sh"
|
||||
name: "stable"
|
||||
timeout_in_minutes: 60
|
||||
artifact_paths: "log-*.txt"
|
||||
agents:
|
||||
- "queue=rpc-test-capable"
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-move.sh"
|
||||
name: "move"
|
||||
timeout_in_minutes: 20
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-local-cluster.sh"
|
||||
name: "local-cluster"
|
||||
timeout_in_minutes: 45
|
||||
artifact_paths: "log-*.txt"
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
|
||||
name: "coverage"
|
||||
timeout_in_minutes: 30
|
||||
agents:
|
||||
- "queue=rpc-test-capable"
|
||||
|
||||
- command: "ci/maybe-trigger-tests.sh"
|
||||
name: "maybe-trigger-tests"
|
||||
timeout_in_minutes: 2
|
||||
|
||||
- wait
|
||||
|
||||
- trigger: "solana-secondary"
|
||||
branches: "!pull/*"
|
||||
async: true
|
||||
|
||||
@@ -49,7 +49,7 @@ else
|
||||
# ~/.cargo
|
||||
ARGS+=(--volume "$PWD:/home")
|
||||
fi
|
||||
ARGS+=(--env "CARGO_HOME=/home/.cargo")
|
||||
ARGS+=(--env "HOME=/home" --env "CARGO_HOME=/home/.cargo")
|
||||
|
||||
# kcov tries to set the personality of the binary which docker
|
||||
# doesn't allow by default.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM solanalabs/rust:1.42.0
|
||||
FROM solanalabs/rust:1.43.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.42.0
|
||||
FROM rust:1.43.0
|
||||
|
||||
# Add Google Protocol Buffers for Libra's metrics library.
|
||||
ENV PROTOC_VERSION 3.8.0
|
||||
@@ -32,6 +32,7 @@ RUN set -x \
|
||||
&& cargo install cargo-audit \
|
||||
&& cargo install svgbob_cli \
|
||||
&& cargo install mdbook \
|
||||
&& cargo install mdbook-linkcheck \
|
||||
&& rustc --version \
|
||||
&& cargo --version \
|
||||
&& curl -OL https://github.com/google/protobuf/releases/download/v$PROTOC_VERSION/$PROTOC_ZIP \
|
||||
|
||||
21
ci/maybe-trigger-tests.sh
Executable file
21
ci/maybe-trigger-tests.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
annotate() {
|
||||
${BUILDKITE:-false} && {
|
||||
buildkite-agent annotate "$@"
|
||||
}
|
||||
}
|
||||
|
||||
# Skip if only the docs have been modified
|
||||
ci/affects-files.sh \
|
||||
\!^docs/ \
|
||||
|| {
|
||||
annotate --style info \
|
||||
"Skipping all further tests as only docs/ files were modified"
|
||||
exit 0
|
||||
}
|
||||
|
||||
annotate --style info "Triggering tests"
|
||||
buildkite-agent pipeline upload ci/buildkite-tests.yml
|
||||
@@ -71,7 +71,7 @@ echo --- Creating release tarball
|
||||
export CHANNEL
|
||||
|
||||
source ci/rust-version.sh stable
|
||||
scripts/cargo-install-all.sh +"$rust_stable" --use-move solana-release
|
||||
scripts/cargo-install-all.sh +"$rust_stable" solana-release
|
||||
|
||||
tar cvf solana-release-$TARGET.tar solana-release
|
||||
bzip2 solana-release-$TARGET.tar
|
||||
@@ -95,9 +95,8 @@ fi
|
||||
source ci/upload-ci-artifact.sh
|
||||
|
||||
for file in solana-release-$TARGET.tar.bz2 solana-release-$TARGET.yml solana-install-init-"$TARGET"* $MAYBE_TARBALLS; do
|
||||
upload-ci-artifact "$file"
|
||||
|
||||
if [[ -n $DO_NOT_PUBLISH_TAR ]]; then
|
||||
upload-ci-artifact "$file"
|
||||
echo "Skipped $file due to DO_NOT_PUBLISH_TAR"
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
# shellcheck source=multinode-demo/common.sh
|
||||
source multinode-demo/common.sh
|
||||
|
||||
rm -f config/run/init-completed
|
||||
rm -rf config/run/init-completed config/ledger config/snapshot-ledger
|
||||
|
||||
timeout 15 ./run.sh &
|
||||
pid=$!
|
||||
@@ -17,6 +19,13 @@ while [[ ! -f config/run/init-completed ]]; do
|
||||
fi
|
||||
done
|
||||
|
||||
while [[ $($solana_cli slot --commitment recent) -eq 0 ]]; do
|
||||
sleep 1
|
||||
done
|
||||
curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"validatorExit"}' http://localhost:8899
|
||||
|
||||
wait $pid
|
||||
|
||||
$solana_ledger_tool create-snapshot --ledger config/ledger 1 config/snapshot-ledger
|
||||
cp config/ledger/genesis.tar.bz2 config/snapshot-ledger
|
||||
$solana_ledger_tool verify --ledger config/snapshot-ledger
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
#
|
||||
# This file maintains the rust versions for use by CI.
|
||||
#
|
||||
# Build with stable rust, updating the stable toolchain if necessary:
|
||||
# $ source ci/rust-version.sh stable
|
||||
# $ cargo +"$rust_stable" build
|
||||
#
|
||||
# Build with nightly rust, updating the nightly toolchain if necessary:
|
||||
# $ source ci/rust-version.sh nightly
|
||||
# $ cargo +"$rust_nightly" build
|
||||
#
|
||||
# Obtain the environment variables without any automatic toolchain updating:
|
||||
# $ source ci/rust-version.sh
|
||||
#
|
||||
# Obtain the environment variables updating both stable and nightly, only stable, or
|
||||
# only nightly:
|
||||
# $ source ci/rust-version.sh all
|
||||
# $ source ci/rust-version.sh stable
|
||||
# $ source ci/rust-version.sh nightly
|
||||
|
||||
# Then to build with either stable or nightly:
|
||||
# $ cargo +"$rust_stable" build
|
||||
# $ cargo +"$rust_nightly" build
|
||||
#
|
||||
|
||||
if [[ -n $RUST_STABLE_VERSION ]]; then
|
||||
stable_version="$RUST_STABLE_VERSION"
|
||||
else
|
||||
stable_version=1.42.0
|
||||
stable_version=1.43.0
|
||||
fi
|
||||
|
||||
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
||||
nightly_version="$RUST_NIGHTLY_VERSION"
|
||||
else
|
||||
nightly_version=2020-03-12
|
||||
nightly_version=2020-04-23
|
||||
fi
|
||||
|
||||
|
||||
@@ -51,6 +53,10 @@ export rust_nightly_docker_image=solanalabs/rust-nightly:"$nightly_version"
|
||||
nightly)
|
||||
rustup_install "$rust_nightly"
|
||||
;;
|
||||
all)
|
||||
rustup_install "$rust_stable"
|
||||
rustup_install "$rust_nightly"
|
||||
;;
|
||||
*)
|
||||
echo "Note: ignoring unknown argument: $1"
|
||||
;;
|
||||
|
||||
@@ -25,7 +25,7 @@ source ci/_
|
||||
source ci/upload-ci-artifact.sh
|
||||
|
||||
eval "$(ci/channel-info.sh)"
|
||||
source ci/rust-version.sh nightly
|
||||
source ci/rust-version.sh all
|
||||
|
||||
set -o pipefail
|
||||
export RUST_BACKTRACE=1
|
||||
@@ -67,8 +67,9 @@ _ cargo +$rust_nightly bench --manifest-path core/Cargo.toml ${V:+--verbose} \
|
||||
_ cargo +$rust_nightly bench --manifest-path programs/bpf/Cargo.toml ${V:+--verbose} --features=bpf_c \
|
||||
-- -Z unstable-options --format=json --nocapture | tee -a "$BENCH_FILE"
|
||||
|
||||
# Run banking bench. Doesn't require nightly, but use since it is already built.
|
||||
# Run banking/accounts bench. Doesn't require nightly, but use since it is already built.
|
||||
_ cargo +$rust_nightly run --release --manifest-path banking-bench/Cargo.toml ${V:+--verbose} | tee -a "$BENCH_FILE"
|
||||
_ cargo +$rust_nightly run --release --manifest-path accounts-bench/Cargo.toml ${V:+--verbose} -- --num_accounts 10000 --num_slots 4 | tee -a "$BENCH_FILE"
|
||||
|
||||
# `solana-upload-perf` disabled as it can take over 30 minutes to complete for some
|
||||
# reason
|
||||
|
||||
@@ -22,7 +22,7 @@ _ cargo +"$rust_stable" clippy --all --exclude solana-sdk-c -- --deny=warnings
|
||||
_ cargo +"$rust_stable" clippy --manifest-path sdk-c/Cargo.toml -- --deny=warnings
|
||||
|
||||
_ cargo +"$rust_stable" audit --version
|
||||
_ cargo +"$rust_stable" audit --ignore RUSTSEC-2020-0002
|
||||
_ cargo +"$rust_stable" audit --ignore RUSTSEC-2020-0002 --ignore RUSTSEC-2020-0008
|
||||
_ ci/nits.sh
|
||||
_ ci/order-crates-for-publishing.py
|
||||
_ docs/build.sh
|
||||
|
||||
@@ -38,11 +38,16 @@ test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
|
||||
# Clear the BPF sysroot files, they are not automatically rebuilt
|
||||
rm -rf target/xargo # Issue #3105
|
||||
|
||||
# Limit compiler jobs to reduce memory usage
|
||||
# on machines with 1gb/thread of memory
|
||||
NPROC=$(nproc)
|
||||
NPROC=$((NPROC>16 ? 16 : NPROC))
|
||||
|
||||
echo "Executing $testName"
|
||||
case $testName in
|
||||
test-stable)
|
||||
_ cargo +"$rust_stable" test --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
_ cargo +"$rust_stable" test --manifest-path bench-tps/Cargo.toml --features=move ${V:+--verbose} test_bench_tps_local_cluster_move -- --nocapture
|
||||
_ cargo +"$rust_stable" test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
#_ cargo +"$rust_stable" test --manifest-path bench-tps/Cargo.toml --features=move ${V:+--verbose} test_bench_tps_local_cluster_move -- --nocapture
|
||||
;;
|
||||
test-stable-perf)
|
||||
ci/affects-files.sh \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-clap-utils"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
description = "Solana utilities for the clap"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -11,8 +11,8 @@ edition = "2018"
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
rpassword = "4.0"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.1.15" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.15" }
|
||||
thiserror = "1.0.11"
|
||||
tiny-bip39 = "0.7.0"
|
||||
url = "2.1.0"
|
||||
@@ -20,3 +20,6 @@ chrono = "0.4"
|
||||
|
||||
[lib]
|
||||
name = "solana_clap_utils"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
22
clap-utils/src/commitment.rs
Normal file
22
clap-utils/src/commitment.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use crate::ArgConstant;
|
||||
use clap::Arg;
|
||||
|
||||
pub const COMMITMENT_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "commitment",
|
||||
long: "commitment",
|
||||
help: "Return information at the selected commitment level",
|
||||
};
|
||||
|
||||
pub fn commitment_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
commitment_arg_with_default("recent")
|
||||
}
|
||||
|
||||
pub fn commitment_arg_with_default<'a, 'b>(default_value: &'static str) -> Arg<'a, 'b> {
|
||||
Arg::with_name(COMMITMENT_ARG.name)
|
||||
.long(COMMITMENT_ARG.long)
|
||||
.takes_value(true)
|
||||
.possible_values(&["recent", "root", "max"])
|
||||
.default_value(default_value)
|
||||
.value_name("COMMITMENT_LEVEL")
|
||||
.help(COMMITMENT_ARG.help)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use clap::ArgMatches;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
clock::UnixTimestamp,
|
||||
commitment_config::CommitmentConfig,
|
||||
native_token::sol_to_lamports,
|
||||
pubkey::Pubkey,
|
||||
signature::{read_keypair_file, Keypair, Signature, Signer},
|
||||
@@ -62,6 +63,21 @@ pub fn keypair_of(matches: &ArgMatches<'_>, name: &str) -> Option<Keypair> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keypairs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<Keypair>> {
|
||||
matches.values_of(name).map(|values| {
|
||||
values
|
||||
.filter_map(|value| {
|
||||
if value == ASK_KEYWORD {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
keypair_from_seed_phrase(name, skip_validation, true).ok()
|
||||
} else {
|
||||
read_keypair_file(value).ok()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
// Return a pubkey for an argument that can itself be parsed into a pubkey,
|
||||
// or is a filename that can be read as a keypair
|
||||
pub fn pubkey_of(matches: &ArgMatches<'_>, name: &str) -> Option<Pubkey> {
|
||||
@@ -101,7 +117,7 @@ pub fn pubkeys_sigs_of(matches: &ArgMatches<'_>, name: &str) -> Option<Vec<(Pubk
|
||||
pub fn signer_of(
|
||||
matches: &ArgMatches<'_>,
|
||||
name: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<(Option<Box<dyn Signer>>, Option<Pubkey>), Box<dyn std::error::Error>> {
|
||||
if let Some(location) = matches.value_of(name) {
|
||||
let signer = signer_from_path(matches, location, name, wallet_manager)?;
|
||||
@@ -115,7 +131,7 @@ pub fn signer_of(
|
||||
pub fn pubkey_of_signer(
|
||||
matches: &ArgMatches<'_>,
|
||||
name: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<Pubkey>, Box<dyn std::error::Error>> {
|
||||
if let Some(location) = matches.value_of(name) {
|
||||
Ok(Some(pubkey_from_path(
|
||||
@@ -132,7 +148,7 @@ pub fn pubkey_of_signer(
|
||||
pub fn pubkeys_of_multiple_signers(
|
||||
matches: &ArgMatches<'_>,
|
||||
name: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<Vec<Pubkey>>, Box<dyn std::error::Error>> {
|
||||
if let Some(pubkey_matches) = matches.values_of(name) {
|
||||
let mut pubkeys: Vec<Pubkey> = vec![];
|
||||
@@ -148,7 +164,7 @@ pub fn pubkeys_of_multiple_signers(
|
||||
pub fn resolve_signer(
|
||||
matches: &ArgMatches<'_>,
|
||||
name: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
Ok(resolve_signer_from_path(
|
||||
matches,
|
||||
@@ -162,6 +178,15 @@ pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> {
|
||||
value_of(matches, name).map(sol_to_lamports)
|
||||
}
|
||||
|
||||
pub fn commitment_of(matches: &ArgMatches<'_>, name: &str) -> Option<CommitmentConfig> {
|
||||
matches.value_of(name).map(|value| match value {
|
||||
"max" => CommitmentConfig::max(),
|
||||
"recent" => CommitmentConfig::recent(),
|
||||
"root" => CommitmentConfig::root(),
|
||||
_ => CommitmentConfig::default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -8,7 +8,7 @@ use clap::ArgMatches;
|
||||
use rpassword::prompt_password_stderr;
|
||||
use solana_remote_wallet::{
|
||||
remote_keypair::generate_remote_keypair,
|
||||
remote_wallet::{RemoteWalletError, RemoteWalletManager},
|
||||
remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager},
|
||||
};
|
||||
use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
@@ -64,7 +64,7 @@ pub fn signer_from_path(
|
||||
matches: &ArgMatches,
|
||||
path: &str,
|
||||
keypair_name: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Box<dyn Signer>, Box<dyn error::Error>> {
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Ask => {
|
||||
@@ -88,6 +88,9 @@ pub fn signer_from_path(
|
||||
Ok(Box::new(read_keypair(&mut stdin)?))
|
||||
}
|
||||
KeypairUrl::Usb(path) => {
|
||||
if wallet_manager.is_none() {
|
||||
*wallet_manager = maybe_wallet_manager()?;
|
||||
}
|
||||
if let Some(wallet_manager) = wallet_manager {
|
||||
Ok(Box::new(generate_remote_keypair(
|
||||
path,
|
||||
@@ -122,7 +125,7 @@ pub fn pubkey_from_path(
|
||||
matches: &ArgMatches,
|
||||
path: &str,
|
||||
keypair_name: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Pubkey, Box<dyn error::Error>> {
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Pubkey(pubkey) => Ok(pubkey),
|
||||
@@ -134,7 +137,7 @@ pub fn resolve_signer_from_path(
|
||||
matches: &ArgMatches,
|
||||
path: &str,
|
||||
keypair_name: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<String>, Box<dyn error::Error>> {
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Ask => {
|
||||
@@ -158,6 +161,9 @@ pub fn resolve_signer_from_path(
|
||||
read_keypair(&mut stdin).map(|_| None)
|
||||
}
|
||||
KeypairUrl::Usb(path) => {
|
||||
if wallet_manager.is_none() {
|
||||
*wallet_manager = maybe_wallet_manager()?;
|
||||
}
|
||||
if let Some(wallet_manager) = wallet_manager {
|
||||
let path = generate_remote_keypair(
|
||||
path,
|
||||
|
||||
@@ -42,6 +42,7 @@ impl std::fmt::Debug for DisplayError {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod commitment;
|
||||
pub mod input_parsers;
|
||||
pub mod input_validators;
|
||||
pub mod keypair;
|
||||
|
||||
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-config"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -11,7 +11,10 @@ homepage = "https://solana.com/"
|
||||
[dependencies]
|
||||
dirs = "2.0.2"
|
||||
lazy_static = "1.4.0"
|
||||
serde = "1.0.104"
|
||||
serde = "1.0.105"
|
||||
serde_derive = "1.0.103"
|
||||
serde_yaml = "0.8.11"
|
||||
url = "2.1.1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
// Wallet settings that can be configured for long-term use
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fs::{create_dir_all, File},
|
||||
io::{self, Write},
|
||||
path::Path,
|
||||
};
|
||||
use std::io;
|
||||
use url::Url;
|
||||
|
||||
lazy_static! {
|
||||
@@ -30,7 +26,7 @@ impl Default for Config {
|
||||
keypair_path.extend(&[".config", "solana", "id.json"]);
|
||||
keypair_path.to_str().unwrap().to_string()
|
||||
};
|
||||
let json_rpc_url = "http://127.0.0.1:8899".to_string();
|
||||
let json_rpc_url = "https://api.mainnet-beta.solana.com".to_string();
|
||||
|
||||
// Empty websocket_url string indicates the client should
|
||||
// `Config::compute_websocket_url(&json_rpc_url)`
|
||||
@@ -46,23 +42,11 @@ impl Default for Config {
|
||||
|
||||
impl Config {
|
||||
pub fn load(config_file: &str) -> Result<Self, io::Error> {
|
||||
let file = File::open(config_file.to_string())?;
|
||||
let config = serde_yaml::from_reader(file)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
|
||||
Ok(config)
|
||||
crate::load_config_file(config_file)
|
||||
}
|
||||
|
||||
pub fn save(&self, config_file: &str) -> Result<(), io::Error> {
|
||||
let serialized = serde_yaml::to_string(self)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
|
||||
|
||||
if let Some(outdir) = Path::new(&config_file).parent() {
|
||||
create_dir_all(outdir)?;
|
||||
}
|
||||
let mut file = File::create(config_file)?;
|
||||
file.write_all(&serialized.into_bytes())?;
|
||||
|
||||
Ok(())
|
||||
crate::save_config_file(self, config_file)
|
||||
}
|
||||
|
||||
pub fn compute_websocket_url(json_rpc_url: &str) -> String {
|
||||
@@ -76,17 +60,38 @@ impl Config {
|
||||
ws_url
|
||||
.set_scheme(if is_secure { "wss" } else { "ws" })
|
||||
.expect("unable to set scheme");
|
||||
let ws_port = match json_rpc_url.port() {
|
||||
Some(port) => port + 1,
|
||||
None => {
|
||||
if is_secure {
|
||||
8901
|
||||
} else {
|
||||
8900
|
||||
}
|
||||
}
|
||||
};
|
||||
ws_url.set_port(Some(ws_port)).expect("unable to set port");
|
||||
if let Some(port) = json_rpc_url.port() {
|
||||
ws_url.set_port(Some(port + 1)).expect("unable to set port");
|
||||
}
|
||||
ws_url.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn compute_websocket_url() {
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"http://devnet.solana.com"),
|
||||
"ws://devnet.solana.com/".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"https://devnet.solana.com"),
|
||||
"wss://devnet.solana.com/".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"http://example.com:8899"),
|
||||
"ws://example.com:8900/".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"https://example.com:1234"),
|
||||
"wss://example.com:1235/".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(Config::compute_websocket_url(&"garbage"), String::new());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,3 +3,37 @@ extern crate lazy_static;
|
||||
|
||||
mod config;
|
||||
pub use config::{Config, CONFIG_FILE};
|
||||
|
||||
use std::{
|
||||
fs::{create_dir_all, File},
|
||||
io::{self, Write},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
pub fn load_config_file<T, P>(config_file: P) -> Result<T, io::Error>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let file = File::open(config_file)?;
|
||||
let config = serde_yaml::from_reader(file)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn save_config_file<T, P>(config: &T, config_file: P) -> Result<(), io::Error>
|
||||
where
|
||||
T: serde::ser::Serialize,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let serialized = serde_yaml::to_string(config)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
|
||||
|
||||
if let Some(outdir) = config_file.as_ref().parent() {
|
||||
create_dir_all(outdir)?;
|
||||
}
|
||||
let mut file = File::create(config_file)?;
|
||||
file.write_all(&serialized.into_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -11,45 +11,49 @@ homepage = "https://solana.com/"
|
||||
[dependencies]
|
||||
bincode = "1.2.1"
|
||||
bs58 = "0.3.0"
|
||||
chrono = { version = "0.4.10", features = ["serde"] }
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
clap = "2.33.0"
|
||||
criterion-stats = "0.3.0"
|
||||
ctrlc = { version = "3.1.4", features = ["termination"] }
|
||||
console = "0.9.2"
|
||||
console = "0.10.0"
|
||||
dirs = "2.0.2"
|
||||
log = "0.4.8"
|
||||
Inflector = "0.11.4"
|
||||
indicatif = "0.14.0"
|
||||
humantime = "2.0.0"
|
||||
num-traits = "0.2"
|
||||
pretty-hex = "0.1.1"
|
||||
reqwest = { version = "0.10.1", default-features = false, features = ["blocking", "rustls-tls"] }
|
||||
serde = "1.0.104"
|
||||
reqwest = { version = "0.10.4", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serde = "1.0.105"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.46"
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.9" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.0.9" }
|
||||
solana-client = { path = "../client", version = "1.0.9" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.0.9" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.9" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.9" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.9" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.9" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.0.9" }
|
||||
titlecase = "1.1.0"
|
||||
thiserror = "1.0.11"
|
||||
serde_json = "1.0.48"
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.1.15" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.1.15" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.1.15" }
|
||||
solana-client = { path = "../client", version = "1.1.15" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.1.15" }
|
||||
solana-faucet = { path = "../faucet", version = "1.1.15" }
|
||||
solana-logger = { path = "../logger", version = "1.1.15" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.15" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.1.15" }
|
||||
solana-runtime = { path = "../runtime", version = "1.1.15" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.15" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.1.15" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.1.15" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.1.15" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.1.15" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.1.15" }
|
||||
thiserror = "1.0.13"
|
||||
url = "2.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.9" }
|
||||
solana-core = { path = "../core", version = "1.1.15" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.1.15" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "solana"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
589
cli/src/cli.rs
589
cli/src/cli.rs
File diff suppressed because it is too large
Load Diff
982
cli/src/cli_output.rs
Normal file
982
cli/src/cli_output.rs
Normal file
@@ -0,0 +1,982 @@
|
||||
use crate::{cli::build_balance_message, display::writeln_name_value};
|
||||
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
|
||||
use console::{style, Emoji};
|
||||
use inflector::cases::titlecase::to_title_case;
|
||||
use serde::Serialize;
|
||||
use serde_json::{Map, Value};
|
||||
use solana_client::rpc_response::{
|
||||
RpcAccountBalance, RpcEpochInfo, RpcKeyedAccount, RpcSupply, RpcVoteAccountInfo,
|
||||
};
|
||||
use solana_sdk::{
|
||||
clock::{self, Epoch, Slot, UnixTimestamp},
|
||||
native_token::lamports_to_sol,
|
||||
stake_history::StakeHistoryEntry,
|
||||
};
|
||||
use solana_stake_program::stake_state::{Authorized, Lockup};
|
||||
use solana_vote_program::{
|
||||
authorized_voters::AuthorizedVoters,
|
||||
vote_state::{BlockTimestamp, Lockout},
|
||||
};
|
||||
use std::{collections::BTreeMap, fmt, time::Duration};
|
||||
|
||||
static WARNING: Emoji = Emoji("⚠️", "!");
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum OutputFormat {
|
||||
Display,
|
||||
Json,
|
||||
JsonCompact,
|
||||
}
|
||||
|
||||
impl OutputFormat {
|
||||
pub fn formatted_string<T>(&self, item: &T) -> String
|
||||
where
|
||||
T: Serialize + fmt::Display,
|
||||
{
|
||||
match self {
|
||||
OutputFormat::Display => format!("{}", item),
|
||||
OutputFormat::Json => serde_json::to_string_pretty(item).unwrap(),
|
||||
OutputFormat::JsonCompact => serde_json::to_value(item).unwrap().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliAccount {
|
||||
#[serde(flatten)]
|
||||
pub keyed_account: RpcKeyedAccount,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliAccount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Public Key:", &self.keyed_account.pubkey)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Balance:",
|
||||
&build_balance_message(
|
||||
self.keyed_account.account.lamports,
|
||||
self.use_lamports_unit,
|
||||
true,
|
||||
),
|
||||
)?;
|
||||
writeln_name_value(f, "Owner:", &self.keyed_account.account.owner)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Executable:",
|
||||
&self.keyed_account.account.executable.to_string(),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Rent Epoch:",
|
||||
&self.keyed_account.account.rent_epoch.to_string(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct CliBlockProduction {
|
||||
pub epoch: Epoch,
|
||||
pub start_slot: Slot,
|
||||
pub end_slot: Slot,
|
||||
pub total_slots: usize,
|
||||
pub total_blocks_produced: usize,
|
||||
pub total_slots_skipped: usize,
|
||||
pub leaders: Vec<CliBlockProductionEntry>,
|
||||
pub individual_slot_status: Vec<CliSlotStatus>,
|
||||
#[serde(skip_serializing)]
|
||||
pub verbose: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliBlockProduction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<44} {:>15} {:>15} {:>15} {:>23}",
|
||||
"Identity Pubkey",
|
||||
"Leader Slots",
|
||||
"Blocks Produced",
|
||||
"Skipped Slots",
|
||||
"Skipped Slot Percentage",
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for leader in &self.leaders {
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
|
||||
leader.identity_pubkey,
|
||||
leader.leader_slots,
|
||||
leader.blocks_produced,
|
||||
leader.skipped_slots,
|
||||
leader.skipped_slots as f64 / leader.leader_slots as f64 * 100.
|
||||
)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
|
||||
format!("Epoch {} total:", self.epoch),
|
||||
self.total_slots,
|
||||
self.total_blocks_produced,
|
||||
self.total_slots_skipped,
|
||||
self.total_slots_skipped as f64 / self.total_slots as f64 * 100.
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" (using data from {} slots: {} to {})",
|
||||
self.total_slots, self.start_slot, self.end_slot
|
||||
)?;
|
||||
if self.verbose {
|
||||
writeln!(f)?;
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(" {:<15} {:<44}", "Slot", "Identity Pubkey")).bold(),
|
||||
)?;
|
||||
for status in &self.individual_slot_status {
|
||||
if status.skipped {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<15} {:<44} SKIPPED",
|
||||
status.slot, status.leader
|
||||
))
|
||||
.red()
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(" {:<15} {:<44}", status.slot, status.leader))
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliBlockProductionEntry {
|
||||
pub identity_pubkey: String,
|
||||
pub leader_slots: u64,
|
||||
pub blocks_produced: u64,
|
||||
pub skipped_slots: u64,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliSlotStatus {
|
||||
pub slot: Slot,
|
||||
pub leader: String,
|
||||
pub skipped: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliEpochInfo {
|
||||
#[serde(flatten)]
|
||||
pub epoch_info: RpcEpochInfo,
|
||||
}
|
||||
|
||||
impl From<RpcEpochInfo> for CliEpochInfo {
|
||||
fn from(epoch_info: RpcEpochInfo) -> Self {
|
||||
Self { epoch_info }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliEpochInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Slot:", &self.epoch_info.absolute_slot.to_string())?;
|
||||
writeln_name_value(f, "Epoch:", &self.epoch_info.epoch.to_string())?;
|
||||
let start_slot = self.epoch_info.absolute_slot - self.epoch_info.slot_index;
|
||||
let end_slot = start_slot + self.epoch_info.slots_in_epoch;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Epoch Slot Range:",
|
||||
&format!("[{}..{})", start_slot, end_slot),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Epoch Completed Percent:",
|
||||
&format!(
|
||||
"{:>3.3}%",
|
||||
self.epoch_info.slot_index as f64 / self.epoch_info.slots_in_epoch as f64 * 100_f64
|
||||
),
|
||||
)?;
|
||||
let remaining_slots_in_epoch = self.epoch_info.slots_in_epoch - self.epoch_info.slot_index;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Epoch Completed Slots:",
|
||||
&format!(
|
||||
"{}/{} ({} remaining)",
|
||||
self.epoch_info.slot_index,
|
||||
self.epoch_info.slots_in_epoch,
|
||||
remaining_slots_in_epoch
|
||||
),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Epoch Completed Time:",
|
||||
&format!(
|
||||
"{}/{} ({} remaining)",
|
||||
slot_to_human_time(self.epoch_info.slot_index),
|
||||
slot_to_human_time(self.epoch_info.slots_in_epoch),
|
||||
slot_to_human_time(remaining_slots_in_epoch)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn slot_to_human_time(slot: Slot) -> String {
|
||||
humantime::format_duration(Duration::from_secs(
|
||||
slot * clock::DEFAULT_TICKS_PER_SLOT / clock::DEFAULT_TICKS_PER_SECOND,
|
||||
))
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliValidators {
|
||||
pub total_active_stake: u64,
|
||||
pub total_current_stake: u64,
|
||||
pub total_deliquent_stake: u64,
|
||||
pub current_validators: Vec<CliValidator>,
|
||||
pub delinquent_validators: Vec<CliValidator>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliValidators {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn write_vote_account(
|
||||
f: &mut fmt::Formatter,
|
||||
validator: &CliValidator,
|
||||
total_active_stake: u64,
|
||||
use_lamports_unit: bool,
|
||||
delinquent: bool,
|
||||
) -> fmt::Result {
|
||||
fn non_zero_or_dash(v: u64) -> String {
|
||||
if v == 0 {
|
||||
"-".into()
|
||||
} else {
|
||||
format!("{}", v)
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"{} {:<44} {:<44} {:>9}% {:>8} {:>10} {:>7} {}",
|
||||
if delinquent {
|
||||
WARNING.to_string()
|
||||
} else {
|
||||
" ".to_string()
|
||||
},
|
||||
validator.identity_pubkey,
|
||||
validator.vote_account_pubkey,
|
||||
validator.commission,
|
||||
non_zero_or_dash(validator.last_vote),
|
||||
non_zero_or_dash(validator.root_slot),
|
||||
validator.credits,
|
||||
if validator.activated_stake > 0 {
|
||||
format!(
|
||||
"{} ({:.2}%)",
|
||||
build_balance_message(validator.activated_stake, use_lamports_unit, true),
|
||||
100. * validator.activated_stake as f64 / total_active_stake as f64
|
||||
)
|
||||
} else {
|
||||
"-".into()
|
||||
},
|
||||
)
|
||||
}
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Active Stake:",
|
||||
&build_balance_message(self.total_active_stake, self.use_lamports_unit, true),
|
||||
)?;
|
||||
if self.total_deliquent_stake > 0 {
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Current Stake:",
|
||||
&format!(
|
||||
"{} ({:0.2}%)",
|
||||
&build_balance_message(self.total_current_stake, self.use_lamports_unit, true),
|
||||
100. * self.total_current_stake as f64 / self.total_active_stake as f64
|
||||
),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Delinquent Stake:",
|
||||
&format!(
|
||||
"{} ({:0.2}%)",
|
||||
&build_balance_message(
|
||||
self.total_deliquent_stake,
|
||||
self.use_lamports_unit,
|
||||
true
|
||||
),
|
||||
100. * self.total_deliquent_stake as f64 / self.total_active_stake as f64
|
||||
),
|
||||
)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<44} {:<44} {} {} {} {:>7} {}",
|
||||
"Identity Pubkey",
|
||||
"Vote Account Pubkey",
|
||||
"Commission",
|
||||
"Last Vote",
|
||||
"Root Block",
|
||||
"Credits",
|
||||
"Active Stake",
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for validator in &self.current_validators {
|
||||
write_vote_account(
|
||||
f,
|
||||
validator,
|
||||
self.total_active_stake,
|
||||
self.use_lamports_unit,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
for validator in &self.delinquent_validators {
|
||||
write_vote_account(
|
||||
f,
|
||||
validator,
|
||||
self.total_active_stake,
|
||||
self.use_lamports_unit,
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliValidator {
|
||||
pub identity_pubkey: String,
|
||||
pub vote_account_pubkey: String,
|
||||
pub commission: u8,
|
||||
pub last_vote: u64,
|
||||
pub root_slot: u64,
|
||||
pub credits: u64,
|
||||
pub activated_stake: u64,
|
||||
}
|
||||
|
||||
impl CliValidator {
|
||||
pub fn new(vote_account: &RpcVoteAccountInfo, current_epoch: Epoch) -> Self {
|
||||
Self {
|
||||
identity_pubkey: vote_account.node_pubkey.to_string(),
|
||||
vote_account_pubkey: vote_account.vote_pubkey.to_string(),
|
||||
commission: vote_account.commission,
|
||||
last_vote: vote_account.last_vote,
|
||||
root_slot: vote_account.root_slot,
|
||||
credits: vote_account
|
||||
.epoch_credits
|
||||
.iter()
|
||||
.find_map(|(epoch, credits, _)| {
|
||||
if *epoch == current_epoch {
|
||||
Some(*credits)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(0),
|
||||
activated_stake: vote_account.activated_stake,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliNonceAccount {
|
||||
pub balance: u64,
|
||||
pub minimum_balance_for_rent_exemption: u64,
|
||||
pub nonce: Option<String>,
|
||||
pub lamports_per_signature: Option<u64>,
|
||||
pub authority: Option<String>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliNonceAccount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"Balance: {}",
|
||||
build_balance_message(self.balance, self.use_lamports_unit, true)
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Minimum Balance Required: {}",
|
||||
build_balance_message(
|
||||
self.minimum_balance_for_rent_exemption,
|
||||
self.use_lamports_unit,
|
||||
true
|
||||
)
|
||||
)?;
|
||||
let nonce = self.nonce.as_deref().unwrap_or("uninitialized");
|
||||
writeln!(f, "Nonce: {}", nonce)?;
|
||||
if let Some(fees) = self.lamports_per_signature {
|
||||
writeln!(f, "Fee: {} lamports per signature", fees)?;
|
||||
} else {
|
||||
writeln!(f, "Fees: uninitialized")?;
|
||||
}
|
||||
let authority = self.authority.as_deref().unwrap_or("uninitialized");
|
||||
writeln!(f, "Authority: {}", authority)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliStakeVec(Vec<CliKeyedStakeState>);
|
||||
|
||||
impl CliStakeVec {
|
||||
pub fn new(list: Vec<CliKeyedStakeState>) -> Self {
|
||||
Self(list)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliStakeVec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for state in &self.0 {
|
||||
writeln!(f)?;
|
||||
write!(f, "{}", state)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliKeyedStakeState {
|
||||
pub stake_pubkey: String,
|
||||
#[serde(flatten)]
|
||||
pub stake_state: CliStakeState,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliKeyedStakeState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "Stake Pubkey: {}", self.stake_pubkey)?;
|
||||
write!(f, "{}", self.stake_state)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliStakeState {
|
||||
pub stake_type: CliStakeType,
|
||||
pub total_stake: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delegated_stake: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delegated_vote_account_address: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub activation_epoch: Option<Epoch>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deactivation_epoch: Option<Epoch>,
|
||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||
pub authorized: Option<CliAuthorized>,
|
||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||
pub lockup: Option<CliLockup>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliStakeState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn show_authorized(f: &mut fmt::Formatter, authorized: &CliAuthorized) -> fmt::Result {
|
||||
writeln!(f, "Stake Authority: {}", authorized.staker)?;
|
||||
writeln!(f, "Withdraw Authority: {}", authorized.withdrawer)?;
|
||||
Ok(())
|
||||
}
|
||||
fn show_lockup(f: &mut fmt::Formatter, lockup: &CliLockup) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"Lockup Timestamp: {} (UnixTimestamp: {})",
|
||||
DateTime::<Utc>::from_utc(
|
||||
NaiveDateTime::from_timestamp(lockup.unix_timestamp, 0),
|
||||
Utc
|
||||
)
|
||||
.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
lockup.unix_timestamp
|
||||
)?;
|
||||
writeln!(f, "Lockup Epoch: {}", lockup.epoch)?;
|
||||
writeln!(f, "Lockup Custodian: {}", lockup.custodian)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
match self.stake_type {
|
||||
CliStakeType::RewardsPool => writeln!(f, "Stake account is a rewards pool")?,
|
||||
CliStakeType::Uninitialized => writeln!(f, "Stake account is uninitialized")?,
|
||||
CliStakeType::Initialized => {
|
||||
writeln!(
|
||||
f,
|
||||
"Total Stake: {}",
|
||||
build_balance_message(self.total_stake, self.use_lamports_unit, true)
|
||||
)?;
|
||||
writeln!(f, "Stake account is undelegated")?;
|
||||
show_authorized(f, self.authorized.as_ref().unwrap())?;
|
||||
show_lockup(f, self.lockup.as_ref().unwrap())?;
|
||||
}
|
||||
CliStakeType::Stake => {
|
||||
writeln!(
|
||||
f,
|
||||
"Total Stake: {}",
|
||||
build_balance_message(self.total_stake, self.use_lamports_unit, true)
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Delegated Stake: {}",
|
||||
build_balance_message(
|
||||
self.delegated_stake.unwrap(),
|
||||
self.use_lamports_unit,
|
||||
true
|
||||
)
|
||||
)?;
|
||||
if let Some(delegated_vote_account_address) = &self.delegated_vote_account_address {
|
||||
writeln!(
|
||||
f,
|
||||
"Delegated Vote Account Address: {}",
|
||||
delegated_vote_account_address
|
||||
)?;
|
||||
}
|
||||
writeln!(
|
||||
f,
|
||||
"Stake activates starting from epoch: {}",
|
||||
self.activation_epoch.unwrap()
|
||||
)?;
|
||||
if let Some(deactivation_epoch) = self.deactivation_epoch {
|
||||
writeln!(
|
||||
f,
|
||||
"Stake deactivates starting from epoch: {}",
|
||||
deactivation_epoch
|
||||
)?;
|
||||
}
|
||||
show_authorized(f, self.authorized.as_ref().unwrap())?;
|
||||
show_lockup(f, self.lockup.as_ref().unwrap())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum CliStakeType {
|
||||
Stake,
|
||||
RewardsPool,
|
||||
Uninitialized,
|
||||
Initialized,
|
||||
}
|
||||
|
||||
impl Default for CliStakeType {
|
||||
fn default() -> Self {
|
||||
Self::Uninitialized
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliStakeHistory {
|
||||
pub entries: Vec<CliStakeHistoryEntry>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliStakeHistory {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<5} {:>20} {:>20} {:>20}",
|
||||
"Epoch", "Effective Stake", "Activating Stake", "Deactivating Stake",
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for entry in &self.entries {
|
||||
writeln!(
|
||||
f,
|
||||
" {:>5} {:>20} {:>20} {:>20} {}",
|
||||
entry.epoch,
|
||||
build_balance_message(entry.effective_stake, self.use_lamports_unit, false),
|
||||
build_balance_message(entry.activating_stake, self.use_lamports_unit, false),
|
||||
build_balance_message(entry.deactivating_stake, self.use_lamports_unit, false),
|
||||
if self.use_lamports_unit {
|
||||
"lamports"
|
||||
} else {
|
||||
"SOL"
|
||||
}
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&(Epoch, StakeHistoryEntry)> for CliStakeHistoryEntry {
|
||||
fn from((epoch, entry): &(Epoch, StakeHistoryEntry)) -> Self {
|
||||
Self {
|
||||
epoch: *epoch,
|
||||
effective_stake: entry.effective,
|
||||
activating_stake: entry.activating,
|
||||
deactivating_stake: entry.deactivating,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliStakeHistoryEntry {
|
||||
pub epoch: Epoch,
|
||||
pub effective_stake: u64,
|
||||
pub activating_stake: u64,
|
||||
pub deactivating_stake: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliAuthorized {
|
||||
pub staker: String,
|
||||
pub withdrawer: String,
|
||||
}
|
||||
|
||||
impl From<&Authorized> for CliAuthorized {
|
||||
fn from(authorized: &Authorized) -> Self {
|
||||
Self {
|
||||
staker: authorized.staker.to_string(),
|
||||
withdrawer: authorized.withdrawer.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliLockup {
|
||||
pub unix_timestamp: UnixTimestamp,
|
||||
pub epoch: Epoch,
|
||||
pub custodian: String,
|
||||
}
|
||||
|
||||
impl From<&Lockup> for CliLockup {
|
||||
fn from(lockup: &Lockup) -> Self {
|
||||
Self {
|
||||
unix_timestamp: lockup.unix_timestamp,
|
||||
epoch: lockup.epoch,
|
||||
custodian: lockup.custodian.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliValidatorInfoVec(Vec<CliValidatorInfo>);
|
||||
|
||||
impl CliValidatorInfoVec {
|
||||
pub fn new(list: Vec<CliValidatorInfo>) -> Self {
|
||||
Self(list)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliValidatorInfoVec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.0.is_empty() {
|
||||
writeln!(f, "No validator info accounts found")?;
|
||||
}
|
||||
for validator_info in &self.0 {
|
||||
writeln!(f)?;
|
||||
write!(f, "{}", validator_info)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliValidatorInfo {
|
||||
pub identity_pubkey: String,
|
||||
pub info_pubkey: String,
|
||||
pub info: Map<String, Value>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliValidatorInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_name_value(f, "Validator Identity Pubkey:", &self.identity_pubkey)?;
|
||||
writeln_name_value(f, " Info Pubkey:", &self.info_pubkey)?;
|
||||
for (key, value) in self.info.iter() {
|
||||
writeln_name_value(
|
||||
f,
|
||||
&format!(" {}:", to_title_case(key)),
|
||||
&value.as_str().unwrap_or("?"),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliVoteAccount {
|
||||
pub account_balance: u64,
|
||||
pub validator_identity: String,
|
||||
#[serde(flatten)]
|
||||
pub authorized_voters: CliAuthorizedVoters,
|
||||
pub authorized_withdrawer: String,
|
||||
pub credits: u64,
|
||||
pub commission: u8,
|
||||
pub root_slot: Option<Slot>,
|
||||
pub recent_timestamp: BlockTimestamp,
|
||||
pub votes: Vec<CliLockout>,
|
||||
pub epoch_voting_history: Vec<CliEpochVotingHistory>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliVoteAccount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"Account Balance: {}",
|
||||
build_balance_message(self.account_balance, self.use_lamports_unit, true)
|
||||
)?;
|
||||
writeln!(f, "Validator Identity: {}", self.validator_identity)?;
|
||||
writeln!(f, "Authorized Voters: {}", self.authorized_voters)?;
|
||||
writeln!(f, "Authorized Withdrawer: {}", self.authorized_withdrawer)?;
|
||||
writeln!(f, "Credits: {}", self.credits)?;
|
||||
writeln!(f, "Commission: {}%", self.commission)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Root Slot: {}",
|
||||
match self.root_slot {
|
||||
Some(slot) => slot.to_string(),
|
||||
None => "~".to_string(),
|
||||
}
|
||||
)?;
|
||||
writeln!(f, "Recent Timestamp: {:?}", self.recent_timestamp)?;
|
||||
if !self.votes.is_empty() {
|
||||
writeln!(f, "Recent Votes:")?;
|
||||
for vote in &self.votes {
|
||||
writeln!(
|
||||
f,
|
||||
"- slot: {}\n confirmation count: {}",
|
||||
vote.slot, vote.confirmation_count
|
||||
)?;
|
||||
}
|
||||
writeln!(f, "Epoch Voting History:")?;
|
||||
for epoch_info in &self.epoch_voting_history {
|
||||
writeln!(
|
||||
f,
|
||||
"- epoch: {}\n slots in epoch: {}\n credits earned: {}",
|
||||
epoch_info.epoch, epoch_info.slots_in_epoch, epoch_info.credits_earned,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliAuthorizedVoters {
|
||||
authorized_voters: BTreeMap<Epoch, String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliAuthorizedVoters {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self.authorized_voters)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AuthorizedVoters> for CliAuthorizedVoters {
|
||||
fn from(authorized_voters: &AuthorizedVoters) -> Self {
|
||||
let mut voter_map: BTreeMap<Epoch, String> = BTreeMap::new();
|
||||
for (epoch, voter) in authorized_voters.iter() {
|
||||
voter_map.insert(*epoch, voter.to_string());
|
||||
}
|
||||
Self {
|
||||
authorized_voters: voter_map,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliEpochVotingHistory {
|
||||
pub epoch: Epoch,
|
||||
pub slots_in_epoch: u64,
|
||||
pub credits_earned: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliLockout {
|
||||
pub slot: Slot,
|
||||
pub confirmation_count: u32,
|
||||
}
|
||||
|
||||
impl From<&Lockout> for CliLockout {
|
||||
fn from(lockout: &Lockout) -> Self {
|
||||
Self {
|
||||
slot: lockout.slot,
|
||||
confirmation_count: lockout.confirmation_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliBlockTime {
|
||||
pub slot: Slot,
|
||||
pub timestamp: UnixTimestamp,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliBlockTime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_name_value(f, "Block:", &self.slot.to_string())?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Date:",
|
||||
&format!(
|
||||
"{} (UnixTimestamp: {})",
|
||||
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.timestamp, 0), Utc)
|
||||
.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
self.timestamp
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliSignOnlyData {
|
||||
pub blockhash: String,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub signers: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub absent: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub bad_sig: Vec<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliSignOnlyData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Blockhash:", &self.blockhash)?;
|
||||
if !self.signers.is_empty() {
|
||||
writeln!(f, "{}", style("Signers (Pubkey=Signature):").bold())?;
|
||||
for signer in self.signers.iter() {
|
||||
writeln!(f, " {}", signer)?;
|
||||
}
|
||||
}
|
||||
if !self.absent.is_empty() {
|
||||
writeln!(f, "{}", style("Absent Signers (Pubkey):").bold())?;
|
||||
for pubkey in self.absent.iter() {
|
||||
writeln!(f, " {}", pubkey)?;
|
||||
}
|
||||
}
|
||||
if !self.bad_sig.is_empty() {
|
||||
writeln!(f, "{}", style("Bad Signatures (Pubkey):").bold())?;
|
||||
for pubkey in self.bad_sig.iter() {
|
||||
writeln!(f, " {}", pubkey)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliSignature {
|
||||
pub signature: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliSignature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Signature:", &self.signature)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliAccountBalances {
|
||||
pub accounts: Vec<RpcAccountBalance>,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliAccountBalances {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!("{:<44} {}", "Address", "Balance",)).bold()
|
||||
)?;
|
||||
for account in &self.accounts {
|
||||
writeln!(
|
||||
f,
|
||||
"{:<44} {}",
|
||||
account.address,
|
||||
&format!("{} SOL", lamports_to_sol(account.lamports))
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CliSupply {
|
||||
pub total: u64,
|
||||
pub circulating: u64,
|
||||
pub non_circulating: u64,
|
||||
pub non_circulating_accounts: Vec<String>,
|
||||
#[serde(skip_serializing)]
|
||||
pub print_accounts: bool,
|
||||
}
|
||||
|
||||
impl From<RpcSupply> for CliSupply {
|
||||
fn from(rpc_supply: RpcSupply) -> Self {
|
||||
Self {
|
||||
total: rpc_supply.total,
|
||||
circulating: rpc_supply.circulating,
|
||||
non_circulating: rpc_supply.non_circulating,
|
||||
non_circulating_accounts: rpc_supply.non_circulating_accounts,
|
||||
print_accounts: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliSupply {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_name_value(f, "Total:", &format!("{} SOL", lamports_to_sol(self.total)))?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Circulating:",
|
||||
&format!("{} SOL", lamports_to_sol(self.circulating)),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Non-Circulating:",
|
||||
&format!("{} SOL", lamports_to_sol(self.non_circulating)),
|
||||
)?;
|
||||
if self.print_accounts {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Non-Circulating Accounts:", " ")?;
|
||||
for account in &self.non_circulating_accounts {
|
||||
writeln!(f, " {}", account)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,11 @@
|
||||
use crate::cli::SettingType;
|
||||
use console::style;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::{
|
||||
hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize,
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_transaction_status::RpcTransactionStatusMeta;
|
||||
use std::{fmt, io};
|
||||
|
||||
// Pretty print a "name value"
|
||||
pub fn println_name_value(name: &str, value: &str) {
|
||||
@@ -12,6 +17,15 @@ pub fn println_name_value(name: &str, value: &str) {
|
||||
println!("{} {}", style(name).bold(), styled_value);
|
||||
}
|
||||
|
||||
pub fn writeln_name_value(f: &mut fmt::Formatter, name: &str, value: &str) -> fmt::Result {
|
||||
let styled_value = if value == "" {
|
||||
style("(not set)").italic()
|
||||
} else {
|
||||
style(value)
|
||||
};
|
||||
writeln!(f, "{} {}", style(name).bold(), styled_value)
|
||||
}
|
||||
|
||||
pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
|
||||
let description = match setting_type {
|
||||
SettingType::Explicit => "",
|
||||
@@ -49,3 +63,140 @@ pub fn println_signers(
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
pub fn write_transaction<W: io::Write>(
|
||||
w: &mut W,
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<RpcTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
) -> io::Result<()> {
|
||||
let message = &transaction.message;
|
||||
writeln!(
|
||||
w,
|
||||
"{}Recent Blockhash: {:?}",
|
||||
prefix, message.recent_blockhash
|
||||
)?;
|
||||
for (signature_index, signature) in transaction.signatures.iter().enumerate() {
|
||||
writeln!(
|
||||
w,
|
||||
"{}Signature {}: {:?}",
|
||||
prefix, signature_index, signature
|
||||
)?;
|
||||
}
|
||||
writeln!(w, "{}{:?}", prefix, message.header)?;
|
||||
for (account_index, account) in message.account_keys.iter().enumerate() {
|
||||
writeln!(w, "{}Account {}: {:?}", prefix, account_index, account)?;
|
||||
}
|
||||
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
||||
let program_pubkey = message.account_keys[instruction.program_id_index as usize];
|
||||
writeln!(w, "{}Instruction {}", prefix, instruction_index)?;
|
||||
writeln!(
|
||||
w,
|
||||
"{} Program: {} ({})",
|
||||
prefix, program_pubkey, instruction.program_id_index
|
||||
)?;
|
||||
for (account_index, account) in instruction.accounts.iter().enumerate() {
|
||||
let account_pubkey = message.account_keys[*account as usize];
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {}: {} ({})",
|
||||
prefix, account_index, account_pubkey, account
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut raw = true;
|
||||
if program_pubkey == solana_vote_program::id() {
|
||||
if let Ok(vote_instruction) = limited_deserialize::<
|
||||
solana_vote_program::vote_instruction::VoteInstruction,
|
||||
>(&instruction.data)
|
||||
{
|
||||
writeln!(w, "{} {:?}", prefix, vote_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
} else if program_pubkey == solana_stake_program::id() {
|
||||
if let Ok(stake_instruction) = limited_deserialize::<
|
||||
solana_stake_program::stake_instruction::StakeInstruction,
|
||||
>(&instruction.data)
|
||||
{
|
||||
writeln!(w, "{} {:?}", prefix, stake_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
} else if program_pubkey == solana_sdk::system_program::id() {
|
||||
if let Ok(system_instruction) = limited_deserialize::<
|
||||
solana_sdk::system_instruction::SystemInstruction,
|
||||
>(&instruction.data)
|
||||
{
|
||||
writeln!(w, "{} {:?}", prefix, system_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
}
|
||||
|
||||
if raw {
|
||||
writeln!(w, "{} Data: {:?}", prefix, instruction.data)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(transaction_status) = transaction_status {
|
||||
writeln!(
|
||||
w,
|
||||
"{}Status: {}",
|
||||
prefix,
|
||||
match &transaction_status.status {
|
||||
Ok(_) => "Ok".into(),
|
||||
Err(err) => err.to_string(),
|
||||
}
|
||||
)?;
|
||||
writeln!(
|
||||
w,
|
||||
"{} Fee: {} SOL",
|
||||
prefix,
|
||||
lamports_to_sol(transaction_status.fee)
|
||||
)?;
|
||||
assert_eq!(
|
||||
transaction_status.pre_balances.len(),
|
||||
transaction_status.post_balances.len()
|
||||
);
|
||||
for (i, (pre, post)) in transaction_status
|
||||
.pre_balances
|
||||
.iter()
|
||||
.zip(transaction_status.post_balances.iter())
|
||||
.enumerate()
|
||||
{
|
||||
if pre == post {
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {} balance: {} SOL",
|
||||
prefix,
|
||||
i,
|
||||
lamports_to_sol(*pre)
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {} balance: {} SOL -> {} SOL",
|
||||
prefix,
|
||||
i,
|
||||
lamports_to_sol(*pre),
|
||||
lamports_to_sol(*post)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeln!(w, "{}Status: Unavailable", prefix)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn println_transaction(
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<RpcTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
) {
|
||||
let mut w = Vec::new();
|
||||
if write_transaction(&mut w, transaction, transaction_status, prefix).is_ok() {
|
||||
if let Ok(s) = String::from_utf8(w) {
|
||||
print!("{}", s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod cli;
|
||||
pub mod cli_output;
|
||||
pub mod cluster_query;
|
||||
pub mod display;
|
||||
pub mod nonce;
|
||||
|
||||
@@ -2,15 +2,15 @@ use clap::{crate_description, crate_name, AppSettings, Arg, ArgGroup, ArgMatches
|
||||
use console::style;
|
||||
|
||||
use solana_clap_utils::{
|
||||
input_validators::is_url, keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, offline::SIGN_ONLY_ARG,
|
||||
DisplayError,
|
||||
input_validators::is_url, keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, DisplayError,
|
||||
};
|
||||
use solana_cli::{
|
||||
cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners},
|
||||
cli_output::OutputFormat,
|
||||
display::{println_name_value, println_name_value_or},
|
||||
};
|
||||
use solana_cli_config::{Config, CONFIG_FILE};
|
||||
use solana_remote_wallet::remote_wallet::{maybe_wallet_manager, RemoteWalletManager};
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use std::{error, sync::Arc};
|
||||
|
||||
fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error>> {
|
||||
@@ -102,7 +102,7 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
|
||||
pub fn parse_args<'a>(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: Option<Arc<RemoteWalletManager>>,
|
||||
mut wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<(CliConfig<'a>, CliSigners), Box<dyn error::Error>> {
|
||||
let config = if let Some(config_file) = matches.value_of("config_file") {
|
||||
Config::load(config_file).unwrap_or_default()
|
||||
@@ -125,7 +125,16 @@ pub fn parse_args<'a>(
|
||||
);
|
||||
|
||||
let CliCommandInfo { command, signers } =
|
||||
parse_command(&matches, &default_signer_path, wallet_manager.as_ref())?;
|
||||
parse_command(&matches, &default_signer_path, &mut wallet_manager)?;
|
||||
|
||||
let output_format = matches
|
||||
.value_of("output_format")
|
||||
.map(|value| match value {
|
||||
"json" => OutputFormat::Json,
|
||||
"json-compact" => OutputFormat::JsonCompact,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.unwrap_or(OutputFormat::Display);
|
||||
|
||||
Ok((
|
||||
CliConfig {
|
||||
@@ -136,6 +145,7 @@ pub fn parse_args<'a>(
|
||||
keypair_path: default_signer_path,
|
||||
rpc_client: None,
|
||||
verbose: matches.is_present("verbose"),
|
||||
output_format,
|
||||
},
|
||||
signers,
|
||||
))
|
||||
@@ -197,6 +207,14 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.global(true)
|
||||
.help("Show additional information"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output_format")
|
||||
.long("output")
|
||||
.global(true)
|
||||
.takes_value(true)
|
||||
.possible_values(&["json", "json-compact"])
|
||||
.help("Return information in specified output format. Supports: json, json-compact"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
|
||||
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
|
||||
@@ -238,18 +256,12 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
|
||||
fn do_main(matches: &ArgMatches<'_>) -> Result<(), Box<dyn error::Error>> {
|
||||
if parse_settings(&matches)? {
|
||||
let wallet_manager = maybe_wallet_manager()?;
|
||||
let mut wallet_manager = None;
|
||||
|
||||
let (mut config, signers) = parse_args(&matches, wallet_manager)?;
|
||||
let (mut config, signers) = parse_args(&matches, &mut wallet_manager)?;
|
||||
config.signers = signers.iter().map(|s| s.as_ref()).collect();
|
||||
let result = process_command(&config)?;
|
||||
let (_, submatches) = matches.subcommand();
|
||||
let sign_only = submatches
|
||||
.map(|m| m.is_present(SIGN_ONLY_ARG.name))
|
||||
.unwrap_or(false);
|
||||
if !sign_only {
|
||||
println!("{}", result);
|
||||
}
|
||||
println!("{}", result);
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
118
cli/src/nonce.rs
118
cli/src/nonce.rs
@@ -1,7 +1,10 @@
|
||||
use crate::cli::{
|
||||
build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
|
||||
SignerIndex,
|
||||
use crate::{
|
||||
cli::{
|
||||
check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
|
||||
ProcessResult, SignerIndex,
|
||||
},
|
||||
cli_output::CliNonceAccount,
|
||||
};
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{
|
||||
@@ -21,9 +24,8 @@ use solana_sdk::{
|
||||
},
|
||||
pubkey::Pubkey,
|
||||
system_instruction::{
|
||||
advance_nonce_account, authorize_nonce_account, create_address_with_seed,
|
||||
create_nonce_account, create_nonce_account_with_seed, withdraw_nonce_account, NonceError,
|
||||
SystemError,
|
||||
advance_nonce_account, authorize_nonce_account, create_nonce_account,
|
||||
create_nonce_account_with_seed, withdraw_nonce_account, NonceError, SystemError,
|
||||
},
|
||||
system_program,
|
||||
transaction::Transaction,
|
||||
@@ -277,7 +279,7 @@ pub fn data_from_state(state: &State) -> Result<&Data, CliNonceError> {
|
||||
pub fn parse_authorize_nonce_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
let new_authority = pubkey_of_signer(matches, "new_authority", wallet_manager)?.unwrap();
|
||||
@@ -305,7 +307,7 @@ pub fn parse_authorize_nonce_account(
|
||||
pub fn parse_nonce_create_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let (nonce_account, nonce_account_pubkey) =
|
||||
signer_of(matches, "nonce_account_keypair", wallet_manager)?;
|
||||
@@ -334,7 +336,7 @@ pub fn parse_nonce_create_account(
|
||||
|
||||
pub fn parse_get_nonce(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account_pubkey =
|
||||
pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -348,7 +350,7 @@ pub fn parse_get_nonce(
|
||||
pub fn parse_new_nonce(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
let (nonce_authority, nonce_authority_pubkey) =
|
||||
@@ -373,7 +375,7 @@ pub fn parse_new_nonce(
|
||||
|
||||
pub fn parse_show_nonce_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account_pubkey =
|
||||
pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -391,7 +393,7 @@ pub fn parse_show_nonce_account(
|
||||
pub fn parse_withdraw_from_nonce_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
let destination_account_pubkey =
|
||||
@@ -460,8 +462,8 @@ pub fn process_authorize_nonce_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<NonceError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<NonceError>(result, &config)
|
||||
}
|
||||
|
||||
pub fn process_create_nonce_account(
|
||||
@@ -474,7 +476,7 @@ pub fn process_create_nonce_account(
|
||||
) -> ProcessResult {
|
||||
let nonce_account_pubkey = config.signers[nonce_account].pubkey();
|
||||
let nonce_account_address = if let Some(seed) = seed.clone() {
|
||||
create_address_with_seed(&nonce_account_pubkey, &seed, &system_program::id())?
|
||||
Pubkey::create_with_seed(&nonce_account_pubkey, &seed, &system_program::id())?
|
||||
} else {
|
||||
nonce_account_pubkey
|
||||
};
|
||||
@@ -537,8 +539,8 @@ pub fn process_create_nonce_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
|
||||
pub fn process_get_nonce(rpc_client: &RpcClient, nonce_account_pubkey: &Pubkey) -> ProcessResult {
|
||||
@@ -578,46 +580,32 @@ pub fn process_new_nonce(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client
|
||||
.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0], nonce_authority]);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
|
||||
pub fn process_show_nonce_account(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
nonce_account_pubkey: &Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
let nonce_account = get_account(rpc_client, nonce_account_pubkey)?;
|
||||
let print_account = |data: Option<&nonce::state::Data>| {
|
||||
println!(
|
||||
"Balance: {}",
|
||||
build_balance_message(nonce_account.lamports, use_lamports_unit, true)
|
||||
);
|
||||
println!(
|
||||
"Minimum Balance Required: {}",
|
||||
build_balance_message(
|
||||
rpc_client.get_minimum_balance_for_rent_exemption(State::size())?,
|
||||
use_lamports_unit,
|
||||
true
|
||||
)
|
||||
);
|
||||
match data {
|
||||
Some(ref data) => {
|
||||
println!("Nonce: {}", data.blockhash);
|
||||
println!(
|
||||
"Fee: {} lamports per signature",
|
||||
data.fee_calculator.lamports_per_signature
|
||||
);
|
||||
println!("Authority: {}", data.authority);
|
||||
}
|
||||
None => {
|
||||
println!("Nonce: uninitialized");
|
||||
println!("Fees: uninitialized");
|
||||
println!("Authority: uninitialized");
|
||||
}
|
||||
let mut nonce_account = CliNonceAccount {
|
||||
balance: nonce_account.lamports,
|
||||
minimum_balance_for_rent_exemption: rpc_client
|
||||
.get_minimum_balance_for_rent_exemption(State::size())?,
|
||||
use_lamports_unit,
|
||||
..CliNonceAccount::default()
|
||||
};
|
||||
if let Some(ref data) = data {
|
||||
nonce_account.nonce = Some(data.blockhash.to_string());
|
||||
nonce_account.lamports_per_signature = Some(data.fee_calculator.lamports_per_signature);
|
||||
nonce_account.authority = Some(data.authority.to_string());
|
||||
}
|
||||
Ok("".to_string())
|
||||
|
||||
Ok(config.output_format.formatted_string(&nonce_account))
|
||||
};
|
||||
match state_from_account(&nonce_account)? {
|
||||
State::Uninitialized => print_account(None),
|
||||
@@ -651,8 +639,8 @@ pub fn process_withdraw_from_nonce_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<NonceError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<NonceError>(result, &config)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -698,7 +686,12 @@ mod tests {
|
||||
&Pubkey::default().to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize_nonce_account, &default_keypair_file, None).unwrap(),
|
||||
parse_command(
|
||||
&test_authorize_nonce_account,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::AuthorizeNonceAccount {
|
||||
nonce_account: nonce_account_pubkey,
|
||||
@@ -719,7 +712,12 @@ mod tests {
|
||||
&authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize_nonce_account, &default_keypair_file, None).unwrap(),
|
||||
parse_command(
|
||||
&test_authorize_nonce_account,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::AuthorizeNonceAccount {
|
||||
nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
|
||||
@@ -741,7 +739,7 @@ mod tests {
|
||||
"50",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_nonce_account, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_create_nonce_account, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateNonceAccount {
|
||||
nonce_account: 1,
|
||||
@@ -766,7 +764,7 @@ mod tests {
|
||||
&authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_nonce_account, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_create_nonce_account, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateNonceAccount {
|
||||
nonce_account: 1,
|
||||
@@ -788,7 +786,7 @@ mod tests {
|
||||
&nonce_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_get_nonce, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_get_nonce, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::GetNonce(nonce_account_keypair.pubkey()),
|
||||
signers: vec![],
|
||||
@@ -802,7 +800,7 @@ mod tests {
|
||||
.get_matches_from(vec!["test", "new-nonce", &keypair_file]);
|
||||
let nonce_account = read_keypair_file(&keypair_file).unwrap();
|
||||
assert_eq!(
|
||||
parse_command(&test_new_nonce, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_new_nonce, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::NewNonce {
|
||||
nonce_account: nonce_account.pubkey(),
|
||||
@@ -822,7 +820,7 @@ mod tests {
|
||||
]);
|
||||
let nonce_account = read_keypair_file(&keypair_file).unwrap();
|
||||
assert_eq!(
|
||||
parse_command(&test_new_nonce, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_new_nonce, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::NewNonce {
|
||||
nonce_account: nonce_account.pubkey(),
|
||||
@@ -842,7 +840,7 @@ mod tests {
|
||||
&nonce_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_show_nonce_account, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_show_nonce_account, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::ShowNonceAccount {
|
||||
nonce_account_pubkey: nonce_account_keypair.pubkey(),
|
||||
@@ -864,7 +862,7 @@ mod tests {
|
||||
parse_command(
|
||||
&test_withdraw_from_nonce_account,
|
||||
&default_keypair_file,
|
||||
None
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
@@ -892,7 +890,7 @@ mod tests {
|
||||
parse_command(
|
||||
&test_withdraw_from_nonce_account,
|
||||
&default_keypair_file,
|
||||
None
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
|
||||
@@ -79,32 +79,47 @@ pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
|
||||
let object: Value = serde_json::from_str(&reply).unwrap();
|
||||
let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
|
||||
let blockhash = blockhash_str.parse::<Hash>().unwrap();
|
||||
let signer_strings = object.get("signers").unwrap().as_array().unwrap();
|
||||
let present_signers = signer_strings
|
||||
.iter()
|
||||
.map(|signer_string| {
|
||||
let mut signer = signer_string.as_str().unwrap().split('=');
|
||||
let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
|
||||
let sig = Signature::from_str(signer.next().unwrap()).unwrap();
|
||||
(key, sig)
|
||||
})
|
||||
.collect();
|
||||
let signer_strings = object.get("absent").unwrap().as_array().unwrap();
|
||||
let absent_signers = signer_strings
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
let signer_strings = object.get("badSig").unwrap().as_array().unwrap();
|
||||
let bad_signers = signer_strings
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
let mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
|
||||
let signer_strings = object.get("signers");
|
||||
if let Some(sig_strings) = signer_strings {
|
||||
present_signers = sig_strings
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|signer_string| {
|
||||
let mut signer = signer_string.as_str().unwrap().split('=');
|
||||
let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
|
||||
let sig = Signature::from_str(signer.next().unwrap()).unwrap();
|
||||
(key, sig)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
let mut absent_signers: Vec<Pubkey> = Vec::new();
|
||||
let signer_strings = object.get("absent");
|
||||
if let Some(sig_strings) = signer_strings {
|
||||
absent_signers = sig_strings
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
let mut bad_signers: Vec<Pubkey> = Vec::new();
|
||||
let signer_strings = object.get("badSig");
|
||||
if let Some(sig_strings) = signer_strings {
|
||||
bad_signers = sig_strings
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
SignOnly {
|
||||
blockhash,
|
||||
present_signers,
|
||||
|
||||
352
cli/src/stake.rs
352
cli/src/stake.rs
@@ -1,15 +1,14 @@
|
||||
use crate::{
|
||||
cli::{
|
||||
build_balance_message, check_account_for_fee, check_unique_pubkeys, fee_payer_arg,
|
||||
generate_unique_signers, log_instruction_custom_error, nonce_authority_arg, return_signers,
|
||||
CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult, SignerIndex, FEE_PAYER_ARG,
|
||||
check_account_for_fee, check_unique_pubkeys, fee_payer_arg, generate_unique_signers,
|
||||
log_instruction_custom_error, nonce_authority_arg, return_signers, CliCommand,
|
||||
CliCommandInfo, CliConfig, CliError, ProcessResult, SignerIndex, FEE_PAYER_ARG,
|
||||
},
|
||||
cli_output::{CliStakeHistory, CliStakeHistoryEntry, CliStakeState, CliStakeType},
|
||||
nonce::{check_nonce_account, nonce_arg, NONCE_ARG, NONCE_AUTHORITY_ARG},
|
||||
offline::{blockhash_query::BlockhashQuery, *},
|
||||
};
|
||||
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
|
||||
use clap::{App, Arg, ArgGroup, ArgMatches, SubCommand};
|
||||
use console::style;
|
||||
use solana_clap_utils::{input_parsers::*, input_validators::*, offline::*, ArgConstant};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
@@ -17,7 +16,7 @@ use solana_sdk::{
|
||||
account_utils::StateMut,
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
system_instruction::{create_address_with_seed, SystemError},
|
||||
system_instruction::SystemError,
|
||||
sysvar::{
|
||||
stake_history::{self, StakeHistory},
|
||||
Sysvar,
|
||||
@@ -86,7 +85,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.takes_value(true)
|
||||
.validator(is_amount)
|
||||
.required(true)
|
||||
.help("The amount of send to the vote account, in SOL")
|
||||
.help("The amount to send to the stake account, in SOL")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("custodian")
|
||||
@@ -314,6 +313,14 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.arg(nonce_arg())
|
||||
.arg(nonce_authority_arg())
|
||||
.arg(fee_payer_arg())
|
||||
.arg(
|
||||
Arg::with_name("custodian")
|
||||
.long("custodian")
|
||||
.takes_value(true)
|
||||
.value_name("KEYPAIR")
|
||||
.validator(is_valid_signer)
|
||||
.help("Authority to override account lockup")
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("stake-set-lockup")
|
||||
@@ -403,7 +410,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
pub fn parse_stake_create_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let seed = matches.value_of("seed").map(|s| s.to_string());
|
||||
let epoch = value_of(matches, "lockup_epoch").unwrap_or(0);
|
||||
@@ -455,7 +462,7 @@ pub fn parse_stake_create_account(
|
||||
pub fn parse_stake_delegate_stake(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -497,7 +504,7 @@ pub fn parse_stake_delegate_stake(
|
||||
pub fn parse_stake_authorize(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -580,7 +587,7 @@ pub fn parse_stake_authorize(
|
||||
pub fn parse_split_stake(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -625,7 +632,7 @@ pub fn parse_split_stake(
|
||||
pub fn parse_stake_deactivate_stake(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -662,7 +669,7 @@ pub fn parse_stake_deactivate_stake(
|
||||
pub fn parse_stake_withdraw_stake(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -677,11 +684,15 @@ pub fn parse_stake_withdraw_stake(
|
||||
let (nonce_authority, nonce_authority_pubkey) =
|
||||
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
|
||||
let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
|
||||
|
||||
let mut bulk_signers = vec![withdraw_authority, fee_payer];
|
||||
if nonce_account.is_some() {
|
||||
bulk_signers.push(nonce_authority);
|
||||
}
|
||||
if custodian.is_some() {
|
||||
bulk_signers.push(custodian);
|
||||
}
|
||||
let signer_info =
|
||||
generate_unique_signers(bulk_signers, matches, default_signer_path, wallet_manager)?;
|
||||
|
||||
@@ -696,6 +707,7 @@ pub fn parse_stake_withdraw_stake(
|
||||
nonce_account,
|
||||
nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
|
||||
fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
|
||||
custodian: custodian_pubkey.and_then(|_| signer_info.index_of(custodian_pubkey)),
|
||||
},
|
||||
signers: signer_info.signers,
|
||||
})
|
||||
@@ -704,7 +716,7 @@ pub fn parse_stake_withdraw_stake(
|
||||
pub fn parse_stake_set_lockup(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -749,7 +761,7 @@ pub fn parse_stake_set_lockup(
|
||||
|
||||
pub fn parse_show_stake_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -790,7 +802,7 @@ pub fn process_create_stake_account(
|
||||
) -> ProcessResult {
|
||||
let stake_account = config.signers[stake_account];
|
||||
let stake_account_address = if let Some(seed) = seed {
|
||||
create_address_with_seed(&stake_account.pubkey(), &seed, &solana_stake_program::id())?
|
||||
Pubkey::create_with_seed(&stake_account.pubkey(), &seed, &solana_stake_program::id())?
|
||||
} else {
|
||||
stake_account.pubkey()
|
||||
};
|
||||
@@ -869,7 +881,7 @@ pub fn process_create_stake_account(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx)
|
||||
return_signers(&tx, &config)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@@ -882,8 +894,8 @@ pub fn process_create_stake_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -934,7 +946,7 @@ pub fn process_stake_authorize(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx)
|
||||
return_signers(&tx, &config)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@@ -947,8 +959,8 @@ pub fn process_stake_authorize(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<StakeError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -988,7 +1000,7 @@ pub fn process_deactivate_stake_account(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx)
|
||||
return_signers(&tx, &config)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@@ -1001,8 +1013,8 @@ pub fn process_deactivate_stake_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<StakeError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1014,6 +1026,7 @@ pub fn process_withdraw_stake(
|
||||
destination_account_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
withdraw_authority: SignerIndex,
|
||||
custodian: Option<SignerIndex>,
|
||||
sign_only: bool,
|
||||
blockhash_query: &BlockhashQuery,
|
||||
nonce_account: Option<&Pubkey>,
|
||||
@@ -1023,12 +1036,14 @@ pub fn process_withdraw_stake(
|
||||
let (recent_blockhash, fee_calculator) =
|
||||
blockhash_query.get_blockhash_and_fee_calculator(rpc_client)?;
|
||||
let withdraw_authority = config.signers[withdraw_authority];
|
||||
let custodian = custodian.map(|index| config.signers[index]);
|
||||
|
||||
let ixs = vec![stake_instruction::withdraw(
|
||||
stake_account_pubkey,
|
||||
&withdraw_authority.pubkey(),
|
||||
destination_account_pubkey,
|
||||
lamports,
|
||||
custodian.map(|signer| signer.pubkey()).as_ref(),
|
||||
)];
|
||||
|
||||
let fee_payer = config.signers[fee_payer];
|
||||
@@ -1048,7 +1063,7 @@ pub fn process_withdraw_stake(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx)
|
||||
return_signers(&tx, &config)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@@ -1061,8 +1076,8 @@ pub fn process_withdraw_stake(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1108,7 +1123,7 @@ pub fn process_split_stake(
|
||||
let stake_authority = config.signers[stake_authority];
|
||||
|
||||
let split_stake_account_address = if let Some(seed) = split_stake_account_seed {
|
||||
create_address_with_seed(
|
||||
Pubkey::create_with_seed(
|
||||
&split_stake_account.pubkey(),
|
||||
&seed,
|
||||
&solana_stake_program::id(),
|
||||
@@ -1182,7 +1197,7 @@ pub fn process_split_stake(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx)
|
||||
return_signers(&tx, &config)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@@ -1195,8 +1210,8 @@ pub fn process_split_stake(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<StakeError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1239,7 +1254,7 @@ pub fn process_stake_set_lockup(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx)
|
||||
return_signers(&tx, &config)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@@ -1252,84 +1267,66 @@ pub fn process_stake_set_lockup(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<StakeError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_stake_state(stake_lamports: u64, stake_state: &StakeState, use_lamports_unit: bool) {
|
||||
fn show_authorized(authorized: &Authorized) {
|
||||
println!("Stake Authority: {}", authorized.staker);
|
||||
println!("Withdraw Authority: {}", authorized.withdrawer);
|
||||
}
|
||||
fn show_lockup(lockup: &Lockup) {
|
||||
println!(
|
||||
"Lockup Timestamp: {} (UnixTimestamp: {})",
|
||||
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(lockup.unix_timestamp, 0), Utc)
|
||||
.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
lockup.unix_timestamp
|
||||
);
|
||||
println!("Lockup Epoch: {}", lockup.epoch);
|
||||
println!("Lockup Custodian: {}", lockup.custodian);
|
||||
}
|
||||
pub fn build_stake_state(
|
||||
stake_lamports: u64,
|
||||
stake_state: &StakeState,
|
||||
use_lamports_unit: bool,
|
||||
) -> CliStakeState {
|
||||
match stake_state {
|
||||
StakeState::Stake(
|
||||
Meta {
|
||||
authorized, lockup, ..
|
||||
},
|
||||
stake,
|
||||
) => {
|
||||
println!(
|
||||
"Total Stake: {}",
|
||||
build_balance_message(stake_lamports, use_lamports_unit, true)
|
||||
);
|
||||
println!("Credits Observed: {}", stake.credits_observed);
|
||||
println!(
|
||||
"Delegated Stake: {}",
|
||||
build_balance_message(stake.delegation.stake, use_lamports_unit, true)
|
||||
);
|
||||
if stake.delegation.voter_pubkey != Pubkey::default() {
|
||||
println!(
|
||||
"Delegated Vote Account Address: {}",
|
||||
stake.delegation.voter_pubkey
|
||||
);
|
||||
}
|
||||
println!(
|
||||
"Stake activates starting from epoch: {}",
|
||||
if stake.delegation.activation_epoch < std::u64::MAX {
|
||||
stake.delegation.activation_epoch
|
||||
} else {
|
||||
0
|
||||
}
|
||||
);
|
||||
if stake.delegation.deactivation_epoch < std::u64::MAX {
|
||||
println!(
|
||||
"Stake deactivates starting from epoch: {}",
|
||||
stake.delegation.deactivation_epoch
|
||||
);
|
||||
}
|
||||
show_authorized(&authorized);
|
||||
show_lockup(&lockup);
|
||||
}
|
||||
StakeState::RewardsPool => println!("Stake account is a rewards pool"),
|
||||
StakeState::Uninitialized => println!("Stake account is uninitialized"),
|
||||
) => CliStakeState {
|
||||
stake_type: CliStakeType::Stake,
|
||||
total_stake: stake_lamports,
|
||||
delegated_stake: Some(stake.delegation.stake),
|
||||
delegated_vote_account_address: if stake.delegation.voter_pubkey != Pubkey::default() {
|
||||
Some(stake.delegation.voter_pubkey.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
activation_epoch: Some(if stake.delegation.activation_epoch < std::u64::MAX {
|
||||
stake.delegation.activation_epoch
|
||||
} else {
|
||||
0
|
||||
}),
|
||||
deactivation_epoch: if stake.delegation.deactivation_epoch < std::u64::MAX {
|
||||
Some(stake.delegation.deactivation_epoch)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
authorized: Some(authorized.into()),
|
||||
lockup: Some(lockup.into()),
|
||||
use_lamports_unit,
|
||||
},
|
||||
StakeState::RewardsPool => CliStakeState {
|
||||
stake_type: CliStakeType::RewardsPool,
|
||||
..CliStakeState::default()
|
||||
},
|
||||
StakeState::Uninitialized => CliStakeState::default(),
|
||||
StakeState::Initialized(Meta {
|
||||
authorized, lockup, ..
|
||||
}) => {
|
||||
println!(
|
||||
"Total Stake: {}",
|
||||
build_balance_message(stake_lamports, use_lamports_unit, true)
|
||||
);
|
||||
println!("Stake account is undelegated");
|
||||
show_authorized(&authorized);
|
||||
show_lockup(&lockup);
|
||||
}
|
||||
}) => CliStakeState {
|
||||
stake_type: CliStakeType::Initialized,
|
||||
total_stake: stake_lamports,
|
||||
authorized: Some(authorized.into()),
|
||||
lockup: Some(lockup.into()),
|
||||
use_lamports_unit,
|
||||
..CliStakeState::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_show_stake_account(
|
||||
rpc_client: &RpcClient,
|
||||
_config: &CliConfig,
|
||||
config: &CliConfig,
|
||||
stake_account_pubkey: &Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
@@ -1343,8 +1340,8 @@ pub fn process_show_stake_account(
|
||||
}
|
||||
match stake_account.state() {
|
||||
Ok(stake_state) => {
|
||||
print_stake_state(stake_account.lamports, &stake_state, use_lamports_unit);
|
||||
Ok("".to_string())
|
||||
let state = build_stake_state(stake_account.lamports, &stake_state, use_lamports_unit);
|
||||
Ok(config.output_format.formatted_string(&state))
|
||||
}
|
||||
Err(err) => Err(CliError::RpcRequestError(format!(
|
||||
"Account data could not be deserialized to stake state: {}",
|
||||
@@ -1356,7 +1353,7 @@ pub fn process_show_stake_account(
|
||||
|
||||
pub fn process_show_stake_history(
|
||||
rpc_client: &RpcClient,
|
||||
_config: &CliConfig,
|
||||
config: &CliConfig,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
let stake_history_account = rpc_client.get_account(&stake_history::id())?;
|
||||
@@ -1364,27 +1361,15 @@ pub fn process_show_stake_history(
|
||||
CliError::RpcRequestError("Failed to deserialize stake history".to_string())
|
||||
})?;
|
||||
|
||||
println!();
|
||||
println!(
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<5} {:>20} {:>20} {:>20}",
|
||||
"Epoch", "Effective Stake", "Activating Stake", "Deactivating Stake",
|
||||
))
|
||||
.bold()
|
||||
);
|
||||
|
||||
for (epoch, entry) in stake_history.deref() {
|
||||
println!(
|
||||
" {:>5} {:>20} {:>20} {:>20} {}",
|
||||
epoch,
|
||||
build_balance_message(entry.effective, use_lamports_unit, false),
|
||||
build_balance_message(entry.activating, use_lamports_unit, false),
|
||||
build_balance_message(entry.deactivating, use_lamports_unit, false),
|
||||
if use_lamports_unit { "lamports" } else { "SOL" }
|
||||
);
|
||||
let mut entries: Vec<CliStakeHistoryEntry> = vec![];
|
||||
for entry in stake_history.deref() {
|
||||
entries.push(entry.into());
|
||||
}
|
||||
Ok("".to_string())
|
||||
let stake_history_output = CliStakeHistory {
|
||||
entries,
|
||||
use_lamports_unit,
|
||||
};
|
||||
Ok(config.output_format.formatted_string(&stake_history_output))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -1477,7 +1462,7 @@ pub fn process_delegate_stake(
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx)
|
||||
return_signers(&tx, &config)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
@@ -1490,8 +1475,8 @@ pub fn process_delegate_stake(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<StakeError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1525,6 +1510,9 @@ mod tests {
|
||||
let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let stake_authority_keypair = Keypair::new();
|
||||
write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
let (custodian_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let custodian_keypair = Keypair::new();
|
||||
write_keypair(&custodian_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
|
||||
// stake-authorize subcommand
|
||||
let stake_account_string = stake_account_pubkey.to_string();
|
||||
@@ -1542,7 +1530,7 @@ mod tests {
|
||||
&new_withdraw_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -1576,7 +1564,7 @@ mod tests {
|
||||
&withdraw_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -1614,7 +1602,7 @@ mod tests {
|
||||
&withdraw_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -1644,7 +1632,7 @@ mod tests {
|
||||
&new_stake_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -1668,7 +1656,7 @@ mod tests {
|
||||
&stake_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -1698,7 +1686,7 @@ mod tests {
|
||||
&withdraw_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -1725,7 +1713,7 @@ mod tests {
|
||||
&new_withdraw_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -1753,7 +1741,7 @@ mod tests {
|
||||
&withdraw_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_stake_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -1791,7 +1779,7 @@ mod tests {
|
||||
"--sign-only",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -1824,7 +1812,7 @@ mod tests {
|
||||
&pubkey.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -1870,7 +1858,7 @@ mod tests {
|
||||
&pubkey2.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -1902,7 +1890,7 @@ mod tests {
|
||||
&blockhash_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -1939,7 +1927,7 @@ mod tests {
|
||||
&nonce_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -1975,7 +1963,7 @@ mod tests {
|
||||
&fee_payer_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -2009,7 +1997,7 @@ mod tests {
|
||||
&signer,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_authorize, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
@@ -2050,7 +2038,7 @@ mod tests {
|
||||
"43",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_stake_account, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_create_stake_account, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateStakeAccount {
|
||||
stake_account: 1,
|
||||
@@ -2091,7 +2079,12 @@ mod tests {
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
parse_command(&test_create_stake_account2, &default_keypair_file, None).unwrap(),
|
||||
parse_command(
|
||||
&test_create_stake_account2,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateStakeAccount {
|
||||
stake_account: 1,
|
||||
@@ -2144,7 +2137,12 @@ mod tests {
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
parse_command(&test_create_stake_account2, &default_keypair_file, None).unwrap(),
|
||||
parse_command(
|
||||
&test_create_stake_account2,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateStakeAccount {
|
||||
stake_account: 1,
|
||||
@@ -2180,7 +2178,7 @@ mod tests {
|
||||
&vote_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2209,7 +2207,7 @@ mod tests {
|
||||
&stake_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2240,7 +2238,7 @@ mod tests {
|
||||
&vote_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2269,7 +2267,7 @@ mod tests {
|
||||
&blockhash_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2299,7 +2297,7 @@ mod tests {
|
||||
"--sign-only",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2333,7 +2331,7 @@ mod tests {
|
||||
&key1.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2379,7 +2377,7 @@ mod tests {
|
||||
&key2.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2416,7 +2414,7 @@ mod tests {
|
||||
&fee_payer_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_delegate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DelegateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2446,13 +2444,14 @@ mod tests {
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::WithdrawStake {
|
||||
stake_account_pubkey,
|
||||
destination_account_pubkey: stake_account_pubkey,
|
||||
lamports: 42_000_000_000,
|
||||
withdraw_authority: 0,
|
||||
custodian: None,
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
@@ -2475,13 +2474,14 @@ mod tests {
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::WithdrawStake {
|
||||
stake_account_pubkey,
|
||||
destination_account_pubkey: stake_account_pubkey,
|
||||
lamports: 42_000_000_000,
|
||||
withdraw_authority: 1,
|
||||
custodian: None,
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
@@ -2497,6 +2497,39 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
// Test WithdrawStake Subcommand w/ custodian
|
||||
let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"withdraw-stake",
|
||||
&stake_account_string,
|
||||
&stake_account_string,
|
||||
"42",
|
||||
"--custodian",
|
||||
&custodian_keypair_file,
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::WithdrawStake {
|
||||
stake_account_pubkey,
|
||||
destination_account_pubkey: stake_account_pubkey,
|
||||
lamports: 42_000_000_000,
|
||||
withdraw_authority: 0,
|
||||
custodian: Some(1),
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
read_keypair_file(&custodian_keypair_file).unwrap().into()
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
// Test WithdrawStake Subcommand w/ authority and offline nonce
|
||||
let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
@@ -2519,13 +2552,14 @@ mod tests {
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_withdraw_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::WithdrawStake {
|
||||
stake_account_pubkey,
|
||||
destination_account_pubkey: stake_account_pubkey,
|
||||
lamports: 42_000_000_000,
|
||||
withdraw_authority: 0,
|
||||
custodian: None,
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account),
|
||||
@@ -2551,7 +2585,7 @@ mod tests {
|
||||
&stake_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2575,7 +2609,7 @@ mod tests {
|
||||
&stake_authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2606,7 +2640,7 @@ mod tests {
|
||||
&blockhash_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2633,7 +2667,7 @@ mod tests {
|
||||
"--sign-only",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2664,7 +2698,7 @@ mod tests {
|
||||
&key1.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2707,7 +2741,7 @@ mod tests {
|
||||
&key2.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2738,7 +2772,7 @@ mod tests {
|
||||
&fee_payer_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_deactivate_stake, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::DeactivateStake {
|
||||
stake_account_pubkey,
|
||||
@@ -2772,7 +2806,7 @@ mod tests {
|
||||
"50",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_split_stake_account, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_split_stake_account, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::SplitStake {
|
||||
stake_account_pubkey: stake_account_keypair.pubkey(),
|
||||
@@ -2833,7 +2867,7 @@ mod tests {
|
||||
&stake_signer,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_split_stake_account, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_split_stake_account, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::SplitStake {
|
||||
stake_account_pubkey: stake_account_keypair.pubkey(),
|
||||
|
||||
@@ -102,7 +102,7 @@ impl StorageSubCommands for App<'_, '_> {
|
||||
pub fn parse_storage_create_archiver_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let account_owner =
|
||||
pubkey_of_signer(matches, "storage_account_owner", wallet_manager)?.unwrap();
|
||||
@@ -130,7 +130,7 @@ pub fn parse_storage_create_archiver_account(
|
||||
pub fn parse_storage_create_validator_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let account_owner =
|
||||
pubkey_of_signer(matches, "storage_account_owner", wallet_manager)?.unwrap();
|
||||
@@ -158,7 +158,7 @@ pub fn parse_storage_create_validator_account(
|
||||
pub fn parse_storage_claim_reward(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let node_account_pubkey =
|
||||
pubkey_of_signer(matches, "node_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -180,7 +180,7 @@ pub fn parse_storage_claim_reward(
|
||||
|
||||
pub fn parse_storage_get_account_command(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let storage_account_pubkey =
|
||||
pubkey_of_signer(matches, "storage_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -242,8 +242,8 @@ pub fn process_create_storage_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
|
||||
pub fn process_claim_storage_reward(
|
||||
@@ -266,8 +266,8 @@ pub fn process_claim_storage_reward(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let signature_str = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &signers)?;
|
||||
Ok(signature_str)
|
||||
let signature = rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
|
||||
Ok(signature.to_string())
|
||||
}
|
||||
|
||||
pub fn process_show_storage_account(
|
||||
@@ -330,7 +330,7 @@ mod tests {
|
||||
parse_command(
|
||||
&test_create_archiver_storage_account,
|
||||
&default_keypair_file,
|
||||
None
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
@@ -362,7 +362,7 @@ mod tests {
|
||||
parse_command(
|
||||
&test_create_validator_storage_account,
|
||||
&default_keypair_file,
|
||||
None
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
@@ -385,7 +385,7 @@ mod tests {
|
||||
&storage_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_claim_storage_reward, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_claim_storage_reward, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::ClaimStorageReward {
|
||||
node_account_pubkey: pubkey,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
cli::{check_account_for_fee, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
||||
display::println_name_value,
|
||||
cli_output::{CliValidatorInfo, CliValidatorInfoVec},
|
||||
};
|
||||
use bincode::deserialize;
|
||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
@@ -25,7 +25,6 @@ use solana_sdk::{
|
||||
transaction::Transaction,
|
||||
};
|
||||
use std::{error, sync::Arc};
|
||||
use titlecase::titlecase;
|
||||
|
||||
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
|
||||
pub const MAX_LONG_FIELD_LENGTH: usize = 300;
|
||||
@@ -229,7 +228,7 @@ impl ValidatorInfoSubCommands for App<'_, '_> {
|
||||
pub fn parse_validator_info_command(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let info_pubkey = pubkey_of(matches, "info_pubkey");
|
||||
// Prepare validator info
|
||||
@@ -368,14 +367,18 @@ pub fn process_set_validator_info(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let signature_str = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &signers)?;
|
||||
let signature_str = rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
|
||||
|
||||
println!("Success! Validator info published at: {:?}", info_pubkey);
|
||||
println!("{}", signature_str);
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
pub fn process_get_validator_info(rpc_client: &RpcClient, pubkey: Option<Pubkey>) -> ProcessResult {
|
||||
pub fn process_get_validator_info(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
pubkey: Option<Pubkey>,
|
||||
) -> ProcessResult {
|
||||
let validator_info: Vec<(Pubkey, Account)> = if let Some(validator_info_pubkey) = pubkey {
|
||||
vec![(
|
||||
validator_info_pubkey,
|
||||
@@ -394,24 +397,22 @@ pub fn process_get_validator_info(rpc_client: &RpcClient, pubkey: Option<Pubkey>
|
||||
.collect()
|
||||
};
|
||||
|
||||
let mut validator_info_list: Vec<CliValidatorInfo> = vec![];
|
||||
if validator_info.is_empty() {
|
||||
println!("No validator info accounts found");
|
||||
}
|
||||
for (validator_info_pubkey, validator_info_account) in validator_info.iter() {
|
||||
let (validator_pubkey, validator_info) =
|
||||
parse_validator_info(&validator_info_pubkey, &validator_info_account)?;
|
||||
println!();
|
||||
println_name_value("Validator Identity Pubkey:", &validator_pubkey.to_string());
|
||||
println_name_value(" Info Pubkey:", &validator_info_pubkey.to_string());
|
||||
for (key, value) in validator_info.iter() {
|
||||
println_name_value(
|
||||
&format!(" {}:", titlecase(key)),
|
||||
&value.as_str().unwrap_or("?"),
|
||||
);
|
||||
}
|
||||
validator_info_list.push(CliValidatorInfo {
|
||||
identity_pubkey: validator_pubkey.to_string(),
|
||||
info_pubkey: validator_info_pubkey.to_string(),
|
||||
info: validator_info,
|
||||
});
|
||||
}
|
||||
|
||||
Ok("".to_string())
|
||||
Ok(config
|
||||
.output_format
|
||||
.formatted_string(&CliValidatorInfoVec::new(validator_info_list)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
222
cli/src/vote.rs
222
cli/src/vote.rs
@@ -1,19 +1,22 @@
|
||||
use crate::cli::{
|
||||
build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, CliSignerInfo,
|
||||
ProcessResult, SignerIndex,
|
||||
use crate::{
|
||||
cli::{
|
||||
check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
|
||||
ProcessResult, SignerIndex,
|
||||
},
|
||||
cli_output::{CliEpochVotingHistory, CliLockout, CliVoteAccount},
|
||||
};
|
||||
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
||||
use solana_clap_utils::{
|
||||
commitment::{commitment_arg, COMMITMENT_ARG},
|
||||
input_parsers::*,
|
||||
input_validators::*,
|
||||
};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
commitment_config::CommitmentConfig,
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
system_instruction::{create_address_with_seed, SystemError},
|
||||
transaction::Transaction,
|
||||
account::Account, commitment_config::CommitmentConfig, message::Message, pubkey::Pubkey,
|
||||
system_instruction::SystemError, transaction::Transaction,
|
||||
};
|
||||
use solana_vote_program::{
|
||||
vote_instruction::{self, withdraw, VoteError},
|
||||
@@ -93,8 +96,16 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.help("Vote account in which to set the authorized voter"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_authorized_pubkey")
|
||||
Arg::with_name("authorized")
|
||||
.index(2)
|
||||
.value_name("AUTHORIZED_KEYPAIR")
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("Current authorized vote signer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_authorized_pubkey")
|
||||
.index(3)
|
||||
.value_name("AUTHORIZED_PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
@@ -115,8 +126,16 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.help("Vote account in which to set the authorized withdrawer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_authorized_pubkey")
|
||||
Arg::with_name("authorized")
|
||||
.index(2)
|
||||
.value_name("AUTHORIZED_KEYPAIR")
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("Current authorized withdrawer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_authorized_pubkey")
|
||||
.index(3)
|
||||
.value_name("AUTHORIZED_PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
@@ -159,14 +178,6 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
SubCommand::with_name("vote-account")
|
||||
.about("Show the contents of a vote account")
|
||||
.alias("show-vote-account")
|
||||
.arg(
|
||||
Arg::with_name("confirmed")
|
||||
.long("confirmed")
|
||||
.takes_value(false)
|
||||
.help(
|
||||
"Return information at maximum-lockout commitment level",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
@@ -181,7 +192,8 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.long("lamports")
|
||||
.takes_value(false)
|
||||
.help("Display balance in lamports instead of SOL"),
|
||||
),
|
||||
)
|
||||
.arg(commitment_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("withdraw-from-vote-account")
|
||||
@@ -228,7 +240,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
pub fn parse_create_vote_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let (vote_account, _) = signer_of(matches, "vote_account", wallet_manager)?;
|
||||
let seed = matches.value_of("seed").map(|s| s.to_string());
|
||||
@@ -261,17 +273,18 @@ pub fn parse_create_vote_account(
|
||||
pub fn parse_vote_authorize(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
vote_authorize: VoteAuthorize,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
let new_authorized_pubkey =
|
||||
pubkey_of_signer(matches, "new_authorized_pubkey", wallet_manager)?.unwrap();
|
||||
let (authorized, _) = signer_of(matches, "authorized", wallet_manager)?;
|
||||
|
||||
let authorized_voter_provided = None;
|
||||
let CliSignerInfo { signers } = generate_unique_signers(
|
||||
vec![authorized_voter_provided],
|
||||
let payer_provided = None;
|
||||
let signer_info = generate_unique_signers(
|
||||
vec![payer_provided, authorized],
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
@@ -283,14 +296,14 @@ pub fn parse_vote_authorize(
|
||||
new_authorized_pubkey,
|
||||
vote_authorize,
|
||||
},
|
||||
signers,
|
||||
signers: signer_info.signers,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_vote_update_validator(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -317,16 +330,12 @@ pub fn parse_vote_update_validator(
|
||||
|
||||
pub fn parse_vote_get_account_command(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
let use_lamports_unit = matches.is_present("lamports");
|
||||
let commitment_config = if matches.is_present("confirmed") {
|
||||
CommitmentConfig::default()
|
||||
} else {
|
||||
CommitmentConfig::recent()
|
||||
};
|
||||
let commitment_config = commitment_of(matches, COMMITMENT_ARG.long).unwrap();
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::ShowVoteAccount {
|
||||
pubkey: vote_account_pubkey,
|
||||
@@ -340,7 +349,7 @@ pub fn parse_vote_get_account_command(
|
||||
pub fn parse_withdraw_from_vote_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -381,7 +390,7 @@ pub fn process_create_vote_account(
|
||||
let vote_account = config.signers[1];
|
||||
let vote_account_pubkey = vote_account.pubkey();
|
||||
let vote_account_address = if let Some(seed) = seed {
|
||||
create_address_with_seed(&vote_account_pubkey, &seed, &solana_vote_program::id())?
|
||||
Pubkey::create_with_seed(&vote_account_pubkey, &seed, &solana_vote_program::id())?
|
||||
} else {
|
||||
vote_account_pubkey
|
||||
};
|
||||
@@ -448,8 +457,8 @@ pub fn process_create_vote_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
|
||||
pub fn process_vote_authorize(
|
||||
@@ -459,16 +468,24 @@ pub fn process_vote_authorize(
|
||||
new_authorized_pubkey: &Pubkey,
|
||||
vote_authorize: VoteAuthorize,
|
||||
) -> ProcessResult {
|
||||
// If the `authorized_account` is also the fee payer, `config.signers` will only have one
|
||||
// keypair in it
|
||||
let authorized = if config.signers.len() == 2 {
|
||||
config.signers[1]
|
||||
} else {
|
||||
config.signers[0]
|
||||
};
|
||||
|
||||
check_unique_pubkeys(
|
||||
(vote_account_pubkey, "vote_account_pubkey".to_string()),
|
||||
(&authorized.pubkey(), "authorized_account".to_string()),
|
||||
(new_authorized_pubkey, "new_authorized_pubkey".to_string()),
|
||||
)?;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let ixs = vec![vote_instruction::authorize(
|
||||
vote_account_pubkey, // vote account to update
|
||||
&config.signers[0].pubkey(), // current authorized voter
|
||||
new_authorized_pubkey, // new vote signer/withdrawer
|
||||
vote_authorize, // vote or withdraw
|
||||
vote_account_pubkey, // vote account to update
|
||||
&authorized.pubkey(), // current authorized
|
||||
new_authorized_pubkey, // new vote signer/withdrawer
|
||||
vote_authorize, // vote or withdraw
|
||||
)];
|
||||
|
||||
let message = Message::new_with_payer(&ixs, Some(&config.signers[0].pubkey()));
|
||||
@@ -480,9 +497,8 @@ pub fn process_vote_authorize(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result =
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0]]);
|
||||
log_instruction_custom_error::<VoteError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
}
|
||||
|
||||
pub fn process_vote_update_validator(
|
||||
@@ -499,7 +515,7 @@ pub fn process_vote_update_validator(
|
||||
(&new_identity_pubkey, "new_identity_account".to_string()),
|
||||
)?;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let ixs = vec![vote_instruction::update_node(
|
||||
let ixs = vec![vote_instruction::update_validator_identity(
|
||||
vote_account_pubkey,
|
||||
&authorized_withdrawer.pubkey(),
|
||||
&new_identity_pubkey,
|
||||
@@ -514,8 +530,8 @@ pub fn process_vote_update_validator(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<VoteError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
}
|
||||
|
||||
fn get_vote_account(
|
||||
@@ -548,7 +564,7 @@ fn get_vote_account(
|
||||
|
||||
pub fn process_show_vote_account(
|
||||
rpc_client: &RpcClient,
|
||||
_config: &CliConfig,
|
||||
config: &CliConfig,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
commitment_config: CommitmentConfig,
|
||||
@@ -558,46 +574,38 @@ pub fn process_show_vote_account(
|
||||
|
||||
let epoch_schedule = rpc_client.get_epoch_schedule()?;
|
||||
|
||||
println!(
|
||||
"Account Balance: {}",
|
||||
build_balance_message(vote_account.lamports, use_lamports_unit, true)
|
||||
);
|
||||
println!("Validator Identity: {}", vote_state.node_pubkey);
|
||||
println!("Authorized Voter: {:?}", vote_state.authorized_voters());
|
||||
println!(
|
||||
"Authorized Withdrawer: {}",
|
||||
vote_state.authorized_withdrawer
|
||||
);
|
||||
println!("Credits: {}", vote_state.credits());
|
||||
println!("Commission: {}%", vote_state.commission);
|
||||
println!(
|
||||
"Root Slot: {}",
|
||||
match vote_state.root_slot {
|
||||
Some(slot) => slot.to_string(),
|
||||
None => "~".to_string(),
|
||||
}
|
||||
);
|
||||
println!("Recent Timestamp: {:?}", vote_state.last_timestamp);
|
||||
let mut votes: Vec<CliLockout> = vec![];
|
||||
let mut epoch_voting_history: Vec<CliEpochVotingHistory> = vec![];
|
||||
if !vote_state.votes.is_empty() {
|
||||
println!("recent votes:");
|
||||
for vote in &vote_state.votes {
|
||||
println!(
|
||||
"- slot: {}\n confirmation count: {}",
|
||||
vote.slot, vote.confirmation_count
|
||||
);
|
||||
votes.push(vote.into());
|
||||
}
|
||||
|
||||
println!("Epoch Voting History:");
|
||||
for (epoch, credits, prev_credits) in vote_state.epoch_credits() {
|
||||
let credits_earned = credits - prev_credits;
|
||||
let slots_in_epoch = epoch_schedule.get_slots_in_epoch(*epoch);
|
||||
println!(
|
||||
"- epoch: {}\n slots in epoch: {}\n credits earned: {}",
|
||||
epoch, slots_in_epoch, credits_earned,
|
||||
);
|
||||
epoch_voting_history.push(CliEpochVotingHistory {
|
||||
epoch: *epoch,
|
||||
slots_in_epoch,
|
||||
credits_earned,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok("".to_string())
|
||||
|
||||
let vote_account_data = CliVoteAccount {
|
||||
account_balance: vote_account.lamports,
|
||||
validator_identity: vote_state.node_pubkey.to_string(),
|
||||
authorized_voters: vote_state.authorized_voters().into(),
|
||||
authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
|
||||
credits: vote_state.credits(),
|
||||
commission: vote_state.commission,
|
||||
root_slot: vote_state.root_slot,
|
||||
recent_timestamp: vote_state.last_timestamp.clone(),
|
||||
votes,
|
||||
epoch_voting_history,
|
||||
use_lamports_unit,
|
||||
};
|
||||
|
||||
Ok(config.output_format.formatted_string(&vote_account_data))
|
||||
}
|
||||
|
||||
pub fn process_withdraw_from_vote_account(
|
||||
@@ -627,9 +635,8 @@ pub fn process_withdraw_from_vote_account(
|
||||
&fee_calculator,
|
||||
&transaction.message,
|
||||
)?;
|
||||
let result =
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&mut transaction, &config.signers);
|
||||
log_instruction_custom_error::<VoteError>(result)
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&transaction);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -662,10 +669,11 @@ mod tests {
|
||||
"test",
|
||||
"vote-authorize-voter",
|
||||
&pubkey_string,
|
||||
&default_keypair_file,
|
||||
&pubkey2_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize_voter, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_authorize_voter, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::VoteAuthorize {
|
||||
vote_account_pubkey: pubkey,
|
||||
@@ -676,6 +684,32 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
let authorized_keypair = Keypair::new();
|
||||
let (authorized_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
write_keypair(&authorized_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
|
||||
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"vote-authorize-voter",
|
||||
&pubkey_string,
|
||||
&authorized_keypair_file,
|
||||
&pubkey2_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize_voter, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::VoteAuthorize {
|
||||
vote_account_pubkey: pubkey,
|
||||
new_authorized_pubkey: pubkey2,
|
||||
vote_authorize: VoteAuthorize::Voter
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
read_keypair_file(&authorized_keypair_file).unwrap().into(),
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
let (keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let keypair = Keypair::new();
|
||||
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
|
||||
@@ -692,7 +726,7 @@ mod tests {
|
||||
"10",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_vote_account, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_create_vote_account, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
seed: None,
|
||||
@@ -720,7 +754,7 @@ mod tests {
|
||||
&identity_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_vote_account2, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_create_vote_account2, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
seed: None,
|
||||
@@ -752,7 +786,7 @@ mod tests {
|
||||
&authed.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_vote_account3, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_create_vote_account3, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
seed: None,
|
||||
@@ -782,7 +816,7 @@ mod tests {
|
||||
&authed.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_vote_account4, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_create_vote_account4, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
seed: None,
|
||||
@@ -807,7 +841,7 @@ mod tests {
|
||||
&keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_update_validator, &default_keypair_file, None).unwrap(),
|
||||
parse_command(&test_update_validator, &default_keypair_file, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::VoteUpdateValidator {
|
||||
vote_account_pubkey: pubkey,
|
||||
@@ -833,7 +867,7 @@ mod tests {
|
||||
parse_command(
|
||||
&test_withdraw_from_vote_account,
|
||||
&default_keypair_file,
|
||||
None
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
@@ -864,7 +898,7 @@ mod tests {
|
||||
parse_command(
|
||||
&test_withdraw_from_vote_account,
|
||||
&default_keypair_file,
|
||||
None
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use solana_cli::{
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
cli_output::OutputFormat,
|
||||
nonce,
|
||||
offline::{
|
||||
blockhash_query::{self, BlockhashQuery},
|
||||
@@ -13,7 +14,6 @@ use solana_sdk::{
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
system_instruction::create_address_with_seed,
|
||||
system_program,
|
||||
};
|
||||
use std::{fs::remove_dir_all, sync::mpsc::channel, thread::sleep, time::Duration};
|
||||
@@ -120,6 +120,7 @@ fn full_battery_tests(
|
||||
&faucet_addr,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
2000,
|
||||
&config_payer,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(2000, &rpc_client, &config_payer.signers[0].pubkey());
|
||||
@@ -130,7 +131,7 @@ fn full_battery_tests(
|
||||
config_nonce.signers = vec![&nonce_keypair];
|
||||
|
||||
let nonce_account = if let Some(seed) = seed.as_ref() {
|
||||
create_address_with_seed(
|
||||
Pubkey::create_with_seed(
|
||||
&config_nonce.signers[0].pubkey(),
|
||||
seed,
|
||||
&system_program::id(),
|
||||
@@ -266,6 +267,7 @@ fn test_create_account_with_seed() {
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: 1,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
..TestValidatorOptions::default()
|
||||
});
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
@@ -275,6 +277,7 @@ fn test_create_account_with_seed() {
|
||||
let offline_nonce_authority_signer = keypair_from_seed(&[1u8; 32]).unwrap();
|
||||
let online_nonce_creator_signer = keypair_from_seed(&[2u8; 32]).unwrap();
|
||||
let to_address = Pubkey::new(&[3u8; 32]);
|
||||
let config = CliConfig::default();
|
||||
|
||||
// Setup accounts
|
||||
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
||||
@@ -283,6 +286,7 @@ fn test_create_account_with_seed() {
|
||||
&faucet_addr,
|
||||
&offline_nonce_authority_signer.pubkey(),
|
||||
42,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(
|
||||
@@ -290,6 +294,7 @@ fn test_create_account_with_seed() {
|
||||
&faucet_addr,
|
||||
&online_nonce_creator_signer.pubkey(),
|
||||
4242,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(42, &rpc_client, &offline_nonce_authority_signer.pubkey());
|
||||
@@ -301,7 +306,7 @@ fn test_create_account_with_seed() {
|
||||
let authority_pubkey = offline_nonce_authority_signer.pubkey();
|
||||
let seed = authority_pubkey.to_string()[0..32].to_string();
|
||||
let nonce_address =
|
||||
create_address_with_seed(&creator_pubkey, &seed, &system_program::id()).unwrap();
|
||||
Pubkey::create_with_seed(&creator_pubkey, &seed, &system_program::id()).unwrap();
|
||||
check_balance(0, &rpc_client, &nonce_address);
|
||||
|
||||
let mut creator_config = CliConfig::default();
|
||||
@@ -338,11 +343,13 @@ fn test_create_account_with_seed() {
|
||||
to: to_address,
|
||||
from: 0,
|
||||
sign_only: true,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_address),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
authority_config.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&authority_config).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
let authority_presigner = sign_only.presigner_of(&authority_pubkey).unwrap();
|
||||
@@ -358,6 +365,7 @@ fn test_create_account_with_seed() {
|
||||
to: to_address,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_address),
|
||||
sign_only.blockhash,
|
||||
|
||||
@@ -2,6 +2,7 @@ use chrono::prelude::*;
|
||||
use serde_json::Value;
|
||||
use solana_cli::{
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig, PayCommand},
|
||||
cli_output::OutputFormat,
|
||||
nonce,
|
||||
offline::{
|
||||
blockhash_query::{self, BlockhashQuery},
|
||||
@@ -69,6 +70,7 @@ fn test_cli_timestamp_tx() {
|
||||
&faucet_addr,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
50,
|
||||
&config_witness,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(50, &rpc_client, &config_payer.signers[0].pubkey());
|
||||
@@ -78,6 +80,7 @@ fn test_cli_timestamp_tx() {
|
||||
&faucet_addr,
|
||||
&config_witness.signers[0].pubkey(),
|
||||
1,
|
||||
&config_witness,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -154,6 +157,7 @@ fn test_cli_witness_tx() {
|
||||
&faucet_addr,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
50,
|
||||
&config_witness,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(
|
||||
@@ -161,6 +165,7 @@ fn test_cli_witness_tx() {
|
||||
&faucet_addr,
|
||||
&config_witness.signers[0].pubkey(),
|
||||
1,
|
||||
&config_witness,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -234,6 +239,7 @@ fn test_cli_cancel_tx() {
|
||||
&faucet_addr,
|
||||
&config_payer.signers[0].pubkey(),
|
||||
50,
|
||||
&config_witness,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -307,6 +313,7 @@ fn test_offline_pay_tx() {
|
||||
&faucet_addr,
|
||||
&config_offline.signers[0].pubkey(),
|
||||
50,
|
||||
&config_offline,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -315,6 +322,7 @@ fn test_offline_pay_tx() {
|
||||
&faucet_addr,
|
||||
&config_online.signers[0].pubkey(),
|
||||
50,
|
||||
&config_offline,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(50, &rpc_client, &config_offline.signers[0].pubkey());
|
||||
@@ -328,6 +336,7 @@ fn test_offline_pay_tx() {
|
||||
sign_only: true,
|
||||
..PayCommand::default()
|
||||
});
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
|
||||
check_balance(50, &rpc_client, &config_offline.signers[0].pubkey());
|
||||
@@ -388,6 +397,7 @@ fn test_nonced_pay_tx() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
50 + minimum_nonce_balance,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use solana_cli::{
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
cli_output::OutputFormat,
|
||||
nonce,
|
||||
offline::{
|
||||
blockhash_query::{self, BlockhashQuery},
|
||||
@@ -14,7 +15,6 @@ use solana_sdk::{
|
||||
nonce::State as NonceState,
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
system_instruction::create_address_with_seed,
|
||||
};
|
||||
use solana_stake_program::{
|
||||
stake_instruction::LockupArgs,
|
||||
@@ -60,6 +60,7 @@ fn test_stake_delegation_force() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -156,11 +157,12 @@ fn test_seed_stake_delegation_and_deactivation() {
|
||||
&faucet_addr,
|
||||
&config_validator.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config_validator,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
|
||||
let stake_address = create_address_with_seed(
|
||||
let stake_address = Pubkey::create_with_seed(
|
||||
&config_validator.signers[0].pubkey(),
|
||||
"hi there",
|
||||
&solana_stake_program::id(),
|
||||
@@ -246,6 +248,7 @@ fn test_stake_delegation_and_deactivation() {
|
||||
&faucet_addr,
|
||||
&config_validator.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config_validator,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
@@ -342,6 +345,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
&faucet_addr,
|
||||
&config_validator.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config_offline,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &config_validator.signers[0].pubkey());
|
||||
@@ -351,6 +355,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
&faucet_addr,
|
||||
&config_offline.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config_validator,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &config_offline.signers[0].pubkey());
|
||||
@@ -386,6 +391,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@@ -471,6 +477,7 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -580,6 +587,7 @@ fn test_stake_authorize() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -597,6 +605,7 @@ fn test_stake_authorize() {
|
||||
&faucet_addr,
|
||||
&config_offline.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -704,6 +713,7 @@ fn test_stake_authorize() {
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sign_reply = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@@ -812,6 +822,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: SIG_FEE,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
..TestValidatorOptions::default()
|
||||
});
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(alice, sender, None);
|
||||
@@ -841,13 +852,16 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &default_pubkey, 100_000).unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &default_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &payer_pubkey, 100_000).unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &payer_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &payer_pubkey);
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create stake account, identity is authority
|
||||
@@ -901,6 +915,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sign_reply = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@@ -939,6 +954,7 @@ fn test_stake_split() {
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: 1,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
..TestValidatorOptions::default()
|
||||
});
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(alice, sender, None);
|
||||
@@ -965,11 +981,13 @@ fn test_stake_split() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
500_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(500_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create stake account, identity is authority
|
||||
@@ -1037,6 +1055,7 @@ fn test_stake_split() {
|
||||
lamports: 2 * minimum_stake_balance,
|
||||
fee_payer: 0,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@@ -1086,6 +1105,7 @@ fn test_stake_set_lockup() {
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: 1,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
..TestValidatorOptions::default()
|
||||
});
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(alice, sender, None);
|
||||
@@ -1112,11 +1132,13 @@ fn test_stake_set_lockup() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
500_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(500_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create stake account, identity is authority
|
||||
@@ -1290,6 +1312,7 @@ fn test_stake_set_lockup() {
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@@ -1362,11 +1385,13 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
200_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(200_000, &rpc_client, &config.signers[0].pubkey());
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000).unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 100_000, &config)
|
||||
.unwrap();
|
||||
check_balance(100_000, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Create nonce account
|
||||
@@ -1408,6 +1433,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
fee_payer: 0,
|
||||
from: 0,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@@ -1449,6 +1475,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
destination_account_pubkey: recipient_pubkey,
|
||||
lamports: 42,
|
||||
withdraw_authority: 0,
|
||||
custodian: None,
|
||||
sign_only: true,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_pubkey),
|
||||
@@ -1464,6 +1491,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
destination_account_pubkey: recipient_pubkey,
|
||||
lamports: 42,
|
||||
withdraw_authority: 0,
|
||||
custodian: None,
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_pubkey),
|
||||
@@ -1523,7 +1551,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let seed_address =
|
||||
create_address_with_seed(&stake_pubkey, seed, &solana_stake_program::id()).unwrap();
|
||||
Pubkey::create_with_seed(&stake_pubkey, seed, &solana_stake_program::id()).unwrap();
|
||||
check_balance(50_000, &rpc_client, &seed_address);
|
||||
|
||||
server.close().unwrap();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use solana_cli::{
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
cli_output::OutputFormat,
|
||||
nonce,
|
||||
offline::{
|
||||
blockhash_query::{self, BlockhashQuery},
|
||||
@@ -40,6 +41,7 @@ fn test_transfer() {
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: 1,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
..TestValidatorOptions::default()
|
||||
});
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
@@ -58,7 +60,8 @@ fn test_transfer() {
|
||||
let sender_pubkey = config.signers[0].pubkey();
|
||||
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 50_000).unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 50_000, &config)
|
||||
.unwrap();
|
||||
check_balance(50_000, &rpc_client, &sender_pubkey);
|
||||
check_balance(0, &rpc_client, &recipient_pubkey);
|
||||
|
||||
@@ -68,6 +71,7 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@@ -85,7 +89,7 @@ fn test_transfer() {
|
||||
process_command(&offline).unwrap_err();
|
||||
|
||||
let offline_pubkey = offline.signers[0].pubkey();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 50).unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_pubkey, 50, &config).unwrap();
|
||||
check_balance(50, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Offline transfer
|
||||
@@ -95,11 +99,13 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: true,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
offline.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@@ -110,6 +116,7 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
@@ -147,6 +154,7 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
nonce_hash,
|
||||
@@ -187,6 +195,7 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: true,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
@@ -202,6 +211,7 @@ fn test_transfer() {
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
sign_only.blockhash,
|
||||
@@ -229,6 +239,7 @@ fn test_transfer_multisession_signing() {
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: 1,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
..TestValidatorOptions::default()
|
||||
});
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
@@ -239,16 +250,24 @@ fn test_transfer_multisession_signing() {
|
||||
let offline_from_signer = keypair_from_seed(&[2u8; 32]).unwrap();
|
||||
let offline_fee_payer_signer = keypair_from_seed(&[3u8; 32]).unwrap();
|
||||
let from_null_signer = NullSigner::new(&offline_from_signer.pubkey());
|
||||
let config = CliConfig::default();
|
||||
|
||||
// Setup accounts
|
||||
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_from_signer.pubkey(), 43)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&offline_from_signer.pubkey(),
|
||||
43,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&offline_fee_payer_signer.pubkey(),
|
||||
3,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(43, &rpc_client, &offline_from_signer.pubkey());
|
||||
@@ -269,11 +288,13 @@ fn test_transfer_multisession_signing() {
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: true,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
fee_payer_config.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&fee_payer_config).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
assert!(!sign_only.has_all_signers());
|
||||
@@ -293,11 +314,13 @@ fn test_transfer_multisession_signing() {
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: true,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
from_config.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&from_config).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
@@ -314,6 +337,7 @@ fn test_transfer_multisession_signing() {
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
|
||||
@@ -48,6 +48,7 @@ fn test_vote_authorize_and_withdraw() {
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
100_000,
|
||||
&config,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-client"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
description = "Solana Client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -14,14 +14,15 @@ bs58 = "0.3.0"
|
||||
indicatif = "0.14.0"
|
||||
jsonrpc-core = "14.0.5"
|
||||
log = "0.4.8"
|
||||
rayon = "1.2.0"
|
||||
reqwest = { version = "0.10.1", default-features = false, features = ["blocking", "rustls-tls"] }
|
||||
serde = "1.0.104"
|
||||
rayon = "1.3.0"
|
||||
reqwest = { version = "0.10.4", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serde = "1.0.105"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.46"
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.9" }
|
||||
serde_json = "1.0.48"
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.1.15" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.15" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.15" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.1.15" }
|
||||
thiserror = "1.0"
|
||||
tungstenite = "0.10.1"
|
||||
url = "2.1.1"
|
||||
@@ -30,4 +31,7 @@ url = "2.1.1"
|
||||
assert_matches = "1.3.0"
|
||||
jsonrpc-core = "14.0.5"
|
||||
jsonrpc-http-server = "14.0.6"
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.1.15" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
@@ -50,29 +50,29 @@ impl Into<TransportError> for ClientErrorKind {
|
||||
#[derive(Error, Debug)]
|
||||
#[error("{kind}")]
|
||||
pub struct ClientError {
|
||||
command: Option<&'static str>,
|
||||
request: Option<rpc_request::RpcRequest>,
|
||||
|
||||
#[source]
|
||||
#[error(transparent)]
|
||||
kind: ClientErrorKind,
|
||||
}
|
||||
|
||||
impl ClientError {
|
||||
pub fn new_with_command(kind: ClientErrorKind, command: &'static str) -> Self {
|
||||
pub fn new_with_request(kind: ClientErrorKind, request: rpc_request::RpcRequest) -> Self {
|
||||
Self {
|
||||
command: Some(command),
|
||||
request: Some(request),
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_with_command(self, command: &'static str) -> Self {
|
||||
pub fn into_with_request(self, request: rpc_request::RpcRequest) -> Self {
|
||||
Self {
|
||||
command: Some(command),
|
||||
request: Some(request),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(&self) -> Option<&'static str> {
|
||||
self.command
|
||||
pub fn request(&self) -> Option<&rpc_request::RpcRequest> {
|
||||
self.request.as_ref()
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> &ClientErrorKind {
|
||||
@@ -83,7 +83,7 @@ impl ClientError {
|
||||
impl From<ClientErrorKind> for ClientError {
|
||||
fn from(kind: ClientErrorKind) -> Self {
|
||||
Self {
|
||||
command: None,
|
||||
request: None,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ impl From<ClientErrorKind> for ClientError {
|
||||
impl From<TransportError> for ClientError {
|
||||
fn from(err: TransportError) -> Self {
|
||||
Self {
|
||||
command: None,
|
||||
request: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ impl Into<TransportError> for ClientError {
|
||||
impl From<std::io::Error> for ClientError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
Self {
|
||||
command: None,
|
||||
request: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,7 @@ impl From<std::io::Error> for ClientError {
|
||||
impl From<reqwest::Error> for ClientError {
|
||||
fn from(err: reqwest::Error) -> Self {
|
||||
Self {
|
||||
command: None,
|
||||
request: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ impl From<reqwest::Error> for ClientError {
|
||||
impl From<rpc_request::RpcError> for ClientError {
|
||||
fn from(err: rpc_request::RpcError) -> Self {
|
||||
Self {
|
||||
command: None,
|
||||
request: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ impl From<rpc_request::RpcError> for ClientError {
|
||||
impl From<serde_json::error::Error> for ClientError {
|
||||
fn from(err: serde_json::error::Error) -> Self {
|
||||
Self {
|
||||
command: None,
|
||||
request: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ impl From<serde_json::error::Error> for ClientError {
|
||||
impl From<SignerError> for ClientError {
|
||||
fn from(err: SignerError) -> Self {
|
||||
Self {
|
||||
command: None,
|
||||
request: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
@@ -152,7 +152,7 @@ impl From<SignerError> for ClientError {
|
||||
impl From<TransactionError> for ClientError {
|
||||
fn from(err: TransactionError) -> Self {
|
||||
Self {
|
||||
command: None,
|
||||
request: None,
|
||||
kind: err.into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
use crate::{client_error::Result, rpc_request::RpcRequest};
|
||||
|
||||
pub(crate) trait GenericRpcClientRequest {
|
||||
fn send(
|
||||
&self,
|
||||
request: &RpcRequest,
|
||||
params: serde_json::Value,
|
||||
retries: usize,
|
||||
) -> Result<serde_json::Value>;
|
||||
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value>;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ pub mod perf_utils;
|
||||
pub mod pubsub_client;
|
||||
pub mod rpc_client;
|
||||
pub mod rpc_client_request;
|
||||
pub mod rpc_config;
|
||||
pub mod rpc_request;
|
||||
pub mod rpc_response;
|
||||
pub mod thin_client;
|
||||
|
||||
@@ -2,14 +2,16 @@ use crate::{
|
||||
client_error::Result,
|
||||
generic_rpc_client_request::GenericRpcClientRequest,
|
||||
rpc_request::RpcRequest,
|
||||
rpc_response::{Response, RpcResponseContext, RpcTransactionStatus},
|
||||
rpc_response::{Response, RpcResponseContext},
|
||||
};
|
||||
use serde_json::{Number, Value};
|
||||
use solana_sdk::{
|
||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||
instruction::InstructionError,
|
||||
transaction::{self, TransactionError},
|
||||
signature::Signature,
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
};
|
||||
use solana_transaction_status::TransactionStatus;
|
||||
use std::{collections::HashMap, sync::RwLock};
|
||||
|
||||
pub const PUBKEY: &str = "7RoSF9fUmdphVCpabEoefH81WwrW7orsWonXWqTXkKV8";
|
||||
@@ -36,30 +38,14 @@ impl MockRpcClientRequest {
|
||||
}
|
||||
|
||||
impl GenericRpcClientRequest for MockRpcClientRequest {
|
||||
fn send(
|
||||
&self,
|
||||
request: &RpcRequest,
|
||||
params: serde_json::Value,
|
||||
_retries: usize,
|
||||
) -> Result<serde_json::Value> {
|
||||
if let Some(value) = self.mocks.write().unwrap().remove(request) {
|
||||
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);
|
||||
}
|
||||
if self.url == "fails" {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
let val = match request {
|
||||
RpcRequest::ConfirmTransaction => {
|
||||
if let Some(params_array) = params.as_array() {
|
||||
if let Value::String(param_string) = ¶ms_array[0] {
|
||||
Value::Bool(param_string == SIGNATURE)
|
||||
} else {
|
||||
Value::Null
|
||||
}
|
||||
} else {
|
||||
Value::Null
|
||||
}
|
||||
}
|
||||
RpcRequest::GetBalance => serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: Value::Number(Number::from(50)),
|
||||
@@ -86,7 +72,7 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: serde_json::to_value(FeeRateGovernor::default()).unwrap(),
|
||||
})?,
|
||||
RpcRequest::GetSignatureStatus => {
|
||||
RpcRequest::GetSignatureStatuses => {
|
||||
let status: transaction::Result<()> = if self.url == "account_in_use" {
|
||||
Err(TransactionError::AccountInUse)
|
||||
} else if self.url == "instruction_error" {
|
||||
@@ -100,13 +86,32 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
|
||||
let status = if self.url == "sig_not_found" {
|
||||
None
|
||||
} else {
|
||||
Some(RpcTransactionStatus { status, slot: 1 })
|
||||
let err = status.clone().err();
|
||||
Some(TransactionStatus {
|
||||
status,
|
||||
slot: 1,
|
||||
confirmations: None,
|
||||
err,
|
||||
})
|
||||
};
|
||||
serde_json::to_value(vec![status])?
|
||||
serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: vec![status],
|
||||
})?
|
||||
}
|
||||
RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)),
|
||||
RpcRequest::GetSlot => Value::Number(Number::from(0)),
|
||||
RpcRequest::SendTransaction => Value::String(SIGNATURE.to_string()),
|
||||
RpcRequest::SendTransaction => {
|
||||
let signature = if self.url == "malicious" {
|
||||
Signature::new(&[8; 64]).to_string()
|
||||
} else {
|
||||
let tx_str = params.as_array().unwrap()[0].as_str().unwrap().to_string();
|
||||
let data = bs58::decode(tx_str).into_vec().unwrap();
|
||||
let tx: Transaction = bincode::deserialize(&data).unwrap();
|
||||
tx.signatures[0].to_string()
|
||||
};
|
||||
Value::String(signature)
|
||||
}
|
||||
RpcRequest::GetMinimumBalanceForRentExemption => Value::Number(Number::from(1234)),
|
||||
_ => Value::Null,
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,7 @@ use crate::{
|
||||
rpc_request::{RpcError, RpcRequest},
|
||||
};
|
||||
use log::*;
|
||||
use reqwest::{self, header::CONTENT_TYPE};
|
||||
use solana_sdk::clock::{DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT};
|
||||
use reqwest::{self, header::CONTENT_TYPE, StatusCode};
|
||||
use std::{thread::sleep, time::Duration};
|
||||
|
||||
pub struct RpcClientRequest {
|
||||
@@ -29,17 +28,13 @@ impl RpcClientRequest {
|
||||
}
|
||||
|
||||
impl GenericRpcClientRequest for RpcClientRequest {
|
||||
fn send(
|
||||
&self,
|
||||
request: &RpcRequest,
|
||||
params: serde_json::Value,
|
||||
mut retries: usize,
|
||||
) -> Result<serde_json::Value> {
|
||||
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value> {
|
||||
// Concurrent requests are not supported so reuse the same request id for all requests
|
||||
let request_id = 1;
|
||||
|
||||
let request_json = request.build_request_json(request_id, params);
|
||||
|
||||
let mut too_many_requests_retries = 5;
|
||||
loop {
|
||||
match self
|
||||
.client
|
||||
@@ -50,6 +45,19 @@ impl GenericRpcClientRequest for RpcClientRequest {
|
||||
{
|
||||
Ok(response) => {
|
||||
if !response.status().is_success() {
|
||||
if response.status() == StatusCode::TOO_MANY_REQUESTS
|
||||
&& too_many_requests_retries > 0
|
||||
{
|
||||
too_many_requests_retries -= 1;
|
||||
debug!(
|
||||
"Server responded with {:?}, {} retries left",
|
||||
response, too_many_requests_retries
|
||||
);
|
||||
|
||||
// Sleep for 500ms to give the server a break
|
||||
sleep(Duration::from_millis(500));
|
||||
continue;
|
||||
}
|
||||
return Err(response.error_for_status().unwrap_err().into());
|
||||
}
|
||||
|
||||
@@ -63,17 +71,8 @@ impl GenericRpcClientRequest for RpcClientRequest {
|
||||
}
|
||||
return Ok(json["result"].clone());
|
||||
}
|
||||
Err(e) => {
|
||||
info!("{:?} failed, {} retries left: {:?}", request, retries, e);
|
||||
if retries == 0 {
|
||||
return Err(e.into());
|
||||
}
|
||||
retries -= 1;
|
||||
|
||||
// Sleep for approximately half a slot
|
||||
sleep(Duration::from_millis(
|
||||
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND,
|
||||
));
|
||||
Err(err) => {
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
31
client/src/rpc_config.rs
Normal file
31
client/src/rpc_config.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use solana_sdk::commitment_config::CommitmentConfig;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSignatureStatusConfig {
|
||||
pub search_transaction_history: Option<bool>,
|
||||
// DEPRECATED
|
||||
#[serde(flatten)]
|
||||
pub commitment: Option<CommitmentConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSimulateTransactionConfig {
|
||||
pub sig_verify: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RpcLargestAccountsFilter {
|
||||
Circulating,
|
||||
NonCirculating,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcLargestAccountsConfig {
|
||||
#[serde(flatten)]
|
||||
pub commitment: Option<CommitmentConfig>,
|
||||
pub filter: Option<RpcLargestAccountsFilter>,
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
use serde_json::{json, Value};
|
||||
use std::fmt;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum RpcRequest {
|
||||
ConfirmTransaction,
|
||||
DeregisterNode,
|
||||
ValidatorExit,
|
||||
GetAccountInfo,
|
||||
@@ -12,40 +12,43 @@ pub enum RpcRequest {
|
||||
GetClusterNodes,
|
||||
GetConfirmedBlock,
|
||||
GetConfirmedBlocks,
|
||||
GetConfirmedSignaturesForAddress,
|
||||
GetConfirmedTransaction,
|
||||
GetEpochInfo,
|
||||
GetEpochSchedule,
|
||||
GetGenesisHash,
|
||||
GetIdentity,
|
||||
GetInflation,
|
||||
GetLargestAccounts,
|
||||
GetLeaderSchedule,
|
||||
GetNumBlocksSinceSignatureConfirmation,
|
||||
GetProgramAccounts,
|
||||
GetRecentBlockhash,
|
||||
GetFeeCalculatorForBlockhash,
|
||||
GetFeeRateGovernor,
|
||||
GetSignatureStatus,
|
||||
GetSignatureStatuses,
|
||||
GetSlot,
|
||||
GetSlotLeader,
|
||||
GetStorageTurn,
|
||||
GetStorageTurnRate,
|
||||
GetSlotsPerSegment,
|
||||
GetStoragePubkeysForSlot,
|
||||
GetSupply,
|
||||
GetTotalSupply,
|
||||
GetTransactionCount,
|
||||
GetVersion,
|
||||
GetVoteAccounts,
|
||||
RegisterNode,
|
||||
RequestAirdrop,
|
||||
SendTransaction,
|
||||
SimulateTransaction,
|
||||
SignVote,
|
||||
GetMinimumBalanceForRentExemption,
|
||||
MinimumLedgerSlot,
|
||||
}
|
||||
|
||||
impl RpcRequest {
|
||||
pub(crate) fn build_request_json(&self, id: u64, params: Value) -> Value {
|
||||
let jsonrpc = "2.0";
|
||||
impl fmt::Display for RpcRequest {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let method = match self {
|
||||
RpcRequest::ConfirmTransaction => "confirmTransaction",
|
||||
RpcRequest::DeregisterNode => "deregisterNode",
|
||||
RpcRequest::ValidatorExit => "validatorExit",
|
||||
RpcRequest::GetAccountInfo => "getAccountInfo",
|
||||
@@ -54,40 +57,55 @@ impl RpcRequest {
|
||||
RpcRequest::GetClusterNodes => "getClusterNodes",
|
||||
RpcRequest::GetConfirmedBlock => "getConfirmedBlock",
|
||||
RpcRequest::GetConfirmedBlocks => "getConfirmedBlocks",
|
||||
RpcRequest::GetConfirmedSignaturesForAddress => "getConfirmedSignaturesForAddress",
|
||||
RpcRequest::GetConfirmedTransaction => "getConfirmedTransaction",
|
||||
RpcRequest::GetEpochInfo => "getEpochInfo",
|
||||
RpcRequest::GetEpochSchedule => "getEpochSchedule",
|
||||
RpcRequest::GetGenesisHash => "getGenesisHash",
|
||||
RpcRequest::GetIdentity => "getIdentity",
|
||||
RpcRequest::GetInflation => "getInflation",
|
||||
RpcRequest::GetLargestAccounts => "getLargestAccounts",
|
||||
RpcRequest::GetLeaderSchedule => "getLeaderSchedule",
|
||||
RpcRequest::GetNumBlocksSinceSignatureConfirmation => {
|
||||
"getNumBlocksSinceSignatureConfirmation"
|
||||
}
|
||||
RpcRequest::GetProgramAccounts => "getProgramAccounts",
|
||||
RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
|
||||
RpcRequest::GetFeeCalculatorForBlockhash => "getFeeCalculatorForBlockhash",
|
||||
RpcRequest::GetFeeRateGovernor => "getFeeRateGovernor",
|
||||
RpcRequest::GetSignatureStatus => "getSignatureStatus",
|
||||
RpcRequest::GetSignatureStatuses => "getSignatureStatuses",
|
||||
RpcRequest::GetSlot => "getSlot",
|
||||
RpcRequest::GetSlotLeader => "getSlotLeader",
|
||||
RpcRequest::GetStorageTurn => "getStorageTurn",
|
||||
RpcRequest::GetStorageTurnRate => "getStorageTurnRate",
|
||||
RpcRequest::GetSlotsPerSegment => "getSlotsPerSegment",
|
||||
RpcRequest::GetStoragePubkeysForSlot => "getStoragePubkeysForSlot",
|
||||
RpcRequest::GetSupply => "getSupply",
|
||||
RpcRequest::GetTotalSupply => "getTotalSupply",
|
||||
RpcRequest::GetTransactionCount => "getTransactionCount",
|
||||
RpcRequest::GetVersion => "getVersion",
|
||||
RpcRequest::GetVoteAccounts => "getVoteAccounts",
|
||||
RpcRequest::RegisterNode => "registerNode",
|
||||
RpcRequest::RequestAirdrop => "requestAirdrop",
|
||||
RpcRequest::SendTransaction => "sendTransaction",
|
||||
RpcRequest::SimulateTransaction => "simulateTransaction",
|
||||
RpcRequest::SignVote => "signVote",
|
||||
RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption",
|
||||
RpcRequest::MinimumLedgerSlot => "minimumLedgerSlot",
|
||||
};
|
||||
|
||||
write!(f, "{}", method)
|
||||
}
|
||||
}
|
||||
|
||||
pub const NUM_LARGEST_ACCOUNTS: usize = 20;
|
||||
pub const MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS: usize = 256;
|
||||
pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE: u64 = 10_000;
|
||||
|
||||
impl RpcRequest {
|
||||
pub(crate) fn build_request_json(self, id: u64, params: Value) -> Value {
|
||||
let jsonrpc = "2.0";
|
||||
json!({
|
||||
"jsonrpc": jsonrpc,
|
||||
"id": id,
|
||||
"method": method,
|
||||
"method": format!("{}", self),
|
||||
"params": params,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
use crate::{client_error, rpc_request::RpcError};
|
||||
use bincode::serialize;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
clock::{Epoch, Slot},
|
||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||
message::MessageHeader,
|
||||
pubkey::Pubkey,
|
||||
transaction::{Result, Transaction},
|
||||
transaction::{Result, TransactionError},
|
||||
};
|
||||
use std::{collections::HashMap, net::SocketAddr, str::FromStr};
|
||||
|
||||
@@ -30,126 +28,6 @@ pub struct RpcBlockCommitment<T> {
|
||||
pub total_stake: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RpcReward {
|
||||
pub pubkey: String,
|
||||
pub lamports: i64,
|
||||
}
|
||||
|
||||
pub type RpcRewards = Vec<RpcReward>;
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcConfirmedBlock {
|
||||
pub previous_blockhash: String,
|
||||
pub blockhash: String,
|
||||
pub parent_slot: Slot,
|
||||
pub transactions: Vec<RpcTransactionWithStatusMeta>,
|
||||
pub rewards: RpcRewards,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcTransactionWithStatusMeta {
|
||||
pub transaction: RpcEncodedTransaction,
|
||||
pub meta: Option<RpcTransactionStatusMeta>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RpcTransactionEncoding {
|
||||
Binary,
|
||||
Json,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum RpcEncodedTransaction {
|
||||
Binary(String),
|
||||
Json(RpcTransaction),
|
||||
}
|
||||
|
||||
impl RpcEncodedTransaction {
|
||||
pub fn encode(transaction: Transaction, encoding: RpcTransactionEncoding) -> Self {
|
||||
if encoding == RpcTransactionEncoding::Json {
|
||||
RpcEncodedTransaction::Json(RpcTransaction {
|
||||
signatures: transaction
|
||||
.signatures
|
||||
.iter()
|
||||
.map(|sig| sig.to_string())
|
||||
.collect(),
|
||||
message: RpcMessage {
|
||||
header: transaction.message.header,
|
||||
account_keys: transaction
|
||||
.message
|
||||
.account_keys
|
||||
.iter()
|
||||
.map(|pubkey| pubkey.to_string())
|
||||
.collect(),
|
||||
recent_blockhash: transaction.message.recent_blockhash.to_string(),
|
||||
instructions: transaction
|
||||
.message
|
||||
.instructions
|
||||
.iter()
|
||||
.map(|instruction| RpcCompiledInstruction {
|
||||
program_id_index: instruction.program_id_index,
|
||||
accounts: instruction.accounts.clone(),
|
||||
data: bs58::encode(instruction.data.clone()).into_string(),
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
RpcEncodedTransaction::Binary(
|
||||
bs58::encode(serialize(&transaction).unwrap()).into_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A duplicate representation of a Transaction for pretty JSON serialization
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcTransaction {
|
||||
pub signatures: Vec<String>,
|
||||
pub message: RpcMessage,
|
||||
}
|
||||
|
||||
/// A duplicate representation of a Message for pretty JSON serialization
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcMessage {
|
||||
pub header: MessageHeader,
|
||||
pub account_keys: Vec<String>,
|
||||
pub recent_blockhash: String,
|
||||
pub instructions: Vec<RpcCompiledInstruction>,
|
||||
}
|
||||
|
||||
/// A duplicate representation of a Message for pretty JSON serialization
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcCompiledInstruction {
|
||||
pub program_id_index: u8,
|
||||
pub accounts: Vec<u8>,
|
||||
pub data: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcTransactionStatusMeta {
|
||||
pub status: Result<()>,
|
||||
pub fee: u64,
|
||||
pub pre_balances: Vec<u64>,
|
||||
pub post_balances: Vec<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcTransactionStatus {
|
||||
pub slot: Slot,
|
||||
pub status: Result<()>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcBlockhashFeeCalculator {
|
||||
@@ -176,6 +54,12 @@ pub struct RpcKeyedAccount {
|
||||
pub account: RpcAccount,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSignatureResult {
|
||||
pub err: Option<TransactionError>,
|
||||
}
|
||||
|
||||
/// A duplicate representation of a Message for pretty JSON serialization
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -224,6 +108,8 @@ pub struct RpcContactInfo {
|
||||
pub tpu: Option<SocketAddr>,
|
||||
/// JSON RPC port
|
||||
pub rpc: Option<SocketAddr>,
|
||||
/// Software version
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
/// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot
|
||||
@@ -308,3 +194,19 @@ pub struct RpcStorageTurn {
|
||||
pub blockhash: String,
|
||||
pub slot: Slot,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcAccountBalance {
|
||||
pub address: String,
|
||||
pub lamports: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSupply {
|
||||
pub total: u64,
|
||||
pub circulating: u64,
|
||||
pub non_circulating: u64,
|
||||
pub non_circulating_accounts: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -471,7 +471,7 @@ impl SyncClient for ThinClient {
|
||||
) -> TransportResult<Option<transaction::Result<()>>> {
|
||||
let status = self
|
||||
.rpc_client()
|
||||
.get_signature_status(&signature.to_string())
|
||||
.get_signature_status(&signature)
|
||||
.map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
@@ -488,7 +488,7 @@ impl SyncClient for ThinClient {
|
||||
) -> TransportResult<Option<transaction::Result<()>>> {
|
||||
let status = self
|
||||
.rpc_client()
|
||||
.get_signature_status_with_commitment(&signature.to_string(), commitment_config)
|
||||
.get_signature_status_with_commitment(&signature, commitment_config)
|
||||
.map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "solana-core"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.0.9"
|
||||
version = "1.1.15"
|
||||
documentation = "https://docs.rs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
readme = "../README.md"
|
||||
@@ -17,65 +17,67 @@ codecov = { repository = "solana-labs/solana", branch = "master", service = "git
|
||||
bincode = "1.2.1"
|
||||
bv = { version = "0.11.1", features = ["serde"] }
|
||||
bs58 = "0.3.0"
|
||||
byteorder = "1.3.2"
|
||||
chrono = { version = "0.4.10", features = ["serde"] }
|
||||
compression = "0.1.5"
|
||||
byteorder = "1.3.4"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
core_affinity = "0.5.10"
|
||||
crossbeam-channel = "0.3"
|
||||
crossbeam-channel = "0.4"
|
||||
fs_extra = "1.1.0"
|
||||
flate2 = "1.0"
|
||||
indexmap = "1.3"
|
||||
itertools = "0.8.2"
|
||||
itertools = "0.9.0"
|
||||
jsonrpc-core = "14.0.5"
|
||||
jsonrpc-core-client = { version = "14.0.5", features = ["ws"] }
|
||||
jsonrpc-derive = "14.0.5"
|
||||
jsonrpc-http-server = "14.0.6"
|
||||
jsonrpc-pubsub = "14.0.6"
|
||||
jsonrpc-ws-server = "14.0.6"
|
||||
libc = "0.2.66"
|
||||
log = "0.4.8"
|
||||
nix = "0.17.0"
|
||||
num_cpus = "1.0.0"
|
||||
num-traits = "0.2"
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
rayon = "1.2.0"
|
||||
regex = "1.3.4"
|
||||
serde = "1.0.104"
|
||||
rand = "0.7.0"
|
||||
rand_chacha = "0.2.2"
|
||||
rayon = "1.3.0"
|
||||
regex = "1.3.6"
|
||||
serde = "1.0.105"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.46"
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.9" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-client = { path = "../client", version = "1.0.9" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.9" }
|
||||
ed25519-dalek = "=1.0.0-pre.1"
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.0.9" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.9" }
|
||||
solana-measure = { path = "../measure", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-chacha-cuda = { path = "../chacha-cuda", version = "1.0.9" }
|
||||
solana-perf = { path = "../perf", version = "1.0.9" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.9" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.9" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.9" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.0.9" }
|
||||
solana-sys-tuner = { path = "../sys-tuner", version = "1.0.9" }
|
||||
serde_json = "1.0.48"
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.1.15" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.1.15" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.1.15" }
|
||||
solana-client = { path = "../client", version = "1.1.15" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.1.15" }
|
||||
solana-faucet = { path = "../faucet", version = "1.1.15" }
|
||||
ed25519-dalek = "=1.0.0-pre.3"
|
||||
solana-ledger = { path = "../ledger", version = "1.1.15" }
|
||||
solana-logger = { path = "../logger", version = "1.1.15" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.1.15" }
|
||||
solana-metrics = { path = "../metrics", version = "1.1.15" }
|
||||
solana-measure = { path = "../measure", version = "1.1.15" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.1.15" }
|
||||
solana-chacha-cuda = { path = "../chacha-cuda", version = "1.1.15" }
|
||||
solana-perf = { path = "../perf", version = "1.1.15" }
|
||||
solana-runtime = { path = "../runtime", version = "1.1.15" }
|
||||
solana-sdk = { path = "../sdk", version = "1.1.15" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.1.15" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.1.15" }
|
||||
solana-streamer = { path = "../streamer", version = "1.1.15" }
|
||||
solana-version = { path = "../version", version = "1.1.15" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.1.15" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.1.15" }
|
||||
solana-sys-tuner = { path = "../sys-tuner", version = "1.1.15" }
|
||||
tempfile = "3.1.0"
|
||||
thiserror = "1.0"
|
||||
tokio = "0.1"
|
||||
tokio-codec = "0.1"
|
||||
tokio-fs = "0.1"
|
||||
tokio-io = "0.1"
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.0.9" }
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.1.15" }
|
||||
trees = "0.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
matches = "0.1.6"
|
||||
reqwest = { version = "0.10.1", default-features = false, features = ["blocking", "rustls-tls"] }
|
||||
serial_test = "0.3.2"
|
||||
reqwest = { version = "0.10.4", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serial_test = "0.4.0"
|
||||
serial_test_derive = "0.4.0"
|
||||
systemstat = "0.1.5"
|
||||
|
||||
@@ -103,3 +105,6 @@ name = "cluster_info"
|
||||
[[bench]]
|
||||
name = "chacha"
|
||||
required-features = ["chacha"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
@@ -9,12 +9,12 @@ use rayon::prelude::*;
|
||||
use solana_core::banking_stage::{create_test_recorder, BankingStage};
|
||||
use solana_core::cluster_info::ClusterInfo;
|
||||
use solana_core::cluster_info::Node;
|
||||
use solana_core::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_core::packet::to_packets_chunked;
|
||||
use solana_core::poh_recorder::WorkingBankEntry;
|
||||
use solana_ledger::blockstore_processor::process_entries;
|
||||
use solana_ledger::entry::{next_hash, Entry};
|
||||
use solana_ledger::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path};
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_perf::test_tx::test_tx;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::genesis_config::GenesisConfig;
|
||||
@@ -190,7 +190,7 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) {
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, None);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let _banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
|
||||
@@ -3,13 +3,18 @@
|
||||
extern crate test;
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
use solana_core::broadcast_stage::{broadcast_shreds, get_broadcast_peers};
|
||||
use solana_core::cluster_info::{ClusterInfo, Node};
|
||||
use solana_core::contact_info::ContactInfo;
|
||||
use solana_ledger::shred::{Shred, NONCE_SHRED_PAYLOAD_SIZE};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::timing::timestamp;
|
||||
use std::collections::HashMap;
|
||||
use std::net::UdpSocket;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::UdpSocket,
|
||||
sync::{atomic::AtomicU64, Arc},
|
||||
};
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
@@ -20,10 +25,8 @@ fn broadcast_shreds_bench(bencher: &mut Bencher) {
|
||||
let mut cluster_info = ClusterInfo::new_with_invalid_keypair(leader_info.info.clone());
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
|
||||
const SHRED_SIZE: usize = 1024;
|
||||
const NUM_SHREDS: usize = 32;
|
||||
let shreds = vec![vec![0; SHRED_SIZE]; NUM_SHREDS];
|
||||
let seeds = vec![[0u8; 32]; NUM_SHREDS];
|
||||
let shreds = vec![Shred::new_empty_data_shred(NONCE_SHRED_PAYLOAD_SIZE); NUM_SHREDS];
|
||||
let mut stakes = HashMap::new();
|
||||
const NUM_PEERS: usize = 200;
|
||||
for _ in 0..NUM_PEERS {
|
||||
@@ -33,10 +36,20 @@ fn broadcast_shreds_bench(bencher: &mut Bencher) {
|
||||
stakes.insert(id, thread_rng().gen_range(1, NUM_PEERS) as u64);
|
||||
}
|
||||
let stakes = Arc::new(stakes);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let (peers, peers_and_stakes) = get_broadcast_peers(&cluster_info, Some(stakes.clone()));
|
||||
let shreds = Arc::new(shreds);
|
||||
let last_datapoint = Arc::new(AtomicU64::new(0));
|
||||
bencher.iter(move || {
|
||||
let shreds = shreds.clone();
|
||||
cluster_info
|
||||
.broadcast_shreds(&socket, shreds, &seeds, Some(stakes.clone()))
|
||||
.unwrap();
|
||||
broadcast_shreds(
|
||||
&socket,
|
||||
&shreds,
|
||||
&peers_and_stakes,
|
||||
&peers,
|
||||
&last_datapoint,
|
||||
&mut 0,
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ extern crate test;
|
||||
use log::*;
|
||||
use solana_core::cluster_info::{ClusterInfo, Node};
|
||||
use solana_core::contact_info::ContactInfo;
|
||||
use solana_core::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_core::packet::to_packets_chunked;
|
||||
use solana_core::retransmit_stage::retransmitter;
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use solana_ledger::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_ledger::leader_schedule_cache::LeaderScheduleCache;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_perf::test_tx::test_tx;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
@@ -45,7 +45,7 @@ fn bench_retransmitter(bencher: &mut Bencher) {
|
||||
peer_sockets.push(socket);
|
||||
}
|
||||
let peer_sockets = Arc::new(peer_sockets);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
|
||||
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(100_000);
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
|
||||
@@ -5,7 +5,7 @@ extern crate test;
|
||||
use solana_ledger::entry::{create_ticks, Entry};
|
||||
use solana_ledger::shred::{
|
||||
max_entries_per_n_shred, max_ticks_per_n_shreds, Shred, Shredder, RECOMMENDED_FEC_RATE,
|
||||
SIZE_OF_DATA_SHRED_PAYLOAD,
|
||||
SIZE_OF_NONCE_DATA_SHRED_PAYLOAD,
|
||||
};
|
||||
use solana_perf::test_tx;
|
||||
use solana_sdk::hash::Hash;
|
||||
@@ -29,10 +29,11 @@ fn make_large_unchained_entries(txs_per_entry: u64, num_entries: u64) -> Vec<Ent
|
||||
#[bench]
|
||||
fn bench_shredder_ticks(bencher: &mut Bencher) {
|
||||
let kp = Arc::new(Keypair::new());
|
||||
let shred_size = SIZE_OF_DATA_SHRED_PAYLOAD;
|
||||
let shred_size = SIZE_OF_NONCE_DATA_SHRED_PAYLOAD;
|
||||
let num_shreds = ((1000 * 1000) + (shred_size - 1)) / shred_size;
|
||||
// ~1Mb
|
||||
let num_ticks = max_ticks_per_n_shreds(1) * num_shreds as u64;
|
||||
let num_ticks =
|
||||
max_ticks_per_n_shreds(1, Some(SIZE_OF_NONCE_DATA_SHRED_PAYLOAD)) * num_shreds as u64;
|
||||
let entries = create_ticks(num_ticks, 0, Hash::default());
|
||||
bencher.iter(|| {
|
||||
let shredder = Shredder::new(1, 0, RECOMMENDED_FEC_RATE, kp.clone(), 0, 0).unwrap();
|
||||
@@ -43,10 +44,14 @@ fn bench_shredder_ticks(bencher: &mut Bencher) {
|
||||
#[bench]
|
||||
fn bench_shredder_large_entries(bencher: &mut Bencher) {
|
||||
let kp = Arc::new(Keypair::new());
|
||||
let shred_size = SIZE_OF_DATA_SHRED_PAYLOAD;
|
||||
let shred_size = SIZE_OF_NONCE_DATA_SHRED_PAYLOAD;
|
||||
let num_shreds = ((1000 * 1000) + (shred_size - 1)) / shred_size;
|
||||
let txs_per_entry = 128;
|
||||
let num_entries = max_entries_per_n_shred(&make_test_entry(txs_per_entry), num_shreds as u64);
|
||||
let num_entries = max_entries_per_n_shred(
|
||||
&make_test_entry(txs_per_entry),
|
||||
num_shreds as u64,
|
||||
Some(shred_size),
|
||||
);
|
||||
let entries = make_large_unchained_entries(txs_per_entry, num_entries);
|
||||
// 1Mb
|
||||
bencher.iter(|| {
|
||||
@@ -58,10 +63,10 @@ fn bench_shredder_large_entries(bencher: &mut Bencher) {
|
||||
#[bench]
|
||||
fn bench_deshredder(bencher: &mut Bencher) {
|
||||
let kp = Arc::new(Keypair::new());
|
||||
let shred_size = SIZE_OF_DATA_SHRED_PAYLOAD;
|
||||
let shred_size = SIZE_OF_NONCE_DATA_SHRED_PAYLOAD;
|
||||
// ~10Mb
|
||||
let num_shreds = ((10000 * 1000) + (shred_size - 1)) / shred_size;
|
||||
let num_ticks = max_ticks_per_n_shreds(1) * num_shreds as u64;
|
||||
let num_ticks = max_ticks_per_n_shreds(1, Some(shred_size)) * num_shreds as u64;
|
||||
let entries = create_ticks(num_ticks, 0, Hash::default());
|
||||
let shredder = Shredder::new(1, 0, RECOMMENDED_FEC_RATE, kp, 0, 0).unwrap();
|
||||
let data_shreds = shredder.entries_to_shreds(&entries, true, 0).0;
|
||||
@@ -73,7 +78,7 @@ fn bench_deshredder(bencher: &mut Bencher) {
|
||||
|
||||
#[bench]
|
||||
fn bench_deserialize_hdr(bencher: &mut Bencher) {
|
||||
let data = vec![0; SIZE_OF_DATA_SHRED_PAYLOAD];
|
||||
let data = vec![0; SIZE_OF_NONCE_DATA_SHRED_PAYLOAD];
|
||||
|
||||
let shred = Shred::new_from_data(2, 1, 1, Some(&data), true, true, 0, 0, 1);
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ extern crate test;
|
||||
use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use solana_core::packet::to_packets_chunked;
|
||||
use solana_core::sigverify::TransactionSigVerifier;
|
||||
use solana_core::sigverify_stage::SigVerifyStage;
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_perf::test_tx::test_tx;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
|
||||
45
core/src/accounts_background_service.rs
Normal file
45
core/src/accounts_background_service.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
// Service to clean up dead slots in accounts_db
|
||||
//
|
||||
// This can be expensive since we have to walk the append vecs being cleaned up.
|
||||
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, RwLock,
|
||||
};
|
||||
use std::thread::{self, sleep, Builder, JoinHandle};
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct AccountsBackgroundService {
|
||||
t_background: JoinHandle<()>,
|
||||
}
|
||||
|
||||
const INTERVAL_MS: u64 = 100;
|
||||
|
||||
impl AccountsBackgroundService {
|
||||
pub fn new(bank_forks: Arc<RwLock<BankForks>>, exit: &Arc<AtomicBool>) -> Self {
|
||||
info!("AccountsBackgroundService active");
|
||||
let exit = exit.clone();
|
||||
let t_background = Builder::new()
|
||||
.name("solana-accounts-background".to_string())
|
||||
.spawn(move || loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
let bank = bank_forks.read().unwrap().working_bank();
|
||||
|
||||
bank.process_dead_slots();
|
||||
|
||||
// Currently, given INTERVAL_MS, we process 1 slot/100 ms
|
||||
bank.process_stale_slot();
|
||||
|
||||
sleep(Duration::from_millis(INTERVAL_MS));
|
||||
})
|
||||
.unwrap();
|
||||
Self { t_background }
|
||||
}
|
||||
|
||||
pub fn join(self) -> thread::Result<()> {
|
||||
self.t_background.join()
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
// hash on gossip. Monitor gossip for messages from validators in the --trusted-validators
|
||||
// set and halt the node if a mismatch is detected.
|
||||
|
||||
use crate::cluster_info::ClusterInfo;
|
||||
use crate::cluster_info::{ClusterInfo, MAX_SNAPSHOT_HASHES};
|
||||
use solana_ledger::{
|
||||
snapshot_package::SnapshotPackage, snapshot_package::SnapshotPackageReceiver,
|
||||
snapshot_package::SnapshotPackageSender,
|
||||
@@ -15,7 +15,7 @@ use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::RecvTimeoutError,
|
||||
Arc, RwLock,
|
||||
Arc,
|
||||
},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
time::Duration,
|
||||
@@ -30,7 +30,7 @@ impl AccountsHashVerifier {
|
||||
snapshot_package_receiver: SnapshotPackageReceiver,
|
||||
snapshot_package_sender: Option<SnapshotPackageSender>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &Arc<ClusterInfo>,
|
||||
trusted_validators: Option<HashSet<Pubkey>>,
|
||||
halt_on_trusted_validators_accounts_hash_mismatch: bool,
|
||||
fault_injection_rate_slots: u64,
|
||||
@@ -72,7 +72,7 @@ impl AccountsHashVerifier {
|
||||
|
||||
fn process_snapshot(
|
||||
snapshot_package: SnapshotPackage,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
trusted_validators: &Option<HashSet<Pubkey>>,
|
||||
halt_on_trusted_validator_accounts_hash_mismatch: bool,
|
||||
snapshot_package_sender: &Option<SnapshotPackageSender>,
|
||||
@@ -94,6 +94,10 @@ impl AccountsHashVerifier {
|
||||
hashes.push((snapshot_package.root, snapshot_package.hash));
|
||||
}
|
||||
|
||||
while hashes.len() > MAX_SNAPSHOT_HASHES {
|
||||
hashes.remove(0);
|
||||
}
|
||||
|
||||
if halt_on_trusted_validator_accounts_hash_mismatch {
|
||||
let mut slot_to_hash = HashMap::new();
|
||||
for (slot, hash) in hashes.iter() {
|
||||
@@ -107,25 +111,21 @@ impl AccountsHashVerifier {
|
||||
if sender.send(snapshot_package).is_err() {}
|
||||
}
|
||||
|
||||
cluster_info
|
||||
.write()
|
||||
.unwrap()
|
||||
.push_accounts_hashes(hashes.clone());
|
||||
cluster_info.push_accounts_hashes(hashes.clone());
|
||||
}
|
||||
|
||||
fn should_halt(
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
trusted_validators: &Option<HashSet<Pubkey>>,
|
||||
slot_to_hash: &mut HashMap<Slot, Hash>,
|
||||
) -> bool {
|
||||
let mut verified_count = 0;
|
||||
let mut highest_slot = 0;
|
||||
if let Some(trusted_validators) = trusted_validators.as_ref() {
|
||||
for trusted_validator in trusted_validators {
|
||||
let cluster_info_r = cluster_info.read().unwrap();
|
||||
if let Some(accounts_hashes) =
|
||||
cluster_info_r.get_accounts_hash_for_node(trusted_validator)
|
||||
let is_conflicting = cluster_info.get_accounts_hash_for_node(trusted_validator, |accounts_hashes|
|
||||
{
|
||||
for (slot, hash) in accounts_hashes {
|
||||
accounts_hashes.iter().any(|(slot, hash)| {
|
||||
if let Some(reference_hash) = slot_to_hash.get(slot) {
|
||||
if *hash != *reference_hash {
|
||||
error!("Trusted validator {} produced conflicting hashes for slot: {} ({} != {})",
|
||||
@@ -134,19 +134,29 @@ impl AccountsHashVerifier {
|
||||
hash,
|
||||
reference_hash,
|
||||
);
|
||||
|
||||
return true;
|
||||
true
|
||||
} else {
|
||||
verified_count += 1;
|
||||
false
|
||||
}
|
||||
} else {
|
||||
highest_slot = std::cmp::max(*slot, highest_slot);
|
||||
slot_to_hash.insert(*slot, *hash);
|
||||
false
|
||||
}
|
||||
}
|
||||
})
|
||||
}).unwrap_or(false);
|
||||
|
||||
if is_conflicting {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
inc_new_counter_info!("accounts_hash_verifier-hashes_verified", verified_count);
|
||||
datapoint_info!(
|
||||
"accounts_hash_verifier",
|
||||
("highest_slot_verified", highest_slot, i64),
|
||||
);
|
||||
false
|
||||
}
|
||||
|
||||
@@ -171,7 +181,7 @@ mod tests {
|
||||
|
||||
let contact_info = ContactInfo::new_localhost(&keypair.pubkey(), 0);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
|
||||
let mut trusted_validators = HashSet::new();
|
||||
let mut slot_to_hash = HashMap::new();
|
||||
@@ -186,8 +196,7 @@ mod tests {
|
||||
let hash2 = hash(&[2]);
|
||||
{
|
||||
let message = make_accounts_hashes_message(&validator1, vec![(0, hash1)]).unwrap();
|
||||
let mut cluster_info_w = cluster_info.write().unwrap();
|
||||
cluster_info_w.push_message(message);
|
||||
cluster_info.push_message(message);
|
||||
}
|
||||
slot_to_hash.insert(0, hash2);
|
||||
trusted_validators.insert(validator1.pubkey());
|
||||
@@ -197,4 +206,56 @@ mod tests {
|
||||
&mut slot_to_hash,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_max_hashes() {
|
||||
solana_logger::setup();
|
||||
use std::path::PathBuf;
|
||||
use tempfile::TempDir;
|
||||
let keypair = Keypair::new();
|
||||
|
||||
let contact_info = ContactInfo::new_localhost(&keypair.pubkey(), 0);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
|
||||
let trusted_validators = HashSet::new();
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let mut hashes = vec![];
|
||||
for i in 0..MAX_SNAPSHOT_HASHES + 1 {
|
||||
let snapshot_links = TempDir::new().unwrap();
|
||||
let snapshot_package = SnapshotPackage {
|
||||
hash: hash(&[i as u8]),
|
||||
root: 100 + i as u64,
|
||||
slot_deltas: vec![],
|
||||
snapshot_links,
|
||||
tar_output_file: PathBuf::from("."),
|
||||
storages: vec![],
|
||||
};
|
||||
|
||||
AccountsHashVerifier::process_snapshot(
|
||||
snapshot_package,
|
||||
&cluster_info,
|
||||
&Some(trusted_validators.clone()),
|
||||
false,
|
||||
&None,
|
||||
&mut hashes,
|
||||
&exit,
|
||||
0,
|
||||
);
|
||||
}
|
||||
let cluster_hashes = cluster_info
|
||||
.get_accounts_hash_for_node(&keypair.pubkey(), |c| c.clone())
|
||||
.unwrap();
|
||||
info!("{:?}", cluster_hashes);
|
||||
assert_eq!(hashes.len(), MAX_SNAPSHOT_HASHES);
|
||||
assert_eq!(cluster_hashes.len(), MAX_SNAPSHOT_HASHES);
|
||||
assert_eq!(cluster_hashes[0], (101, hash(&[1])));
|
||||
assert_eq!(
|
||||
cluster_hashes[MAX_SNAPSHOT_HASHES - 1],
|
||||
(
|
||||
100 + MAX_SNAPSHOT_HASHES as u64,
|
||||
hash(&[MAX_SNAPSHOT_HASHES as u8])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
//! can do its processing in parallel with signature verification on the GPU.
|
||||
use crate::{
|
||||
cluster_info::ClusterInfo,
|
||||
packet::{limited_deserialize, Packet, Packets, PACKETS_PER_BATCH},
|
||||
poh_recorder::{PohRecorder, PohRecorderError, WorkingBankEntry},
|
||||
poh_service::PohService,
|
||||
};
|
||||
@@ -17,7 +16,11 @@ use solana_ledger::{
|
||||
};
|
||||
use solana_measure::{measure::Measure, thread_mem_usage};
|
||||
use solana_metrics::{inc_new_counter_debug, inc_new_counter_info, inc_new_counter_warn};
|
||||
use solana_perf::{cuda_runtime::PinnedVec, perf_libs};
|
||||
use solana_perf::{
|
||||
cuda_runtime::PinnedVec,
|
||||
packet::{limited_deserialize, Packet, Packets, PACKETS_PER_BATCH},
|
||||
perf_libs,
|
||||
};
|
||||
use solana_runtime::{
|
||||
accounts_db::ErrorCounters,
|
||||
bank::{Bank, TransactionBalancesSet, TransactionProcessResult},
|
||||
@@ -38,7 +41,7 @@ use std::{
|
||||
net::UdpSocket,
|
||||
sync::atomic::AtomicBool,
|
||||
sync::mpsc::Receiver,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
sync::{Arc, Mutex},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
time::Duration,
|
||||
time::Instant,
|
||||
@@ -73,7 +76,7 @@ impl BankingStage {
|
||||
/// Create the stage using `bank`. Exit when `verified_receiver` is dropped.
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &Arc<ClusterInfo>,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
verified_receiver: CrossbeamReceiver<Vec<Packets>>,
|
||||
verified_vote_receiver: CrossbeamReceiver<Vec<Packets>>,
|
||||
@@ -90,7 +93,7 @@ impl BankingStage {
|
||||
}
|
||||
|
||||
fn new_num_threads(
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &Arc<ClusterInfo>,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
verified_receiver: CrossbeamReceiver<Vec<Packets>>,
|
||||
verified_vote_receiver: CrossbeamReceiver<Vec<Packets>>,
|
||||
@@ -101,7 +104,7 @@ impl BankingStage {
|
||||
// Single thread to generate entries from many banks.
|
||||
// This thread talks to poh_service and broadcasts the entries once they have been recorded.
|
||||
// Once an entry has been recorded, its blockhash is registered with the bank.
|
||||
let my_pubkey = cluster_info.read().unwrap().id();
|
||||
let my_pubkey = cluster_info.id();
|
||||
// Many banks that process transactions in parallel.
|
||||
let bank_thread_hdls: Vec<JoinHandle<()>> = (0..num_threads)
|
||||
.map(|i| {
|
||||
@@ -284,12 +287,12 @@ impl BankingStage {
|
||||
my_pubkey: &Pubkey,
|
||||
socket: &std::net::UdpSocket,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
buffered_packets: &mut Vec<PacketsAndOffsets>,
|
||||
enable_forwarding: bool,
|
||||
batch_limit: usize,
|
||||
transaction_status_sender: Option<TransactionStatusSender>,
|
||||
) {
|
||||
) -> BufferedPacketsDecision {
|
||||
let (leader_at_slot_offset, poh_has_bank, would_be_leader) = {
|
||||
let poh = poh_recorder.lock().unwrap();
|
||||
(
|
||||
@@ -328,10 +331,7 @@ impl BankingStage {
|
||||
next_leader.map_or((), |leader_pubkey| {
|
||||
let leader_addr = {
|
||||
cluster_info
|
||||
.read()
|
||||
.unwrap()
|
||||
.lookup(&leader_pubkey)
|
||||
.map(|leader| leader.tpu_forwards)
|
||||
.lookup_contact_info(&leader_pubkey, |leader| leader.tpu_forwards)
|
||||
};
|
||||
|
||||
leader_addr.map_or((), |leader_addr| {
|
||||
@@ -349,13 +349,14 @@ impl BankingStage {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
decision
|
||||
}
|
||||
|
||||
pub fn process_loop(
|
||||
my_pubkey: Pubkey,
|
||||
verified_receiver: &CrossbeamReceiver<Vec<Packets>>,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
recv_start: &mut Instant,
|
||||
enable_forwarding: bool,
|
||||
id: u32,
|
||||
@@ -365,8 +366,8 @@ impl BankingStage {
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
let mut buffered_packets = vec![];
|
||||
loop {
|
||||
if !buffered_packets.is_empty() {
|
||||
Self::process_buffered_packets(
|
||||
while !buffered_packets.is_empty() {
|
||||
let decision = Self::process_buffered_packets(
|
||||
&my_pubkey,
|
||||
&socket,
|
||||
poh_recorder,
|
||||
@@ -376,6 +377,11 @@ impl BankingStage {
|
||||
batch_limit,
|
||||
transaction_status_sender.clone(),
|
||||
);
|
||||
if decision == BufferedPacketsDecision::Hold {
|
||||
// If we are waiting on a new bank,
|
||||
// check the receiver for more transactions/for exiting
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let recv_timeout = if !buffered_packets.is_empty() {
|
||||
@@ -1009,20 +1015,18 @@ pub fn create_test_recorder(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
cluster_info::Node,
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
packet::to_packets,
|
||||
poh_recorder::WorkingBank,
|
||||
cluster_info::Node, poh_recorder::WorkingBank,
|
||||
transaction_status_service::TransactionStatusService,
|
||||
};
|
||||
use crossbeam_channel::unbounded;
|
||||
use itertools::Itertools;
|
||||
use solana_client::rpc_response::{RpcEncodedTransaction, RpcTransactionWithStatusMeta};
|
||||
use solana_ledger::{
|
||||
blockstore::entries_to_test_shreds,
|
||||
entry::{next_entry, Entry, EntrySlice},
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
get_tmp_ledger_path,
|
||||
};
|
||||
use solana_perf::packet::to_packets;
|
||||
use solana_runtime::bank::HashAgeKind;
|
||||
use solana_sdk::{
|
||||
instruction::InstructionError,
|
||||
@@ -1030,6 +1034,7 @@ mod tests {
|
||||
system_transaction,
|
||||
transaction::TransactionError,
|
||||
};
|
||||
use solana_transaction_status::{EncodedTransaction, TransactionWithStatusMeta};
|
||||
use std::{sync::atomic::Ordering, thread::sleep};
|
||||
|
||||
#[test]
|
||||
@@ -1047,7 +1052,7 @@ mod tests {
|
||||
let (exit, poh_recorder, poh_service, _entry_receiever) =
|
||||
create_test_recorder(&bank, &blockstore, None);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
@@ -1087,7 +1092,7 @@ mod tests {
|
||||
let (exit, poh_recorder, poh_service, entry_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, Some(poh_config));
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
@@ -1150,7 +1155,7 @@ mod tests {
|
||||
let (exit, poh_recorder, poh_service, entry_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, Some(poh_config));
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
@@ -1291,7 +1296,7 @@ mod tests {
|
||||
create_test_recorder(&bank, &blockstore, Some(poh_config));
|
||||
let cluster_info =
|
||||
ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let _banking_stage = BankingStage::new_num_threads(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
@@ -1975,18 +1980,28 @@ mod tests {
|
||||
let confirmed_block = blockstore.get_confirmed_block(bank.slot(), None).unwrap();
|
||||
assert_eq!(confirmed_block.transactions.len(), 3);
|
||||
|
||||
for RpcTransactionWithStatusMeta { transaction, meta } in
|
||||
for TransactionWithStatusMeta { transaction, meta } in
|
||||
confirmed_block.transactions.into_iter()
|
||||
{
|
||||
if let RpcEncodedTransaction::Json(transaction) = transaction {
|
||||
if let EncodedTransaction::Json(transaction) = transaction {
|
||||
if transaction.signatures[0] == success_signature.to_string() {
|
||||
assert_eq!(meta.unwrap().status, Ok(()));
|
||||
let meta = meta.unwrap();
|
||||
assert_eq!(meta.err, None);
|
||||
assert_eq!(meta.status, Ok(()));
|
||||
} else if transaction.signatures[0] == ix_error_signature.to_string() {
|
||||
let meta = meta.unwrap();
|
||||
assert_eq!(
|
||||
meta.unwrap().status,
|
||||
meta.err,
|
||||
Some(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::Custom(1)
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
meta.status,
|
||||
Err(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::CustomError(1)
|
||||
InstructionError::Custom(1)
|
||||
))
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
//! The `blockstream` module provides a method for streaming entries out via a
|
||||
//! local unix socket, to provide client services such as a block explorer with
|
||||
//! real-time access to entries.
|
||||
|
||||
use bincode::serialize;
|
||||
use chrono::{SecondsFormat, Utc};
|
||||
use serde_json::json;
|
||||
use solana_ledger::entry::Entry;
|
||||
use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey};
|
||||
use std::cell::RefCell;
|
||||
use std::io::Result;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub trait EntryWriter: std::fmt::Debug {
|
||||
fn write(&self, payload: String) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EntryVec {
|
||||
values: RefCell<Vec<String>>,
|
||||
}
|
||||
|
||||
impl EntryWriter for EntryVec {
|
||||
fn write(&self, payload: String) -> Result<()> {
|
||||
self.values.borrow_mut().push(payload);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl EntryVec {
|
||||
pub fn new() -> Self {
|
||||
EntryVec {
|
||||
values: RefCell::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> Vec<String> {
|
||||
self.values.borrow().clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EntrySocket {
|
||||
unix_socket: PathBuf,
|
||||
}
|
||||
|
||||
impl EntryWriter for EntrySocket {
|
||||
#[cfg(not(windows))]
|
||||
fn write(&self, payload: String) -> Result<()> {
|
||||
use std::io::prelude::*;
|
||||
use std::net::Shutdown;
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
const MESSAGE_TERMINATOR: &str = "\n";
|
||||
|
||||
let mut socket = UnixStream::connect(&self.unix_socket)?;
|
||||
socket.write_all(payload.as_bytes())?;
|
||||
socket.write_all(MESSAGE_TERMINATOR.as_bytes())?;
|
||||
socket.shutdown(Shutdown::Write)?;
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(windows)]
|
||||
fn write(&self, _payload: String) -> Result<()> {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"EntryWriter::write() not implemented for windows",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BlockstreamEvents {
|
||||
fn emit_entry_event(
|
||||
&self,
|
||||
slot: Slot,
|
||||
tick_height: u64,
|
||||
leader_pubkey: &Pubkey,
|
||||
entries: &Entry,
|
||||
) -> Result<()>;
|
||||
fn emit_block_event(
|
||||
&self,
|
||||
slot: Slot,
|
||||
tick_height: u64,
|
||||
leader_pubkey: &Pubkey,
|
||||
blockhash: Hash,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Blockstream<T: EntryWriter> {
|
||||
pub output: T,
|
||||
}
|
||||
|
||||
impl<T> BlockstreamEvents for Blockstream<T>
|
||||
where
|
||||
T: EntryWriter,
|
||||
{
|
||||
fn emit_entry_event(
|
||||
&self,
|
||||
slot: Slot,
|
||||
tick_height: u64,
|
||||
leader_pubkey: &Pubkey,
|
||||
entry: &Entry,
|
||||
) -> Result<()> {
|
||||
let transactions: Vec<Vec<u8>> = serialize_transactions(entry);
|
||||
let stream_entry = json!({
|
||||
"num_hashes": entry.num_hashes,
|
||||
"hash": entry.hash,
|
||||
"transactions": transactions
|
||||
});
|
||||
let json_entry = serde_json::to_string(&stream_entry)?;
|
||||
let payload = format!(
|
||||
r#"{{"dt":"{}","t":"entry","s":{},"h":{},"l":"{:?}","entry":{}}}"#,
|
||||
Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true),
|
||||
slot,
|
||||
tick_height,
|
||||
leader_pubkey,
|
||||
json_entry,
|
||||
);
|
||||
self.output.write(payload)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_block_event(
|
||||
&self,
|
||||
slot: Slot,
|
||||
tick_height: u64,
|
||||
leader_pubkey: &Pubkey,
|
||||
blockhash: Hash,
|
||||
) -> Result<()> {
|
||||
let payload = format!(
|
||||
r#"{{"dt":"{}","t":"block","s":{},"h":{},"l":"{:?}","hash":"{:?}"}}"#,
|
||||
Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true),
|
||||
slot,
|
||||
tick_height,
|
||||
leader_pubkey,
|
||||
blockhash,
|
||||
);
|
||||
self.output.write(payload)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub type SocketBlockstream = Blockstream<EntrySocket>;
|
||||
|
||||
impl SocketBlockstream {
|
||||
pub fn new(unix_socket: &Path) -> Self {
|
||||
Blockstream {
|
||||
output: EntrySocket {
|
||||
unix_socket: unix_socket.to_path_buf(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type MockBlockstream = Blockstream<EntryVec>;
|
||||
|
||||
impl MockBlockstream {
|
||||
pub fn new(_: &Path) -> Self {
|
||||
Blockstream {
|
||||
output: EntryVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> Vec<String> {
|
||||
self.output.entries()
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_transactions(entry: &Entry) -> Vec<Vec<u8>> {
|
||||
entry
|
||||
.transactions
|
||||
.iter()
|
||||
.map(|tx| serialize(&tx).unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use serde_json::Value;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::system_transaction;
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn test_serialize_transactions() {
|
||||
let entry = Entry::new(&Hash::default(), 1, vec![]);
|
||||
let empty_vec: Vec<Vec<u8>> = vec![];
|
||||
assert_eq!(serialize_transactions(&entry), empty_vec);
|
||||
|
||||
let keypair0 = Keypair::new();
|
||||
let keypair1 = Keypair::new();
|
||||
let tx0 = system_transaction::transfer(&keypair0, &keypair1.pubkey(), 1, Hash::default());
|
||||
let tx1 = system_transaction::transfer(&keypair1, &keypair0.pubkey(), 2, Hash::default());
|
||||
let serialized_tx0 = serialize(&tx0).unwrap();
|
||||
let serialized_tx1 = serialize(&tx1).unwrap();
|
||||
let entry = Entry::new(&Hash::default(), 1, vec![tx0, tx1]);
|
||||
assert_eq!(
|
||||
serialize_transactions(&entry),
|
||||
vec![serialized_tx0, serialized_tx1]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blockstream() -> () {
|
||||
let blockstream = MockBlockstream::new(&PathBuf::from("test_stream"));
|
||||
let ticks_per_slot = 5;
|
||||
|
||||
let mut blockhash = Hash::default();
|
||||
let mut entries = Vec::new();
|
||||
let mut expected_entries = Vec::new();
|
||||
|
||||
let tick_height_initial = 1;
|
||||
let tick_height_final = tick_height_initial + ticks_per_slot + 2;
|
||||
let mut curr_slot = 0;
|
||||
let leader_pubkey = Pubkey::new_rand();
|
||||
|
||||
for tick_height in tick_height_initial..=tick_height_final {
|
||||
if tick_height == 5 {
|
||||
blockstream
|
||||
.emit_block_event(curr_slot, tick_height, &leader_pubkey, blockhash)
|
||||
.unwrap();
|
||||
curr_slot += 1;
|
||||
}
|
||||
let entry = Entry::new(&mut blockhash, 1, vec![]); // just ticks
|
||||
blockhash = entry.hash;
|
||||
blockstream
|
||||
.emit_entry_event(curr_slot, tick_height, &leader_pubkey, &entry)
|
||||
.unwrap();
|
||||
expected_entries.push(entry.clone());
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
blockstream.entries().len() as u64,
|
||||
// one entry per tick (1..=N+2) is +3, plus one block
|
||||
ticks_per_slot + 3 + 1
|
||||
);
|
||||
|
||||
let mut j = 0;
|
||||
let mut matched_entries = 0;
|
||||
let mut matched_slots = HashSet::new();
|
||||
let mut matched_blocks = HashSet::new();
|
||||
|
||||
for item in blockstream.entries() {
|
||||
let json: Value = serde_json::from_str(&item).unwrap();
|
||||
let dt_str = json["dt"].as_str().unwrap();
|
||||
|
||||
// Ensure `ts` field parses as valid DateTime
|
||||
let _dt: DateTime<FixedOffset> = DateTime::parse_from_rfc3339(dt_str).unwrap();
|
||||
|
||||
let item_type = json["t"].as_str().unwrap();
|
||||
match item_type {
|
||||
"block" => {
|
||||
let hash = json["hash"].to_string();
|
||||
matched_blocks.insert(hash);
|
||||
}
|
||||
|
||||
"entry" => {
|
||||
let slot = json["s"].as_u64().unwrap();
|
||||
matched_slots.insert(slot);
|
||||
let entry_obj = json["entry"].clone();
|
||||
let entry: Entry = serde_json::from_value(entry_obj).unwrap();
|
||||
|
||||
assert_eq!(entry, expected_entries[j]);
|
||||
matched_entries += 1;
|
||||
j += 1;
|
||||
}
|
||||
|
||||
_ => {
|
||||
assert!(false, "unknown item type {}", item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(matched_entries, expected_entries.len());
|
||||
assert_eq!(matched_slots.len(), 2);
|
||||
assert_eq!(matched_blocks.len(), 1);
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
//! The `blockstream_service` implements optional streaming of entries and block metadata
|
||||
//! using the `blockstream` module, providing client services such as a block explorer with
|
||||
//! real-time access to entries.
|
||||
|
||||
use crate::blockstream::BlockstreamEvents;
|
||||
#[cfg(test)]
|
||||
use crate::blockstream::MockBlockstream as Blockstream;
|
||||
#[cfg(not(test))]
|
||||
use crate::blockstream::SocketBlockstream as Blockstream;
|
||||
use crate::result::{Error, Result};
|
||||
use solana_ledger::blockstore::Blockstore;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{Receiver, RecvTimeoutError};
|
||||
use std::sync::Arc;
|
||||
use std::thread::{self, Builder, JoinHandle};
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct BlockstreamService {
|
||||
t_blockstream: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl BlockstreamService {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(
|
||||
slot_full_receiver: Receiver<(u64, Pubkey)>,
|
||||
blockstore: Arc<Blockstore>,
|
||||
unix_socket: &Path,
|
||||
exit: &Arc<AtomicBool>,
|
||||
) -> Self {
|
||||
let mut blockstream = Blockstream::new(unix_socket);
|
||||
let exit = exit.clone();
|
||||
let t_blockstream = Builder::new()
|
||||
.name("solana-blockstream".to_string())
|
||||
.spawn(move || loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
if let Err(e) =
|
||||
Self::process_entries(&slot_full_receiver, &blockstore, &mut blockstream)
|
||||
{
|
||||
match e {
|
||||
Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break,
|
||||
Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (),
|
||||
_ => info!("Error from process_entries: {:?}", e),
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
Self { t_blockstream }
|
||||
}
|
||||
fn process_entries(
|
||||
slot_full_receiver: &Receiver<(u64, Pubkey)>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
blockstream: &mut Blockstream,
|
||||
) -> Result<()> {
|
||||
let timeout = Duration::new(1, 0);
|
||||
let (slot, slot_leader) = slot_full_receiver.recv_timeout(timeout)?;
|
||||
|
||||
// Slot might not exist due to LedgerCleanupService, check first
|
||||
let blockstore_meta = blockstore.meta(slot).unwrap();
|
||||
if let Some(blockstore_meta) = blockstore_meta {
|
||||
// Return error to main loop. Thread won't exit, will just log the error
|
||||
let entries = blockstore.get_slot_entries(slot, 0, None)?;
|
||||
let _parent_slot = if slot == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(blockstore_meta.parent_slot)
|
||||
};
|
||||
let ticks_per_slot = entries.iter().filter(|entry| entry.is_tick()).count() as u64;
|
||||
let mut tick_height = ticks_per_slot * slot;
|
||||
|
||||
for (i, entry) in entries.iter().enumerate() {
|
||||
if entry.is_tick() {
|
||||
tick_height += 1;
|
||||
}
|
||||
blockstream
|
||||
.emit_entry_event(slot, tick_height, &slot_leader, &entry)
|
||||
.unwrap_or_else(|e| {
|
||||
debug!("Blockstream error: {:?}, {:?}", e, blockstream.output);
|
||||
});
|
||||
if i == entries.len() - 1 {
|
||||
blockstream
|
||||
.emit_block_event(slot, tick_height, &slot_leader, entry.hash)
|
||||
.unwrap_or_else(|e| {
|
||||
debug!("Blockstream error: {:?}, {:?}", e, blockstream.output);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn join(self) -> thread::Result<()> {
|
||||
self.t_blockstream.join()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use bincode::{deserialize, serialize};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use serde_json::Value;
|
||||
use solana_ledger::create_new_tmp_ledger;
|
||||
use solana_ledger::entry::{create_ticks, Entry};
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::system_transaction;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
fn test_blockstream_service_process_entries() {
|
||||
let ticks_per_slot = 5;
|
||||
let leader_pubkey = Pubkey::new_rand();
|
||||
|
||||
// Set up genesis config and blockstore
|
||||
let GenesisConfigInfo {
|
||||
mut genesis_config, ..
|
||||
} = create_genesis_config(1000);
|
||||
genesis_config.ticks_per_slot = ticks_per_slot;
|
||||
|
||||
let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_config);
|
||||
let blockstore = Blockstore::open(&ledger_path).unwrap();
|
||||
|
||||
// Set up blockstream
|
||||
let mut blockstream = Blockstream::new(&PathBuf::from("test_stream"));
|
||||
|
||||
// Set up dummy channel to receive a full-slot notification
|
||||
let (slot_full_sender, slot_full_receiver) = channel();
|
||||
|
||||
// Create entries - 4 ticks + 1 populated entry + 1 tick
|
||||
let mut entries = create_ticks(4, 0, Hash::default());
|
||||
|
||||
let keypair = Keypair::new();
|
||||
let mut blockhash = entries[3].hash;
|
||||
let tx = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, Hash::default());
|
||||
let entry = Entry::new(&mut blockhash, 1, vec![tx]);
|
||||
blockhash = entry.hash;
|
||||
entries.push(entry);
|
||||
let final_tick = create_ticks(1, 0, blockhash);
|
||||
entries.extend_from_slice(&final_tick);
|
||||
|
||||
let expected_entries = entries.clone();
|
||||
let expected_tick_heights = [6, 7, 8, 9, 9, 10];
|
||||
|
||||
blockstore
|
||||
.write_entries(
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
ticks_per_slot,
|
||||
None,
|
||||
true,
|
||||
&Arc::new(Keypair::new()),
|
||||
entries,
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
slot_full_sender.send((1, leader_pubkey)).unwrap();
|
||||
BlockstreamService::process_entries(
|
||||
&slot_full_receiver,
|
||||
&Arc::new(blockstore),
|
||||
&mut blockstream,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(blockstream.entries().len(), 7);
|
||||
|
||||
let (entry_events, block_events): (Vec<Value>, Vec<Value>) = blockstream
|
||||
.entries()
|
||||
.iter()
|
||||
.map(|item| {
|
||||
let json: Value = serde_json::from_str(&item).unwrap();
|
||||
let dt_str = json["dt"].as_str().unwrap();
|
||||
// Ensure `ts` field parses as valid DateTime
|
||||
let _dt: DateTime<FixedOffset> = DateTime::parse_from_rfc3339(dt_str).unwrap();
|
||||
json
|
||||
})
|
||||
.partition(|json| {
|
||||
let item_type = json["t"].as_str().unwrap();
|
||||
item_type == "entry"
|
||||
});
|
||||
for (i, json) in entry_events.iter().enumerate() {
|
||||
let height = json["h"].as_u64().unwrap();
|
||||
assert_eq!(height, expected_tick_heights[i]);
|
||||
let entry_obj = json["entry"].clone();
|
||||
let tx = entry_obj["transactions"].as_array().unwrap();
|
||||
let entry: Entry;
|
||||
if tx.len() == 0 {
|
||||
entry = serde_json::from_value(entry_obj).unwrap();
|
||||
} else {
|
||||
let entry_json = entry_obj.as_object().unwrap();
|
||||
entry = Entry {
|
||||
num_hashes: entry_json.get("num_hashes").unwrap().as_u64().unwrap(),
|
||||
hash: serde_json::from_value(entry_json.get("hash").unwrap().clone()).unwrap(),
|
||||
transactions: entry_json
|
||||
.get("transactions")
|
||||
.unwrap()
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(j, tx)| {
|
||||
let tx_vec: Vec<u8> = serde_json::from_value(tx.clone()).unwrap();
|
||||
// Check explicitly that transaction matches bincode-serialized format
|
||||
assert_eq!(
|
||||
tx_vec,
|
||||
serialize(&expected_entries[i].transactions[j]).unwrap()
|
||||
);
|
||||
deserialize(&tx_vec).unwrap()
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
}
|
||||
assert_eq!(entry, expected_entries[i]);
|
||||
}
|
||||
for json in block_events {
|
||||
let slot = json["s"].as_u64().unwrap();
|
||||
assert_eq!(1, slot);
|
||||
let height = json["h"].as_u64().unwrap();
|
||||
assert_eq!(2 * ticks_per_slot, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,51 @@
|
||||
//! A stage to broadcast data from a leader node to validators
|
||||
use self::broadcast_fake_shreds_run::BroadcastFakeShredsRun;
|
||||
use self::fail_entry_verification_broadcast_run::FailEntryVerificationBroadcastRun;
|
||||
use self::standard_broadcast_run::StandardBroadcastRun;
|
||||
use crate::cluster_info::{ClusterInfo, ClusterInfoError};
|
||||
use crate::poh_recorder::WorkingBankEntry;
|
||||
use crate::result::{Error, Result};
|
||||
use solana_ledger::blockstore::Blockstore;
|
||||
use solana_ledger::shred::Shred;
|
||||
use solana_ledger::staking_utils;
|
||||
use self::{
|
||||
broadcast_fake_shreds_run::BroadcastFakeShredsRun, broadcast_metrics::*,
|
||||
fail_entry_verification_broadcast_run::FailEntryVerificationBroadcastRun,
|
||||
standard_broadcast_run::StandardBroadcastRun,
|
||||
};
|
||||
use crate::contact_info::ContactInfo;
|
||||
use crate::crds_gossip_pull::CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS;
|
||||
use crate::weighted_shuffle::weighted_best;
|
||||
use crate::{
|
||||
cluster_info::{ClusterInfo, ClusterInfoError},
|
||||
poh_recorder::WorkingBankEntry,
|
||||
result::{Error, Result},
|
||||
};
|
||||
use crossbeam_channel::{
|
||||
Receiver as CrossbeamReceiver, RecvTimeoutError as CrossbeamRecvTimeoutError,
|
||||
Sender as CrossbeamSender,
|
||||
};
|
||||
use solana_ledger::{blockstore::Blockstore, shred::Shred, staking_utils};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_metrics::{inc_new_counter_error, inc_new_counter_info};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::collections::HashMap;
|
||||
use std::net::UdpSocket;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{channel, Receiver, RecvError, RecvTimeoutError, Sender};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread::{self, Builder, JoinHandle};
|
||||
use std::time::Instant;
|
||||
|
||||
pub const NUM_INSERT_THREADS: usize = 2;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::timing::timestamp;
|
||||
use solana_sdk::{clock::Slot, pubkey::Pubkey};
|
||||
use solana_streamer::sendmmsg::send_mmsg;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::UdpSocket,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
sync::mpsc::{channel, Receiver, RecvError, RecvTimeoutError, Sender},
|
||||
sync::{Arc, Mutex},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
mod broadcast_fake_shreds_run;
|
||||
pub(crate) mod broadcast_metrics;
|
||||
pub(crate) mod broadcast_utils;
|
||||
mod fail_entry_verification_broadcast_run;
|
||||
mod standard_broadcast_run;
|
||||
|
||||
pub(crate) const NUM_INSERT_THREADS: usize = 2;
|
||||
pub(crate) type RetransmitSlotsSender = CrossbeamSender<HashMap<Slot, Arc<Bank>>>;
|
||||
pub(crate) type RetransmitSlotsReceiver = CrossbeamReceiver<HashMap<Slot, Arc<Bank>>>;
|
||||
pub(crate) type RecordReceiver = Receiver<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>;
|
||||
pub(crate) type TransmitReceiver = Receiver<(TransmitShreds, Option<BroadcastShredBatchInfo>)>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum BroadcastStageReturnType {
|
||||
ChannelDisconnected,
|
||||
@@ -41,18 +62,20 @@ impl BroadcastStageType {
|
||||
pub fn new_broadcast_stage(
|
||||
&self,
|
||||
sock: Vec<UdpSocket>,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: Arc<ClusterInfo>,
|
||||
receiver: Receiver<WorkingBankEntry>,
|
||||
retransmit_slots_receiver: RetransmitSlotsReceiver,
|
||||
exit_sender: &Arc<AtomicBool>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
shred_version: u16,
|
||||
) -> BroadcastStage {
|
||||
let keypair = cluster_info.read().unwrap().keypair.clone();
|
||||
let keypair = cluster_info.keypair.clone();
|
||||
match self {
|
||||
BroadcastStageType::Standard => BroadcastStage::new(
|
||||
sock,
|
||||
cluster_info,
|
||||
receiver,
|
||||
retransmit_slots_receiver,
|
||||
exit_sender,
|
||||
blockstore,
|
||||
StandardBroadcastRun::new(keypair, shred_version),
|
||||
@@ -62,6 +85,7 @@ impl BroadcastStageType {
|
||||
sock,
|
||||
cluster_info,
|
||||
receiver,
|
||||
retransmit_slots_receiver,
|
||||
exit_sender,
|
||||
blockstore,
|
||||
FailEntryVerificationBroadcastRun::new(keypair, shred_version),
|
||||
@@ -71,6 +95,7 @@ impl BroadcastStageType {
|
||||
sock,
|
||||
cluster_info,
|
||||
receiver,
|
||||
retransmit_slots_receiver,
|
||||
exit_sender,
|
||||
blockstore,
|
||||
BroadcastFakeShredsRun::new(keypair, 0, shred_version),
|
||||
@@ -79,24 +104,24 @@ impl BroadcastStageType {
|
||||
}
|
||||
}
|
||||
|
||||
type TransmitShreds = (Option<Arc<HashMap<Pubkey, u64>>>, Arc<Vec<Shred>>);
|
||||
pub type TransmitShreds = (Option<Arc<HashMap<Pubkey, u64>>>, Arc<Vec<Shred>>);
|
||||
trait BroadcastRun {
|
||||
fn run(
|
||||
&mut self,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receiver: &Receiver<WorkingBankEntry>,
|
||||
socket_sender: &Sender<TransmitShreds>,
|
||||
blockstore_sender: &Sender<Arc<Vec<Shred>>>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
) -> Result<()>;
|
||||
fn transmit(
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<TransmitShreds>>>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<TransmitReceiver>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
sock: &UdpSocket,
|
||||
) -> Result<()>;
|
||||
fn record(
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<Arc<Vec<Shred>>>>>,
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<RecordReceiver>>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
) -> Result<()>;
|
||||
}
|
||||
@@ -128,32 +153,34 @@ impl BroadcastStage {
|
||||
fn run(
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receiver: &Receiver<WorkingBankEntry>,
|
||||
socket_sender: &Sender<TransmitShreds>,
|
||||
blockstore_sender: &Sender<Arc<Vec<Shred>>>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
mut broadcast_stage_run: impl BroadcastRun,
|
||||
) -> BroadcastStageReturnType {
|
||||
loop {
|
||||
let res =
|
||||
broadcast_stage_run.run(blockstore, receiver, socket_sender, blockstore_sender);
|
||||
let res = Self::handle_error(res);
|
||||
let res = Self::handle_error(res, "run");
|
||||
if let Some(res) = res {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn handle_error(r: Result<()>) -> Option<BroadcastStageReturnType> {
|
||||
fn handle_error(r: Result<()>, name: &str) -> Option<BroadcastStageReturnType> {
|
||||
if let Err(e) = r {
|
||||
match e {
|
||||
Error::RecvTimeoutError(RecvTimeoutError::Disconnected)
|
||||
| Error::SendError
|
||||
| Error::RecvError(RecvError) => {
|
||||
| Error::RecvError(RecvError)
|
||||
| Error::CrossbeamRecvTimeoutError(CrossbeamRecvTimeoutError::Disconnected) => {
|
||||
return Some(BroadcastStageReturnType::ChannelDisconnected);
|
||||
}
|
||||
Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (),
|
||||
Error::RecvTimeoutError(RecvTimeoutError::Timeout)
|
||||
| Error::CrossbeamRecvTimeoutError(CrossbeamRecvTimeoutError::Timeout) => (),
|
||||
Error::ClusterInfoError(ClusterInfoError::NoPeers) => (), // TODO: Why are the unit-tests throwing hundreds of these?
|
||||
_ => {
|
||||
inc_new_counter_error!("streamer-broadcaster-error", 1, 1);
|
||||
error!("broadcaster error: {:?}", e);
|
||||
error!("{} broadcaster error: {:?}", name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,8 +205,9 @@ impl BroadcastStage {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
socks: Vec<UdpSocket>,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: Arc<ClusterInfo>,
|
||||
receiver: Receiver<WorkingBankEntry>,
|
||||
retransmit_slots_receiver: RetransmitSlotsReceiver,
|
||||
exit_sender: &Arc<AtomicBool>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
broadcast_stage_run: impl BroadcastRun + Send + 'static + Clone,
|
||||
@@ -189,6 +217,8 @@ impl BroadcastStage {
|
||||
let (socket_sender, socket_receiver) = channel();
|
||||
let (blockstore_sender, blockstore_receiver) = channel();
|
||||
let bs_run = broadcast_stage_run.clone();
|
||||
|
||||
let socket_sender_ = socket_sender.clone();
|
||||
let thread_hdl = Builder::new()
|
||||
.name("solana-broadcaster".to_string())
|
||||
.spawn(move || {
|
||||
@@ -196,7 +226,7 @@ impl BroadcastStage {
|
||||
Self::run(
|
||||
&btree,
|
||||
&receiver,
|
||||
&socket_sender,
|
||||
&socket_sender_,
|
||||
&blockstore_sender,
|
||||
bs_run,
|
||||
)
|
||||
@@ -206,13 +236,13 @@ impl BroadcastStage {
|
||||
let socket_receiver = Arc::new(Mutex::new(socket_receiver));
|
||||
for sock in socks.into_iter() {
|
||||
let socket_receiver = socket_receiver.clone();
|
||||
let bs_transmit = broadcast_stage_run.clone();
|
||||
let mut bs_transmit = broadcast_stage_run.clone();
|
||||
let cluster_info = cluster_info.clone();
|
||||
let t = Builder::new()
|
||||
.name("solana-broadcaster-transmit".to_string())
|
||||
.spawn(move || loop {
|
||||
let res = bs_transmit.transmit(&socket_receiver, &cluster_info, &sock);
|
||||
let res = Self::handle_error(res);
|
||||
let res = Self::handle_error(res, "solana-broadcaster-transmit");
|
||||
if let Some(res) = res {
|
||||
return res;
|
||||
}
|
||||
@@ -223,13 +253,13 @@ impl BroadcastStage {
|
||||
let blockstore_receiver = Arc::new(Mutex::new(blockstore_receiver));
|
||||
for _ in 0..NUM_INSERT_THREADS {
|
||||
let blockstore_receiver = blockstore_receiver.clone();
|
||||
let bs_record = broadcast_stage_run.clone();
|
||||
let mut bs_record = broadcast_stage_run.clone();
|
||||
let btree = blockstore.clone();
|
||||
let t = Builder::new()
|
||||
.name("solana-broadcaster-record".to_string())
|
||||
.spawn(move || loop {
|
||||
let res = bs_record.record(&blockstore_receiver, &btree);
|
||||
let res = Self::handle_error(res);
|
||||
let res = Self::handle_error(res, "solana-broadcaster-record");
|
||||
if let Some(res) = res {
|
||||
return res;
|
||||
}
|
||||
@@ -238,9 +268,68 @@ impl BroadcastStage {
|
||||
thread_hdls.push(t);
|
||||
}
|
||||
|
||||
let blockstore = blockstore.clone();
|
||||
let retransmit_thread = Builder::new()
|
||||
.name("solana-broadcaster-retransmit".to_string())
|
||||
.spawn(move || loop {
|
||||
if let Some(res) = Self::handle_error(
|
||||
Self::check_retransmit_signals(
|
||||
&blockstore,
|
||||
&retransmit_slots_receiver,
|
||||
&socket_sender,
|
||||
),
|
||||
"solana-broadcaster-retransmit-check_retransmit_signals",
|
||||
) {
|
||||
return res;
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
thread_hdls.push(retransmit_thread);
|
||||
Self { thread_hdls }
|
||||
}
|
||||
|
||||
fn check_retransmit_signals(
|
||||
blockstore: &Blockstore,
|
||||
retransmit_slots_receiver: &RetransmitSlotsReceiver,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
) -> Result<()> {
|
||||
let timer = Duration::from_millis(100);
|
||||
|
||||
// Check for a retransmit signal
|
||||
let mut retransmit_slots = retransmit_slots_receiver.recv_timeout(timer)?;
|
||||
while let Ok(new_retransmit_slots) = retransmit_slots_receiver.try_recv() {
|
||||
retransmit_slots.extend(new_retransmit_slots);
|
||||
}
|
||||
|
||||
for (_, bank) in retransmit_slots.iter() {
|
||||
let bank_epoch = bank.get_leader_schedule_epoch(bank.slot());
|
||||
let stakes = staking_utils::staked_nodes_at_epoch(&bank, bank_epoch);
|
||||
let stakes = stakes.map(Arc::new);
|
||||
let data_shreds = Arc::new(
|
||||
blockstore
|
||||
.get_data_shreds_for_slot(bank.slot(), 0)
|
||||
.expect("My own shreds must be reconstructable"),
|
||||
);
|
||||
|
||||
if !data_shreds.is_empty() {
|
||||
socket_sender.send(((stakes.clone(), data_shreds), None))?;
|
||||
}
|
||||
|
||||
let coding_shreds = Arc::new(
|
||||
blockstore
|
||||
.get_coding_shreds_for_slot(bank.slot(), 0)
|
||||
.expect("My own shreds must be reconstructable"),
|
||||
);
|
||||
|
||||
if !coding_shreds.is_empty() {
|
||||
socket_sender.send(((stakes.clone(), coding_shreds), None))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn join(self) -> thread::Result<BroadcastStageReturnType> {
|
||||
for thread_hdl in self.thread_hdls.into_iter() {
|
||||
let _ = thread_hdl.join();
|
||||
@@ -249,23 +338,238 @@ impl BroadcastStage {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_peer_stats(
|
||||
num_live_peers: i64,
|
||||
broadcast_len: i64,
|
||||
last_datapoint_submit: &Arc<AtomicU64>,
|
||||
) {
|
||||
let now = timestamp();
|
||||
let last = last_datapoint_submit.load(Ordering::Relaxed);
|
||||
if now - last > 1000
|
||||
&& last_datapoint_submit.compare_and_swap(last, now, Ordering::Relaxed) == last
|
||||
{
|
||||
datapoint_info!(
|
||||
"cluster_info-num_nodes",
|
||||
("live_count", num_live_peers, i64),
|
||||
("broadcast_count", broadcast_len, i64)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_broadcast_peers<S: std::hash::BuildHasher>(
|
||||
cluster_info: &ClusterInfo,
|
||||
stakes: Option<Arc<HashMap<Pubkey, u64, S>>>,
|
||||
) -> (Vec<ContactInfo>, Vec<(u64, usize)>) {
|
||||
use crate::cluster_info;
|
||||
let mut peers = cluster_info.tvu_peers();
|
||||
let peers_and_stakes = cluster_info::stake_weight_peers(&mut peers, stakes);
|
||||
(peers, peers_and_stakes)
|
||||
}
|
||||
|
||||
/// broadcast messages from the leader to layer 1 nodes
|
||||
/// # Remarks
|
||||
pub fn broadcast_shreds(
|
||||
s: &UdpSocket,
|
||||
shreds: &Arc<Vec<Shred>>,
|
||||
peers_and_stakes: &[(u64, usize)],
|
||||
peers: &[ContactInfo],
|
||||
last_datapoint_submit: &Arc<AtomicU64>,
|
||||
send_mmsg_total: &mut u64,
|
||||
) -> Result<()> {
|
||||
let broadcast_len = peers_and_stakes.len();
|
||||
if broadcast_len == 0 {
|
||||
update_peer_stats(1, 1, last_datapoint_submit);
|
||||
return Ok(());
|
||||
}
|
||||
let packets: Vec<_> = shreds
|
||||
.iter()
|
||||
.map(|shred| {
|
||||
let broadcast_index = weighted_best(&peers_and_stakes, shred.seed());
|
||||
|
||||
(&shred.payload, &peers[broadcast_index].tvu)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut sent = 0;
|
||||
let mut send_mmsg_time = Measure::start("send_mmsg");
|
||||
while sent < packets.len() {
|
||||
match send_mmsg(s, &packets[sent..]) {
|
||||
Ok(n) => sent += n,
|
||||
Err(e) => {
|
||||
return Err(Error::IO(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
send_mmsg_time.stop();
|
||||
*send_mmsg_total += send_mmsg_time.as_us();
|
||||
|
||||
let num_live_peers = num_live_peers(&peers);
|
||||
update_peer_stats(
|
||||
num_live_peers,
|
||||
broadcast_len as i64 + 1,
|
||||
last_datapoint_submit,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn distance(a: u64, b: u64) -> u64 {
|
||||
if a > b {
|
||||
a - b
|
||||
} else {
|
||||
b - a
|
||||
}
|
||||
}
|
||||
|
||||
fn num_live_peers(peers: &[ContactInfo]) -> i64 {
|
||||
let mut num_live_peers = 1i64;
|
||||
peers.iter().for_each(|p| {
|
||||
// A peer is considered live if they generated their contact info recently
|
||||
if distance(timestamp(), p.wallclock) <= CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS {
|
||||
num_live_peers += 1;
|
||||
}
|
||||
});
|
||||
num_live_peers
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use crate::cluster_info::{ClusterInfo, Node};
|
||||
use crate::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_ledger::entry::create_ticks;
|
||||
use solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path};
|
||||
use crossbeam_channel::unbounded;
|
||||
use solana_ledger::{
|
||||
blockstore::{make_slot_entries, Blockstore},
|
||||
entry::create_ticks,
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
get_tmp_ledger_path,
|
||||
shred::{max_ticks_per_n_shreds, Shredder, RECOMMENDED_FEC_RATE},
|
||||
};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use std::{
|
||||
path::Path, sync::atomic::AtomicBool, sync::mpsc::channel, sync::Arc, thread::sleep,
|
||||
};
|
||||
|
||||
pub fn make_transmit_shreds(
|
||||
slot: Slot,
|
||||
num: u64,
|
||||
stakes: Option<Arc<HashMap<Pubkey, u64>>>,
|
||||
) -> (
|
||||
Vec<Shred>,
|
||||
Vec<Shred>,
|
||||
Vec<TransmitShreds>,
|
||||
Vec<TransmitShreds>,
|
||||
) {
|
||||
let num_entries = max_ticks_per_n_shreds(num, None);
|
||||
let (data_shreds, _) = make_slot_entries(slot, 0, num_entries);
|
||||
let keypair = Arc::new(Keypair::new());
|
||||
let shredder = Shredder::new(slot, 0, RECOMMENDED_FEC_RATE, keypair, 0, 0)
|
||||
.expect("Expected to create a new shredder");
|
||||
|
||||
let coding_shreds = shredder.data_shreds_to_coding_shreds(&data_shreds[0..]);
|
||||
(
|
||||
data_shreds.clone(),
|
||||
coding_shreds.clone(),
|
||||
data_shreds
|
||||
.into_iter()
|
||||
.map(|s| (stakes.clone(), Arc::new(vec![s])))
|
||||
.collect(),
|
||||
coding_shreds
|
||||
.into_iter()
|
||||
.map(|s| (stakes.clone(), Arc::new(vec![s])))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn check_all_shreds_received(
|
||||
transmit_receiver: &TransmitReceiver,
|
||||
mut data_index: u64,
|
||||
mut coding_index: u64,
|
||||
num_expected_data_shreds: u64,
|
||||
num_expected_coding_shreds: u64,
|
||||
) {
|
||||
while let Ok((new_retransmit_slots, _)) = transmit_receiver.try_recv() {
|
||||
if new_retransmit_slots.1[0].is_data() {
|
||||
for data_shred in new_retransmit_slots.1.iter() {
|
||||
assert_eq!(data_shred.index() as u64, data_index);
|
||||
data_index += 1;
|
||||
}
|
||||
} else {
|
||||
assert_eq!(new_retransmit_slots.1[0].index() as u64, coding_index);
|
||||
for coding_shred in new_retransmit_slots.1.iter() {
|
||||
assert_eq!(coding_shred.index() as u64, coding_index);
|
||||
coding_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(num_expected_data_shreds, data_index);
|
||||
assert_eq!(num_expected_coding_shreds, coding_index);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_live_peers() {
|
||||
let mut ci = ContactInfo::default();
|
||||
ci.wallclock = std::u64::MAX;
|
||||
assert_eq!(num_live_peers(&[ci.clone()]), 1);
|
||||
ci.wallclock = timestamp() - 1;
|
||||
assert_eq!(num_live_peers(&[ci.clone()]), 2);
|
||||
ci.wallclock = timestamp() - CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS - 1;
|
||||
assert_eq!(num_live_peers(&[ci]), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duplicate_retransmit_signal() {
|
||||
// Setup
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||
let (transmit_sender, transmit_receiver) = channel();
|
||||
let (retransmit_slots_sender, retransmit_slots_receiver) = unbounded();
|
||||
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(100_000);
|
||||
let bank0 = Arc::new(Bank::new(&genesis_config));
|
||||
|
||||
// Make some shreds
|
||||
let updated_slot = 0;
|
||||
let (all_data_shreds, all_coding_shreds, _, _all_coding_transmit_shreds) =
|
||||
make_transmit_shreds(updated_slot, 10, None);
|
||||
let num_data_shreds = all_data_shreds.len();
|
||||
let num_coding_shreds = all_coding_shreds.len();
|
||||
assert!(num_data_shreds >= 10);
|
||||
|
||||
// Insert all the shreds
|
||||
blockstore
|
||||
.insert_shreds(all_data_shreds, None, true)
|
||||
.unwrap();
|
||||
blockstore
|
||||
.insert_shreds(all_coding_shreds, None, true)
|
||||
.unwrap();
|
||||
|
||||
// Insert duplicate retransmit signal, blocks should
|
||||
// only be retransmitted once
|
||||
retransmit_slots_sender
|
||||
.send(vec![(updated_slot, bank0.clone())].into_iter().collect())
|
||||
.unwrap();
|
||||
retransmit_slots_sender
|
||||
.send(vec![(updated_slot, bank0.clone())].into_iter().collect())
|
||||
.unwrap();
|
||||
BroadcastStage::check_retransmit_signals(
|
||||
&blockstore,
|
||||
&retransmit_slots_receiver,
|
||||
&transmit_sender,
|
||||
)
|
||||
.unwrap();
|
||||
// Check all the data shreds were received only once
|
||||
check_all_shreds_received(
|
||||
&transmit_receiver,
|
||||
0,
|
||||
0,
|
||||
num_data_shreds as u64,
|
||||
num_coding_shreds as u64,
|
||||
);
|
||||
}
|
||||
|
||||
struct MockBroadcastStage {
|
||||
blockstore: Arc<Blockstore>,
|
||||
@@ -277,6 +581,7 @@ mod test {
|
||||
leader_pubkey: &Pubkey,
|
||||
ledger_path: &Path,
|
||||
entry_receiver: Receiver<WorkingBankEntry>,
|
||||
retransmit_slots_receiver: RetransmitSlotsReceiver,
|
||||
) -> MockBroadcastStage {
|
||||
// Make the database ledger
|
||||
let blockstore = Arc::new(Blockstore::open(ledger_path).unwrap());
|
||||
@@ -289,21 +594,22 @@ mod test {
|
||||
let broadcast_buddy = Node::new_localhost_with_pubkey(&buddy_keypair.pubkey());
|
||||
|
||||
// Fill the cluster_info with the buddy's info
|
||||
let mut cluster_info = ClusterInfo::new_with_invalid_keypair(leader_info.info.clone());
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(leader_info.info.clone());
|
||||
cluster_info.insert_info(broadcast_buddy.info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
|
||||
let exit_sender = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
|
||||
let bank = Arc::new(Bank::new(&genesis_config));
|
||||
|
||||
let leader_keypair = cluster_info.read().unwrap().keypair.clone();
|
||||
let leader_keypair = cluster_info.keypair.clone();
|
||||
// Start up the broadcast stage
|
||||
let broadcast_service = BroadcastStage::new(
|
||||
leader_info.sockets.broadcast,
|
||||
cluster_info,
|
||||
entry_receiver,
|
||||
retransmit_slots_receiver,
|
||||
&exit_sender,
|
||||
&blockstore,
|
||||
StandardBroadcastRun::new(leader_keypair, 0),
|
||||
@@ -326,10 +632,12 @@ mod test {
|
||||
let leader_keypair = Keypair::new();
|
||||
|
||||
let (entry_sender, entry_receiver) = channel();
|
||||
let (retransmit_slots_sender, retransmit_slots_receiver) = unbounded();
|
||||
let broadcast_service = setup_dummy_broadcast_service(
|
||||
&leader_keypair.pubkey(),
|
||||
&ledger_path,
|
||||
entry_receiver,
|
||||
retransmit_slots_receiver,
|
||||
);
|
||||
let start_tick_height;
|
||||
let max_tick_height;
|
||||
@@ -348,6 +656,7 @@ mod test {
|
||||
.expect("Expect successful send to broadcast service");
|
||||
}
|
||||
}
|
||||
|
||||
sleep(Duration::from_millis(2000));
|
||||
|
||||
trace!(
|
||||
@@ -364,6 +673,7 @@ mod test {
|
||||
assert_eq!(entries.len(), max_tick_height as usize);
|
||||
|
||||
drop(entry_sender);
|
||||
drop(retransmit_slots_sender);
|
||||
broadcast_service
|
||||
.broadcast_service
|
||||
.join()
|
||||
|
||||
@@ -28,8 +28,8 @@ impl BroadcastRun for BroadcastFakeShredsRun {
|
||||
&mut self,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receiver: &Receiver<WorkingBankEntry>,
|
||||
socket_sender: &Sender<TransmitShreds>,
|
||||
blockstore_sender: &Sender<Arc<Vec<Shred>>>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
) -> Result<()> {
|
||||
// 1) Pull entries from banking stage
|
||||
let receive_results = broadcast_utils::recv_slot_entries(receiver)?;
|
||||
@@ -83,26 +83,32 @@ impl BroadcastRun for BroadcastFakeShredsRun {
|
||||
}
|
||||
|
||||
let data_shreds = Arc::new(data_shreds);
|
||||
blockstore_sender.send(data_shreds.clone())?;
|
||||
blockstore_sender.send((data_shreds.clone(), None))?;
|
||||
|
||||
// 3) Start broadcast step
|
||||
//some indicates fake shreds
|
||||
socket_sender.send((Some(Arc::new(HashMap::new())), Arc::new(fake_data_shreds)))?;
|
||||
socket_sender.send((Some(Arc::new(HashMap::new())), Arc::new(fake_coding_shreds)))?;
|
||||
socket_sender.send((
|
||||
(Some(Arc::new(HashMap::new())), Arc::new(fake_data_shreds)),
|
||||
None,
|
||||
))?;
|
||||
socket_sender.send((
|
||||
(Some(Arc::new(HashMap::new())), Arc::new(fake_coding_shreds)),
|
||||
None,
|
||||
))?;
|
||||
//none indicates real shreds
|
||||
socket_sender.send((None, data_shreds))?;
|
||||
socket_sender.send((None, Arc::new(coding_shreds)))?;
|
||||
socket_sender.send(((None, data_shreds), None))?;
|
||||
socket_sender.send(((None, Arc::new(coding_shreds)), None))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn transmit(
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<TransmitShreds>>>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<TransmitReceiver>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
sock: &UdpSocket,
|
||||
) -> Result<()> {
|
||||
for (stakes, data_shreds) in receiver.lock().unwrap().iter() {
|
||||
let peers = cluster_info.read().unwrap().tvu_peers();
|
||||
for ((stakes, data_shreds), _) in receiver.lock().unwrap().iter() {
|
||||
let peers = cluster_info.tvu_peers();
|
||||
peers.iter().enumerate().for_each(|(i, peer)| {
|
||||
if i <= self.partition && stakes.is_some() {
|
||||
// Send fake shreds to the first N peers
|
||||
@@ -119,11 +125,11 @@ impl BroadcastRun for BroadcastFakeShredsRun {
|
||||
Ok(())
|
||||
}
|
||||
fn record(
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<Arc<Vec<Shred>>>>>,
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<RecordReceiver>>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
) -> Result<()> {
|
||||
for data_shreds in receiver.lock().unwrap().iter() {
|
||||
for (data_shreds, _) in receiver.lock().unwrap().iter() {
|
||||
blockstore.insert_shreds(data_shreds.to_vec(), None, true)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -139,7 +145,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_tvu_peers_ordering() {
|
||||
let mut cluster = ClusterInfo::new_with_invalid_keypair(ContactInfo::new_localhost(
|
||||
let cluster = ClusterInfo::new_with_invalid_keypair(ContactInfo::new_localhost(
|
||||
&Pubkey::new_rand(),
|
||||
0,
|
||||
));
|
||||
|
||||
288
core/src/broadcast_stage/broadcast_metrics.rs
Normal file
288
core/src/broadcast_stage/broadcast_metrics.rs
Normal file
@@ -0,0 +1,288 @@
|
||||
use super::*;
|
||||
|
||||
pub(crate) trait BroadcastStats {
|
||||
fn update(&mut self, new_stats: &Self);
|
||||
fn report_stats(&mut self, slot: Slot, slot_start: Instant);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct BroadcastShredBatchInfo {
|
||||
pub(crate) slot: Slot,
|
||||
pub(crate) num_expected_batches: Option<usize>,
|
||||
pub(crate) slot_start_ts: Instant,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct ProcessShredsStats {
|
||||
// Per-slot elapsed time
|
||||
pub(crate) shredding_elapsed: u64,
|
||||
pub(crate) receive_elapsed: u64,
|
||||
}
|
||||
impl ProcessShredsStats {
|
||||
pub(crate) fn update(&mut self, new_stats: &ProcessShredsStats) {
|
||||
self.shredding_elapsed += new_stats.shredding_elapsed;
|
||||
self.receive_elapsed += new_stats.receive_elapsed;
|
||||
}
|
||||
pub(crate) fn reset(&mut self) {
|
||||
*self = Self::default();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct TransmitShredsStats {
|
||||
pub(crate) transmit_elapsed: u64,
|
||||
pub(crate) send_mmsg_elapsed: u64,
|
||||
pub(crate) get_peers_elapsed: u64,
|
||||
pub(crate) num_shreds: usize,
|
||||
}
|
||||
|
||||
impl BroadcastStats for TransmitShredsStats {
|
||||
fn update(&mut self, new_stats: &TransmitShredsStats) {
|
||||
self.transmit_elapsed += new_stats.transmit_elapsed;
|
||||
self.send_mmsg_elapsed += new_stats.send_mmsg_elapsed;
|
||||
self.get_peers_elapsed += new_stats.get_peers_elapsed;
|
||||
self.num_shreds += new_stats.num_shreds;
|
||||
}
|
||||
fn report_stats(&mut self, slot: Slot, slot_start: Instant) {
|
||||
datapoint_info!(
|
||||
"broadcast-transmit-shreds-stats",
|
||||
("slot", slot as i64, i64),
|
||||
(
|
||||
"end_to_end_elapsed",
|
||||
// `slot_start` signals when the first batch of shreds was
|
||||
// received, used to measure duration of broadcast
|
||||
slot_start.elapsed().as_micros() as i64,
|
||||
i64
|
||||
),
|
||||
("transmit_elapsed", self.transmit_elapsed as i64, i64),
|
||||
("send_mmsg_elapsed", self.send_mmsg_elapsed as i64, i64),
|
||||
("get_peers_elapsed", self.get_peers_elapsed as i64, i64),
|
||||
("num_shreds", self.num_shreds as i64, i64),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct InsertShredsStats {
|
||||
pub(crate) insert_shreds_elapsed: u64,
|
||||
pub(crate) num_shreds: usize,
|
||||
}
|
||||
impl BroadcastStats for InsertShredsStats {
|
||||
fn update(&mut self, new_stats: &InsertShredsStats) {
|
||||
self.insert_shreds_elapsed += new_stats.insert_shreds_elapsed;
|
||||
self.num_shreds += new_stats.num_shreds;
|
||||
}
|
||||
fn report_stats(&mut self, slot: Slot, slot_start: Instant) {
|
||||
datapoint_info!(
|
||||
"broadcast-insert-shreds-stats",
|
||||
("slot", slot as i64, i64),
|
||||
(
|
||||
"end_to_end_elapsed",
|
||||
// `slot_start` signals when the first batch of shreds was
|
||||
// received, used to measure duration of broadcast
|
||||
slot_start.elapsed().as_micros() as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"insert_shreds_elapsed",
|
||||
self.insert_shreds_elapsed as i64,
|
||||
i64
|
||||
),
|
||||
("num_shreds", self.num_shreds as i64, i64),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Tracks metrics of type `T` acrosss multiple threads
|
||||
#[derive(Default)]
|
||||
pub(crate) struct BatchCounter<T: BroadcastStats + Default> {
|
||||
// The number of batches processed across all threads so far
|
||||
num_batches: usize,
|
||||
// Filled in when the last batch of shreds is received,
|
||||
// signals how many batches of shreds to expect
|
||||
num_expected_batches: Option<usize>,
|
||||
broadcast_shred_stats: T,
|
||||
}
|
||||
|
||||
impl<T: BroadcastStats + Default> BatchCounter<T> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn num_batches(&self) -> usize {
|
||||
self.num_batches
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct SlotBroadcastStats<T: BroadcastStats + Default>(HashMap<Slot, BatchCounter<T>>);
|
||||
|
||||
impl<T: BroadcastStats + Default> SlotBroadcastStats<T> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn get(&self, slot: Slot) -> Option<&BatchCounter<T>> {
|
||||
self.0.get(&slot)
|
||||
}
|
||||
pub(crate) fn update(&mut self, new_stats: &T, batch_info: &Option<BroadcastShredBatchInfo>) {
|
||||
if let Some(batch_info) = batch_info {
|
||||
let mut should_delete = false;
|
||||
{
|
||||
let slot_batch_counter = self.0.entry(batch_info.slot).or_default();
|
||||
slot_batch_counter.broadcast_shred_stats.update(new_stats);
|
||||
// Only count the ones where `broadcast_shred_batch_info`.is_some(), because
|
||||
// there could potentially be other `retransmit` slots inserted into the
|
||||
// transmit pipeline (signaled by ReplayStage) that are not created by the
|
||||
// main shredding/broadcast pipeline
|
||||
slot_batch_counter.num_batches += 1;
|
||||
if let Some(num_expected_batches) = batch_info.num_expected_batches {
|
||||
slot_batch_counter.num_expected_batches = Some(num_expected_batches);
|
||||
}
|
||||
if let Some(num_expected_batches) = slot_batch_counter.num_expected_batches {
|
||||
if slot_batch_counter.num_batches == num_expected_batches {
|
||||
slot_batch_counter
|
||||
.broadcast_shred_stats
|
||||
.report_stats(batch_info.slot, batch_info.slot_start_ts);
|
||||
should_delete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if should_delete {
|
||||
self.0
|
||||
.remove(&batch_info.slot)
|
||||
.expect("delete should be successful");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[derive(Default)]
|
||||
struct TestStats {
|
||||
sender: Option<Sender<(usize, Slot, Instant)>>,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl BroadcastStats for TestStats {
|
||||
fn update(&mut self, new_stats: &TestStats) {
|
||||
self.count += new_stats.count;
|
||||
self.sender = new_stats.sender.clone();
|
||||
}
|
||||
fn report_stats(&mut self, slot: Slot, slot_start: Instant) {
|
||||
self.sender
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send((self.count, slot, slot_start))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update() {
|
||||
let start = Instant::now();
|
||||
let mut slot_broadcast_stats = SlotBroadcastStats::default();
|
||||
slot_broadcast_stats.update(
|
||||
&TransmitShredsStats {
|
||||
transmit_elapsed: 1,
|
||||
get_peers_elapsed: 1,
|
||||
send_mmsg_elapsed: 1,
|
||||
num_shreds: 1,
|
||||
},
|
||||
&Some(BroadcastShredBatchInfo {
|
||||
slot: 0,
|
||||
num_expected_batches: Some(2),
|
||||
slot_start_ts: start.clone(),
|
||||
}),
|
||||
);
|
||||
|
||||
// Singular update
|
||||
let slot_0_stats = slot_broadcast_stats.0.get(&0).unwrap();
|
||||
assert_eq!(slot_0_stats.num_batches, 1);
|
||||
assert_eq!(slot_0_stats.num_expected_batches.unwrap(), 2);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.transmit_elapsed, 1);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.get_peers_elapsed, 1);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.send_mmsg_elapsed, 1);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.num_shreds, 1);
|
||||
|
||||
slot_broadcast_stats.update(
|
||||
&TransmitShredsStats {
|
||||
transmit_elapsed: 1,
|
||||
get_peers_elapsed: 1,
|
||||
send_mmsg_elapsed: 1,
|
||||
num_shreds: 1,
|
||||
},
|
||||
&None,
|
||||
);
|
||||
|
||||
// If BroadcastShredBatchInfo == None, then update should be ignored
|
||||
let slot_0_stats = slot_broadcast_stats.0.get(&0).unwrap();
|
||||
assert_eq!(slot_0_stats.num_batches, 1);
|
||||
assert_eq!(slot_0_stats.num_expected_batches.unwrap(), 2);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.transmit_elapsed, 1);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.get_peers_elapsed, 1);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.send_mmsg_elapsed, 1);
|
||||
assert_eq!(slot_0_stats.broadcast_shred_stats.num_shreds, 1);
|
||||
|
||||
// If another batch is given, then total number of batches == num_expected_batches == 2,
|
||||
// so the batch should be purged from the HashMap
|
||||
slot_broadcast_stats.update(
|
||||
&TransmitShredsStats {
|
||||
transmit_elapsed: 1,
|
||||
get_peers_elapsed: 1,
|
||||
send_mmsg_elapsed: 1,
|
||||
num_shreds: 1,
|
||||
},
|
||||
&Some(BroadcastShredBatchInfo {
|
||||
slot: 0,
|
||||
num_expected_batches: None,
|
||||
slot_start_ts: start.clone(),
|
||||
}),
|
||||
);
|
||||
|
||||
assert!(slot_broadcast_stats.0.get(&0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_multi_threaded() {
|
||||
for round in 0..50 {
|
||||
let start = Instant::now();
|
||||
let slot_broadcast_stats = Arc::new(Mutex::new(SlotBroadcastStats::default()));
|
||||
let num_threads = 5;
|
||||
let slot = 0;
|
||||
let (sender, receiver) = channel();
|
||||
let thread_handles: Vec<_> = (0..num_threads)
|
||||
.into_iter()
|
||||
.map(|i| {
|
||||
let slot_broadcast_stats = slot_broadcast_stats.clone();
|
||||
let sender = Some(sender.clone());
|
||||
let test_stats = TestStats { sender, count: 1 };
|
||||
let mut broadcast_batch_info = BroadcastShredBatchInfo {
|
||||
slot,
|
||||
num_expected_batches: None,
|
||||
slot_start_ts: start.clone(),
|
||||
};
|
||||
if i == round % num_threads {
|
||||
broadcast_batch_info.num_expected_batches = Some(num_threads);
|
||||
}
|
||||
Builder::new()
|
||||
.name("test_update_multi_threaded".to_string())
|
||||
.spawn(move || {
|
||||
slot_broadcast_stats
|
||||
.lock()
|
||||
.unwrap()
|
||||
.update(&test_stats, &Some(broadcast_batch_info))
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
for t in thread_handles {
|
||||
t.join().unwrap();
|
||||
}
|
||||
|
||||
assert!(slot_broadcast_stats.lock().unwrap().0.get(&slot).is_none());
|
||||
let (returned_count, returned_slot, returned_instant) = receiver.recv().unwrap();
|
||||
assert_eq!(returned_count, num_threads);
|
||||
assert_eq!(returned_slot, slot);
|
||||
assert_eq!(returned_instant, returned_instant);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ pub(super) fn recv_slot_entries(receiver: &Receiver<WorkingBankEntry>) -> Result
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_ledger::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_sdk::genesis_config::GenesisConfig;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::system_transaction;
|
||||
|
||||
@@ -23,8 +23,8 @@ impl BroadcastRun for FailEntryVerificationBroadcastRun {
|
||||
&mut self,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receiver: &Receiver<WorkingBankEntry>,
|
||||
socket_sender: &Sender<TransmitShreds>,
|
||||
blockstore_sender: &Sender<Arc<Vec<Shred>>>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
) -> Result<()> {
|
||||
// 1) Pull entries from banking stage
|
||||
let mut receive_results = broadcast_utils::recv_slot_entries(receiver)?;
|
||||
@@ -61,38 +61,44 @@ impl BroadcastRun for FailEntryVerificationBroadcastRun {
|
||||
);
|
||||
|
||||
let data_shreds = Arc::new(data_shreds);
|
||||
blockstore_sender.send(data_shreds.clone())?;
|
||||
blockstore_sender.send((data_shreds.clone(), None))?;
|
||||
// 3) Start broadcast step
|
||||
let bank_epoch = bank.get_leader_schedule_epoch(bank.slot());
|
||||
let stakes = staking_utils::staked_nodes_at_epoch(&bank, bank_epoch);
|
||||
|
||||
let stakes = stakes.map(Arc::new);
|
||||
socket_sender.send((stakes.clone(), data_shreds))?;
|
||||
socket_sender.send((stakes, Arc::new(coding_shreds)))?;
|
||||
socket_sender.send(((stakes.clone(), data_shreds), None))?;
|
||||
socket_sender.send(((stakes, Arc::new(coding_shreds)), None))?;
|
||||
Ok(())
|
||||
}
|
||||
fn transmit(
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<TransmitShreds>>>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<TransmitReceiver>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
sock: &UdpSocket,
|
||||
) -> Result<()> {
|
||||
let (stakes, shreds) = receiver.lock().unwrap().recv()?;
|
||||
let all_seeds: Vec<[u8; 32]> = shreds.iter().map(|s| s.seed()).collect();
|
||||
let ((stakes, shreds), _) = receiver.lock().unwrap().recv()?;
|
||||
// Broadcast data
|
||||
let all_shred_bufs: Vec<Vec<u8>> = shreds.to_vec().into_iter().map(|s| s.payload).collect();
|
||||
cluster_info
|
||||
.write()
|
||||
.unwrap()
|
||||
.broadcast_shreds(sock, all_shred_bufs, &all_seeds, stakes)?;
|
||||
let (peers, peers_and_stakes) = get_broadcast_peers(cluster_info, stakes);
|
||||
|
||||
let mut send_mmsg_total = 0;
|
||||
broadcast_shreds(
|
||||
sock,
|
||||
&shreds,
|
||||
&peers_and_stakes,
|
||||
&peers,
|
||||
&Arc::new(AtomicU64::new(0)),
|
||||
&mut send_mmsg_total,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn record(
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<Arc<Vec<Shred>>>>>,
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<RecordReceiver>>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
) -> Result<()> {
|
||||
let all_shreds = receiver.lock().unwrap().recv()?;
|
||||
let (all_shreds, _) = receiver.lock().unwrap().recv()?;
|
||||
blockstore
|
||||
.insert_shreds(all_shreds.to_vec(), None, true)
|
||||
.expect("Failed to insert shreds in blockstore");
|
||||
|
||||
@@ -1,53 +1,43 @@
|
||||
use super::broadcast_utils::{self, ReceiveResults};
|
||||
use super::*;
|
||||
use super::{
|
||||
broadcast_utils::{self, ReceiveResults},
|
||||
*,
|
||||
};
|
||||
use crate::broadcast_stage::broadcast_utils::UnfinishedSlotInfo;
|
||||
use solana_ledger::entry::Entry;
|
||||
use solana_ledger::shred::{Shred, Shredder, RECOMMENDED_FEC_RATE, SHRED_TICK_REFERENCE_MASK};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::Keypair;
|
||||
use solana_sdk::timing::duration_as_us;
|
||||
use solana_ledger::{
|
||||
entry::Entry,
|
||||
shred::{Shred, Shredder, RECOMMENDED_FEC_RATE, SHRED_TICK_REFERENCE_MASK},
|
||||
};
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Keypair, timing::duration_as_us};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Default)]
|
||||
struct BroadcastStats {
|
||||
// Per-slot elapsed time
|
||||
shredding_elapsed: u64,
|
||||
insert_shreds_elapsed: u64,
|
||||
broadcast_elapsed: u64,
|
||||
receive_elapsed: u64,
|
||||
seed_elapsed: u64,
|
||||
}
|
||||
|
||||
impl BroadcastStats {
|
||||
fn reset(&mut self) {
|
||||
self.insert_shreds_elapsed = 0;
|
||||
self.shredding_elapsed = 0;
|
||||
self.broadcast_elapsed = 0;
|
||||
self.receive_elapsed = 0;
|
||||
self.seed_elapsed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct StandardBroadcastRun {
|
||||
stats: Arc<RwLock<BroadcastStats>>,
|
||||
pub struct StandardBroadcastRun {
|
||||
process_shreds_stats: ProcessShredsStats,
|
||||
transmit_shreds_stats: Arc<Mutex<SlotBroadcastStats<TransmitShredsStats>>>,
|
||||
insert_shreds_stats: Arc<Mutex<SlotBroadcastStats<InsertShredsStats>>>,
|
||||
unfinished_slot: Option<UnfinishedSlotInfo>,
|
||||
current_slot_and_parent: Option<(u64, u64)>,
|
||||
slot_broadcast_start: Option<Instant>,
|
||||
keypair: Arc<Keypair>,
|
||||
shred_version: u16,
|
||||
last_datapoint_submit: Arc<AtomicU64>,
|
||||
num_batches: usize,
|
||||
}
|
||||
|
||||
impl StandardBroadcastRun {
|
||||
pub(super) fn new(keypair: Arc<Keypair>, shred_version: u16) -> Self {
|
||||
Self {
|
||||
stats: Arc::new(RwLock::new(BroadcastStats::default())),
|
||||
process_shreds_stats: ProcessShredsStats::default(),
|
||||
transmit_shreds_stats: Arc::new(Mutex::new(SlotBroadcastStats::default())),
|
||||
insert_shreds_stats: Arc::new(Mutex::new(SlotBroadcastStats::default())),
|
||||
unfinished_slot: None,
|
||||
current_slot_and_parent: None,
|
||||
slot_broadcast_start: None,
|
||||
keypair,
|
||||
shred_version,
|
||||
last_datapoint_submit: Arc::new(AtomicU64::new(0)),
|
||||
num_batches: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +120,7 @@ impl StandardBroadcastRun {
|
||||
#[cfg(test)]
|
||||
fn test_process_receive_results(
|
||||
&mut self,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
sock: &UdpSocket,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receive_results: ReceiveResults,
|
||||
@@ -142,6 +132,7 @@ impl StandardBroadcastRun {
|
||||
let brecv = Arc::new(Mutex::new(brecv));
|
||||
//data
|
||||
let _ = self.transmit(&srecv, cluster_info, sock);
|
||||
let _ = self.record(&brecv, blockstore);
|
||||
//coding
|
||||
let _ = self.transmit(&srecv, cluster_info, sock);
|
||||
let _ = self.record(&brecv, blockstore);
|
||||
@@ -151,8 +142,8 @@ impl StandardBroadcastRun {
|
||||
fn process_receive_results(
|
||||
&mut self,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
socket_sender: &Sender<TransmitShreds>,
|
||||
blockstore_sender: &Sender<Arc<Vec<Shred>>>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
receive_results: ReceiveResults,
|
||||
) -> Result<()> {
|
||||
let mut receive_elapsed = receive_results.time_elapsed;
|
||||
@@ -160,11 +151,13 @@ impl StandardBroadcastRun {
|
||||
let bank = receive_results.bank.clone();
|
||||
let last_tick_height = receive_results.last_tick_height;
|
||||
inc_new_counter_info!("broadcast_service-entries_received", num_entries);
|
||||
|
||||
let old_broadcast_start = self.slot_broadcast_start;
|
||||
let old_num_batches = self.num_batches;
|
||||
if self.current_slot_and_parent.is_none()
|
||||
|| bank.slot() != self.current_slot_and_parent.unwrap().0
|
||||
{
|
||||
self.slot_broadcast_start = Some(Instant::now());
|
||||
self.num_batches = 0;
|
||||
let slot = bank.slot();
|
||||
let parent_slot = bank.parent_slot();
|
||||
|
||||
@@ -179,19 +172,19 @@ impl StandardBroadcastRun {
|
||||
self.check_for_interrupted_slot(bank.ticks_per_slot() as u8);
|
||||
|
||||
// 2) Convert entries to shreds and coding shreds
|
||||
|
||||
let (shredder, next_shred_index) = self.init_shredder(
|
||||
blockstore,
|
||||
(bank.tick_height() % bank.ticks_per_slot()) as u8,
|
||||
);
|
||||
let mut data_shreds = self.entries_to_data_shreds(
|
||||
let is_last_in_slot = last_tick_height == bank.max_tick_height();
|
||||
let data_shreds = self.entries_to_data_shreds(
|
||||
&shredder,
|
||||
next_shred_index,
|
||||
&receive_results.entries,
|
||||
last_tick_height == bank.max_tick_height(),
|
||||
is_last_in_slot,
|
||||
);
|
||||
//Insert the first shred so blockstore stores that the leader started this block
|
||||
//This must be done before the blocks are sent out over the wire.
|
||||
// Insert the first shred so blockstore stores that the leader started this block
|
||||
// This must be done before the blocks are sent out over the wire.
|
||||
if !data_shreds.is_empty() && data_shreds[0].index() == 0 {
|
||||
let first = vec![data_shreds[0].clone()];
|
||||
blockstore
|
||||
@@ -199,26 +192,56 @@ impl StandardBroadcastRun {
|
||||
.expect("Failed to insert shreds in blockstore");
|
||||
}
|
||||
let last_data_shred = data_shreds.len();
|
||||
if let Some(last_shred) = last_unfinished_slot_shred {
|
||||
data_shreds.push(last_shred);
|
||||
}
|
||||
let to_shreds_elapsed = to_shreds_start.elapsed();
|
||||
|
||||
let bank_epoch = bank.get_leader_schedule_epoch(bank.slot());
|
||||
let stakes = staking_utils::staked_nodes_at_epoch(&bank, bank_epoch);
|
||||
let stakes = stakes.map(Arc::new);
|
||||
let data_shreds = Arc::new(data_shreds);
|
||||
socket_sender.send((stakes.clone(), data_shreds.clone()))?;
|
||||
blockstore_sender.send(data_shreds.clone())?;
|
||||
let coding_shreds = shredder.data_shreds_to_coding_shreds(&data_shreds[0..last_data_shred]);
|
||||
let coding_shreds = Arc::new(coding_shreds);
|
||||
socket_sender.send((stakes, coding_shreds))?;
|
||||
self.update_broadcast_stats(BroadcastStats {
|
||||
shredding_elapsed: duration_as_us(&to_shreds_elapsed),
|
||||
receive_elapsed: duration_as_us(&receive_elapsed),
|
||||
..BroadcastStats::default()
|
||||
|
||||
// Broadcast the last shred of the interrupted slot if necessary
|
||||
if let Some(last_shred) = last_unfinished_slot_shred {
|
||||
let batch_info = Some(BroadcastShredBatchInfo {
|
||||
slot: last_shred.slot(),
|
||||
num_expected_batches: Some(old_num_batches + 1),
|
||||
slot_start_ts: old_broadcast_start.expect(
|
||||
"Old broadcast start time for previous slot must exist if the previous slot
|
||||
was interrupted",
|
||||
),
|
||||
});
|
||||
let last_shred = Arc::new(vec![last_shred]);
|
||||
socket_sender.send(((stakes.clone(), last_shred.clone()), batch_info.clone()))?;
|
||||
blockstore_sender.send((last_shred, batch_info))?;
|
||||
}
|
||||
|
||||
// Increment by two batches, one for the data batch, one for the coding batch.
|
||||
self.num_batches += 2;
|
||||
let num_expected_batches = {
|
||||
if is_last_in_slot {
|
||||
Some(self.num_batches)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let batch_info = Some(BroadcastShredBatchInfo {
|
||||
slot: bank.slot(),
|
||||
num_expected_batches,
|
||||
slot_start_ts: self
|
||||
.slot_broadcast_start
|
||||
.clone()
|
||||
.expect("Start timestamp must exist for a slot if we're broadcasting the slot"),
|
||||
});
|
||||
|
||||
let data_shreds = Arc::new(data_shreds);
|
||||
socket_sender.send(((stakes.clone(), data_shreds.clone()), batch_info.clone()))?;
|
||||
blockstore_sender.send((data_shreds.clone(), batch_info.clone()))?;
|
||||
let coding_shreds = shredder.data_shreds_to_coding_shreds(&data_shreds[0..last_data_shred]);
|
||||
let coding_shreds = Arc::new(coding_shreds);
|
||||
socket_sender.send(((stakes, coding_shreds.clone()), batch_info.clone()))?;
|
||||
blockstore_sender.send((coding_shreds, batch_info))?;
|
||||
self.process_shreds_stats.update(&ProcessShredsStats {
|
||||
shredding_elapsed: duration_as_us(&to_shreds_elapsed),
|
||||
receive_elapsed: duration_as_us(&receive_elapsed),
|
||||
});
|
||||
if last_tick_height == bank.max_tick_height() {
|
||||
self.report_and_reset_stats();
|
||||
self.unfinished_slot = None;
|
||||
@@ -227,10 +250,15 @@ impl StandardBroadcastRun {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert(&self, blockstore: &Arc<Blockstore>, shreds: Arc<Vec<Shred>>) -> Result<()> {
|
||||
fn insert(
|
||||
&mut self,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
shreds: Arc<Vec<Shred>>,
|
||||
broadcast_shred_batch_info: Option<BroadcastShredBatchInfo>,
|
||||
) -> Result<()> {
|
||||
// Insert shreds into blockstore
|
||||
let insert_shreds_start = Instant::now();
|
||||
//The first shred is inserted synchronously
|
||||
// The first shred is inserted synchronously
|
||||
let data_shreds = if !shreds.is_empty() && shreds[0].index() == 0 {
|
||||
shreds[1..].to_vec()
|
||||
} else {
|
||||
@@ -240,66 +268,80 @@ impl StandardBroadcastRun {
|
||||
.insert_shreds(data_shreds, None, true)
|
||||
.expect("Failed to insert shreds in blockstore");
|
||||
let insert_shreds_elapsed = insert_shreds_start.elapsed();
|
||||
self.update_broadcast_stats(BroadcastStats {
|
||||
let new_insert_shreds_stats = InsertShredsStats {
|
||||
insert_shreds_elapsed: duration_as_us(&insert_shreds_elapsed),
|
||||
..BroadcastStats::default()
|
||||
});
|
||||
num_shreds: shreds.len(),
|
||||
};
|
||||
self.update_insertion_metrics(&new_insert_shreds_stats, &broadcast_shred_batch_info);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_insertion_metrics(
|
||||
&mut self,
|
||||
new_insertion_shreds_stats: &InsertShredsStats,
|
||||
broadcast_shred_batch_info: &Option<BroadcastShredBatchInfo>,
|
||||
) {
|
||||
let mut insert_shreds_stats = self.insert_shreds_stats.lock().unwrap();
|
||||
insert_shreds_stats.update(new_insertion_shreds_stats, broadcast_shred_batch_info);
|
||||
}
|
||||
|
||||
fn broadcast(
|
||||
&self,
|
||||
&mut self,
|
||||
sock: &UdpSocket,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
stakes: Option<Arc<HashMap<Pubkey, u64>>>,
|
||||
shreds: Arc<Vec<Shred>>,
|
||||
broadcast_shred_batch_info: Option<BroadcastShredBatchInfo>,
|
||||
) -> Result<()> {
|
||||
let seed_start = Instant::now();
|
||||
let seeds: Vec<[u8; 32]> = shreds.iter().map(|s| s.seed()).collect();
|
||||
let seed_elapsed = seed_start.elapsed();
|
||||
trace!("Broadcasting {:?} shreds", shreds.len());
|
||||
// Get the list of peers to broadcast to
|
||||
let get_peers_start = Instant::now();
|
||||
let (peers, peers_and_stakes) = get_broadcast_peers(cluster_info, stakes);
|
||||
let get_peers_elapsed = get_peers_start.elapsed();
|
||||
|
||||
// Broadcast the shreds
|
||||
let broadcast_start = Instant::now();
|
||||
let shred_bufs: Vec<Vec<u8>> = shreds.to_vec().into_iter().map(|s| s.payload).collect();
|
||||
trace!("Broadcasting {:?} shreds", shred_bufs.len());
|
||||
let transmit_start = Instant::now();
|
||||
let mut send_mmsg_total = 0;
|
||||
broadcast_shreds(
|
||||
sock,
|
||||
&shreds,
|
||||
&peers_and_stakes,
|
||||
&peers,
|
||||
&self.last_datapoint_submit,
|
||||
&mut send_mmsg_total,
|
||||
)?;
|
||||
let transmit_elapsed = transmit_start.elapsed();
|
||||
let new_transmit_shreds_stats = TransmitShredsStats {
|
||||
transmit_elapsed: duration_as_us(&transmit_elapsed),
|
||||
get_peers_elapsed: duration_as_us(&get_peers_elapsed),
|
||||
send_mmsg_elapsed: send_mmsg_total,
|
||||
num_shreds: shreds.len(),
|
||||
};
|
||||
|
||||
cluster_info
|
||||
.write()
|
||||
.unwrap()
|
||||
.broadcast_shreds(sock, shred_bufs, &seeds, stakes)?;
|
||||
|
||||
let broadcast_elapsed = broadcast_start.elapsed();
|
||||
|
||||
self.update_broadcast_stats(BroadcastStats {
|
||||
broadcast_elapsed: duration_as_us(&broadcast_elapsed),
|
||||
seed_elapsed: duration_as_us(&seed_elapsed),
|
||||
..BroadcastStats::default()
|
||||
});
|
||||
// Process metrics
|
||||
self.update_transmit_metrics(&new_transmit_shreds_stats, &broadcast_shred_batch_info);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_broadcast_stats(&self, stats: BroadcastStats) {
|
||||
let mut wstats = self.stats.write().unwrap();
|
||||
wstats.receive_elapsed += stats.receive_elapsed;
|
||||
wstats.shredding_elapsed += stats.shredding_elapsed;
|
||||
wstats.insert_shreds_elapsed += stats.insert_shreds_elapsed;
|
||||
wstats.broadcast_elapsed += stats.broadcast_elapsed;
|
||||
wstats.seed_elapsed += stats.seed_elapsed;
|
||||
fn update_transmit_metrics(
|
||||
&mut self,
|
||||
new_transmit_shreds_stats: &TransmitShredsStats,
|
||||
broadcast_shred_batch_info: &Option<BroadcastShredBatchInfo>,
|
||||
) {
|
||||
let mut transmit_shreds_stats = self.transmit_shreds_stats.lock().unwrap();
|
||||
transmit_shreds_stats.update(new_transmit_shreds_stats, broadcast_shred_batch_info);
|
||||
}
|
||||
|
||||
fn report_and_reset_stats(&mut self) {
|
||||
let stats = self.stats.read().unwrap();
|
||||
let stats = &self.process_shreds_stats;
|
||||
assert!(self.unfinished_slot.is_some());
|
||||
datapoint_info!(
|
||||
"broadcast-bank-stats",
|
||||
"broadcast-process-shreds-stats",
|
||||
("slot", self.unfinished_slot.unwrap().slot as i64, i64),
|
||||
("shredding_time", stats.shredding_elapsed as i64, i64),
|
||||
("insertion_time", stats.insert_shreds_elapsed as i64, i64),
|
||||
("broadcast_time", stats.broadcast_elapsed as i64, i64),
|
||||
("receive_time", stats.receive_elapsed as i64, i64),
|
||||
("seed", stats.seed_elapsed as i64, i64),
|
||||
(
|
||||
"num_shreds",
|
||||
"num_data_shreds",
|
||||
i64::from(self.unfinished_slot.unwrap().next_shred_index),
|
||||
i64
|
||||
),
|
||||
@@ -309,8 +351,7 @@ impl StandardBroadcastRun {
|
||||
i64
|
||||
),
|
||||
);
|
||||
drop(stats);
|
||||
self.stats.write().unwrap().reset();
|
||||
self.process_shreds_stats.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,8 +360,8 @@ impl BroadcastRun for StandardBroadcastRun {
|
||||
&mut self,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receiver: &Receiver<WorkingBankEntry>,
|
||||
socket_sender: &Sender<TransmitShreds>,
|
||||
blockstore_sender: &Sender<Arc<Vec<Shred>>>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
) -> Result<()> {
|
||||
let receive_results = broadcast_utils::recv_slot_entries(receiver)?;
|
||||
self.process_receive_results(
|
||||
@@ -331,21 +372,21 @@ impl BroadcastRun for StandardBroadcastRun {
|
||||
)
|
||||
}
|
||||
fn transmit(
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<TransmitShreds>>>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<TransmitReceiver>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
sock: &UdpSocket,
|
||||
) -> Result<()> {
|
||||
let (stakes, shreds) = receiver.lock().unwrap().recv()?;
|
||||
self.broadcast(sock, cluster_info, stakes, shreds)
|
||||
let ((stakes, shreds), slot_start_ts) = receiver.lock().unwrap().recv()?;
|
||||
self.broadcast(sock, cluster_info, stakes, shreds, slot_start_ts)
|
||||
}
|
||||
fn record(
|
||||
&self,
|
||||
receiver: &Arc<Mutex<Receiver<Arc<Vec<Shred>>>>>,
|
||||
&mut self,
|
||||
receiver: &Arc<Mutex<RecordReceiver>>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
) -> Result<()> {
|
||||
let shreds = receiver.lock().unwrap().recv()?;
|
||||
self.insert(blockstore, shreds)
|
||||
let (shreds, slot_start_ts) = receiver.lock().unwrap().recv()?;
|
||||
self.insert(blockstore, shreds, slot_start_ts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,18 +394,17 @@ impl BroadcastRun for StandardBroadcastRun {
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::cluster_info::{ClusterInfo, Node};
|
||||
use crate::genesis_utils::create_genesis_config;
|
||||
use solana_ledger::genesis_utils::create_genesis_config;
|
||||
use solana_ledger::{
|
||||
blockstore::Blockstore, entry::create_ticks, get_tmp_ledger_path,
|
||||
shred::max_ticks_per_n_shreds,
|
||||
};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::{
|
||||
clock::Slot,
|
||||
genesis_config::GenesisConfig,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
fn setup(
|
||||
@@ -372,7 +412,7 @@ mod test {
|
||||
) -> (
|
||||
Arc<Blockstore>,
|
||||
GenesisConfig,
|
||||
Arc<RwLock<ClusterInfo>>,
|
||||
Arc<ClusterInfo>,
|
||||
Arc<Bank>,
|
||||
Arc<Keypair>,
|
||||
UdpSocket,
|
||||
@@ -385,12 +425,12 @@ mod test {
|
||||
let leader_keypair = Arc::new(Keypair::new());
|
||||
let leader_pubkey = leader_keypair.pubkey();
|
||||
let leader_info = Node::new_localhost_with_pubkey(&leader_pubkey);
|
||||
let cluster_info = Arc::new(RwLock::new(ClusterInfo::new_with_invalid_keypair(
|
||||
let cluster_info = Arc::new(ClusterInfo::new_with_invalid_keypair(
|
||||
leader_info.info.clone(),
|
||||
)));
|
||||
));
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
let mut genesis_config = create_genesis_config(10_000).genesis_config;
|
||||
genesis_config.ticks_per_slot = max_ticks_per_n_shreds(num_shreds_per_slot) + 1;
|
||||
genesis_config.ticks_per_slot = max_ticks_per_n_shreds(num_shreds_per_slot, None) + 1;
|
||||
let bank0 = Arc::new(Bank::new(&genesis_config));
|
||||
(
|
||||
blockstore,
|
||||
@@ -462,29 +502,48 @@ mod test {
|
||||
// Make sure the slot is not complete
|
||||
assert!(!blockstore.is_full(0));
|
||||
// Modify the stats, should reset later
|
||||
standard_broadcast_run
|
||||
.stats
|
||||
.write()
|
||||
.unwrap()
|
||||
.receive_elapsed = 10;
|
||||
|
||||
// Try to fetch ticks from blockstore, nothing should break
|
||||
assert_eq!(blockstore.get_slot_entries(0, 0, None).unwrap(), ticks0);
|
||||
standard_broadcast_run.process_shreds_stats.receive_elapsed = 10;
|
||||
// Broadcast stats should exist, and 2 batches should have been sent,
|
||||
// one for data, one for coding
|
||||
assert_eq!(
|
||||
blockstore
|
||||
.get_slot_entries(0, num_shreds_per_slot, None)
|
||||
.unwrap(),
|
||||
standard_broadcast_run
|
||||
.transmit_shreds_stats
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(unfinished_slot.slot)
|
||||
.unwrap()
|
||||
.num_batches(),
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
standard_broadcast_run
|
||||
.insert_shreds_stats
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(unfinished_slot.slot)
|
||||
.unwrap()
|
||||
.num_batches(),
|
||||
2
|
||||
);
|
||||
// Try to fetch ticks from blockstore, nothing should break
|
||||
assert_eq!(blockstore.get_slot_entries(0, 0).unwrap(), ticks0);
|
||||
assert_eq!(
|
||||
blockstore.get_slot_entries(0, num_shreds_per_slot).unwrap(),
|
||||
vec![],
|
||||
);
|
||||
|
||||
// Step 2: Make a transmission for another bank that interrupts the transmission for
|
||||
// slot 0
|
||||
let bank2 = Arc::new(Bank::new_from_parent(&bank0, &leader_keypair.pubkey(), 2));
|
||||
|
||||
let interrupted_slot = unfinished_slot.slot;
|
||||
// Interrupting the slot should cause the unfinished_slot and stats to reset
|
||||
let num_shreds = 1;
|
||||
assert!(num_shreds < num_shreds_per_slot);
|
||||
let ticks1 = create_ticks(max_ticks_per_n_shreds(num_shreds), 0, genesis_config.hash());
|
||||
let ticks1 = create_ticks(
|
||||
max_ticks_per_n_shreds(num_shreds, None),
|
||||
0,
|
||||
genesis_config.hash(),
|
||||
);
|
||||
let receive_results = ReceiveResults {
|
||||
entries: ticks1.clone(),
|
||||
time_elapsed: Duration::new(2, 0),
|
||||
@@ -504,16 +563,28 @@ mod test {
|
||||
|
||||
// Check that the stats were reset as well
|
||||
assert_eq!(
|
||||
standard_broadcast_run.stats.read().unwrap().receive_elapsed,
|
||||
standard_broadcast_run.process_shreds_stats.receive_elapsed,
|
||||
0
|
||||
);
|
||||
|
||||
// Broadcast stats for interrupted slot should be cleared
|
||||
assert!(standard_broadcast_run
|
||||
.transmit_shreds_stats
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(interrupted_slot)
|
||||
.is_none());
|
||||
assert!(standard_broadcast_run
|
||||
.insert_shreds_stats
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(interrupted_slot)
|
||||
.is_none());
|
||||
|
||||
// Try to fetch the incomplete ticks from blockstore, should succeed
|
||||
assert_eq!(blockstore.get_slot_entries(0, 0, None).unwrap(), ticks0);
|
||||
assert_eq!(blockstore.get_slot_entries(0, 0).unwrap(), ticks0);
|
||||
assert_eq!(
|
||||
blockstore
|
||||
.get_slot_entries(0, num_shreds_per_slot, None)
|
||||
.unwrap(),
|
||||
blockstore.get_slot_entries(0, num_shreds_per_slot).unwrap(),
|
||||
vec![],
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
343
core/src/cluster_slots.rs
Normal file
343
core/src/cluster_slots.rs
Normal file
@@ -0,0 +1,343 @@
|
||||
use crate::{
|
||||
cluster_info::ClusterInfo, contact_info::ContactInfo, epoch_slots::EpochSlots,
|
||||
serve_repair::RepairType,
|
||||
};
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use solana_runtime::epoch_stakes::NodeIdToVoteAccounts;
|
||||
use solana_sdk::{clock::Slot, pubkey::Pubkey};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
pub type SlotPubkeys = HashMap<Arc<Pubkey>, u64>;
|
||||
pub type ClusterSlotsMap = RwLock<HashMap<Slot, Arc<RwLock<SlotPubkeys>>>>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ClusterSlots {
|
||||
cluster_slots: ClusterSlotsMap,
|
||||
keys: RwLock<HashSet<Arc<Pubkey>>>,
|
||||
since: RwLock<Option<u64>>,
|
||||
validator_stakes: RwLock<Arc<NodeIdToVoteAccounts>>,
|
||||
epoch: RwLock<Option<u64>>,
|
||||
self_id: RwLock<Pubkey>,
|
||||
}
|
||||
|
||||
impl ClusterSlots {
|
||||
pub fn lookup(&self, slot: Slot) -> Option<Arc<RwLock<SlotPubkeys>>> {
|
||||
self.cluster_slots.read().unwrap().get(&slot).cloned()
|
||||
}
|
||||
pub fn update(&self, root: Slot, cluster_info: &ClusterInfo, bank_forks: &RwLock<BankForks>) {
|
||||
self.update_peers(cluster_info, bank_forks);
|
||||
let since = *self.since.read().unwrap();
|
||||
let epoch_slots = cluster_info.get_epoch_slots_since(since);
|
||||
self.update_internal(root, epoch_slots);
|
||||
}
|
||||
fn update_internal(&self, root: Slot, epoch_slots: (Vec<EpochSlots>, Option<u64>)) {
|
||||
let (epoch_slots_list, since) = epoch_slots;
|
||||
for epoch_slots in epoch_slots_list {
|
||||
let slots = epoch_slots.to_slots(root);
|
||||
for slot in &slots {
|
||||
if *slot <= root {
|
||||
continue;
|
||||
}
|
||||
let pubkey = Arc::new(epoch_slots.from);
|
||||
let exists = self.keys.read().unwrap().get(&pubkey).is_some();
|
||||
if !exists {
|
||||
self.keys.write().unwrap().insert(pubkey.clone());
|
||||
}
|
||||
let from = self.keys.read().unwrap().get(&pubkey).unwrap().clone();
|
||||
let balance = self
|
||||
.validator_stakes
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&from)
|
||||
.map(|v| v.total_stake)
|
||||
.unwrap_or(0);
|
||||
|
||||
let mut slot_pubkeys = self.cluster_slots.read().unwrap().get(slot).cloned();
|
||||
if slot_pubkeys.is_none() {
|
||||
let new_slot_pubkeys = Arc::new(RwLock::new(HashMap::default()));
|
||||
self.cluster_slots
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(*slot, new_slot_pubkeys.clone());
|
||||
slot_pubkeys = Some(new_slot_pubkeys);
|
||||
}
|
||||
|
||||
slot_pubkeys
|
||||
.unwrap()
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(from.clone(), balance);
|
||||
}
|
||||
}
|
||||
self.cluster_slots.write().unwrap().retain(|x, _| *x > root);
|
||||
self.keys
|
||||
.write()
|
||||
.unwrap()
|
||||
.retain(|x| Arc::strong_count(x) > 1);
|
||||
*self.since.write().unwrap() = since;
|
||||
}
|
||||
pub fn collect(&self, id: &Pubkey) -> HashSet<Slot> {
|
||||
self.cluster_slots
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|(_, keys)| keys.read().unwrap().get(id).is_some())
|
||||
.map(|(slot, _)| slot)
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn update_peers(&self, cluster_info: &ClusterInfo, bank_forks: &RwLock<BankForks>) {
|
||||
let root_bank = bank_forks.read().unwrap().root_bank().clone();
|
||||
let root_epoch = root_bank.epoch();
|
||||
let my_epoch = *self.epoch.read().unwrap();
|
||||
|
||||
if Some(root_epoch) != my_epoch {
|
||||
let validator_stakes = root_bank
|
||||
.epoch_stakes(root_epoch)
|
||||
.expect(
|
||||
"Bank must have epoch stakes
|
||||
for its own epoch",
|
||||
)
|
||||
.node_id_to_vote_accounts()
|
||||
.clone();
|
||||
|
||||
*self.validator_stakes.write().unwrap() = validator_stakes;
|
||||
let id = cluster_info.id();
|
||||
*self.self_id.write().unwrap() = id;
|
||||
*self.epoch.write().unwrap() = Some(root_epoch);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_weights(&self, slot: Slot, repair_peers: &[ContactInfo]) -> Vec<(u64, usize)> {
|
||||
let slot_peers = self.lookup(slot);
|
||||
repair_peers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| {
|
||||
let peer_stake = slot_peers
|
||||
.as_ref()
|
||||
.and_then(|v| v.read().unwrap().get(&x.id).cloned())
|
||||
.unwrap_or(0);
|
||||
(
|
||||
1 + peer_stake
|
||||
+ self
|
||||
.validator_stakes
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&x.id)
|
||||
.map(|v| v.total_stake)
|
||||
.unwrap_or(0),
|
||||
i,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn generate_repairs_for_missing_slots(
|
||||
&self,
|
||||
self_id: &Pubkey,
|
||||
root: Slot,
|
||||
) -> Vec<RepairType> {
|
||||
let my_slots = self.collect(self_id);
|
||||
self.cluster_slots
|
||||
.read()
|
||||
.unwrap()
|
||||
.keys()
|
||||
.filter(|x| **x > root)
|
||||
.filter(|x| !my_slots.contains(*x))
|
||||
.map(|x| RepairType::HighestShred(*x, 0))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_runtime::epoch_stakes::NodeVoteAccounts;
|
||||
|
||||
#[test]
|
||||
fn test_default() {
|
||||
let cs = ClusterSlots::default();
|
||||
assert!(cs.cluster_slots.read().unwrap().is_empty());
|
||||
assert!(cs.since.read().unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_noop() {
|
||||
let cs = ClusterSlots::default();
|
||||
cs.update_internal(0, (vec![], None));
|
||||
assert!(cs.cluster_slots.read().unwrap().is_empty());
|
||||
assert!(cs.since.read().unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_empty() {
|
||||
let cs = ClusterSlots::default();
|
||||
let epoch_slot = EpochSlots::default();
|
||||
cs.update_internal(0, (vec![epoch_slot], Some(0)));
|
||||
assert_eq!(*cs.since.read().unwrap(), Some(0));
|
||||
assert!(cs.lookup(0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_rooted() {
|
||||
//root is 0, so it should clear out the slot
|
||||
let cs = ClusterSlots::default();
|
||||
let mut epoch_slot = EpochSlots::default();
|
||||
epoch_slot.fill(&[0], 0);
|
||||
cs.update_internal(0, (vec![epoch_slot], Some(0)));
|
||||
assert_eq!(*cs.since.read().unwrap(), Some(0));
|
||||
assert!(cs.lookup(0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_new_slot() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut epoch_slot = EpochSlots::default();
|
||||
epoch_slot.fill(&[1], 0);
|
||||
cs.update_internal(0, (vec![epoch_slot], Some(0)));
|
||||
assert_eq!(*cs.since.read().unwrap(), Some(0));
|
||||
assert!(cs.lookup(0).is_none());
|
||||
assert!(cs.lookup(1).is_some());
|
||||
assert_eq!(
|
||||
cs.lookup(1)
|
||||
.unwrap()
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&Pubkey::default()),
|
||||
Some(&0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_weights() {
|
||||
let cs = ClusterSlots::default();
|
||||
let ci = ContactInfo::default();
|
||||
assert_eq!(cs.compute_weights(0, &[ci]), vec![(1, 0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_best_peer_2() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut c1 = ContactInfo::default();
|
||||
let mut c2 = ContactInfo::default();
|
||||
let mut map = HashMap::new();
|
||||
let k1 = Pubkey::new_rand();
|
||||
let k2 = Pubkey::new_rand();
|
||||
map.insert(Arc::new(k1.clone()), std::u64::MAX / 2);
|
||||
map.insert(Arc::new(k2.clone()), 0);
|
||||
cs.cluster_slots
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(0, Arc::new(RwLock::new(map)));
|
||||
c1.id = k1;
|
||||
c2.id = k2;
|
||||
assert_eq!(
|
||||
cs.compute_weights(0, &[c1, c2]),
|
||||
vec![(std::u64::MAX / 2 + 1, 0), (1, 1)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_best_peer_3() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut c1 = ContactInfo::default();
|
||||
let mut c2 = ContactInfo::default();
|
||||
let mut map = HashMap::new();
|
||||
let k1 = Pubkey::new_rand();
|
||||
let k2 = Pubkey::new_rand();
|
||||
map.insert(Arc::new(k2.clone()), 0);
|
||||
cs.cluster_slots
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(0, Arc::new(RwLock::new(map)));
|
||||
//make sure default weights are used as well
|
||||
let validator_stakes: HashMap<_, _> = vec![(
|
||||
*Arc::new(k1.clone()),
|
||||
NodeVoteAccounts {
|
||||
total_stake: std::u64::MAX / 2,
|
||||
vote_accounts: vec![Pubkey::default()],
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
*cs.validator_stakes.write().unwrap() = Arc::new(validator_stakes);
|
||||
c1.id = k1;
|
||||
c2.id = k2;
|
||||
assert_eq!(
|
||||
cs.compute_weights(0, &[c1, c2]),
|
||||
vec![(std::u64::MAX / 2 + 1, 0), (1, 1)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_new_staked_slot() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut epoch_slot = EpochSlots::default();
|
||||
epoch_slot.fill(&[1], 0);
|
||||
|
||||
let map = Arc::new(
|
||||
vec![(
|
||||
Pubkey::default(),
|
||||
NodeVoteAccounts {
|
||||
total_stake: 1,
|
||||
vote_accounts: vec![Pubkey::default()],
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
);
|
||||
|
||||
*cs.validator_stakes.write().unwrap() = map;
|
||||
cs.update_internal(0, (vec![epoch_slot], None));
|
||||
assert!(cs.lookup(1).is_some());
|
||||
assert_eq!(
|
||||
cs.lookup(1)
|
||||
.unwrap()
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&Pubkey::default()),
|
||||
Some(&1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_repairs() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut epoch_slot = EpochSlots::default();
|
||||
epoch_slot.fill(&[1], 0);
|
||||
cs.update_internal(0, (vec![epoch_slot], None));
|
||||
let self_id = Pubkey::new_rand();
|
||||
assert_eq!(
|
||||
cs.generate_repairs_for_missing_slots(&self_id, 0),
|
||||
vec![RepairType::HighestShred(1, 0)]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collect_my_slots() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut epoch_slot = EpochSlots::default();
|
||||
epoch_slot.fill(&[1], 0);
|
||||
let self_id = epoch_slot.from;
|
||||
cs.update_internal(0, (vec![epoch_slot], None));
|
||||
let slots: Vec<Slot> = cs.collect(&self_id).into_iter().collect();
|
||||
assert_eq!(slots, vec![1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_repairs_existing() {
|
||||
let cs = ClusterSlots::default();
|
||||
let mut epoch_slot = EpochSlots::default();
|
||||
epoch_slot.fill(&[1], 0);
|
||||
let self_id = epoch_slot.from;
|
||||
cs.update_internal(0, (vec![epoch_slot], None));
|
||||
assert!(cs
|
||||
.generate_repairs_for_missing_slots(&self_id, 0)
|
||||
.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
use crate::{consensus::VOTE_THRESHOLD_SIZE, rpc_subscriptions::RpcSubscriptions};
|
||||
use solana_ledger::blockstore::Blockstore;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_metrics::datapoint_info;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::clock::Slot;
|
||||
use solana_vote_program::{vote_state::VoteState, vote_state::MAX_LOCKOUT_HISTORY};
|
||||
@@ -10,9 +14,19 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CacheSlotInfo {
|
||||
pub current_slot: Slot,
|
||||
pub node_root: Slot,
|
||||
pub largest_confirmed_root: Slot,
|
||||
pub highest_confirmed_slot: Slot,
|
||||
}
|
||||
|
||||
pub type BlockCommitmentArray = [u64; MAX_LOCKOUT_HISTORY + 1];
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BlockCommitment {
|
||||
pub commitment: [u64; MAX_LOCKOUT_HISTORY],
|
||||
pub commitment: BlockCommitmentArray,
|
||||
}
|
||||
|
||||
impl BlockCommitment {
|
||||
@@ -25,23 +39,75 @@ impl BlockCommitment {
|
||||
assert!(confirmation_count > 0 && confirmation_count <= MAX_LOCKOUT_HISTORY);
|
||||
self.commitment[confirmation_count - 1]
|
||||
}
|
||||
|
||||
pub fn increase_rooted_stake(&mut self, stake: u64) {
|
||||
self.commitment[MAX_LOCKOUT_HISTORY] += stake;
|
||||
}
|
||||
|
||||
pub fn get_rooted_stake(&self) -> u64 {
|
||||
self.commitment[MAX_LOCKOUT_HISTORY]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new(commitment: [u64; MAX_LOCKOUT_HISTORY]) -> Self {
|
||||
pub(crate) fn new(commitment: BlockCommitmentArray) -> Self {
|
||||
Self { commitment }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct BlockCommitmentCache {
|
||||
block_commitment: HashMap<Slot, BlockCommitment>,
|
||||
largest_confirmed_root: Slot,
|
||||
total_stake: u64,
|
||||
bank: Arc<Bank>,
|
||||
blockstore: Arc<Blockstore>,
|
||||
root: Slot,
|
||||
highest_confirmed_slot: Slot,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BlockCommitmentCache {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("BlockCommitmentCache")
|
||||
.field("block_commitment", &self.block_commitment)
|
||||
.field("total_stake", &self.total_stake)
|
||||
.field(
|
||||
"bank",
|
||||
&format_args!("Bank({{current_slot: {:?}}})", self.bank.slot()),
|
||||
)
|
||||
.field("root", &self.root)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockCommitmentCache {
|
||||
pub fn new(block_commitment: HashMap<Slot, BlockCommitment>, total_stake: u64) -> Self {
|
||||
pub fn new(
|
||||
block_commitment: HashMap<Slot, BlockCommitment>,
|
||||
largest_confirmed_root: Slot,
|
||||
total_stake: u64,
|
||||
bank: Arc<Bank>,
|
||||
blockstore: Arc<Blockstore>,
|
||||
root: Slot,
|
||||
highest_confirmed_slot: Slot,
|
||||
) -> Self {
|
||||
Self {
|
||||
block_commitment,
|
||||
largest_confirmed_root,
|
||||
total_stake,
|
||||
bank,
|
||||
blockstore,
|
||||
root,
|
||||
highest_confirmed_slot,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_with_blockstore(blockstore: Arc<Blockstore>) -> Self {
|
||||
Self {
|
||||
block_commitment: HashMap::default(),
|
||||
largest_confirmed_root: Slot::default(),
|
||||
total_stake: u64::default(),
|
||||
bank: Arc::new(Bank::default()),
|
||||
blockstore,
|
||||
root: Slot::default(),
|
||||
highest_confirmed_slot: Slot::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,45 +115,138 @@ impl BlockCommitmentCache {
|
||||
self.block_commitment.get(&slot)
|
||||
}
|
||||
|
||||
pub fn largest_confirmed_root(&self) -> Slot {
|
||||
self.largest_confirmed_root
|
||||
}
|
||||
|
||||
pub fn total_stake(&self) -> u64 {
|
||||
self.total_stake
|
||||
}
|
||||
|
||||
pub fn get_block_with_depth_commitment(
|
||||
&self,
|
||||
minimum_depth: usize,
|
||||
minimum_stake_percentage: f64,
|
||||
) -> Option<Slot> {
|
||||
self.block_commitment
|
||||
.iter()
|
||||
.filter(|&(_, block_commitment)| {
|
||||
let fork_stake_minimum_depth: u64 = block_commitment.commitment[minimum_depth..]
|
||||
.iter()
|
||||
.cloned()
|
||||
.sum();
|
||||
fork_stake_minimum_depth as f64 / self.total_stake as f64
|
||||
>= minimum_stake_percentage
|
||||
})
|
||||
.map(|(slot, _)| *slot)
|
||||
.max()
|
||||
pub fn bank(&self) -> Arc<Bank> {
|
||||
self.bank.clone()
|
||||
}
|
||||
|
||||
pub fn get_rooted_block_with_commitment(&self, minimum_stake_percentage: f64) -> Option<u64> {
|
||||
self.get_block_with_depth_commitment(MAX_LOCKOUT_HISTORY - 1, minimum_stake_percentage)
|
||||
pub fn slot(&self) -> Slot {
|
||||
self.bank.slot()
|
||||
}
|
||||
|
||||
pub fn root(&self) -> Slot {
|
||||
self.root
|
||||
}
|
||||
|
||||
pub fn highest_confirmed_slot(&self) -> Slot {
|
||||
self.highest_confirmed_slot
|
||||
}
|
||||
|
||||
fn highest_slot_with_confirmation_count(&self, confirmation_count: usize) -> Slot {
|
||||
assert!(confirmation_count > 0 && confirmation_count <= MAX_LOCKOUT_HISTORY);
|
||||
for slot in (self.root()..self.slot()).rev() {
|
||||
if let Some(count) = self.get_confirmation_count(slot) {
|
||||
if count >= confirmation_count {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.root
|
||||
}
|
||||
|
||||
fn calculate_highest_confirmed_slot(&self) -> Slot {
|
||||
self.highest_slot_with_confirmation_count(1)
|
||||
}
|
||||
|
||||
pub fn get_confirmation_count(&self, slot: Slot) -> Option<usize> {
|
||||
self.get_lockout_count(slot, VOTE_THRESHOLD_SIZE)
|
||||
}
|
||||
|
||||
// Returns the lowest level at which at least `minimum_stake_percentage` of the total epoch
|
||||
// stake is locked out
|
||||
fn get_lockout_count(&self, slot: Slot, minimum_stake_percentage: f64) -> Option<usize> {
|
||||
self.get_block_commitment(slot).map(|block_commitment| {
|
||||
let iterator = block_commitment.commitment.iter().enumerate().rev();
|
||||
let mut sum = 0;
|
||||
for (i, stake) in iterator {
|
||||
sum += stake;
|
||||
if (sum as f64 / self.total_stake as f64) > minimum_stake_percentage {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
0
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_confirmed_rooted(&self, slot: Slot) -> bool {
|
||||
slot <= self.largest_confirmed_root()
|
||||
&& (self.blockstore.is_root(slot) || self.bank.status_cache_ancestors().contains(&slot))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_for_tests_with_blockstore(blockstore: Arc<Blockstore>) -> Self {
|
||||
let mut block_commitment: HashMap<Slot, BlockCommitment> = HashMap::new();
|
||||
block_commitment.insert(0, BlockCommitment::default());
|
||||
Self {
|
||||
block_commitment,
|
||||
blockstore,
|
||||
total_stake: 42,
|
||||
largest_confirmed_root: Slot::default(),
|
||||
bank: Arc::new(Bank::default()),
|
||||
root: Slot::default(),
|
||||
highest_confirmed_slot: Slot::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_for_tests_with_blockstore_bank(
|
||||
blockstore: Arc<Blockstore>,
|
||||
bank: Arc<Bank>,
|
||||
root: Slot,
|
||||
) -> Self {
|
||||
let mut block_commitment: HashMap<Slot, BlockCommitment> = HashMap::new();
|
||||
block_commitment.insert(0, BlockCommitment::default());
|
||||
Self {
|
||||
block_commitment,
|
||||
blockstore,
|
||||
total_stake: 42,
|
||||
largest_confirmed_root: root,
|
||||
bank,
|
||||
root,
|
||||
highest_confirmed_slot: root,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_largest_confirmed_root(&mut self, root: Slot) {
|
||||
self.largest_confirmed_root = root;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommitmentAggregationData {
|
||||
bank: Arc<Bank>,
|
||||
root: Slot,
|
||||
total_staked: u64,
|
||||
}
|
||||
|
||||
impl CommitmentAggregationData {
|
||||
pub fn new(bank: Arc<Bank>, total_staked: u64) -> Self {
|
||||
Self { bank, total_staked }
|
||||
pub fn new(bank: Arc<Bank>, root: Slot, total_staked: u64) -> Self {
|
||||
Self {
|
||||
bank,
|
||||
root,
|
||||
total_staked,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_largest_confirmed_root(mut rooted_stake: Vec<(Slot, u64)>, total_stake: u64) -> Slot {
|
||||
rooted_stake.sort_by(|a, b| a.0.cmp(&b.0).reverse());
|
||||
let mut stake_sum = 0;
|
||||
for (root, stake) in rooted_stake {
|
||||
stake_sum += stake;
|
||||
if (stake_sum as f64 / total_stake as f64) > VOTE_THRESHOLD_SIZE {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
pub struct AggregateCommitmentService {
|
||||
t_commitment: JoinHandle<()>,
|
||||
}
|
||||
@@ -96,6 +255,7 @@ impl AggregateCommitmentService {
|
||||
pub fn new(
|
||||
exit: &Arc<AtomicBool>,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
subscriptions: Arc<RpcSubscriptions>,
|
||||
) -> (Sender<CommitmentAggregationData>, Self) {
|
||||
let (sender, receiver): (
|
||||
Sender<CommitmentAggregationData>,
|
||||
@@ -113,7 +273,7 @@ impl AggregateCommitmentService {
|
||||
}
|
||||
|
||||
if let Err(RecvTimeoutError::Disconnected) =
|
||||
Self::run(&receiver, &block_commitment_cache, &exit_)
|
||||
Self::run(&receiver, &block_commitment_cache, &subscriptions, &exit_)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -126,6 +286,7 @@ impl AggregateCommitmentService {
|
||||
fn run(
|
||||
receiver: &Receiver<CommitmentAggregationData>,
|
||||
block_commitment_cache: &RwLock<BlockCommitmentCache>,
|
||||
subscriptions: &Arc<RpcSubscriptions>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
) -> Result<(), RecvTimeoutError> {
|
||||
loop {
|
||||
@@ -144,18 +305,51 @@ impl AggregateCommitmentService {
|
||||
continue;
|
||||
}
|
||||
|
||||
let block_commitment = Self::aggregate_commitment(&ancestors, &aggregation_data.bank);
|
||||
let mut aggregate_commitment_time = Measure::start("aggregate-commitment-ms");
|
||||
let (block_commitment, rooted_stake) =
|
||||
Self::aggregate_commitment(&ancestors, &aggregation_data.bank);
|
||||
|
||||
let mut new_block_commitment =
|
||||
BlockCommitmentCache::new(block_commitment, aggregation_data.total_staked);
|
||||
let largest_confirmed_root =
|
||||
get_largest_confirmed_root(rooted_stake, aggregation_data.total_staked);
|
||||
|
||||
let mut new_block_commitment = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
largest_confirmed_root,
|
||||
aggregation_data.total_staked,
|
||||
aggregation_data.bank,
|
||||
block_commitment_cache.read().unwrap().blockstore.clone(),
|
||||
aggregation_data.root,
|
||||
aggregation_data.root,
|
||||
);
|
||||
new_block_commitment.highest_confirmed_slot =
|
||||
new_block_commitment.calculate_highest_confirmed_slot();
|
||||
|
||||
let mut w_block_commitment_cache = block_commitment_cache.write().unwrap();
|
||||
|
||||
std::mem::swap(&mut *w_block_commitment_cache, &mut new_block_commitment);
|
||||
aggregate_commitment_time.stop();
|
||||
datapoint_info!(
|
||||
"block-commitment-cache",
|
||||
(
|
||||
"aggregate-commitment-ms",
|
||||
aggregate_commitment_time.as_ms() as i64,
|
||||
i64
|
||||
)
|
||||
);
|
||||
|
||||
subscriptions.notify_subscribers(CacheSlotInfo {
|
||||
current_slot: w_block_commitment_cache.slot(),
|
||||
node_root: w_block_commitment_cache.root(),
|
||||
largest_confirmed_root: w_block_commitment_cache.largest_confirmed_root(),
|
||||
highest_confirmed_slot: w_block_commitment_cache.highest_confirmed_slot(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn aggregate_commitment(ancestors: &[Slot], bank: &Bank) -> HashMap<Slot, BlockCommitment> {
|
||||
pub fn aggregate_commitment(
|
||||
ancestors: &[Slot],
|
||||
bank: &Bank,
|
||||
) -> (HashMap<Slot, BlockCommitment>, Vec<(Slot, u64)>) {
|
||||
assert!(!ancestors.is_empty());
|
||||
|
||||
// Check ancestors is sorted
|
||||
@@ -164,6 +358,7 @@ impl AggregateCommitmentService {
|
||||
}
|
||||
|
||||
let mut commitment = HashMap::new();
|
||||
let mut rooted_stake: Vec<(Slot, u64)> = Vec::new();
|
||||
for (_, (lamports, account)) in bank.vote_accounts().into_iter() {
|
||||
if lamports == 0 {
|
||||
continue;
|
||||
@@ -176,17 +371,19 @@ impl AggregateCommitmentService {
|
||||
let vote_state = vote_state.unwrap();
|
||||
Self::aggregate_commitment_for_vote_account(
|
||||
&mut commitment,
|
||||
&mut rooted_stake,
|
||||
&vote_state,
|
||||
ancestors,
|
||||
lamports,
|
||||
);
|
||||
}
|
||||
|
||||
commitment
|
||||
(commitment, rooted_stake)
|
||||
}
|
||||
|
||||
fn aggregate_commitment_for_vote_account(
|
||||
commitment: &mut HashMap<Slot, BlockCommitment>,
|
||||
rooted_stake: &mut Vec<(Slot, u64)>,
|
||||
vote_state: &VoteState,
|
||||
ancestors: &[Slot],
|
||||
lamports: u64,
|
||||
@@ -199,12 +396,13 @@ impl AggregateCommitmentService {
|
||||
commitment
|
||||
.entry(*a)
|
||||
.or_insert_with(BlockCommitment::default)
|
||||
.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports);
|
||||
.increase_rooted_stake(lamports);
|
||||
} else {
|
||||
ancestors_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
rooted_stake.push((root, lamports));
|
||||
}
|
||||
|
||||
for vote in &vote_state.votes {
|
||||
@@ -230,8 +428,11 @@ impl AggregateCommitmentService {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_ledger::{
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
get_tmp_ledger_path,
|
||||
};
|
||||
use solana_sdk::{genesis_config::GenesisConfig, pubkey::Pubkey};
|
||||
use solana_stake_program::stake_state;
|
||||
use solana_vote_program::vote_state::{self, VoteStateVersions};
|
||||
|
||||
@@ -246,97 +447,207 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_block_with_depth_commitment() {
|
||||
fn test_get_confirmations() {
|
||||
let bank = Arc::new(Bank::default());
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||
// Build BlockCommitmentCache with votes at depths 0 and 1 for 2 slots
|
||||
let mut cache0 = BlockCommitment::default();
|
||||
cache0.increase_confirmation_stake(1, 15);
|
||||
cache0.increase_confirmation_stake(2, 25);
|
||||
cache0.increase_confirmation_stake(1, 5);
|
||||
cache0.increase_confirmation_stake(2, 40);
|
||||
|
||||
let mut cache1 = BlockCommitment::default();
|
||||
cache1.increase_confirmation_stake(1, 10);
|
||||
cache1.increase_confirmation_stake(2, 20);
|
||||
cache1.increase_confirmation_stake(1, 40);
|
||||
cache1.increase_confirmation_stake(2, 5);
|
||||
|
||||
let mut cache2 = BlockCommitment::default();
|
||||
cache2.increase_confirmation_stake(1, 20);
|
||||
cache2.increase_confirmation_stake(2, 5);
|
||||
|
||||
let mut block_commitment = HashMap::new();
|
||||
block_commitment.entry(0).or_insert(cache0.clone());
|
||||
block_commitment.entry(1).or_insert(cache1.clone());
|
||||
let block_commitment_cache = BlockCommitmentCache::new(block_commitment, 50);
|
||||
block_commitment.entry(2).or_insert(cache2.clone());
|
||||
let block_commitment_cache =
|
||||
BlockCommitmentCache::new(block_commitment, 0, 50, bank, blockstore, 0, 0);
|
||||
|
||||
// Neither slot has rooted votes
|
||||
assert_eq!(
|
||||
block_commitment_cache.get_rooted_block_with_commitment(0.1),
|
||||
None
|
||||
);
|
||||
// Neither slot meets the minimum level of commitment 0.6 at depth 1
|
||||
assert_eq!(
|
||||
block_commitment_cache.get_block_with_depth_commitment(1, 0.6),
|
||||
None
|
||||
);
|
||||
// Only slot 0 meets the minimum level of commitment 0.5 at depth 1
|
||||
assert_eq!(
|
||||
block_commitment_cache.get_block_with_depth_commitment(1, 0.5),
|
||||
Some(0)
|
||||
);
|
||||
// If multiple slots meet the minimum level of commitment, method should return the most recent
|
||||
assert_eq!(
|
||||
block_commitment_cache.get_block_with_depth_commitment(1, 0.4),
|
||||
Some(1)
|
||||
);
|
||||
// If multiple slots meet the minimum level of commitment, method should return the most recent
|
||||
assert_eq!(
|
||||
block_commitment_cache.get_block_with_depth_commitment(0, 0.6),
|
||||
Some(1)
|
||||
);
|
||||
// Neither slot meets the minimum level of commitment 0.9 at depth 0
|
||||
assert_eq!(
|
||||
block_commitment_cache.get_block_with_depth_commitment(0, 0.9),
|
||||
None
|
||||
);
|
||||
assert_eq!(block_commitment_cache.get_confirmation_count(0), Some(2));
|
||||
assert_eq!(block_commitment_cache.get_confirmation_count(1), Some(1));
|
||||
assert_eq!(block_commitment_cache.get_confirmation_count(2), Some(0),);
|
||||
assert_eq!(block_commitment_cache.get_confirmation_count(3), None,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_rooted_block_with_commitment() {
|
||||
// Build BlockCommitmentCache with rooted votes
|
||||
let mut cache0 = BlockCommitment::new([0; MAX_LOCKOUT_HISTORY]);
|
||||
cache0.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, 40);
|
||||
cache0.increase_confirmation_stake(MAX_LOCKOUT_HISTORY - 1, 10);
|
||||
let mut cache1 = BlockCommitment::new([0; MAX_LOCKOUT_HISTORY]);
|
||||
cache1.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, 30);
|
||||
cache1.increase_confirmation_stake(MAX_LOCKOUT_HISTORY - 1, 10);
|
||||
cache1.increase_confirmation_stake(MAX_LOCKOUT_HISTORY - 2, 10);
|
||||
fn test_is_confirmed_rooted() {
|
||||
let bank = Arc::new(Bank::default());
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||
blockstore.set_roots(&[0, 1]).unwrap();
|
||||
// Build BlockCommitmentCache with rooted slots
|
||||
let mut cache0 = BlockCommitment::default();
|
||||
cache0.increase_rooted_stake(50);
|
||||
let mut cache1 = BlockCommitment::default();
|
||||
cache1.increase_rooted_stake(40);
|
||||
let mut cache2 = BlockCommitment::default();
|
||||
cache2.increase_rooted_stake(20);
|
||||
|
||||
let mut block_commitment = HashMap::new();
|
||||
block_commitment.entry(0).or_insert(cache0.clone());
|
||||
block_commitment.entry(1).or_insert(cache1.clone());
|
||||
let block_commitment_cache = BlockCommitmentCache::new(block_commitment, 50);
|
||||
block_commitment.entry(1).or_insert(cache0.clone());
|
||||
block_commitment.entry(2).or_insert(cache1.clone());
|
||||
block_commitment.entry(3).or_insert(cache2.clone());
|
||||
let largest_confirmed_root = 1;
|
||||
let block_commitment_cache = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
largest_confirmed_root,
|
||||
50,
|
||||
bank,
|
||||
blockstore,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
// Only slot 0 meets the minimum level of commitment 0.66 at root
|
||||
assert_eq!(
|
||||
block_commitment_cache.get_rooted_block_with_commitment(0.66),
|
||||
Some(0)
|
||||
assert!(block_commitment_cache.is_confirmed_rooted(0));
|
||||
assert!(block_commitment_cache.is_confirmed_rooted(1));
|
||||
assert!(!block_commitment_cache.is_confirmed_rooted(2));
|
||||
assert!(!block_commitment_cache.is_confirmed_rooted(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_largest_confirmed_root() {
|
||||
assert_eq!(get_largest_confirmed_root(vec![], 10), 0);
|
||||
let mut rooted_stake = vec![];
|
||||
rooted_stake.push((0, 5));
|
||||
rooted_stake.push((1, 5));
|
||||
assert_eq!(get_largest_confirmed_root(rooted_stake, 10), 0);
|
||||
let mut rooted_stake = vec![];
|
||||
rooted_stake.push((1, 5));
|
||||
rooted_stake.push((0, 10));
|
||||
rooted_stake.push((2, 5));
|
||||
rooted_stake.push((1, 4));
|
||||
assert_eq!(get_largest_confirmed_root(rooted_stake, 10), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highest_confirmed_slot() {
|
||||
let bank = Arc::new(Bank::new(&GenesisConfig::default()));
|
||||
let bank_slot_5 = Arc::new(Bank::new_from_parent(&bank, &Pubkey::default(), 5));
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||
let total_stake = 50;
|
||||
|
||||
// Build cache with confirmation_count 2 given total_stake
|
||||
let mut cache0 = BlockCommitment::default();
|
||||
cache0.increase_confirmation_stake(1, 5);
|
||||
cache0.increase_confirmation_stake(2, 40);
|
||||
|
||||
// Build cache with confirmation_count 1 given total_stake
|
||||
let mut cache1 = BlockCommitment::default();
|
||||
cache1.increase_confirmation_stake(1, 40);
|
||||
cache1.increase_confirmation_stake(2, 5);
|
||||
|
||||
// Build cache with confirmation_count 0 given total_stake
|
||||
let mut cache2 = BlockCommitment::default();
|
||||
cache2.increase_confirmation_stake(1, 20);
|
||||
cache2.increase_confirmation_stake(2, 5);
|
||||
|
||||
let mut block_commitment = HashMap::new();
|
||||
block_commitment.entry(1).or_insert(cache0.clone()); // Slot 1, conf 2
|
||||
block_commitment.entry(2).or_insert(cache1.clone()); // Slot 2, conf 1
|
||||
block_commitment.entry(3).or_insert(cache2.clone()); // Slot 3, conf 0
|
||||
let block_commitment_cache = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
0,
|
||||
total_stake,
|
||||
bank_slot_5.clone(),
|
||||
blockstore.clone(),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
// If multiple slots meet the minimum level of commitment, method should return the most recent
|
||||
assert_eq!(
|
||||
block_commitment_cache.get_rooted_block_with_commitment(0.6),
|
||||
Some(1)
|
||||
|
||||
assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 2);
|
||||
|
||||
// Build map with multiple slots at conf 1
|
||||
let mut block_commitment = HashMap::new();
|
||||
block_commitment.entry(1).or_insert(cache1.clone()); // Slot 1, conf 1
|
||||
block_commitment.entry(2).or_insert(cache1.clone()); // Slot 2, conf 1
|
||||
block_commitment.entry(3).or_insert(cache2.clone()); // Slot 3, conf 0
|
||||
let block_commitment_cache = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
0,
|
||||
total_stake,
|
||||
bank_slot_5.clone(),
|
||||
blockstore.clone(),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
// Neither slot meets the minimum level of commitment 0.9 at root
|
||||
assert_eq!(
|
||||
block_commitment_cache.get_rooted_block_with_commitment(0.9),
|
||||
None
|
||||
|
||||
assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 2);
|
||||
|
||||
// Build map with slot gaps
|
||||
let mut block_commitment = HashMap::new();
|
||||
block_commitment.entry(1).or_insert(cache1.clone()); // Slot 1, conf 1
|
||||
block_commitment.entry(3).or_insert(cache1.clone()); // Slot 3, conf 1
|
||||
block_commitment.entry(5).or_insert(cache2.clone()); // Slot 5, conf 0
|
||||
let block_commitment_cache = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
0,
|
||||
total_stake,
|
||||
bank_slot_5.clone(),
|
||||
blockstore.clone(),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 3);
|
||||
|
||||
// Build map with no conf 1 slots, but one higher
|
||||
let mut block_commitment = HashMap::new();
|
||||
block_commitment.entry(1).or_insert(cache0.clone()); // Slot 1, conf 2
|
||||
block_commitment.entry(2).or_insert(cache2.clone()); // Slot 2, conf 0
|
||||
block_commitment.entry(3).or_insert(cache2.clone()); // Slot 3, conf 0
|
||||
let block_commitment_cache = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
0,
|
||||
total_stake,
|
||||
bank_slot_5.clone(),
|
||||
blockstore.clone(),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 1);
|
||||
|
||||
// Build map with no conf 1 or higher slots
|
||||
let mut block_commitment = HashMap::new();
|
||||
block_commitment.entry(1).or_insert(cache2.clone()); // Slot 1, conf 0
|
||||
block_commitment.entry(2).or_insert(cache2.clone()); // Slot 2, conf 0
|
||||
block_commitment.entry(3).or_insert(cache2.clone()); // Slot 3, conf 0
|
||||
let block_commitment_cache = BlockCommitmentCache::new(
|
||||
block_commitment,
|
||||
0,
|
||||
total_stake,
|
||||
bank_slot_5.clone(),
|
||||
blockstore.clone(),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
|
||||
assert_eq!(block_commitment_cache.calculate_highest_confirmed_slot(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aggregate_commitment_for_vote_account_1() {
|
||||
let ancestors = vec![3, 4, 5, 7, 9, 11];
|
||||
let mut commitment = HashMap::new();
|
||||
let mut rooted_stake = vec![];
|
||||
let lamports = 5;
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
let root = ancestors.last().unwrap();
|
||||
vote_state.root_slot = Some(*root);
|
||||
let root = ancestors.last().unwrap().clone();
|
||||
vote_state.root_slot = Some(root);
|
||||
AggregateCommitmentService::aggregate_commitment_for_vote_account(
|
||||
&mut commitment,
|
||||
&mut rooted_stake,
|
||||
&vote_state,
|
||||
&ancestors,
|
||||
lamports,
|
||||
@@ -344,15 +655,17 @@ mod tests {
|
||||
|
||||
for a in ancestors {
|
||||
let mut expected = BlockCommitment::default();
|
||||
expected.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports);
|
||||
expected.increase_rooted_stake(lamports);
|
||||
assert_eq!(*commitment.get(&a).unwrap(), expected);
|
||||
}
|
||||
assert_eq!(rooted_stake[0], (root, lamports));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aggregate_commitment_for_vote_account_2() {
|
||||
let ancestors = vec![3, 4, 5, 7, 9, 11];
|
||||
let mut commitment = HashMap::new();
|
||||
let mut rooted_stake = vec![];
|
||||
let lamports = 5;
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
@@ -361,6 +674,7 @@ mod tests {
|
||||
vote_state.process_slot_vote_unchecked(*ancestors.last().unwrap());
|
||||
AggregateCommitmentService::aggregate_commitment_for_vote_account(
|
||||
&mut commitment,
|
||||
&mut rooted_stake,
|
||||
&vote_state,
|
||||
&ancestors,
|
||||
lamports,
|
||||
@@ -369,7 +683,7 @@ mod tests {
|
||||
for a in ancestors {
|
||||
if a <= root {
|
||||
let mut expected = BlockCommitment::default();
|
||||
expected.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports);
|
||||
expected.increase_rooted_stake(lamports);
|
||||
assert_eq!(*commitment.get(&a).unwrap(), expected);
|
||||
} else {
|
||||
let mut expected = BlockCommitment::default();
|
||||
@@ -377,12 +691,14 @@ mod tests {
|
||||
assert_eq!(*commitment.get(&a).unwrap(), expected);
|
||||
}
|
||||
}
|
||||
assert_eq!(rooted_stake[0], (root, lamports));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_aggregate_commitment_for_vote_account_3() {
|
||||
let ancestors = vec![3, 4, 5, 7, 9, 10, 11];
|
||||
let mut commitment = HashMap::new();
|
||||
let mut rooted_stake = vec![];
|
||||
let lamports = 5;
|
||||
let mut vote_state = VoteState::default();
|
||||
|
||||
@@ -393,6 +709,7 @@ mod tests {
|
||||
vote_state.process_slot_vote_unchecked(ancestors[6]);
|
||||
AggregateCommitmentService::aggregate_commitment_for_vote_account(
|
||||
&mut commitment,
|
||||
&mut rooted_stake,
|
||||
&vote_state,
|
||||
&ancestors,
|
||||
lamports,
|
||||
@@ -401,7 +718,7 @@ mod tests {
|
||||
for (i, a) in ancestors.iter().enumerate() {
|
||||
if *a <= root {
|
||||
let mut expected = BlockCommitment::default();
|
||||
expected.increase_confirmation_stake(MAX_LOCKOUT_HISTORY, lamports);
|
||||
expected.increase_rooted_stake(lamports);
|
||||
assert_eq!(*commitment.get(&a).unwrap(), expected);
|
||||
} else if i <= 4 {
|
||||
let mut expected = BlockCommitment::default();
|
||||
@@ -413,6 +730,7 @@ mod tests {
|
||||
assert_eq!(*commitment.get(&a).unwrap(), expected);
|
||||
}
|
||||
}
|
||||
assert_eq!(rooted_stake[0], (root, lamports));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -422,6 +740,8 @@ mod tests {
|
||||
mut genesis_config, ..
|
||||
} = create_genesis_config(10_000);
|
||||
|
||||
let rooted_stake_amount = 40;
|
||||
|
||||
let sk1 = Pubkey::new_rand();
|
||||
let pk1 = Pubkey::new_rand();
|
||||
let mut vote_account1 = vote_state::create_account(&pk1, &Pubkey::new_rand(), 0, 100);
|
||||
@@ -432,12 +752,36 @@ mod tests {
|
||||
let mut vote_account2 = vote_state::create_account(&pk2, &Pubkey::new_rand(), 0, 50);
|
||||
let stake_account2 =
|
||||
stake_state::create_account(&sk2, &pk2, &vote_account2, &genesis_config.rent, 50);
|
||||
let sk3 = Pubkey::new_rand();
|
||||
let pk3 = Pubkey::new_rand();
|
||||
let mut vote_account3 = vote_state::create_account(&pk3, &Pubkey::new_rand(), 0, 1);
|
||||
let stake_account3 = stake_state::create_account(
|
||||
&sk3,
|
||||
&pk3,
|
||||
&vote_account3,
|
||||
&genesis_config.rent,
|
||||
rooted_stake_amount,
|
||||
);
|
||||
let sk4 = Pubkey::new_rand();
|
||||
let pk4 = Pubkey::new_rand();
|
||||
let mut vote_account4 = vote_state::create_account(&pk4, &Pubkey::new_rand(), 0, 1);
|
||||
let stake_account4 = stake_state::create_account(
|
||||
&sk4,
|
||||
&pk4,
|
||||
&vote_account4,
|
||||
&genesis_config.rent,
|
||||
rooted_stake_amount,
|
||||
);
|
||||
|
||||
genesis_config.accounts.extend(vec![
|
||||
(pk1, vote_account1.clone()),
|
||||
(sk1, stake_account1),
|
||||
(pk2, vote_account2.clone()),
|
||||
(sk2, stake_account2),
|
||||
(pk3, vote_account3.clone()),
|
||||
(sk3, stake_account3),
|
||||
(pk4, vote_account4.clone()),
|
||||
(sk4, stake_account4),
|
||||
]);
|
||||
|
||||
// Create bank
|
||||
@@ -457,7 +801,20 @@ mod tests {
|
||||
VoteState::to(&versioned, &mut vote_account2).unwrap();
|
||||
bank.store_account(&pk2, &vote_account2);
|
||||
|
||||
let commitment = AggregateCommitmentService::aggregate_commitment(&ancestors, &bank);
|
||||
let mut vote_state3 = VoteState::from(&vote_account3).unwrap();
|
||||
vote_state3.root_slot = Some(1);
|
||||
let versioned = VoteStateVersions::Current(Box::new(vote_state3));
|
||||
VoteState::to(&versioned, &mut vote_account3).unwrap();
|
||||
bank.store_account(&pk3, &vote_account3);
|
||||
|
||||
let mut vote_state4 = VoteState::from(&vote_account4).unwrap();
|
||||
vote_state4.root_slot = Some(2);
|
||||
let versioned = VoteStateVersions::Current(Box::new(vote_state4));
|
||||
VoteState::to(&versioned, &mut vote_account4).unwrap();
|
||||
bank.store_account(&pk4, &vote_account4);
|
||||
|
||||
let (commitment, rooted_stake) =
|
||||
AggregateCommitmentService::aggregate_commitment(&ancestors, &bank);
|
||||
|
||||
for a in ancestors {
|
||||
if a <= 3 {
|
||||
@@ -481,5 +838,7 @@ mod tests {
|
||||
assert!(commitment.get(&a).is_none());
|
||||
}
|
||||
}
|
||||
assert_eq!(rooted_stake.len(), 2);
|
||||
assert_eq!(get_largest_confirmed_root(rooted_stake, 100), 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::progress_map::ProgressMap;
|
||||
use chrono::prelude::*;
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use solana_runtime::bank::Bank;
|
||||
@@ -320,7 +321,7 @@ impl Tower {
|
||||
}
|
||||
pub fn check_vote_stake_threshold(
|
||||
&self,
|
||||
slot: u64,
|
||||
slot: Slot,
|
||||
stake_lockouts: &HashMap<u64, StakeLockout>,
|
||||
total_staked: u64,
|
||||
) -> bool {
|
||||
@@ -331,11 +332,8 @@ impl Tower {
|
||||
if let Some(fork_stake) = stake_lockouts.get(&vote.slot) {
|
||||
let lockout = fork_stake.stake as f64 / total_staked as f64;
|
||||
trace!(
|
||||
"fork_stake slot: {} lockout: {} fork_stake: {} total_stake: {}",
|
||||
slot,
|
||||
lockout,
|
||||
fork_stake.stake,
|
||||
total_staked
|
||||
"fork_stake slot: {}, vote slot: {}, lockout: {} fork_stake: {} total_stake: {}",
|
||||
slot, vote.slot, lockout, fork_stake.stake, total_staked
|
||||
);
|
||||
if vote.confirmation_count as usize > self.threshold_depth {
|
||||
for old_vote in &self.lockouts.votes {
|
||||
@@ -355,6 +353,18 @@ impl Tower {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_switch_threshold(
|
||||
&self,
|
||||
_slot: Slot,
|
||||
_ancestors: &HashMap<Slot, HashSet<u64>>,
|
||||
_descendants: &HashMap<Slot, HashSet<u64>>,
|
||||
_progress: &ProgressMap,
|
||||
_total_epoch_stake: u64,
|
||||
_epoch_vote_accounts: &HashMap<Pubkey, (u64, Account)>,
|
||||
) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Update lockouts for all the ancestors
|
||||
fn update_ancestor_lockouts(
|
||||
stake_lockouts: &mut HashMap<Slot, StakeLockout>,
|
||||
@@ -468,7 +478,12 @@ impl Tower {
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use crate::replay_stage::{ForkProgress, ReplayStage};
|
||||
use crate::{
|
||||
cluster_info_vote_listener::VoteTracker,
|
||||
cluster_slots::ClusterSlots,
|
||||
progress_map::ForkProgress,
|
||||
replay_stage::{HeaviestForkFailures, ReplayStage},
|
||||
};
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use solana_runtime::{
|
||||
bank::Bank,
|
||||
@@ -481,107 +496,96 @@ pub mod test {
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_vote_program::{
|
||||
vote_instruction,
|
||||
vote_state::{Vote, VoteStateVersions},
|
||||
vote_transaction,
|
||||
};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
use std::{thread::sleep, time::Duration};
|
||||
use trees::{tr, Node, Tree};
|
||||
use trees::{tr, Tree, TreeWalk};
|
||||
|
||||
pub(crate) struct VoteSimulator<'a> {
|
||||
searchable_nodes: HashMap<u64, &'a Node<u64>>,
|
||||
pub(crate) struct VoteSimulator {
|
||||
pub validator_keypairs: HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
pub node_pubkeys: Vec<Pubkey>,
|
||||
pub vote_pubkeys: Vec<Pubkey>,
|
||||
pub bank_forks: RwLock<BankForks>,
|
||||
pub progress: ProgressMap,
|
||||
}
|
||||
|
||||
impl<'a> VoteSimulator<'a> {
|
||||
pub(crate) fn new(forks: &'a Tree<u64>) -> Self {
|
||||
let mut searchable_nodes = HashMap::new();
|
||||
let root = forks.root();
|
||||
searchable_nodes.insert(root.data, root);
|
||||
Self { searchable_nodes }
|
||||
impl VoteSimulator {
|
||||
pub(crate) fn new(num_keypairs: usize) -> Self {
|
||||
let (validator_keypairs, node_pubkeys, vote_pubkeys, bank_forks, progress) =
|
||||
Self::init_state(num_keypairs);
|
||||
Self {
|
||||
validator_keypairs,
|
||||
node_pubkeys,
|
||||
vote_pubkeys,
|
||||
bank_forks: RwLock::new(bank_forks),
|
||||
progress,
|
||||
}
|
||||
}
|
||||
pub(crate) fn fill_bank_forks(
|
||||
&mut self,
|
||||
forks: Tree<u64>,
|
||||
cluster_votes: &HashMap<Pubkey, Vec<u64>>,
|
||||
) {
|
||||
let root = forks.root().data;
|
||||
assert!(self.bank_forks.read().unwrap().get(root).is_some());
|
||||
|
||||
let mut walk = TreeWalk::from(forks);
|
||||
loop {
|
||||
if let Some(visit) = walk.get() {
|
||||
let slot = visit.node().data;
|
||||
self.progress
|
||||
.entry(slot)
|
||||
.or_insert_with(|| ForkProgress::new(Hash::default(), None, None, 0, 0));
|
||||
if self.bank_forks.read().unwrap().get(slot).is_some() {
|
||||
walk.forward();
|
||||
continue;
|
||||
}
|
||||
let parent = walk.get_parent().unwrap().data;
|
||||
let parent_bank = self.bank_forks.read().unwrap().get(parent).unwrap().clone();
|
||||
let new_bank = Bank::new_from_parent(&parent_bank, &Pubkey::default(), slot);
|
||||
for (pubkey, vote) in cluster_votes.iter() {
|
||||
if vote.contains(&parent) {
|
||||
let keypairs = self.validator_keypairs.get(pubkey).unwrap();
|
||||
let last_blockhash = parent_bank.last_blockhash();
|
||||
let vote_tx = vote_transaction::new_vote_transaction(
|
||||
// Must vote > root to be processed
|
||||
vec![parent],
|
||||
parent_bank.hash(),
|
||||
last_blockhash,
|
||||
&keypairs.node_keypair,
|
||||
&keypairs.vote_keypair,
|
||||
&keypairs.vote_keypair,
|
||||
);
|
||||
info!("voting {} {}", parent_bank.slot(), parent_bank.hash());
|
||||
new_bank.process_transaction(&vote_tx).unwrap();
|
||||
}
|
||||
}
|
||||
new_bank.freeze();
|
||||
self.bank_forks.write().unwrap().insert(new_bank);
|
||||
walk.forward();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn simulate_vote(
|
||||
&mut self,
|
||||
vote_slot: Slot,
|
||||
bank_forks: &RwLock<BankForks>,
|
||||
cluster_votes: &mut HashMap<Pubkey, Vec<u64>>,
|
||||
validator_keypairs: &HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
my_keypairs: &ValidatorVoteKeypairs,
|
||||
progress: &mut HashMap<u64, ForkProgress>,
|
||||
my_pubkey: &Pubkey,
|
||||
tower: &mut Tower,
|
||||
) -> Vec<VoteFailures> {
|
||||
let node = self
|
||||
.find_node_and_update_simulation(vote_slot)
|
||||
.expect("Vote to simulate must be for a slot in the tree");
|
||||
|
||||
let mut missing_nodes = VecDeque::new();
|
||||
let mut current = node;
|
||||
loop {
|
||||
let current_slot = current.data;
|
||||
if bank_forks.read().unwrap().get(current_slot).is_some()
|
||||
|| tower.root().map(|r| current_slot < r).unwrap_or(false)
|
||||
{
|
||||
break;
|
||||
} else {
|
||||
missing_nodes.push_front(current);
|
||||
}
|
||||
|
||||
if let Some(parent) = current.parent() {
|
||||
current = parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create any missing banks along the path
|
||||
for missing_node in missing_nodes {
|
||||
let missing_slot = missing_node.data;
|
||||
let parent = missing_node.parent().unwrap().data;
|
||||
let parent_bank = bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(parent)
|
||||
.expect("parent bank must exist")
|
||||
.clone();
|
||||
info!("parent of {} is {}", missing_slot, parent_bank.slot(),);
|
||||
progress
|
||||
.entry(missing_slot)
|
||||
.or_insert_with(|| ForkProgress::new(parent_bank.last_blockhash()));
|
||||
|
||||
// Create the missing bank
|
||||
let new_bank =
|
||||
Bank::new_from_parent(&parent_bank, &Pubkey::default(), missing_slot);
|
||||
|
||||
// Simulate ingesting the cluster's votes for the parent into this bank
|
||||
for (pubkey, vote) in cluster_votes.iter() {
|
||||
if vote.contains(&parent_bank.slot()) {
|
||||
let keypairs = validator_keypairs.get(pubkey).unwrap();
|
||||
let node_pubkey = keypairs.node_keypair.pubkey();
|
||||
let vote_pubkey = keypairs.vote_keypair.pubkey();
|
||||
let last_blockhash = parent_bank.last_blockhash();
|
||||
let votes = Vote::new(vec![parent_bank.slot()], parent_bank.hash());
|
||||
info!("voting {} {}", parent_bank.slot(), parent_bank.hash());
|
||||
let vote_ix = vote_instruction::vote(&vote_pubkey, &vote_pubkey, votes);
|
||||
let mut vote_tx =
|
||||
Transaction::new_with_payer(vec![vote_ix], Some(&node_pubkey));
|
||||
vote_tx.partial_sign(&[&keypairs.node_keypair], last_blockhash);
|
||||
vote_tx.partial_sign(&[&keypairs.vote_keypair], last_blockhash);
|
||||
new_bank.process_transaction(&vote_tx).unwrap();
|
||||
}
|
||||
}
|
||||
new_bank.freeze();
|
||||
bank_forks.write().unwrap().insert(new_bank);
|
||||
}
|
||||
|
||||
// Now try to simulate the vote
|
||||
let my_pubkey = my_keypairs.node_keypair.pubkey();
|
||||
) -> Vec<HeaviestForkFailures> {
|
||||
// Try to simulate the vote
|
||||
let my_keypairs = self.validator_keypairs.get(&my_pubkey).unwrap();
|
||||
let my_vote_pubkey = my_keypairs.vote_keypair.pubkey();
|
||||
let ancestors = bank_forks.read().unwrap().ancestors();
|
||||
let mut frozen_banks: Vec<_> = bank_forks
|
||||
let ancestors = self.bank_forks.read().unwrap().ancestors();
|
||||
let mut frozen_banks: Vec<_> = self
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.frozen_banks()
|
||||
@@ -594,99 +598,133 @@ pub mod test {
|
||||
&ancestors,
|
||||
&mut frozen_banks,
|
||||
tower,
|
||||
progress,
|
||||
&mut self.progress,
|
||||
&VoteTracker::default(),
|
||||
&ClusterSlots::default(),
|
||||
&self.bank_forks,
|
||||
&mut HashSet::new(),
|
||||
);
|
||||
|
||||
let bank = bank_forks
|
||||
let vote_bank = self
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(vote_slot)
|
||||
.expect("Bank must have been created before vote simulation")
|
||||
.clone();
|
||||
|
||||
// Try to vote on the given slot
|
||||
let descendants = self.bank_forks.read().unwrap().descendants();
|
||||
let (_, _, failure_reasons) = ReplayStage::select_vote_and_reset_forks(
|
||||
&Some(vote_bank.clone()),
|
||||
&None,
|
||||
&ancestors,
|
||||
&descendants,
|
||||
&self.progress,
|
||||
&tower,
|
||||
);
|
||||
|
||||
// Make sure this slot isn't locked out or failing threshold
|
||||
let fork_progress = progress
|
||||
.get(&vote_slot)
|
||||
.expect("Slot for vote must exist in progress map");
|
||||
info!("Checking vote: {}", vote_slot);
|
||||
info!("lockouts: {:?}", fork_progress.fork_stats.stake_lockouts);
|
||||
let mut failures = vec![];
|
||||
if fork_progress.fork_stats.is_locked_out {
|
||||
failures.push(VoteFailures::LockedOut(vote_slot));
|
||||
info!("Checking vote: {}", vote_bank.slot());
|
||||
if !failure_reasons.is_empty() {
|
||||
return failure_reasons;
|
||||
}
|
||||
if !fork_progress.fork_stats.vote_threshold {
|
||||
failures.push(VoteFailures::FailedThreshold(vote_slot));
|
||||
}
|
||||
if !failures.is_empty() {
|
||||
return failures;
|
||||
}
|
||||
let vote = tower.new_vote_from_bank(&bank, &my_vote_pubkey).0;
|
||||
let vote = tower.new_vote_from_bank(&vote_bank, &my_vote_pubkey).0;
|
||||
if let Some(new_root) = tower.record_bank_vote(vote) {
|
||||
ReplayStage::handle_new_root(new_root, bank_forks, progress, &None);
|
||||
ReplayStage::handle_new_root(
|
||||
new_root,
|
||||
&self.bank_forks,
|
||||
&mut self.progress,
|
||||
&None,
|
||||
&mut HashSet::new(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
// Mark the vote for this bank under this node's pubkey so it will be
|
||||
// integrated into any future child banks
|
||||
cluster_votes.entry(my_pubkey).or_default().push(vote_slot);
|
||||
vec![]
|
||||
}
|
||||
|
||||
// Find a node representing the given slot
|
||||
fn find_node_and_update_simulation(&mut self, slot: u64) -> Option<&'a Node<u64>> {
|
||||
let mut successful_search_node: Option<&'a Node<u64>> = None;
|
||||
let mut found_node = None;
|
||||
for search_node in self.searchable_nodes.values() {
|
||||
if let Some((target, new_searchable_nodes)) = Self::find_node(search_node, slot) {
|
||||
successful_search_node = Some(search_node);
|
||||
found_node = Some(target);
|
||||
for node in new_searchable_nodes {
|
||||
self.searchable_nodes.insert(node.data, node);
|
||||
}
|
||||
break;
|
||||
fn can_progress_on_fork(
|
||||
&mut self,
|
||||
my_pubkey: &Pubkey,
|
||||
tower: &mut Tower,
|
||||
start_slot: u64,
|
||||
num_slots: u64,
|
||||
cluster_votes: &mut HashMap<Pubkey, Vec<u64>>,
|
||||
) -> bool {
|
||||
// Check that within some reasonable time, validator can make a new
|
||||
// root on this fork
|
||||
let old_root = tower.root();
|
||||
|
||||
for i in 1..num_slots {
|
||||
// The parent of the tip of the fork
|
||||
let mut fork_tip_parent = tr(start_slot + i - 1);
|
||||
// The tip of the fork
|
||||
fork_tip_parent.push_front(tr(start_slot + i));
|
||||
self.fill_bank_forks(fork_tip_parent, cluster_votes);
|
||||
if self
|
||||
.simulate_vote(i + start_slot, &my_pubkey, tower)
|
||||
.is_empty()
|
||||
{
|
||||
cluster_votes
|
||||
.entry(*my_pubkey)
|
||||
.or_default()
|
||||
.push(start_slot + i);
|
||||
}
|
||||
if old_root != tower.root() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
successful_search_node.map(|node| {
|
||||
self.searchable_nodes.remove(&node.data);
|
||||
});
|
||||
found_node
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn find_node(
|
||||
node: &'a Node<u64>,
|
||||
slot: u64,
|
||||
) -> Option<(&'a Node<u64>, Vec<&'a Node<u64>>)> {
|
||||
if node.data == slot {
|
||||
Some((node, node.iter().collect()))
|
||||
} else {
|
||||
let mut search_result: Option<(&'a Node<u64>, Vec<&'a Node<u64>>)> = None;
|
||||
for child in node.iter() {
|
||||
if let Some((_, ref mut new_searchable_nodes)) = search_result {
|
||||
new_searchable_nodes.push(child);
|
||||
continue;
|
||||
}
|
||||
search_result = Self::find_node(child, slot);
|
||||
}
|
||||
fn init_state(
|
||||
num_keypairs: usize,
|
||||
) -> (
|
||||
HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
Vec<Pubkey>,
|
||||
Vec<Pubkey>,
|
||||
BankForks,
|
||||
ProgressMap,
|
||||
) {
|
||||
let keypairs: HashMap<_, _> = std::iter::repeat_with(|| {
|
||||
let node_keypair = Keypair::new();
|
||||
let vote_keypair = Keypair::new();
|
||||
let stake_keypair = Keypair::new();
|
||||
let node_pubkey = node_keypair.pubkey();
|
||||
(
|
||||
node_pubkey,
|
||||
ValidatorVoteKeypairs::new(node_keypair, vote_keypair, stake_keypair),
|
||||
)
|
||||
})
|
||||
.take(num_keypairs)
|
||||
.collect();
|
||||
let node_pubkeys: Vec<_> = keypairs
|
||||
.values()
|
||||
.map(|keys| keys.node_keypair.pubkey())
|
||||
.collect();
|
||||
let vote_pubkeys: Vec<_> = keypairs
|
||||
.values()
|
||||
.map(|keys| keys.vote_keypair.pubkey())
|
||||
.collect();
|
||||
|
||||
search_result
|
||||
}
|
||||
let (bank_forks, progress) = initialize_state(&keypairs, 10_000);
|
||||
(keypairs, node_pubkeys, vote_pubkeys, bank_forks, progress)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub(crate) enum VoteFailures {
|
||||
LockedOut(u64),
|
||||
FailedThreshold(u64),
|
||||
}
|
||||
|
||||
// Setup BankForks with bank 0 and all the validator accounts
|
||||
pub(crate) fn initialize_state(
|
||||
validator_keypairs_map: &HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
) -> (BankForks, HashMap<u64, ForkProgress>) {
|
||||
stake: u64,
|
||||
) -> (BankForks, ProgressMap) {
|
||||
let validator_keypairs: Vec<_> = validator_keypairs_map.values().collect();
|
||||
let GenesisConfigInfo {
|
||||
genesis_config,
|
||||
mint_keypair,
|
||||
voting_keypair: _,
|
||||
} = create_genesis_config_with_vote_accounts(1_000_000_000, &validator_keypairs);
|
||||
} = create_genesis_config_with_vote_accounts(1_000_000_000, &validator_keypairs, stake);
|
||||
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
|
||||
@@ -695,8 +733,11 @@ pub mod test {
|
||||
}
|
||||
|
||||
bank0.freeze();
|
||||
let mut progress = HashMap::new();
|
||||
progress.insert(0, ForkProgress::new(bank0.last_blockhash()));
|
||||
let mut progress = ProgressMap::default();
|
||||
progress.insert(
|
||||
0,
|
||||
ForkProgress::new(bank0.last_blockhash(), None, None, 0, 0),
|
||||
);
|
||||
(BankForks::new(0, bank0), progress)
|
||||
}
|
||||
|
||||
@@ -720,84 +761,26 @@ pub mod test {
|
||||
stakes
|
||||
}
|
||||
|
||||
fn can_progress_on_fork(
|
||||
my_pubkey: &Pubkey,
|
||||
tower: &mut Tower,
|
||||
start_slot: u64,
|
||||
num_slots: u64,
|
||||
bank_forks: &RwLock<BankForks>,
|
||||
cluster_votes: &mut HashMap<Pubkey, Vec<u64>>,
|
||||
keypairs: &HashMap<Pubkey, ValidatorVoteKeypairs>,
|
||||
progress: &mut HashMap<u64, ForkProgress>,
|
||||
) -> bool {
|
||||
// Check that within some reasonable time, validator can make a new
|
||||
// root on this fork
|
||||
let old_root = tower.root();
|
||||
let mut main_fork = tr(start_slot);
|
||||
let mut tip = main_fork.root_mut();
|
||||
|
||||
for i in 1..num_slots {
|
||||
tip.push_front(tr(start_slot + i));
|
||||
tip = tip.first_mut().unwrap();
|
||||
}
|
||||
let mut voting_simulator = VoteSimulator::new(&main_fork);
|
||||
for i in 1..num_slots {
|
||||
voting_simulator.simulate_vote(
|
||||
i + start_slot,
|
||||
&bank_forks,
|
||||
cluster_votes,
|
||||
&keypairs,
|
||||
keypairs.get(&my_pubkey).unwrap(),
|
||||
progress,
|
||||
tower,
|
||||
);
|
||||
if old_root != tower.root() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_votes() {
|
||||
let node_keypair = Keypair::new();
|
||||
let vote_keypair = Keypair::new();
|
||||
let stake_keypair = Keypair::new();
|
||||
let node_pubkey = node_keypair.pubkey();
|
||||
|
||||
let mut keypairs = HashMap::new();
|
||||
keypairs.insert(
|
||||
node_pubkey,
|
||||
ValidatorVoteKeypairs::new(node_keypair, vote_keypair, stake_keypair),
|
||||
);
|
||||
|
||||
// Initialize BankForks
|
||||
let (bank_forks, mut progress) = initialize_state(&keypairs);
|
||||
let bank_forks = RwLock::new(bank_forks);
|
||||
// Init state
|
||||
let mut vote_simulator = VoteSimulator::new(1);
|
||||
let node_pubkey = vote_simulator.node_pubkeys[0];
|
||||
let mut tower = Tower::new_with_key(&node_pubkey);
|
||||
|
||||
// Create the tree of banks
|
||||
let forks = tr(0) / (tr(1) / (tr(2) / (tr(3) / (tr(4) / tr(5)))));
|
||||
|
||||
// Set the voting behavior
|
||||
let mut voting_simulator = VoteSimulator::new(&forks);
|
||||
let mut cluster_votes = HashMap::new();
|
||||
let votes = vec![0, 1, 2, 3, 4, 5];
|
||||
cluster_votes.insert(node_pubkey, votes.clone());
|
||||
vote_simulator.fill_bank_forks(forks, &cluster_votes);
|
||||
|
||||
// Simulate the votes
|
||||
let mut tower = Tower::new_with_key(&node_pubkey);
|
||||
|
||||
let mut cluster_votes = HashMap::new();
|
||||
for vote in votes {
|
||||
assert!(voting_simulator
|
||||
.simulate_vote(
|
||||
vote,
|
||||
&bank_forks,
|
||||
&mut cluster_votes,
|
||||
&keypairs,
|
||||
keypairs.get(&node_pubkey).unwrap(),
|
||||
&mut progress,
|
||||
&mut tower,
|
||||
)
|
||||
assert!(vote_simulator
|
||||
.simulate_vote(vote, &node_pubkey, &mut tower,)
|
||||
.is_empty());
|
||||
}
|
||||
|
||||
@@ -809,21 +792,14 @@ pub mod test {
|
||||
|
||||
#[test]
|
||||
fn test_double_partition() {
|
||||
solana_logger::setup();
|
||||
let node_keypair = Keypair::new();
|
||||
let vote_keypair = Keypair::new();
|
||||
let stake_keypair = Keypair::new();
|
||||
let node_pubkey = node_keypair.pubkey();
|
||||
let vote_pubkey = vote_keypair.pubkey();
|
||||
// Init state
|
||||
let mut vote_simulator = VoteSimulator::new(2);
|
||||
let node_pubkey = vote_simulator.node_pubkeys[0];
|
||||
let vote_pubkey = vote_simulator.vote_pubkeys[0];
|
||||
let mut tower = Tower::new_with_key(&node_pubkey);
|
||||
|
||||
let mut keypairs = HashMap::new();
|
||||
info!("my_pubkey: {}", node_pubkey);
|
||||
keypairs.insert(
|
||||
node_pubkey,
|
||||
ValidatorVoteKeypairs::new(node_keypair, vote_keypair, stake_keypair),
|
||||
);
|
||||
|
||||
// Create the tree of banks in a BankForks object
|
||||
let num_slots_to_try = 200;
|
||||
// Create the tree of banks
|
||||
let forks = tr(0)
|
||||
/ (tr(1)
|
||||
/ (tr(2)
|
||||
@@ -840,56 +816,37 @@ pub mod test {
|
||||
/ (tr(44)
|
||||
// Minor fork 2
|
||||
/ (tr(45) / (tr(46) / (tr(47) / (tr(48) / (tr(49) / (tr(50)))))))
|
||||
/ (tr(110)))))))))))));
|
||||
/ (tr(110) / (tr(110 + 2 * num_slots_to_try))))))))))))));
|
||||
|
||||
// Set the voting behavior
|
||||
let mut voting_simulator = VoteSimulator::new(&forks);
|
||||
let mut votes: Vec<Slot> = vec![];
|
||||
// Set the successful voting behavior
|
||||
let mut cluster_votes = HashMap::new();
|
||||
let mut my_votes: Vec<Slot> = vec![];
|
||||
let next_unlocked_slot = 110;
|
||||
// Vote on the first minor fork
|
||||
votes.extend((0..=14).into_iter());
|
||||
my_votes.extend((0..=14).into_iter());
|
||||
// Come back to the main fork
|
||||
votes.extend((43..=44).into_iter());
|
||||
my_votes.extend((43..=44).into_iter());
|
||||
// Vote on the second minor fork
|
||||
votes.extend((45..=50).into_iter());
|
||||
my_votes.extend((45..=50).into_iter());
|
||||
// Vote to come back to main fork
|
||||
my_votes.push(next_unlocked_slot);
|
||||
cluster_votes.insert(node_pubkey, my_votes.clone());
|
||||
// Make the other validator vote fork to pass the threshold checks
|
||||
let other_votes = my_votes.clone();
|
||||
cluster_votes.insert(vote_simulator.node_pubkeys[1], other_votes);
|
||||
vote_simulator.fill_bank_forks(forks, &cluster_votes);
|
||||
|
||||
let mut cluster_votes: HashMap<Pubkey, Vec<Slot>> = HashMap::new();
|
||||
let (bank_forks, mut progress) = initialize_state(&keypairs);
|
||||
let bank_forks = RwLock::new(bank_forks);
|
||||
|
||||
// Simulate the votes. Should fail on trying to come back to the main fork
|
||||
// at 106 exclusively due to threshold failure
|
||||
let mut tower = Tower::new_with_key(&node_pubkey);
|
||||
for vote in &votes {
|
||||
// Simulate the votes.
|
||||
for vote in &my_votes {
|
||||
// All these votes should be ok
|
||||
assert!(voting_simulator
|
||||
.simulate_vote(
|
||||
*vote,
|
||||
&bank_forks,
|
||||
&mut cluster_votes,
|
||||
&keypairs,
|
||||
keypairs.get(&node_pubkey).unwrap(),
|
||||
&mut progress,
|
||||
&mut tower,
|
||||
)
|
||||
assert!(vote_simulator
|
||||
.simulate_vote(*vote, &node_pubkey, &mut tower,)
|
||||
.is_empty());
|
||||
}
|
||||
|
||||
// Try to come back to main fork
|
||||
let next_unlocked_slot = 110;
|
||||
assert!(voting_simulator
|
||||
.simulate_vote(
|
||||
next_unlocked_slot,
|
||||
&bank_forks,
|
||||
&mut cluster_votes,
|
||||
&keypairs,
|
||||
keypairs.get(&node_pubkey).unwrap(),
|
||||
&mut progress,
|
||||
&mut tower,
|
||||
)
|
||||
.is_empty());
|
||||
|
||||
info!("local tower: {:#?}", tower.lockouts.votes);
|
||||
let vote_accounts = bank_forks
|
||||
let vote_accounts = vote_simulator
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(next_unlocked_slot)
|
||||
@@ -899,15 +856,17 @@ pub mod test {
|
||||
let state = VoteState::from(&observed.1).unwrap();
|
||||
info!("observed tower: {:#?}", state.votes);
|
||||
|
||||
assert!(can_progress_on_fork(
|
||||
let num_slots_to_try = 200;
|
||||
cluster_votes
|
||||
.get_mut(&vote_simulator.node_pubkeys[1])
|
||||
.unwrap()
|
||||
.extend(next_unlocked_slot + 1..next_unlocked_slot + num_slots_to_try);
|
||||
assert!(vote_simulator.can_progress_on_fork(
|
||||
&node_pubkey,
|
||||
&mut tower,
|
||||
next_unlocked_slot,
|
||||
200,
|
||||
&bank_forks,
|
||||
num_slots_to_try,
|
||||
&mut cluster_votes,
|
||||
&keypairs,
|
||||
&mut progress
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::crds_value::MAX_WALLCLOCK;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
#[cfg(test)]
|
||||
use solana_sdk::rpc_port;
|
||||
use solana_sdk::sanitize::{Sanitize, SanitizeError};
|
||||
#[cfg(test)]
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::timing::timestamp;
|
||||
@@ -37,6 +39,15 @@ pub struct ContactInfo {
|
||||
pub shred_version: u16,
|
||||
}
|
||||
|
||||
impl Sanitize for ContactInfo {
|
||||
fn sanitize(&self) -> std::result::Result<(), SanitizeError> {
|
||||
if self.wallclock >= MAX_WALLCLOCK {
|
||||
return Err(SanitizeError::ValueOutOfBounds);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for ContactInfo {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.id.cmp(&other.id)
|
||||
@@ -314,4 +325,12 @@ mod tests {
|
||||
ci.rpc = socketaddr!("127.0.0.1:234");
|
||||
assert!(ci.valid_client_facing_addr().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize() {
|
||||
let mut ci = ContactInfo::default();
|
||||
assert_eq!(ci.sanitize(), Ok(()));
|
||||
ci.wallclock = MAX_WALLCLOCK;
|
||||
assert_eq!(ci.sanitize(), Err(SanitizeError::ValueOutOfBounds));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user