Compare commits
1650 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4b5a05bf38 | ||
|
7dd7141307 | ||
|
e5175c843d | ||
|
d5ff64b0d7 | ||
|
0fbdc7e152 | ||
|
49aca9ecd8 | ||
|
fcc147b4f2 | ||
|
c455d1b1c5 | ||
|
e9b29fc697 | ||
|
fdea6fad26 | ||
|
a4bc31341a | ||
|
4af797c0a2 | ||
|
a1e06df4a8 | ||
|
1f2480fd9f | ||
|
8587bd0d69 | ||
|
0063a58e95 | ||
|
9aeb3bc5d6 | ||
|
97665b977e | ||
|
c45ed29cf4 | ||
|
635afbabff | ||
|
98afdad1dd | ||
|
c085b94b43 | ||
|
a53946c485 | ||
|
f6de92c346 | ||
|
46f9822d62 | ||
|
6dad84d228 | ||
|
3582607aa0 | ||
|
f051077350 | ||
|
ffd6f3e6bf | ||
|
c6b2eb07ee | ||
|
7a3e1f9826 | ||
|
8a690b6cf7 | ||
|
8688efa89b | ||
|
3b047e5b99 | ||
|
3cddc731b2 | ||
|
1d29a583c6 | ||
|
b5335edb35 | ||
|
abee1e83eb | ||
|
6c5be574c8 | ||
|
c1f993d2fc | ||
|
e2ddb2f0ea | ||
|
f3faba5ca9 | ||
|
3a6fd91739 | ||
|
2d2b3d8287 | ||
|
6e47b88399 | ||
|
941e56c6c7 | ||
|
d1adc2a446 | ||
|
02da7dfedf | ||
|
eb0fd3625a | ||
|
b87e606626 | ||
|
1c91376f78 | ||
|
10067ad07b | ||
|
eb76289107 | ||
|
8926736e1c | ||
|
bf4c169703 | ||
|
0020e43476 | ||
|
a9a2c76221 | ||
|
4754b4e871 | ||
|
52ffb9a64a | ||
|
bd0b1503c6 | ||
|
10e7fa40ac | ||
|
198ed407b7 | ||
|
d96af2dd23 | ||
|
192cca8f98 | ||
|
ee716e1c55 | ||
|
6dd3c7c2dd | ||
|
582b4c9edf | ||
|
f15add2a74 | ||
|
74d48910e2 | ||
|
c53e8ee3ad | ||
|
c5e5fedc47 | ||
|
b9929dcd67 | ||
|
554a158443 | ||
|
b7fa4b7ee1 | ||
|
fd44cee8cc | ||
|
c6a362cce2 | ||
|
252180c244 | ||
|
e02b4e698e | ||
|
4811afe8eb | ||
|
bc4568b10f | ||
|
d59c131e90 | ||
|
825027f9f7 | ||
|
9b8f0bee99 | ||
|
fc13c1d654 | ||
|
a57758e9c9 | ||
|
564590462a | ||
|
269f6af97e | ||
|
57b8a59632 | ||
|
4289f52d2b | ||
|
573f68620b | ||
|
4bfe64688b | ||
|
50034848a5 | ||
|
981294cbc6 | ||
|
ff728e5e56 | ||
|
9aaf41bef2 | ||
|
271eec656c | ||
|
13d071607f | ||
|
ffe35d9a10 | ||
|
bb2fb07b39 | ||
|
85fc51dc61 | ||
|
0276b6c4c2 | ||
|
c481e4fe7f | ||
|
76a3b3ad11 | ||
|
356c663e88 | ||
|
015bbc1e12 | ||
|
454a9f3175 | ||
|
485b3d64a1 | ||
|
5d170d83c0 | ||
|
f54d8ea3ab | ||
|
ef9f54b3d4 | ||
|
8d0b102b44 | ||
|
03fc031611 | ||
|
53fe0bb5eb | ||
|
ab98c1f2d4 | ||
|
8dc5f6327c | ||
|
833cbf27b1 | ||
|
7c8276d2ac | ||
|
c2b7115031 | ||
|
5294f70189 | ||
|
9e90394583 | ||
|
79fb646872 | ||
|
6fa492b580 | ||
|
24be4fc8f8 | ||
|
64ae82f681 | ||
|
e907510a1a | ||
|
42be09afb6 | ||
|
eb35c03bef | ||
|
9593ba9092 | ||
|
7143aaa89b | ||
|
0d139d7ef3 | ||
|
de9ac43ebf | ||
|
4bcc8afdc0 | ||
|
0302d24893 | ||
|
c2ac15d68f | ||
|
cdd3e7d856 | ||
|
deaa27fbdb | ||
|
5788e75188 | ||
|
9f2d154588 | ||
|
5237da4e01 | ||
|
88d950e5a8 | ||
|
1d95b5476e | ||
|
8df9bcc784 | ||
|
55fc963595 | ||
|
549a3107cb | ||
|
a9185d4ea1 | ||
|
e7c85571c7 | ||
|
52c2cbd630 | ||
|
8541ffa328 | ||
|
1792100e2b | ||
|
c20ac62c54 | ||
|
546c6a4531 | ||
|
277b6790c7 | ||
|
8b380ec364 | ||
|
edff62af3d | ||
|
730cb3b7cc | ||
|
d6eff3d62c | ||
|
2d3a337200 | ||
|
53c4fd5f09 | ||
|
dfc727296e | ||
|
7078a6ac61 | ||
|
09bd412b13 | ||
|
4fba7e6865 | ||
|
bbad3fe501 | ||
|
0a9ff1dc9d | ||
|
13db3eca9f | ||
|
83fda2d972 | ||
|
aa2751e614 | ||
|
164b7895b3 | ||
|
d33ab34d75 | ||
|
d9655ea507 | ||
|
7abd8084b6 | ||
|
5ea80e673f | ||
|
68109a46e0 | ||
|
409fe3bca1 | ||
|
5c95d8e963 | ||
|
201637b326 | ||
|
f02d4cdb37 | ||
|
4bcff89b3c | ||
|
c5fe076432 | ||
|
8d1651c8ad | ||
|
1d267eae6b | ||
|
895d7d6a65 | ||
|
542198180a | ||
|
8cd5eb9863 | ||
|
14e241be35 | ||
|
e1a4251b07 | ||
|
e1872a450a | ||
|
f6a7732ff9 | ||
|
28b014ccf4 | ||
|
c494346887 | ||
|
ba475e1112 | ||
|
845c783b0e | ||
|
f6600810d7 | ||
|
6e9dbb4f6e | ||
|
1dc71fb5aa | ||
|
239a191612 | ||
|
20c0e8793e | ||
|
dca579851c | ||
|
6ae4d2e5cb | ||
|
3d9d7557c8 | ||
|
82c75c3786 | ||
|
2f374df494 | ||
|
26df122386 | ||
|
9da3de5861 | ||
|
0720589767 | ||
|
a706706572 | ||
|
3425e98a6b | ||
|
6706f2b3bb | ||
|
f0e9341450 | ||
|
c59cb28bbf | ||
|
882d033233 | ||
|
51d8f36dae | ||
|
ca35bb3ac8 | ||
|
e3a31d896a | ||
|
08674b8388 | ||
|
ed15ce0bbe | ||
|
f1fd999bb0 | ||
|
5dceddd21d | ||
|
f2b31c3a89 | ||
|
4e8565253c | ||
|
ced9f889a4 | ||
|
85aa632be0 | ||
|
34b68288c8 | ||
|
d3a4140899 | ||
|
661ca52135 | ||
|
1c51711c75 | ||
|
c3048b451d | ||
|
b4c24bfa42 | ||
|
04adf12d89 | ||
|
932d5fbf0c | ||
|
3b39e98298 | ||
|
de6ecdf590 | ||
|
81f38738e8 | ||
|
3f841df7cf | ||
|
9b143f030e | ||
|
a877f347f4 | ||
|
5bfe93e74c | ||
|
17defbff13 | ||
|
404fc1570d | ||
|
274312ebb5 | ||
|
be7760caa1 | ||
|
47af5933ca | ||
|
8df2a4bac0 | ||
|
841c7a0f71 | ||
|
85bec37be4 | ||
|
f751a5d4e2 | ||
|
3e3a54b717 | ||
|
480dd15b1e | ||
|
0f76daaf7a | ||
|
10c81a2448 | ||
|
8c40dd34b2 | ||
|
0a8bc347a1 | ||
|
3eece38ffa | ||
|
4e90a9f5fd | ||
|
1a7413d8c9 | ||
|
d22df1227f | ||
|
2f9df2a52f | ||
|
fb4153b7fb | ||
|
73111b005f | ||
|
aebc3a17ce | ||
|
83aaf18d6e | ||
|
733fcbaa6c | ||
|
57dd60f671 | ||
|
8fd1e55805 | ||
|
6705b5a98c | ||
|
254790f8c8 | ||
|
c59e088142 | ||
|
4a4a4d8e49 | ||
|
75704f34ba | ||
|
90d557d916 | ||
|
2d62f2ad03 | ||
|
75e3f5cd48 | ||
|
40dd46680e | ||
|
b47bd0a296 | ||
|
6cf6cd2fba | ||
|
e1793e5a13 | ||
|
6203d1c94c | ||
|
e81c2c826c | ||
|
aa78fa0647 | ||
|
52a2f41f59 | ||
|
daebade239 | ||
|
ce4304cc9a | ||
|
7cbc25a6fe | ||
|
d2a1ec3a05 | ||
|
f936ce02f8 | ||
|
ec167aead9 | ||
|
cc78667118 | ||
|
aebf12e28d | ||
|
6048342c57 | ||
|
008b56e535 | ||
|
d18b417346 | ||
|
0b00a1b4de | ||
|
955b99cf69 | ||
|
761499dfbf | ||
|
df0be063d8 | ||
|
db071303b4 | ||
|
6c608450fd | ||
|
9dc9a1ef6c | ||
|
43b82b31e5 | ||
|
b5f7e39be8 | ||
|
5e2d38227f | ||
|
4ff0f0949a | ||
|
0e2578a0b8 | ||
|
4ef2da0ff0 | ||
|
f96c4ec84e | ||
|
ccfde38f5e | ||
|
d2fda9ada2 | ||
|
b70abdc645 | ||
|
42421e77a9 | ||
|
215ddecaa5 | ||
|
465ab490e1 | ||
|
bee9998e91 | ||
|
630288c1cc | ||
|
c930f92411 | ||
|
4c4c6572e7 | ||
|
c833ede4af | ||
|
32cb8d1462 | ||
|
7678e097fd | ||
|
cfb7bb5319 | ||
|
8e6a54deeb | ||
|
61ab2072bd | ||
|
7befad2f6d | ||
|
e3a92d6905 | ||
|
60ce6f1a9b | ||
|
488ce982f0 | ||
|
6b9a019c0a | ||
|
8e73187990 | ||
|
bb2772d068 | ||
|
dc7f89715a | ||
|
dcbb1dca8a | ||
|
38a3ed96bb | ||
|
f0f99ffc7e | ||
|
879646dee9 | ||
|
c900b76dbb | ||
|
13682007bb | ||
|
1d36a84a2e | ||
|
1c7dd0ae14 | ||
|
26bf2b7e45 | ||
|
a13083aa65 | ||
|
1d008bab4a | ||
|
3087c90490 | ||
|
3bc7d85986 | ||
|
13aa38d307 | ||
|
0ec8069348 | ||
|
c1eb350c47 | ||
|
ed82bf70f5 | ||
|
791fb17437 | ||
|
62fa8b0ed8 | ||
|
c75d97e3f2 | ||
|
11e92f0c9f | ||
|
e4216c3ff8 | ||
|
8808a74593 | ||
|
2cab684ffb | ||
|
9bb11a2dcc | ||
|
87d907db74 | ||
|
10ef38c4bb | ||
|
a0def4981d | ||
|
95f061806a | ||
|
b74d7b5758 | ||
|
2cb006fa44 | ||
|
a8c29505f0 | ||
|
856693ac1f | ||
|
c76cba465d | ||
|
b33e5b87dd | ||
|
f9acbd6e3f | ||
|
af08ba93e6 | ||
|
b58f69297f | ||
|
397cf726fc | ||
|
43d5e47ea9 | ||
|
9668dd85d4 | ||
|
2ef4369237 | ||
|
1ffab5de77 | ||
|
83799356dd | ||
|
110acd20dc | ||
|
1cd70edec0 | ||
|
a39b0f6ee5 | ||
|
f2a1a0ac5c | ||
|
9d75b82840 | ||
|
acc40b1c9e | ||
|
2aee43f3ce | ||
|
d9a07e7ed9 | ||
|
13a08c7f08 | ||
|
15057b087c | ||
|
f25c969ad8 | ||
|
31d2f445a2 | ||
|
536becbfab | ||
|
892a2eacff | ||
|
8ea6dd7513 | ||
|
75664cce59 | ||
|
7640841b02 | ||
|
78dc334afe | ||
|
47fbfc52de | ||
|
645598e615 | ||
|
39932d7664 | ||
|
a7bed62af0 | ||
|
df1f53950e | ||
|
c5299b60ed | ||
|
3827739708 | ||
|
808360d25b | ||
|
8d94ce4128 | ||
|
3a9dca0c67 | ||
|
3e4acba72f | ||
|
ef99689592 | ||
|
bde1e3d004 | ||
|
c2f77a3473 | ||
|
afc1b59475 | ||
|
6276360468 | ||
|
8c922a0198 | ||
|
64a3cf03e2 | ||
|
2b3faa1947 | ||
|
e5f4442325 | ||
|
b5820f9325 | ||
|
ab5814cd90 | ||
|
4069e7b663 | ||
|
e9e5ee4362 | ||
|
6c5fb329b2 | ||
|
0970ed8fce | ||
|
935b85c1de | ||
|
0b64d28582 | ||
|
8761ce4691 | ||
|
bcd303a447 | ||
|
5e8490ab9d | ||
|
cbea9ebc34 | ||
|
f5e0adc693 | ||
|
5d72e52ad0 | ||
|
88ae321d3f | ||
|
b37d73cd56 | ||
|
2b309ec046 | ||
|
e57001e5cd | ||
|
fc54fb88ae | ||
|
baa6b3a261 | ||
|
13f3043c8a | ||
|
34bf80ba9c | ||
|
887b0e4b72 | ||
|
01a4889b53 | ||
|
d611337394 | ||
|
c97a7d1105 | ||
|
a8a77614fe | ||
|
cbcde43765 | ||
|
6dc735e996 | ||
|
328f59ebef | ||
|
9f95704706 | ||
|
48dd9f7efd | ||
|
c1f3f9d27b | ||
|
598e5f58d5 | ||
|
30ef53cb13 | ||
|
4e4e12b384 | ||
|
9821a7754c | ||
|
cfa598616e | ||
|
76b5066dc9 | ||
|
554060b3c5 | ||
|
d2cfeb31b9 | ||
|
7481ba5618 | ||
|
aab5f24518 | ||
|
114b91afe6 | ||
|
dadea873a9 | ||
|
eb306da148 | ||
|
89b474e192 | ||
|
38f15e41b5 | ||
|
58354d166b | ||
|
ab70c15709 | ||
|
58724cb687 | ||
|
e390c8cb7f | ||
|
2660b44d91 | ||
|
2c2432fddc | ||
|
91f4e99b4c | ||
|
9ca8e98525 | ||
|
23c558510a | ||
|
2a96e722b4 | ||
|
599dae8f09 | ||
|
3282334741 | ||
|
70c4626efe | ||
|
a97c04b400 | ||
|
122b707a7d | ||
|
f9719361a5 | ||
|
b4cf968e14 | ||
|
fb815294b3 | ||
|
87924c7111 | ||
|
1d7c00c915 | ||
|
c280d40b40 | ||
|
c0e2ef06dc | ||
|
2eb637d52f | ||
|
14fb75927e | ||
|
337684b384 | ||
|
461ae40eea | ||
|
c644b05c54 | ||
|
dab2ad245f | ||
|
5306eb93cc | ||
|
73ac104df2 | ||
|
737d3e376d | ||
|
a81e7e7749 | ||
|
549492954e | ||
|
c5b9831bfb | ||
|
abc27d2900 | ||
|
b4790120cb | ||
|
4c5f345798 | ||
|
0ea795caa8 | ||
|
fe1e08b9ad | ||
|
491ad59d2e | ||
|
1a70a2a25b | ||
|
d08c3232e2 | ||
|
b7811c8992 | ||
|
1b1d9f6b0c | ||
|
84b139cc94 | ||
|
9a1c1fbab8 | ||
|
920cd5285a | ||
|
9e24c9795c | ||
|
7a4e293b3b | ||
|
5d4015358a | ||
|
2d24160376 | ||
|
4d553f4879 | ||
|
b511e51415 | ||
|
77d9d06a44 | ||
|
5a435a4342 | ||
|
d2b26a65e9 | ||
|
c988d0f573 | ||
|
6c36a2085f | ||
|
78c2d9156b | ||
|
67d570d121 | ||
|
cef0d5879f | ||
|
44b12a1594 | ||
|
ede891a6c6 | ||
|
7f4debdad5 | ||
|
8f0796436a | ||
|
bc62313c66 | ||
|
118ce47b97 | ||
|
5e7565fefe | ||
|
f2e265492c | ||
|
ea8eb5b065 | ||
|
c24fbb6f8b | ||
|
66c3c6c2b3 | ||
|
dd54ea78ac | ||
|
8c4995b22b | ||
|
661a935075 | ||
|
8838a55a1a | ||
|
8aa00381ef | ||
|
cb2e8f9084 | ||
|
97284adabb | ||
|
10fa4f45ab | ||
|
0d663158d0 | ||
|
7ee0c9e68a | ||
|
bb63e9486d | ||
|
4999fe298b | ||
|
5db6e4748d | ||
|
bbe2834c82 | ||
|
43053dcc90 | ||
|
b0d1ae1d8b | ||
|
f349def5d4 | ||
|
88737f24b0 | ||
|
169b334660 | ||
|
d498ac229f | ||
|
2840201e41 | ||
|
b5ef319038 | ||
|
6d9ca0ae15 | ||
|
04c5e6cc48 | ||
|
3d5e778d5d | ||
|
546915ee12 | ||
|
fefa297877 | ||
|
f6a73098a4 | ||
|
9e577d0338 | ||
|
a9a01c308e | ||
|
c3d2d2134c | ||
|
d3a3a7548c | ||
|
120fc03aff | ||
|
582602dc57 | ||
|
c05a038475 | ||
|
c54a3e82e9 | ||
|
5b6f360f51 | ||
|
7996f7e227 | ||
|
84e3218cdb | ||
|
a0f40711c6 | ||
|
5c09193019 | ||
|
1c75db41e3 | ||
|
0e4509c497 | ||
|
55b0428ff7 | ||
|
af9a3f004e | ||
|
e60bda082d | ||
|
a3912bc084 | ||
|
3a1e125ce3 | ||
|
4260b3b416 | ||
|
eb2560e782 | ||
|
90778615f6 | ||
|
80db6c0980 | ||
|
0049ab69fb | ||
|
96b8aa8bd1 | ||
|
2f657bc0ca | ||
|
52a292a75b | ||
|
fa4bab4608 | ||
|
8acc47ee1b | ||
|
e872715fd6 | ||
|
39120b3343 | ||
|
660214754e | ||
|
458e9a1533 | ||
|
4b65e32f22 | ||
|
8415c22b59 | ||
|
d2ee9db214 | ||
|
bc7133d752 | ||
|
1df15d85c3 | ||
|
9263ae1c60 | ||
|
da9548fd12 | ||
|
8330123861 | ||
|
72d41e5801 | ||
|
387d85c5b5 | ||
|
1f36ac9656 | ||
|
7ddf10e602 | ||
|
da361afbb9 | ||
|
7d686b72a0 | ||
|
66e51a7363 | ||
|
33884d847a | ||
|
225bed11c7 | ||
|
2664a1f7ef | ||
|
df8dab9d2b | ||
|
ca00197009 | ||
|
b5c8b86e7c | ||
|
ecd74c3786 | ||
|
6606590b81 | ||
|
7b3f96ab30 | ||
|
636ae12621 | ||
|
0d233370e8 | ||
|
3738611f5c | ||
|
363c148dbe | ||
|
be80f6d5c5 | ||
|
0f05e086fe | ||
|
c74c565d25 | ||
|
65ee3a6bdd | ||
|
a9f339a3ed | ||
|
bacfb5103f | ||
|
ebcc8086f8 | ||
|
5fb8b84d84 | ||
|
ffeadd4e29 | ||
|
c458d4b213 | ||
|
d5e439037b | ||
|
032a7d36ff | ||
|
61c2970141 | ||
|
bc16b58d75 | ||
|
781b92a8c0 | ||
|
144bd36674 | ||
|
92d6521a7f | ||
|
c2dbf53d76 | ||
|
a74f0f90e4 | ||
|
2cf44a9e14 | ||
|
de71fb9bdd | ||
|
e7b7c15198 | ||
|
413dfb01d5 | ||
|
db9ddc7e5b | ||
|
664b6125b6 | ||
|
32c31f2689 | ||
|
eb597cd60f | ||
|
4dc4fefee2 | ||
|
4698ee5e4a | ||
|
ae91270961 | ||
|
49e11e1f9c | ||
|
5919e67c2a | ||
|
fe8c5b0f0f | ||
|
fbc07bf327 | ||
|
3d01433512 | ||
|
e39dd7e739 | ||
|
7d2962135d | ||
|
4e0d1b1d4a | ||
|
6d4c69b7c3 | ||
|
6fa40f10d1 | ||
|
f96ab5a818 | ||
|
c4962af9eb | ||
|
631f029fe9 | ||
|
e666272ab6 | ||
|
cd4a300f0c | ||
|
f11617c192 | ||
|
f5f979ac30 | ||
|
bb6ab3a62d | ||
|
26eba5ac7d | ||
|
c88ec9657b | ||
|
322c667655 | ||
|
ff4b34202c | ||
|
1b343665a1 | ||
|
0475107654 | ||
|
e3c0cc980b | ||
|
fc83a666fc | ||
|
f58bc8589d | ||
|
c4fb77f057 | ||
|
a82971879f | ||
|
8b1638f026 | ||
|
35f77ccc73 | ||
|
39686ef098 | ||
|
4bfda3e766 | ||
|
439c06b460 | ||
|
542355129d | ||
|
9290a1ee95 | ||
|
8862105922 | ||
|
da508b0ff7 | ||
|
41a56e14fc | ||
|
0a89bb4d3c | ||
|
42a4dfab9a | ||
|
5caf81dbf8 | ||
|
66c7a98009 | ||
|
efe683b3be | ||
|
6e13dbe206 | ||
|
0cc9c94c43 | ||
|
a4956844bd | ||
|
dd711ab5fb | ||
|
92ce381d60 | ||
|
85c51f5787 | ||
|
fe68f7f786 | ||
|
63db324204 | ||
|
3718771ffb | ||
|
ccc5958847 | ||
|
766406fd23 | ||
|
0264147d42 | ||
|
b5170b993e | ||
|
c7f4f15e60 | ||
|
c7c50bd32c | ||
|
965ea97b56 | ||
|
16944e218f | ||
|
7d2729f6bd | ||
|
ffb9a49769 | ||
|
37c8842bcb | ||
|
1838e323df | ||
|
87133cdc0a | ||
|
0f239a6f32 | ||
|
13bb34a28d | ||
|
c95f6c4b83 | ||
|
b169d9cfbe | ||
|
4669fa0f98 | ||
|
368aeb2cee | ||
|
84d56c62ce | ||
|
8badd05004 | ||
|
56efec3c17 | ||
|
9a26a70fdc | ||
|
8b0242a5d8 | ||
|
959880db60 | ||
|
17c391121a | ||
|
7bc073defe | ||
|
76f11c7dae | ||
|
0e68ed6a8d | ||
|
61be155413 | ||
|
07a853d6cc | ||
|
e6b821c392 | ||
|
dd6cccaf7e | ||
|
c8fc0a6ba1 | ||
|
0776fa05c7 | ||
|
e10de86440 | ||
|
608b81b412 | ||
|
02bf9ca834 | ||
|
86a982150c | ||
|
7a5934082f | ||
|
42bfe9e5ea | ||
|
8863b773c1 | ||
|
e4231d1028 | ||
|
62fdd7ba07 | ||
|
2905ccc7ec | ||
|
ad65d4785e | ||
|
d63cf1e30a | ||
|
a88223eca6 | ||
|
46d0019955 | ||
|
f11c86b2c5 | ||
|
efdb560e97 | ||
|
2bb27cdb25 | ||
|
6f930351d2 | ||
|
f71677164f | ||
|
6858950f76 | ||
|
81d0c8ae7f | ||
|
83c53ae4b5 | ||
|
62f20bc170 | ||
|
a18add6eed | ||
|
301f3096a0 | ||
|
de04a208c7 | ||
|
c0675968b1 | ||
|
6c55aaf4c7 | ||
|
6dc5be17b2 | ||
|
50a9731257 | ||
|
d732edb446 | ||
|
a4e8606be9 | ||
|
1ecb2f5c5c | ||
|
400b6c56b2 | ||
|
942e4273ba | ||
|
c7c6c28455 | ||
|
3b3f7341fa | ||
|
c5e16383b0 | ||
|
c1c69ecc34 | ||
|
6d5889bdb5 | ||
|
9c53e1dfb2 | ||
|
75d62ca095 | ||
|
cca318f805 | ||
|
456eae6ccb | ||
|
d5163c5786 | ||
|
81bd9afd80 | ||
|
5a9f2a4c64 | ||
|
cc685a4b44 | ||
|
16950122f2 | ||
|
54517ea454 | ||
|
2cc3d7511a | ||
|
6123d71489 | ||
|
87311cce7f | ||
|
4451042c76 | ||
|
8aecea6d05 | ||
|
b6bfed64cb | ||
|
71264bef67 | ||
|
28a72220f6 | ||
|
e524bb7825 | ||
|
97f214b209 | ||
|
809e0ea80a | ||
|
fd8ec27fe8 | ||
|
5a5b7f39c1 | ||
|
359707c85e | ||
|
b510474dcb | ||
|
48283161c3 | ||
|
42943ab86d | ||
|
4f6ebbdbe0 | ||
|
99aecdaf65 | ||
|
d008dfb7ad | ||
|
cc0781e0ac | ||
|
3073dc9801 | ||
|
bb2f0df9e1 | ||
|
3f9e6a600b | ||
|
b1b2c6ee7b | ||
|
cc9b52cf72 | ||
|
05cf15a382 | ||
|
a44e4d386f | ||
|
eec3d25ab9 | ||
|
dd752fcc5e | ||
|
a216d0a86c | ||
|
9ea51ad2cc | ||
|
f47deafe3e | ||
|
1c7ae3aad0 | ||
|
f0d0bdc572 | ||
|
9e7fad1fd2 | ||
|
d0b97c7941 | ||
|
969f7b015b | ||
|
f70762913c | ||
|
f9e0ea112d | ||
|
c26512255d | ||
|
4b04ed86b6 | ||
|
d6ed5d220a | ||
|
7b9871522e | ||
|
ce0edaf6a2 | ||
|
d4e953277e | ||
|
e84a91d417 | ||
|
f8d338c9cb | ||
|
1f1eb9f26e | ||
|
b8f03c9b0f | ||
|
67ed44c007 | ||
|
e4f45bfd3b | ||
|
56211378d3 | ||
|
14d793b22c | ||
|
247228ee61 | ||
|
7de7efe96c | ||
|
f5ed017d69 | ||
|
0472eaff98 | ||
|
b09acebc6a | ||
|
e9dbbdeb81 | ||
|
3aff8f69af | ||
|
beaa7ff556 | ||
|
d83027c0cd | ||
|
253114ca20 | ||
|
649fe6d3b6 | ||
|
c38021502e | ||
|
65213a1782 | ||
|
c24da1ee16 | ||
|
efbe37ba20 | ||
|
1ab36203de | ||
|
2f5bb7e507 | ||
|
80f9df169b | ||
|
447b3c9413 | ||
|
2e58d1d1dd | ||
|
9c64b2f4ab | ||
|
d86c4ef0ef | ||
|
b3c2752bb0 | ||
|
6bbf6a79b7 | ||
|
859eb606da | ||
|
2301dcf973 | ||
|
d3b0f87a49 | ||
|
8a119c1483 | ||
|
1fc7c1ecee | ||
|
1859fbeb30 | ||
|
86ca85d72b | ||
|
f1bbe1cd84 | ||
|
9478063b3d | ||
|
1b16790325 | ||
|
c5c8da1ac0 | ||
|
403790760c | ||
|
5800217998 | ||
|
9ac8db3533 | ||
|
b028c47d2b | ||
|
ed95071c27 | ||
|
c3907be623 | ||
|
16d45b8480 | ||
|
1f4bcf70b0 | ||
|
2cd7cd3149 | ||
|
3fedcdc6bc | ||
|
d6f1befee6 | ||
|
5394ca580f | ||
|
8ec1a66f8a | ||
|
c879e7c1ad | ||
|
81489ccb76 | ||
|
c8807d227a | ||
|
e0eb374d9c | ||
|
6972e63f51 | ||
|
2c5f83c264 | ||
|
3a04026599 | ||
|
8f5431551e | ||
|
9629baa0c7 | ||
|
aea979c32e | ||
|
8be20efc39 | ||
|
6f288436b6 | ||
|
11df2e2236 | ||
|
dadc84fa8c | ||
|
f1a2ad1b7d | ||
|
8e3353d9ef | ||
|
2c38865e70 | ||
|
dd7fae4afb | ||
|
346890ded3 | ||
|
b3f695c83f | ||
|
4feead323d | ||
|
71a308affd | ||
|
b5faa11f73 | ||
|
41ad3dd8f0 | ||
|
7b18f4c5fc | ||
|
32a23746eb | ||
|
973f0965e1 | ||
|
d0596ce9c8 | ||
|
e35889542b | ||
|
7f67d36777 | ||
|
37222683ee | ||
|
5eaf65af4f | ||
|
87de82ac94 | ||
|
5ae704d560 | ||
|
630eb3b907 | ||
|
bff3825298 | ||
|
41ce892f1c | ||
|
a5c6a78f6d | ||
|
4fc6cf4bcc | ||
|
1394952059 | ||
|
2aeba39d82 | ||
|
5564639a05 | ||
|
0571882b1d | ||
|
74a6083269 | ||
|
7b7ea946e3 | ||
|
fbb5e5c4e6 | ||
|
aaf3790d8c | ||
|
457247e5a5 | ||
|
f879f8e110 | ||
|
861402bcea | ||
|
48d99e6c5c | ||
|
de6b368944 | ||
|
d1be1e7092 | ||
|
d0eee0879e | ||
|
0a89a952c7 | ||
|
64c4861e04 | ||
|
adf6f748c0 | ||
|
026e7de819 | ||
|
337da184f3 | ||
|
9abaf6ec1d | ||
|
d0aa8a6446 | ||
|
0ef3cac1f8 | ||
|
4ef3fcc976 | ||
|
71c469c72b | ||
|
adeb06e550 | ||
|
19f385db76 | ||
|
978b26a9c5 | ||
|
2c669f65f1 | ||
|
aa70dbfc62 | ||
|
791306a92d | ||
|
42aeead6b4 | ||
|
14036ac580 | ||
|
75b621160e | ||
|
b2fc7f7caa | ||
|
e03a64ae1b | ||
|
f41a73d76a | ||
|
3af8fb4722 | ||
|
23caaccdec | ||
|
a17907b9a2 | ||
|
e3773d919c | ||
|
8f10e407ee | ||
|
1866521df6 | ||
|
c31a34fbcb | ||
|
18074555fe | ||
|
ce598c5c98 | ||
|
865d01c38d | ||
|
fce3c70b72 | ||
|
ede19ef33b | ||
|
22d16c69b7 | ||
|
edde4f978b | ||
|
45b1e65e03 | ||
|
058bca6632 | ||
|
89424b29bf | ||
|
6c23197f37 | ||
|
bc649da77d | ||
|
3c7b9c2938 | ||
|
65b868f4eb | ||
|
caec631344 | ||
|
f57af4fec2 | ||
|
575400c83f | ||
|
90714941b7 | ||
|
6156dc300d | ||
|
777342a1ef | ||
|
d158d45051 | ||
|
82848d6c73 | ||
|
537bbde22e | ||
|
96a7d4dbd8 | ||
|
74fcb184b2 | ||
|
ce98088457 | ||
|
adf06b635b | ||
|
182552c2e7 | ||
|
f63034752d | ||
|
972619edb4 | ||
|
36d55c0667 | ||
|
b51c0f3095 | ||
|
65cc6a69c8 | ||
|
0d5258b6d3 | ||
|
57ed4e4657 | ||
|
2ff983647f | ||
|
322dbd894f | ||
|
ccb48f5ce9 | ||
|
c69ce29ee7 | ||
|
89621adca7 | ||
|
06f84c65f1 | ||
|
35208c5ee7 | ||
|
c94fe9236f | ||
|
833ad20b01 | ||
|
31696a1d72 | ||
|
6071d0d206 | ||
|
2956cc5aed | ||
|
0718501c72 | ||
|
f9a74b51ef | ||
|
aa5c008fa8 | ||
|
718f33260b | ||
|
6583c8cffe | ||
|
f0e02d2588 | ||
|
965f653471 | ||
|
a36252bfa0 | ||
|
b15d6148dd | ||
|
de04dcd9d6 | ||
|
00b36e6db2 | ||
|
e50386f928 | ||
|
7c4822efb1 | ||
|
5d6410c1cb | ||
|
5dcf348098 | ||
|
c9d12ad410 | ||
|
1014781b87 | ||
|
e39fac9f01 | ||
|
3a2b8c5e5b | ||
|
199940d683 | ||
|
92406cf9a0 | ||
|
cc6ba1e131 | ||
|
7526bb96f3 | ||
|
93ed0ab2bb | ||
|
c10da16d7b | ||
|
35f5f9fc7b | ||
|
93259f0bae | ||
|
d00453f747 | ||
|
baeba41cfd | ||
|
cd5c7f30d5 | ||
|
d326512121 | ||
|
b8c4b88188 | ||
|
c94813e436 | ||
|
1c970bb39f | ||
|
b7d3ddbfe3 | ||
|
0fb56fc61b | ||
|
07dfa37cce | ||
|
d95bce2600 | ||
|
bb144bf758 | ||
|
be7c30df35 | ||
|
720f863937 | ||
|
018cb5035a | ||
|
1d04c1db94 | ||
|
c4aee8c0a0 | ||
|
bb183938d9 | ||
|
42f1ef8acb | ||
|
a5c3fc14b3 | ||
|
c979cc7da8 | ||
|
6601ec8f26 | ||
|
860ecdd376 | ||
|
8a3b217530 | ||
|
ba7f7cca78 | ||
|
586501bb5e | ||
|
59197fb368 | ||
|
731a943239 | ||
|
215bbe85d8 | ||
|
68e5a2ef56 | ||
|
7cab638297 | ||
|
325a7e9f86 | ||
|
ba353c2b1d | ||
|
0c58123b45 | ||
|
be88e868bd | ||
|
9a60353fa9 | ||
|
eaf79152b3 | ||
|
5e34af5a75 | ||
|
89cab47011 | ||
|
6cf74d1166 | ||
|
3fb8428636 | ||
|
011b674d41 | ||
|
a6533c3a21 | ||
|
dda48e2d06 | ||
|
55be8d4016 | ||
|
ff890c173c | ||
|
a713e3c92d | ||
|
de3801da24 | ||
|
54775ffedf | ||
|
bb72cbe7ae | ||
|
6563726f22 | ||
|
1afb138a2c | ||
|
e1a212fb79 | ||
|
65a6bfad09 | ||
|
afd9bfc45f | ||
|
4955898e75 | ||
|
4fa443becf | ||
|
ef60d0f5ba | ||
|
7dd4de80eb | ||
|
6767264aa1 | ||
|
02ba197a06 | ||
|
2cd404b8dd | ||
|
f99151eab9 | ||
|
c9b8b76793 | ||
|
d23126a10c | ||
|
f13e4b64d6 | ||
|
65049bd112 | ||
|
22d8b3c3f8 | ||
|
65b247a922 | ||
|
76dada3118 | ||
|
f561eb917f | ||
|
cd852a5c22 | ||
|
208dd1de3a | ||
|
1a03afccb1 | ||
|
cb8661bd49 | ||
|
28f2c15597 | ||
|
0ed7b0561e | ||
|
a695561f86 | ||
|
327c3d4154 | ||
|
cee8db0da3 | ||
|
add08c673e | ||
|
42b40b7d34 | ||
|
7ee07fd4e8 | ||
|
f52fdc8546 | ||
|
11a8abe3bf | ||
|
06906413ef | ||
|
1211d8118c | ||
|
c4913e3c9e | ||
|
75c3690ccd | ||
|
f03621d24a | ||
|
fdbb22dc07 | ||
|
77e3c3970c | ||
|
cf175fdc98 | ||
|
1a0b791f9a | ||
|
22000f7dba | ||
|
3533e11786 | ||
|
58542cf7f6 | ||
|
19f412b85c | ||
|
9b866d79fb | ||
|
5546b6b676 | ||
|
25b135fbaa | ||
|
7f2ccbff80 | ||
|
287e2600af | ||
|
f2a854fea7 | ||
|
63db4759f8 | ||
|
8d6af087a2 | ||
|
123e2a1cc1 | ||
|
9410eab2af | ||
|
a79790dea6 | ||
|
3ecb390b10 | ||
|
98cfe92745 | ||
|
32dcce0ac1 | ||
|
f6cda2579f | ||
|
c231bb7154 | ||
|
5d682d2e05 | ||
|
c6eea94edc | ||
|
749208fa32 | ||
|
bb9c04895b | ||
|
5517d39dc4 | ||
|
4516e5ad47 | ||
|
a48cc073cf | ||
|
83f93fed02 | ||
|
90a591da0e | ||
|
3930cb865a | ||
|
7237e7065f | ||
|
56282f0c01 | ||
|
5ab4109b7e | ||
|
3d4b9bb00d | ||
|
3fc7ea74c4 | ||
|
96e21e30d5 | ||
|
4ada4d43f2 | ||
|
b85e8497b5 | ||
|
ee3f65d8ef | ||
|
a60b950d5b | ||
|
f1c9f3250f | ||
|
44a88c8156 | ||
|
629572831b | ||
|
241e6f1ecf | ||
|
7d48339b7c | ||
|
c1e76fd5f7 | ||
|
c8f03c7f6d | ||
|
daae638781 | ||
|
63a67f415e | ||
|
3278d78f08 | ||
|
af2262cbba | ||
|
f50ed35c6d | ||
|
a433f3347f | ||
|
5bfb6e60a1 | ||
|
1e08d59ee7 | ||
|
04bb33ad16 | ||
|
917a6784cf | ||
|
d6ec03f13c | ||
|
a4cec5c189 | ||
|
a59d305e09 | ||
|
4bb6c2fffb | ||
|
555252f435 | ||
|
daba17a95c | ||
|
05de260216 | ||
|
9dabcfc388 | ||
|
ce12c1e7a2 | ||
|
3c69cd6d61 | ||
|
bdf5274d18 | ||
|
ae7b15f062 | ||
|
de4a613610 | ||
|
f27665662c | ||
|
711f3488f1 | ||
|
8064da306f | ||
|
ff88c96549 | ||
|
33abd11a47 | ||
|
45d09b33ad | ||
|
cade92d2b4 | ||
|
9c490e06b0 | ||
|
484c8cb8a8 | ||
|
6b35ba9f1a | ||
|
23d1f234a8 | ||
|
91d5b0b30c | ||
|
6f325d4594 | ||
|
361e5322e4 | ||
|
fd47d38e59 | ||
|
c575f45e24 | ||
|
492858029b | ||
|
1ee2b4e409 | ||
|
e610247185 | ||
|
11df22d89b | ||
|
7e1682db7d | ||
|
e1abb64f41 | ||
|
10ce839ec0 | ||
|
1db5c9d346 | ||
|
8143ea0dea | ||
|
de3a2eff22 | ||
|
cd684577a6 | ||
|
4431080066 | ||
|
0a80e83cce | ||
|
1e116bba0b | ||
|
e0468db113 | ||
|
05db41fe9c | ||
|
28f2fa3fd5 | ||
|
697e004e0d | ||
|
ec5573ca4b | ||
|
080b896796 | ||
|
c5037ec196 | ||
|
ef8eced081 | ||
|
775340c6f3 | ||
|
f54941fa15 | ||
|
502e93f55b | ||
|
c274e26eb8 | ||
|
0abf4db82e | ||
|
5b2442d54e | ||
|
9e96180ce4 | ||
|
073cee0143 | ||
|
323df5407a | ||
|
272132fcc2 | ||
|
53b8ea4464 | ||
|
c67f8bd821 | ||
|
9eebaa2e59 | ||
|
49b3d51e57 | ||
|
529c7fe683 | ||
|
b0a2a8b645 | ||
|
11b199cccf | ||
|
9940870c89 | ||
|
2665c5b3c2 | ||
|
4013447c08 | ||
|
cfe9b8b744 | ||
|
ee646aa7a2 | ||
|
1e3b1d324f | ||
|
649dcac2ec | ||
|
9ade73841f | ||
|
d3750b47d2 | ||
|
d4cbd0d171 | ||
|
27752c4e4d | ||
|
6677996369 | ||
|
b529fc7fb9 | ||
|
369d376d10 | ||
|
3b1dbfcce2 | ||
|
6367d02277 | ||
|
eabc63cdcd | ||
|
69e7e0129e | ||
|
2332dd774f | ||
|
3bd3795e2c | ||
|
023bb8f150 | ||
|
14f3bafe6f | ||
|
f90e56e6c7 | ||
|
879c98efeb | ||
|
b64fb295a1 | ||
|
4ba43c29ce | ||
|
cd3c134b58 | ||
|
d3611f74c8 | ||
|
bafdcf24f5 | ||
|
bc7731b969 | ||
|
2b8a521562 | ||
|
91a56caed2 | ||
|
d8e2038dda | ||
|
a13efc52b3 | ||
|
954b017f85 | ||
|
24f551a9e8 | ||
|
055ed72cd9 | ||
|
114c211b66 | ||
|
e2aaa6706c | ||
|
1e7ae7185b | ||
|
38f36a7a7a | ||
|
39246f9dd7 | ||
|
2c091e4fca | ||
|
8068566f20 | ||
|
dff8242887 | ||
|
bc7adb97ed | ||
|
90fad36613 | ||
|
28cf896a64 | ||
|
b22de369b7 | ||
|
b940da4040 | ||
|
3f39ab1e04 | ||
|
bd7be8401f | ||
|
36a294aae0 | ||
|
fb71ee60aa | ||
|
4b1cb51a3e | ||
|
89bca6110a | ||
|
29cfbce21d | ||
|
da1b480bd3 | ||
|
e525999d59 | ||
|
b041afe1be | ||
|
7bc9eddb99 | ||
|
af08221aec | ||
|
2ed6a457b4 | ||
|
46aac4819a | ||
|
1c1a3f979d | ||
|
7568bb573f | ||
|
fab2d44abd | ||
|
213196212e | ||
|
8f22887c45 | ||
|
b720921c83 | ||
|
d57f021057 | ||
|
721393ce12 | ||
|
12eb0af3d1 | ||
|
e048e08dae | ||
|
27050f1ee4 | ||
|
d1c08d0b04 | ||
|
7341e60043 | ||
|
53a900a28c | ||
|
839b926e0b | ||
|
c177d3c33e | ||
|
11ac4eb21d | ||
|
07ecb56753 | ||
|
0ed360b5e5 | ||
|
2de7768f41 | ||
|
7641b60a2b | ||
|
a19f696a42 | ||
|
d167feda98 | ||
|
ae0fd3043a | ||
|
9b9d559312 | ||
|
278f2fe078 | ||
|
f78594dfc1 | ||
|
8f578bfddb | ||
|
48e5a11d68 | ||
|
af5084856a | ||
|
f385af25e5 | ||
|
6234909373 | ||
|
5b1aca1a91 | ||
|
49b7497491 | ||
|
e4d7e1fe3f | ||
|
f8bb93a0f4 | ||
|
eee0c83324 | ||
|
a07980536a | ||
|
729c4a9399 | ||
|
8932e4821d | ||
|
998f9725d0 | ||
|
60c7ac6f95 | ||
|
5e017dc0f2 | ||
|
0b47cd1c67 | ||
|
befd99edac | ||
|
2eff9a19c3 | ||
|
c4253dc0f9 | ||
|
8df263340f | ||
|
42620e7f39 | ||
|
9081f672d3 | ||
|
0bbfd59540 | ||
|
8c43a3da32 | ||
|
df7710a131 | ||
|
450c667aa7 | ||
|
76be36c9ce | ||
|
0a8523b349 | ||
|
7e5e7673ae | ||
|
9393dce1ff | ||
|
4aec1db8f3 | ||
|
8ba3a33129 | ||
|
7895d84c40 | ||
|
d8c529a9b8 | ||
|
736dd13262 | ||
|
3eb5b95e27 | ||
|
f65887be33 | ||
|
57174cdabe | ||
|
36e8441149 | ||
|
f7426b18b7 | ||
|
ea179ad762 | ||
|
5c7080c1f4 | ||
|
5c7d743883 | ||
|
1988ee9cd6 | ||
|
2e0fefd1ce | ||
|
ebe9d20fcb | ||
|
58b265cd71 | ||
|
79deb98486 | ||
|
7aa8b1c658 | ||
|
d6ecb2699f | ||
|
b0e5714ca0 | ||
|
db4bbb3569 | ||
|
2c5366f259 | ||
|
2395e57f45 | ||
|
4593c3a172 | ||
|
dbd079f54c | ||
|
72fa1d29a9 | ||
|
6dd5e51bd8 | ||
|
8b2b6769c2 | ||
|
4988dc891b | ||
|
5553732ae2 | ||
|
c2e5dae7ba | ||
|
40ca3ae796 | ||
|
9a366281d3 | ||
|
53799c4c5b | ||
|
803442fc5f | ||
|
5332fcade6 | ||
|
a1e2357d12 | ||
|
dec9d534e6 | ||
|
b660704faa | ||
|
4bd0ced87d | ||
|
6af36adf1c | ||
|
8d362f682b | ||
|
418b483af6 | ||
|
f02a78d8ff | ||
|
247f27af37 | ||
|
41868f8adb | ||
|
d459a03ea5 | ||
|
bfe31137a9 | ||
|
d3a17d25e1 | ||
|
0e1de75af8 | ||
|
c8d67aa8eb | ||
|
f7adb68599 | ||
|
fe7ce94dac | ||
|
432138f645 | ||
|
8841c3398c | ||
|
747f8d5877 | ||
|
835bf32c5e | ||
|
ea88bbdc33 | ||
|
2fd2aceeb2 | ||
|
dc17e2983d | ||
|
c8b40da7ab | ||
|
bfa456aaed | ||
|
b7ed06b17a | ||
|
cf941b0eda | ||
|
b1bc901a66 | ||
|
eb007a233f | ||
|
5354df8c1c | ||
|
11951eb009 | ||
|
e2d66cf796 | ||
|
5f8d34feb3 | ||
|
d1500ae229 | ||
|
35828e8fe7 | ||
|
0f0a2ddafe | ||
|
46830124f8 | ||
|
c099b29b36 | ||
|
55ce2ebd53 | ||
|
9e89a963d9 | ||
|
2ae71b30ae | ||
|
48e281c5bd | ||
|
5544dd35d0 | ||
|
d15173ad9d | ||
|
a86397a650 | ||
|
0413a305d2 | ||
|
eb83604eaf | ||
|
d527e39c64 | ||
|
de736e00ad | ||
|
e5281157fa | ||
|
adc984a225 | ||
|
757e147b3b | ||
|
2ebc68a9e2 | ||
|
b5f3ced860 | ||
|
3534145960 | ||
|
d749f759af | ||
|
e310bad7ab | ||
|
71d5409b3b | ||
|
069ba2bc9e | ||
|
967d6e0e3d | ||
|
2f3ae52e8f | ||
|
e9b610b8df | ||
|
f1ba2387d3 | ||
|
750e5344f1 | ||
|
f8606fca4f | ||
|
7f108f373a | ||
|
5ab8d19bac | ||
|
fec016f710 | ||
|
2e4eb26fca | ||
|
4196686acf | ||
|
768b386f0a | ||
|
23fa84b322 | ||
|
6c5b8f324a | ||
|
b10f874f49 | ||
|
820af533a4 | ||
|
7c736f71fe | ||
|
346e982e28 | ||
|
f6af505ae4 | ||
|
2011ed3344 | ||
|
de22f3d0e7 | ||
|
279000eeb3 | ||
|
74bf0d8d3b | ||
|
30dbe257cf | ||
|
6222fbcc66 | ||
|
338f66f9aa | ||
|
4773e13ef7 | ||
|
8f88ab1a05 | ||
|
1592597d93 | ||
|
6194a29875 | ||
|
99fb36fe45 | ||
|
84a4f743b8 | ||
|
072be66704 | ||
|
fdac3c6b80 | ||
|
6162c2d0d5 | ||
|
00a8f90f79 | ||
|
13c5c9d31b | ||
|
4da1e9833c | ||
|
f519fdecc2 | ||
|
1277b3a8f6 | ||
|
9f0c20aa5d | ||
|
574c356863 | ||
|
6c887c2b87 | ||
|
7dbd2d3519 | ||
|
dfd829a2e2 | ||
|
e5210029b5 | ||
|
b1e452f876 | ||
|
9071da6da7 | ||
|
d4309b6481 | ||
|
be03731379 | ||
|
54137e3446 | ||
|
bb8ccbb022 | ||
|
473b5249e3 | ||
|
cbccac90ce | ||
|
5715f0b81a | ||
|
55b5957d49 | ||
|
0a94e7e7fa | ||
|
f8221797c6 | ||
|
a992bb5f94 | ||
|
8ddb116659 | ||
|
c300748884 | ||
|
f2703e2f53 | ||
|
4adbcae167 | ||
|
51e818ad64 | ||
|
5f05086ed6 | ||
|
9290e561e1 | ||
|
697a0e2947 | ||
|
1b238dd63e | ||
|
7ef50a9352 | ||
|
17645ee20c | ||
|
6c242f3fec | ||
|
2910fd467f | ||
|
713851b68d | ||
|
f12fc66a69 | ||
|
e1dc05fae0 | ||
|
177c9c3aec | ||
|
0c97e39675 | ||
|
c0d6761f63 | ||
|
15d7619bdc | ||
|
191a2eb088 | ||
|
97e53f867f | ||
|
88ca04dbdb | ||
|
da210ddd51 | ||
|
1925b0bd0b | ||
|
140b2392f6 | ||
|
de5fb3ba0e | ||
|
685e456eff | ||
|
5a3f10adbc | ||
|
19c053c839 | ||
|
40bdbc426e | ||
|
a4f5f3e978 | ||
|
fe3729c33e | ||
|
c40a427b3a | ||
|
91f843c56c | ||
|
ebc45bd73f | ||
|
a9f76862fb | ||
|
068d23f298 | ||
|
4d918f83ff | ||
|
88d8d3d02a | ||
|
921529426d | ||
|
fb822688b7 | ||
|
4e815eaeb6 | ||
|
c544116cf2 | ||
|
102d15f081 | ||
|
3d97b04815 | ||
|
40656911a6 | ||
|
c7eba80836 | ||
|
5a7e99f283 | ||
|
edadd5d6d5 | ||
|
7e25130529 | ||
|
b7c2681903 | ||
|
67fdf593a2 | ||
|
bad486823c | ||
|
4f2f9bd26f | ||
|
4b52306063 | ||
|
ac9787cd1c | ||
|
c0ac8aae08 | ||
|
e6aa3a4e07 | ||
|
bf01dca13f | ||
|
a3165c6a61 | ||
|
770d3d383c | ||
|
5530ee4c95 | ||
|
1061b50665 | ||
|
3fdd8ffdf0 | ||
|
14dcaaee6c | ||
|
4ac75a8558 | ||
|
c7a2fbe7eb | ||
|
b36e60738e | ||
|
5a63c9d535 | ||
|
c9b1d08218 | ||
|
5c4b8153c6 | ||
|
9abb7db5f8 | ||
|
4222932e08 | ||
|
8d1400d3d6 | ||
|
2261c066f3 | ||
|
7430896c79 | ||
|
03263c850a | ||
|
964cfb05ea | ||
|
02c0981ecf | ||
|
a11f137810 | ||
|
087fd32ce3 | ||
|
1b2276520b | ||
|
175c0e611b | ||
|
227ea934ff | ||
|
22c46ebf96 | ||
|
1f7af14386 | ||
|
e56ea138c7 | ||
|
dfae9a9864 | ||
|
0e02740565 | ||
|
b876fb84ba | ||
|
f9049d6ee4 | ||
|
98cca1e774 | ||
|
6e0353965a | ||
|
243e05d59f | ||
|
019c75797d | ||
|
54dae6ba2c | ||
|
57dfebc5ba | ||
|
494968be66 | ||
|
c31e1f5bf0 | ||
|
faa016e4b7 | ||
|
59d266a111 | ||
|
1617a025ce | ||
|
9d2293bb32 | ||
|
e12ab9d0dd |
9
.buildkite/env/secrets.ejson
vendored
9
.buildkite/env/secrets.ejson
vendored
@@ -1,12 +1,7 @@
|
||||
{
|
||||
"_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f",
|
||||
"_comment": "These credentials are encrypted and pose no risk",
|
||||
"environment": {
|
||||
"CODECOV_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:3K68mE38LJ2RB98VWmjuNLFBNn1XTGR4:cR4r05/TOZQKmEZp1v4CSgUJtC6QJiOaL85QjXW0qZ061fMnsBA8AtAPMDoDq4WCGOZM1A==]",
|
||||
"CRATES_IO_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:GGRTYDjMXksevzR6kq4Jx+FaIQZz50RU:xkbwDxcgoCyU+aT2tiI9mymigrEl6YiOr3axe3aX70ELIBKbCdPGilXP/wixvKi94g2u]",
|
||||
"GEOLOCATION_API_KEY": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:U2PZLi5MU3Ru/zK1SilianEeizcMvxml:AJKf2OAtDHmJh0KyXrBnNnistItZvVVP3cZ7ZLtrVupjmWN/PzmKwSsXeCNObWS+]",
|
||||
"GITHUB_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:0NJNlpD/O19mvOakCGBYDhIDfySxWFSC:Dz4NXv9x6ncRQ1u9sVoWOcqmkg0sI09qmefghB0GXZgPcFGgn6T0mw7ynNnbUvjyH8dLruKHauk=]",
|
||||
"INFLUX_DATABASE": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:SzwHIeOVpmbTcGQOGngoFgYumsLZJUGq:t7Rpk49njsWvoM+ztv5Uwuiz]",
|
||||
"INFLUX_PASSWORD": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:/MUs+q7pdGrUjzwcq+6pgIFxur4hxdqu:am22z2E2dtmw1f1J1Mq5JLcUHZsrEjQAJ0pp21M4AZeJbNO6bVb44d9zSkHj7xdN6U+GNlCk+wU=]",
|
||||
"INFLUX_USERNAME": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:XjghH20xGVWro9B+epGlJaJcW8Wze0Bi:ZIdOtXudTY5TqKseDU7gVvQXfmXV99Xh]"
|
||||
"CODECOV_TOKEN": "EJ[1:Z7OneT3RdJJ0DipCHQ7rC84snQ+FPbgHwZADQiz54wk=:3K68mE38LJ2RB98VWmjuNLFBNn1XTGR4:cR4r05/TOZQKmEZp1v4CSgUJtC6QJiOaL85QjXW0qZ061fMnsBA8AtAPMDoDq4WCGOZM1A==]"
|
||||
}
|
||||
}
|
||||
|
@@ -3,16 +3,19 @@
|
||||
#
|
||||
# Save target/ for the next CI build on this machine
|
||||
#
|
||||
(
|
||||
set -x
|
||||
d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL"
|
||||
mkdir -p "$d"
|
||||
set -x
|
||||
rsync -a --delete --link-dest="$PWD" target "$d"
|
||||
du -hs "$d"
|
||||
read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d")
|
||||
echo "--- ${cacheSizeInGB}GB: $d"
|
||||
)
|
||||
if [[ -z $CARGO_TARGET_CACHE ]]; then
|
||||
echo "+++ CARGO_TARGET_CACHE not defined" # pre-command should have defined it
|
||||
else
|
||||
(
|
||||
set -x
|
||||
mkdir -p "$CARGO_TARGET_CACHE"
|
||||
set -x
|
||||
rsync -a --delete --link-dest="$PWD" target "$CARGO_TARGET_CACHE"
|
||||
du -hs "$CARGO_TARGET_CACHE"
|
||||
read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$CARGO_TARGET_CACHE")
|
||||
echo "--- ${cacheSizeInGB}GB: $CARGO_TARGET_CACHE"
|
||||
)
|
||||
fi
|
||||
|
||||
#
|
||||
# Add job_stats data point
|
||||
|
@@ -11,23 +11,29 @@ export PS4="++"
|
||||
#
|
||||
# Restore target/ from the previous CI build on this machine
|
||||
#
|
||||
eval "$(ci/channel-info.sh)"
|
||||
export CARGO_TARGET_CACHE=$HOME/cargo-target-cache/"$CHANNEL"-"$BUILDKITE_LABEL"
|
||||
(
|
||||
set -x
|
||||
d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL"
|
||||
MAX_CACHE_SIZE=18 # gigabytes
|
||||
|
||||
if [[ -d $d ]]; then
|
||||
du -hs "$d"
|
||||
read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d")
|
||||
echo "--- ${cacheSizeInGB}GB: $d"
|
||||
if [[ -d $CARGO_TARGET_CACHE ]]; then
|
||||
du -hs "$CARGO_TARGET_CACHE"
|
||||
read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$CARGO_TARGET_CACHE")
|
||||
echo "--- ${cacheSizeInGB}GB: $CARGO_TARGET_CACHE"
|
||||
if [[ $cacheSizeInGB -gt $MAX_CACHE_SIZE ]]; then
|
||||
echo "--- $d is too large, removing it"
|
||||
rm -rf "$d"
|
||||
echo "--- $CARGO_TARGET_CACHE is too large, removing it"
|
||||
rm -rf "$CARGO_TARGET_CACHE"
|
||||
fi
|
||||
else
|
||||
echo "--- $d not present"
|
||||
echo "--- $CARGO_TARGET_CACHE not present"
|
||||
fi
|
||||
|
||||
mkdir -p "$d"/target
|
||||
rsync -a --delete --link-dest="$d" "$d"/target .
|
||||
mkdir -p "$CARGO_TARGET_CACHE"/target
|
||||
rsync -a --delete --link-dest="$CARGO_TARGET_CACHE" "$CARGO_TARGET_CACHE"/target .
|
||||
|
||||
# Don't reuse BPF target build artifacts due to incremental build issues with
|
||||
# `std:
|
||||
# "found possibly newer version of crate `std` which `xyz` depends on
|
||||
rm -rf target/bpfel-unknown-unknown
|
||||
)
|
||||
|
46
.github/stale.yml
vendored
46
.github/stale.yml
vendored
@@ -1,11 +1,3 @@
|
||||
only: pulls
|
||||
|
||||
# Number of days of inactivity before a pull request becomes stale
|
||||
daysUntilStale: 7
|
||||
|
||||
# Number of days of inactivity before a stale pull request is closed
|
||||
daysUntilClose: 7
|
||||
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- security
|
||||
@@ -14,12 +6,34 @@ exemptLabels:
|
||||
# Label to use when marking a pull request as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking a pull request as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This pull request has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs.
|
||||
pulls:
|
||||
# Number of days of inactivity before a pull request becomes stale
|
||||
daysUntilStale: 7
|
||||
|
||||
# Comment to post when closing a stale pull request. Set to `false` to disable
|
||||
closeComment: >
|
||||
This stale pull request has been automatically closed.
|
||||
Thank you for your contributions.
|
||||
# Number of days of inactivity before a stale pull request is closed
|
||||
daysUntilClose: 7
|
||||
# Comment to post when marking a pull request as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This pull request has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs.
|
||||
|
||||
# Comment to post when closing a stale pull request. Set to `false` to disable
|
||||
closeComment: >
|
||||
This stale pull request has been automatically closed.
|
||||
Thank you for your contributions.
|
||||
|
||||
issues:
|
||||
# Number of days of inactivity before a issue becomes stale
|
||||
daysUntilStale: 365
|
||||
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Comment to post when marking a issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs.
|
||||
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: >
|
||||
This stale issue has been automatically closed.
|
||||
Thank you for your contributions.
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -14,6 +14,8 @@
|
||||
|
||||
/config/
|
||||
|
||||
.cache
|
||||
|
||||
# log files
|
||||
*.log
|
||||
log-*.txt
|
||||
|
40
.mergify.yml
40
.mergify.yml
@@ -50,22 +50,6 @@ pull_request_rules:
|
||||
label:
|
||||
add:
|
||||
- automerge
|
||||
- name: v1.1 backport
|
||||
conditions:
|
||||
- label=v1.1
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.1
|
||||
- name: v1.2 backport
|
||||
conditions:
|
||||
- label=v1.2
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.2
|
||||
- name: v1.3 backport
|
||||
conditions:
|
||||
- label=v1.3
|
||||
@@ -74,3 +58,27 @@ pull_request_rules:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.3
|
||||
- name: v1.4 backport
|
||||
conditions:
|
||||
- label=v1.4
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.4
|
||||
- name: v1.5 backport
|
||||
conditions:
|
||||
- label=v1.5
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.5
|
||||
- name: v1.6 backport
|
||||
conditions:
|
||||
- label=v1.6
|
||||
actions:
|
||||
backport:
|
||||
ignore_conflicts: true
|
||||
branches:
|
||||
- v1.6
|
||||
|
10
.travis.yml
10
.travis.yml
@@ -34,6 +34,8 @@ jobs:
|
||||
- stable
|
||||
install:
|
||||
- source ci/rust-version.sh
|
||||
- PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"
|
||||
- readlink -f .
|
||||
script:
|
||||
- source ci/env.sh
|
||||
- ci/publish-tarball.sh
|
||||
@@ -67,7 +69,8 @@ jobs:
|
||||
|
||||
# explorer pull request
|
||||
- name: "explorer"
|
||||
if: type = pull_request
|
||||
if: type = pull_request AND branch = master
|
||||
|
||||
language: node_js
|
||||
node_js:
|
||||
- "node"
|
||||
@@ -86,7 +89,8 @@ jobs:
|
||||
|
||||
# web3.js pull request
|
||||
- name: "web3.js"
|
||||
if: type = pull_request
|
||||
if: type = pull_request AND branch = master
|
||||
|
||||
language: node_js
|
||||
node_js:
|
||||
- "lts/*"
|
||||
@@ -122,6 +126,8 @@ jobs:
|
||||
- ~/.npm
|
||||
|
||||
before_install:
|
||||
- source ci/env.sh
|
||||
- .travis/channel_restriction.sh edge beta || travis_terminate 0
|
||||
- .travis/affects.sh docs/ .travis || travis_terminate 0
|
||||
- cd docs/
|
||||
- source .travis/before_install.sh
|
||||
|
19
.travis/channel_restriction.sh
Executable file
19
.travis/channel_restriction.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Only proceed if we are on one of the channels passed in, or a tag build
|
||||
#
|
||||
|
||||
set -ex
|
||||
|
||||
[[ -n $CI_TAG ]] && exit 0
|
||||
|
||||
eval "$(ci/channel-info.sh)"
|
||||
|
||||
for acceptable_channel in "$@"; do
|
||||
if [[ "$CHANNEL" == "$acceptable_channel" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Not running from one of the following channels: $*"
|
||||
exit 1
|
@@ -232,7 +232,7 @@ confused with 3-letter acronyms.
|
||||
Solana's architecture is described by docs generated from markdown files in
|
||||
the `docs/src/` directory, maintained by an *editor* (currently @garious). To
|
||||
add a design proposal, you'll need to include it in the
|
||||
[Accepted Design Proposals](https://docs.solana.com/proposals)
|
||||
[Accepted Design Proposals](https://docs.solana.com/proposals/accepted-design-proposals)
|
||||
section of the Solana docs. Here's the full process:
|
||||
|
||||
1. Propose a design by creating a PR that adds a markdown document to the
|
||||
|
2897
Cargo.lock
generated
2897
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
@@ -5,17 +5,21 @@ members = [
|
||||
"bench-tps",
|
||||
"accounts-bench",
|
||||
"banking-bench",
|
||||
"banks-client",
|
||||
"banks-interface",
|
||||
"banks-server",
|
||||
"clap-utils",
|
||||
"cli-config",
|
||||
"cli-output",
|
||||
"client",
|
||||
"core",
|
||||
"dos",
|
||||
"download-utils",
|
||||
"faucet",
|
||||
"frozen-abi",
|
||||
"perf",
|
||||
"validator",
|
||||
"genesis",
|
||||
"genesis-programs",
|
||||
"gossip",
|
||||
"install",
|
||||
"keygen",
|
||||
@@ -26,12 +30,16 @@ members = [
|
||||
"log-analyzer",
|
||||
"merkle-tree",
|
||||
"stake-o-matic",
|
||||
"storage-bigtable",
|
||||
"storage-proto",
|
||||
"streamer",
|
||||
"measure",
|
||||
"metrics",
|
||||
"net-shaper",
|
||||
"notifier",
|
||||
"poh-bench",
|
||||
"program-test",
|
||||
"programs/secp256k1",
|
||||
"programs/bpf_loader",
|
||||
"programs/budget",
|
||||
"programs/config",
|
||||
@@ -45,7 +53,10 @@ members = [
|
||||
"remote-wallet",
|
||||
"ramp-tps",
|
||||
"runtime",
|
||||
"runtime/store-tool",
|
||||
"sdk",
|
||||
"sdk/cargo-build-bpf",
|
||||
"sdk/cargo-test-bpf",
|
||||
"scripts",
|
||||
"stake-accounts",
|
||||
"stake-monitor",
|
||||
@@ -56,7 +67,6 @@ members = [
|
||||
"upload-perf",
|
||||
"net-utils",
|
||||
"version",
|
||||
"vote-signer",
|
||||
"cli",
|
||||
"rayon-threadlimit",
|
||||
"watchtower",
|
||||
|
13
README.md
13
README.md
@@ -19,7 +19,7 @@ $ source $HOME/.cargo/env
|
||||
$ rustup component add rustfmt
|
||||
```
|
||||
|
||||
If your rustc version is lower than 1.39.0, please update it:
|
||||
Please sure you are always using the latest stable rust version by running:
|
||||
|
||||
```bash
|
||||
$ rustup update
|
||||
@@ -29,7 +29,7 @@ On Linux systems you may need to install libssl-dev, pkg-config, zlib1g-dev, etc
|
||||
|
||||
```bash
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang
|
||||
$ sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang make
|
||||
```
|
||||
|
||||
## **2. Download the source code.**
|
||||
@@ -59,10 +59,11 @@ $ cargo test
|
||||
```
|
||||
|
||||
### Starting a local testnet
|
||||
Start your own testnet locally, instructions are in the [online docs](https://docs.solana.com/bench-tps).
|
||||
Start your own testnet locally, instructions are in the [online docs](https://docs.solana.com/cluster/bench-tps).
|
||||
|
||||
### Accessing the remote testnet
|
||||
* `testnet` - public stable testnet accessible via devnet.solana.com. Runs 24/7
|
||||
### Accessing the remote development cluster
|
||||
* `devnet` - stable public cluster for development accessible via
|
||||
devnet.solana.com. Runs 24/7. Learn more about the [public clusters](https://docs.solana.com/clusters)
|
||||
|
||||
# Benchmarking
|
||||
|
||||
@@ -107,3 +108,5 @@ send us that patch!
|
||||
# Disclaimer
|
||||
|
||||
All claims, content, designs, algorithms, estimates, roadmaps, specifications, and performance measurements described in this project are done with the author's best effort. It is up to the reader to check and validate their accuracy and truthfulness. Furthermore nothing in this project constitutes a solicitation for investment.
|
||||
|
||||
Any content produced by Solana, or developer resources that Solana provides, are for educational and inspiration purposes only. Solana does not encourage, induce or sanction the deployment of any such applications in violation of applicable laws or regulations.
|
||||
|
17
RELEASE.md
17
RELEASE.md
@@ -107,11 +107,15 @@ Alternatively use the Github UI.
|
||||
1. If the Cargo.toml version field is **0.12.3**, then the release tag must be **v0.12.3**
|
||||
1. Make sure the Target Branch field matches the branch you want to make a release on.
|
||||
1. If you want to release v0.12.0, the target branch must be v0.12
|
||||
1. If this is the first release on the branch (e.g. v0.13.**0**), paste in [this
|
||||
template](https://raw.githubusercontent.com/solana-labs/solana/master/.github/RELEASE_TEMPLATE.md). Engineering Lead can provide summary contents for release notes if needed. If this is a patch release, review all the commits since the previous release on this branch and add details as needed.
|
||||
1. Fill the release notes.
|
||||
1. If this is the first release on the branch (e.g. v0.13.**0**), paste in [this
|
||||
template](https://raw.githubusercontent.com/solana-labs/solana/master/.github/RELEASE_TEMPLATE.md). Engineering Lead can provide summary contents for release notes if needed.
|
||||
1. If this is a patch release, review all the commits since the previous release on this branch and add details as needed.
|
||||
1. Click "Save Draft", then confirm the release notes look good and the tag name and branch are correct.
|
||||
1. Ensure the release is marked **"This is a pre-release"**. This flag will then need to be be removed once the the Linux binary artifacts appear later.
|
||||
1. Go back into edit the release and click "Publish release" when ready.
|
||||
1. Ensure all desired commits (usually backports) are landed on the branch by now.
|
||||
1. Ensure the release is marked **"This is a pre-release"**. This flag will need to be be removed manually after confirming the the Linux binary artifacts appear at a later step.
|
||||
1. Go back into edit the release and click "Publish release" while being marked as a pre-release.
|
||||
1. Confirm there is new git tag with intended version number at the intended revision after running `git fetch` locally.
|
||||
|
||||
|
||||
### Update release branch with the next patch version
|
||||
@@ -131,7 +135,8 @@ Alternatively use the Github UI.
|
||||
1. Open a PR against origin/vX.Y and then merge the PR after passing CI.
|
||||
|
||||
### Prepare for the next release
|
||||
1. Go to [GitHub Releases](https://github.com/solana-labs/solana/releases) and create a new draft release for `X.Y.Z+1` with empty release nodes. This allows people to incrementally add new release notes until it's time for the next release
|
||||
1. Go to [GitHub Releases](https://github.com/solana-labs/solana/releases) and create a new draft release for `X.Y.Z+1` with empty release notes. This allows people to incrementally add new release notes until it's time for the next release
|
||||
1. Also, point the branch field to the same branch and mark the relese as **"This is a pre-release"**.
|
||||
1. Go to the [Github Milestones](https://github.com/solana-labs/solana/milestones). Create a new milestone for the `X.Y.Z+1`, move over
|
||||
unresolved issues still in the `X.Y.Z` milestone, then close the `X.Y.Z` milestone.
|
||||
|
||||
@@ -147,5 +152,5 @@ appearing. To check for progress:
|
||||
[Crates.io](https://crates.io/crates/solana) should have an updated Solana version. This can take 2-3 hours, and sometimes fails in the `solana-secondary` job.
|
||||
If this happens and the error is non-fatal, click "Retry" on the "publish crate" job
|
||||
|
||||
### Update software on devnet.solana.com/testnet.solama.com/mainnet-beta.solana.com
|
||||
### Update software on devnet.solana.com/testnet.solana.com/mainnet-beta.solana.com
|
||||
See the documentation at https://github.com/solana-labs/cluster-ops/
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-account-decoder"
|
||||
version = "1.3.0"
|
||||
version = "1.5.2"
|
||||
description = "Solana account decoder"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -9,17 +9,22 @@ license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.12.3"
|
||||
bincode = "1.3.1"
|
||||
bs58 = "0.3.1"
|
||||
bv = "0.11.1"
|
||||
Inflector = "0.11.4"
|
||||
lazy_static = "1.4.0"
|
||||
solana-sdk = { path = "../sdk", version = "1.3.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
|
||||
spl-token-v1-0 = { package = "spl-token", version = "1.0.6", features = ["skip-no-mangle"] }
|
||||
serde = "1.0.112"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-config-program = { path = "../programs/config", version = "1.5.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.2" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.2" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.2" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.0.1", features = ["no-entrypoint"] }
|
||||
thiserror = "1.0"
|
||||
zstd = "0.5.1"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -4,13 +4,24 @@ extern crate lazy_static;
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod parse_account_data;
|
||||
pub mod parse_config;
|
||||
pub mod parse_nonce;
|
||||
pub mod parse_stake;
|
||||
pub mod parse_sysvar;
|
||||
pub mod parse_token;
|
||||
pub mod parse_vote;
|
||||
pub mod validator_info;
|
||||
|
||||
use crate::parse_account_data::{parse_account_data, ParsedAccount};
|
||||
use solana_sdk::{account::Account, clock::Epoch, pubkey::Pubkey};
|
||||
use std::str::FromStr;
|
||||
use {
|
||||
crate::parse_account_data::{parse_account_data, AccountAdditionalData, ParsedAccount},
|
||||
solana_sdk::{account::Account, clock::Epoch, fee_calculator::FeeCalculator, pubkey::Pubkey},
|
||||
std::{
|
||||
io::{Read, Write},
|
||||
str::FromStr,
|
||||
},
|
||||
};
|
||||
|
||||
pub type StringAmount = String;
|
||||
|
||||
/// A duplicate representation of an Account for pretty JSON serialization
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
@@ -26,32 +37,62 @@ pub struct UiAccount {
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum UiAccountData {
|
||||
Binary(String),
|
||||
LegacyBinary(String), // Legacy. Retained for RPC backwards compatibility
|
||||
Json(ParsedAccount),
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for UiAccountData {
|
||||
fn from(data: Vec<u8>) -> Self {
|
||||
Self::Binary(bs58::encode(data).into_string())
|
||||
}
|
||||
Binary(String, UiAccountEncoding),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum UiAccountEncoding {
|
||||
Binary,
|
||||
Binary, // Legacy. Retained for RPC backwards compatibility
|
||||
Base58,
|
||||
Base64,
|
||||
JsonParsed,
|
||||
#[serde(rename = "base64+zstd")]
|
||||
Base64Zstd,
|
||||
}
|
||||
|
||||
impl UiAccount {
|
||||
pub fn encode(account: Account, encoding: UiAccountEncoding) -> Self {
|
||||
pub fn encode(
|
||||
pubkey: &Pubkey,
|
||||
account: Account,
|
||||
encoding: UiAccountEncoding,
|
||||
additional_data: Option<AccountAdditionalData>,
|
||||
data_slice_config: Option<UiDataSliceConfig>,
|
||||
) -> Self {
|
||||
let data = match encoding {
|
||||
UiAccountEncoding::Binary => account.data.into(),
|
||||
UiAccountEncoding::Binary => UiAccountData::LegacyBinary(
|
||||
bs58::encode(slice_data(&account.data, data_slice_config)).into_string(),
|
||||
),
|
||||
UiAccountEncoding::Base58 => UiAccountData::Binary(
|
||||
bs58::encode(slice_data(&account.data, data_slice_config)).into_string(),
|
||||
encoding,
|
||||
),
|
||||
UiAccountEncoding::Base64 => UiAccountData::Binary(
|
||||
base64::encode(slice_data(&account.data, data_slice_config)),
|
||||
encoding,
|
||||
),
|
||||
UiAccountEncoding::Base64Zstd => {
|
||||
let mut encoder = zstd::stream::write::Encoder::new(Vec::new(), 0).unwrap();
|
||||
match encoder
|
||||
.write_all(slice_data(&account.data, data_slice_config))
|
||||
.and_then(|()| encoder.finish())
|
||||
{
|
||||
Ok(zstd_data) => UiAccountData::Binary(base64::encode(zstd_data), encoding),
|
||||
Err(_) => UiAccountData::Binary(
|
||||
base64::encode(slice_data(&account.data, data_slice_config)),
|
||||
UiAccountEncoding::Base64,
|
||||
),
|
||||
}
|
||||
}
|
||||
UiAccountEncoding::JsonParsed => {
|
||||
if let Ok(parsed_data) = parse_account_data(&account.owner, &account.data) {
|
||||
if let Ok(parsed_data) =
|
||||
parse_account_data(pubkey, &account.owner, &account.data, additional_data)
|
||||
{
|
||||
UiAccountData::Json(parsed_data)
|
||||
} else {
|
||||
account.data.into()
|
||||
UiAccountData::Binary(base64::encode(&account.data), UiAccountEncoding::Base64)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -67,7 +108,22 @@ impl UiAccount {
|
||||
pub fn decode(&self) -> Option<Account> {
|
||||
let data = match &self.data {
|
||||
UiAccountData::Json(_) => None,
|
||||
UiAccountData::Binary(blob) => bs58::decode(blob).into_vec().ok(),
|
||||
UiAccountData::LegacyBinary(blob) => bs58::decode(blob).into_vec().ok(),
|
||||
UiAccountData::Binary(blob, encoding) => match encoding {
|
||||
UiAccountEncoding::Base58 => bs58::decode(blob).into_vec().ok(),
|
||||
UiAccountEncoding::Base64 => base64::decode(blob).ok(),
|
||||
UiAccountEncoding::Base64Zstd => base64::decode(blob)
|
||||
.ok()
|
||||
.map(|zstd_data| {
|
||||
let mut data = vec![];
|
||||
zstd::stream::read::Decoder::new(zstd_data.as_slice())
|
||||
.and_then(|mut reader| reader.read_to_end(&mut data))
|
||||
.map(|_| data)
|
||||
.ok()
|
||||
})
|
||||
.flatten(),
|
||||
UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None,
|
||||
},
|
||||
}?;
|
||||
Some(Account {
|
||||
lamports: self.lamports,
|
||||
@@ -78,3 +134,100 @@ impl UiAccount {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiFeeCalculator {
|
||||
pub lamports_per_signature: StringAmount,
|
||||
}
|
||||
|
||||
impl From<FeeCalculator> for UiFeeCalculator {
|
||||
fn from(fee_calculator: FeeCalculator) -> Self {
|
||||
Self {
|
||||
lamports_per_signature: fee_calculator.lamports_per_signature.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UiFeeCalculator {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
lamports_per_signature: "0".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiDataSliceConfig {
|
||||
pub offset: usize,
|
||||
pub length: usize,
|
||||
}
|
||||
|
||||
fn slice_data(data: &[u8], data_slice_config: Option<UiDataSliceConfig>) -> &[u8] {
|
||||
if let Some(UiDataSliceConfig { offset, length }) = data_slice_config {
|
||||
if offset >= data.len() {
|
||||
&[]
|
||||
} else if length > data.len() - offset {
|
||||
&data[offset..]
|
||||
} else {
|
||||
&data[offset..offset + length]
|
||||
}
|
||||
} else {
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_slice_data() {
|
||||
let data = vec![1, 2, 3, 4, 5];
|
||||
let slice_config = Some(UiDataSliceConfig {
|
||||
offset: 0,
|
||||
length: 5,
|
||||
});
|
||||
assert_eq!(slice_data(&data, slice_config), &data[..]);
|
||||
|
||||
let slice_config = Some(UiDataSliceConfig {
|
||||
offset: 0,
|
||||
length: 10,
|
||||
});
|
||||
assert_eq!(slice_data(&data, slice_config), &data[..]);
|
||||
|
||||
let slice_config = Some(UiDataSliceConfig {
|
||||
offset: 1,
|
||||
length: 2,
|
||||
});
|
||||
assert_eq!(slice_data(&data, slice_config), &data[1..3]);
|
||||
|
||||
let slice_config = Some(UiDataSliceConfig {
|
||||
offset: 10,
|
||||
length: 2,
|
||||
});
|
||||
assert_eq!(slice_data(&data, slice_config), &[] as &[u8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_base64_zstd() {
|
||||
let encoded_account = UiAccount::encode(
|
||||
&Pubkey::default(),
|
||||
Account {
|
||||
data: vec![0; 1024],
|
||||
..Account::default()
|
||||
},
|
||||
UiAccountEncoding::Base64Zstd,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert!(matches!(
|
||||
encoded_account.data,
|
||||
UiAccountData::Binary(_, UiAccountEncoding::Base64Zstd)
|
||||
));
|
||||
|
||||
let decoded_account = encoded_account.decode().unwrap();
|
||||
assert_eq!(decoded_account.data, vec![0; 1024]);
|
||||
}
|
||||
}
|
||||
|
@@ -1,22 +1,31 @@
|
||||
use crate::{
|
||||
parse_config::parse_config,
|
||||
parse_nonce::parse_nonce,
|
||||
parse_token::{parse_token, spl_token_id_v1_0},
|
||||
parse_stake::parse_stake,
|
||||
parse_sysvar::parse_sysvar,
|
||||
parse_token::{parse_token, spl_token_id_v2_0},
|
||||
parse_vote::parse_vote,
|
||||
};
|
||||
use inflector::Inflector;
|
||||
use serde_json::Value;
|
||||
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program};
|
||||
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program, sysvar};
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
|
||||
lazy_static! {
|
||||
static ref CONFIG_PROGRAM_ID: Pubkey = solana_config_program::id();
|
||||
static ref STAKE_PROGRAM_ID: Pubkey = solana_stake_program::id();
|
||||
static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
|
||||
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v1_0();
|
||||
static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id();
|
||||
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v2_0();
|
||||
static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id();
|
||||
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config);
|
||||
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
|
||||
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken);
|
||||
m.insert(*STAKE_PROGRAM_ID, ParsableAccount::Stake);
|
||||
m.insert(*SYSVAR_PROGRAM_ID, ParsableAccount::Sysvar);
|
||||
m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
|
||||
m
|
||||
};
|
||||
@@ -30,6 +39,9 @@ pub enum ParseAccountError {
|
||||
#[error("Program not parsable")]
|
||||
ProgramNotParsable,
|
||||
|
||||
#[error("Additional data required to parse: {0}")]
|
||||
AdditionalDataMissing(String),
|
||||
|
||||
#[error("Instruction error")]
|
||||
InstructionError(#[from] InstructionError),
|
||||
|
||||
@@ -42,31 +54,49 @@ pub enum ParseAccountError {
|
||||
pub struct ParsedAccount {
|
||||
pub program: String,
|
||||
pub parsed: Value,
|
||||
pub space: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ParsableAccount {
|
||||
Config,
|
||||
Nonce,
|
||||
SplToken,
|
||||
Stake,
|
||||
Sysvar,
|
||||
Vote,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AccountAdditionalData {
|
||||
pub spl_token_decimals: Option<u8>,
|
||||
}
|
||||
|
||||
pub fn parse_account_data(
|
||||
pubkey: &Pubkey,
|
||||
program_id: &Pubkey,
|
||||
data: &[u8],
|
||||
additional_data: Option<AccountAdditionalData>,
|
||||
) -> Result<ParsedAccount, ParseAccountError> {
|
||||
let program_name = PARSABLE_PROGRAM_IDS
|
||||
.get(program_id)
|
||||
.ok_or_else(|| ParseAccountError::ProgramNotParsable)?;
|
||||
.ok_or(ParseAccountError::ProgramNotParsable)?;
|
||||
let additional_data = additional_data.unwrap_or_default();
|
||||
let parsed_json = match program_name {
|
||||
ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
|
||||
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
|
||||
ParsableAccount::SplToken => serde_json::to_value(parse_token(data)?)?,
|
||||
ParsableAccount::SplToken => {
|
||||
serde_json::to_value(parse_token(data, additional_data.spl_token_decimals)?)?
|
||||
}
|
||||
ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
|
||||
ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?,
|
||||
ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
|
||||
};
|
||||
Ok(ParsedAccount {
|
||||
program: format!("{:?}", program_name).to_kebab_case(),
|
||||
parsed: parsed_json,
|
||||
space: data.len() as u64,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -81,20 +111,35 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_parse_account_data() {
|
||||
let other_program = Pubkey::new_rand();
|
||||
let account_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let other_program = solana_sdk::pubkey::new_rand();
|
||||
let data = vec![0; 4];
|
||||
assert!(parse_account_data(&other_program, &data).is_err());
|
||||
assert!(parse_account_data(&account_pubkey, &other_program, &data, None).is_err());
|
||||
|
||||
let vote_state = VoteState::default();
|
||||
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
|
||||
let versioned = VoteStateVersions::Current(Box::new(vote_state));
|
||||
let versioned = VoteStateVersions::new_current(vote_state);
|
||||
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
|
||||
let parsed = parse_account_data(&solana_vote_program::id(), &vote_account_data).unwrap();
|
||||
let parsed = parse_account_data(
|
||||
&account_pubkey,
|
||||
&solana_vote_program::id(),
|
||||
&vote_account_data,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(parsed.program, "vote".to_string());
|
||||
assert_eq!(parsed.space, VoteState::size_of() as u64);
|
||||
|
||||
let nonce_data = Versions::new_current(State::Initialized(Data::default()));
|
||||
let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
|
||||
let parsed = parse_account_data(&system_program::id(), &nonce_account_data).unwrap();
|
||||
let parsed = parse_account_data(
|
||||
&account_pubkey,
|
||||
&system_program::id(),
|
||||
&nonce_account_data,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(parsed.program, "nonce".to_string());
|
||||
assert_eq!(parsed.space, State::size() as u64);
|
||||
}
|
||||
}
|
||||
|
146
account-decoder/src/parse_config.rs
Normal file
146
account-decoder/src/parse_config.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
use crate::{
|
||||
parse_account_data::{ParsableAccount, ParseAccountError},
|
||||
validator_info,
|
||||
};
|
||||
use bincode::deserialize;
|
||||
use serde_json::Value;
|
||||
use solana_config_program::{get_config_data, ConfigKeys};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_stake_program::config::Config as StakeConfig;
|
||||
|
||||
pub fn parse_config(data: &[u8], pubkey: &Pubkey) -> Result<ConfigAccountType, ParseAccountError> {
|
||||
let parsed_account = if pubkey == &solana_stake_program::config::id() {
|
||||
get_config_data(data)
|
||||
.ok()
|
||||
.and_then(|data| deserialize::<StakeConfig>(data).ok())
|
||||
.map(|config| ConfigAccountType::StakeConfig(config.into()))
|
||||
} else {
|
||||
deserialize::<ConfigKeys>(data).ok().and_then(|key_list| {
|
||||
if !key_list.keys.is_empty() && key_list.keys[0].0 == validator_info::id() {
|
||||
parse_config_data::<String>(data, key_list.keys).and_then(|validator_info| {
|
||||
Some(ConfigAccountType::ValidatorInfo(UiConfig {
|
||||
keys: validator_info.keys,
|
||||
config_data: serde_json::from_str(&validator_info.config_data).ok()?,
|
||||
}))
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
};
|
||||
parsed_account.ok_or(ParseAccountError::AccountNotParsable(
|
||||
ParsableAccount::Config,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_config_data<T>(data: &[u8], keys: Vec<(Pubkey, bool)>) -> Option<UiConfig<T>>
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
let config_data: T = deserialize(&get_config_data(data).ok()?).ok()?;
|
||||
let keys = keys
|
||||
.iter()
|
||||
.map(|key| UiConfigKey {
|
||||
pubkey: key.0.to_string(),
|
||||
signer: key.1,
|
||||
})
|
||||
.collect();
|
||||
Some(UiConfig { keys, config_data })
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
|
||||
pub enum ConfigAccountType {
|
||||
StakeConfig(UiStakeConfig),
|
||||
ValidatorInfo(UiConfig<Value>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiConfigKey {
|
||||
pub pubkey: String,
|
||||
pub signer: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiStakeConfig {
|
||||
pub warmup_cooldown_rate: f64,
|
||||
pub slash_penalty: u8,
|
||||
}
|
||||
|
||||
impl From<StakeConfig> for UiStakeConfig {
|
||||
fn from(config: StakeConfig) -> Self {
|
||||
Self {
|
||||
warmup_cooldown_rate: config.warmup_cooldown_rate,
|
||||
slash_penalty: config.slash_penalty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiConfig<T> {
|
||||
pub keys: Vec<UiConfigKey>,
|
||||
pub config_data: T,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::validator_info::ValidatorInfo;
|
||||
use serde_json::json;
|
||||
use solana_config_program::create_config_account;
|
||||
|
||||
#[test]
|
||||
fn test_parse_config() {
|
||||
let stake_config = StakeConfig {
|
||||
warmup_cooldown_rate: 0.25,
|
||||
slash_penalty: 50,
|
||||
};
|
||||
let stake_config_account = create_config_account(vec![], &stake_config, 10);
|
||||
assert_eq!(
|
||||
parse_config(
|
||||
&stake_config_account.data,
|
||||
&solana_stake_program::config::id()
|
||||
)
|
||||
.unwrap(),
|
||||
ConfigAccountType::StakeConfig(UiStakeConfig {
|
||||
warmup_cooldown_rate: 0.25,
|
||||
slash_penalty: 50,
|
||||
}),
|
||||
);
|
||||
|
||||
let validator_info = ValidatorInfo {
|
||||
info: serde_json::to_string(&json!({
|
||||
"name": "Solana",
|
||||
}))
|
||||
.unwrap(),
|
||||
};
|
||||
let info_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let validator_info_config_account = create_config_account(
|
||||
vec![(validator_info::id(), false), (info_pubkey, true)],
|
||||
&validator_info,
|
||||
10,
|
||||
);
|
||||
assert_eq!(
|
||||
parse_config(&validator_info_config_account.data, &info_pubkey).unwrap(),
|
||||
ConfigAccountType::ValidatorInfo(UiConfig {
|
||||
keys: vec![
|
||||
UiConfigKey {
|
||||
pubkey: validator_info::id().to_string(),
|
||||
signer: false,
|
||||
},
|
||||
UiConfigKey {
|
||||
pubkey: info_pubkey.to_string(),
|
||||
signer: true,
|
||||
}
|
||||
],
|
||||
config_data: serde_json::from_str(r#"{"name":"Solana"}"#).unwrap(),
|
||||
}),
|
||||
);
|
||||
|
||||
let bad_data = vec![0; 4];
|
||||
assert!(parse_config(&bad_data, &info_pubkey).is_err());
|
||||
}
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
use crate::parse_account_data::ParseAccountError;
|
||||
use crate::{parse_account_data::ParseAccountError, UiFeeCalculator};
|
||||
use solana_sdk::{
|
||||
fee_calculator::FeeCalculator,
|
||||
instruction::InstructionError,
|
||||
nonce::{state::Versions, State},
|
||||
};
|
||||
@@ -14,7 +13,7 @@ pub fn parse_nonce(data: &[u8]) -> Result<UiNonceState, ParseAccountError> {
|
||||
State::Initialized(data) => Ok(UiNonceState::Initialized(UiNonceData {
|
||||
authority: data.authority.to_string(),
|
||||
blockhash: data.blockhash.to_string(),
|
||||
fee_calculator: data.fee_calculator,
|
||||
fee_calculator: data.fee_calculator.into(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
@@ -32,7 +31,7 @@ pub enum UiNonceState {
|
||||
pub struct UiNonceData {
|
||||
pub authority: String,
|
||||
pub blockhash: String,
|
||||
pub fee_calculator: FeeCalculator,
|
||||
pub fee_calculator: UiFeeCalculator,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -56,7 +55,9 @@ mod test {
|
||||
UiNonceState::Initialized(UiNonceData {
|
||||
authority: Pubkey::default().to_string(),
|
||||
blockhash: Hash::default().to_string(),
|
||||
fee_calculator: FeeCalculator::default(),
|
||||
fee_calculator: UiFeeCalculator {
|
||||
lamports_per_signature: 0.to_string(),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
|
234
account-decoder/src/parse_stake.rs
Normal file
234
account-decoder/src/parse_stake.rs
Normal file
@@ -0,0 +1,234 @@
|
||||
use crate::{
|
||||
parse_account_data::{ParsableAccount, ParseAccountError},
|
||||
StringAmount,
|
||||
};
|
||||
use bincode::deserialize;
|
||||
use solana_sdk::clock::{Epoch, UnixTimestamp};
|
||||
use solana_stake_program::stake_state::{Authorized, Delegation, Lockup, Meta, Stake, StakeState};
|
||||
|
||||
pub fn parse_stake(data: &[u8]) -> Result<StakeAccountType, ParseAccountError> {
|
||||
let stake_state: StakeState = deserialize(data)
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Stake))?;
|
||||
let parsed_account = match stake_state {
|
||||
StakeState::Uninitialized => StakeAccountType::Uninitialized,
|
||||
StakeState::Initialized(meta) => StakeAccountType::Initialized(UiStakeAccount {
|
||||
meta: meta.into(),
|
||||
stake: None,
|
||||
}),
|
||||
StakeState::Stake(meta, stake) => StakeAccountType::Delegated(UiStakeAccount {
|
||||
meta: meta.into(),
|
||||
stake: Some(stake.into()),
|
||||
}),
|
||||
StakeState::RewardsPool => StakeAccountType::RewardsPool,
|
||||
};
|
||||
Ok(parsed_account)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
|
||||
pub enum StakeAccountType {
|
||||
Uninitialized,
|
||||
Initialized(UiStakeAccount),
|
||||
Delegated(UiStakeAccount),
|
||||
RewardsPool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiStakeAccount {
|
||||
pub meta: UiMeta,
|
||||
pub stake: Option<UiStake>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiMeta {
|
||||
pub rent_exempt_reserve: StringAmount,
|
||||
pub authorized: UiAuthorized,
|
||||
pub lockup: UiLockup,
|
||||
}
|
||||
|
||||
impl From<Meta> for UiMeta {
|
||||
fn from(meta: Meta) -> Self {
|
||||
Self {
|
||||
rent_exempt_reserve: meta.rent_exempt_reserve.to_string(),
|
||||
authorized: meta.authorized.into(),
|
||||
lockup: meta.lockup.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiLockup {
|
||||
pub unix_timestamp: UnixTimestamp,
|
||||
pub epoch: Epoch,
|
||||
pub custodian: String,
|
||||
}
|
||||
|
||||
impl From<Lockup> for UiLockup {
|
||||
fn from(lockup: Lockup) -> Self {
|
||||
Self {
|
||||
unix_timestamp: lockup.unix_timestamp,
|
||||
epoch: lockup.epoch,
|
||||
custodian: lockup.custodian.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiAuthorized {
|
||||
pub staker: String,
|
||||
pub withdrawer: String,
|
||||
}
|
||||
|
||||
impl From<Authorized> for UiAuthorized {
|
||||
fn from(authorized: Authorized) -> Self {
|
||||
Self {
|
||||
staker: authorized.staker.to_string(),
|
||||
withdrawer: authorized.withdrawer.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiStake {
|
||||
pub delegation: UiDelegation,
|
||||
pub credits_observed: u64,
|
||||
}
|
||||
|
||||
impl From<Stake> for UiStake {
|
||||
fn from(stake: Stake) -> Self {
|
||||
Self {
|
||||
delegation: stake.delegation.into(),
|
||||
credits_observed: stake.credits_observed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiDelegation {
|
||||
pub voter: String,
|
||||
pub stake: StringAmount,
|
||||
pub activation_epoch: StringAmount,
|
||||
pub deactivation_epoch: StringAmount,
|
||||
pub warmup_cooldown_rate: f64,
|
||||
}
|
||||
|
||||
impl From<Delegation> for UiDelegation {
|
||||
fn from(delegation: Delegation) -> Self {
|
||||
Self {
|
||||
voter: delegation.voter_pubkey.to_string(),
|
||||
stake: delegation.stake.to_string(),
|
||||
activation_epoch: delegation.activation_epoch.to_string(),
|
||||
deactivation_epoch: delegation.deactivation_epoch.to_string(),
|
||||
warmup_cooldown_rate: delegation.warmup_cooldown_rate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use bincode::serialize;
|
||||
|
||||
#[test]
|
||||
fn test_parse_stake() {
|
||||
let stake_state = StakeState::Uninitialized;
|
||||
let stake_data = serialize(&stake_state).unwrap();
|
||||
assert_eq!(
|
||||
parse_stake(&stake_data).unwrap(),
|
||||
StakeAccountType::Uninitialized
|
||||
);
|
||||
|
||||
let pubkey = solana_sdk::pubkey::new_rand();
|
||||
let custodian = solana_sdk::pubkey::new_rand();
|
||||
let authorized = Authorized::auto(&pubkey);
|
||||
let lockup = Lockup {
|
||||
unix_timestamp: 0,
|
||||
epoch: 1,
|
||||
custodian,
|
||||
};
|
||||
let meta = Meta {
|
||||
rent_exempt_reserve: 42,
|
||||
authorized,
|
||||
lockup,
|
||||
};
|
||||
|
||||
let stake_state = StakeState::Initialized(meta);
|
||||
let stake_data = serialize(&stake_state).unwrap();
|
||||
assert_eq!(
|
||||
parse_stake(&stake_data).unwrap(),
|
||||
StakeAccountType::Initialized(UiStakeAccount {
|
||||
meta: UiMeta {
|
||||
rent_exempt_reserve: 42.to_string(),
|
||||
authorized: UiAuthorized {
|
||||
staker: pubkey.to_string(),
|
||||
withdrawer: pubkey.to_string(),
|
||||
},
|
||||
lockup: UiLockup {
|
||||
unix_timestamp: 0,
|
||||
epoch: 1,
|
||||
custodian: custodian.to_string(),
|
||||
}
|
||||
},
|
||||
stake: None,
|
||||
})
|
||||
);
|
||||
|
||||
let voter_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let stake = Stake {
|
||||
delegation: Delegation {
|
||||
voter_pubkey,
|
||||
stake: 20,
|
||||
activation_epoch: 2,
|
||||
deactivation_epoch: std::u64::MAX,
|
||||
warmup_cooldown_rate: 0.25,
|
||||
},
|
||||
credits_observed: 10,
|
||||
};
|
||||
|
||||
let stake_state = StakeState::Stake(meta, stake);
|
||||
let stake_data = serialize(&stake_state).unwrap();
|
||||
assert_eq!(
|
||||
parse_stake(&stake_data).unwrap(),
|
||||
StakeAccountType::Delegated(UiStakeAccount {
|
||||
meta: UiMeta {
|
||||
rent_exempt_reserve: 42.to_string(),
|
||||
authorized: UiAuthorized {
|
||||
staker: pubkey.to_string(),
|
||||
withdrawer: pubkey.to_string(),
|
||||
},
|
||||
lockup: UiLockup {
|
||||
unix_timestamp: 0,
|
||||
epoch: 1,
|
||||
custodian: custodian.to_string(),
|
||||
}
|
||||
},
|
||||
stake: Some(UiStake {
|
||||
delegation: UiDelegation {
|
||||
voter: voter_pubkey.to_string(),
|
||||
stake: 20.to_string(),
|
||||
activation_epoch: 2.to_string(),
|
||||
deactivation_epoch: std::u64::MAX.to_string(),
|
||||
warmup_cooldown_rate: 0.25,
|
||||
},
|
||||
credits_observed: 10,
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
let stake_state = StakeState::RewardsPool;
|
||||
let stake_data = serialize(&stake_state).unwrap();
|
||||
assert_eq!(
|
||||
parse_stake(&stake_data).unwrap(),
|
||||
StakeAccountType::RewardsPool
|
||||
);
|
||||
|
||||
let bad_data = vec![1, 2, 3, 4];
|
||||
assert!(parse_stake(&bad_data).is_err());
|
||||
}
|
||||
}
|
329
account-decoder/src/parse_sysvar.rs
Normal file
329
account-decoder/src/parse_sysvar.rs
Normal file
@@ -0,0 +1,329 @@
|
||||
use crate::{
|
||||
parse_account_data::{ParsableAccount, ParseAccountError},
|
||||
StringAmount, UiFeeCalculator,
|
||||
};
|
||||
use bincode::deserialize;
|
||||
use bv::BitVec;
|
||||
use solana_sdk::{
|
||||
clock::{Clock, Epoch, Slot, UnixTimestamp},
|
||||
epoch_schedule::EpochSchedule,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
slot_hashes::SlotHashes,
|
||||
slot_history::{self, SlotHistory},
|
||||
stake_history::{StakeHistory, StakeHistoryEntry},
|
||||
sysvar::{self, fees::Fees, recent_blockhashes::RecentBlockhashes, rewards::Rewards},
|
||||
};
|
||||
|
||||
pub fn parse_sysvar(data: &[u8], pubkey: &Pubkey) -> Result<SysvarAccountType, ParseAccountError> {
|
||||
let parsed_account = {
|
||||
if pubkey == &sysvar::clock::id() {
|
||||
deserialize::<Clock>(data)
|
||||
.ok()
|
||||
.map(|clock| SysvarAccountType::Clock(clock.into()))
|
||||
} else if pubkey == &sysvar::epoch_schedule::id() {
|
||||
deserialize(data).ok().map(SysvarAccountType::EpochSchedule)
|
||||
} else if pubkey == &sysvar::fees::id() {
|
||||
deserialize::<Fees>(data)
|
||||
.ok()
|
||||
.map(|fees| SysvarAccountType::Fees(fees.into()))
|
||||
} else if pubkey == &sysvar::recent_blockhashes::id() {
|
||||
deserialize::<RecentBlockhashes>(data)
|
||||
.ok()
|
||||
.map(|recent_blockhashes| {
|
||||
let recent_blockhashes = recent_blockhashes
|
||||
.iter()
|
||||
.map(|entry| UiRecentBlockhashesEntry {
|
||||
blockhash: entry.blockhash.to_string(),
|
||||
fee_calculator: entry.fee_calculator.clone().into(),
|
||||
})
|
||||
.collect();
|
||||
SysvarAccountType::RecentBlockhashes(recent_blockhashes)
|
||||
})
|
||||
} else if pubkey == &sysvar::rent::id() {
|
||||
deserialize::<Rent>(data)
|
||||
.ok()
|
||||
.map(|rent| SysvarAccountType::Rent(rent.into()))
|
||||
} else if pubkey == &sysvar::rewards::id() {
|
||||
deserialize::<Rewards>(data)
|
||||
.ok()
|
||||
.map(|rewards| SysvarAccountType::Rewards(rewards.into()))
|
||||
} else if pubkey == &sysvar::slot_hashes::id() {
|
||||
deserialize::<SlotHashes>(data).ok().map(|slot_hashes| {
|
||||
let slot_hashes = slot_hashes
|
||||
.iter()
|
||||
.map(|slot_hash| UiSlotHashEntry {
|
||||
slot: slot_hash.0,
|
||||
hash: slot_hash.1.to_string(),
|
||||
})
|
||||
.collect();
|
||||
SysvarAccountType::SlotHashes(slot_hashes)
|
||||
})
|
||||
} else if pubkey == &sysvar::slot_history::id() {
|
||||
deserialize::<SlotHistory>(data).ok().map(|slot_history| {
|
||||
SysvarAccountType::SlotHistory(UiSlotHistory {
|
||||
next_slot: slot_history.next_slot,
|
||||
bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
|
||||
})
|
||||
})
|
||||
} else if pubkey == &sysvar::stake_history::id() {
|
||||
deserialize::<StakeHistory>(data).ok().map(|stake_history| {
|
||||
let stake_history = stake_history
|
||||
.iter()
|
||||
.map(|entry| UiStakeHistoryEntry {
|
||||
epoch: entry.0,
|
||||
stake_history: entry.1.clone(),
|
||||
})
|
||||
.collect();
|
||||
SysvarAccountType::StakeHistory(stake_history)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
parsed_account.ok_or(ParseAccountError::AccountNotParsable(
|
||||
ParsableAccount::Sysvar,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
|
||||
pub enum SysvarAccountType {
|
||||
Clock(UiClock),
|
||||
EpochSchedule(EpochSchedule),
|
||||
Fees(UiFees),
|
||||
RecentBlockhashes(Vec<UiRecentBlockhashesEntry>),
|
||||
Rent(UiRent),
|
||||
Rewards(UiRewards),
|
||||
SlotHashes(Vec<UiSlotHashEntry>),
|
||||
SlotHistory(UiSlotHistory),
|
||||
StakeHistory(Vec<UiStakeHistoryEntry>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiClock {
|
||||
pub slot: Slot,
|
||||
pub epoch: Epoch,
|
||||
pub epoch_start_timestamp: UnixTimestamp,
|
||||
pub leader_schedule_epoch: Epoch,
|
||||
pub unix_timestamp: UnixTimestamp,
|
||||
}
|
||||
|
||||
impl From<Clock> for UiClock {
|
||||
fn from(clock: Clock) -> Self {
|
||||
Self {
|
||||
slot: clock.slot,
|
||||
epoch: clock.epoch,
|
||||
epoch_start_timestamp: clock.epoch_start_timestamp,
|
||||
leader_schedule_epoch: clock.leader_schedule_epoch,
|
||||
unix_timestamp: clock.unix_timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiFees {
|
||||
pub fee_calculator: UiFeeCalculator,
|
||||
}
|
||||
impl From<Fees> for UiFees {
|
||||
fn from(fees: Fees) -> Self {
|
||||
Self {
|
||||
fee_calculator: fees.fee_calculator.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiRent {
|
||||
pub lamports_per_byte_year: StringAmount,
|
||||
pub exemption_threshold: f64,
|
||||
pub burn_percent: u8,
|
||||
}
|
||||
|
||||
impl From<Rent> for UiRent {
|
||||
fn from(rent: Rent) -> Self {
|
||||
Self {
|
||||
lamports_per_byte_year: rent.lamports_per_byte_year.to_string(),
|
||||
exemption_threshold: rent.exemption_threshold,
|
||||
burn_percent: rent.burn_percent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiRewards {
|
||||
pub validator_point_value: f64,
|
||||
}
|
||||
|
||||
impl From<Rewards> for UiRewards {
|
||||
fn from(rewards: Rewards) -> Self {
|
||||
Self {
|
||||
validator_point_value: rewards.validator_point_value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiRecentBlockhashesEntry {
|
||||
pub blockhash: String,
|
||||
pub fee_calculator: UiFeeCalculator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiSlotHashEntry {
|
||||
pub slot: Slot,
|
||||
pub hash: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiSlotHistory {
|
||||
pub next_slot: Slot,
|
||||
pub bits: String,
|
||||
}
|
||||
|
||||
struct SlotHistoryBits(BitVec<u64>);
|
||||
|
||||
impl std::fmt::Debug for SlotHistoryBits {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for i in 0..slot_history::MAX_ENTRIES {
|
||||
if self.0.get(i) {
|
||||
write!(f, "1")?;
|
||||
} else {
|
||||
write!(f, "0")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiStakeHistoryEntry {
|
||||
pub epoch: Epoch,
|
||||
pub stake_history: StakeHistoryEntry,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use solana_sdk::{
|
||||
account::create_account, fee_calculator::FeeCalculator, hash::Hash,
|
||||
sysvar::recent_blockhashes::IterItem,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_parse_sysvars() {
|
||||
let clock_sysvar = create_account(&Clock::default(), 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&clock_sysvar.data, &sysvar::clock::id()).unwrap(),
|
||||
SysvarAccountType::Clock(UiClock::default()),
|
||||
);
|
||||
|
||||
let epoch_schedule = EpochSchedule {
|
||||
slots_per_epoch: 12,
|
||||
leader_schedule_slot_offset: 0,
|
||||
warmup: false,
|
||||
first_normal_epoch: 1,
|
||||
first_normal_slot: 12,
|
||||
};
|
||||
let epoch_schedule_sysvar = create_account(&epoch_schedule, 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&epoch_schedule_sysvar.data, &sysvar::epoch_schedule::id()).unwrap(),
|
||||
SysvarAccountType::EpochSchedule(epoch_schedule),
|
||||
);
|
||||
|
||||
let fees_sysvar = create_account(&Fees::default(), 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&fees_sysvar.data, &sysvar::fees::id()).unwrap(),
|
||||
SysvarAccountType::Fees(UiFees::default()),
|
||||
);
|
||||
|
||||
let hash = Hash::new(&[1; 32]);
|
||||
let fee_calculator = FeeCalculator {
|
||||
lamports_per_signature: 10,
|
||||
};
|
||||
let recent_blockhashes: RecentBlockhashes = vec![IterItem(0, &hash, &fee_calculator)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let recent_blockhashes_sysvar = create_account(&recent_blockhashes, 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(
|
||||
&recent_blockhashes_sysvar.data,
|
||||
&sysvar::recent_blockhashes::id()
|
||||
)
|
||||
.unwrap(),
|
||||
SysvarAccountType::RecentBlockhashes(vec![UiRecentBlockhashesEntry {
|
||||
blockhash: hash.to_string(),
|
||||
fee_calculator: fee_calculator.into(),
|
||||
}]),
|
||||
);
|
||||
|
||||
let rent = Rent {
|
||||
lamports_per_byte_year: 10,
|
||||
exemption_threshold: 2.0,
|
||||
burn_percent: 5,
|
||||
};
|
||||
let rent_sysvar = create_account(&rent, 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&rent_sysvar.data, &sysvar::rent::id()).unwrap(),
|
||||
SysvarAccountType::Rent(rent.into()),
|
||||
);
|
||||
|
||||
let rewards_sysvar = create_account(&Rewards::default(), 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&rewards_sysvar.data, &sysvar::rewards::id()).unwrap(),
|
||||
SysvarAccountType::Rewards(UiRewards::default()),
|
||||
);
|
||||
|
||||
let mut slot_hashes = SlotHashes::default();
|
||||
slot_hashes.add(1, hash);
|
||||
let slot_hashes_sysvar = create_account(&slot_hashes, 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&slot_hashes_sysvar.data, &sysvar::slot_hashes::id()).unwrap(),
|
||||
SysvarAccountType::SlotHashes(vec![UiSlotHashEntry {
|
||||
slot: 1,
|
||||
hash: hash.to_string(),
|
||||
}]),
|
||||
);
|
||||
|
||||
let mut slot_history = SlotHistory::default();
|
||||
slot_history.add(42);
|
||||
let slot_history_sysvar = create_account(&slot_history, 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&slot_history_sysvar.data, &sysvar::slot_history::id()).unwrap(),
|
||||
SysvarAccountType::SlotHistory(UiSlotHistory {
|
||||
next_slot: slot_history.next_slot,
|
||||
bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
|
||||
}),
|
||||
);
|
||||
|
||||
let mut stake_history = StakeHistory::default();
|
||||
let stake_history_entry = StakeHistoryEntry {
|
||||
effective: 10,
|
||||
activating: 2,
|
||||
deactivating: 3,
|
||||
};
|
||||
stake_history.add(1, stake_history_entry.clone());
|
||||
let stake_history_sysvar = create_account(&stake_history, 1);
|
||||
assert_eq!(
|
||||
parse_sysvar(&stake_history_sysvar.data, &sysvar::stake_history::id()).unwrap(),
|
||||
SysvarAccountType::StakeHistory(vec![UiStakeHistoryEntry {
|
||||
epoch: 1,
|
||||
stake_history: stake_history_entry,
|
||||
}]),
|
||||
);
|
||||
|
||||
let bad_pubkey = solana_sdk::pubkey::new_rand();
|
||||
assert!(parse_sysvar(&stake_history_sysvar.data, &bad_pubkey).is_err());
|
||||
|
||||
let bad_data = vec![0; 4];
|
||||
assert!(parse_sysvar(&bad_data, &sysvar::stake_history::id()).is_err());
|
||||
}
|
||||
}
|
@@ -1,54 +1,95 @@
|
||||
use crate::parse_account_data::{ParsableAccount, ParseAccountError};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use spl_token_v1_0::{
|
||||
option::COption,
|
||||
solana_sdk::pubkey::Pubkey as SplTokenPubkey,
|
||||
state::{unpack, Account, Mint, Multisig},
|
||||
use crate::{
|
||||
parse_account_data::{ParsableAccount, ParseAccountError},
|
||||
StringAmount,
|
||||
};
|
||||
use std::{mem::size_of, str::FromStr};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use spl_token_v2_0::{
|
||||
solana_program::{
|
||||
program_option::COption, program_pack::Pack, pubkey::Pubkey as SplTokenPubkey,
|
||||
},
|
||||
state::{Account, AccountState, Mint, Multisig},
|
||||
};
|
||||
use std::str::FromStr;
|
||||
|
||||
// A helper function to convert spl_token_v1_0::id() as spl_sdk::pubkey::Pubkey to
|
||||
// A helper function to convert spl_token_v2_0::id() as spl_sdk::pubkey::Pubkey to
|
||||
// solana_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_id_v1_0() -> Pubkey {
|
||||
Pubkey::from_str(&spl_token_v1_0::id().to_string()).unwrap()
|
||||
pub fn spl_token_id_v2_0() -> Pubkey {
|
||||
Pubkey::from_str(&spl_token_v2_0::id().to_string()).unwrap()
|
||||
}
|
||||
|
||||
// A helper function to convert spl_token_v1_0::native_mint::id() as spl_sdk::pubkey::Pubkey to
|
||||
// A helper function to convert spl_token_v2_0::native_mint::id() as spl_sdk::pubkey::Pubkey to
|
||||
// solana_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_v1_0_native_mint() -> Pubkey {
|
||||
Pubkey::from_str(&spl_token_v1_0::native_mint::id().to_string()).unwrap()
|
||||
pub fn spl_token_v2_0_native_mint() -> Pubkey {
|
||||
Pubkey::from_str(&spl_token_v2_0::native_mint::id().to_string()).unwrap()
|
||||
}
|
||||
|
||||
pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
|
||||
let mut data = data.to_vec();
|
||||
if data.len() == size_of::<Account>() {
|
||||
let account: Account = *unpack(&mut data)
|
||||
// A helper function to convert a solana_sdk::pubkey::Pubkey to spl_sdk::pubkey::Pubkey
|
||||
pub fn spl_token_v2_0_pubkey(pubkey: &Pubkey) -> SplTokenPubkey {
|
||||
SplTokenPubkey::from_str(&pubkey.to_string()).unwrap()
|
||||
}
|
||||
|
||||
// A helper function to convert a spl_sdk::pubkey::Pubkey to solana_sdk::pubkey::Pubkey
|
||||
pub fn pubkey_from_spl_token_v2_0(pubkey: &SplTokenPubkey) -> Pubkey {
|
||||
Pubkey::from_str(&pubkey.to_string()).unwrap()
|
||||
}
|
||||
|
||||
pub fn parse_token(
|
||||
data: &[u8],
|
||||
mint_decimals: Option<u8>,
|
||||
) -> Result<TokenAccountType, ParseAccountError> {
|
||||
if data.len() == Account::get_packed_len() {
|
||||
let account = Account::unpack(data)
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
||||
let decimals = mint_decimals.ok_or_else(|| {
|
||||
ParseAccountError::AdditionalDataMissing(
|
||||
"no mint_decimals provided to parse spl-token account".to_string(),
|
||||
)
|
||||
})?;
|
||||
Ok(TokenAccountType::Account(UiTokenAccount {
|
||||
mint: account.mint.to_string(),
|
||||
owner: account.owner.to_string(),
|
||||
amount: account.amount,
|
||||
token_amount: token_amount_to_ui_amount(account.amount, decimals),
|
||||
delegate: match account.delegate {
|
||||
COption::Some(pubkey) => Some(pubkey.to_string()),
|
||||
COption::None => None,
|
||||
},
|
||||
is_initialized: account.is_initialized,
|
||||
is_native: account.is_native,
|
||||
delegated_amount: account.delegated_amount,
|
||||
}))
|
||||
} else if data.len() == size_of::<Mint>() {
|
||||
let mint: Mint = *unpack(&mut data)
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
||||
Ok(TokenAccountType::Mint(UiMint {
|
||||
owner: match mint.owner {
|
||||
state: account.state.into(),
|
||||
is_native: account.is_native(),
|
||||
rent_exempt_reserve: match account.is_native {
|
||||
COption::Some(reserve) => Some(token_amount_to_ui_amount(reserve, decimals)),
|
||||
COption::None => None,
|
||||
},
|
||||
delegated_amount: if account.delegate.is_none() {
|
||||
None
|
||||
} else {
|
||||
Some(token_amount_to_ui_amount(
|
||||
account.delegated_amount,
|
||||
decimals,
|
||||
))
|
||||
},
|
||||
close_authority: match account.close_authority {
|
||||
COption::Some(pubkey) => Some(pubkey.to_string()),
|
||||
COption::None => None,
|
||||
},
|
||||
}))
|
||||
} else if data.len() == Mint::get_packed_len() {
|
||||
let mint = Mint::unpack(data)
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
||||
Ok(TokenAccountType::Mint(UiMint {
|
||||
mint_authority: match mint.mint_authority {
|
||||
COption::Some(pubkey) => Some(pubkey.to_string()),
|
||||
COption::None => None,
|
||||
},
|
||||
supply: mint.supply.to_string(),
|
||||
decimals: mint.decimals,
|
||||
is_initialized: mint.is_initialized,
|
||||
freeze_authority: match mint.freeze_authority {
|
||||
COption::Some(pubkey) => Some(pubkey.to_string()),
|
||||
COption::None => None,
|
||||
},
|
||||
}))
|
||||
} else if data.len() == size_of::<Multisig>() {
|
||||
let multisig: Multisig = *unpack(&mut data)
|
||||
} else if data.len() == Multisig::get_packed_len() {
|
||||
let multisig = Multisig::unpack(data)
|
||||
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
||||
Ok(TokenAccountType::Multisig(UiMultisig {
|
||||
num_required_signers: multisig.m,
|
||||
@@ -86,19 +127,88 @@ pub enum TokenAccountType {
|
||||
pub struct UiTokenAccount {
|
||||
pub mint: String,
|
||||
pub owner: String,
|
||||
pub amount: u64,
|
||||
pub token_amount: UiTokenAmount,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delegate: Option<String>,
|
||||
pub is_initialized: bool,
|
||||
pub state: UiAccountState,
|
||||
pub is_native: bool,
|
||||
pub delegated_amount: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub rent_exempt_reserve: Option<UiTokenAmount>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delegated_amount: Option<UiTokenAmount>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub close_authority: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum UiAccountState {
|
||||
Uninitialized,
|
||||
Initialized,
|
||||
Frozen,
|
||||
}
|
||||
|
||||
impl From<AccountState> for UiAccountState {
|
||||
fn from(state: AccountState) -> Self {
|
||||
match state {
|
||||
AccountState::Uninitialized => UiAccountState::Uninitialized,
|
||||
AccountState::Initialized => UiAccountState::Initialized,
|
||||
AccountState::Frozen => UiAccountState::Frozen,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiTokenAmount {
|
||||
pub ui_amount: f64,
|
||||
pub decimals: u8,
|
||||
pub amount: StringAmount,
|
||||
}
|
||||
|
||||
impl UiTokenAmount {
|
||||
pub fn real_number_string(&self) -> String {
|
||||
let decimals = self.decimals as usize;
|
||||
if decimals > 0 {
|
||||
let amount = u64::from_str(&self.amount).unwrap_or(0);
|
||||
|
||||
// Left-pad zeros to decimals + 1, so we at least have an integer zero
|
||||
let mut s = format!("{:01$}", amount, decimals + 1);
|
||||
|
||||
// Add the decimal point (Sorry, "," locales!)
|
||||
s.insert(s.len() - decimals, '.');
|
||||
s
|
||||
} else {
|
||||
self.amount.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn real_number_string_trimmed(&self) -> String {
|
||||
let s = self.real_number_string();
|
||||
let zeros_trimmed = s.trim_end_matches('0');
|
||||
let decimal_trimmed = zeros_trimmed.trim_end_matches('.');
|
||||
decimal_trimmed.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount {
|
||||
// Use `amount_to_ui_amount()` once spl_token is bumped to a version that supports it: https://github.com/solana-labs/solana-program-library/pull/211
|
||||
let amount_decimals = amount as f64 / 10_usize.pow(decimals as u32) as f64;
|
||||
UiTokenAmount {
|
||||
ui_amount: amount_decimals,
|
||||
decimals,
|
||||
amount: amount.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiMint {
|
||||
pub owner: Option<String>,
|
||||
pub mint_authority: Option<String>,
|
||||
pub supply: StringAmount,
|
||||
pub decimals: u8,
|
||||
pub is_initialized: bool,
|
||||
pub freeze_authority: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
@@ -110,63 +220,89 @@ pub struct UiMultisig {
|
||||
pub signers: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn get_token_account_mint(data: &[u8]) -> Option<Pubkey> {
|
||||
if data.len() == Account::get_packed_len() {
|
||||
Some(Pubkey::new(&data[0..32]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use spl_token_v1_0::state::unpack_unchecked;
|
||||
|
||||
#[test]
|
||||
fn test_parse_token() {
|
||||
let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
|
||||
let owner_pubkey = SplTokenPubkey::new(&[3; 32]);
|
||||
let mut account_data = [0; size_of::<Account>()];
|
||||
let mut account: &mut Account = unpack_unchecked(&mut account_data).unwrap();
|
||||
let mut account_data = vec![0; Account::get_packed_len()];
|
||||
let mut account = Account::unpack_unchecked(&account_data).unwrap();
|
||||
account.mint = mint_pubkey;
|
||||
account.owner = owner_pubkey;
|
||||
account.amount = 42;
|
||||
account.is_initialized = true;
|
||||
account.state = AccountState::Initialized;
|
||||
account.is_native = COption::None;
|
||||
account.close_authority = COption::Some(owner_pubkey);
|
||||
Account::pack(account, &mut account_data).unwrap();
|
||||
|
||||
assert!(parse_token(&account_data, None).is_err());
|
||||
assert_eq!(
|
||||
parse_token(&account_data).unwrap(),
|
||||
parse_token(&account_data, Some(2)).unwrap(),
|
||||
TokenAccountType::Account(UiTokenAccount {
|
||||
mint: mint_pubkey.to_string(),
|
||||
owner: owner_pubkey.to_string(),
|
||||
amount: 42,
|
||||
token_amount: UiTokenAmount {
|
||||
ui_amount: 0.42,
|
||||
decimals: 2,
|
||||
amount: "42".to_string()
|
||||
},
|
||||
delegate: None,
|
||||
is_initialized: true,
|
||||
state: UiAccountState::Initialized,
|
||||
is_native: false,
|
||||
delegated_amount: 0,
|
||||
rent_exempt_reserve: None,
|
||||
delegated_amount: None,
|
||||
close_authority: Some(owner_pubkey.to_string()),
|
||||
}),
|
||||
);
|
||||
|
||||
let mut mint_data = [0; size_of::<Mint>()];
|
||||
let mut mint: &mut Mint = unpack_unchecked(&mut mint_data).unwrap();
|
||||
mint.owner = COption::Some(owner_pubkey);
|
||||
let mut mint_data = vec![0; Mint::get_packed_len()];
|
||||
let mut mint = Mint::unpack_unchecked(&mint_data).unwrap();
|
||||
mint.mint_authority = COption::Some(owner_pubkey);
|
||||
mint.supply = 42;
|
||||
mint.decimals = 3;
|
||||
mint.is_initialized = true;
|
||||
mint.freeze_authority = COption::Some(owner_pubkey);
|
||||
Mint::pack(mint, &mut mint_data).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
parse_token(&mint_data).unwrap(),
|
||||
parse_token(&mint_data, None).unwrap(),
|
||||
TokenAccountType::Mint(UiMint {
|
||||
owner: Some(owner_pubkey.to_string()),
|
||||
mint_authority: Some(owner_pubkey.to_string()),
|
||||
supply: 42.to_string(),
|
||||
decimals: 3,
|
||||
is_initialized: true,
|
||||
freeze_authority: Some(owner_pubkey.to_string()),
|
||||
}),
|
||||
);
|
||||
|
||||
let signer1 = SplTokenPubkey::new(&[1; 32]);
|
||||
let signer2 = SplTokenPubkey::new(&[2; 32]);
|
||||
let signer3 = SplTokenPubkey::new(&[3; 32]);
|
||||
let mut multisig_data = [0; size_of::<Multisig>()];
|
||||
let mut multisig: &mut Multisig = unpack_unchecked(&mut multisig_data).unwrap();
|
||||
let mut multisig_data = vec![0; Multisig::get_packed_len()];
|
||||
let mut signers = [SplTokenPubkey::default(); 11];
|
||||
signers[0] = signer1;
|
||||
signers[1] = signer2;
|
||||
signers[2] = signer3;
|
||||
let mut multisig = Multisig::unpack_unchecked(&multisig_data).unwrap();
|
||||
multisig.m = 2;
|
||||
multisig.n = 3;
|
||||
multisig.is_initialized = true;
|
||||
multisig.signers = signers;
|
||||
Multisig::pack(multisig, &mut multisig_data).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
parse_token(&multisig_data).unwrap(),
|
||||
parse_token(&multisig_data, None).unwrap(),
|
||||
TokenAccountType::Multisig(UiMultisig {
|
||||
num_required_signers: 2,
|
||||
num_valid_signers: 3,
|
||||
@@ -180,6 +316,37 @@ mod test {
|
||||
);
|
||||
|
||||
let bad_data = vec![0; 4];
|
||||
assert!(parse_token(&bad_data).is_err());
|
||||
assert!(parse_token(&bad_data, None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_token_account_mint() {
|
||||
let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
|
||||
let mut account_data = vec![0; Account::get_packed_len()];
|
||||
let mut account = Account::unpack_unchecked(&account_data).unwrap();
|
||||
account.mint = mint_pubkey;
|
||||
Account::pack(account, &mut account_data).unwrap();
|
||||
|
||||
let expected_mint_pubkey = Pubkey::new(&[2; 32]);
|
||||
assert_eq!(
|
||||
get_token_account_mint(&account_data),
|
||||
Some(expected_mint_pubkey)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ui_token_amount_real_string() {
|
||||
let token_amount = token_amount_to_ui_amount(1, 0);
|
||||
assert_eq!(&token_amount.real_number_string(), "1");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "1");
|
||||
let token_amount = token_amount_to_ui_amount(1, 9);
|
||||
assert_eq!(&token_amount.real_number_string(), "0.000000001");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "0.000000001");
|
||||
let token_amount = token_amount_to_ui_amount(1_000_000_000, 9);
|
||||
assert_eq!(&token_amount.real_number_string(), "1.000000000");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "1");
|
||||
let token_amount = token_amount_to_ui_amount(1_234_567_890, 3);
|
||||
assert_eq!(&token_amount.real_number_string(), "1234567.890");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "1234567.89");
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use crate::parse_account_data::ParseAccountError;
|
||||
use crate::{parse_account_data::ParseAccountError, StringAmount};
|
||||
use solana_sdk::{
|
||||
clock::{Epoch, Slot},
|
||||
pubkey::Pubkey,
|
||||
@@ -12,8 +12,8 @@ pub fn parse_vote(data: &[u8]) -> Result<VoteAccountType, ParseAccountError> {
|
||||
.iter()
|
||||
.map(|(epoch, credits, previous_credits)| UiEpochCredits {
|
||||
epoch: *epoch,
|
||||
credits: *credits,
|
||||
previous_credits: *previous_credits,
|
||||
credits: credits.to_string(),
|
||||
previous_credits: previous_credits.to_string(),
|
||||
})
|
||||
.collect();
|
||||
let votes = vote_state
|
||||
@@ -115,8 +115,8 @@ struct UiPriorVoters {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct UiEpochCredits {
|
||||
epoch: Epoch,
|
||||
credits: u64,
|
||||
previous_credits: u64,
|
||||
credits: StringAmount,
|
||||
previous_credits: StringAmount,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -128,11 +128,13 @@ mod test {
|
||||
fn test_parse_vote() {
|
||||
let vote_state = VoteState::default();
|
||||
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
|
||||
let versioned = VoteStateVersions::Current(Box::new(vote_state));
|
||||
let versioned = VoteStateVersions::new_current(vote_state);
|
||||
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
|
||||
let mut expected_vote_state = UiVoteState::default();
|
||||
expected_vote_state.node_pubkey = Pubkey::default().to_string();
|
||||
expected_vote_state.authorized_withdrawer = Pubkey::default().to_string();
|
||||
let expected_vote_state = UiVoteState {
|
||||
node_pubkey: Pubkey::default().to_string(),
|
||||
authorized_withdrawer: Pubkey::default().to_string(),
|
||||
..UiVoteState::default()
|
||||
};
|
||||
assert_eq!(
|
||||
parse_vote(&vote_account_data).unwrap(),
|
||||
VoteAccountType::Vote(expected_vote_state)
|
||||
|
18
account-decoder/src/validator_info.rs
Normal file
18
account-decoder/src/validator_info.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use solana_config_program::ConfigState;
|
||||
|
||||
pub const MAX_SHORT_FIELD_LENGTH: usize = 70;
|
||||
pub const MAX_LONG_FIELD_LENGTH: usize = 300;
|
||||
pub const MAX_VALIDATOR_INFO: u64 = 576;
|
||||
|
||||
solana_sdk::declare_id!("Va1idator1nfo111111111111111111111111111111");
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize, Default)]
|
||||
pub struct ValidatorInfo {
|
||||
pub info: String,
|
||||
}
|
||||
|
||||
impl ConfigState for ValidatorInfo {
|
||||
fn max_space() -> u64 {
|
||||
MAX_VALIDATOR_INFO
|
||||
}
|
||||
}
|
@@ -2,18 +2,20 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-accounts-bench"
|
||||
version = "1.3.0"
|
||||
version = "1.5.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.6"
|
||||
rayon = "1.3.1"
|
||||
solana-logger = { path = "../logger", version = "1.3.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.3.0" }
|
||||
solana-measure = { path = "../measure", version = "1.3.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.3.0" }
|
||||
log = "0.4.11"
|
||||
rayon = "1.4.0"
|
||||
solana-logger = { path = "../logger", version = "1.5.2" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.2" }
|
||||
solana-measure = { path = "../measure", version = "1.5.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.2" }
|
||||
solana-version = { path = "../version", version = "1.5.2" }
|
||||
rand = "0.7.0"
|
||||
clap = "2.33.1"
|
||||
crossbeam-channel = "0.4"
|
||||
|
@@ -1,20 +1,21 @@
|
||||
use clap::{value_t, App, Arg};
|
||||
use clap::{crate_description, crate_name, value_t, App, Arg};
|
||||
use rayon::prelude::*;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_runtime::{
|
||||
accounts::{create_test_accounts, update_accounts, Accounts},
|
||||
accounts_index::Ancestors,
|
||||
};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::{genesis_config::ClusterType, pubkey::Pubkey};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
|
||||
let matches = App::new("crate")
|
||||
.about("about")
|
||||
.version("version")
|
||||
let matches = App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(solana_version::version!())
|
||||
.arg(
|
||||
Arg::with_name("num_slots")
|
||||
.long("num_slots")
|
||||
@@ -50,11 +51,12 @@ fn main() {
|
||||
let clean = matches.is_present("clean");
|
||||
println!("clean: {:?}", clean);
|
||||
|
||||
let path = PathBuf::from("farf/accounts-bench");
|
||||
let path = PathBuf::from(env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_owned()))
|
||||
.join("accounts-bench");
|
||||
if fs::remove_dir_all(path.clone()).is_err() {
|
||||
println!("Warning: Couldn't remove {:?}", path);
|
||||
}
|
||||
let accounts = Accounts::new(vec![path]);
|
||||
let accounts = Accounts::new(vec![path], &ClusterType::Testnet);
|
||||
println!("Creating {} accounts", num_accounts);
|
||||
let mut create_time = Measure::start("create accounts");
|
||||
let pubkeys: Vec<_> = (0..num_slots)
|
||||
@@ -86,7 +88,7 @@ fn main() {
|
||||
for x in 0..iterations {
|
||||
if clean {
|
||||
let mut time = Measure::start("clean");
|
||||
accounts.accounts_db.clean_accounts();
|
||||
accounts.accounts_db.clean_accounts(None);
|
||||
time.stop();
|
||||
println!("{}", time);
|
||||
for slot in 0..num_slots {
|
||||
@@ -96,7 +98,10 @@ fn main() {
|
||||
} else {
|
||||
let mut pubkeys: Vec<Pubkey> = vec![];
|
||||
let mut time = Measure::start("hash");
|
||||
let hash = accounts.accounts_db.update_accounts_hash(0, &ancestors);
|
||||
let hash = accounts
|
||||
.accounts_db
|
||||
.update_accounts_hash(0, &ancestors, true)
|
||||
.0;
|
||||
time.stop();
|
||||
println!("hash: {} {}", hash, time);
|
||||
create_test_accounts(&accounts, &mut pubkeys, 1, 0);
|
||||
|
@@ -2,27 +2,28 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "1.3.0"
|
||||
version = "1.5.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
crossbeam-channel = "0.4"
|
||||
log = "0.4.6"
|
||||
log = "0.4.11"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.3.1"
|
||||
solana-core = { path = "../core", version = "1.3.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.3.0" }
|
||||
solana-streamer = { path = "../streamer", version = "1.3.0" }
|
||||
solana-perf = { path = "../perf", version = "1.3.0" }
|
||||
solana-ledger = { path = "../ledger", version = "1.3.0" }
|
||||
solana-logger = { path = "../logger", version = "1.3.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.3.0" }
|
||||
solana-measure = { path = "../measure", version = "1.3.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.3.0" }
|
||||
solana-version = { path = "../version", version = "1.3.0" }
|
||||
rayon = "1.4.0"
|
||||
solana-core = { path = "../core", version = "1.5.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.2" }
|
||||
solana-streamer = { path = "../streamer", version = "1.5.2" }
|
||||
solana-perf = { path = "../perf", version = "1.5.2" }
|
||||
solana-ledger = { path = "../ledger", version = "1.5.2" }
|
||||
solana-logger = { path = "../logger", version = "1.5.2" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.2" }
|
||||
solana-measure = { path = "../measure", version = "1.5.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.2" }
|
||||
solana-version = { path = "../version", version = "1.5.2" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -17,10 +17,11 @@ use solana_ledger::{
|
||||
};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_runtime::{bank::Bank, bank_forks::BankForks};
|
||||
use solana_runtime::{
|
||||
accounts_background_service::ABSRequestSender, bank::Bank, bank_forks::BankForks,
|
||||
};
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::Keypair,
|
||||
signature::Signature,
|
||||
system_transaction,
|
||||
@@ -69,7 +70,7 @@ fn make_accounts_txs(
|
||||
hash: Hash,
|
||||
same_payer: bool,
|
||||
) -> Vec<Transaction> {
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
let to_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let payer_key = Keypair::new();
|
||||
let dummy = system_transaction::transfer(&payer_key, &to_pubkey, 1, hash);
|
||||
(0..total_num_transactions)
|
||||
@@ -78,9 +79,9 @@ fn make_accounts_txs(
|
||||
let mut new = dummy.clone();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
if !same_payer {
|
||||
new.message.account_keys[0] = Pubkey::new_rand();
|
||||
new.message.account_keys[0] = solana_sdk::pubkey::new_rand();
|
||||
}
|
||||
new.message.account_keys[1] = Pubkey::new_rand();
|
||||
new.message.account_keys[1] = solana_sdk::pubkey::new_rand();
|
||||
new.signatures = vec![Signature::new(&sig[0..64])];
|
||||
new
|
||||
})
|
||||
@@ -167,6 +168,7 @@ fn main() {
|
||||
|
||||
let (verified_sender, verified_receiver) = unbounded();
|
||||
let (vote_sender, vote_receiver) = unbounded();
|
||||
let (replay_vote_sender, _replay_vote_receiver) = unbounded();
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
let mut bank_forks = BankForks::new(bank0);
|
||||
let mut bank = bank_forks.working_bank();
|
||||
@@ -224,6 +226,7 @@ fn main() {
|
||||
verified_receiver,
|
||||
vote_receiver,
|
||||
None,
|
||||
replay_vote_sender,
|
||||
);
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
|
||||
@@ -239,7 +242,7 @@ fn main() {
|
||||
let base_tx_count = bank.transaction_count();
|
||||
let mut txs_processed = 0;
|
||||
let mut root = 1;
|
||||
let collector = Pubkey::new_rand();
|
||||
let collector = solana_sdk::pubkey::new_rand();
|
||||
let config = Config {
|
||||
packets_per_batch: packets_per_chunk,
|
||||
chunk_len,
|
||||
@@ -322,7 +325,7 @@ fn main() {
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
assert!(poh_recorder.lock().unwrap().bank().is_some());
|
||||
if bank.slot() > 32 {
|
||||
bank_forks.set_root(root, &None, None);
|
||||
bank_forks.set_root(root, &ABSRequestSender::default(), None);
|
||||
root += 1;
|
||||
}
|
||||
debug!(
|
||||
|
30
banks-client/Cargo.toml
Normal file
30
banks-client/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "solana-banks-client"
|
||||
version = "1.5.2"
|
||||
description = "Solana banks client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.1"
|
||||
futures = "0.3"
|
||||
mio = "0.7.6"
|
||||
solana-banks-interface = { path = "../banks-interface", version = "1.5.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.2" }
|
||||
tarpc = { version = "0.23.0", features = ["full"] }
|
||||
tokio = { version = "0.3.5", features = ["full"] }
|
||||
tokio-serde = { version = "0.6", features = ["bincode"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-runtime = { path = "../runtime", version = "1.5.2" }
|
||||
solana-banks-server = { path = "../banks-server", version = "1.5.2" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
name = "solana_banks_client"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
371
banks-client/src/lib.rs
Normal file
371
banks-client/src/lib.rs
Normal file
@@ -0,0 +1,371 @@
|
||||
//! A client for the ledger state, from the perspective of an arbitrary validator.
|
||||
//!
|
||||
//! Use start_tcp_client() to create a client and then import BanksClientExt to
|
||||
//! access its methods. Additional "*_with_context" methods are also available,
|
||||
//! but they are undocumented, may change over time, and are generally more
|
||||
//! cumbersome to use.
|
||||
|
||||
use futures::{future::join_all, Future, FutureExt};
|
||||
pub use solana_banks_interface::{BanksClient as TarpcClient, TransactionStatus};
|
||||
use solana_banks_interface::{BanksRequest, BanksResponse};
|
||||
use solana_sdk::{
|
||||
account::{from_account, Account},
|
||||
clock::Slot,
|
||||
commitment_config::CommitmentLevel,
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
signature::Signature,
|
||||
sysvar,
|
||||
transaction::{self, Transaction},
|
||||
transport,
|
||||
};
|
||||
use std::io::{self, Error, ErrorKind};
|
||||
use tarpc::{
|
||||
client::{self, channel::RequestDispatch, NewClient},
|
||||
context::{self, Context},
|
||||
rpc::{ClientMessage, Response},
|
||||
serde_transport::tcp,
|
||||
Transport,
|
||||
};
|
||||
use tokio::{net::ToSocketAddrs, time::Duration};
|
||||
use tokio_serde::formats::Bincode;
|
||||
|
||||
// This exists only for backward compatibility
|
||||
pub trait BanksClientExt {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BanksClient {
|
||||
inner: TarpcClient,
|
||||
}
|
||||
|
||||
impl BanksClient {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new<C>(
|
||||
config: client::Config,
|
||||
transport: C,
|
||||
) -> NewClient<TarpcClient, RequestDispatch<BanksRequest, BanksResponse, C>>
|
||||
where
|
||||
C: Transport<ClientMessage<BanksRequest>, Response<BanksResponse>>,
|
||||
{
|
||||
TarpcClient::new(config, transport)
|
||||
}
|
||||
|
||||
pub fn send_transaction_with_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
transaction: Transaction,
|
||||
) -> impl Future<Output = io::Result<()>> + '_ {
|
||||
self.inner.send_transaction_with_context(ctx, transaction)
|
||||
}
|
||||
|
||||
pub fn get_fees_with_commitment_and_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<(FeeCalculator, Hash, Slot)>> + '_ {
|
||||
self.inner
|
||||
.get_fees_with_commitment_and_context(ctx, commitment)
|
||||
}
|
||||
|
||||
pub fn get_transaction_status_with_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
signature: Signature,
|
||||
) -> impl Future<Output = io::Result<Option<TransactionStatus>>> + '_ {
|
||||
self.inner
|
||||
.get_transaction_status_with_context(ctx, signature)
|
||||
}
|
||||
|
||||
pub fn get_slot_with_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<Slot>> + '_ {
|
||||
self.inner.get_slot_with_context(ctx, commitment)
|
||||
}
|
||||
|
||||
pub fn process_transaction_with_commitment_and_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
transaction: Transaction,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<Option<transaction::Result<()>>>> + '_ {
|
||||
self.inner
|
||||
.process_transaction_with_commitment_and_context(ctx, transaction, commitment)
|
||||
}
|
||||
|
||||
pub fn get_account_with_commitment_and_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
address: Pubkey,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<Option<Account>>> + '_ {
|
||||
self.inner
|
||||
.get_account_with_commitment_and_context(ctx, address, commitment)
|
||||
}
|
||||
|
||||
/// Send a transaction and return immediately. The server will resend the
|
||||
/// transaction until either it is accepted by the cluster or the transaction's
|
||||
/// blockhash expires.
|
||||
pub fn send_transaction(
|
||||
&mut self,
|
||||
transaction: Transaction,
|
||||
) -> impl Future<Output = io::Result<()>> + '_ {
|
||||
self.send_transaction_with_context(context::current(), transaction)
|
||||
}
|
||||
|
||||
/// Return the fee parameters associated with a recent, rooted blockhash. The cluster
|
||||
/// will use the transaction's blockhash to look up these same fee parameters and
|
||||
/// use them to calculate the transaction fee.
|
||||
pub fn get_fees(
|
||||
&mut self,
|
||||
) -> impl Future<Output = io::Result<(FeeCalculator, Hash, Slot)>> + '_ {
|
||||
self.get_fees_with_commitment_and_context(context::current(), CommitmentLevel::Root)
|
||||
}
|
||||
|
||||
/// Return the cluster rent
|
||||
pub fn get_rent(&mut self) -> impl Future<Output = io::Result<Rent>> + '_ {
|
||||
self.get_account(sysvar::rent::id()).map(|result| {
|
||||
let rent_sysvar = result?
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Rent sysvar not present"))?;
|
||||
from_account::<Rent>(&rent_sysvar).ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::Other, "Failed to deserialize Rent sysvar")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a recent, rooted blockhash from the server. The cluster will only accept
|
||||
/// transactions with a blockhash that has not yet expired. Use the `get_fees`
|
||||
/// method to get both a blockhash and the blockhash's last valid slot.
|
||||
pub fn get_recent_blockhash(&mut self) -> impl Future<Output = io::Result<Hash>> + '_ {
|
||||
self.get_fees().map(|result| Ok(result?.1))
|
||||
}
|
||||
|
||||
/// Send a transaction and return after the transaction has been rejected or
|
||||
/// reached the given level of commitment.
|
||||
pub fn process_transaction_with_commitment(
|
||||
&mut self,
|
||||
transaction: Transaction,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = transport::Result<()>> + '_ {
|
||||
let mut ctx = context::current();
|
||||
ctx.deadline += Duration::from_secs(50);
|
||||
self.process_transaction_with_commitment_and_context(ctx, transaction, commitment)
|
||||
.map(|result| match result? {
|
||||
None => {
|
||||
Err(Error::new(ErrorKind::TimedOut, "invalid blockhash or fee-payer").into())
|
||||
}
|
||||
Some(transaction_result) => Ok(transaction_result?),
|
||||
})
|
||||
}
|
||||
|
||||
/// Send a transaction and return until the transaction has been finalized or rejected.
|
||||
pub fn process_transaction(
|
||||
&mut self,
|
||||
transaction: Transaction,
|
||||
) -> impl Future<Output = transport::Result<()>> + '_ {
|
||||
self.process_transaction_with_commitment(transaction, CommitmentLevel::default())
|
||||
}
|
||||
|
||||
pub async fn process_transactions_with_commitment(
|
||||
&mut self,
|
||||
transactions: Vec<Transaction>,
|
||||
commitment: CommitmentLevel,
|
||||
) -> transport::Result<()> {
|
||||
let mut clients: Vec<_> = transactions.iter().map(|_| self.clone()).collect();
|
||||
let futures = clients
|
||||
.iter_mut()
|
||||
.zip(transactions)
|
||||
.map(|(client, transaction)| {
|
||||
client.process_transaction_with_commitment(transaction, commitment)
|
||||
});
|
||||
let statuses = join_all(futures).await;
|
||||
statuses.into_iter().collect() // Convert Vec<Result<_, _>> to Result<Vec<_>>
|
||||
}
|
||||
|
||||
/// Send transactions and return until the transaction has been finalized or rejected.
|
||||
pub fn process_transactions(
|
||||
&mut self,
|
||||
transactions: Vec<Transaction>,
|
||||
) -> impl Future<Output = transport::Result<()>> + '_ {
|
||||
self.process_transactions_with_commitment(transactions, CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the most recent rooted slot height. All transactions at or below this height
|
||||
/// are said to be finalized. The cluster will not fork to a higher slot height.
|
||||
pub fn get_root_slot(&mut self) -> impl Future<Output = io::Result<Slot>> + '_ {
|
||||
self.get_slot_with_context(context::current(), CommitmentLevel::Root)
|
||||
}
|
||||
|
||||
/// Return the account at the given address at the slot corresponding to the given
|
||||
/// commitment level. If the account is not found, None is returned.
|
||||
pub fn get_account_with_commitment(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<Option<Account>>> + '_ {
|
||||
self.get_account_with_commitment_and_context(context::current(), address, commitment)
|
||||
}
|
||||
|
||||
/// Return the account at the given address at the time of the most recent root slot.
|
||||
/// If the account is not found, None is returned.
|
||||
pub fn get_account(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
) -> impl Future<Output = io::Result<Option<Account>>> + '_ {
|
||||
self.get_account_with_commitment(address, CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the balance in lamports of an account at the given address at the slot
|
||||
/// corresponding to the given commitment level.
|
||||
pub fn get_balance_with_commitment(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<u64>> + '_ {
|
||||
self.get_account_with_commitment_and_context(context::current(), address, commitment)
|
||||
.map(|result| Ok(result?.map(|x| x.lamports).unwrap_or(0)))
|
||||
}
|
||||
|
||||
/// Return the balance in lamports of an account at the given address at the time
|
||||
/// of the most recent root slot.
|
||||
pub fn get_balance(&mut self, address: Pubkey) -> impl Future<Output = io::Result<u64>> + '_ {
|
||||
self.get_balance_with_commitment(address, CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the status of a transaction with a signature matching the transaction's first
|
||||
/// signature. Return None if the transaction is not found, which may be because the
|
||||
/// blockhash was expired or the fee-paying account had insufficient funds to pay the
|
||||
/// transaction fee. Note that servers rarely store the full transaction history. This
|
||||
/// method may return None if the transaction status has been discarded.
|
||||
pub fn get_transaction_status(
|
||||
&mut self,
|
||||
signature: Signature,
|
||||
) -> impl Future<Output = io::Result<Option<TransactionStatus>>> + '_ {
|
||||
self.get_transaction_status_with_context(context::current(), signature)
|
||||
}
|
||||
|
||||
/// Same as get_transaction_status, but for multiple transactions.
|
||||
pub async fn get_transaction_statuses(
|
||||
&mut self,
|
||||
signatures: Vec<Signature>,
|
||||
) -> io::Result<Vec<Option<TransactionStatus>>> {
|
||||
// tarpc futures oddly hold a mutable reference back to the client so clone the client upfront
|
||||
let mut clients_and_signatures: Vec<_> = signatures
|
||||
.into_iter()
|
||||
.map(|signature| (self.clone(), signature))
|
||||
.collect();
|
||||
|
||||
let futs = clients_and_signatures
|
||||
.iter_mut()
|
||||
.map(|(client, signature)| client.get_transaction_status(*signature));
|
||||
|
||||
let statuses = join_all(futs).await;
|
||||
|
||||
// Convert Vec<Result<_, _>> to Result<Vec<_>>
|
||||
statuses.into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_client<C>(transport: C) -> io::Result<BanksClient>
|
||||
where
|
||||
C: Transport<ClientMessage<BanksRequest>, Response<BanksResponse>> + Send + 'static,
|
||||
{
|
||||
Ok(BanksClient {
|
||||
inner: TarpcClient::new(client::Config::default(), transport).spawn()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn start_tcp_client<T: ToSocketAddrs>(addr: T) -> io::Result<BanksClient> {
|
||||
let transport = tcp::connect(addr, Bincode::default).await?;
|
||||
Ok(BanksClient {
|
||||
inner: TarpcClient::new(client::Config::default(), transport).spawn()?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_banks_server::banks_server::start_local_server;
|
||||
use solana_runtime::{bank::Bank, bank_forks::BankForks, genesis_utils::create_genesis_config};
|
||||
use solana_sdk::{message::Message, signature::Signer, system_instruction};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use tarpc::transport;
|
||||
use tokio::{runtime::Runtime, time::sleep};
|
||||
|
||||
#[test]
|
||||
fn test_banks_client_new() {
|
||||
let (client_transport, _server_transport) = transport::channel::unbounded();
|
||||
BanksClient::new(client::Config::default(), client_transport);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_banks_server_transfer_via_server() -> io::Result<()> {
|
||||
// This test shows the preferred way to interact with BanksServer.
|
||||
// It creates a runtime explicitly (no globals via tokio macros) and calls
|
||||
// `runtime.block_on()` just once, to run all the async code.
|
||||
|
||||
let genesis = create_genesis_config(10);
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::new(
|
||||
&genesis.genesis_config,
|
||||
))));
|
||||
|
||||
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let mint_pubkey = genesis.mint_keypair.pubkey();
|
||||
let instruction = system_instruction::transfer(&mint_pubkey, &bob_pubkey, 1);
|
||||
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
||||
|
||||
Runtime::new()?.block_on(async {
|
||||
let client_transport = start_local_server(&bank_forks).await;
|
||||
let mut banks_client = start_client(client_transport).await?;
|
||||
|
||||
let recent_blockhash = banks_client.get_recent_blockhash().await?;
|
||||
let transaction = Transaction::new(&[&genesis.mint_keypair], message, recent_blockhash);
|
||||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
assert_eq!(banks_client.get_balance(bob_pubkey).await?, 1);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_banks_server_transfer_via_client() -> io::Result<()> {
|
||||
// The caller may not want to hold the connection open until the transaction
|
||||
// is processed (or blockhash expires). In this test, we verify the
|
||||
// server-side functionality is available to the client.
|
||||
|
||||
let genesis = create_genesis_config(10);
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::new(
|
||||
&genesis.genesis_config,
|
||||
))));
|
||||
|
||||
let mint_pubkey = &genesis.mint_keypair.pubkey();
|
||||
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let instruction = system_instruction::transfer(&mint_pubkey, &bob_pubkey, 1);
|
||||
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
||||
|
||||
Runtime::new()?.block_on(async {
|
||||
let client_transport = start_local_server(&bank_forks).await;
|
||||
let mut banks_client = start_client(client_transport).await?;
|
||||
let (_, recent_blockhash, last_valid_slot) = banks_client.get_fees().await?;
|
||||
let transaction = Transaction::new(&[&genesis.mint_keypair], message, recent_blockhash);
|
||||
let signature = transaction.signatures[0];
|
||||
banks_client.send_transaction(transaction).await?;
|
||||
|
||||
let mut status = banks_client.get_transaction_status(signature).await?;
|
||||
|
||||
while status.is_none() {
|
||||
let root_slot = banks_client.get_root_slot().await?;
|
||||
if root_slot > last_valid_slot {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
status = banks_client.get_transaction_status(signature).await?;
|
||||
}
|
||||
assert!(status.unwrap().err.is_none());
|
||||
assert_eq!(banks_client.get_balance(bob_pubkey).await?, 1);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
25
banks-interface/Cargo.toml
Normal file
25
banks-interface/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "solana-banks-interface"
|
||||
version = "1.5.2"
|
||||
description = "Solana banks RPC interface"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
mio = "0.7.6"
|
||||
serde = { version = "1.0.112", features = ["derive"] }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.2" }
|
||||
tarpc = { version = "0.23.0", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.3.5", features = ["full"] }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
name = "solana_banks_interface"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
49
banks-interface/src/lib.rs
Normal file
49
banks-interface/src/lib.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
clock::Slot,
|
||||
commitment_config::CommitmentLevel,
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TransactionStatus {
|
||||
pub slot: Slot,
|
||||
pub confirmations: Option<usize>, // None = rooted
|
||||
pub err: Option<TransactionError>,
|
||||
}
|
||||
|
||||
#[tarpc::service]
|
||||
pub trait Banks {
|
||||
async fn send_transaction_with_context(transaction: Transaction);
|
||||
async fn get_fees_with_commitment_and_context(
|
||||
commitment: CommitmentLevel,
|
||||
) -> (FeeCalculator, Hash, Slot);
|
||||
async fn get_transaction_status_with_context(signature: Signature)
|
||||
-> Option<TransactionStatus>;
|
||||
async fn get_slot_with_context(commitment: CommitmentLevel) -> Slot;
|
||||
async fn process_transaction_with_commitment_and_context(
|
||||
transaction: Transaction,
|
||||
commitment: CommitmentLevel,
|
||||
) -> Option<transaction::Result<()>>;
|
||||
async fn get_account_with_commitment_and_context(
|
||||
address: Pubkey,
|
||||
commitment: CommitmentLevel,
|
||||
) -> Option<Account>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tarpc::{client, transport};
|
||||
|
||||
#[test]
|
||||
fn test_banks_client_new() {
|
||||
let (client_transport, _server_transport) = transport::channel::unbounded();
|
||||
BanksClient::new(client::Config::default(), client_transport);
|
||||
}
|
||||
}
|
29
banks-server/Cargo.toml
Normal file
29
banks-server/Cargo.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "solana-banks-server"
|
||||
version = "1.5.2"
|
||||
description = "Solana banks server"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.1"
|
||||
futures = "0.3"
|
||||
log = "0.4.11"
|
||||
mio = "0.7.6"
|
||||
solana-banks-interface = { path = "../banks-interface", version = "1.5.2" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.2" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.2" }
|
||||
tarpc = { version = "0.23.0", features = ["full"] }
|
||||
tokio = { version = "0.3", features = ["full"] }
|
||||
tokio-serde = { version = "0.6", features = ["bincode"] }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
name = "solana_banks_server"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
275
banks-server/src/banks_server.rs
Normal file
275
banks-server/src/banks_server.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
use crate::send_transaction_service::{SendTransactionService, TransactionInfo};
|
||||
use bincode::{deserialize, serialize};
|
||||
use futures::{
|
||||
future,
|
||||
prelude::stream::{self, StreamExt},
|
||||
};
|
||||
use solana_banks_interface::{Banks, BanksRequest, BanksResponse, TransactionStatus};
|
||||
use solana_runtime::{bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache};
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
clock::Slot,
|
||||
commitment_config::CommitmentLevel,
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
transaction::{self, Transaction},
|
||||
};
|
||||
use std::{
|
||||
io,
|
||||
net::{Ipv4Addr, SocketAddr},
|
||||
sync::{
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
Arc, RwLock,
|
||||
},
|
||||
thread::Builder,
|
||||
time::Duration,
|
||||
};
|
||||
use tarpc::{
|
||||
context::Context,
|
||||
rpc::{transport::channel::UnboundedChannel, ClientMessage, Response},
|
||||
serde_transport::tcp,
|
||||
server::{self, Channel, Handler},
|
||||
transport,
|
||||
};
|
||||
use tokio::time::sleep;
|
||||
use tokio_serde::formats::Bincode;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct BanksServer {
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
transaction_sender: Sender<TransactionInfo>,
|
||||
}
|
||||
|
||||
impl BanksServer {
|
||||
/// Return a BanksServer that forwards transactions to the
|
||||
/// given sender. If unit-testing, those transactions can go to
|
||||
/// a bank in the given BankForks. Otherwise, the receiver should
|
||||
/// forward them to a validator in the leader schedule.
|
||||
fn new(
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
transaction_sender: Sender<TransactionInfo>,
|
||||
) -> Self {
|
||||
Self {
|
||||
bank_forks,
|
||||
block_commitment_cache,
|
||||
transaction_sender,
|
||||
}
|
||||
}
|
||||
|
||||
fn run(bank: &Bank, transaction_receiver: Receiver<TransactionInfo>) {
|
||||
while let Ok(info) = transaction_receiver.recv() {
|
||||
let mut transaction_infos = vec![info];
|
||||
while let Ok(info) = transaction_receiver.try_recv() {
|
||||
transaction_infos.push(info);
|
||||
}
|
||||
let transactions: Vec<_> = transaction_infos
|
||||
.into_iter()
|
||||
.map(|info| deserialize(&info.wire_transaction).unwrap())
|
||||
.collect();
|
||||
let _ = bank.process_transactions(&transactions);
|
||||
}
|
||||
}
|
||||
|
||||
/// Useful for unit-testing
|
||||
fn new_loopback(bank_forks: Arc<RwLock<BankForks>>) -> Self {
|
||||
let (transaction_sender, transaction_receiver) = channel();
|
||||
let bank = bank_forks.read().unwrap().working_bank();
|
||||
let slot = bank.slot();
|
||||
let block_commitment_cache = Arc::new(RwLock::new(
|
||||
BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
|
||||
));
|
||||
Builder::new()
|
||||
.name("solana-bank-forks-client".to_string())
|
||||
.spawn(move || Self::run(&bank, transaction_receiver))
|
||||
.unwrap();
|
||||
Self::new(bank_forks, block_commitment_cache, transaction_sender)
|
||||
}
|
||||
|
||||
fn slot(&self, commitment: CommitmentLevel) -> Slot {
|
||||
self.block_commitment_cache
|
||||
.read()
|
||||
.unwrap()
|
||||
.slot_with_commitment(commitment)
|
||||
}
|
||||
|
||||
fn bank(&self, commitment: CommitmentLevel) -> Arc<Bank> {
|
||||
self.bank_forks.read().unwrap()[self.slot(commitment)].clone()
|
||||
}
|
||||
|
||||
async fn poll_signature_status(
|
||||
self,
|
||||
signature: &Signature,
|
||||
blockhash: &Hash,
|
||||
last_valid_slot: Slot,
|
||||
commitment: CommitmentLevel,
|
||||
) -> Option<transaction::Result<()>> {
|
||||
let mut status = self
|
||||
.bank(commitment)
|
||||
.get_signature_status_with_blockhash(signature, blockhash);
|
||||
while status.is_none() {
|
||||
sleep(Duration::from_millis(200)).await;
|
||||
let bank = self.bank(commitment);
|
||||
if bank.slot() > last_valid_slot {
|
||||
break;
|
||||
}
|
||||
status = bank.get_signature_status_with_blockhash(signature, blockhash);
|
||||
}
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_transaction(transaction: &Transaction) -> transaction::Result<()> {
|
||||
if let Err(err) = transaction.verify() {
|
||||
Err(err)
|
||||
} else if let Err(err) = transaction.verify_precompiles() {
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tarpc::server]
|
||||
impl Banks for BanksServer {
|
||||
async fn send_transaction_with_context(self, _: Context, transaction: Transaction) {
|
||||
let blockhash = &transaction.message.recent_blockhash;
|
||||
let last_valid_slot = self
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.root_bank()
|
||||
.get_blockhash_last_valid_slot(&blockhash)
|
||||
.unwrap();
|
||||
let signature = transaction.signatures.get(0).cloned().unwrap_or_default();
|
||||
let info =
|
||||
TransactionInfo::new(signature, serialize(&transaction).unwrap(), last_valid_slot);
|
||||
self.transaction_sender.send(info).unwrap();
|
||||
}
|
||||
|
||||
async fn get_fees_with_commitment_and_context(
|
||||
self,
|
||||
_: Context,
|
||||
commitment: CommitmentLevel,
|
||||
) -> (FeeCalculator, Hash, Slot) {
|
||||
let bank = self.bank(commitment);
|
||||
let (blockhash, fee_calculator) = bank.last_blockhash_with_fee_calculator();
|
||||
let last_valid_slot = bank.get_blockhash_last_valid_slot(&blockhash).unwrap();
|
||||
(fee_calculator, blockhash, last_valid_slot)
|
||||
}
|
||||
|
||||
async fn get_transaction_status_with_context(
|
||||
self,
|
||||
_: Context,
|
||||
signature: Signature,
|
||||
) -> Option<TransactionStatus> {
|
||||
let bank = self.bank(CommitmentLevel::Recent);
|
||||
let (slot, status) = bank.get_signature_status_slot(&signature)?;
|
||||
let r_block_commitment_cache = self.block_commitment_cache.read().unwrap();
|
||||
|
||||
let confirmations = if r_block_commitment_cache.root() >= slot {
|
||||
None
|
||||
} else {
|
||||
r_block_commitment_cache
|
||||
.get_confirmation_count(slot)
|
||||
.or(Some(0))
|
||||
};
|
||||
Some(TransactionStatus {
|
||||
slot,
|
||||
confirmations,
|
||||
err: status.err(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_slot_with_context(self, _: Context, commitment: CommitmentLevel) -> Slot {
|
||||
self.slot(commitment)
|
||||
}
|
||||
|
||||
async fn process_transaction_with_commitment_and_context(
|
||||
self,
|
||||
_: Context,
|
||||
transaction: Transaction,
|
||||
commitment: CommitmentLevel,
|
||||
) -> Option<transaction::Result<()>> {
|
||||
if let Err(err) = verify_transaction(&transaction) {
|
||||
return Some(Err(err));
|
||||
}
|
||||
|
||||
let blockhash = &transaction.message.recent_blockhash;
|
||||
let last_valid_slot = self
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.root_bank()
|
||||
.get_blockhash_last_valid_slot(blockhash)
|
||||
.unwrap();
|
||||
let signature = transaction.signatures.get(0).cloned().unwrap_or_default();
|
||||
let info =
|
||||
TransactionInfo::new(signature, serialize(&transaction).unwrap(), last_valid_slot);
|
||||
self.transaction_sender.send(info).unwrap();
|
||||
self.poll_signature_status(&signature, blockhash, last_valid_slot, commitment)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_account_with_commitment_and_context(
|
||||
self,
|
||||
_: Context,
|
||||
address: Pubkey,
|
||||
commitment: CommitmentLevel,
|
||||
) -> Option<Account> {
|
||||
let bank = self.bank(commitment);
|
||||
bank.get_account(&address)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_local_server(
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
) -> UnboundedChannel<Response<BanksResponse>, ClientMessage<BanksRequest>> {
|
||||
let banks_server = BanksServer::new_loopback(bank_forks.clone());
|
||||
let (client_transport, server_transport) = transport::channel::unbounded();
|
||||
let server = server::new(server::Config::default())
|
||||
.incoming(stream::once(future::ready(server_transport)))
|
||||
.respond_with(banks_server.serve());
|
||||
tokio::spawn(server);
|
||||
client_transport
|
||||
}
|
||||
|
||||
pub async fn start_tcp_server(
|
||||
listen_addr: SocketAddr,
|
||||
tpu_addr: SocketAddr,
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
) -> io::Result<()> {
|
||||
// Note: These settings are copied straight from the tarpc example.
|
||||
let server = tcp::listen(listen_addr, Bincode::default)
|
||||
.await?
|
||||
// Ignore accept errors.
|
||||
.filter_map(|r| future::ready(r.ok()))
|
||||
.map(server::BaseChannel::with_defaults)
|
||||
// Limit channels to 1 per IP.
|
||||
.max_channels_per_key(1, |t| {
|
||||
t.as_ref()
|
||||
.peer_addr()
|
||||
.map(|x| x.ip())
|
||||
.unwrap_or_else(|_| Ipv4Addr::new(0, 0, 0, 0).into())
|
||||
})
|
||||
// serve is generated by the service attribute. It takes as input any type implementing
|
||||
// the generated Banks trait.
|
||||
.map(move |chan| {
|
||||
let (sender, receiver) = channel();
|
||||
|
||||
SendTransactionService::new(tpu_addr, &bank_forks, receiver);
|
||||
|
||||
let server =
|
||||
BanksServer::new(bank_forks.clone(), block_commitment_cache.clone(), sender);
|
||||
chan.respond_with(server.serve()).execute()
|
||||
})
|
||||
// Max 10 channels.
|
||||
.buffer_unordered(10)
|
||||
.for_each(|_| async {});
|
||||
|
||||
server.await;
|
||||
Ok(())
|
||||
}
|
6
banks-server/src/lib.rs
Normal file
6
banks-server/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod banks_server;
|
||||
pub mod rpc_banks_service;
|
||||
pub mod send_transaction_service;
|
||||
|
||||
#[macro_use]
|
||||
extern crate solana_metrics;
|
116
banks-server/src/rpc_banks_service.rs
Normal file
116
banks-server/src/rpc_banks_service.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
//! The `rpc_banks_service` module implements the Solana Banks RPC API.
|
||||
|
||||
use crate::banks_server::start_tcp_server;
|
||||
use futures::{future::FutureExt, pin_mut, prelude::stream::StreamExt, select};
|
||||
use solana_runtime::{bank_forks::BankForks, commitment::BlockCommitmentCache};
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, RwLock,
|
||||
},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
};
|
||||
use tokio::{
|
||||
runtime::Runtime,
|
||||
time::{self, Duration},
|
||||
};
|
||||
|
||||
pub struct RpcBanksService {
|
||||
thread_hdl: JoinHandle<()>,
|
||||
}
|
||||
|
||||
/// Run the TCP service until `exit` is set to true
|
||||
async fn start_abortable_tcp_server(
|
||||
listen_addr: SocketAddr,
|
||||
tpu_addr: SocketAddr,
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
exit: Arc<AtomicBool>,
|
||||
) {
|
||||
let server = start_tcp_server(
|
||||
listen_addr,
|
||||
tpu_addr,
|
||||
bank_forks.clone(),
|
||||
block_commitment_cache.clone(),
|
||||
)
|
||||
.fuse();
|
||||
let interval = time::interval(Duration::from_millis(100)).fuse();
|
||||
pin_mut!(server, interval);
|
||||
loop {
|
||||
select! {
|
||||
_ = server => {},
|
||||
_ = interval.select_next_some() => {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RpcBanksService {
|
||||
fn run(
|
||||
listen_addr: SocketAddr,
|
||||
tpu_addr: SocketAddr,
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
exit: Arc<AtomicBool>,
|
||||
) {
|
||||
let server = start_abortable_tcp_server(
|
||||
listen_addr,
|
||||
tpu_addr,
|
||||
bank_forks,
|
||||
block_commitment_cache,
|
||||
exit,
|
||||
);
|
||||
Runtime::new().unwrap().block_on(server);
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
listen_addr: SocketAddr,
|
||||
tpu_addr: SocketAddr,
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
block_commitment_cache: &Arc<RwLock<BlockCommitmentCache>>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
) -> Self {
|
||||
let bank_forks = bank_forks.clone();
|
||||
let block_commitment_cache = block_commitment_cache.clone();
|
||||
let exit = exit.clone();
|
||||
let thread_hdl = Builder::new()
|
||||
.name("solana-rpc-banks".to_string())
|
||||
.spawn(move || {
|
||||
Self::run(
|
||||
listen_addr,
|
||||
tpu_addr,
|
||||
bank_forks,
|
||||
block_commitment_cache,
|
||||
exit,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Self { thread_hdl }
|
||||
}
|
||||
|
||||
pub fn join(self) -> thread::Result<()> {
|
||||
self.thread_hdl.join()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_runtime::bank::Bank;
|
||||
|
||||
#[test]
|
||||
fn test_rpc_banks_server_exit() {
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::default())));
|
||||
let block_commitment_cache = Arc::new(RwLock::new(BlockCommitmentCache::default()));
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let addr = "127.0.0.1:0".parse().unwrap();
|
||||
let service = RpcBanksService::new(addr, addr, &bank_forks, &block_commitment_cache, &exit);
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
service.join().unwrap();
|
||||
}
|
||||
}
|
@@ -1,13 +1,13 @@
|
||||
use crate::{bank::Bank, bank_forks::BankForks};
|
||||
// TODO: Merge this implementation with the one at `core/src/send_transaction_service.rs`
|
||||
use log::*;
|
||||
use solana_metrics::{datapoint_warn, inc_new_counter_info};
|
||||
use solana_runtime::{bank::Bank, bank_forks::BankForks};
|
||||
use solana_sdk::{clock::Slot, signature::Signature};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{SocketAddr, UdpSocket},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::Receiver,
|
||||
mpsc::{Receiver, RecvTimeoutError},
|
||||
Arc, RwLock,
|
||||
},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
@@ -22,9 +22,9 @@ pub struct SendTransactionService {
|
||||
}
|
||||
|
||||
pub struct TransactionInfo {
|
||||
signature: Signature,
|
||||
wire_transaction: Vec<u8>,
|
||||
last_valid_slot: Slot,
|
||||
pub signature: Signature,
|
||||
pub wire_transaction: Vec<u8>,
|
||||
pub last_valid_slot: Slot,
|
||||
}
|
||||
|
||||
impl TransactionInfo {
|
||||
@@ -50,10 +50,9 @@ impl SendTransactionService {
|
||||
pub fn new(
|
||||
tpu_address: SocketAddr,
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
receiver: Receiver<TransactionInfo>,
|
||||
) -> Self {
|
||||
let thread = Self::retry_thread(receiver, bank_forks.clone(), tpu_address, exit.clone());
|
||||
let thread = Self::retry_thread(receiver, bank_forks.clone(), tpu_address);
|
||||
Self { thread }
|
||||
}
|
||||
|
||||
@@ -61,7 +60,6 @@ impl SendTransactionService {
|
||||
receiver: Receiver<TransactionInfo>,
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
tpu_address: SocketAddr,
|
||||
exit: Arc<AtomicBool>,
|
||||
) -> JoinHandle<()> {
|
||||
let mut last_status_check = Instant::now();
|
||||
let mut transactions = HashMap::new();
|
||||
@@ -70,20 +68,20 @@ impl SendTransactionService {
|
||||
Builder::new()
|
||||
.name("send-tx-svc".to_string())
|
||||
.spawn(move || loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Ok(transaction_info) = receiver.recv_timeout(Duration::from_secs(1)) {
|
||||
Self::send_transaction(
|
||||
&send_socket,
|
||||
&tpu_address,
|
||||
&transaction_info.wire_transaction,
|
||||
);
|
||||
if transactions.len() < MAX_TRANSACTION_QUEUE_SIZE {
|
||||
transactions.insert(transaction_info.signature, transaction_info);
|
||||
} else {
|
||||
datapoint_warn!("send_transaction_service-queue-overflow");
|
||||
match receiver.recv_timeout(Duration::from_secs(1)) {
|
||||
Err(RecvTimeoutError::Disconnected) => break,
|
||||
Err(RecvTimeoutError::Timeout) => {}
|
||||
Ok(transaction_info) => {
|
||||
Self::send_transaction(
|
||||
&send_socket,
|
||||
&tpu_address,
|
||||
&transaction_info.wire_transaction,
|
||||
);
|
||||
if transactions.len() < MAX_TRANSACTION_QUEUE_SIZE {
|
||||
transactions.insert(transaction_info.signature, transaction_info);
|
||||
} else {
|
||||
datapoint_warn!("send_transaction_service-queue-overflow");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,20 +191,17 @@ mod test {
|
||||
let tpu_address = "127.0.0.1:0".parse().unwrap();
|
||||
let bank = Bank::default();
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let (_sender, receiver) = channel();
|
||||
let (sender, receiver) = channel();
|
||||
|
||||
let send_tranaction_service =
|
||||
SendTransactionService::new(tpu_address, &bank_forks, &exit, receiver);
|
||||
SendTransactionService::new(tpu_address, &bank_forks, receiver);
|
||||
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
drop(sender);
|
||||
send_tranaction_service.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_transactions() {
|
||||
solana_logger::setup();
|
||||
|
||||
let (genesis_config, mint_keypair) = create_genesis_config(4);
|
||||
let bank = Bank::new(&genesis_config);
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "1.3.0"
|
||||
version = "1.5.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -11,28 +11,28 @@ publish = false
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
itertools = "0.9.0"
|
||||
log = "0.4.8"
|
||||
log = "0.4.11"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.3.1"
|
||||
rayon = "1.4.0"
|
||||
serde_json = "1.0.56"
|
||||
serde_yaml = "0.8.13"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.3.0" }
|
||||
solana-core = { path = "../core", version = "1.3.0" }
|
||||
solana-genesis = { path = "../genesis", version = "1.3.0" }
|
||||
solana-client = { path = "../client", version = "1.3.0" }
|
||||
solana-faucet = { path = "../faucet", version = "1.3.0" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.3.0" }
|
||||
solana-logger = { path = "../logger", version = "1.3.0" }
|
||||
solana-metrics = { path = "../metrics", version = "1.3.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.3.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.3.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.3.0" }
|
||||
solana-version = { path = "../version", version = "1.3.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.2" }
|
||||
solana-core = { path = "../core", version = "1.5.2" }
|
||||
solana-genesis = { path = "../genesis", version = "1.5.2" }
|
||||
solana-client = { path = "../client", version = "1.5.2" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.2" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.5.2" }
|
||||
solana-logger = { path = "../logger", version = "1.5.2" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.2" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.2" }
|
||||
solana-version = { path = "../version", version = "1.5.2" }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.3.0" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.5.2" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -163,7 +163,8 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
pub fn extract_args(matches: &ArgMatches) -> Config {
|
||||
let mut args = Config::default();
|
||||
|
||||
args.entrypoint_addr = solana_net_utils::parse_host_port(
|
||||
|
@@ -22,15 +22,17 @@ fn test_exchange_local_cluster() {
|
||||
|
||||
const NUM_NODES: usize = 1;
|
||||
|
||||
let mut config = Config::default();
|
||||
config.identity = Keypair::new();
|
||||
config.duration = Duration::from_secs(1);
|
||||
config.fund_amount = 100_000;
|
||||
config.threads = 1;
|
||||
config.transfer_delay = 20; // 15
|
||||
config.batch_size = 100; // 1000;
|
||||
config.chunk_size = 10; // 200;
|
||||
config.account_groups = 1; // 10;
|
||||
let config = Config {
|
||||
identity: Keypair::new(),
|
||||
duration: Duration::from_secs(1),
|
||||
fund_amount: 100_000,
|
||||
threads: 1,
|
||||
transfer_delay: 20, // 15
|
||||
batch_size: 100, // 1000
|
||||
chunk_size: 10, // 200
|
||||
account_groups: 1, // 10
|
||||
..Config::default()
|
||||
};
|
||||
let Config {
|
||||
fund_amount,
|
||||
batch_size,
|
||||
@@ -39,7 +41,7 @@ fn test_exchange_local_cluster() {
|
||||
} = config;
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
|
||||
let cluster = LocalCluster::new(&ClusterConfig {
|
||||
let cluster = LocalCluster::new(&mut ClusterConfig {
|
||||
node_stakes: vec![100_000; NUM_NODES],
|
||||
cluster_lamports: 100_000_000_000_000,
|
||||
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
|
||||
@@ -86,18 +88,21 @@ 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_builtin_program("exchange_program", id(), process_instruction);
|
||||
bank.add_builtin("exchange_program", id(), process_instruction);
|
||||
let clients = vec![BankClient::new(bank)];
|
||||
|
||||
let mut config = Config::default();
|
||||
config.identity = identity;
|
||||
config.duration = Duration::from_secs(1);
|
||||
config.fund_amount = 100_000;
|
||||
config.threads = 1;
|
||||
config.transfer_delay = 20; // 0;
|
||||
config.batch_size = 100; // 1500;
|
||||
config.chunk_size = 10; // 1500;
|
||||
config.account_groups = 1; // 50;
|
||||
|
||||
do_bench_exchange(clients, config);
|
||||
do_bench_exchange(
|
||||
clients,
|
||||
Config {
|
||||
identity,
|
||||
duration: Duration::from_secs(1),
|
||||
fund_amount: 100_000,
|
||||
threads: 1,
|
||||
transfer_delay: 20, // 0;
|
||||
batch_size: 100, // 1500;
|
||||
chunk_size: 10, // 1500;
|
||||
account_groups: 1, // 50;
|
||||
..Config::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@@ -2,18 +2,19 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "1.3.0"
|
||||
version = "1.5.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.3.0" }
|
||||
solana-streamer = { path = "../streamer", version = "1.3.0" }
|
||||
solana-logger = { path = "../logger", version = "1.3.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.3.0" }
|
||||
solana-version = { path = "../version", version = "1.3.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.2" }
|
||||
solana-streamer = { path = "../streamer", version = "1.5.2" }
|
||||
solana-logger = { path = "../logger", version = "1.5.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.2" }
|
||||
solana-version = { path = "../version", version = "1.5.2" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -27,7 +27,7 @@ fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
|
||||
let mut num = 0;
|
||||
for p in &msgs.packets {
|
||||
let a = p.meta.addr();
|
||||
assert!(p.meta.size < PACKET_DATA_SIZE);
|
||||
assert!(p.meta.size <= PACKET_DATA_SIZE);
|
||||
send.send_to(&p.data[..p.meta.size], &a).unwrap();
|
||||
num += 1;
|
||||
}
|
||||
|
@@ -2,35 +2,36 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "1.3.0"
|
||||
version = "1.5.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.1"
|
||||
clap = "2.33.1"
|
||||
log = "0.4.8"
|
||||
rayon = "1.3.1"
|
||||
log = "0.4.11"
|
||||
rayon = "1.4.0"
|
||||
serde_json = "1.0.56"
|
||||
serde_yaml = "0.8.13"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.3.0" }
|
||||
solana-core = { path = "../core", version = "1.3.0" }
|
||||
solana-genesis = { path = "../genesis", version = "1.3.0" }
|
||||
solana-client = { path = "../client", version = "1.3.0" }
|
||||
solana-faucet = { path = "../faucet", version = "1.3.0" }
|
||||
solana-logger = { path = "../logger", version = "1.3.0" }
|
||||
solana-metrics = { path = "../metrics", version = "1.3.0" }
|
||||
solana-measure = { path = "../measure", version = "1.3.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.3.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.3.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.3.0" }
|
||||
solana-version = { path = "../version", version = "1.3.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.2" }
|
||||
solana-core = { path = "../core", version = "1.5.2" }
|
||||
solana-genesis = { path = "../genesis", version = "1.5.2" }
|
||||
solana-client = { path = "../client", version = "1.5.2" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.2" }
|
||||
solana-logger = { path = "../logger", version = "1.5.2" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.2" }
|
||||
solana-measure = { path = "../measure", version = "1.5.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.2" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.2" }
|
||||
solana-version = { path = "../version", version = "1.5.2" }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.4.0"
|
||||
serial_test_derive = "0.4.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.3.0" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.5.2" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -938,10 +938,12 @@ mod tests {
|
||||
let bank = Bank::new(&genesis_config);
|
||||
let client = Arc::new(BankClient::new(bank));
|
||||
|
||||
let mut config = Config::default();
|
||||
config.id = id;
|
||||
config.tx_count = 10;
|
||||
config.duration = Duration::from_secs(5);
|
||||
let config = Config {
|
||||
id,
|
||||
tx_count: 10,
|
||||
duration: Duration::from_secs(5),
|
||||
..Config::default()
|
||||
};
|
||||
|
||||
let keypair_count = config.tx_count * config.keypair_multiplier;
|
||||
let keypairs =
|
||||
|
@@ -196,7 +196,7 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
|
||||
/// * `matches` - command line arguments parsed by clap
|
||||
/// # Panics
|
||||
/// Panics if there is trouble parsing any of the arguments
|
||||
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
pub fn extract_args(matches: &ArgMatches) -> Config {
|
||||
let mut args = Config::default();
|
||||
|
||||
if let Some(addr) = matches.value_of("entrypoint") {
|
||||
|
@@ -15,7 +15,7 @@ fn test_bench_tps_local_cluster(config: Config) {
|
||||
|
||||
solana_logger::setup();
|
||||
const NUM_NODES: usize = 1;
|
||||
let cluster = LocalCluster::new(&ClusterConfig {
|
||||
let cluster = LocalCluster::new(&mut ClusterConfig {
|
||||
node_stakes: vec![999_990; NUM_NODES],
|
||||
cluster_lamports: 200_000_000,
|
||||
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
|
||||
@@ -60,9 +60,9 @@ fn test_bench_tps_local_cluster(config: Config) {
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_bench_tps_local_cluster_solana() {
|
||||
let mut config = Config::default();
|
||||
config.tx_count = 100;
|
||||
config.duration = Duration::from_secs(10);
|
||||
|
||||
test_bench_tps_local_cluster(config);
|
||||
test_bench_tps_local_cluster(Config {
|
||||
tx_count: 100,
|
||||
duration: Duration::from_secs(10),
|
||||
..Config::default()
|
||||
});
|
||||
}
|
||||
|
31
cargo
Executable file
31
cargo
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# shellcheck source=ci/rust-version.sh
|
||||
here=$(dirname "$0")
|
||||
|
||||
source "${here}"/ci/rust-version.sh all
|
||||
|
||||
toolchain=
|
||||
case "$1" in
|
||||
stable)
|
||||
# shellcheck disable=SC2054 # rust_stable is sourced from rust-version.sh
|
||||
toolchain="$rust_stable"
|
||||
shift
|
||||
;;
|
||||
nightly)
|
||||
# shellcheck disable=SC2054 # rust_nightly is sourced from rust-version.sh
|
||||
toolchain="$rust_nightly"
|
||||
shift
|
||||
;;
|
||||
+*)
|
||||
toolchain="${1#+}"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
# shellcheck disable=SC2054 # rust_stable is sourced from rust-version.sh
|
||||
toolchain="$rust_stable"
|
||||
;;
|
||||
esac
|
||||
|
||||
set -x
|
||||
exec cargo "+${toolchain}" "${@}"
|
13
cargo-build-bpf
Executable file
13
cargo-build-bpf
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
here=$(dirname "$0")
|
||||
|
||||
maybe_bpf_sdk="--bpf-sdk $here/sdk/bpf"
|
||||
for a in "$@"; do
|
||||
if [[ $a = --bpf-sdk ]]; then
|
||||
maybe_bpf_sdk=
|
||||
fi
|
||||
done
|
||||
|
||||
set -x
|
||||
exec "$here"/cargo run --manifest-path "$here"/sdk/cargo-build-bpf/Cargo.toml -- $maybe_bpf_sdk "$@"
|
14
cargo-test-bpf
Executable file
14
cargo-test-bpf
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
here=$(dirname "$0")
|
||||
|
||||
maybe_bpf_sdk="--bpf-sdk $here/sdk/bpf"
|
||||
for a in "$@"; do
|
||||
if [[ $a = --bpf-sdk ]]; then
|
||||
maybe_bpf_sdk=
|
||||
fi
|
||||
done
|
||||
|
||||
export CARGO_BUILD_BPF="$here"/cargo-build-bpf
|
||||
set -x
|
||||
exec "$here"/cargo run --manifest-path "$here"/sdk/cargo-test-bpf/Cargo.toml -- $maybe_bpf_sdk "$@"
|
@@ -47,6 +47,8 @@ sudo ./setup-new-buildkite-agent/setup-buildkite.sh
|
||||
```
|
||||
- Copy the pubkey contents from `~buildkite-agent/.ssh/id_ecdsa.pub` and
|
||||
add the pubkey as an authorized SSH key on github.
|
||||
- In net/scripts/solana-user-authorized_keys.sh
|
||||
- Bug mvines to add it to the "solana-grimes" github user
|
||||
- Edit `/etc/buildkite-agent/buildkite-agent.cfg` and/or `/etc/systemd/system/buildkite-agent@*` to the desired configuration of the agent(s)
|
||||
- Copy `ejson` keys from another CI node at `/opt/ejson/keys/`
|
||||
to the same location on the new node.
|
||||
|
@@ -175,6 +175,30 @@ EOF
|
||||
"Stable-perf skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Downstream backwards compatibility
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-stable-perf.sh \
|
||||
^ci/test-stable.sh \
|
||||
^ci/test-local-cluster.sh \
|
||||
^core/build.rs \
|
||||
^fetch-perf-libs.sh \
|
||||
^programs/ \
|
||||
^sdk/ \
|
||||
^scripts/build-downstream-projects.sh \
|
||||
; then
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- command: "scripts/build-downstream-projects.sh"
|
||||
name: "downstream-projects"
|
||||
timeout_in_minutes: 30
|
||||
EOF
|
||||
else
|
||||
annotate --style info \
|
||||
"downstream-projects skipped as no relevant files were modified"
|
||||
fi
|
||||
# Benches...
|
||||
if affects \
|
||||
.rs$ \
|
||||
@@ -239,7 +263,7 @@ if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
|
||||
annotate --style info --context pr-backlink \
|
||||
"Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH"
|
||||
|
||||
if [[ $GITHUB_USER = "dependabot-preview[bot]" ]]; then
|
||||
if [[ $GITHUB_USER = "dependabot[bot]" ]]; then
|
||||
command_step dependabot "ci/dependabot-pr.sh" 5
|
||||
wait_step
|
||||
fi
|
||||
|
@@ -89,12 +89,20 @@ BETA_CHANNEL_LATEST_TAG=${beta_tag:+v$beta_tag}
|
||||
STABLE_CHANNEL_LATEST_TAG=${stable_tag:+v$stable_tag}
|
||||
|
||||
|
||||
if [[ $CI_BRANCH = "$STABLE_CHANNEL" ]]; then
|
||||
CHANNEL=stable
|
||||
elif [[ $CI_BRANCH = "$EDGE_CHANNEL" ]]; then
|
||||
CHANNEL=edge
|
||||
elif [[ $CI_BRANCH = "$BETA_CHANNEL" ]]; then
|
||||
CHANNEL=beta
|
||||
if [[ -n $CI_BASE_BRANCH ]]; then
|
||||
BRANCH="$CI_BASE_BRANCH"
|
||||
elif [[ -n $CI_BRANCH ]]; then
|
||||
BRANCH="$CI_BRANCH"
|
||||
fi
|
||||
|
||||
if [[ -z "$CHANNEL" ]]; then
|
||||
if [[ $BRANCH = "$STABLE_CHANNEL" ]]; then
|
||||
CHANNEL=stable
|
||||
elif [[ $BRANCH = "$EDGE_CHANNEL" ]]; then
|
||||
CHANNEL=edge
|
||||
elif [[ $BRANCH = "$BETA_CHANNEL" ]]; then
|
||||
CHANNEL=beta
|
||||
fi
|
||||
fi
|
||||
|
||||
echo EDGE_CHANNEL="$EDGE_CHANNEL"
|
||||
|
@@ -6,13 +6,13 @@ source ci/_
|
||||
|
||||
commit_range="$(git merge-base HEAD origin/master)..HEAD"
|
||||
parsed_update_args="$(
|
||||
git log "$commit_range" --author "dependabot-preview" --oneline -n1 |
|
||||
git log "$commit_range" --author "dependabot\[bot\]" --oneline -n1 |
|
||||
grep -o '[Bb]ump.*$' |
|
||||
sed -r 's/[Bb]ump ([^ ]+) from ([^ ]+) to ([^ ]+)/-p \1:\2 --precise \3/'
|
||||
)"
|
||||
# relaxed_parsed_update_args is temporal measure...
|
||||
relaxed_parsed_update_args="$(
|
||||
git log "$commit_range" --author "dependabot-preview" --oneline -n1 |
|
||||
git log "$commit_range" --author "dependabot\[bot\]" --oneline -n1 |
|
||||
grep -o '[Bb]ump.*$' |
|
||||
sed -r 's/[Bb]ump ([^ ]+) from [^ ]+ to ([^ ]+)/-p \1 --precise \2/'
|
||||
)"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM solanalabs/rust:1.45.1
|
||||
FROM solanalabs/rust:1.48.0
|
||||
ARG date
|
||||
|
||||
RUN set -x \
|
||||
|
@@ -20,7 +20,7 @@ To update the pinned version:
|
||||
specific YYYY-MM-DD that is desired (default is today's build).
|
||||
Check https://rust-lang.github.io/rustup-components-history/ for build
|
||||
status
|
||||
1. Update `ci/rust-version.sh` to reflect the new nightly `YYY-MM-DD`
|
||||
1. Update `ci/rust-version.sh` to reflect the new nightly `YYYY-MM-DD`
|
||||
1. Run `SOLANA_ALLOCATE_TTY=1 SOLANA_DOCKER_RUN_NOSETUID=1 ci/docker-run.sh --nopull solanalabs/rust-nightly:YYYY-MM-DD ci/test-checks.sh`
|
||||
and `SOLANA_ALLOCATE_TTY=1 SOLANA_DOCKER_RUN_NOSETUID=1 ci/docker-run.sh --nopull solanalabs/rust-nightly:YYYY-MM-DD ci/test-coverage.sh [args]...`
|
||||
to confirm the new nightly image builds. Fix any issues as needed
|
||||
|
@@ -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.45.1
|
||||
FROM rust:1.48.0
|
||||
|
||||
# Add Google Protocol Buffers for Libra's metrics library.
|
||||
ENV PROTOC_VERSION 3.8.0
|
||||
|
@@ -26,6 +26,9 @@ declare print_free_tree=(
|
||||
':runtime/src/**.rs'
|
||||
':sdk/bpf/rust/rust-utils/**.rs'
|
||||
':sdk/**.rs'
|
||||
':^sdk/cargo-build-bpf/**.rs'
|
||||
':^sdk/program/src/program_option.rs'
|
||||
':^sdk/program/src/program_stubs.rs'
|
||||
':programs/**.rs'
|
||||
':^**bin**.rs'
|
||||
':^**bench**.rs'
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2.7
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# This script figures the order in which workspace crates must be published to
|
||||
# crates.io. Along the way it also ensures there are no circular dependencies
|
||||
@@ -45,21 +45,27 @@ def get_packages():
|
||||
sorted_dependency_graph = []
|
||||
max_iterations = pow(len(dependency_graph),2)
|
||||
while dependency_graph:
|
||||
deleted_packages = []
|
||||
if max_iterations == 0:
|
||||
# One day be more helpful and find the actual cycle for the user...
|
||||
sys.exit('Error: Circular dependency suspected between these packages: \n {}\n'.format('\n '.join(dependency_graph.keys())))
|
||||
|
||||
max_iterations -= 1
|
||||
|
||||
for package, dependencies in dependency_graph.items():
|
||||
if package in deleted_packages:
|
||||
continue
|
||||
for dependency in dependencies:
|
||||
if dependency in dependency_graph:
|
||||
break
|
||||
else:
|
||||
del dependency_graph[package]
|
||||
deleted_packages.append(package)
|
||||
sorted_dependency_graph.append((package, manifest_path[package]))
|
||||
|
||||
dependency_graph = {p: d for p, d in dependency_graph.items() if not p in deleted_packages }
|
||||
|
||||
|
||||
return sorted_dependency_graph
|
||||
|
||||
for package, manifest in get_packages():
|
||||
print os.path.relpath(manifest)
|
||||
print(os.path.relpath(manifest))
|
||||
|
@@ -16,21 +16,12 @@ fi
|
||||
[[ -f bpf-sdk.tar.bz2 ]]
|
||||
)
|
||||
|
||||
source ci/upload-ci-artifact.sh
|
||||
echo --- AWS S3 Store
|
||||
if [[ -z $CHANNEL_OR_TAG ]]; then
|
||||
echo Skipped
|
||||
else
|
||||
(
|
||||
set -x
|
||||
docker run \
|
||||
--rm \
|
||||
--env AWS_ACCESS_KEY_ID \
|
||||
--env AWS_SECRET_ACCESS_KEY \
|
||||
--volume "$PWD:/solana" \
|
||||
eremite/aws-cli:2018.12.18 \
|
||||
/usr/bin/s3cmd --acl-public put /solana/bpf-sdk.tar.bz2 \
|
||||
s3://solana-sdk/"$CHANNEL_OR_TAG"/bpf-sdk.tar.bz2
|
||||
)
|
||||
upload-s3-artifact "/solana/bpf-sdk.tar.bz2" "s3://solana-sdk/$CHANNEL_OR_TAG/bpf-sdk.tar.bz2"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
@@ -4,6 +4,8 @@ cd "$(dirname "$0")/.."
|
||||
source ci/semver_bash/semver.sh
|
||||
source ci/rust-version.sh stable
|
||||
|
||||
cargo="$(readlink -f ./cargo)"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
is_crate_version_uploaded() {
|
||||
name=$1
|
||||
@@ -38,7 +40,7 @@ for Cargo_toml in $Cargo_tomls; do
|
||||
crate_name=$(grep -m 1 '^name = ' "$Cargo_toml" | cut -f 3 -d ' ' | tr -d \")
|
||||
|
||||
if grep -q "^publish = false" "$Cargo_toml"; then
|
||||
echo "$crate_name is is marked as unpublishable"
|
||||
echo "$crate_name is marked as unpublishable"
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -66,11 +68,11 @@ for Cargo_toml in $Cargo_tomls; do
|
||||
(
|
||||
set -x
|
||||
rm -rf crate-test
|
||||
cargo +"$rust_stable" init crate-test
|
||||
"$cargo" stable init crate-test
|
||||
cd crate-test/
|
||||
echo "${crate_name} = \"${expectedCrateVersion}\"" >> Cargo.toml
|
||||
echo "[workspace]" >> Cargo.toml
|
||||
cargo +"$rust_stable" check
|
||||
"$cargo" stable check
|
||||
) && really_uploaded=1
|
||||
if ((really_uploaded)); then
|
||||
break;
|
||||
|
@@ -91,17 +91,15 @@ echo --- Creating release tarball
|
||||
cp "${RELEASE_BASENAME}"/version.yml "${TARBALL_BASENAME}"-$TARGET.yml
|
||||
)
|
||||
|
||||
# Metrics tarball is platform agnostic, only publish it from Linux
|
||||
# Maybe tarballs are platform agnostic, only publish them from the Linux build
|
||||
MAYBE_TARBALLS=
|
||||
if [[ "$CI_OS_NAME" = linux ]]; then
|
||||
metrics/create-metrics-tarball.sh
|
||||
(
|
||||
set -x
|
||||
sdk/bpf/scripts/package.sh
|
||||
[[ -f bpf-sdk.tar.bz2 ]]
|
||||
|
||||
)
|
||||
MAYBE_TARBALLS="bpf-sdk.tar.bz2 solana-metrics.tar.bz2"
|
||||
MAYBE_TARBALLS="bpf-sdk.tar.bz2"
|
||||
fi
|
||||
|
||||
source ci/upload-ci-artifact.sh
|
||||
@@ -115,19 +113,10 @@ for file in "${TARBALL_BASENAME}"-$TARGET.tar.bz2 "${TARBALL_BASENAME}"-$TARGET.
|
||||
|
||||
if [[ -n $BUILDKITE ]]; then
|
||||
echo --- AWS S3 Store: "$file"
|
||||
(
|
||||
set -x
|
||||
$DRYRUN docker run \
|
||||
--rm \
|
||||
--env AWS_ACCESS_KEY_ID \
|
||||
--env AWS_SECRET_ACCESS_KEY \
|
||||
--volume "$PWD:/solana" \
|
||||
eremite/aws-cli:2018.12.18 \
|
||||
/usr/bin/s3cmd --acl-public put /solana/"$file" s3://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
|
||||
upload-s3-artifact "/solana/$file" s3://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
|
||||
|
||||
echo Published to:
|
||||
$DRYRUN ci/format-url.sh http://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
|
||||
)
|
||||
echo Published to:
|
||||
$DRYRUN ci/format-url.sh https://release.solana.com/"$CHANNEL_OR_TAG"/"$file"
|
||||
|
||||
if [[ -n $TAG ]]; then
|
||||
ci/upload-github-release-asset.sh "$file"
|
||||
@@ -149,4 +138,22 @@ for file in "${TARBALL_BASENAME}"-$TARGET.tar.bz2 "${TARBALL_BASENAME}"-$TARGET.
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
# Create install wrapper for release.solana.com
|
||||
if [[ -n $DO_NOT_PUBLISH_TAR ]]; then
|
||||
echo "Skipping publishing install wrapper"
|
||||
elif [[ -n $BUILDKITE ]]; then
|
||||
cat > release.solana.com-install <<EOF
|
||||
SOLANA_RELEASE=$CHANNEL_OR_TAG
|
||||
SOLANA_INSTALL_INIT_ARGS=$CHANNEL_OR_TAG
|
||||
SOLANA_DOWNLOAD_ROOT=http://release.solana.com
|
||||
EOF
|
||||
cat install/solana-install-init.sh >> release.solana.com-install
|
||||
|
||||
echo --- AWS S3 Store: "install"
|
||||
$DRYRUN upload-s3-artifact "/solana/release.solana.com-install" "s3://release.solana.com/$CHANNEL_OR_TAG/install"
|
||||
echo Published to:
|
||||
$DRYRUN ci/format-url.sh https://release.solana.com/"$CHANNEL_OR_TAG"/install
|
||||
fi
|
||||
|
||||
echo --- ok
|
||||
|
@@ -7,7 +7,7 @@ source multinode-demo/common.sh
|
||||
|
||||
rm -rf config/run/init-completed config/ledger config/snapshot-ledger
|
||||
|
||||
timeout 15 ./run.sh &
|
||||
timeout 120 ./run.sh &
|
||||
pid=$!
|
||||
|
||||
attempts=20
|
||||
|
@@ -18,13 +18,13 @@
|
||||
if [[ -n $RUST_STABLE_VERSION ]]; then
|
||||
stable_version="$RUST_STABLE_VERSION"
|
||||
else
|
||||
stable_version=1.45.1
|
||||
stable_version=1.48.0
|
||||
fi
|
||||
|
||||
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
||||
nightly_version="$RUST_NIGHTLY_VERSION"
|
||||
else
|
||||
nightly_version=2020-07-27
|
||||
nightly_version=2020-12-13
|
||||
fi
|
||||
|
||||
|
||||
|
@@ -76,7 +76,7 @@ RestartForceExitStatus=SIGPIPE
|
||||
TimeoutStartSec=10
|
||||
TimeoutStopSec=0
|
||||
KillMode=process
|
||||
LimitNOFILE=65536
|
||||
LimitNOFILE=500000
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
@@ -8,5 +8,5 @@ source "$HERE"/utils.sh
|
||||
ensure_env || exit 1
|
||||
|
||||
# Allow more files to be opened by a user
|
||||
sed -i 's/^\(# End of file\)/* soft nofile 65535\n\n\1/' /etc/security/limits.conf
|
||||
echo "* - nofile 500000" > /etc/security/limits.d/90-solana-nofiles.conf
|
||||
|
||||
|
@@ -7,6 +7,7 @@ SOLANA_ROOT="$HERE"/../..
|
||||
source "$HERE"/utils.sh
|
||||
|
||||
ensure_env || exit 1
|
||||
check_ssh_authorized_keys || exit 1
|
||||
|
||||
set -ex
|
||||
|
||||
|
@@ -6,6 +6,11 @@ HERE="$(dirname "$0")"
|
||||
source "$HERE"/utils.sh
|
||||
|
||||
ensure_env || exit 1
|
||||
# This is a last ditch effort to prevent the caller from locking themselves
|
||||
# out of the machine. Exiting here will likely leave the system in some
|
||||
# half-configured state. To prevent this, duplicate the next line at the top
|
||||
# of the entrypoint script.
|
||||
check_ssh_authorized_keys || exit 1
|
||||
|
||||
set -xe
|
||||
# Setup sshd
|
||||
|
@@ -14,3 +14,33 @@ ensure_env() {
|
||||
$RC
|
||||
}
|
||||
|
||||
# Some scripts disable SSH password logins. If no one hash setup authorized_keys
|
||||
# this will result in the machine being remotely inaccessible. Check that the
|
||||
# user running this script has setup their keys
|
||||
check_ssh_authorized_keys() {
|
||||
declare rc=false
|
||||
declare user_home=
|
||||
if [[ -n "$SUDO_USER" ]]; then
|
||||
declare user uid gid home
|
||||
declare passwd_entry
|
||||
passwd_entry="$(grep "$SUDO_USER:[^:]*:$SUDO_UID:$SUDO_GID" /etc/passwd)"
|
||||
IFS=: read -r user _ uid gid _ home _ <<<"$passwd_entry"
|
||||
if [[ "$user" == "$SUDO_USER" && "$uid" == "$SUDO_UID" && "$gid" == "$SUDO_GID" ]]; then
|
||||
user_home="$home"
|
||||
fi
|
||||
else
|
||||
user_home="$HOME"
|
||||
fi
|
||||
declare authorized_keys="${user_home}/.ssh/authorized_keys"
|
||||
if [[ -n "$user_home" ]]; then
|
||||
[[ -s "$authorized_keys" ]] && rc=true
|
||||
fi
|
||||
if ! $rc; then
|
||||
echo "ERROR! This script will disable SSH password logins and you don't"
|
||||
echo "appear to have set up any authorized keys. Please add you SSH"
|
||||
echo "public key to ${authorized_keys} before continuing!"
|
||||
fi
|
||||
$rc
|
||||
}
|
||||
|
||||
check_ssh_authorized_keys
|
||||
|
@@ -6,7 +6,8 @@ source ci/_
|
||||
source ci/upload-ci-artifact.sh
|
||||
|
||||
eval "$(ci/channel-info.sh)"
|
||||
source ci/rust-version.sh all
|
||||
|
||||
cargo="$(readlink -f "./cargo")"
|
||||
|
||||
set -o pipefail
|
||||
export RUST_BACKTRACE=1
|
||||
@@ -27,35 +28,35 @@ test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
|
||||
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
|
||||
|
||||
# Ensure all dependencies are built
|
||||
_ cargo +$rust_nightly build --release
|
||||
_ "$cargo" nightly build --release
|
||||
|
||||
# Remove "BENCH_FILE", if it exists so that the following commands can append
|
||||
rm -f "$BENCH_FILE"
|
||||
|
||||
# Run sdk benches
|
||||
_ cargo +$rust_nightly bench --manifest-path sdk/Cargo.toml ${V:+--verbose} \
|
||||
_ "$cargo" nightly bench --manifest-path sdk/Cargo.toml ${V:+--verbose} \
|
||||
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
|
||||
|
||||
# Run runtime benches
|
||||
_ cargo +$rust_nightly bench --manifest-path runtime/Cargo.toml ${V:+--verbose} \
|
||||
_ "$cargo" nightly bench --manifest-path runtime/Cargo.toml ${V:+--verbose} \
|
||||
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
|
||||
|
||||
# Run core benches
|
||||
_ cargo +$rust_nightly bench --manifest-path core/Cargo.toml ${V:+--verbose} \
|
||||
_ "$cargo" nightly bench --manifest-path core/Cargo.toml ${V:+--verbose} \
|
||||
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
|
||||
|
||||
# Run bpf benches
|
||||
_ cargo +$rust_nightly bench --manifest-path programs/bpf/Cargo.toml ${V:+--verbose} --features=bpf_c \
|
||||
_ "$cargo" nightly bench --manifest-path programs/bpf/Cargo.toml ${V:+--verbose} --features=bpf_c \
|
||||
-- -Z unstable-options --format=json --nocapture | tee -a "$BENCH_FILE"
|
||||
|
||||
# Run banking/accounts bench. Doesn't require nightly, but use since it is already built.
|
||||
_ 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"
|
||||
_ "$cargo" nightly run --release --manifest-path banking-bench/Cargo.toml ${V:+--verbose} | tee -a "$BENCH_FILE"
|
||||
_ "$cargo" 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
|
||||
exit 0
|
||||
_ cargo +$rust_nightly run --release --package solana-upload-perf \
|
||||
_ "$cargo" nightly run --release --package solana-upload-perf \
|
||||
-- "$BENCH_FILE" "$TARGET_BRANCH" "$UPLOAD_METRICS" | tee "$BENCH_ARTIFACT"
|
||||
|
||||
upload-ci-artifact "$BENCH_FILE"
|
||||
|
@@ -8,6 +8,9 @@ source ci/_
|
||||
source ci/rust-version.sh stable
|
||||
source ci/rust-version.sh nightly
|
||||
eval "$(ci/channel-info.sh)"
|
||||
cargo="$(readlink -f "./cargo")"
|
||||
|
||||
scripts/increment-cargo-version.sh check
|
||||
|
||||
echo --- build environment
|
||||
(
|
||||
@@ -16,14 +19,14 @@ echo --- build environment
|
||||
rustup run "$rust_stable" rustc --version --verbose
|
||||
rustup run "$rust_nightly" rustc --version --verbose
|
||||
|
||||
cargo +"$rust_stable" --version --verbose
|
||||
cargo +"$rust_nightly" --version --verbose
|
||||
"$cargo" stable --version --verbose
|
||||
"$cargo" nightly --version --verbose
|
||||
|
||||
cargo +"$rust_stable" clippy --version --verbose
|
||||
cargo +"$rust_nightly" clippy --version --verbose
|
||||
"$cargo" stable clippy --version --verbose
|
||||
"$cargo" nightly clippy --version --verbose
|
||||
|
||||
# audit is done only with stable
|
||||
cargo +"$rust_stable" audit --version
|
||||
"$cargo" stable audit --version
|
||||
)
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
@@ -41,32 +44,59 @@ if [[ $CI_BASE_BRANCH = "$EDGE_CHANNEL" ]]; then
|
||||
echo "$0: [tree (for outdated Cargo.lock sync)|check (for compilation error)|update -p foo --precise x.y.z (for your Cargo.toml update)] ..." >&2
|
||||
exit "$check_status"
|
||||
fi
|
||||
|
||||
# Ensure nightly and --benches
|
||||
_ scripts/cargo-for-all-lock-files.sh +"$rust_nightly" check --locked --all-targets
|
||||
else
|
||||
echo "Note: cargo-for-all-lock-files.sh skipped because $CI_BASE_BRANCH != $EDGE_CHANNEL"
|
||||
fi
|
||||
|
||||
# Ensure nightly and --benches
|
||||
_ scripts/cargo-for-all-lock-files.sh +"$rust_nightly" check --locked --all-targets
|
||||
|
||||
_ ci/order-crates-for-publishing.py
|
||||
_ cargo +"$rust_stable" fmt --all -- --check
|
||||
_ "$cargo" stable fmt --all -- --check
|
||||
|
||||
# -Z... is needed because of clippy bug: https://github.com/rust-lang/rust-clippy/issues/4612
|
||||
# run nightly clippy for `sdk/` as there's a moderate amount of nightly-only code there
|
||||
_ cargo +"$rust_nightly" clippy -Zunstable-options --workspace --all-targets -- --deny=warnings
|
||||
_ "$cargo" nightly clippy -Zunstable-options --workspace --all-targets -- --deny=warnings
|
||||
|
||||
_ scripts/cargo-for-all-lock-files.sh +"$rust_stable" audit --ignore RUSTSEC-2020-0002 --ignore RUSTSEC-2020-0008
|
||||
cargo_audit_ignores=(
|
||||
# failure is officially deprecated/unmaintained
|
||||
#
|
||||
# Blocked on multiple upstream crates removing their `failure` dependency.
|
||||
--ignore RUSTSEC-2020-0036
|
||||
|
||||
# `net2` crate has been deprecated; use `socket2` instead
|
||||
#
|
||||
# Blocked on https://github.com/paritytech/jsonrpc/issues/575
|
||||
--ignore RUSTSEC-2020-0016
|
||||
|
||||
# stdweb is unmaintained
|
||||
#
|
||||
# Blocked on multiple upstream crates removing their `stdweb` dependency.
|
||||
--ignore RUSTSEC-2020-0056
|
||||
|
||||
# Potential segfault in the time crate
|
||||
#
|
||||
# Blocked on multiple crates updating `time` to >= 0.2.23
|
||||
--ignore RUSTSEC-2020-0071
|
||||
|
||||
# difference is unmaintained
|
||||
#
|
||||
# Blocked on predicates v1.0.6 removing its dependency on `difference`
|
||||
--ignore RUSTSEC-2020-0095
|
||||
|
||||
)
|
||||
_ scripts/cargo-for-all-lock-files.sh +"$rust_stable" audit "${cargo_audit_ignores[@]}"
|
||||
|
||||
{
|
||||
cd programs/bpf
|
||||
_ cargo +"$rust_stable" audit
|
||||
_ "$cargo" stable audit
|
||||
for project in rust/*/ ; do
|
||||
echo "+++ do_bpf_checks $project"
|
||||
(
|
||||
cd "$project"
|
||||
_ cargo +"$rust_stable" fmt -- --check
|
||||
_ cargo +"$rust_nightly" test
|
||||
_ cargo +"$rust_nightly" clippy -- --deny=warnings --allow=clippy::missing_safety_doc
|
||||
_ "$cargo" stable fmt -- --check
|
||||
_ "$cargo" nightly test
|
||||
_ "$cargo" nightly clippy -- --deny=warnings --allow=clippy::missing_safety_doc
|
||||
)
|
||||
done
|
||||
}
|
||||
|
@@ -8,10 +8,16 @@ source ci/_
|
||||
(
|
||||
echo --- git diff --check
|
||||
set -x
|
||||
|
||||
if [[ -n $CI_BASE_BRANCH ]]
|
||||
then branch="$CI_BASE_BRANCH"
|
||||
else branch="master"
|
||||
fi
|
||||
|
||||
# Look for failed mergify.io backports by searching leftover conflict markers
|
||||
# Also check for any trailing whitespaces!
|
||||
git fetch origin "$CI_BASE_BRANCH"
|
||||
git diff "$(git merge-base HEAD "origin/$CI_BASE_BRANCH")..HEAD" --check --oneline
|
||||
git fetch origin "$branch"
|
||||
git diff "$(git merge-base HEAD "origin/$branch")" --check --oneline
|
||||
)
|
||||
|
||||
echo
|
||||
|
@@ -2,6 +2,8 @@
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
cargo="$(readlink -f "./cargo")"
|
||||
|
||||
source ci/_
|
||||
|
||||
annotate() {
|
||||
@@ -19,9 +21,6 @@ export RUST_BACKTRACE=1
|
||||
export RUSTFLAGS="-D warnings"
|
||||
source scripts/ulimit-n.sh
|
||||
|
||||
# Clear cached json keypair files
|
||||
rm -rf "$HOME/.config/solana"
|
||||
|
||||
# Clear the C dependency files, if dependency moves these files are not regenerated
|
||||
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
|
||||
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
|
||||
@@ -37,12 +36,15 @@ NPROC=$((NPROC>14 ? 14 : NPROC))
|
||||
echo "Executing $testName"
|
||||
case $testName in
|
||||
test-stable)
|
||||
_ cargo +"$rust_stable" test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
_ "$cargo" stable test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
;;
|
||||
test-stable-perf)
|
||||
# BPF solana-sdk legacy compile test
|
||||
./cargo-build-bpf --manifest-path sdk/Cargo.toml
|
||||
|
||||
# BPF program tests
|
||||
_ make -C programs/bpf/c tests
|
||||
_ cargo +"$rust_stable" test \
|
||||
_ "$cargo" stable test \
|
||||
--manifest-path programs/bpf/Cargo.toml \
|
||||
--no-default-features --features=bpf_c,bpf_rust -- --nocapture
|
||||
|
||||
@@ -62,13 +64,13 @@ test-stable-perf)
|
||||
export SOLANA_CUDA=1
|
||||
fi
|
||||
|
||||
_ cargo +"$rust_stable" build --bins ${V:+--verbose}
|
||||
_ cargo +"$rust_stable" test --package solana-perf --package solana-ledger --package solana-core --lib ${V:+--verbose} -- --nocapture
|
||||
_ cargo +"$rust_stable" run --manifest-path poh-bench/Cargo.toml ${V:+--verbose} -- --hashes-per-tick 10
|
||||
_ "$cargo" stable build --bins ${V:+--verbose}
|
||||
_ "$cargo" stable test --package solana-perf --package solana-ledger --package solana-core --lib ${V:+--verbose} -- --nocapture
|
||||
_ "$cargo" stable run --manifest-path poh-bench/Cargo.toml ${V:+--verbose} -- --hashes-per-tick 10
|
||||
;;
|
||||
test-local-cluster)
|
||||
_ cargo +"$rust_stable" build --release --bins ${V:+--verbose}
|
||||
_ cargo +"$rust_stable" test --release --package solana-local-cluster ${V:+--verbose} -- --nocapture --test-threads=1
|
||||
_ "$cargo" stable build --release --bins ${V:+--verbose}
|
||||
_ "$cargo" stable test --release --package solana-local-cluster ${V:+--verbose} -- --nocapture --test-threads=1
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
|
@@ -16,3 +16,16 @@ upload-ci-artifact() {
|
||||
fi
|
||||
}
|
||||
|
||||
upload-s3-artifact() {
|
||||
echo "--- artifact: $1 to $2"
|
||||
(
|
||||
set -x
|
||||
docker run \
|
||||
--rm \
|
||||
--env AWS_ACCESS_KEY_ID \
|
||||
--env AWS_SECRET_ACCESS_KEY \
|
||||
--volume "$PWD:/solana" \
|
||||
eremite/aws-cli:2018.12.18 \
|
||||
/usr/bin/s3cmd --acl-public put "$1" "$2"
|
||||
)
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-clap-utils"
|
||||
version = "1.3.0"
|
||||
version = "1.5.2"
|
||||
description = "Solana utilities for the clap"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -11,9 +11,9 @@ edition = "2018"
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
rpassword = "4.0"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.3.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.3.0" }
|
||||
thiserror = "1.0.20"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.5.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.2" }
|
||||
thiserror = "1.0.21"
|
||||
tiny-bip39 = "0.7.0"
|
||||
url = "2.1.0"
|
||||
chrono = "0.4"
|
||||
|
@@ -15,7 +15,7 @@ pub fn commitment_arg_with_default<'a, 'b>(default_value: &'static str) -> Arg<'
|
||||
Arg::with_name(COMMITMENT_ARG.name)
|
||||
.long(COMMITMENT_ARG.long)
|
||||
.takes_value(true)
|
||||
.possible_values(&["recent", "single", "root", "max"])
|
||||
.possible_values(&["recent", "single", "singleGossip", "root", "max"])
|
||||
.default_value(default_value)
|
||||
.value_name("COMMITMENT_LEVEL")
|
||||
.help(COMMITMENT_ARG.help)
|
||||
|
19
clap-utils/src/fee_payer.rs
Normal file
19
clap-utils/src/fee_payer.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use crate::{input_validators, ArgConstant};
|
||||
use clap::Arg;
|
||||
|
||||
pub const FEE_PAYER_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "fee_payer",
|
||||
long: "fee-payer",
|
||||
help: "Specify the fee-payer account. This may be a keypair file, the ASK keyword \n\
|
||||
or the pubkey of an offline signer, provided an appropriate --signer argument \n\
|
||||
is also passed. Defaults to the client keypair.",
|
||||
};
|
||||
|
||||
pub fn fee_payer_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(FEE_PAYER_ARG.name)
|
||||
.long(FEE_PAYER_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("KEYPAIR")
|
||||
.validator(input_validators::is_valid_signer)
|
||||
.help(FEE_PAYER_ARG.help)
|
||||
}
|
@@ -8,6 +8,7 @@ use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
clock::UnixTimestamp,
|
||||
commitment_config::CommitmentConfig,
|
||||
genesis_config::ClusterType,
|
||||
native_token::sol_to_lamports,
|
||||
pubkey::Pubkey,
|
||||
signature::{read_keypair_file, Keypair, Signature, Signer},
|
||||
@@ -178,12 +179,17 @@ pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> {
|
||||
value_of(matches, name).map(sol_to_lamports)
|
||||
}
|
||||
|
||||
pub fn cluster_type_of(matches: &ArgMatches<'_>, name: &str) -> Option<ClusterType> {
|
||||
value_of(matches, name)
|
||||
}
|
||||
|
||||
pub fn commitment_of(matches: &ArgMatches<'_>, name: &str) -> Option<CommitmentConfig> {
|
||||
matches.value_of(name).map(|value| match value {
|
||||
"max" => CommitmentConfig::max(),
|
||||
"recent" => CommitmentConfig::recent(),
|
||||
"root" => CommitmentConfig::root(),
|
||||
"single" => CommitmentConfig::single(),
|
||||
"singleGossip" => CommitmentConfig::single_gossip(),
|
||||
_ => CommitmentConfig::default(),
|
||||
})
|
||||
}
|
||||
@@ -223,8 +229,8 @@ mod tests {
|
||||
assert_eq!(values_of(&matches, "multiple"), Some(vec![50, 39]));
|
||||
assert_eq!(values_of::<u64>(&matches, "single"), None);
|
||||
|
||||
let pubkey0 = Pubkey::new_rand();
|
||||
let pubkey1 = Pubkey::new_rand();
|
||||
let pubkey0 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey1 = solana_sdk::pubkey::new_rand();
|
||||
let matches = app().clone().get_matches_from(vec![
|
||||
"test",
|
||||
"--multiple",
|
||||
@@ -246,7 +252,7 @@ mod tests {
|
||||
assert_eq!(value_of(&matches, "single"), Some(50));
|
||||
assert_eq!(value_of::<u64>(&matches, "multiple"), None);
|
||||
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let pubkey = solana_sdk::pubkey::new_rand();
|
||||
let matches = app()
|
||||
.clone()
|
||||
.get_matches_from(vec!["test", "--single", &pubkey.to_string()]);
|
||||
@@ -326,8 +332,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_pubkeys_sigs_of() {
|
||||
let key1 = Pubkey::new_rand();
|
||||
let key2 = Pubkey::new_rand();
|
||||
let key1 = solana_sdk::pubkey::new_rand();
|
||||
let key2 = solana_sdk::pubkey::new_rand();
|
||||
let sig1 = Keypair::new().sign_message(&[0u8]);
|
||||
let sig2 = Keypair::new().sign_message(&[1u8]);
|
||||
let signer1 = format!("{}={}", key1, sig1);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
use crate::keypair::{parse_keypair_path, KeypairUrl, ASK_KEYWORD};
|
||||
use chrono::DateTime;
|
||||
use solana_sdk::{
|
||||
clock::Slot,
|
||||
clock::{Epoch, Slot},
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{read_keypair_file, Signature},
|
||||
@@ -148,6 +148,40 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_url_or_moniker<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
match url::Url::parse(&normalize_to_url_if_moniker(string.as_ref())) {
|
||||
Ok(url) => {
|
||||
if url.has_host() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("no host provided".to_string())
|
||||
}
|
||||
}
|
||||
Err(err) => Err(format!("{}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_to_url_if_moniker(url_or_moniker: &str) -> String {
|
||||
match url_or_moniker {
|
||||
"m" | "mainnet-beta" => "https://api.mainnet-beta.solana.com",
|
||||
"t" | "testnet" => "https://testnet.solana.com",
|
||||
"d" | "devnet" => "https://devnet.solana.com",
|
||||
"l" | "localhost" => "http://localhost:8899",
|
||||
url => url,
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn is_epoch<T>(epoch: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
is_parsable_generic::<Epoch, _>(epoch)
|
||||
}
|
||||
|
||||
pub fn is_slot<T>(slot: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
|
@@ -11,6 +11,7 @@ use solana_remote_wallet::{
|
||||
remote_wallet::{maybe_wallet_manager, RemoteWalletError, RemoteWalletManager},
|
||||
};
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{
|
||||
keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair,
|
||||
@@ -25,6 +26,90 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub struct SignOnly {
|
||||
pub blockhash: Hash,
|
||||
pub present_signers: Vec<(Pubkey, Signature)>,
|
||||
pub absent_signers: Vec<Pubkey>,
|
||||
pub bad_signers: Vec<Pubkey>,
|
||||
}
|
||||
|
||||
impl SignOnly {
|
||||
pub fn has_all_signers(&self) -> bool {
|
||||
self.absent_signers.is_empty() && self.bad_signers.is_empty()
|
||||
}
|
||||
|
||||
pub fn presigner_of(&self, pubkey: &Pubkey) -> Option<Presigner> {
|
||||
presigner_from_pubkey_sigs(pubkey, &self.present_signers)
|
||||
}
|
||||
}
|
||||
pub type CliSigners = Vec<Box<dyn Signer>>;
|
||||
pub type SignerIndex = usize;
|
||||
pub struct CliSignerInfo {
|
||||
pub signers: CliSigners,
|
||||
}
|
||||
|
||||
impl CliSignerInfo {
|
||||
pub fn index_of(&self, pubkey: Option<Pubkey>) -> Option<usize> {
|
||||
if let Some(pubkey) = pubkey {
|
||||
self.signers
|
||||
.iter()
|
||||
.position(|signer| signer.pubkey() == pubkey)
|
||||
} else {
|
||||
Some(0)
|
||||
}
|
||||
}
|
||||
pub fn index_of_or_none(&self, pubkey: Option<Pubkey>) -> Option<usize> {
|
||||
if let Some(pubkey) = pubkey {
|
||||
self.signers
|
||||
.iter()
|
||||
.position(|signer| signer.pubkey() == pubkey)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultSigner {
|
||||
pub arg_name: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
impl DefaultSigner {
|
||||
pub fn generate_unique_signers(
|
||||
&self,
|
||||
bulk_signers: Vec<Option<Box<dyn Signer>>>,
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliSignerInfo, Box<dyn error::Error>> {
|
||||
let mut unique_signers = vec![];
|
||||
|
||||
// Determine if the default signer is needed
|
||||
if bulk_signers.iter().any(|signer| signer.is_none()) {
|
||||
let default_signer = self.signer_from_path(matches, wallet_manager)?;
|
||||
unique_signers.push(default_signer);
|
||||
}
|
||||
|
||||
for signer in bulk_signers.into_iter() {
|
||||
if let Some(signer) = signer {
|
||||
if !unique_signers.iter().any(|s| s == &signer) {
|
||||
unique_signers.push(signer);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(CliSignerInfo {
|
||||
signers: unique_signers,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn signer_from_path(
|
||||
&self,
|
||||
matches: &ArgMatches,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Box<dyn Signer>, Box<dyn std::error::Error>> {
|
||||
signer_from_path(matches, &self.path, &self.arg_name, wallet_manager)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum KeypairUrl {
|
||||
Ask,
|
||||
Filepath(String),
|
||||
@@ -78,7 +163,7 @@ pub fn signer_from_path(
|
||||
KeypairUrl::Filepath(path) => match read_keypair_file(&path) {
|
||||
Err(e) => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("could not find keypair file: {} error: {}", path, e),
|
||||
format!("could not read keypair file \"{}\". Run \"solana-keygen new\" to create a keypair file: {}", path, e),
|
||||
)
|
||||
.into()),
|
||||
Ok(file) => Ok(Box::new(file)),
|
||||
@@ -149,7 +234,7 @@ pub fn resolve_signer_from_path(
|
||||
KeypairUrl::Filepath(path) => match read_keypair_file(&path) {
|
||||
Err(e) => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("could not find keypair file: {} error: {}", path, e),
|
||||
format!("could not read keypair file \"{}\". Run \"solana-keygen new\" to create a keypair file: {}", path, e),
|
||||
)
|
||||
.into()),
|
||||
Ok(_) => Ok(Some(path.to_string())),
|
||||
@@ -222,7 +307,24 @@ pub fn keypair_from_seed_phrase(
|
||||
keypair_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase)?
|
||||
} else {
|
||||
let sanitized = sanitize_seed_phrase(seed_phrase);
|
||||
let mnemonic = Mnemonic::from_phrase(&sanitized, Language::English)?;
|
||||
let parse_language_fn = || {
|
||||
for language in &[
|
||||
Language::English,
|
||||
Language::ChineseSimplified,
|
||||
Language::ChineseTraditional,
|
||||
Language::Japanese,
|
||||
Language::Spanish,
|
||||
Language::Korean,
|
||||
Language::French,
|
||||
Language::Italian,
|
||||
] {
|
||||
if let Ok(mnemonic) = Mnemonic::from_phrase(&sanitized, *language) {
|
||||
return Ok(mnemonic);
|
||||
}
|
||||
}
|
||||
Err("Can't get mnemonic from seed phrases")
|
||||
};
|
||||
let mnemonic = parse_language_fn()?;
|
||||
let passphrase = prompt_passphrase(&passphrase_prompt)?;
|
||||
let seed = Seed::new(&mnemonic, &passphrase);
|
||||
keypair_from_seed(seed.as_bytes())?
|
||||
|
@@ -24,7 +24,9 @@ impl std::fmt::Debug for DisplayError {
|
||||
}
|
||||
|
||||
pub mod commitment;
|
||||
pub mod fee_payer;
|
||||
pub mod input_parsers;
|
||||
pub mod input_validators;
|
||||
pub mod keypair;
|
||||
pub mod nonce;
|
||||
pub mod offline;
|
||||
|
50
clap-utils/src/nonce.rs
Normal file
50
clap-utils/src/nonce.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use crate::{input_validators::*, offline::BLOCKHASH_ARG, ArgConstant};
|
||||
use clap::{App, Arg};
|
||||
|
||||
pub const NONCE_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "nonce",
|
||||
long: "nonce",
|
||||
help: "Provide the nonce account to use when creating a nonced \n\
|
||||
transaction. Nonced transactions are useful when a transaction \n\
|
||||
requires a lengthy signing process. Learn more about nonced \n\
|
||||
transactions at https://docs.solana.com/offline-signing/durable-nonce",
|
||||
};
|
||||
|
||||
pub const NONCE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "nonce_authority",
|
||||
long: "nonce-authority",
|
||||
help: "Provide the nonce authority keypair to use when signing a nonced transaction",
|
||||
};
|
||||
|
||||
fn nonce_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(NONCE_ARG.name)
|
||||
.long(NONCE_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("PUBKEY")
|
||||
.requires(BLOCKHASH_ARG.name)
|
||||
.validator(is_valid_pubkey)
|
||||
.help(NONCE_ARG.help)
|
||||
}
|
||||
|
||||
pub fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(NONCE_AUTHORITY_ARG.name)
|
||||
.long(NONCE_AUTHORITY_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("KEYPAIR")
|
||||
.validator(is_valid_signer)
|
||||
.help(NONCE_AUTHORITY_ARG.help)
|
||||
}
|
||||
|
||||
pub trait NonceArgs {
|
||||
fn nonce_args(self, global: bool) -> Self;
|
||||
}
|
||||
|
||||
impl NonceArgs for App<'_, '_> {
|
||||
fn nonce_args(self, global: bool) -> Self {
|
||||
self.arg(nonce_arg().global(global)).arg(
|
||||
nonce_authority_arg()
|
||||
.requires(NONCE_ARG.name)
|
||||
.global(global),
|
||||
)
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
use crate::ArgConstant;
|
||||
use crate::{input_validators::*, ArgConstant};
|
||||
use clap::{App, Arg};
|
||||
|
||||
pub const BLOCKHASH_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "blockhash",
|
||||
@@ -17,3 +18,61 @@ pub const SIGNER_ARG: ArgConstant<'static> = ArgConstant {
|
||||
long: "signer",
|
||||
help: "Provide a public-key/signature pair for the transaction",
|
||||
};
|
||||
|
||||
pub fn blockhash_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(BLOCKHASH_ARG.name)
|
||||
.long(BLOCKHASH_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("BLOCKHASH")
|
||||
.validator(is_hash)
|
||||
.help(BLOCKHASH_ARG.help)
|
||||
}
|
||||
|
||||
pub fn sign_only_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(SIGN_ONLY_ARG.name)
|
||||
.long(SIGN_ONLY_ARG.long)
|
||||
.takes_value(false)
|
||||
.requires(BLOCKHASH_ARG.name)
|
||||
.help(SIGN_ONLY_ARG.help)
|
||||
}
|
||||
|
||||
fn signer_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(SIGNER_ARG.name)
|
||||
.long(SIGNER_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("PUBKEY=SIGNATURE")
|
||||
.validator(is_pubkey_sig)
|
||||
.requires(BLOCKHASH_ARG.name)
|
||||
.multiple(true)
|
||||
.help(SIGNER_ARG.help)
|
||||
}
|
||||
|
||||
pub trait ArgsConfig {
|
||||
fn blockhash_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
|
||||
arg
|
||||
}
|
||||
fn sign_only_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
|
||||
arg
|
||||
}
|
||||
fn signer_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
|
||||
arg
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OfflineArgs {
|
||||
fn offline_args(self) -> Self;
|
||||
fn offline_args_config(self, config: &dyn ArgsConfig) -> Self;
|
||||
}
|
||||
|
||||
impl OfflineArgs for App<'_, '_> {
|
||||
fn offline_args_config(self, config: &dyn ArgsConfig) -> Self {
|
||||
self.arg(config.blockhash_arg(blockhash_arg()))
|
||||
.arg(config.sign_only_arg(sign_only_arg()))
|
||||
.arg(config.signer_arg(signer_arg()))
|
||||
}
|
||||
fn offline_args(self) -> Self {
|
||||
struct NullArgsConfig {}
|
||||
impl ArgsConfig for NullArgsConfig {}
|
||||
self.offline_args_config(&NullArgsConfig {})
|
||||
}
|
||||
}
|
||||
|
@@ -3,13 +3,13 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-config"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.3.0"
|
||||
version = "1.5.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
dirs = "2.0.2"
|
||||
dirs-next = "2.0.0"
|
||||
lazy_static = "1.4.0"
|
||||
serde = "1.0.112"
|
||||
serde_derive = "1.0.103"
|
||||
|
@@ -5,7 +5,7 @@ use url::Url;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CONFIG_FILE: Option<String> = {
|
||||
dirs::home_dir().map(|mut path| {
|
||||
dirs_next::home_dir().map(|mut path| {
|
||||
path.extend(&[".config", "solana", "cli", "config.yml"]);
|
||||
path.to_str().unwrap().to_string()
|
||||
})
|
||||
@@ -25,7 +25,7 @@ pub struct Config {
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let keypair_path = {
|
||||
let mut keypair_path = dirs::home_dir().expect("home directory");
|
||||
let mut keypair_path = dirs_next::home_dir().expect("home directory");
|
||||
keypair_path.extend(&[".config", "solana", "id.json"]);
|
||||
keypair_path.to_str().unwrap().to_string()
|
||||
};
|
||||
|
29
cli-output/Cargo.toml
Normal file
29
cli-output/Cargo.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-output"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.5.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
console = "0.11.3"
|
||||
humantime = "2.0.1"
|
||||
Inflector = "0.11.4"
|
||||
indicatif = "0.15.0"
|
||||
serde = "1.0.112"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.5.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.2" }
|
||||
solana-client = { path = "../client", version = "1.5.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.2" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.2" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.5.2" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.2" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
@@ -1,20 +1,26 @@
|
||||
use crate::{
|
||||
cli::build_balance_message,
|
||||
display::{format_labeled_address, writeln_name_value},
|
||||
display::{build_balance_message, format_labeled_address, writeln_name_value},
|
||||
QuietDisplay, VerboseDisplay,
|
||||
};
|
||||
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
|
||||
use console::{style, Emoji};
|
||||
use inflector::cases::titlecase::to_title_case;
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
use solana_account_decoder::parse_token::UiTokenAccount;
|
||||
use solana_clap_utils::keypair::SignOnly;
|
||||
use solana_client::rpc_response::{
|
||||
RpcAccountBalance, RpcKeyedAccount, RpcSupply, RpcVoteAccountInfo,
|
||||
};
|
||||
use solana_sdk::{
|
||||
clock::{self, Epoch, Slot, UnixTimestamp},
|
||||
epoch_info::EpochInfo,
|
||||
hash::Hash,
|
||||
native_token::lamports_to_sol,
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
stake_history::StakeHistoryEntry,
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_stake_program::stake_state::{Authorized, Lockup};
|
||||
use solana_vote_program::{
|
||||
@@ -24,6 +30,7 @@ use solana_vote_program::{
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
fmt,
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
@@ -34,15 +41,27 @@ pub enum OutputFormat {
|
||||
Display,
|
||||
Json,
|
||||
JsonCompact,
|
||||
DisplayQuiet,
|
||||
DisplayVerbose,
|
||||
}
|
||||
|
||||
impl OutputFormat {
|
||||
pub fn formatted_string<T>(&self, item: &T) -> String
|
||||
where
|
||||
T: Serialize + fmt::Display,
|
||||
T: Serialize + fmt::Display + QuietDisplay + VerboseDisplay,
|
||||
{
|
||||
match self {
|
||||
OutputFormat::Display => format!("{}", item),
|
||||
OutputFormat::DisplayQuiet => {
|
||||
let mut s = String::new();
|
||||
QuietDisplay::write_str(item, &mut s).unwrap();
|
||||
s
|
||||
}
|
||||
OutputFormat::DisplayVerbose => {
|
||||
let mut s = String::new();
|
||||
VerboseDisplay::write_str(item, &mut s).unwrap();
|
||||
s
|
||||
}
|
||||
OutputFormat::Json => serde_json::to_string_pretty(item).unwrap(),
|
||||
OutputFormat::JsonCompact => serde_json::to_value(item).unwrap().to_string(),
|
||||
}
|
||||
@@ -57,6 +76,9 @@ pub struct CliAccount {
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliAccount {}
|
||||
impl VerboseDisplay for CliAccount {}
|
||||
|
||||
impl fmt::Display for CliAccount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
@@ -99,6 +121,9 @@ pub struct CliBlockProduction {
|
||||
pub verbose: bool,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliBlockProduction {}
|
||||
impl VerboseDisplay for CliBlockProduction {}
|
||||
|
||||
impl fmt::Display for CliBlockProduction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
@@ -107,7 +132,7 @@ impl fmt::Display for CliBlockProduction {
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<44} {:>15} {:>15} {:>15} {:>23}",
|
||||
"Identity Pubkey",
|
||||
"Identity",
|
||||
"Leader Slots",
|
||||
"Blocks Produced",
|
||||
"Skipped Slots",
|
||||
@@ -203,6 +228,9 @@ impl From<EpochInfo> for CliEpochInfo {
|
||||
}
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliEpochInfo {}
|
||||
impl VerboseDisplay for CliEpochInfo {}
|
||||
|
||||
impl fmt::Display for CliEpochInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
@@ -213,6 +241,9 @@ impl fmt::Display for CliEpochInfo {
|
||||
)?;
|
||||
writeln_name_value(f, "Slot:", &self.epoch_info.absolute_slot.to_string())?;
|
||||
writeln_name_value(f, "Epoch:", &self.epoch_info.epoch.to_string())?;
|
||||
if let Some(transaction_count) = &self.epoch_info.transaction_count {
|
||||
writeln_name_value(f, "Transaction Count:", &transaction_count.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(
|
||||
@@ -273,7 +304,7 @@ pub struct CliValidatorsStakeByVersion {
|
||||
pub struct CliValidators {
|
||||
pub total_active_stake: u64,
|
||||
pub total_current_stake: u64,
|
||||
pub total_deliquent_stake: u64,
|
||||
pub total_delinquent_stake: u64,
|
||||
pub current_validators: Vec<CliValidator>,
|
||||
pub delinquent_validators: Vec<CliValidator>,
|
||||
pub stake_by_version: BTreeMap<String, CliValidatorsStakeByVersion>,
|
||||
@@ -281,6 +312,9 @@ pub struct CliValidators {
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliValidators {}
|
||||
impl VerboseDisplay for CliValidators {}
|
||||
|
||||
impl fmt::Display for CliValidators {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn write_vote_account(
|
||||
@@ -300,7 +334,7 @@ impl fmt::Display for CliValidators {
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"{} {:<44} {:<44} {:>3}% {:>8} {:>10} {:>10} {:>17} {}",
|
||||
"{} {:<44} {:<44} {:>3}% {:>8} {:>10} {:>10} {:>8} {}",
|
||||
if delinquent {
|
||||
WARNING.to_string()
|
||||
} else {
|
||||
@@ -329,7 +363,7 @@ impl fmt::Display for CliValidators {
|
||||
"Active Stake:",
|
||||
&build_balance_message(self.total_active_stake, self.use_lamports_unit, true),
|
||||
)?;
|
||||
if self.total_deliquent_stake > 0 {
|
||||
if self.total_delinquent_stake > 0 {
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Current Stake:",
|
||||
@@ -345,11 +379,11 @@ impl fmt::Display for CliValidators {
|
||||
&format!(
|
||||
"{} ({:0.2}%)",
|
||||
&build_balance_message(
|
||||
self.total_deliquent_stake,
|
||||
self.total_delinquent_stake,
|
||||
self.use_lamports_unit,
|
||||
true
|
||||
),
|
||||
100. * self.total_deliquent_stake as f64 / self.total_active_stake as f64
|
||||
100. * self.total_delinquent_stake as f64 / self.total_active_stake as f64
|
||||
),
|
||||
)?;
|
||||
}
|
||||
@@ -359,7 +393,7 @@ impl fmt::Display for CliValidators {
|
||||
for (version, info) in self.stake_by_version.iter() {
|
||||
writeln!(
|
||||
f,
|
||||
"{:<16} - {:3} current validators ({:>5.2}%){}",
|
||||
"{:<8} - {:3} current validators ({:>5.2}%){}",
|
||||
version,
|
||||
info.current_validators,
|
||||
100. * info.current_active_stake as f64 / self.total_active_stake as f64,
|
||||
@@ -380,9 +414,9 @@ impl fmt::Display for CliValidators {
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<44} {:<38} {} {} {} {:>10} {:^17} {}",
|
||||
"Identity Pubkey",
|
||||
"Vote Account Pubkey",
|
||||
" {:<44} {:<38} {} {} {} {:>10} {:^8} {}",
|
||||
"Identity",
|
||||
"Vote Account",
|
||||
"Commission",
|
||||
"Last Vote",
|
||||
"Root Block",
|
||||
@@ -469,6 +503,9 @@ pub struct CliNonceAccount {
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliNonceAccount {}
|
||||
impl VerboseDisplay for CliNonceAccount {}
|
||||
|
||||
impl fmt::Display for CliNonceAccount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
@@ -486,7 +523,7 @@ impl fmt::Display for CliNonceAccount {
|
||||
)
|
||||
)?;
|
||||
let nonce = self.nonce.as_deref().unwrap_or("uninitialized");
|
||||
writeln!(f, "Nonce: {}", nonce)?;
|
||||
writeln!(f, "Nonce blockhash: {}", nonce)?;
|
||||
if let Some(fees) = self.lamports_per_signature {
|
||||
writeln!(f, "Fee: {} lamports per signature", fees)?;
|
||||
} else {
|
||||
@@ -506,6 +543,17 @@ impl CliStakeVec {
|
||||
}
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliStakeVec {}
|
||||
impl VerboseDisplay for CliStakeVec {
|
||||
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
|
||||
for state in &self.0 {
|
||||
writeln!(w)?;
|
||||
VerboseDisplay::write_str(state, w)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliStakeVec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for state in &self.0 {
|
||||
@@ -524,6 +572,14 @@ pub struct CliKeyedStakeState {
|
||||
pub stake_state: CliStakeState,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliKeyedStakeState {}
|
||||
impl VerboseDisplay for CliKeyedStakeState {
|
||||
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
|
||||
writeln!(w, "Stake Pubkey: {}", self.stake_pubkey)?;
|
||||
VerboseDisplay::write_str(&self.stake_state, w)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliKeyedStakeState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "Stake Pubkey: {}", self.stake_pubkey)?;
|
||||
@@ -531,12 +587,98 @@ impl fmt::Display for CliKeyedStakeState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliEpochReward {
|
||||
pub epoch: Epoch,
|
||||
pub effective_slot: Slot,
|
||||
pub amount: u64, // lamports
|
||||
pub post_balance: u64, // lamports
|
||||
pub percent_change: f64,
|
||||
pub apr: Option<f64>,
|
||||
}
|
||||
|
||||
fn show_votes_and_credits(
|
||||
f: &mut fmt::Formatter,
|
||||
votes: &[CliLockout],
|
||||
epoch_voting_history: &[CliEpochVotingHistory],
|
||||
) -> fmt::Result {
|
||||
if votes.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
writeln!(f, "Recent Votes:")?;
|
||||
for vote in votes {
|
||||
writeln!(f, "- slot: {}", vote.slot)?;
|
||||
writeln!(f, " confirmation count: {}", vote.confirmation_count)?;
|
||||
}
|
||||
writeln!(f, "Epoch Voting History:")?;
|
||||
writeln!(
|
||||
f,
|
||||
"* missed credits include slots unavailable to vote on due to delinquent leaders",
|
||||
)?;
|
||||
for entry in epoch_voting_history {
|
||||
writeln!(
|
||||
f, // tame fmt so that this will be folded like following
|
||||
"- epoch: {}",
|
||||
entry.epoch
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" credits range: [{}..{})",
|
||||
entry.prev_credits, entry.credits
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
" credits/slots: {}/{}",
|
||||
entry.credits_earned, entry.slots_in_epoch
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_epoch_rewards(
|
||||
f: &mut fmt::Formatter,
|
||||
epoch_rewards: &Option<Vec<CliEpochReward>>,
|
||||
) -> fmt::Result {
|
||||
if let Some(epoch_rewards) = epoch_rewards {
|
||||
if epoch_rewards.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
writeln!(f, "Epoch Rewards:")?;
|
||||
writeln!(
|
||||
f,
|
||||
" {:<6} {:<11} {:<16} {:<16} {:>14} {:>14}",
|
||||
"Epoch", "Reward Slot", "Amount", "New Balance", "Percent Change", "APR"
|
||||
)?;
|
||||
for reward in epoch_rewards {
|
||||
writeln!(
|
||||
f,
|
||||
" {:<6} {:<11} ◎{:<16.9} ◎{:<14.9} {:>13.2}% {}",
|
||||
reward.epoch,
|
||||
reward.effective_slot,
|
||||
lamports_to_sol(reward.amount),
|
||||
lamports_to_sol(reward.post_balance),
|
||||
reward.percent_change,
|
||||
reward
|
||||
.apr
|
||||
.map(|apr| format!("{:>13.2}%", apr))
|
||||
.unwrap_or_default(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliStakeState {
|
||||
pub stake_type: CliStakeType,
|
||||
pub account_balance: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub credits_observed: Option<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>,
|
||||
@@ -560,6 +702,19 @@ pub struct CliStakeState {
|
||||
pub activating_stake: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deactivating_stake: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub epoch_rewards: Option<Vec<CliEpochReward>>,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliStakeState {}
|
||||
impl VerboseDisplay for CliStakeState {
|
||||
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
|
||||
write!(w, "{}", self)?;
|
||||
if let Some(credits) = self.credits_observed {
|
||||
writeln!(w, "Credits Observed: {}", credits)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliStakeState {
|
||||
@@ -574,13 +729,8 @@ impl fmt::Display for CliStakeState {
|
||||
if lockup.unix_timestamp != UnixTimestamp::default() {
|
||||
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
|
||||
"Lockup Timestamp: {}",
|
||||
unix_timestamp_to_string(lockup.unix_timestamp)
|
||||
)?;
|
||||
}
|
||||
if lockup.epoch != Epoch::default() {
|
||||
@@ -710,13 +860,14 @@ impl fmt::Display for CliStakeState {
|
||||
}
|
||||
show_authorized(f, self.authorized.as_ref().unwrap())?;
|
||||
show_lockup(f, self.lockup.as_ref())?;
|
||||
show_epoch_rewards(f, &self.epoch_rewards)?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, PartialEq)]
|
||||
pub enum CliStakeType {
|
||||
Stake,
|
||||
RewardsPool,
|
||||
@@ -738,6 +889,9 @@ pub struct CliStakeHistory {
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliStakeHistory {}
|
||||
impl VerboseDisplay for CliStakeHistory {}
|
||||
|
||||
impl fmt::Display for CliStakeHistory {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
@@ -832,6 +986,9 @@ impl CliValidatorInfoVec {
|
||||
}
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliValidatorInfoVec {}
|
||||
impl VerboseDisplay for CliValidatorInfoVec {}
|
||||
|
||||
impl fmt::Display for CliValidatorInfoVec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.0.is_empty() {
|
||||
@@ -853,10 +1010,13 @@ pub struct CliValidatorInfo {
|
||||
pub info: Map<String, Value>,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliValidatorInfo {}
|
||||
impl VerboseDisplay for CliValidatorInfo {}
|
||||
|
||||
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)?;
|
||||
writeln_name_value(f, "Validator Identity:", &self.identity_pubkey)?;
|
||||
writeln_name_value(f, " Info Address:", &self.info_pubkey)?;
|
||||
for (key, value) in self.info.iter() {
|
||||
writeln_name_value(
|
||||
f,
|
||||
@@ -884,8 +1044,13 @@ pub struct CliVoteAccount {
|
||||
pub epoch_voting_history: Vec<CliEpochVotingHistory>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub epoch_rewards: Option<Vec<CliEpochReward>>,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliVoteAccount {}
|
||||
impl VerboseDisplay for CliVoteAccount {}
|
||||
|
||||
impl fmt::Display for CliVoteAccount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
@@ -906,25 +1071,14 @@ impl fmt::Display for CliVoteAccount {
|
||||
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,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
writeln!(
|
||||
f,
|
||||
"Recent Timestamp: {} from slot {}",
|
||||
unix_timestamp_to_string(self.recent_timestamp.timestamp),
|
||||
self.recent_timestamp.slot
|
||||
)?;
|
||||
show_votes_and_credits(f, &self.votes, &self.epoch_voting_history)?;
|
||||
show_epoch_rewards(f, &self.epoch_rewards)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -935,6 +1089,9 @@ pub struct CliAuthorizedVoters {
|
||||
authorized_voters: BTreeMap<Epoch, String>,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliAuthorizedVoters {}
|
||||
impl VerboseDisplay for CliAuthorizedVoters {}
|
||||
|
||||
impl fmt::Display for CliAuthorizedVoters {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self.authorized_voters)
|
||||
@@ -959,6 +1116,8 @@ pub struct CliEpochVotingHistory {
|
||||
pub epoch: Epoch,
|
||||
pub slots_in_epoch: u64,
|
||||
pub credits_earned: u64,
|
||||
pub credits: u64,
|
||||
pub prev_credits: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -984,19 +1143,25 @@ pub struct CliBlockTime {
|
||||
pub timestamp: UnixTimestamp,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliBlockTime {}
|
||||
impl VerboseDisplay for CliBlockTime {}
|
||||
|
||||
fn unix_timestamp_to_string(unix_timestamp: UnixTimestamp) -> String {
|
||||
format!(
|
||||
"{} (UnixTimestamp: {})",
|
||||
match NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) {
|
||||
Some(ndt) =>
|
||||
DateTime::<Utc>::from_utc(ndt, Utc).to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
None => "unknown".to_string(),
|
||||
},
|
||||
unix_timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
),
|
||||
)
|
||||
writeln_name_value(f, "Date:", &unix_timestamp_to_string(self.timestamp))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1012,6 +1177,9 @@ pub struct CliSignOnlyData {
|
||||
pub bad_sig: Vec<String>,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliSignOnlyData {}
|
||||
impl VerboseDisplay for CliSignOnlyData {}
|
||||
|
||||
impl fmt::Display for CliSignOnlyData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
@@ -1044,6 +1212,9 @@ pub struct CliSignature {
|
||||
pub signature: String,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliSignature {}
|
||||
impl VerboseDisplay for CliSignature {}
|
||||
|
||||
impl fmt::Display for CliSignature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
@@ -1058,6 +1229,9 @@ pub struct CliAccountBalances {
|
||||
pub accounts: Vec<RpcAccountBalance>,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliAccountBalances {}
|
||||
impl VerboseDisplay for CliAccountBalances {}
|
||||
|
||||
impl fmt::Display for CliAccountBalances {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
@@ -1100,6 +1274,9 @@ impl From<RpcSupply> for CliSupply {
|
||||
}
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliSupply {}
|
||||
impl VerboseDisplay for CliSupply {}
|
||||
|
||||
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)))?;
|
||||
@@ -1133,6 +1310,9 @@ pub struct CliFees {
|
||||
pub last_valid_slot: Slot,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliFees {}
|
||||
impl VerboseDisplay for CliFees {}
|
||||
|
||||
impl fmt::Display for CliFees {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_name_value(f, "Blockhash:", &self.blockhash)?;
|
||||
@@ -1145,3 +1325,240 @@ impl fmt::Display for CliFees {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliTokenAccount {
|
||||
pub address: String,
|
||||
#[serde(flatten)]
|
||||
pub token_account: UiTokenAccount,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliTokenAccount {}
|
||||
impl VerboseDisplay for CliTokenAccount {}
|
||||
|
||||
impl fmt::Display for CliTokenAccount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Address:", &self.address)?;
|
||||
let account = &self.token_account;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Balance:",
|
||||
&account.token_amount.real_number_string_trimmed(),
|
||||
)?;
|
||||
let mint = format!(
|
||||
"{}{}",
|
||||
account.mint,
|
||||
if account.is_native { " (native)" } else { "" }
|
||||
);
|
||||
writeln_name_value(f, "Mint:", &mint)?;
|
||||
writeln_name_value(f, "Owner:", &account.owner)?;
|
||||
writeln_name_value(f, "State:", &format!("{:?}", account.state))?;
|
||||
if let Some(delegate) = &account.delegate {
|
||||
writeln!(f, "Delegation:")?;
|
||||
writeln_name_value(f, " Delegate:", delegate)?;
|
||||
let allowance = account.delegated_amount.as_ref().unwrap();
|
||||
writeln_name_value(f, " Allowance:", &allowance.real_number_string_trimmed())?;
|
||||
}
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Close authority:",
|
||||
&account.close_authority.as_ref().unwrap_or(&String::new()),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn return_signers(
|
||||
tx: &Transaction,
|
||||
output_format: &OutputFormat,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let verify_results = tx.verify_with_results();
|
||||
let mut signers = Vec::new();
|
||||
let mut absent = Vec::new();
|
||||
let mut bad_sig = Vec::new();
|
||||
tx.signatures
|
||||
.iter()
|
||||
.zip(tx.message.account_keys.iter())
|
||||
.zip(verify_results.into_iter())
|
||||
.for_each(|((sig, key), res)| {
|
||||
if res {
|
||||
signers.push(format!("{}={}", key, sig))
|
||||
} else if *sig == Signature::default() {
|
||||
absent.push(key.to_string());
|
||||
} else {
|
||||
bad_sig.push(key.to_string());
|
||||
}
|
||||
});
|
||||
|
||||
let cli_command = CliSignOnlyData {
|
||||
blockhash: tx.message.recent_blockhash.to_string(),
|
||||
signers,
|
||||
absent,
|
||||
bad_sig,
|
||||
};
|
||||
|
||||
Ok(output_format.formatted_string(&cli_command))
|
||||
}
|
||||
|
||||
pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
|
||||
let object: Value = serde_json::from_str(&reply).unwrap();
|
||||
let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
|
||||
let blockhash = blockhash_str.parse::<Hash>().unwrap();
|
||||
let mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
|
||||
let signer_strings = object.get("signers");
|
||||
if let Some(sig_strings) = signer_strings {
|
||||
present_signers = sig_strings
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|signer_string| {
|
||||
let mut signer = signer_string.as_str().unwrap().split('=');
|
||||
let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
|
||||
let sig = Signature::from_str(signer.next().unwrap()).unwrap();
|
||||
(key, sig)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
let mut absent_signers: Vec<Pubkey> = Vec::new();
|
||||
let signer_strings = object.get("absent");
|
||||
if let Some(sig_strings) = signer_strings {
|
||||
absent_signers = sig_strings
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
let mut bad_signers: Vec<Pubkey> = Vec::new();
|
||||
let signer_strings = object.get("badSig");
|
||||
if let Some(sig_strings) = signer_strings {
|
||||
bad_signers = sig_strings
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
SignOnly {
|
||||
blockhash,
|
||||
present_signers,
|
||||
absent_signers,
|
||||
bad_signers,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_sdk::{
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, NullSigner, Signature, Signer, SignerError},
|
||||
system_instruction,
|
||||
transaction::Transaction,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_return_signers() {
|
||||
struct BadSigner {
|
||||
pubkey: Pubkey,
|
||||
}
|
||||
|
||||
impl BadSigner {
|
||||
pub fn new(pubkey: Pubkey) -> Self {
|
||||
Self { pubkey }
|
||||
}
|
||||
}
|
||||
|
||||
impl Signer for BadSigner {
|
||||
fn try_pubkey(&self) -> Result<Pubkey, SignerError> {
|
||||
Ok(self.pubkey)
|
||||
}
|
||||
|
||||
fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, SignerError> {
|
||||
Ok(Signature::new(&[1u8; 64]))
|
||||
}
|
||||
}
|
||||
|
||||
let present: Box<dyn Signer> = Box::new(keypair_from_seed(&[2u8; 32]).unwrap());
|
||||
let absent: Box<dyn Signer> = Box::new(NullSigner::new(&Pubkey::new(&[3u8; 32])));
|
||||
let bad: Box<dyn Signer> = Box::new(BadSigner::new(Pubkey::new(&[4u8; 32])));
|
||||
let to = Pubkey::new(&[5u8; 32]);
|
||||
let nonce = Pubkey::new(&[6u8; 32]);
|
||||
let from = present.pubkey();
|
||||
let fee_payer = absent.pubkey();
|
||||
let nonce_auth = bad.pubkey();
|
||||
let mut tx = Transaction::new_unsigned(Message::new_with_nonce(
|
||||
vec![system_instruction::transfer(&from, &to, 42)],
|
||||
Some(&fee_payer),
|
||||
&nonce,
|
||||
&nonce_auth,
|
||||
));
|
||||
|
||||
let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()];
|
||||
let blockhash = Hash::new(&[7u8; 32]);
|
||||
tx.try_partial_sign(&signers, blockhash).unwrap();
|
||||
let res = return_signers(&tx, &OutputFormat::JsonCompact).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&res);
|
||||
assert_eq!(sign_only.blockhash, blockhash);
|
||||
assert_eq!(sign_only.present_signers[0].0, present.pubkey());
|
||||
assert_eq!(sign_only.absent_signers[0], absent.pubkey());
|
||||
assert_eq!(sign_only.bad_signers[0], bad.pubkey());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verbose_quiet_output_formats() {
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct FallbackToDisplay {}
|
||||
impl std::fmt::Display for FallbackToDisplay {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "display")
|
||||
}
|
||||
}
|
||||
impl QuietDisplay for FallbackToDisplay {}
|
||||
impl VerboseDisplay for FallbackToDisplay {}
|
||||
|
||||
let f = FallbackToDisplay {};
|
||||
assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
|
||||
assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "display");
|
||||
assert_eq!(
|
||||
&OutputFormat::DisplayVerbose.formatted_string(&f),
|
||||
"display"
|
||||
);
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct DiscreteVerbosityDisplay {}
|
||||
impl std::fmt::Display for DiscreteVerbosityDisplay {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "display")
|
||||
}
|
||||
}
|
||||
impl QuietDisplay for DiscreteVerbosityDisplay {
|
||||
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
|
||||
write!(w, "quiet")
|
||||
}
|
||||
}
|
||||
impl VerboseDisplay for DiscreteVerbosityDisplay {
|
||||
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
|
||||
write!(w, "verbose")
|
||||
}
|
||||
}
|
||||
|
||||
let f = DiscreteVerbosityDisplay {};
|
||||
assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
|
||||
assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "quiet");
|
||||
assert_eq!(
|
||||
&OutputFormat::DisplayVerbose.formatted_string(&f),
|
||||
"verbose"
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,4 +1,3 @@
|
||||
use crate::cli::SettingType;
|
||||
use console::style;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use solana_sdk::{
|
||||
@@ -8,9 +7,27 @@ use solana_sdk::{
|
||||
use solana_transaction_status::UiTransactionStatusMeta;
|
||||
use std::{collections::HashMap, fmt, io};
|
||||
|
||||
pub fn build_balance_message(lamports: u64, use_lamports_unit: bool, show_unit: bool) -> String {
|
||||
if use_lamports_unit {
|
||||
let ess = if lamports == 1 { "" } else { "s" };
|
||||
let unit = if show_unit {
|
||||
format!(" lamport{}", ess)
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
format!("{:?}{}", lamports, unit)
|
||||
} else {
|
||||
let sol = lamports_to_sol(lamports);
|
||||
let sol_str = format!("{:.9}", sol);
|
||||
let pretty_sol = sol_str.trim_end_matches('0').trim_end_matches('.');
|
||||
let unit = if show_unit { " SOL" } else { "" };
|
||||
format!("{}{}", pretty_sol, unit)
|
||||
}
|
||||
}
|
||||
|
||||
// Pretty print a "name value"
|
||||
pub fn println_name_value(name: &str, value: &str) {
|
||||
let styled_value = if value == "" {
|
||||
let styled_value = if value.is_empty() {
|
||||
style("(not set)").italic()
|
||||
} else {
|
||||
style(value)
|
||||
@@ -19,7 +36,7 @@ pub fn println_name_value(name: &str, value: &str) {
|
||||
}
|
||||
|
||||
pub fn writeln_name_value(f: &mut fmt::Formatter, name: &str, value: &str) -> fmt::Result {
|
||||
let styled_value = if value == "" {
|
||||
let styled_value = if value.is_empty() {
|
||||
style("(not set)").italic()
|
||||
} else {
|
||||
style(value)
|
||||
@@ -40,21 +57,6 @@ pub fn format_labeled_address(pubkey: &str, address_labels: &HashMap<String, Str
|
||||
}
|
||||
}
|
||||
|
||||
pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
|
||||
let description = match setting_type {
|
||||
SettingType::Explicit => "",
|
||||
SettingType::Computed => "(computed)",
|
||||
SettingType::SystemDefault => "(default)",
|
||||
};
|
||||
|
||||
println!(
|
||||
"{} {} {}",
|
||||
style(name).bold(),
|
||||
style(value),
|
||||
style(description).italic(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn println_signers(
|
||||
blockhash: &Hash,
|
||||
signers: &[String],
|
||||
@@ -162,7 +164,7 @@ pub fn write_transaction<W: io::Write>(
|
||||
)?;
|
||||
writeln!(
|
||||
w,
|
||||
"{} Fee: {} SOL",
|
||||
"{} Fee: ◎{}",
|
||||
prefix,
|
||||
lamports_to_sol(transaction_status.fee)
|
||||
)?;
|
||||
@@ -179,7 +181,7 @@ pub fn write_transaction<W: io::Write>(
|
||||
if pre == post {
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {} balance: {} SOL",
|
||||
"{} Account {} balance: ◎{}",
|
||||
prefix,
|
||||
i,
|
||||
lamports_to_sol(*pre)
|
||||
@@ -187,7 +189,7 @@ pub fn write_transaction<W: io::Write>(
|
||||
} else {
|
||||
writeln!(
|
||||
w,
|
||||
"{} Account {} balance: {} SOL -> {} SOL",
|
||||
"{} Account {} balance: ◎{} -> ◎{}",
|
||||
prefix,
|
||||
i,
|
||||
lamports_to_sol(*pre),
|
||||
@@ -195,6 +197,15 @@ pub fn write_transaction<W: io::Write>(
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(log_messages) = &transaction_status.log_messages {
|
||||
if !log_messages.is_empty() {
|
||||
writeln!(w, "{}Log Messages:", prefix,)?;
|
||||
for log_message in log_messages {
|
||||
writeln!(w, "{} {}", prefix, log_message,)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeln!(w, "{}Status: Unavailable", prefix)?;
|
||||
}
|
15
cli-output/src/lib.rs
Normal file
15
cli-output/src/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
mod cli_output;
|
||||
pub mod display;
|
||||
pub use cli_output::*;
|
||||
|
||||
pub trait QuietDisplay: std::fmt::Display {
|
||||
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
|
||||
write!(w, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VerboseDisplay: std::fmt::Display {
|
||||
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
|
||||
write!(w, "{}", self)
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.3.0"
|
||||
version = "1.5.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -16,40 +16,40 @@ clap = "2.33.1"
|
||||
criterion-stats = "0.3.0"
|
||||
ctrlc = { version = "3.1.5", features = ["termination"] }
|
||||
console = "0.11.3"
|
||||
dirs = "2.0.2"
|
||||
log = "0.4.8"
|
||||
dirs-next = "2.0.0"
|
||||
log = "0.4.11"
|
||||
Inflector = "0.11.4"
|
||||
indicatif = "0.15.0"
|
||||
humantime = "2.0.1"
|
||||
num-traits = "0.2"
|
||||
pretty-hex = "0.1.1"
|
||||
reqwest = { version = "0.10.6", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
pretty-hex = "0.2.1"
|
||||
reqwest = { version = "0.10.8", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serde = "1.0.112"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.3.0" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.3.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.3.0" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.3.0" }
|
||||
solana-client = { path = "../client", version = "1.3.0" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.3.0" }
|
||||
solana-faucet = { path = "../faucet", version = "1.3.0" }
|
||||
solana-logger = { path = "../logger", version = "1.3.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.3.0" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.3.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.3.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.3.0" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.3.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.3.0" }
|
||||
solana-version = { path = "../version", version = "1.3.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.3.0" }
|
||||
thiserror = "1.0.20"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.5.2" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.5.2" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.2" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.5.2" }
|
||||
solana-cli-output = { path = "../cli-output", version = "1.5.2" }
|
||||
solana-client = { path = "../client", version = "1.5.2" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.5.2" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.2" }
|
||||
solana-logger = { path = "../logger", version = "1.5.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.2" }
|
||||
solana_rbpf = "=0.2.2"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.5.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.2" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.2" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.5.2" }
|
||||
solana-version = { path = "../version", version = "1.5.2" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.2" }
|
||||
thiserror = "1.0.21"
|
||||
tiny-bip39 = "0.7.0"
|
||||
url = "2.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-core = { path = "../core", version = "1.3.0" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.3.0" }
|
||||
solana-core = { path = "../core", version = "1.5.2" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[[bin]]
|
||||
|
@@ -54,12 +54,42 @@ pub fn check_account_for_multiple_fees_with_commitment(
|
||||
fee_calculator: &FeeCalculator,
|
||||
messages: &[&Message],
|
||||
commitment: CommitmentConfig,
|
||||
) -> Result<(), CliError> {
|
||||
check_account_for_spend_multiple_fees_with_commitment(
|
||||
rpc_client,
|
||||
account_pubkey,
|
||||
0,
|
||||
fee_calculator,
|
||||
messages,
|
||||
commitment,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn check_account_for_spend_multiple_fees_with_commitment(
|
||||
rpc_client: &RpcClient,
|
||||
account_pubkey: &Pubkey,
|
||||
balance: u64,
|
||||
fee_calculator: &FeeCalculator,
|
||||
messages: &[&Message],
|
||||
commitment: CommitmentConfig,
|
||||
) -> Result<(), CliError> {
|
||||
let fee = calculate_fee(fee_calculator, messages);
|
||||
if !check_account_for_balance_with_commitment(rpc_client, account_pubkey, fee, commitment)
|
||||
.map_err(Into::<ClientError>::into)?
|
||||
if !check_account_for_balance_with_commitment(
|
||||
rpc_client,
|
||||
account_pubkey,
|
||||
balance + fee,
|
||||
commitment,
|
||||
)
|
||||
.map_err(Into::<ClientError>::into)?
|
||||
{
|
||||
return Err(CliError::InsufficientFundsForFee(lamports_to_sol(fee)));
|
||||
if balance > 0 {
|
||||
return Err(CliError::InsufficientFundsForSpendAndFee(
|
||||
lamports_to_sol(balance),
|
||||
lamports_to_sol(fee),
|
||||
));
|
||||
} else {
|
||||
return Err(CliError::InsufficientFundsForFee(lamports_to_sol(fee)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -131,7 +161,7 @@ mod tests {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: json!(account_balance),
|
||||
});
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let pubkey = solana_sdk::pubkey::new_rand();
|
||||
let fee_calculator = FeeCalculator::new(1);
|
||||
|
||||
let pubkey0 = Pubkey::new(&[0; 32]);
|
||||
@@ -191,7 +221,7 @@ mod tests {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: json!(account_balance),
|
||||
});
|
||||
let pubkey = Pubkey::new_rand();
|
||||
let pubkey = solana_sdk::pubkey::new_rand();
|
||||
|
||||
let mut mocks = HashMap::new();
|
||||
mocks.insert(RpcRequest::GetBalance, account_balance_response);
|
||||
@@ -237,9 +267,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_check_unique_pubkeys() {
|
||||
let pubkey0 = Pubkey::new_rand();
|
||||
let pubkey0 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey_clone = pubkey0;
|
||||
let pubkey1 = Pubkey::new_rand();
|
||||
let pubkey1 = solana_sdk::pubkey::new_rand();
|
||||
|
||||
check_unique_pubkeys((&pubkey0, "foo".to_string()), (&pubkey1, "bar".to_string()))
|
||||
.expect("unexpected result");
|
||||
|
1870
cli/src/cli.rs
1870
cli/src/cli.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
379
cli/src/feature.rs
Normal file
379
cli/src/feature.rs
Normal file
@@ -0,0 +1,379 @@
|
||||
use crate::{
|
||||
cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
||||
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
||||
};
|
||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use console::style;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_clap_utils::{input_parsers::*, input_validators::*, keypair::*};
|
||||
use solana_cli_output::{QuietDisplay, VerboseDisplay};
|
||||
use solana_client::{client_error::ClientError, rpc_client::RpcClient};
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
clock::Slot,
|
||||
feature::{self, Feature},
|
||||
feature_set::FEATURE_NAMES,
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
transaction::Transaction,
|
||||
};
|
||||
use std::{collections::HashMap, fmt, sync::Arc};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum FeatureCliCommand {
|
||||
Status { features: Vec<Pubkey> },
|
||||
Activate { feature: Pubkey },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", tag = "status", content = "sinceSlot")]
|
||||
pub enum CliFeatureStatus {
|
||||
Inactive,
|
||||
Pending,
|
||||
Active(Slot),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliFeature {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
#[serde(flatten)]
|
||||
pub status: CliFeatureStatus,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliFeatures {
|
||||
pub features: Vec<CliFeature>,
|
||||
pub feature_activation_allowed: bool,
|
||||
#[serde(skip)]
|
||||
pub inactive: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for CliFeatures {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.features.len() > 1 {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
"{:<44} {:<40} {}",
|
||||
"Feature", "Description", "Status"
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
}
|
||||
for feature in &self.features {
|
||||
writeln!(
|
||||
f,
|
||||
"{:<44} {:<40} {}",
|
||||
feature.id,
|
||||
feature.description,
|
||||
match feature.status {
|
||||
CliFeatureStatus::Inactive => style("inactive".to_string()).red(),
|
||||
CliFeatureStatus::Pending => style("activation pending".to_string()).yellow(),
|
||||
CliFeatureStatus::Active(activation_slot) =>
|
||||
style(format!("active since slot {}", activation_slot)).green(),
|
||||
}
|
||||
)?;
|
||||
}
|
||||
if self.inactive && !self.feature_activation_allowed {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style("\nFeature activation is not allowed at this time")
|
||||
.bold()
|
||||
.red()
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliFeatures {}
|
||||
impl VerboseDisplay for CliFeatures {}
|
||||
|
||||
pub trait FeatureSubCommands {
|
||||
fn feature_subcommands(self) -> Self;
|
||||
}
|
||||
|
||||
impl FeatureSubCommands for App<'_, '_> {
|
||||
fn feature_subcommands(self) -> Self {
|
||||
self.subcommand(
|
||||
SubCommand::with_name("feature")
|
||||
.about("Runtime feature management")
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
.subcommand(
|
||||
SubCommand::with_name("status")
|
||||
.about("Query runtime feature status")
|
||||
.arg(
|
||||
Arg::with_name("features")
|
||||
.value_name("ADDRESS")
|
||||
.validator(is_valid_pubkey)
|
||||
.index(1)
|
||||
.multiple(true)
|
||||
.help("Feature status to query [default: all known features]"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("activate")
|
||||
.about("Activate a runtime feature")
|
||||
.arg(
|
||||
Arg::with_name("feature")
|
||||
.value_name("FEATURE_KEYPAIR")
|
||||
.validator(is_valid_signer)
|
||||
.index(1)
|
||||
.required(true)
|
||||
.help("The signer for the feature to activate"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn known_feature(feature: &Pubkey) -> Result<(), CliError> {
|
||||
if FEATURE_NAMES.contains_key(feature) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CliError::BadParameter(format!(
|
||||
"Unknown feature: {}",
|
||||
feature
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_feature_subcommand(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer: &DefaultSigner,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let response = match matches.subcommand() {
|
||||
("activate", Some(matches)) => {
|
||||
let (feature_signer, feature) = signer_of(matches, "feature", wallet_manager)?;
|
||||
let mut signers = vec![default_signer.signer_from_path(matches, wallet_manager)?];
|
||||
signers.push(feature_signer.unwrap());
|
||||
let feature = feature.unwrap();
|
||||
|
||||
known_feature(&feature)?;
|
||||
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Feature(FeatureCliCommand::Activate { feature }),
|
||||
signers,
|
||||
}
|
||||
}
|
||||
("status", Some(matches)) => {
|
||||
let mut features = if let Some(features) = pubkeys_of(matches, "features") {
|
||||
for feature in &features {
|
||||
known_feature(feature)?;
|
||||
}
|
||||
features
|
||||
} else {
|
||||
FEATURE_NAMES.keys().cloned().collect()
|
||||
};
|
||||
features.sort();
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Feature(FeatureCliCommand::Status { features }),
|
||||
signers: vec![],
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn process_feature_subcommand(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
feature_subcommand: &FeatureCliCommand,
|
||||
) -> ProcessResult {
|
||||
match feature_subcommand {
|
||||
FeatureCliCommand::Status { features } => process_status(rpc_client, config, features),
|
||||
FeatureCliCommand::Activate { feature } => process_activate(rpc_client, config, *feature),
|
||||
}
|
||||
}
|
||||
|
||||
fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, u64>, ClientError> {
|
||||
// Validator identity -> feature set
|
||||
let feature_set_map = rpc_client
|
||||
.get_cluster_nodes()?
|
||||
.into_iter()
|
||||
.map(|contact_info| (contact_info.pubkey, contact_info.feature_set))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let vote_accounts = rpc_client.get_vote_accounts()?;
|
||||
|
||||
let total_active_stake: u64 = vote_accounts
|
||||
.current
|
||||
.iter()
|
||||
.chain(vote_accounts.delinquent.iter())
|
||||
.map(|vote_account| vote_account.activated_stake)
|
||||
.sum();
|
||||
|
||||
// Sum all active stake by feature set
|
||||
let mut active_stake_by_feature_set = HashMap::new();
|
||||
for vote_account in vote_accounts.current {
|
||||
if let Some(Some(feature_set)) = feature_set_map.get(&vote_account.node_pubkey) {
|
||||
*active_stake_by_feature_set.entry(*feature_set).or_default() +=
|
||||
vote_account.activated_stake;
|
||||
} else {
|
||||
*active_stake_by_feature_set
|
||||
.entry(0 /* "unknown" */)
|
||||
.or_default() += vote_account.activated_stake;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert active stake to a percentage so the caller doesn't need `total_active_stake`
|
||||
for (_, val) in active_stake_by_feature_set.iter_mut() {
|
||||
*val = *val * 100 / total_active_stake;
|
||||
}
|
||||
Ok(active_stake_by_feature_set)
|
||||
}
|
||||
|
||||
// Feature activation is only allowed when 95% of the active stake is on the current feature set
|
||||
fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<bool, ClientError> {
|
||||
let my_feature_set = solana_version::Version::default().feature_set;
|
||||
|
||||
let active_stake_by_feature_set = active_stake_by_feature_set(rpc_client)?;
|
||||
|
||||
let feature_activation_allowed = active_stake_by_feature_set
|
||||
.get(&my_feature_set)
|
||||
.map(|percentage| *percentage >= 95)
|
||||
.unwrap_or(false);
|
||||
|
||||
if !feature_activation_allowed && !quiet {
|
||||
if active_stake_by_feature_set.get(&my_feature_set).is_none() {
|
||||
println!(
|
||||
"{}",
|
||||
style("To activate features the tool and cluster feature sets must match, select a tool version that matches the cluster")
|
||||
.bold());
|
||||
} else {
|
||||
println!(
|
||||
"{}",
|
||||
style("To activate features the stake must be >= 95%").bold()
|
||||
);
|
||||
}
|
||||
println!(
|
||||
"{}",
|
||||
style(format!("Tool Feture Set: {}", my_feature_set)).bold()
|
||||
);
|
||||
println!("{}", style("Cluster Feature Sets and Stakes:").bold());
|
||||
for (feature_set, percentage) in active_stake_by_feature_set.iter() {
|
||||
if *feature_set == 0 {
|
||||
println!("unknown - {}%", percentage);
|
||||
} else {
|
||||
println!(
|
||||
"{} - {}% {}",
|
||||
feature_set,
|
||||
percentage,
|
||||
if *feature_set == my_feature_set {
|
||||
" <-- me"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
Ok(feature_activation_allowed)
|
||||
}
|
||||
|
||||
fn process_status(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
feature_ids: &[Pubkey],
|
||||
) -> ProcessResult {
|
||||
let mut features: Vec<CliFeature> = vec![];
|
||||
let mut inactive = false;
|
||||
for (i, account) in rpc_client
|
||||
.get_multiple_accounts(feature_ids)?
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let feature_id = &feature_ids[i];
|
||||
let feature_name = FEATURE_NAMES.get(feature_id).unwrap();
|
||||
if let Some(account) = account {
|
||||
if let Some(feature) = feature::from_account(&account) {
|
||||
let feature_status = match feature.activated_at {
|
||||
None => CliFeatureStatus::Pending,
|
||||
Some(activation_slot) => CliFeatureStatus::Active(activation_slot),
|
||||
};
|
||||
features.push(CliFeature {
|
||||
id: feature_id.to_string(),
|
||||
description: feature_name.to_string(),
|
||||
status: feature_status,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
inactive = true;
|
||||
features.push(CliFeature {
|
||||
id: feature_id.to_string(),
|
||||
description: feature_name.to_string(),
|
||||
status: CliFeatureStatus::Inactive,
|
||||
});
|
||||
}
|
||||
|
||||
let feature_activation_allowed = feature_activation_allowed(rpc_client, features.len() <= 1)?;
|
||||
let feature_set = CliFeatures {
|
||||
features,
|
||||
feature_activation_allowed,
|
||||
inactive,
|
||||
};
|
||||
Ok(config.output_format.formatted_string(&feature_set))
|
||||
}
|
||||
|
||||
fn process_activate(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
feature_id: Pubkey,
|
||||
) -> ProcessResult {
|
||||
let account = rpc_client
|
||||
.get_multiple_accounts(&[feature_id])?
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
if let Some(account) = account {
|
||||
if feature::from_account(&account).is_some() {
|
||||
return Err(format!("{} has already been activated", feature_id).into());
|
||||
}
|
||||
}
|
||||
|
||||
if !feature_activation_allowed(rpc_client, false)? {
|
||||
return Err("Feature activation is not allowed at this time".into());
|
||||
}
|
||||
|
||||
let rent = rpc_client.get_minimum_balance_for_rent_exemption(Feature::size_of())?;
|
||||
|
||||
let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let (message, _) = resolve_spend_tx_and_check_account_balance(
|
||||
rpc_client,
|
||||
false,
|
||||
SpendAmount::Some(rent),
|
||||
&fee_calculator,
|
||||
&config.signers[0].pubkey(),
|
||||
|lamports| {
|
||||
Message::new(
|
||||
&feature::activate_with_lamports(
|
||||
&feature_id,
|
||||
&config.signers[0].pubkey(),
|
||||
lamports,
|
||||
),
|
||||
Some(&config.signers[0].pubkey()),
|
||||
)
|
||||
},
|
||||
config.commitment,
|
||||
)?;
|
||||
let mut transaction = Transaction::new_unsigned(message);
|
||||
transaction.try_sign(&config.signers, blockhash)?;
|
||||
|
||||
println!(
|
||||
"Activating {} ({})",
|
||||
FEATURE_NAMES.get(&feature_id).unwrap(),
|
||||
feature_id
|
||||
);
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&transaction)?;
|
||||
Ok("".to_string())
|
||||
}
|
89
cli/src/inflation.rs
Normal file
89
cli/src/inflation.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use crate::cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult};
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use console::style;
|
||||
use solana_clap_utils::keypair::*;
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum InflationCliCommand {
|
||||
Show,
|
||||
}
|
||||
|
||||
pub trait InflationSubCommands {
|
||||
fn inflation_subcommands(self) -> Self;
|
||||
}
|
||||
|
||||
impl InflationSubCommands for App<'_, '_> {
|
||||
fn inflation_subcommands(self) -> Self {
|
||||
self.subcommand(SubCommand::with_name("inflation").about("Show inflation information"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_inflation_subcommand(
|
||||
_matches: &ArgMatches<'_>,
|
||||
_default_signer: &DefaultSigner,
|
||||
_wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::Inflation(InflationCliCommand::Show),
|
||||
signers: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process_inflation_subcommand(
|
||||
rpc_client: &RpcClient,
|
||||
_config: &CliConfig,
|
||||
inflation_subcommand: &InflationCliCommand,
|
||||
) -> ProcessResult {
|
||||
assert_eq!(*inflation_subcommand, InflationCliCommand::Show);
|
||||
|
||||
let governor = rpc_client.get_inflation_governor()?;
|
||||
let current_inflation_rate = rpc_client.get_inflation_rate()?;
|
||||
|
||||
println!("{}", style("Inflation Governor:").bold());
|
||||
if (governor.initial - governor.terminal).abs() < f64::EPSILON {
|
||||
println!(
|
||||
"Fixed APR: {:>5.2}%",
|
||||
governor.terminal * 100.
|
||||
);
|
||||
} else {
|
||||
println!("Initial APR: {:>5.2}%", governor.initial * 100.);
|
||||
println!(
|
||||
"Terminal APR: {:>5.2}%",
|
||||
governor.terminal * 100.
|
||||
);
|
||||
println!("Rate reduction per year: {:>5.2}%", governor.taper * 100.);
|
||||
}
|
||||
if governor.foundation_term > 0. {
|
||||
println!("Foundation percentage: {:>5.2}%", governor.foundation);
|
||||
println!(
|
||||
"Foundation term: {:.1} years",
|
||||
governor.foundation_term
|
||||
);
|
||||
}
|
||||
|
||||
println!(
|
||||
"\n{}",
|
||||
style(format!(
|
||||
"Inflation for Epoch {}:",
|
||||
current_inflation_rate.epoch
|
||||
))
|
||||
.bold()
|
||||
);
|
||||
println!(
|
||||
"Total APR: {:>5.2}%",
|
||||
current_inflation_rate.total * 100.
|
||||
);
|
||||
println!(
|
||||
"Staking APR: {:>5.2}%",
|
||||
current_inflation_rate.validator * 100.
|
||||
);
|
||||
println!(
|
||||
"Foundation APR: {:>5.2}%",
|
||||
current_inflation_rate.foundation * 100.
|
||||
);
|
||||
|
||||
Ok("".to_string())
|
||||
}
|
@@ -18,16 +18,16 @@ macro_rules! pubkey {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod checks;
|
||||
pub mod cli;
|
||||
pub mod cli_output;
|
||||
pub mod cluster_query;
|
||||
pub mod display;
|
||||
pub mod feature;
|
||||
pub mod inflation;
|
||||
pub mod nonce;
|
||||
pub mod offline;
|
||||
pub mod program;
|
||||
pub mod send_tpu;
|
||||
pub mod spend_utils;
|
||||
pub mod stake;
|
||||
pub mod test_utils;
|
||||
|
@@ -5,22 +5,37 @@ use clap::{
|
||||
use console::style;
|
||||
|
||||
use solana_clap_utils::{
|
||||
commitment::COMMITMENT_ARG, input_parsers::commitment_of, input_validators::is_url,
|
||||
keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, DisplayError,
|
||||
commitment::COMMITMENT_ARG,
|
||||
input_parsers::commitment_of,
|
||||
input_validators::{is_url, is_url_or_moniker},
|
||||
keypair::{CliSigners, DefaultSigner, SKIP_SEED_PHRASE_VALIDATION_ARG},
|
||||
DisplayError,
|
||||
};
|
||||
use solana_cli::{
|
||||
cli::{
|
||||
app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners,
|
||||
DEFAULT_RPC_TIMEOUT_SECONDS,
|
||||
},
|
||||
cli_output::OutputFormat,
|
||||
display::{println_name_value, println_name_value_or},
|
||||
use solana_cli::cli::{
|
||||
app, parse_command, process_command, CliCommandInfo, CliConfig, SettingType,
|
||||
DEFAULT_RPC_TIMEOUT_SECONDS,
|
||||
};
|
||||
use solana_cli_config::{Config, CONFIG_FILE};
|
||||
use solana_cli_output::{display::println_name_value, OutputFormat};
|
||||
use solana_client::rpc_config::RpcSendTransactionConfig;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use std::{collections::HashMap, error, path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType) {
|
||||
let description = match setting_type {
|
||||
SettingType::Explicit => "",
|
||||
SettingType::Computed => "(computed)",
|
||||
SettingType::SystemDefault => "(default)",
|
||||
};
|
||||
|
||||
println!(
|
||||
"{} {} {}",
|
||||
style(name).bold(),
|
||||
style(value),
|
||||
style(description).italic(),
|
||||
);
|
||||
}
|
||||
|
||||
fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error>> {
|
||||
let parse_args = match matches.subcommand() {
|
||||
("config", Some(matches)) => {
|
||||
@@ -139,14 +154,21 @@ pub fn parse_args<'a>(
|
||||
matches.value_of("json_rpc_url").unwrap_or(""),
|
||||
&config.json_rpc_url,
|
||||
);
|
||||
let default_signer_arg_name = "keypair".to_string();
|
||||
let (_, default_signer_path) = CliConfig::compute_keypair_path_setting(
|
||||
matches.value_of("keypair").unwrap_or(""),
|
||||
matches.value_of(&default_signer_arg_name).unwrap_or(""),
|
||||
&config.keypair_path,
|
||||
);
|
||||
|
||||
let CliCommandInfo { command, signers } =
|
||||
parse_command(&matches, &default_signer_path, &mut wallet_manager)?;
|
||||
let default_signer = DefaultSigner {
|
||||
arg_name: default_signer_arg_name,
|
||||
path: default_signer_path.clone(),
|
||||
};
|
||||
|
||||
let CliCommandInfo { command, signers } =
|
||||
parse_command(&matches, &default_signer, &mut wallet_manager)?;
|
||||
|
||||
let verbose = matches.is_present("verbose");
|
||||
let output_format = matches
|
||||
.value_of("output_format")
|
||||
.map(|value| match value {
|
||||
@@ -154,13 +176,22 @@ pub fn parse_args<'a>(
|
||||
"json-compact" => OutputFormat::JsonCompact,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.unwrap_or(OutputFormat::Display);
|
||||
.unwrap_or(if verbose {
|
||||
OutputFormat::DisplayVerbose
|
||||
} else {
|
||||
OutputFormat::Display
|
||||
});
|
||||
|
||||
let commitment = matches
|
||||
.subcommand_name()
|
||||
.and_then(|name| matches.subcommand_matches(name))
|
||||
.and_then(|sub_matches| commitment_of(sub_matches, COMMITMENT_ARG.long))
|
||||
.unwrap_or_default();
|
||||
let commitment = {
|
||||
let mut sub_matches = matches;
|
||||
while let Some(subcommand_name) = sub_matches.subcommand_name() {
|
||||
sub_matches = sub_matches
|
||||
.subcommand_matches(subcommand_name)
|
||||
.expect("subcommand_matches");
|
||||
}
|
||||
commitment_of(sub_matches, COMMITMENT_ARG.long)
|
||||
}
|
||||
.unwrap_or_default();
|
||||
|
||||
let address_labels = if matches.is_present("no_address_labels") {
|
||||
HashMap::new()
|
||||
@@ -177,10 +208,13 @@ pub fn parse_args<'a>(
|
||||
keypair_path: default_signer_path,
|
||||
rpc_client: None,
|
||||
rpc_timeout,
|
||||
verbose: matches.is_present("verbose"),
|
||||
verbose,
|
||||
output_format,
|
||||
commitment,
|
||||
send_transaction_config: RpcSendTransactionConfig::default(),
|
||||
send_transaction_config: RpcSendTransactionConfig {
|
||||
preflight_commitment: Some(commitment.commitment),
|
||||
..RpcSendTransactionConfig::default()
|
||||
},
|
||||
address_labels,
|
||||
},
|
||||
signers,
|
||||
@@ -212,11 +246,14 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
Arg::with_name("json_rpc_url")
|
||||
.short("u")
|
||||
.long("url")
|
||||
.value_name("URL")
|
||||
.value_name("URL_OR_MONIKER")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
.validator(is_url)
|
||||
.help("JSON RPC URL for the solana cluster"),
|
||||
.validator(is_url_or_moniker)
|
||||
.help(
|
||||
"URL for Solana's JSON RPC or moniker (or their first letter): \
|
||||
[mainnet-beta, testnet, devnet, localhost]",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("websocket_url")
|
||||
|
223
cli/src/nonce.rs
223
cli/src/nonce.rs
@@ -1,29 +1,26 @@
|
||||
use crate::{
|
||||
checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
|
||||
cli::{
|
||||
generate_unique_signers, log_instruction_custom_error, CliCommand, CliCommandInfo,
|
||||
CliConfig, CliError, ProcessResult, SignerIndex,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
|
||||
ProcessResult,
|
||||
},
|
||||
cli_output::CliNonceAccount,
|
||||
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
||||
};
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{
|
||||
input_parsers::*, input_validators::*, offline::BLOCKHASH_ARG, ArgConstant,
|
||||
input_parsers::*,
|
||||
input_validators::*,
|
||||
keypair::{DefaultSigner, SignerIndex},
|
||||
nonce::*,
|
||||
};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_cli_output::CliNonceAccount;
|
||||
use solana_client::{nonce_utils::*, rpc_client::RpcClient};
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
account_utils::StateMut,
|
||||
commitment_config::CommitmentConfig,
|
||||
hash::Hash,
|
||||
message::Message,
|
||||
nonce::{
|
||||
self,
|
||||
state::{Data, Versions},
|
||||
State,
|
||||
},
|
||||
nonce::{self, State},
|
||||
pubkey::Pubkey,
|
||||
system_instruction::{
|
||||
advance_nonce_account, authorize_nonce_account, create_nonce_account,
|
||||
@@ -33,64 +30,11 @@ use solana_sdk::{
|
||||
transaction::Transaction,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
pub enum CliNonceError {
|
||||
#[error("invalid account owner")]
|
||||
InvalidAccountOwner,
|
||||
#[error("invalid account data")]
|
||||
InvalidAccountData,
|
||||
#[error("unexpected account data size")]
|
||||
UnexpectedDataSize,
|
||||
#[error("query hash does not match stored hash")]
|
||||
InvalidHash,
|
||||
#[error("query authority does not match account authority")]
|
||||
InvalidAuthority,
|
||||
#[error("invalid state for requested operation")]
|
||||
InvalidStateForOperation,
|
||||
#[error("client error: {0}")]
|
||||
Client(String),
|
||||
}
|
||||
|
||||
pub const NONCE_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "nonce",
|
||||
long: "nonce",
|
||||
help: "Provide the nonce account to use when creating a nonced \n\
|
||||
transaction. Nonced transactions are useful when a transaction \n\
|
||||
requires a lengthy signing process. Learn more about nonced \n\
|
||||
transactions at https://docs.solana.com/offline-signing/durable-nonce",
|
||||
};
|
||||
|
||||
pub const NONCE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "nonce_authority",
|
||||
long: "nonce-authority",
|
||||
help: "Provide the nonce authority keypair to use when signing a nonced transaction",
|
||||
};
|
||||
|
||||
pub trait NonceSubCommands {
|
||||
fn nonce_subcommands(self) -> Self;
|
||||
}
|
||||
|
||||
pub fn nonce_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(NONCE_ARG.name)
|
||||
.long(NONCE_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("PUBKEY")
|
||||
.requires(BLOCKHASH_ARG.name)
|
||||
.validator(is_valid_pubkey)
|
||||
.help(NONCE_ARG.help)
|
||||
}
|
||||
|
||||
pub fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(NONCE_AUTHORITY_ARG.name)
|
||||
.long(NONCE_AUTHORITY_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("KEYPAIR")
|
||||
.validator(is_valid_signer)
|
||||
.help(NONCE_AUTHORITY_ARG.help)
|
||||
}
|
||||
|
||||
impl NonceSubCommands for App<'_, '_> {
|
||||
fn nonce_subcommands(self) -> Self {
|
||||
self.subcommand(
|
||||
@@ -220,64 +164,9 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_account(
|
||||
rpc_client: &RpcClient,
|
||||
nonce_pubkey: &Pubkey,
|
||||
) -> Result<Account, CliNonceError> {
|
||||
get_account_with_commitment(rpc_client, nonce_pubkey, CommitmentConfig::default())
|
||||
}
|
||||
|
||||
pub fn get_account_with_commitment(
|
||||
rpc_client: &RpcClient,
|
||||
nonce_pubkey: &Pubkey,
|
||||
commitment: CommitmentConfig,
|
||||
) -> Result<Account, CliNonceError> {
|
||||
rpc_client
|
||||
.get_account_with_commitment(nonce_pubkey, commitment)
|
||||
.map_err(|e| CliNonceError::Client(format!("{}", e)))
|
||||
.and_then(|result| {
|
||||
result.value.ok_or_else(|| {
|
||||
CliNonceError::Client(format!("AccountNotFound: pubkey={}", nonce_pubkey))
|
||||
})
|
||||
})
|
||||
.and_then(|a| match account_identity_ok(&a) {
|
||||
Ok(()) => Ok(a),
|
||||
Err(e) => Err(e),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn account_identity_ok(account: &Account) -> Result<(), CliNonceError> {
|
||||
if account.owner != system_program::id() {
|
||||
Err(CliNonceError::InvalidAccountOwner)
|
||||
} else if account.data.is_empty() {
|
||||
Err(CliNonceError::UnexpectedDataSize)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_from_account(account: &Account) -> Result<State, CliNonceError> {
|
||||
account_identity_ok(account)?;
|
||||
StateMut::<Versions>::state(account)
|
||||
.map_err(|_| CliNonceError::InvalidAccountData)
|
||||
.map(|v| v.convert_to_current())
|
||||
}
|
||||
|
||||
pub fn data_from_account(account: &Account) -> Result<Data, CliNonceError> {
|
||||
account_identity_ok(account)?;
|
||||
state_from_account(account).and_then(|ref s| data_from_state(s).map(|d| d.clone()))
|
||||
}
|
||||
|
||||
pub fn data_from_state(state: &State) -> Result<&Data, CliNonceError> {
|
||||
match state {
|
||||
State::Uninitialized => Err(CliNonceError::InvalidStateForOperation),
|
||||
State::Initialized(data) => Ok(data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_authorize_nonce_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
default_signer: &DefaultSigner,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -286,10 +175,9 @@ pub fn parse_authorize_nonce_account(
|
||||
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
|
||||
let payer_provided = None;
|
||||
let signer_info = generate_unique_signers(
|
||||
let signer_info = default_signer.generate_unique_signers(
|
||||
vec![payer_provided, nonce_authority],
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
)?;
|
||||
|
||||
@@ -305,7 +193,7 @@ pub fn parse_authorize_nonce_account(
|
||||
|
||||
pub fn parse_nonce_create_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
default_signer: &DefaultSigner,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let (nonce_account, nonce_account_pubkey) =
|
||||
@@ -315,10 +203,9 @@ pub fn parse_nonce_create_account(
|
||||
let nonce_authority = pubkey_of_signer(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
|
||||
let payer_provided = None;
|
||||
let signer_info = generate_unique_signers(
|
||||
let signer_info = default_signer.generate_unique_signers(
|
||||
vec![payer_provided, nonce_account],
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
)?;
|
||||
|
||||
@@ -348,7 +235,7 @@ pub fn parse_get_nonce(
|
||||
|
||||
pub fn parse_new_nonce(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
default_signer: &DefaultSigner,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -356,10 +243,9 @@ pub fn parse_new_nonce(
|
||||
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
|
||||
let payer_provided = None;
|
||||
let signer_info = generate_unique_signers(
|
||||
let signer_info = default_signer.generate_unique_signers(
|
||||
vec![payer_provided, nonce_authority],
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
)?;
|
||||
|
||||
@@ -391,7 +277,7 @@ pub fn parse_show_nonce_account(
|
||||
|
||||
pub fn parse_withdraw_from_nonce_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
default_signer: &DefaultSigner,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
@@ -402,10 +288,9 @@ pub fn parse_withdraw_from_nonce_account(
|
||||
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
|
||||
let payer_provided = None;
|
||||
let signer_info = generate_unique_signers(
|
||||
let signer_info = default_signer.generate_unique_signers(
|
||||
vec![payer_provided, nonce_authority],
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
)?;
|
||||
|
||||
@@ -429,14 +314,14 @@ pub fn check_nonce_account(
|
||||
match state_from_account(nonce_account)? {
|
||||
State::Initialized(ref data) => {
|
||||
if &data.blockhash != nonce_hash {
|
||||
Err(CliNonceError::InvalidHash.into())
|
||||
Err(Error::InvalidHash.into())
|
||||
} else if nonce_authority != &data.authority {
|
||||
Err(CliNonceError::InvalidAuthority.into())
|
||||
Err(Error::InvalidAuthority.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
State::Uninitialized => Err(CliNonceError::InvalidStateForOperation.into()),
|
||||
State::Uninitialized => Err(Error::InvalidStateForOperation.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,9 +576,11 @@ mod tests {
|
||||
use crate::cli::{app, parse_command};
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
account_utils::StateMut,
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::hash,
|
||||
nonce::{self, State},
|
||||
nonce::{self, state::Versions, State},
|
||||
nonce_account,
|
||||
signature::{read_keypair_file, write_keypair, Keypair, Signer},
|
||||
system_program,
|
||||
};
|
||||
@@ -710,6 +597,10 @@ mod tests {
|
||||
let default_keypair = Keypair::new();
|
||||
let (default_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
let default_signer = DefaultSigner {
|
||||
path: default_keypair_file.clone(),
|
||||
arg_name: String::new(),
|
||||
};
|
||||
let (keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let nonce_account_keypair = Keypair::new();
|
||||
write_keypair(&nonce_account_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
@@ -728,12 +619,7 @@ mod tests {
|
||||
&Pubkey::default().to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(
|
||||
&test_authorize_nonce_account,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::AuthorizeNonceAccount {
|
||||
nonce_account: nonce_account_pubkey,
|
||||
@@ -754,12 +640,7 @@ mod tests {
|
||||
&authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(
|
||||
&test_authorize_nonce_account,
|
||||
&default_keypair_file,
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::AuthorizeNonceAccount {
|
||||
nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
|
||||
@@ -781,7 +662,7 @@ mod tests {
|
||||
"50",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_nonce_account, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateNonceAccount {
|
||||
nonce_account: 1,
|
||||
@@ -806,7 +687,7 @@ mod tests {
|
||||
&authority_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_nonce_account, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateNonceAccount {
|
||||
nonce_account: 1,
|
||||
@@ -828,7 +709,7 @@ mod tests {
|
||||
&nonce_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_get_nonce, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_get_nonce, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::GetNonce(nonce_account_keypair.pubkey()),
|
||||
signers: vec![],
|
||||
@@ -842,7 +723,7 @@ mod tests {
|
||||
.get_matches_from(vec!["test", "new-nonce", &keypair_file]);
|
||||
let nonce_account = read_keypair_file(&keypair_file).unwrap();
|
||||
assert_eq!(
|
||||
parse_command(&test_new_nonce, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::NewNonce {
|
||||
nonce_account: nonce_account.pubkey(),
|
||||
@@ -862,7 +743,7 @@ mod tests {
|
||||
]);
|
||||
let nonce_account = read_keypair_file(&keypair_file).unwrap();
|
||||
assert_eq!(
|
||||
parse_command(&test_new_nonce, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::NewNonce {
|
||||
nonce_account: nonce_account.pubkey(),
|
||||
@@ -882,7 +763,7 @@ mod tests {
|
||||
&nonce_account_string,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_show_nonce_account, &default_keypair_file, &mut None).unwrap(),
|
||||
parse_command(&test_show_nonce_account, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::ShowNonceAccount {
|
||||
nonce_account_pubkey: nonce_account_keypair.pubkey(),
|
||||
@@ -903,7 +784,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
parse_command(
|
||||
&test_withdraw_from_nonce_account,
|
||||
&default_keypair_file,
|
||||
&default_signer,
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
@@ -931,7 +812,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
parse_command(
|
||||
&test_withdraw_from_nonce_account,
|
||||
&default_keypair_file,
|
||||
&default_signer,
|
||||
&mut None
|
||||
)
|
||||
.unwrap(),
|
||||
@@ -953,7 +834,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_check_nonce_account() {
|
||||
let blockhash = Hash::default();
|
||||
let nonce_pubkey = Pubkey::new_rand();
|
||||
let nonce_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let data = Versions::new_current(State::Initialized(nonce::state::Data {
|
||||
authority: nonce_pubkey,
|
||||
blockhash,
|
||||
@@ -966,14 +847,14 @@ mod tests {
|
||||
if let CliError::InvalidNonce(err) =
|
||||
check_nonce_account(&invalid_owner.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
|
||||
{
|
||||
assert_eq!(err, CliNonceError::InvalidAccountOwner,);
|
||||
assert_eq!(err, Error::InvalidAccountOwner,);
|
||||
}
|
||||
|
||||
let invalid_data = Account::new_data(1, &"invalid", &system_program::ID);
|
||||
if let CliError::InvalidNonce(err) =
|
||||
check_nonce_account(&invalid_data.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
|
||||
{
|
||||
assert_eq!(err, CliNonceError::InvalidAccountData,);
|
||||
assert_eq!(err, Error::InvalidAccountData,);
|
||||
}
|
||||
|
||||
let data = Versions::new_current(State::Initialized(nonce::state::Data {
|
||||
@@ -985,11 +866,11 @@ mod tests {
|
||||
if let CliError::InvalidNonce(err) =
|
||||
check_nonce_account(&invalid_hash.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
|
||||
{
|
||||
assert_eq!(err, CliNonceError::InvalidHash,);
|
||||
assert_eq!(err, Error::InvalidHash,);
|
||||
}
|
||||
|
||||
let data = Versions::new_current(State::Initialized(nonce::state::Data {
|
||||
authority: Pubkey::new_rand(),
|
||||
authority: solana_sdk::pubkey::new_rand(),
|
||||
blockhash,
|
||||
fee_calculator: FeeCalculator::default(),
|
||||
}));
|
||||
@@ -997,7 +878,7 @@ mod tests {
|
||||
if let CliError::InvalidNonce(err) =
|
||||
check_nonce_account(&invalid_authority.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
|
||||
{
|
||||
assert_eq!(err, CliNonceError::InvalidAuthority,);
|
||||
assert_eq!(err, Error::InvalidAuthority,);
|
||||
}
|
||||
|
||||
let data = Versions::new_current(State::Uninitialized);
|
||||
@@ -1005,32 +886,32 @@ mod tests {
|
||||
if let CliError::InvalidNonce(err) =
|
||||
check_nonce_account(&invalid_state.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
|
||||
{
|
||||
assert_eq!(err, CliNonceError::InvalidStateForOperation,);
|
||||
assert_eq!(err, Error::InvalidStateForOperation,);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_account_identity_ok() {
|
||||
let nonce_account = nonce::create_account(1).into_inner();
|
||||
let nonce_account = nonce_account::create_account(1).into_inner();
|
||||
assert_eq!(account_identity_ok(&nonce_account), Ok(()));
|
||||
|
||||
let system_account = Account::new(1, 0, &system_program::id());
|
||||
assert_eq!(
|
||||
account_identity_ok(&system_account),
|
||||
Err(CliNonceError::UnexpectedDataSize),
|
||||
Err(Error::UnexpectedDataSize),
|
||||
);
|
||||
|
||||
let other_program = Pubkey::new(&[1u8; 32]);
|
||||
let other_account_no_data = Account::new(1, 0, &other_program);
|
||||
assert_eq!(
|
||||
account_identity_ok(&other_account_no_data),
|
||||
Err(CliNonceError::InvalidAccountOwner),
|
||||
Err(Error::InvalidAccountOwner),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_state_from_account() {
|
||||
let mut nonce_account = nonce::create_account(1).into_inner();
|
||||
let mut nonce_account = nonce_account::create_account(1).into_inner();
|
||||
assert_eq!(state_from_account(&nonce_account), Ok(State::Uninitialized));
|
||||
|
||||
let data = nonce::state::Data {
|
||||
@@ -1049,21 +930,21 @@ mod tests {
|
||||
let wrong_data_size_account = Account::new(1, 1, &system_program::id());
|
||||
assert_eq!(
|
||||
state_from_account(&wrong_data_size_account),
|
||||
Err(CliNonceError::InvalidAccountData),
|
||||
Err(Error::InvalidAccountData),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_data_from_helpers() {
|
||||
let mut nonce_account = nonce::create_account(1).into_inner();
|
||||
let mut nonce_account = nonce_account::create_account(1).into_inner();
|
||||
let state = state_from_account(&nonce_account).unwrap();
|
||||
assert_eq!(
|
||||
data_from_state(&state),
|
||||
Err(CliNonceError::InvalidStateForOperation)
|
||||
Err(Error::InvalidStateForOperation)
|
||||
);
|
||||
assert_eq!(
|
||||
data_from_account(&nonce_account),
|
||||
Err(CliNonceError::InvalidStateForOperation)
|
||||
Err(Error::InvalidStateForOperation)
|
||||
);
|
||||
|
||||
let data = nonce::state::Data {
|
||||
|
@@ -1,129 +0,0 @@
|
||||
pub mod blockhash_query;
|
||||
|
||||
use crate::nonce;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use serde_json::Value;
|
||||
use solana_clap_utils::{
|
||||
input_parsers::{pubkey_of, value_of},
|
||||
input_validators::{is_hash, is_pubkey_sig},
|
||||
keypair::presigner_from_pubkey_sigs,
|
||||
offline::{BLOCKHASH_ARG, SIGNER_ARG, SIGN_ONLY_ARG},
|
||||
};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_sdk::{
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{Presigner, Signature},
|
||||
};
|
||||
use std::str::FromStr;
|
||||
|
||||
fn blockhash_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(BLOCKHASH_ARG.name)
|
||||
.long(BLOCKHASH_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("BLOCKHASH")
|
||||
.validator(is_hash)
|
||||
.help(BLOCKHASH_ARG.help)
|
||||
}
|
||||
|
||||
fn sign_only_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(SIGN_ONLY_ARG.name)
|
||||
.long(SIGN_ONLY_ARG.long)
|
||||
.takes_value(false)
|
||||
.requires(BLOCKHASH_ARG.name)
|
||||
.help(SIGN_ONLY_ARG.help)
|
||||
}
|
||||
|
||||
fn signer_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(SIGNER_ARG.name)
|
||||
.long(SIGNER_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("PUBKEY=SIGNATURE")
|
||||
.validator(is_pubkey_sig)
|
||||
.requires(BLOCKHASH_ARG.name)
|
||||
.multiple(true)
|
||||
.help(SIGNER_ARG.help)
|
||||
}
|
||||
|
||||
pub trait OfflineArgs {
|
||||
fn offline_args(self) -> Self;
|
||||
}
|
||||
|
||||
impl OfflineArgs for App<'_, '_> {
|
||||
fn offline_args(self) -> Self {
|
||||
self.arg(blockhash_arg())
|
||||
.arg(sign_only_arg())
|
||||
.arg(signer_arg())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SignOnly {
|
||||
pub blockhash: Hash,
|
||||
pub present_signers: Vec<(Pubkey, Signature)>,
|
||||
pub absent_signers: Vec<Pubkey>,
|
||||
pub bad_signers: Vec<Pubkey>,
|
||||
}
|
||||
|
||||
impl SignOnly {
|
||||
pub fn has_all_signers(&self) -> bool {
|
||||
self.absent_signers.is_empty() && self.bad_signers.is_empty()
|
||||
}
|
||||
|
||||
pub fn presigner_of(&self, pubkey: &Pubkey) -> Option<Presigner> {
|
||||
presigner_from_pubkey_sigs(pubkey, &self.present_signers)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
|
||||
let object: Value = serde_json::from_str(&reply).unwrap();
|
||||
let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
|
||||
let blockhash = blockhash_str.parse::<Hash>().unwrap();
|
||||
let mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
|
||||
let signer_strings = object.get("signers");
|
||||
if let Some(sig_strings) = signer_strings {
|
||||
present_signers = sig_strings
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|signer_string| {
|
||||
let mut signer = signer_string.as_str().unwrap().split('=');
|
||||
let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
|
||||
let sig = Signature::from_str(signer.next().unwrap()).unwrap();
|
||||
(key, sig)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
let mut absent_signers: Vec<Pubkey> = Vec::new();
|
||||
let signer_strings = object.get("absent");
|
||||
if let Some(sig_strings) = signer_strings {
|
||||
absent_signers = sig_strings
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
let mut bad_signers: Vec<Pubkey> = Vec::new();
|
||||
let signer_strings = object.get("badSig");
|
||||
if let Some(sig_strings) = signer_strings {
|
||||
bad_signers = sig_strings
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
SignOnly {
|
||||
blockhash,
|
||||
present_signers,
|
||||
absent_signers,
|
||||
bad_signers,
|
||||
}
|
||||
}
|
2304
cli/src/program.rs
Normal file
2304
cli/src/program.rs
Normal file
File diff suppressed because it is too large
Load Diff
29
cli/src/send_tpu.rs
Normal file
29
cli/src/send_tpu.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use log::*;
|
||||
use solana_client::rpc_response::{RpcContactInfo, RpcLeaderSchedule};
|
||||
use std::net::{SocketAddr, UdpSocket};
|
||||
|
||||
pub fn get_leader_tpu(
|
||||
slot_index: u64,
|
||||
leader_schedule: Option<&RpcLeaderSchedule>,
|
||||
cluster_nodes: Option<&Vec<RpcContactInfo>>,
|
||||
) -> Option<SocketAddr> {
|
||||
leader_schedule?
|
||||
.iter()
|
||||
.find(|(_pubkey, slots)| slots.iter().any(|slot| *slot as u64 == slot_index))
|
||||
.and_then(|(pubkey, _)| {
|
||||
cluster_nodes?
|
||||
.iter()
|
||||
.find(|contact_info| contact_info.pubkey == *pubkey)
|
||||
.and_then(|contact_info| contact_info.tpu)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send_transaction_tpu(
|
||||
send_socket: &UdpSocket,
|
||||
tpu_address: &SocketAddr,
|
||||
wire_transaction: &[u8],
|
||||
) {
|
||||
if let Err(err) = send_socket.send_to(wire_transaction, tpu_address) {
|
||||
warn!("Failed to send transaction to {}: {:?}", tpu_address, err);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user