Compare commits
5846 Commits
Author | SHA1 | Date | |
---|---|---|---|
9f532cb50f | |||
c35f4927cd | |||
2d0f4b5c8c | |||
fe59ee61e6 | |||
bda2acb06d | |||
ac3fe1da02 | |||
57813041d2 | |||
7e446da82c | |||
d94f5c94a3 | |||
4d9aee4794 | |||
f32c152bce | |||
5caf9110e0 | |||
d1c80143ea | |||
bb132df121 | |||
b3af1c7e57 | |||
14cba53338 | |||
dc8abbe9e3 | |||
dd06001ed8 | |||
298b7de2e2 | |||
74cbc6953f | |||
27e5203078 | |||
73787e162c | |||
74ae40be41 | |||
8dd58e9cea | |||
7d86179c60 | |||
8115cf1360 | |||
3e5d45053d | |||
061319f35a | |||
dc75837500 | |||
dfe26f5275 | |||
f4385f7ad2 | |||
8df4cf2895 | |||
dad62e132e | |||
0d4131ae68 | |||
a2539e1892 | |||
210659e6c3 | |||
15a0fb1fa9 | |||
4db31f5b48 | |||
b38a535c63 | |||
218b02aaf8 | |||
d6e7cbd4e8 | |||
f2fda14333 | |||
1c576d4a68 | |||
f6232e1b3c | |||
ad71fa3f12 | |||
9c326c7c71 | |||
ac545cadaf | |||
082d8fff36 | |||
2c3632a042 | |||
7b23e79922 | |||
9afd14a0c6 | |||
f42aa34ab6 | |||
43af91ff4b | |||
a920a0f946 | |||
4a8a6d0b3f | |||
a64b8a2705 | |||
0198f9e8af | |||
1582a3a927 | |||
e713f0e5fb | |||
77031000c4 | |||
635a962fba | |||
c59ec2dcff | |||
abc6c5e264 | |||
87cfac12dd | |||
60b43e34b6 | |||
9ab6222f03 | |||
2298dd5c07 | |||
822d166115 | |||
8f71580615 | |||
cc3352ff06 | |||
8f5928b7c7 | |||
888e9617ff | |||
4695c4cf7d | |||
bbfc56ff7f | |||
4103d99018 | |||
c375ce1fcd | |||
df813b31c5 | |||
7db92e2951 | |||
6585518f78 | |||
6398e256e4 | |||
b64ebb45e7 | |||
307ac66d9c | |||
dc02f2ea8b | |||
b7386f9d84 | |||
223f9707ca | |||
ea5b00364f | |||
fb98df76b7 | |||
4ddbf8d509 | |||
aa80f69171 | |||
0ace22d03f | |||
0e6aca5a7e | |||
3f04226864 | |||
d308eed136 | |||
ed1149c8e0 | |||
48f58a88bc | |||
d238371b0c | |||
0b7e8d0162 | |||
18fd52367e | |||
2d665da3e1 | |||
5ef06a9d36 | |||
f4622d67e9 | |||
b65c9ea544 | |||
cc7c6c960e | |||
01697a9f5c | |||
ab361a8073 | |||
ec5c02cb7f | |||
e8124324ff | |||
1720fe6a46 | |||
e50bc0d34b | |||
45774dc4aa | |||
ea8d9d1aea | |||
221866f74e | |||
3e96d59359 | |||
8c19b6268c | |||
8ae26867c5 | |||
19baaea0da | |||
e3cebcf82d | |||
ccad5d5aaf | |||
d0bcde001e | |||
83a8e82626 | |||
7305a1f407 | |||
3975c7f8c9 | |||
ac1d075d73 | |||
73a278dc64 | |||
a042ee609a | |||
0d5c1239c6 | |||
027ec71aa9 | |||
ef718c651e | |||
fc2a0d53d9 | |||
bb47844ae6 | |||
b997d3eb4e | |||
9bcca268a3 | |||
8a2d4e2f72 | |||
335675c51c | |||
1bf2285fa2 | |||
71f77a8e0a | |||
644a7f9a44 | |||
965361ff69 | |||
4593d333c7 | |||
940519ea5a | |||
a0bcbf70d5 | |||
17fb8258e5 | |||
c350543b46 | |||
5b4ecb01ca | |||
28b115497f | |||
0604029661 | |||
2374cf09e2 | |||
ab475e4849 | |||
1c97b31eaf | |||
bd257050e3 | |||
2d362ed337 | |||
cb7117beac | |||
b358ff66e1 | |||
6309c97697 | |||
58727463e1 | |||
741d148a0d | |||
127553ce4b | |||
ecb055a252 | |||
dfa6fbaa0c | |||
cf11d4c7dc | |||
d0a4686990 | |||
2542d5dd42 | |||
1e0f2b2446 | |||
a8028fbb93 | |||
ed87229cec | |||
c4fd81fc1c | |||
ad43babe3d | |||
36c0cb052b | |||
ed58bcda4c | |||
268bb1b59b | |||
059764586a | |||
72b11081a4 | |||
0bbee9456f | |||
fcac910989 | |||
2e9ba149f2 | |||
d3712dd26d | |||
60877f9ba4 | |||
4f2c76150f | |||
137577fb86 | |||
bf623219d2 | |||
25d1f841ee | |||
517fe73734 | |||
890919d140 | |||
33ea1e0edd | |||
7614af2a45 | |||
1528959327 | |||
46b6cedff4 | |||
8d8f28c1d0 | |||
df782b93ae | |||
124f77cdb1 | |||
fc15f74c3c | |||
1d06aa3b31 | |||
0b263f8714 | |||
84b3e12e1f | |||
669282ae69 | |||
485806c488 | |||
1412ee1ca6 | |||
ef5fb6fa46 | |||
99432833d2 | |||
fa00803fbf | |||
04ef977509 | |||
87c6508305 | |||
ed0c1d3b52 | |||
8b5598fabd | |||
5b070ad014 | |||
6246405afd | |||
27c8ba6afc | |||
b832a03315 | |||
09686290bc | |||
4aaa7b30ca | |||
fe590da3b6 | |||
a7fa92b372 | |||
a25e57c397 | |||
eed676113e | |||
b57f24f1bc | |||
0e084358b4 | |||
f016c9a669 | |||
59ba1df910 | |||
71a2c90f21 | |||
8436457e75 | |||
3ac0192d40 | |||
3db159f616 | |||
e21f5c784e | |||
65c24db83c | |||
ed5101b031 | |||
1420628b28 | |||
15ab966ed1 | |||
b5a735878a | |||
b6d09f1901 | |||
78f6ddc5b7 | |||
4e595e8e3c | |||
79249360f7 | |||
336d5136bf | |||
0c8cee8c4a | |||
4c0420b884 | |||
0d7e093415 | |||
c835749563 | |||
0172d2a065 | |||
927f272f0e | |||
5e2891ae5d | |||
4f85481a2b | |||
d314e0395a | |||
69a6d07371 | |||
fab8ef379f | |||
408ef8b2cb | |||
a2a2f1c2d2 | |||
dc2888c9a3 | |||
9739be9ecf | |||
e61257695f | |||
b9988b62e4 | |||
d6b3961530 | |||
6d0be323ad | |||
09256adbc3 | |||
8e3a7da596 | |||
7d96510d17 | |||
0fd795a676 | |||
eff876881b | |||
9adf0d4ee0 | |||
3bc9789e8d | |||
fd207b6907 | |||
2226c1b75c | |||
a0964bb2c2 | |||
b5383b8b54 | |||
39f86050a6 | |||
a03d441e6f | |||
3900d09f6f | |||
e218f4e56e | |||
81ba18eea6 | |||
1671ece9df | |||
775fa0c968 | |||
dd276138c2 | |||
0c55b37976 | |||
966d077431 | |||
400412d76c | |||
c7e77a2238 | |||
64c42e28dc | |||
c2baf7b07d | |||
a52a9afa3c | |||
669502ede7 | |||
b19f730527 | |||
d5ff5f4739 | |||
1c82f84595 | |||
bea9cd9684 | |||
1bc9a9c23b | |||
c4faccc77f | |||
e6803daf10 | |||
effe6e3ff3 | |||
0d6c233747 | |||
015e696077 | |||
7faab2072c | |||
83718a3b3e | |||
4a074133f7 | |||
12eff5a2f9 | |||
4197cce8c9 | |||
fed3817ed3 | |||
4ffd7693d6 | |||
1596c961d9 | |||
fd7d5cbe0d | |||
7058287273 | |||
912aafcefd | |||
2f34f433b3 | |||
1ff4dd9a9a | |||
fdcaad96c7 | |||
14a72b0fc0 | |||
c13ab9f14e | |||
cff1bc6e71 | |||
bb6c4efe9b | |||
c324e71768 | |||
e2570c98ee | |||
b5125479ec | |||
989355e885 | |||
a2f2c46f87 | |||
605623baf5 | |||
fdc452c536 | |||
1b391dd36b | |||
917067741a | |||
34ed93d57c | |||
d400a64b9a | |||
2c7447b73e | |||
c0f0fa24f8 | |||
bda5f949bb | |||
992e985972 | |||
afaa359b0d | |||
3c17db41dc | |||
d62ed4f6b3 | |||
79f3194d0c | |||
b045f9a50d | |||
ce231602dc | |||
6f5e0cd161 | |||
1269a79a4d | |||
1b3424ff61 | |||
8b8033c72b | |||
7ca0109732 | |||
6b5172d002 | |||
9e19a635bb | |||
15193d0e1f | |||
f1c5c72e62 | |||
25dfed207c | |||
006cbee88a | |||
c95e5346a4 | |||
e54bf563b5 | |||
8f79327190 | |||
a197ac092a | |||
1e2b55c0d7 | |||
964ff522be | |||
934c32cbc6 | |||
9bd6be779f | |||
ce70d6eedc | |||
3a0d13aa77 | |||
f9323c5273 | |||
7587656cf6 | |||
023074650f | |||
d854e90c23 | |||
3aabeb2b81 | |||
65f5885bce | |||
7a132eabb4 | |||
7e1b380f01 | |||
1a2d9b8eed | |||
6eefa0b72d | |||
44372db955 | |||
e24cce4aed | |||
a8595c0418 | |||
340424e03a | |||
93036bec01 | |||
663e98969d | |||
37d1daf58e | |||
1a18f0ca55 | |||
bb950ec93e | |||
39ab3557a3 | |||
dcdc46b97c | |||
da3ed0dfb3 | |||
e391b9fb90 | |||
e346cdad26 | |||
7e4c6ff218 | |||
356f246a74 | |||
80da552834 | |||
2dd8ab197d | |||
1fe11e9ae2 | |||
21d5fe6272 | |||
52bc4a3598 | |||
cccaacee36 | |||
ebf6e1c0e9 | |||
5cf090c896 | |||
cc299053cc | |||
82b75796f9 | |||
a560d94a9f | |||
0827d52c6f | |||
a8d33c9950 | |||
43c32ea280 | |||
30d40e9a32 | |||
e28508ad56 | |||
182e4cec86 | |||
a32de96ab1 | |||
0de35fdd1f | |||
470d9cd752 | |||
87598c7612 | |||
57bf618627 | |||
c576a707b0 | |||
b78b1bbfa9 | |||
e710964d05 | |||
2d00657756 | |||
0526d4ff21 | |||
76e20015a4 | |||
f5e797e3aa | |||
787e36a28f | |||
8572b57834 | |||
ed0129f881 | |||
78836a9e22 | |||
4c08184379 | |||
da165d6943 | |||
8ffccfbaff | |||
a6d083d69d | |||
91bae9d510 | |||
f0f185509f | |||
5947ef7706 | |||
4f663a2a86 | |||
1d01777a13 | |||
6d3b8b6d7d | |||
50c1c08235 | |||
b16c30b4c6 | |||
ff1ca1e0d3 | |||
721c4378c1 | |||
5f4e0c7e3e | |||
e6af4511a8 | |||
965ad778dd | |||
3b78be83cf | |||
564cd4e09d | |||
699ca5fec1 | |||
f91ffbbfdf | |||
156292e408 | |||
81ae44f858 | |||
c948814eae | |||
b5dba77056 | |||
ef06d165b4 | |||
5cb23c814d | |||
8f7ded33e0 | |||
a17d5795fb | |||
ad4d41e602 | |||
9754fc789e | |||
fd3c6eb320 | |||
b7b68ecdba | |||
08ba27627d | |||
27d2c0aaf3 | |||
b714a4be63 | |||
2356b25c58 | |||
05cad05505 | |||
1e3082fbc0 | |||
80d2573b10 | |||
6adcdc41f4 | |||
2d08dddfc8 | |||
6da8f49d8b | |||
bcd072c5e8 | |||
e90a31781c | |||
2e89ec9105 | |||
865c42465a | |||
73c93cc345 | |||
cf32fdf672 | |||
c33b54794c | |||
6775e83420 | |||
719785a8d3 | |||
287995ffdf | |||
0e506a53b5 | |||
70e1a15973 | |||
09cff5e4cc | |||
57858b8015 | |||
07855e3125 | |||
2f5f8e7afd | |||
43897de12e | |||
4b577aa77b | |||
85c3d64f29 | |||
47dd293904 | |||
c4220a4853 | |||
48ab88a2af | |||
d9cf9709d2 | |||
9720c894f1 | |||
8dad3af36d | |||
e5425d4a27 | |||
58e6d4aabb | |||
9ce142606c | |||
e75a64a8a2 | |||
bc71e1b612 | |||
580ca36a62 | |||
447fe48d2a | |||
e8a6c8cd6d | |||
a8fd42c1df | |||
e782c26908 | |||
cd65a1e172 | |||
6e51c5685e | |||
84a37a2c0c | |||
7e94cc2cc3 | |||
7002ccb866 | |||
4fe0b116ae | |||
a0fb9de515 | |||
5d42dcc9ec | |||
96e88c90e8 | |||
75d94240ed | |||
6c544708e1 | |||
078e7246ac | |||
06cff1fb9f | |||
2e8bbed75b | |||
a707c9410e | |||
a956bb08d8 | |||
db52cc6749 | |||
73c6224a95 | |||
a217920561 | |||
48a36f59a6 | |||
965b132664 | |||
63f185f9bf | |||
e97b0088f2 | |||
374c17a0d9 | |||
4b3bc587ab | |||
06c63f2026 | |||
6b7d9942a7 | |||
760a56964f | |||
6ca575b5a3 | |||
ce1d36cacb | |||
87b2525e03 | |||
faa77aca2e | |||
5d2158792c | |||
e1ebaa902b | |||
e0564f628e | |||
44e45aa090 | |||
89f5f336af | |||
727be309b2 | |||
ce2d7a2d5a | |||
fad6c7201e | |||
8f0e1f3349 | |||
6f7d0c6928 | |||
120c8f244c | |||
352a367570 | |||
9f65d22909 | |||
141131f3a6 | |||
488420fdf2 | |||
10e6b8f769 | |||
419da18405 | |||
7329d4bf3a | |||
c8fe4043b6 | |||
3d133d61ca | |||
d51e42c707 | |||
79e39d6f0b | |||
7dec934bb3 | |||
83f866df01 | |||
d88d8e2dbb | |||
3a40dff999 | |||
3f69d58498 | |||
ca10cf081f | |||
f120449aae | |||
636f51c93c | |||
9bb47c8c61 | |||
8886db2000 | |||
a7040896f0 | |||
2ebfab8e07 | |||
9bd5888f5e | |||
8b7bbbc6af | |||
0383ffa5ab | |||
3c361eb759 | |||
37eaa6e4f9 | |||
0ae7e86fcb | |||
3f405d8908 | |||
0245847ea8 | |||
54f16ca2bf | |||
a096ade345 | |||
848fe51f3d | |||
e82db6fc2f | |||
4b3176a9a1 | |||
5e6c58716e | |||
e98132fd76 | |||
ff171baa67 | |||
05664d150b | |||
fcda972cec | |||
01f44f531e | |||
c5b076ec7e | |||
05cf5a38af | |||
bd22b641b3 | |||
6a9005645a | |||
7392505bd8 | |||
6aaf742dfe | |||
dcaf69a5d5 | |||
323673c3c0 | |||
e16ccf8cf8 | |||
434cde179f | |||
629a4b5bf8 | |||
6a8f6fb3cc | |||
807e930786 | |||
554188e88e | |||
585fca06a1 | |||
282667c4b5 | |||
8176470b7f | |||
acb7ce16ca | |||
12bdef51f5 | |||
84b07c81fd | |||
107360a001 | |||
3f541df669 | |||
da17783242 | |||
0ea2843ec9 | |||
7c92bf15e2 | |||
97589f77f8 | |||
504adcc8c8 | |||
f03ed9f5bf | |||
b22dc38ba1 | |||
7a7992ab0b | |||
3513f4ee84 | |||
f33703aefc | |||
389089859d | |||
844dddfee0 | |||
862e7a410d | |||
7ad64c8d45 | |||
5b50990879 | |||
71b93468d5 | |||
6b88da2b82 | |||
d01ea20273 | |||
f05860672c | |||
2b5e919a47 | |||
27c8df6140 | |||
9ac112104c | |||
98b80288ed | |||
ecdea54203 | |||
9d5a07bac4 | |||
7adc721d96 | |||
f5137028fa | |||
48f9b2fdcc | |||
b7d6ff6770 | |||
f7a87d5e52 | |||
75d1aa5135 | |||
49396a69bf | |||
d94041e98d | |||
cc5408482e | |||
115bf2613d | |||
1d172b07a8 | |||
777ae3c215 | |||
1b2a9270e8 | |||
e082418e4a | |||
83218c479a | |||
dbb8267b09 | |||
ea0ba19089 | |||
2db28cae41 | |||
dd54fff978 | |||
c4f3bb9b67 | |||
45487a91f9 | |||
dad5c62df5 | |||
a1ab81a896 | |||
1d0ba0d1f2 | |||
46a4ea8f67 | |||
42f2b14a74 | |||
bec5835289 | |||
0aa4dc904e | |||
f526c424c5 | |||
601d7a52e9 | |||
7f6fc74c36 | |||
9e2ce1751b | |||
8920ac02f6 | |||
06415de8ee | |||
12d471e2da | |||
7d6777a96f | |||
96c08cd295 | |||
f3633a2e04 | |||
feeb1cb566 | |||
146bc95c16 | |||
5792f5bfb5 | |||
11521dca08 | |||
6f457292ff | |||
696cb298ab | |||
6d2861f358 | |||
7879fa5095 | |||
a03062af4f | |||
19ecce1e32 | |||
5e0a69f68b | |||
a33bcac52f | |||
39cd6dff7d | |||
ed9cf3566c | |||
d4d246bfd1 | |||
c02a14c798 | |||
781ce30e27 | |||
4b68c7c154 | |||
daddd90058 | |||
5d2b27d916 | |||
7a37363817 | |||
bee3829960 | |||
e0600e5a91 | |||
b55b646f12 | |||
43e608af47 | |||
32d6d811c5 | |||
0d6fca5abc | |||
48a085c28f | |||
059e631f41 | |||
deb7ac549c | |||
891767c6b7 | |||
62810d769a | |||
5253c27ca8 | |||
1ffd6b4b4d | |||
6469606baf | |||
77cd292828 | |||
22d6951de5 | |||
33f7103eae | |||
c00216e3be | |||
42247e0e1a | |||
8a908a6864 | |||
2d6ed7142f | |||
9ecb844de7 | |||
3ab8185777 | |||
b8008ae1e9 | |||
ab9ec45c9d | |||
6a0d683f79 | |||
711487267d | |||
503bf69ab3 | |||
a60521269d | |||
fe96f85410 | |||
275fab003f | |||
edfb386ef0 | |||
186709ed75 | |||
b7d4330dd4 | |||
7c3be2ec9a | |||
8fac9102eb | |||
178854ac97 | |||
f4a089cc26 | |||
422eab5846 | |||
95e1404a2b | |||
cfc21e1225 | |||
3799190fa0 | |||
d6c3396182 | |||
a95d37ea25 | |||
d8e1a196bc | |||
1e2970b7e1 | |||
0d1fed78af | |||
709bda5939 | |||
8a28734603 | |||
9485eba73d | |||
23c4a7dc49 | |||
39b578fde9 | |||
8e16079157 | |||
eabd23fc07 | |||
c7932b710c | |||
9d7a926a8b | |||
0a390cbc91 | |||
76829457df | |||
703a5348e8 | |||
1a135fa30e | |||
e4d75c77bf | |||
75d505c431 | |||
b72c99e46a | |||
fae9c08815 | |||
c3e7deb4b6 | |||
c9245751e9 | |||
9b172879a2 | |||
9077a94dfe | |||
e2f07a5220 | |||
ae93d574c2 | |||
369f37a0a4 | |||
e1b7f40c2b | |||
94dcd3fe12 | |||
2dc1ae9026 | |||
7cfff75c3e | |||
a66a49d384 | |||
5f58e0661b | |||
f0a40862d6 | |||
f75c51ff71 | |||
d357192025 | |||
c996c8ff49 | |||
1af4e256c9 | |||
bc09365c98 | |||
ba688cf629 | |||
d5c8b26a45 | |||
d38f3f664f | |||
5ac435325b | |||
b874441a47 | |||
a35087a5ed | |||
1aeaf052a6 | |||
a0eafa12e3 | |||
757425a360 | |||
64d1e776f7 | |||
c6695a3120 | |||
076e384bb5 | |||
41cff1b49d | |||
6796b08909 | |||
f9df17d8d0 | |||
7f71a0ba37 | |||
0e2e13f018 | |||
bd099e2f4d | |||
42f56b9f86 | |||
704c50ea17 | |||
887bff572a | |||
1eaf71b5b4 | |||
0f872af502 | |||
b13696ea1a | |||
5fbbf7c748 | |||
e7fe0db051 | |||
dcb7bd8c74 | |||
92d485dd4d | |||
f4229a5d3e | |||
f97626346b | |||
7f4feaee08 | |||
5a30ef180a | |||
0a0412e47e | |||
57d4b50467 | |||
8d75efdc58 | |||
c706f9b2cd | |||
c810913861 | |||
2b13158e29 | |||
4fe1716c7a | |||
d7a82783be | |||
0a0f15baca | |||
58c144ee55 | |||
280315a314 | |||
506ff5809e | |||
acd1505050 | |||
578b56fc10 | |||
88cb0c6ae3 | |||
294662a1ce | |||
eaa3e87eb0 | |||
9b3a1a99e5 | |||
76a68c26c9 | |||
ef64f00cbb | |||
acbe89a159 | |||
0f66e5e49b | |||
686aa3a150 | |||
d8bc828839 | |||
094c391cd7 | |||
c8491724b4 | |||
d5beb8a9e4 | |||
702f7cc51d | |||
b8cd0a1bc0 | |||
7f87ac4b65 | |||
306fbd8bd8 | |||
3e0b272a20 | |||
6c89226ccf | |||
f040987c9f | |||
2a42ddbcbf | |||
8bb68c4e6a | |||
4485b978c1 | |||
68bad56e7d | |||
ef55c15537 | |||
ce8d37984d | |||
c8166aed97 | |||
0bd41f98ed | |||
d8ead57fbb | |||
d9e7a5fcbe | |||
c965a110f2 | |||
8a879faac7 | |||
a2a9f1e331 | |||
15d7568038 | |||
8cbc450192 | |||
79199711b8 | |||
2c1b8fdd39 | |||
d9024db68d | |||
96dd044f8e | |||
e66b29943b | |||
100b9dd12a | |||
3415db9739 | |||
186bf7ae32 | |||
97ca6858b7 | |||
ee6b11d36d | |||
f58fef60fb | |||
a76eb64bbb | |||
8590326b50 | |||
b0271394cd | |||
c39633f968 | |||
1fef74b00c | |||
9f6a2e51b2 | |||
b150da837a | |||
ba9aaee7cd | |||
3aa67969f9 | |||
d4f336db40 | |||
d184d3a732 | |||
42da1ce4e2 | |||
d2ed921bc6 | |||
d32a072190 | |||
95c137158f | |||
7151b92239 | |||
716caeb17c | |||
f8e4bdd23d | |||
55dfd03007 | |||
854fc8d552 | |||
f2badf2c5d | |||
ea656b1a3f | |||
5b7bd24f0a | |||
2d7c7b0982 | |||
b958bf9086 | |||
43144cfe8b | |||
11d2d2eccd | |||
e22f89853f | |||
7ccc029f77 | |||
0eb78e461d | |||
3615209ce7 | |||
6bfe0fca1f | |||
bfa2535ea1 | |||
6ec918fabb | |||
cbf7c0080b | |||
a6196901de | |||
c09469fa3a | |||
3acd84d9c0 | |||
c902fd0303 | |||
955aaef2e6 | |||
e0a2bb9d86 | |||
3bc8d78801 | |||
b66c03667c | |||
6e04a646ba | |||
086e5da8d0 | |||
c1b06817a2 | |||
c3926e6af0 | |||
70322d1ff8 | |||
7c32640a9b | |||
5ad09afc15 | |||
5d8c1a303e | |||
24b254459b | |||
30089841f6 | |||
0bee05b849 | |||
afd9ae9999 | |||
5ab70c4e97 | |||
cab2232aba | |||
946e937549 | |||
0ca943f49b | |||
b2db0b97fc | |||
d565ec7968 | |||
36e3ccfc68 | |||
892ca196f1 | |||
59413b3124 | |||
e1643c91c4 | |||
3ce6248f8c | |||
006c39380a | |||
22f2247f46 | |||
852a2146ab | |||
99b42f210c | |||
ae3c9033c1 | |||
03f7f0d18c | |||
79d7090867 | |||
e7f63cd336 | |||
f108f483b7 | |||
d6cbb02c92 | |||
42af8b199f | |||
dbbd9663b2 | |||
f4846b6fe4 | |||
a28a34f61c | |||
96d47c51a1 | |||
f27c11ccd8 | |||
43e2301e2c | |||
7b05b3dbb3 | |||
c96b8c8d68 | |||
4fc767b3f6 | |||
cc96848b01 | |||
6009801c5f | |||
f116cdeed9 | |||
5f38fa379c | |||
e2fb9ac829 | |||
f83254d760 | |||
ee5cc733a1 | |||
18a17cfbbf | |||
a3a830e1ab | |||
4b1e9ada18 | |||
9026339d35 | |||
0be13a6295 | |||
fcc2874591 | |||
9246bee12b | |||
30a08f4282 | |||
e5c5f34f9a | |||
361eab1bf7 | |||
2fd2140f64 | |||
86faa3f995 | |||
81acd94153 | |||
48987bed67 | |||
4405e8a15b | |||
24cb4798bc | |||
986e9e268e | |||
71bf8c5f85 | |||
5a629ff387 | |||
148a58865e | |||
2523fa73cf | |||
6d76c34291 | |||
3faeb7fa79 | |||
bb00904fc8 | |||
73e3fc7c4f | |||
5903339c17 | |||
2688ae614c | |||
5670cafda4 | |||
4bc8fd3267 | |||
bb2fa9957a | |||
c6b108ef4f | |||
bb158a9b48 | |||
c2fdbde68f | |||
7e82450d7b | |||
188dbdb068 | |||
25866f3652 | |||
c7e2057d2d | |||
d84f367317 | |||
95d6586dd7 | |||
e8e13fdeeb | |||
816b2d7ff8 | |||
91cfa0aac9 | |||
4be646c695 | |||
a23c6177d5 | |||
cc6e1ea200 | |||
596d30661a | |||
b971eeca4b | |||
cfab36cb1d | |||
5835b3b8eb | |||
62eea636b0 | |||
b14e61ff79 | |||
59adc25c23 | |||
86ead6a65c | |||
fbfbafa3d4 | |||
1ddf90ed08 | |||
0fbd508c5f | |||
24a7b0ce74 | |||
68eafb3f30 | |||
2649f6bdd6 | |||
9807f47d4e | |||
63425bed10 | |||
02058ea699 | |||
91be35731c | |||
d1daeb44e6 | |||
efdfc5c327 | |||
9c00ad9ff2 | |||
151adab739 | |||
162b1bdef7 | |||
da425cc225 | |||
346213da4c | |||
8babecd890 | |||
2855c55ac1 | |||
bb9649e18d | |||
2f7d0e7884 | |||
dfc4d7cb50 | |||
b800642fa4 | |||
5b6c590057 | |||
66a0f54097 | |||
f8e64aad5b | |||
cd5ec8cd35 | |||
75fd13de5d | |||
807af8670e | |||
5bd05fba09 | |||
f7b6e777bf | |||
68353b7e57 | |||
8e81bc1b49 | |||
80a89b5e6d | |||
b64b54f48f | |||
20a52f153b | |||
d89271528e | |||
ccac35fc01 | |||
23e232b496 | |||
ddcf906a88 | |||
09e8124017 | |||
67d1e2903c | |||
a9c4cd6cbe | |||
180bc1784e | |||
f984feda42 | |||
56fc15f44d | |||
e0d9f7d1d4 | |||
87ba66b6d0 | |||
e420800aeb | |||
a684984f8b | |||
079682fbdc | |||
2491719f36 | |||
65de227520 | |||
29f3b198cf | |||
0ace79939b | |||
b3a75a60a4 | |||
5e8668799c | |||
8fa6935c9d | |||
a1fe6265fd | |||
67f636545a | |||
ec50c20400 | |||
18f146ace5 | |||
a91bf296d7 | |||
bb8985d76c | |||
7ff2a44a63 | |||
b5074d8577 | |||
5c1abaf43c | |||
dc3988eff8 | |||
24102a7435 | |||
9614d17024 | |||
8e3be6413e | |||
09e648f957 | |||
0c2bf022fa | |||
1c5d2a85cf | |||
8993b15248 | |||
8f91b5aab3 | |||
46391397b8 | |||
85c9a231c1 | |||
c312d4fba0 | |||
7203036e3e | |||
b9d8e3e55a | |||
08973f9f05 | |||
c6931dcb07 | |||
cea13e964c | |||
d207a34736 | |||
fba1af6ea9 | |||
b825d04597 | |||
3133ee2401 | |||
4d52f47f87 | |||
f54cfcdb8f | |||
57983980a7 | |||
33f4aaf3fd | |||
c138d692b1 | |||
fb12136975 | |||
efe260f12e | |||
b9b535c30f | |||
d085c8626f | |||
5e3697807c | |||
5416c114cf | |||
a0127e63c6 | |||
8b2327ed34 | |||
3938142535 | |||
66f76c8067 | |||
568475e2db | |||
9ea398416e | |||
50a17fc00b | |||
f9a9b7f610 | |||
a57f6b70da | |||
bae83ba2b6 | |||
385b4ce959 | |||
7b6e3a23be | |||
1cc8956f74 | |||
e6c8bfd008 | |||
2d67962c2f | |||
2e30926ac3 | |||
d2c66c40c6 | |||
a4d48df30a | |||
c52830980a | |||
e8e5ddc55d | |||
111942a47d | |||
bc88180058 | |||
3a616de47b | |||
9d65e6f183 | |||
328a6a866e | |||
5264fded00 | |||
83d5115a02 | |||
0559212df7 | |||
f131255066 | |||
59f3dc3b6b | |||
6454bfe754 | |||
7bb224f54a | |||
fa12a5f70b | |||
d2d78a073f | |||
6d403f2d85 | |||
8032141311 | |||
38491c8c4b | |||
627664b785 | |||
dfa1c7493c | |||
801337a422 | |||
4ec95043d7 | |||
b4dc1a7263 | |||
ef3aa2731c | |||
e738019c48 | |||
a5ef78f709 | |||
4156cea704 | |||
a587d05098 | |||
489dc657c6 | |||
029a2837e4 | |||
618ecfd1c6 | |||
83174b919c | |||
d952b38f93 | |||
1e2ab89b47 | |||
34a9619806 | |||
9ee65009cd | |||
85ccba366a | |||
579a02529d | |||
b04c8c1c1a | |||
243fa6cf63 | |||
30c0a7d069 | |||
71b4e765c8 | |||
73dd5aa2d1 | |||
96e209db49 | |||
0c14ca58c7 | |||
f3c0aa154a | |||
6efaaa9d7a | |||
08238e8307 | |||
e1b35f9847 | |||
e174af7838 | |||
be74801236 | |||
68acfd36d0 | |||
c9cea2152b | |||
e966c96644 | |||
73c31d873e | |||
a2a9d54985 | |||
ea2b26e5f5 | |||
d68e2c4d06 | |||
0cfa3d3de7 | |||
0d1f463f7f | |||
ff34bfebde | |||
e103789994 | |||
8a37b1e742 | |||
0cf4eb2ee4 | |||
5496f85dbc | |||
71ff269780 | |||
3879109e4c | |||
f901d71202 | |||
1738632822 | |||
bbd5dde66d | |||
43c0103e4c | |||
6eeca9c6f1 | |||
28d3af6f35 | |||
7f3072d53a | |||
90461245f9 | |||
1c91c1e880 | |||
53c7be32b6 | |||
397ea05aa7 | |||
dadcb632d8 | |||
2de2fbd5e3 | |||
14eca5aea6 | |||
27f38a3770 | |||
7a7abe692e | |||
f46a2cec3c | |||
8e5e48dd92 | |||
a2543e5a8d | |||
e9bdee3dc7 | |||
88033bccbb | |||
b4119c454a | |||
d398898c38 | |||
39fc677781 | |||
dc52b17c4d | |||
ddefc96433 | |||
955d0ab76f | |||
f1172617cc | |||
6ce115ec95 | |||
03d29a8311 | |||
35cc74ef25 | |||
35d6196384 | |||
01fe7c90a5 | |||
26b8747014 | |||
bedb05bdeb | |||
6829b8a6fb | |||
e462a7d1d5 | |||
4c515d0ef1 | |||
7d650eff8d | |||
0b2d4f32fa | |||
4f25013954 | |||
5c7735c40f | |||
e6438098e1 | |||
45b2c138e5 | |||
75d68edfe7 | |||
f80a5b8c34 | |||
1b1980ad49 | |||
3b9b9b1500 | |||
18feba2431 | |||
929a81e636 | |||
00809a67c0 | |||
d1b18a5060 | |||
3fb70b8d47 | |||
b38bf90de7 | |||
8319fa05d0 | |||
564c14a2c6 | |||
6996f45d54 | |||
b1c2c6009e | |||
934f69b660 | |||
84e911361a | |||
364583ea5c | |||
951e1f8b48 | |||
9232057e95 | |||
6c79f56c2c | |||
48eafcc74f | |||
dec9272813 | |||
eb3093d43e | |||
09abbd93b1 | |||
91920cc390 | |||
cc1cc7be94 | |||
2636418659 | |||
31e9074ae5 | |||
e2c316d2d0 | |||
74ee88d9bc | |||
f52c813fc2 | |||
badeb4d31a | |||
e59af8269e | |||
785c2574cd | |||
1a77f7ce3b | |||
6e7dccbbfb | |||
32bfced6a4 | |||
985f5c7351 | |||
621c67a8cb | |||
f2fd53e773 | |||
0fc3c7eee2 | |||
e81ba8e79f | |||
35461df92d | |||
a19ffb353d | |||
35ed432d1a | |||
8c29700402 | |||
171c0d5421 | |||
c01bc4afbd | |||
c404008743 | |||
193c9a08e0 | |||
5468be2ef9 | |||
6f58bdfcb1 | |||
9cf9de6044 | |||
a48dcb1421 | |||
51dad397ed | |||
27c0d30a07 | |||
6c33c3a5ba | |||
e6198debd6 | |||
298ba34c3c | |||
52b5edcb8f | |||
c73e8d9a82 | |||
842eaf90df | |||
24846b7b61 | |||
326a4282bb | |||
854c62e208 | |||
1759968c1e | |||
9e52d11ad0 | |||
d865f1f0c5 | |||
2747c9db23 | |||
bfc67e8680 | |||
d3068c3918 | |||
a931ad40c8 | |||
b4ed88e0f7 | |||
b7b71b31d3 | |||
83c1831a01 | |||
b85996494b | |||
26d31b68d7 | |||
8740bb42c0 | |||
ccb4e32ee0 | |||
2d351d3952 | |||
7ae5ff838b | |||
605b477e06 | |||
7e6e7e8406 | |||
e267dfacdd | |||
f4c5da3c72 | |||
a258e1e0b3 | |||
1fd84cb52b | |||
8dd24bc7d9 | |||
b7af5f08d6 | |||
781dfd9dc4 | |||
f6b48b0a67 | |||
ee099b0880 | |||
51ac05b3cf | |||
9267931ef6 | |||
60141e0c2c | |||
609d6cdf61 | |||
a3ccbe02d0 | |||
996c8cf2eb | |||
528d0b6af8 | |||
33052c1dd2 | |||
c1b401a04a | |||
78d5c1de9a | |||
2ee05f1234 | |||
20e800230f | |||
37a29b979f | |||
1afc527919 | |||
d89174ee82 | |||
f6255c2f9e | |||
ae41c88eb2 | |||
41067de5e4 | |||
dfca2b510b | |||
8bc9d8988f | |||
f7279804b4 | |||
47e1ea107b | |||
799d6aeb19 | |||
f8ccd90eeb | |||
169b772398 | |||
d2e28b0f7e | |||
88bb55ffd2 | |||
5508ac6272 | |||
2be03ca631 | |||
9803f167bd | |||
6a161c740d | |||
5d99853502 | |||
c2ebf466fd | |||
3313b2ff58 | |||
e210e76bd5 | |||
b75438ff32 | |||
82fea9ce73 | |||
79e32c92c1 | |||
322fcea6e5 | |||
5650231df3 | |||
78b2e4df9f | |||
bf9c815b9e | |||
798065fc71 | |||
578aa439be | |||
364781366a | |||
fa64a0b367 | |||
ba46bc4624 | |||
c6e4641781 | |||
9cde67086f | |||
f8b36f4658 | |||
753bd77b41 | |||
a9276700ea | |||
1960ea8ed7 | |||
1b775044f7 | |||
570b98c7bc | |||
81fb9e6a59 | |||
c2761a1259 | |||
0f7bf28617 | |||
60e8cf5a47 | |||
10cf728e11 | |||
eca56eb87d | |||
54d0168746 | |||
e58e48e919 | |||
1f345ce2d9 | |||
ed85aa43a4 | |||
33e34cbba9 | |||
4b0250192a | |||
dd66d16fdb | |||
de82e60c64 | |||
72d227ae91 | |||
4713cb8675 | |||
fdaee4ab17 | |||
32312f3c16 | |||
95d15dc720 | |||
2db83e1a21 | |||
cfbfcb5734 | |||
c28633a949 | |||
7cf90766a3 | |||
f2ee01ace3 | |||
9fbbb17c3b | |||
5e31565574 | |||
723f9a9b81 | |||
baf4e767e1 | |||
c5e5342325 | |||
6123d2f9e8 | |||
788296047a | |||
9dceb8ac74 | |||
ac2374e9a1 | |||
667f9e0d79 | |||
57916f8be6 | |||
e12c577b16 | |||
ba7efbb136 | |||
79987e788e | |||
4a071b06bd | |||
17f169f446 | |||
6662986169 | |||
1c86160e16 | |||
c34cc4918f | |||
4870a2cbac | |||
da7d94d0f0 | |||
cdef065cca | |||
e6676b4d4d | |||
896351e0e8 | |||
fb39bd45d7 | |||
5ef012b2c1 | |||
9c9754fa0f | |||
2e921437cd | |||
5617162cb6 | |||
0c3ff6b75c | |||
7f53737000 | |||
23ea8ae56b | |||
b5f7a4bff9 | |||
18653b825b | |||
aa3694cca8 | |||
844d231d74 | |||
d759a447be | |||
ffae4662bc | |||
a05d772aa9 | |||
cf3bbc09b6 | |||
d25576f8ef | |||
4b42fa2d75 | |||
c1c7e0ff08 | |||
1d503faa2c | |||
18c0f76f89 | |||
4d458a5e00 | |||
92ea11fca1 | |||
cf2bcee607 | |||
db7a3ac826 | |||
f792171ae9 | |||
81550e609b | |||
c28d0d7c34 | |||
6cb0790796 | |||
c2961617bd | |||
08e59b4a3c | |||
7ac4ce637f | |||
586e0a67ef | |||
5aab2866e1 | |||
a20f12865a | |||
0bf1a24bf5 | |||
f9f5bc2eb5 | |||
9fe8c98047 | |||
13fc518268 | |||
c06876eb3d | |||
f331f1d1e9 | |||
054deb809b | |||
865ddfc63f | |||
315940b6a9 | |||
211cae5811 | |||
2c6599c73b | |||
58139ce5ae | |||
8e888059d8 | |||
8d0236e3f1 | |||
774e9df2e5 | |||
faae122375 | |||
a6363e56b6 | |||
214c041bf7 | |||
ae7700296d | |||
f09183765c | |||
2f92b92a8a | |||
fee97236bf | |||
520f7c3e18 | |||
97752b4937 | |||
2c8c2029d8 | |||
4fbe36d9c6 | |||
4f4618441c | |||
e5a7d08966 | |||
11fc684f3c | |||
d50aef8404 | |||
5637f88aff | |||
f14bc0bb59 | |||
c50d2a6311 | |||
284273a73f | |||
4c5d0fc51f | |||
75a92d58cb | |||
db18611c86 | |||
bf199a2ebc | |||
db05864a69 | |||
f97d33e3a7 | |||
16e3ba86d5 | |||
cc05019bbb | |||
f57e48a209 | |||
7c964cf79f | |||
c9e58743e7 | |||
a09cf1470a | |||
57dc46fcfe | |||
06b445ac07 | |||
b4da83a3ab | |||
a964570b1a | |||
50bbe34b66 | |||
c10b2e6cc0 | |||
c4ed80d544 | |||
67d07254c2 | |||
74a648accb | |||
35365974bf | |||
355a40800d | |||
701d90a41d | |||
56f6ee84f1 | |||
e2a5ec9cd2 | |||
aea0326b82 | |||
93ad637c5c | |||
6be5e21aaf | |||
43795193c4 | |||
62429585ba | |||
e987d0094f | |||
093b5b5267 | |||
678a5aff83 | |||
03dc4a20a1 | |||
de3765ab70 | |||
5f079137e5 | |||
94f0c081a6 | |||
229836511d | |||
f2f041bb7c | |||
3562774f8b | |||
374b776a3e | |||
5763d63737 | |||
9d805dfc59 | |||
e6390b754f | |||
7babfd00c1 | |||
571dc4e387 | |||
3ed34b571c | |||
d7e4c8e3cf | |||
57e90948a8 | |||
26a20a7e62 | |||
b6a8268da3 | |||
61d7467ba8 | |||
7fa809c16d | |||
84f74807d4 | |||
4f59077318 | |||
3a9c03cc89 | |||
f055d2f0cc | |||
72fb52ec60 | |||
62c22c6cb1 | |||
dbd337c616 | |||
eeda7338cc | |||
261ea00efb | |||
02647c25a9 | |||
433b0808e4 | |||
529b163bd0 | |||
9c9991db1d | |||
aacead62c0 | |||
ae5a6a06bb | |||
60320e6b6e | |||
169ece8226 | |||
5020a4aa6b | |||
4c49566a89 | |||
ab60c578b9 | |||
050021cf77 | |||
8240d1fe0a | |||
fd6e7020eb | |||
261b869e27 | |||
d6d5b4429c | |||
67d7375ab9 | |||
020d34187c | |||
5486e4c364 | |||
33e2af341a | |||
cca08c3923 | |||
bb9f07183b | |||
22e807c212 | |||
a60a3efc1a | |||
558a362c46 | |||
19ae556857 | |||
5dd3a07a23 | |||
58a6c9a5f0 | |||
7053978861 | |||
3d44cffcda | |||
4b1de02bbb | |||
078a3aeccd | |||
abaccd6882 | |||
3fe54206aa | |||
debee350f8 | |||
890be36fd3 | |||
c9be9acd14 | |||
8eab673b1c | |||
e5806d07a6 | |||
11e6197a83 | |||
accd49f2e4 | |||
54cf9aaa1e | |||
8bbc8343ff | |||
a4e72ac037 | |||
1d0be265d9 | |||
d379786c90 | |||
ca9d4e34df | |||
6657312f44 | |||
2636a9c9f1 | |||
05ada97d00 | |||
4c54245969 | |||
5157bdd8ce | |||
8fa28f965c | |||
51b3451e20 | |||
fee5c6c057 | |||
9917ece826 | |||
8d94972d88 | |||
5cbd1190b2 | |||
1a71804ef2 | |||
1650519962 | |||
355564e486 | |||
1e3543e953 | |||
e83f6332bf | |||
0dbf7995b5 | |||
0d16db2d1b | |||
10565277d6 | |||
e0858cfe06 | |||
48d754220b | |||
958cbe688b | |||
783e8672e7 | |||
d93b552e8c | |||
365fe70f77 | |||
6c4e656795 | |||
86213d38fe | |||
b757294864 | |||
8b99e6dfbe | |||
0d4a2c5eb0 | |||
64f23ab26a | |||
31a276b628 | |||
742562fc2e | |||
ce65604154 | |||
75c0a268e0 | |||
badcb8b0e3 | |||
c48c9be913 | |||
92295dea4f | |||
76223f5ae7 | |||
ea015ccbe8 | |||
2f50d0e145 | |||
268beb3489 | |||
20d13f51a9 | |||
ffdf36c65b | |||
ff608992ee | |||
7e31a67d81 | |||
c0ec2ca27a | |||
a2595b44c6 | |||
180f415736 | |||
6541d9fbb0 | |||
de4f564780 | |||
14cb6353c0 | |||
9e680112e7 | |||
c90595cba1 | |||
de1636c792 | |||
e26f68fe62 | |||
39ba9cb489 | |||
08d4570ce5 | |||
084706c5ea | |||
d63518a835 | |||
b31d334ef4 | |||
5c4c562a2d | |||
f10438d530 | |||
7459eb15c3 | |||
c44e7ce184 | |||
bd19fe5909 | |||
82615c703b | |||
bc2141fbe0 | |||
f5964b4f3c | |||
d5ba90d375 | |||
167adff22c | |||
5f54573613 | |||
2b43b117dc | |||
1aec9e38fa | |||
c1880e3f3e | |||
c490a50c91 | |||
ee791e2e3e | |||
140d4ccf77 | |||
ceacc42126 | |||
a6479eb6e9 | |||
84c8a5bbec | |||
e1f4e8a84a | |||
8135279335 | |||
5dceeec1ca | |||
8f5a1535af | |||
92a5979558 | |||
8b64de0a3c | |||
9c30e98df6 | |||
c1d788880d | |||
385086359c | |||
176c7d8b13 | |||
a85604b2ba | |||
bf1ecc2441 | |||
92d2452f33 | |||
1853771930 | |||
772ee4b29d | |||
c62a4a1c13 | |||
008dcd71b9 | |||
ee4266bc59 | |||
294d531e0b | |||
e05f8faa74 | |||
fc4aa71193 | |||
0d7efe5176 | |||
b426dfb2c0 | |||
fd33b27af1 | |||
39f89e5a56 | |||
b881029de3 | |||
7682db4826 | |||
61fe1aa9cf | |||
468095ede2 | |||
9dc5da7dbd | |||
a18cd29411 | |||
b13c690f0c | |||
a7fd726872 | |||
6a082d2310 | |||
a317e9513f | |||
ee0c570d54 | |||
7607800d47 | |||
b35c022629 | |||
11cec8f24e | |||
df205f8752 | |||
affcb5ec43 | |||
bdda79343e | |||
1833db51a5 | |||
81c36699c4 | |||
d3052d094c | |||
4c4b7d39b8 | |||
e8d88f3237 | |||
cc8575dd96 | |||
f28782cb84 | |||
c58e7dd631 | |||
d9817c153a | |||
6057768fdc | |||
4a20c2aa1b | |||
e5f902369c | |||
1f9fde5f7b | |||
c3782082bc | |||
a452249bf3 | |||
3d3b03a123 | |||
719c03d33f | |||
609b18c2cd | |||
5279b83d34 | |||
05d2eec45c | |||
0cbc0dc79c | |||
9210f40c38 | |||
3237e897d7 | |||
f1110f2e85 | |||
5ffb6b874b | |||
c4a5442146 | |||
bd74e63702 | |||
f78b865cba | |||
7062fe4b47 | |||
b6da5a3f47 | |||
5fb2d7a98f | |||
ceaf4781b0 | |||
933e835838 | |||
94eb78d399 | |||
02ee2a601c | |||
b19d9a50d3 | |||
355640b5db | |||
dfa6238342 | |||
3b0d48e3b8 | |||
2b696ac8dc | |||
8362b408d9 | |||
62f6a78ccd | |||
f7e039e7ac | |||
61bd14c40a | |||
5dd85f1533 | |||
0d20bc5e14 | |||
a82754913f | |||
5840e3bbdf | |||
e8ab599bae | |||
85e5fbeb35 | |||
475f6fe666 | |||
9f354522a7 | |||
0c2a49391a | |||
e3a6c9234a | |||
6089c8030b | |||
643d0b0868 | |||
3cc5d8df7f | |||
34155fc36f | |||
f840eefcbf | |||
e1f3e33bfb | |||
36fcb4fbca | |||
22667d64d1 | |||
4786143524 | |||
f78baf80e4 | |||
33e7e23484 | |||
50214f059f | |||
57f778bcdb | |||
c3f07eb85a | |||
8adac30c05 | |||
5a5a6b3840 | |||
2803eb0d72 | |||
f41fb7d772 | |||
156399e8aa | |||
5745a54d4c | |||
3548d42a6c | |||
7dfb735db9 | |||
1609765740 | |||
2510f3d352 | |||
50ab34ad92 | |||
47535b9ff1 | |||
ffc748becb | |||
34ab25a88b | |||
8b9c3a2561 | |||
362a39a941 | |||
9f2119920c | |||
afb24d28ca | |||
0c62cf8980 | |||
f1d58f980b | |||
b1dfbf0ac4 | |||
12ad95eb5e | |||
7aaf5bc02c | |||
85f03b590d | |||
a29f0484dc | |||
8e6e72babd | |||
def71164f4 | |||
eda46d30bb | |||
d87910eb15 | |||
7257d2845d | |||
9744eb0ccd | |||
a273ddcd97 | |||
99a97b7008 | |||
3d098d2ed9 | |||
db768b4c3a | |||
4ac1213c9c | |||
a0f3208828 | |||
97db802be3 | |||
28f2c75137 | |||
81bb208a62 | |||
6979a17674 | |||
bd20c5e791 | |||
b4935ff4ed | |||
e1dd74f1bf | |||
e2ecacc141 | |||
6512aced21 | |||
615da845cd | |||
2c7f49c3e6 | |||
ba59741b60 | |||
52da207f83 | |||
ef8eff69e4 | |||
1abdeca4c1 | |||
6e82978931 | |||
4e827af392 | |||
f6b63a7dbc | |||
6bb22902cc | |||
881a6dc0f7 | |||
877e7a3893 | |||
bb80116605 | |||
0ffe7a9c8f | |||
9b8d59d2e9 | |||
f7bd7a41d2 | |||
3fc5009ef2 | |||
bde4ba04af | |||
f1ad69c84e | |||
97ea75a890 | |||
52f6da5cee | |||
aeaa0feb61 | |||
1207664bbb | |||
19d16e75c6 | |||
51cf559ce1 | |||
63d62c33c6 | |||
919c066e5a | |||
4125d01668 | |||
087c43b9ef | |||
c18ea3ccc9 | |||
564b590c89 | |||
d36ecb5c91 | |||
e2d6f01ad3 | |||
5034331131 | |||
faafee6b42 | |||
80f618f011 | |||
84f763d079 | |||
0dc0594aaa | |||
d651cb7a25 | |||
f18aa4e423 | |||
ab4f370e15 | |||
d6f824abc0 | |||
3450b9a44d | |||
afaf95cf53 | |||
8c371dd2fb | |||
bb558acdf0 | |||
159e518671 | |||
4798e7fa73 | |||
f4534ef12d | |||
8e0f41a790 | |||
b1203da82c | |||
e366fb6328 | |||
32de5e6e7a | |||
93ae98812b | |||
2c2de12e88 | |||
bd193535c9 | |||
d4d1e5e15b | |||
f7a670596f | |||
a8b82a0b68 | |||
bb25a06baa | |||
8b7cca986a | |||
626e16a177 | |||
814af378a7 | |||
a252acf539 | |||
01eb7600d9 | |||
52c2191545 | |||
25403e61ed | |||
f402b477b2 | |||
8df8f84701 | |||
ccee6241a6 | |||
4d13d3871d | |||
bb0c9d6145 | |||
8d105042ea | |||
84304cb0fc | |||
89fe297416 | |||
d853b20d7f | |||
b28407d98a | |||
4fa795b026 | |||
c298474e6f | |||
d925902b3f | |||
99eeb63f71 | |||
ff95f6dcfa | |||
8258532791 | |||
e73cbdda61 | |||
94f1132fb6 | |||
4ee212ae4c | |||
d5fb493aa4 | |||
88ea950652 | |||
e4519d6447 | |||
471bc73a23 | |||
75a2b74751 | |||
4e69408f54 | |||
38602d60b3 | |||
1fe1550a30 | |||
827f2b3a5c | |||
a948c9b7f9 | |||
1363841f32 | |||
4688f9821f | |||
0c90c889cd | |||
9f6c9c428b | |||
fd443d85c4 | |||
b4f0f4abcc | |||
d22848f9b1 | |||
79416381dc | |||
d791c70d90 | |||
802537564b | |||
1d0608200c | |||
cd14a940d8 | |||
58d4e32c97 | |||
1b6a200d6f | |||
08f6a2ea3e | |||
97d57d168b | |||
2b219228ce | |||
07d11be6ab | |||
7981431f09 | |||
a43922ccbf | |||
687818aad6 | |||
b7a5136136 | |||
0fde19239b | |||
771d1a78fd | |||
a8eb0409b7 | |||
b6151b5200 | |||
c68ebbb0a6 | |||
1b84092b94 | |||
b1d43ace14 | |||
6085109171 | |||
cd89f280b7 | |||
54f4d13350 | |||
799d3b1575 | |||
b3b782988c | |||
5e128f8cc2 | |||
c8c0815144 | |||
d59aae4849 | |||
342733be54 | |||
2da7601084 | |||
958c345f0c | |||
fe83c66686 | |||
5884469d11 | |||
9ee5f36068 | |||
c02373493b | |||
4090600717 | |||
8a4179da67 | |||
ed093f86f9 | |||
07a049aa59 | |||
7b77fbd525 | |||
e1e295e1b6 | |||
5b4ee36cfd | |||
784943ecab | |||
4f86c0b74a | |||
5b4f24eabd | |||
a2986d3b6b | |||
032d523737 | |||
238aa2133d | |||
eaf1b91148 | |||
4ae48b56f3 | |||
8c15214923 | |||
7a603d72bf | |||
5b51bb27b6 | |||
8231d2b672 | |||
6597c71e23 | |||
e30ca01999 | |||
12bb05c320 | |||
8aa7a851ca | |||
2a17e90b7b | |||
f154a53e5e | |||
7911895b67 | |||
d6aaab0b2c | |||
be9fa22db7 | |||
b72c5689c9 | |||
9dcf3347f5 | |||
72e9492ca6 | |||
572e942413 | |||
3ae9357a36 | |||
1dbb5c8647 | |||
06d8c06119 | |||
cc0e455a51 | |||
a01520e694 | |||
c524d62ce0 | |||
dd4640e1ed | |||
42c7d57fc0 | |||
efd09ecd37 | |||
14f6d5c82b | |||
c7710fdd24 | |||
b5aa03dd7c | |||
a81dd80d60 | |||
09ca92d416 | |||
56ed033233 | |||
e56efe237c | |||
3f0ff45de0 | |||
3709dc6558 | |||
6ec0318bae | |||
92e419f1c7 | |||
ccc0f2d956 | |||
80bb0158b7 | |||
f12592826f | |||
8d38777c1f | |||
832dfd4ab0 | |||
04d2db4dbb | |||
6f269e5a0e | |||
eb3991b9ba | |||
aee63f15c2 | |||
aced847735 | |||
e360e63b74 | |||
a6c4525998 | |||
77b196a226 | |||
b6b9c2cf56 | |||
59d900977d | |||
0f5acb86d3 | |||
911dee24c5 | |||
f03e066ec5 | |||
f7d3f55566 | |||
4298b1f595 | |||
870503ee36 | |||
4d14abbd04 | |||
5212b2716c | |||
97c0573c7d | |||
43cc9fcb1d | |||
47b5ba44e9 | |||
e95397e0a8 | |||
c7cdf8ba93 | |||
6ee734e1b4 | |||
3ab1b46ef7 | |||
22891b39d6 | |||
b6ce7ec782 | |||
a41c7451f1 | |||
6cb2040a1b | |||
937f9ad049 | |||
c2fc0f2418 | |||
9278201198 | |||
149a63100d | |||
d09afdbefe | |||
1d6bafbc77 | |||
01d2b4e952 | |||
05f3437601 | |||
f859243191 | |||
9ddc25283c | |||
388d4a8592 | |||
0b0b679120 | |||
3b752876ac | |||
9b8b7dbfd7 | |||
c209e14e40 | |||
6df1f6450f | |||
6d7cb23c61 | |||
bd7e269280 | |||
b05b42d74d | |||
af733a678a | |||
8a5045f05c | |||
4a336eb5ff | |||
b7e08052ae | |||
f6a4acfac3 | |||
68eff230f0 | |||
c78db6a94b | |||
294d9288d2 | |||
7dc5cc26a6 | |||
d7a2b790dc | |||
a7a10e12c7 | |||
8d243221f0 | |||
84368697af | |||
4a57cd3300 | |||
2214d2dbb5 | |||
50a991fdf9 | |||
4e093525c7 | |||
506b305959 | |||
e83efcfc80 | |||
4f1c881227 | |||
a642168369 | |||
8d296d0969 | |||
68b11c1c29 | |||
c209718a6f | |||
b8835312bb | |||
7796e87814 | |||
64c770275b | |||
855f7ff352 | |||
b59a99111c | |||
252257fe66 | |||
e2c9d87d91 | |||
9d34b80ed6 | |||
c63a38ae57 | |||
20da2604f8 | |||
33de2cad6d | |||
aef7bae60d | |||
54ac7ed1ea | |||
0180246680 | |||
dab7de7496 | |||
feaf29792f | |||
5f09aa36b3 | |||
d6c74f438a | |||
349ebec629 | |||
f4554be72c | |||
8537da19bb | |||
d1eff5d607 | |||
19e4f70244 | |||
a233a1c822 | |||
27bc0a22dd | |||
7ee8383e02 | |||
bab0f6be1e | |||
535df0026d | |||
3bd35dd7cc | |||
39d29fab82 | |||
fbfe1a59a6 | |||
77c79effc1 | |||
83540087c3 | |||
937816e67b | |||
c3a941086d | |||
1046c5e32c | |||
baac8d2590 | |||
610a02c518 | |||
444bd7a702 | |||
7afc61e0b9 | |||
d4d9bec2a9 | |||
d647a4ec57 | |||
536b4c1a25 | |||
547a7a345f | |||
26e380e53c | |||
8a12ed029c | |||
b41e8333b1 | |||
8f646e21d7 | |||
5608af0246 | |||
17b9ea3e3b | |||
88d4d1db7a | |||
cab4c88c71 | |||
4ec5a899f5 | |||
c2f74330ef | |||
2c8e0bcf87 | |||
4966ab528e | |||
5f81a67298 | |||
a0ccdccff1 | |||
735c7c9841 | |||
3a69459645 | |||
21cef2fe21 | |||
038c6ea0a7 | |||
81f4fd56c7 | |||
264a3d7dde | |||
43bf176fab | |||
baec17fdf4 | |||
186b514ebb | |||
2d42c1e33e | |||
9cef522eee | |||
a6302acfd5 | |||
ac72265c6b | |||
09da6b4b48 | |||
0d8f5379a0 | |||
02c7b89a8f | |||
90ae33c200 | |||
55c879ce2d | |||
1b5a332239 | |||
595017499e | |||
9b1471acae | |||
b766ac0899 | |||
e6b525a614 | |||
a07b17b9b5 | |||
9d2940d487 | |||
6969ece2dd | |||
48fc35884c | |||
0958905df8 | |||
c95cda51c9 | |||
3f54c0f1a6 | |||
4684faa5e8 | |||
111d0eb89b | |||
8b69998379 | |||
a21251dfea | |||
06cd7c1020 | |||
782846f295 | |||
19e131d710 | |||
9fd34cd985 | |||
adfb8ff2a1 | |||
83aa609540 | |||
1e1cb7c57c | |||
cdbd1b908a | |||
a12e7a2e33 | |||
25080f1a33 | |||
afa05acb32 | |||
d47caf2af8 | |||
a3a91ba222 | |||
751b54b60b | |||
488dd0e563 | |||
b58558ea4e | |||
6ad9dc18d8 | |||
027ebb6670 | |||
0ffd91df27 | |||
10d85f8366 | |||
7aad427511 | |||
bbd0455418 | |||
5174b3bc3f | |||
f88c72c41e | |||
9f678cc32a | |||
57036fbcc1 | |||
349e5001d6 | |||
94db9cd412 | |||
b505a0df22 | |||
acf096c5f7 | |||
e8583f5cfe | |||
5825b967d2 | |||
bf5bce50a4 | |||
77ea8b9b3e | |||
176cec6215 | |||
5ab4975c44 | |||
7e60ee39d9 | |||
3ea2933e2d | |||
fe87c05423 | |||
6b86f85916 | |||
04649de6a6 | |||
92d78451b1 | |||
0c87928132 | |||
db7e78bf99 | |||
adecd4cfdc | |||
40faaef9da | |||
9b54528c8e | |||
440d006ec1 | |||
6c49b10784 | |||
c858d1dbb3 | |||
741a0a8a4e | |||
16b6576839 | |||
6accf21229 | |||
d2b21ce8d0 | |||
b01990d480 | |||
d7fdfb7e21 | |||
19fe468dbc | |||
259a5130a8 | |||
0d27515d09 | |||
1c966aac25 | |||
d2b6c2e0ce | |||
7aecb87bce | |||
4a02914b30 | |||
7c12ecbe81 | |||
f093377805 | |||
5ac173d208 | |||
9f58318fc5 | |||
ebcdc06dc3 | |||
22315d88e7 | |||
0a36a78133 | |||
a25446f045 | |||
2860d2fe27 | |||
e4861f52e0 | |||
5698d48dc8 | |||
5b95685e12 | |||
4c90898f0b | |||
a191f3fd90 | |||
b2c776eabc | |||
2c8d6f87e6 | |||
08f6de0acd | |||
bd92f37553 | |||
2abbc89dcd | |||
8cad992170 | |||
41d0db078e | |||
8781aebe06 | |||
727c15ef8a | |||
e4926e4110 | |||
b50a3bae72 | |||
35ec7a5156 | |||
a383ea532f | |||
b1a678b2db | |||
e563a4dda3 | |||
dbe533385e | |||
f537482c86 | |||
aebd70ddce | |||
7d80cfb17a | |||
b8e7736af2 | |||
32b55e6703 | |||
0a949677f0 | |||
f777a1a74c | |||
d111223085 | |||
1ca7e9f67b | |||
bc8f435d45 | |||
5e221bf219 | |||
fc58b3e8c3 | |||
1033f52877 | |||
4771177f9d | |||
50c6b5d62d | |||
f9a2254688 | |||
49250f62aa | |||
22ef3c7c54 | |||
417e8d5064 | |||
1feb9bea21 | |||
563c42b829 | |||
841e5e326c | |||
281deae102 | |||
c5ba2e0883 | |||
eb4edd75e6 | |||
bb6bcd79c0 | |||
ef7022d638 | |||
2aac094f63 | |||
fc180f4cbf | |||
e26a0bf840 | |||
3557975c1f | |||
b4aebbd991 | |||
db13b52e6a | |||
f1f6537837 | |||
2ec5d2c7f5 | |||
42e5623e26 | |||
ab9f2adc69 | |||
f551b34725 | |||
55b8ff72d0 | |||
bf319ab06d | |||
12ef0c25b5 | |||
8620d0a3b2 | |||
933ae51fcc | |||
c1201e54fa | |||
3615445a12 | |||
091999a17e | |||
417066ad30 | |||
2abe051a1f | |||
65adce65fa | |||
0c8f187993 | |||
cbd2938035 | |||
0999225794 | |||
38b44f2496 | |||
c1953dca8f | |||
19ea5fe0c0 | |||
d7ed3b8024 | |||
a89589a1d5 | |||
41bda18046 | |||
0c832f4668 | |||
75b494d4a3 | |||
f0191a98ab | |||
76413cbfac | |||
0fa1af5d47 | |||
af1c70f032 | |||
278614fc7c | |||
baca35ef4d | |||
66552d7047 | |||
979df17328 | |||
6cec61dcfc | |||
a9b044f0ab | |||
fbea9d8621 | |||
44a572416d | |||
97c97db97e | |||
b8ae025f90 | |||
27221e28f6 | |||
9a52b01171 | |||
8cea650535 | |||
531679eeaf | |||
850f77ab3b | |||
4a10fd3272 | |||
9e2eb9e4f9 | |||
8120b57f17 | |||
f651c0922a | |||
8d2ec20201 | |||
dce1f80aac | |||
df1c473341 | |||
8a64e1ddc3 | |||
eb47538a82 | |||
861d6468ca | |||
d6737b8cc9 | |||
30592f2b12 | |||
1f950781c2 | |||
f20ba423ca | |||
c5e6ebb496 | |||
9e7f618cff | |||
74a06e4230 | |||
70f93cc126 | |||
3f8ff23125 | |||
29611fb61d | |||
407b1d3e6f | |||
206e62271b | |||
4e78354ab6 | |||
1561d9c8d4 | |||
0e1480b84e | |||
fd6047d1c5 | |||
b0467be393 | |||
1b0b095813 | |||
bd43724dfc | |||
11992946a4 | |||
0cc8a841ab | |||
23b6b85bf0 | |||
96b56fa6f7 | |||
405ca1bcb2 | |||
c6316bb24b | |||
b7f169e06e | |||
e4b466874c | |||
9911942dbd | |||
8acbb4ab2f | |||
a49f5378e2 | |||
f39e74f0d7 | |||
22b767308a | |||
36aa876833 | |||
06ba0b7279 | |||
a38e1a81ef | |||
da925142d1 | |||
5feeb257bb | |||
06c547094a | |||
a40c5cf185 | |||
deb83cdef6 | |||
20db335aed | |||
407db65336 | |||
9c5a3cd277 | |||
138a49e820 | |||
36c9e22e3d | |||
aa0f8538ed | |||
4177c56c51 | |||
425ac8d520 | |||
ada4d16c4c | |||
4069ef2e02 | |||
ace98bba08 | |||
e59b53dfa8 | |||
aacb38864c | |||
33d13a3aea | |||
1f0f947ed2 | |||
6854c64a09 | |||
4a32bc48d2 | |||
b430762a23 | |||
f8523db51d | |||
48b11d1841 | |||
3600a926df | |||
c228792967 | |||
7ea522e851 | |||
63503ad589 | |||
9800e09431 | |||
2e2b1881f5 | |||
61483c18ca | |||
a5279bb835 | |||
357554b209 | |||
41fbdc6e08 | |||
8bd1c57448 | |||
2562e48b9d | |||
46bb79df29 | |||
6bc0d2a0cb | |||
465cd45833 | |||
b4484b89c3 | |||
c029f069f0 | |||
fdb57bc5db | |||
e43a634944 | |||
2da7c7fbd3 | |||
5683282c94 | |||
44967abd1c | |||
8b41a5d725 | |||
07c183bb84 | |||
7fd879b417 | |||
dc5c6e7cf8 | |||
bd633d2b81 | |||
feeaad619a | |||
b44d8c394e | |||
0ff9c4cd8e | |||
9cafd1f85e | |||
7fe10ba060 | |||
cc48773b03 | |||
8fbf0e2d9f | |||
d86358eedc | |||
fe04fb4cd3 | |||
de3f7e9634 | |||
5e8fcdbe1d | |||
3ee7256c0c | |||
2a7a9fdf03 | |||
5bf87de136 | |||
97a136ea20 | |||
735dfab02e | |||
b5f65ce49c | |||
a283863694 | |||
25908feef9 | |||
b91ad6fd96 | |||
02abf422df | |||
3fe5f886d7 | |||
4c6a6d63bf | |||
589a9d3a72 | |||
bd884a56bf | |||
119467df59 | |||
ee68b9800e | |||
c6b4a3a706 | |||
b1ac8f933b | |||
9e3758983d | |||
34c0537e9b | |||
8628f33d0b | |||
ed05aeaef8 | |||
e1444a9b00 | |||
9514169bf6 | |||
fa8394f526 | |||
1cd8c1865e | |||
e3f895d7d4 | |||
8abf22f34b | |||
a016bc2736 | |||
470debef16 | |||
c147dc3028 | |||
bdd95b2286 | |||
efe676bc94 | |||
fc34687687 | |||
6042ccf496 | |||
f1197e1b1f | |||
8c1b9a0b67 | |||
0da9ac1a47 | |||
c1f316721a | |||
8e86014311 | |||
d807217be7 | |||
bc44516eb4 | |||
b78a13d42c | |||
0dcdc37fec | |||
dd1c3514a8 | |||
767efab941 | |||
288a3bdcd9 | |||
8019bff391 | |||
575a897ffc | |||
697228a484 | |||
ca907f37c3 | |||
439e7cc26a | |||
3217a1d70c | |||
6dbba86cc6 | |||
8cc863ea6c | |||
1d957b6b80 | |||
e56430c9fb | |||
e4d8ea11ac | |||
a4035a3c65 | |||
807c69d97c | |||
9259d342ac | |||
b4d4edb645 | |||
966b6999d1 | |||
73491e3ca1 | |||
d1d53c3fb6 | |||
a77e576cd9 | |||
9e14cde461 | |||
a2a7c86c0d | |||
38aeed02fc | |||
64d63966c7 | |||
38ae54b720 | |||
a18c0e34f4 | |||
be3a0b6b10 | |||
9f6496d38a | |||
1fa31c9410 | |||
2b5e757d57 | |||
0dbe5ee559 | |||
6926e89e86 | |||
ec0007217d | |||
91b23f8316 | |||
2fd8d57504 | |||
0595109f98 | |||
9f46b2a6ce | |||
a357d08524 | |||
177c9cc026 | |||
0c4cb76acf | |||
8676b5d40c | |||
efab896c9e | |||
97b9d57b62 | |||
487826a539 | |||
4acb764589 | |||
9de4c1dcd9 | |||
e8c4302d6d | |||
a9f73ea321 | |||
66c41b3e8c | |||
8435fbfa0b | |||
9a4c449135 | |||
ac6dbf8f04 | |||
b55927370b | |||
002fbc4d53 | |||
53deb7919c | |||
8e46c44f3e | |||
37c2fa1d8d | |||
fdaa939892 | |||
c9d63204eb | |||
cfab54511b | |||
492cc93850 | |||
fd9fd43e83 | |||
191483f4ee | |||
688f8a669a | |||
46eea85022 | |||
1c765124e7 | |||
194491ae96 | |||
2ae595294c | |||
ead947e710 | |||
82df267ec9 | |||
53275cc678 | |||
44835a91db | |||
ee42040e6b | |||
2b98a16ec6 | |||
aa4a7b0c73 | |||
8f50c3dd2e | |||
9c47ce30a7 | |||
3433b08b8c | |||
d26fd27bf9 | |||
5c98c1d306 | |||
51aacfe3ca | |||
82bd2df986 | |||
aa88c40a9e | |||
8ec5a47027 | |||
5bd3eb4557 | |||
e9cb4a12dc | |||
de5cad9211 | |||
e3365529de | |||
ce2ce76958 | |||
16f2fb5c09 | |||
d77c98530f | |||
fe40b75ac6 | |||
e7129757c9 | |||
3635a68129 | |||
70a16e91a5 | |||
41daf1ef0c | |||
ff77789718 | |||
a77775cb58 | |||
167e15a5ae | |||
dea663d509 | |||
9754e551cb | |||
40a4ac15f1 | |||
c56052ff16 | |||
482ef51502 | |||
e4ca3900ae | |||
3574469052 | |||
e15246746d | |||
ec5cca41bc | |||
bc1368ba3e | |||
c0a161afe8 | |||
d343c409e6 | |||
64e8a21d73 | |||
ce04d2bfc2 | |||
1c1d83bd56 | |||
028e111fbc | |||
9670788bf5 | |||
d2f9625878 | |||
182096dc1a | |||
2d284ba6db | |||
1de805e7cd | |||
d642125f68 | |||
b8aff218e2 | |||
045d4d5294 | |||
d67dd8ce1f | |||
4d6679906b | |||
4537f54532 | |||
39b40dfff8 | |||
c82f4a1b6d | |||
7a021dff05 | |||
348c2263ba | |||
b5324063f1 | |||
6ed071c4dd | |||
4404634b14 | |||
6a1de33138 | |||
c05c3e69ca | |||
534244b322 | |||
335dfdc4d5 | |||
a7ef409c2b | |||
14594217db | |||
c8a03c7b3d | |||
9fcd162412 | |||
441fed7a5b | |||
ff31ffbd54 | |||
0e26ee854b | |||
5340800cea | |||
13c2e50b38 | |||
dd39b2b056 | |||
65f89d6729 | |||
1eceb4831d | |||
50303c9ede | |||
ed6a438c51 | |||
2adb98a4a0 | |||
471465a5f4 | |||
942785b626 | |||
aa3c00231a | |||
d772a27936 | |||
0302f13b97 | |||
16b25d0874 | |||
c2dcbee6af | |||
1f71d05299 | |||
bfa1c025fd | |||
8611b40074 | |||
916844d399 | |||
4c9b7c9d2b | |||
9843c3a5cb | |||
f56955a17c | |||
9784bbf154 | |||
45642c4da1 | |||
8eac199e8b | |||
2e251ccc5c | |||
cf4bb70d80 | |||
57f8a15b96 | |||
cfe5afd34c | |||
94beb4b8c2 | |||
50207a30ef | |||
35e8f966e3 | |||
943cd0a24a | |||
0b892b2579 | |||
fb2eac20bb | |||
b37d2fde3d | |||
6b35e16676 | |||
6a9e0bc593 | |||
591fd72e0b | |||
2ed77b040a | |||
7ada8510c4 | |||
b8f6c17dee | |||
2f976ae460 | |||
36019cb1e3 | |||
99d2428041 | |||
c121498b5b | |||
eef2bdf690 | |||
190656967d | |||
90e73515ed | |||
1d7a758c97 | |||
e5b7aead12 | |||
578c2ad3ea | |||
de6838da78 | |||
604071c5d8 | |||
41a377013f | |||
52d453d06f | |||
58295b825d | |||
f6c7812fcc | |||
2f7561e4ee | |||
1cbd2372fc | |||
28f948aa7f | |||
c9ba9e4eb7 | |||
f877fb8c8f | |||
772ba41ede | |||
6374e69a69 | |||
ef0580bd3d | |||
1a77486f8e | |||
ead15d294e | |||
1acfcf3acf | |||
d15e248cdb | |||
f1e5edee14 | |||
7153abd483 | |||
90fb5d074d | |||
af82b0dce9 | |||
d4da2fbacd | |||
77efe95730 | |||
86e03a6d1b | |||
114e2989fa | |||
7024c73e9b | |||
6d418aa3f1 | |||
f079a78c5e | |||
6365c4c061 | |||
55cee5742f | |||
034eda4546 | |||
44ff25d044 | |||
a7e160e5c4 | |||
6283cc916d | |||
4b6aca6120 | |||
20b2be6e0b | |||
cbebc7a80c | |||
06eb2364f2 | |||
167890ca63 | |||
392a39dd54 | |||
7e1a7862db | |||
458ae3fdac | |||
431cc82032 | |||
18c6729d6c | |||
9476fe5ce3 | |||
788290ad82 | |||
6b5bcfaa58 | |||
4ed0cded9c | |||
035a364122 | |||
b114bc3674 | |||
bc74ee7117 | |||
b2ce5dc9f5 | |||
e920191de0 | |||
39e85a3e53 | |||
41156da4ca | |||
9271ba0039 | |||
b3e45fd6b7 | |||
7bfb60f82e | |||
359c50f1b3 | |||
fff1631a8b | |||
7d42ae30d9 | |||
87414de3e2 | |||
a0ffbf50a5 | |||
d40b66ff7b | |||
abd7f6b090 | |||
d8735df1de | |||
481853e1b1 | |||
778bcbce50 | |||
fd3f2cb910 | |||
915956b94b | |||
4576250342 | |||
2bef1b0433 | |||
628128b376 | |||
916017ca2c | |||
3204a00e73 | |||
1d327a5167 | |||
6e4f9cedf2 | |||
a79fbbafc9 | |||
1d54d29076 | |||
10b9a4806b | |||
0c1191c3ee | |||
18b386cd10 | |||
714b8c7fc8 | |||
216e9a61a0 | |||
0f498e6265 | |||
e8ad822111 | |||
65a82ebf50 | |||
727802684c | |||
e20a8329d3 | |||
88c2d0fad4 | |||
3bd921264a | |||
7501ed65e5 | |||
2eaa64c4e8 | |||
c9b86018c6 | |||
a4fb01b42b | |||
0d2574f8f0 | |||
796000e96f | |||
e2f00dc205 | |||
5e91f8f59d | |||
e2830f5b0e | |||
a2e3a92b01 | |||
23c696706b | |||
1393d26f63 | |||
1b68da7572 | |||
8542006259 | |||
426d06b89b | |||
06378d6db6 | |||
dccfe31e8c | |||
1dce5976cf | |||
340d01665c | |||
50f79e495e | |||
dd12db2f06 | |||
1afccb7351 | |||
bfc65e829e | |||
eb4515525d | |||
55f5f6a033 | |||
7ae421eaf6 | |||
e7da2c0931 | |||
133be2df51 | |||
06a93dcb43 | |||
ad7f04a245 | |||
0da6e1af14 | |||
576524f13b | |||
f567877d1d | |||
9881820444 | |||
ba8f49366d | |||
81fa69d347 | |||
abf2b300da | |||
a8254fd258 | |||
b15848de3b | |||
ab3c988146 | |||
575a0e318b | |||
a031b09190 | |||
df43e721e3 | |||
5f72650c7f | |||
5d0d467287 | |||
994515d0f2 | |||
1e949caa7f | |||
f2b727b534 | |||
f7680752e7 | |||
da4c37beec | |||
d486d2b8ce | |||
bba94c43b9 | |||
9cdffc7d64 | |||
5a86f2506d | |||
518227eac0 | |||
b8fd51e97d | |||
965c1e0000 | |||
a80176496d | |||
5719b8f251 | |||
1a2b131ceb | |||
349306ddf7 | |||
791ee411a5 | |||
f690c64375 | |||
427963f554 | |||
b0f2220ef6 | |||
908b48bf0e | |||
b49f8c0984 | |||
7609a007c6 | |||
674a49f8d7 | |||
d10bde656a | |||
401764ddb1 | |||
69eeb7cf08 | |||
55e3b7d380 | |||
d9e18a71ec | |||
2107e15bd3 | |||
4f3b22d04e | |||
2c78a93001 | |||
2621aeee82 | |||
8e400fc4bd | |||
29c2a63c8b | |||
736ada4e21 | |||
3df9b44d4c | |||
7225b89142 | |||
0cc0d3ab7a | |||
88d9618788 | |||
57038529e0 | |||
5c25eae631 | |||
b8b4d33f72 | |||
673a9417ef | |||
3fd9aada8b | |||
453fdb9e28 | |||
3f6a79b078 | |||
e9f80e5542 | |||
694d28acf8 | |||
88fdba5aca | |||
a19df7a36c | |||
9b50583641 | |||
71f9b44687 | |||
0139e5db21 | |||
586fb15c2c | |||
297328ff9a | |||
6b3384c205 | |||
3ef961fe37 | |||
a7b695c27a | |||
5bb75a5894 | |||
f3f416b7ba | |||
31b74bdf0b | |||
ed48d8323c | |||
f91627a230 | |||
f9c093022c | |||
7fe3c75c6b | |||
c8ed41167a | |||
5b2a82a951 | |||
441e76ebeb | |||
c2dfb9900e | |||
916458e132 | |||
ffb15578ce | |||
abcbbb925f | |||
059755fe59 | |||
ae12dc2c75 | |||
37b5c6afaa | |||
92ed7b36a2 | |||
379d2e6d95 | |||
7f75cc8906 | |||
1ab5098576 | |||
598f765960 | |||
aac626c2c2 | |||
3eec3cfac2 | |||
5eee9e62e5 | |||
a7d18125d3 | |||
8202310073 | |||
1e2ba110eb | |||
62c9b7d850 | |||
4f18fc836f | |||
950d8494ba | |||
cb528af4e2 | |||
ad27c30623 | |||
9add8d0afc | |||
af2e7ea285 | |||
675a78aaa1 | |||
408bdbce7a | |||
1a259d4a3f | |||
c5f8b4960c | |||
21f845ed39 | |||
7a369df9a7 | |||
f02ec31c68 | |||
d21fa4a177 | |||
bd0871cbe7 | |||
2604f8ac0a | |||
a7574f8657 | |||
73f250f03a | |||
bae0aadafa | |||
5524146ddf | |||
3b2adbc9df | |||
4e41c81bc7 | |||
c545e812d0 | |||
c2193a37ce | |||
fabba82173 | |||
c3ec5ad846 | |||
c4945cc04a | |||
e2e55f81d0 | |||
d862565b16 | |||
0cc3956693 | |||
4e5677f116 | |||
acba1d6f9e | |||
3e14af5033 | |||
6f56501034 | |||
0b7269b64e | |||
457a2d948b | |||
528bdf34fd | |||
697cd17b59 | |||
13fcfcb964 | |||
9c1fd55768 | |||
7f9a476660 | |||
b07290df81 | |||
4b599a95b3 | |||
64222cfff7 | |||
e81d434903 | |||
bf0dd158de | |||
18e398131d | |||
4a5837a286 | |||
656e2649a7 | |||
d36af917ea | |||
c81733b41a | |||
b6558a2ef3 | |||
634d8e25ee | |||
fea212e64e | |||
e3ab76f1a7 | |||
87f1bd58b9 | |||
a056c1f18f | |||
8b34fd2c75 | |||
b912ee7fdf | |||
3cf708f019 | |||
070e0e9613 | |||
3e678511d2 | |||
4ce2105548 | |||
721c6a7e2d | |||
08f0fb1e14 | |||
f5f5281f85 | |||
1684a7bd18 | |||
8b1724bb70 | |||
eebdfe8d73 | |||
82776b333d | |||
e71ab55288 | |||
fd60ef8a8d | |||
aa0b67c93c | |||
15aa07f2a0 | |||
e4536621df | |||
a3c302c36a | |||
d12705f9b0 | |||
0add5c1dc8 | |||
a9e63455a1 | |||
4dc0495a1b | |||
5a79676b8a | |||
b67b0bff05 | |||
4c200635b7 | |||
b98200aca4 | |||
d59c1cd412 | |||
c4d9dff590 | |||
cf91ff8694 | |||
e867ce0944 | |||
29a25990d3 | |||
9a40ad76bd | |||
54b44977e0 | |||
9c7ccc0e2b | |||
7710ef8b2b | |||
c969975fde | |||
3eed6a6090 | |||
1661a7a55f | |||
6293d324db | |||
c1ecfec3b0 | |||
05b4dbf148 | |||
4efada6d84 | |||
23c01473a0 | |||
f2e2106f62 | |||
0cbac26591 | |||
4e7e5ace9d | |||
ab11327e34 | |||
3ba93aa8fe | |||
c309cd80aa | |||
d22a1c9b1f | |||
29698fcd38 | |||
7372ec9e1a | |||
840a64ee8b | |||
524bc2b9a6 | |||
62a29a41d1 | |||
5406d82d89 | |||
de6af95061 | |||
43f7cd8149 | |||
69e67d06a7 | |||
4f47fc00bc | |||
4b04c37c36 | |||
b27b515186 | |||
05bcb7f292 | |||
95a16426f3 | |||
bec094bb3e | |||
af9ebf1d1a | |||
6f2f7018e8 | |||
101d6b92ee | |||
349e8a9462 | |||
c0bffb56df | |||
970cc32e65 | |||
3ab492ccf8 | |||
d83a71d89f | |||
efbb573316 | |||
85554087d1 | |||
c3155a6e39 | |||
4abe95abec | |||
e0acd48944 | |||
afb00432d4 | |||
320bd66c84 | |||
1a9ac62f60 | |||
809b051f10 | |||
baac21209e | |||
5fb8baed04 | |||
512bfc93cb | |||
0f88872650 | |||
f4e40d2c41 | |||
6eac5951ed | |||
475a74d37f | |||
b8ee952135 | |||
15bed29afa | |||
6dbe7e8bee | |||
2cd556e43c | |||
060793f451 | |||
7e409a13cd | |||
aab410380e | |||
67b8ad6a0f | |||
c1e39a3b98 | |||
7e1a7b1f64 | |||
a9cfae486c | |||
8514d27c2f | |||
8999bfef65 | |||
96425fb520 | |||
ce505d24b1 | |||
f2187780d2 | |||
6a878602f2 | |||
f8543a268f | |||
e9b82bacda | |||
684e1c73dd | |||
901c74b653 | |||
2c0afe71b2 | |||
2f4a3ed190 | |||
26a7eb6fa5 | |||
aa21f5343a | |||
9c2809db21 | |||
9ccd362461 | |||
596f611ede | |||
78d5ace754 | |||
2b3218b5f2 | |||
d0fb55d9b1 | |||
a2c8e3952f | |||
beb8c7914e | |||
6bef16a6a1 | |||
e03215c4c0 | |||
8d1fd29fa6 | |||
46f655eddd | |||
ca36a6f4e0 | |||
fdb12b54fa | |||
09dd4bb702 | |||
01657ddfe7 | |||
51a2988bb2 | |||
083090817a | |||
f3676e2d03 | |||
4b8cb72977 | |||
2518e95fb0 | |||
bc17edcda3 | |||
eb185b9ea5 | |||
aa6c82cfdc | |||
b9bb5af4a5 | |||
1e20d449ce | |||
e94f268346 | |||
7ec198b9cc | |||
b2e762ccc6 | |||
bee411e826 | |||
34344982a9 | |||
f73d38739a | |||
63d66ece57 | |||
a4b5493ba1 | |||
8d613f3977 | |||
0ff2bfdd0c | |||
141e25d567 | |||
c67cc694ae | |||
d77359914f | |||
9293a54234 | |||
d9983905b3 | |||
3dc47a46d5 | |||
8638b3bb19 | |||
819a0c5c7e | |||
7afd8644b3 | |||
68fc303b9b | |||
2bbed7727f | |||
63b1fd3675 | |||
3fcf03ff3e | |||
80f3568062 | |||
3e1214a871 | |||
149d809e86 | |||
784dbb00ab | |||
87aef92e71 | |||
d026ebb83a | |||
64c6f05da2 | |||
8963500aa8 | |||
175c0090de | |||
5c4689a326 | |||
5e2831f09e | |||
666882fbbd | |||
6c9fba058b | |||
0767c0c07f | |||
6859907df9 | |||
de52747950 | |||
bd1db51e07 | |||
dd005fb50e | |||
542bafeb71 | |||
e57a0ab05d | |||
2c745ce108 | |||
f6aa90e193 | |||
c7a7d6db84 | |||
2277a39dd2 | |||
ee35ed5250 | |||
92b5e131fe | |||
1f35779821 | |||
5b438d917d | |||
bf4d5745c9 | |||
1e8f83a74a | |||
1db80d79fc | |||
1dac4c33b8 | |||
656b3139e3 | |||
8b08fe265a | |||
29dc139a22 | |||
44ebfa736a | |||
b001685e7b | |||
ca6290b117 | |||
767e0a201e | |||
877ec08280 | |||
485013b7ce | |||
efd19b07e7 | |||
d31989f878 | |||
f669ae5868 | |||
a28c3b0e9a | |||
0aa05158c9 | |||
787dc5748a | |||
8ada4bfd1f | |||
5d4624e75f | |||
2f1b0bf4f5 | |||
e1d5bb1a26 | |||
d0f46d6a8a | |||
4b6c0198ad | |||
f1e7237c09 | |||
1b5845ac3e | |||
58a049ebe5 | |||
c0808d01f8 | |||
7fd5e51168 | |||
d2ea782372 | |||
e6f02d1a10 | |||
894135a084 | |||
df9cf92782 | |||
f243a96e01 | |||
842d146b0d | |||
81d43c57a2 | |||
7da4142d33 | |||
88e5b14afc | |||
0b95a5c121 | |||
62c28a8592 | |||
b80c6840da | |||
003fd6545c | |||
393ed978d1 | |||
2c93062f54 | |||
7b2abf2087 | |||
a5254a3f7a | |||
dc6c34da5d | |||
d4eebcc2aa | |||
4f232cbc27 | |||
76e524ae48 | |||
6ac919c71a | |||
1ba4806f8c | |||
20a2c59b70 | |||
6540fa9121 | |||
21287ba554 | |||
7295a84d69 | |||
483cc2fa4e | |||
e551f6b552 | |||
44b391096d | |||
d45d8e9670 | |||
88bda58836 | |||
79bf3cf70d | |||
72b7419e1c | |||
7baff0920c | |||
d9ecc278b4 | |||
0904df327d | |||
444e87f888 | |||
20aa4434e2 | |||
03da63b41b | |||
878a842611 | |||
f3eda38b65 | |||
68e21911eb | |||
95cc36af96 | |||
d3c4e4f7b3 | |||
4068612300 | |||
f349c1f0dc | |||
90c1300bb6 | |||
569a289a6f | |||
89efe67e73 | |||
c3654b0f65 | |||
f5f4434e0a | |||
d30049b8eb | |||
42d8a7d9e7 | |||
adcda3c715 | |||
a5b5248a09 | |||
3fcca5bc0a | |||
9d4c6f6aaa | |||
d570b08134 | |||
8b6d7129f3 | |||
50444181c5 | |||
0c51f156ae | |||
fe2fb40d88 | |||
9ba0439593 | |||
b33a1fa019 | |||
63fd4222aa | |||
ef5df6f3fe | |||
2f90f9fbd4 | |||
12b099ea78 | |||
9f046a023e | |||
46e6911ec1 | |||
d3844ef32a | |||
4507dca342 | |||
c2fdd1362a | |||
4ea19b90a4 | |||
9cd555cad5 | |||
ed78c8d3bb | |||
0b23af324b | |||
1598a02a7a | |||
167f5bdc58 | |||
5cd7bccdf3 | |||
acbc261891 | |||
f97f0c4758 | |||
e6ac5bc546 | |||
ef1e5db0ee | |||
5cdfd79e96 | |||
b441bac7b2 | |||
00cb52c444 | |||
9323a3e257 | |||
35298e01a8 | |||
867f6f107b | |||
43bb813cbe | |||
7b82e96467 | |||
978ff87b76 | |||
4c0bc1fd88 | |||
025b4f90de | |||
20189c5d45 | |||
2e4acba579 | |||
d90b8c331d | |||
efbb49d579 | |||
f0079cd7b3 | |||
a0041cec97 | |||
77bb9e7ffc | |||
f441177840 | |||
cd634801a2 | |||
5f10a87dec | |||
fa1c1e3734 | |||
947cdd8748 | |||
0a9f063d3e | |||
dd4c512954 | |||
d228b6467c | |||
92c66a411b | |||
af97ad3d68 | |||
6ff2a0a75e | |||
5b7d5e2e02 | |||
97bd7a00f1 | |||
25a2f08f8d | |||
3152090a66 | |||
9a0f9b910e | |||
f853c39169 | |||
75ad1305c0 | |||
cb3adea94f | |||
fcef54d062 | |||
32683cac7c | |||
15947b8642 | |||
4e0316f792 | |||
9594b7fdce | |||
1adf8355f2 | |||
8660c3581e | |||
f886b3b12b | |||
5646daa820 | |||
7896e8288d | |||
9369ea86ea | |||
dee5ede16d | |||
3b516c0710 | |||
0887832b00 | |||
8e04fadb05 | |||
31f8b6d352 | |||
98d60e6124 | |||
fc678f53ba | |||
8e25c39564 | |||
78ab79c322 | |||
052fc9b74f | |||
f482c9ab61 | |||
75dcd97f5f | |||
4776dc36ab | |||
10239c3b3c | |||
753d0dcabe | |||
b708998d9d | |||
3759b0d2a5 | |||
c4bc710d3a | |||
857dc2ba47 | |||
981e057363 | |||
37494c67d0 | |||
7a81f327ce | |||
845ddc3496 | |||
c61bb16fdf | |||
15b945a652 | |||
1d48c4dd45 | |||
2ab50cbae8 | |||
0482f153d0 | |||
92e1c4c531 | |||
4bca60861e | |||
50b0a5ae83 | |||
c30eb6185c | |||
a94bc80383 | |||
586b6fc3d7 | |||
a14c202d60 | |||
ed48c495a3 | |||
f0abd06a46 | |||
7d0ff8e713 | |||
e8cc566b2b | |||
e45f7afd85 | |||
054ae3a3e3 | |||
36ea088387 | |||
47b6707c07 | |||
0346b9cb5c | |||
6bfe497ab5 | |||
6956bf635e | |||
e27d6d0988 | |||
3fc09fb23f | |||
cecdb7061e | |||
0ac865f08c | |||
55115d0eeb | |||
16ff4ac1a8 | |||
5ce31168ef | |||
b9ff70c8ab | |||
77498c6efe | |||
8c69c40834 | |||
d497b99abb | |||
ca2ac1e5ea | |||
c09e0eb536 | |||
0d90dfae1a | |||
bf61321cab | |||
591653981b | |||
e651510805 | |||
9d73fbb84a | |||
215b07c1a9 | |||
420cbc45cd | |||
df333e8b6e | |||
9759ac2961 | |||
af9b173dfd | |||
b61aed7250 | |||
e1c0425c2b | |||
615472b52c | |||
4d34102d9c | |||
3e22ce4154 | |||
215f33680b | |||
a5420f19da | |||
4bc3f70150 | |||
e8814b1297 | |||
46ab0e6449 | |||
59b4f40f4e | |||
93c57934cb | |||
e8e1d6b8ce | |||
4916cd8da5 | |||
573dec63da | |||
34c051f183 | |||
51004881f8 | |||
5c536e423c | |||
4efa144916 | |||
f3936c21a3 | |||
caff603497 | |||
aefa9891c0 | |||
6286947697 | |||
33972ef89e | |||
b53cbdd9e6 | |||
c49e84c75b | |||
dcf2337e58 | |||
5a65c3f72e | |||
8ff1987d2d | |||
acedf4ca5a | |||
68c35bfde6 | |||
e1a3708844 | |||
46ecac3310 | |||
028b9da0da | |||
74cea2748c | |||
a478b2a05a | |||
41a52dbfea | |||
4923f889c4 | |||
31b8743052 | |||
6505221629 | |||
de2b6bc9fc | |||
f565292852 | |||
90f17e8fd4 | |||
d6da7dc1b6 | |||
7e2aad2590 | |||
f09b8d3921 | |||
52f6c33ff9 | |||
60dfb35924 | |||
5f41909098 | |||
a28f7db950 | |||
38fdbbba3f | |||
0a5b6154e8 | |||
4542a7042a | |||
6113b64fee | |||
f777ed76a3 | |||
e6b9babf53 | |||
ed8bada439 | |||
06b0c98c75 | |||
dbb145c266 | |||
437481853b | |||
3b5a9f512c | |||
045af04784 | |||
d0761f57e8 | |||
4bb88619fd | |||
412ebfcaf2 | |||
3a7647f611 | |||
d4cc48f99d | |||
852fcbd700 | |||
8ab4b8e6ac | |||
a8095e204f | |||
98979c7d53 | |||
c18fcde385 | |||
f286bbac99 | |||
4e029d81a2 | |||
2b00a42b06 | |||
07d55d0092 | |||
fb44e2bf48 | |||
9b0bf5ad66 | |||
4247fa946e | |||
071b1d8b77 | |||
63aadc4905 | |||
d2415613de | |||
58f071b7a0 | |||
148e08a8a5 | |||
402a733cd7 | |||
78be3652de | |||
b03d9884a3 | |||
799085a105 | |||
7812b67471 | |||
4033fa031b | |||
b41737259a | |||
7c8a4bf6a4 | |||
71314d79a7 | |||
d7ff6645a9 | |||
1824e09d0a | |||
205907d3d7 | |||
d4bcc4d474 | |||
bcb190a12a | |||
63e8496473 | |||
4107d70e93 | |||
4fb0782892 | |||
9b7c1d5650 | |||
985592cf40 | |||
2694654a98 | |||
4126461f87 | |||
791ead6053 | |||
3048de18bb | |||
df9fd2bc0b | |||
13c9d3d4e1 | |||
0dc364c17a | |||
b3cdf58e4b | |||
61f950a60c | |||
da77789881 | |||
61af87972e | |||
fe9e771b9b | |||
94b5835738 | |||
a4652a9aaf | |||
7246d72f03 | |||
70b21b3795 | |||
682b1b89b3 | |||
f1802e592a | |||
ee58c1f960 | |||
07f4dd385d | |||
1be7ee51be | |||
56fcc93ef5 | |||
c70412d7bb | |||
5e21268ca0 | |||
b38e3bef01 | |||
89cc82c71b | |||
1d0f6a5d85 | |||
d0292b1cf1 | |||
15aed9f320 | |||
5a67362b8e | |||
5d73ab299b | |||
ef111dcbe1 | |||
da7e49c880 | |||
f16f88873d | |||
211c81f2a2 | |||
efc39ffdde | |||
61a4b998fa | |||
cedff2fca1 | |||
8d032aba9d | |||
607b368fe3 | |||
a54854abc7 | |||
ce6257a069 | |||
7b28d3a231 | |||
ea01ff2aab | |||
3369019943 | |||
dbd4176b97 | |||
122c7bc2ef | |||
99671472d1 | |||
0c0716abfb | |||
c09accb685 | |||
ae4d14a2ad | |||
55cdbedb52 | |||
ee39f31d81 | |||
70b45de012 | |||
60437a8dcb | |||
a35ebe1186 | |||
c498775a3d | |||
3ad019a176 | |||
9632136cda | |||
42cea7a785 | |||
4c9d852b08 | |||
9566a5cc68 | |||
ac03c59b41 | |||
73ceaf07b1 | |||
7b314f47f7 | |||
23337e08eb | |||
97e73311c5 | |||
e2c24481e4 | |||
ad252fe4c5 | |||
4b04bc8612 | |||
bcc34b906c | |||
c2b1010f18 | |||
e3ef4f25d3 | |||
ad12b0efce | |||
00f005af25 | |||
656fb173f9 | |||
5f58e9cd6e | |||
1d876df8b3 | |||
c8bbca08f8 | |||
971da7325d | |||
ca4f874f52 | |||
a88b36d718 | |||
24d9138067 | |||
aca739b800 | |||
e091aa87ea | |||
968022a1b0 | |||
66fb1bbb2e | |||
fa3e1fa7c9 | |||
36763d0802 | |||
be5f800390 | |||
ca69b7b75b | |||
a15927f8d0 | |||
4ba4ad9878 | |||
be1511a7ff | |||
41b98c603b | |||
5430dd28b6 | |||
e9d687329b | |||
d72cac6e97 | |||
4e51a444f4 | |||
48d86683e2 | |||
42d5dde5b1 | |||
142eeffe5d | |||
6a68df3ebd | |||
8306c1841c | |||
73bd396dfb | |||
36fb0a0aef | |||
4d53be8350 | |||
f8bf9ca218 | |||
7b4568b9bf | |||
bd8502e87e | |||
21815f26d5 | |||
8ef5195037 | |||
57606c6bf8 | |||
0465abf75b | |||
8a142966be | |||
7498488f5f | |||
ede99d5913 | |||
3ced91319f | |||
3d1413e619 | |||
8f25548781 | |||
47ddbbe53b | |||
5741400713 | |||
9f02a8d3d0 | |||
c208f4dbb5 | |||
a17843c8f6 | |||
3f2fc21bb3 | |||
9fac3b26ee | |||
c1eec0290e | |||
de13082347 | |||
48b5d666d0 | |||
70bb49a46d | |||
105fc7029e | |||
77a7ffe543 | |||
7d593e6c61 | |||
bb420cb995 | |||
e58220282a | |||
4ca4038d54 | |||
150cd31ec0 | |||
6fd0d4dcf5 | |||
296415945a | |||
1de5ae1ef0 | |||
6a89c68a1d | |||
c14cce4c85 | |||
959961b596 | |||
6f76c2da6c | |||
8d2bd2b30f | |||
34a8d591fa | |||
d94ff4bf4a | |||
af03df38b9 | |||
242bcf44db | |||
ebd540972d | |||
a17be9f8bd | |||
42ad297778 | |||
0568d7238e | |||
9bc05313a2 | |||
fedbae6f8c | |||
64de639817 | |||
ec9e13d1f4 | |||
5d27f221f7 | |||
61db74d98e | |||
1d689e84f1 | |||
b7f420412b | |||
e3ac9e9679 | |||
12fde77ecd | |||
3fc96c4a18 | |||
cb3eeace56 | |||
76feb2098e | |||
06cb266cfe | |||
866d3f467f | |||
c1e726da87 | |||
7d7528eb18 | |||
9f916f9d47 | |||
a7d8bfdf8b | |||
abdd4f371b | |||
13adee332e | |||
a799f8f4b1 | |||
1ee43a7633 | |||
3d2b7dd1ef | |||
7b35114c0f | |||
b418525464 | |||
8bba11367e | |||
9eb7e63819 | |||
092501039c | |||
6899bd7099 | |||
5a0416b925 | |||
ba2cdd0bf6 | |||
fe1676bc3a | |||
1a9ef37251 | |||
db5370c5df | |||
804378e8f7 | |||
56b0ba2601 | |||
3073ebb20d | |||
f8e07ef5a3 | |||
a4b6d181a2 | |||
0b8c5d807d | |||
e201136eee | |||
55f660d5f9 | |||
a4acc631ee | |||
3ddf4b6c24 | |||
ccd1173a83 | |||
cd1a9faacd | |||
b60b8ec5ae | |||
536c8accf8 | |||
7beefb3f81 | |||
fe1f67ea9a | |||
069ce71256 | |||
e3cacb9296 | |||
0c592c52f6 | |||
78bb96ee51 | |||
86e2f35ac4 | |||
7696a64891 | |||
799ed24113 | |||
63477dabcd | |||
cd0bc1dea5 | |||
195a880576 | |||
ac226c3e14 | |||
4d5b832775 | |||
79b2542ca4 | |||
17921c9fae | |||
5de38852d2 | |||
0acdbc0d03 | |||
c8c85ff93b | |||
31cbb52654 | |||
cd88f81817 | |||
6de24ff0be | |||
de4d14ddc0 | |||
5b386ec30a | |||
8f0aa956a3 | |||
e04148ff44 | |||
d5d853838c | |||
e18673953c | |||
12f3fd75e8 | |||
7bd0929157 | |||
19488ba42a | |||
f0dc10c67b | |||
f55103498f | |||
639cb49356 | |||
c5e9c6fdb6 | |||
7a4ccc8719 | |||
125a345c90 | |||
3dc22e7323 | |||
17dcd1f62a | |||
a277f3e816 | |||
10b16753af | |||
4625aed3a5 | |||
259c820f15 | |||
e888c90ecf | |||
b053bc2790 | |||
6a81f9e443 | |||
0ef1fa7c76 | |||
02eb234399 | |||
8d80da6b46 | |||
22855def27 | |||
0be59cad4e | |||
5edbd6a7fb | |||
54ff9b3ac2 | |||
5463226184 | |||
b96bccd71f | |||
07a948a0d0 | |||
8f034280dc | |||
83f551d9b9 | |||
f83a64d17f | |||
8bc7d5a172 | |||
96c0222b30 | |||
679a718cbf | |||
b083e4db48 | |||
a3cab470d3 | |||
bb93504965 | |||
4d58bf4b28 | |||
505f77b108 | |||
5b672f8921 | |||
9e9c0785e7 | |||
94882418ab | |||
c6cb3bb0bc | |||
9fedc9513b | |||
0badc90058 | |||
61fbea3ee4 | |||
a4a3995a84 | |||
01fb76f4bd | |||
d09639f7d2 | |||
946ee8a354 | |||
e63b899ca5 | |||
63a4ed74a4 | |||
a3782d699d | |||
97f2c96a7e | |||
5979627258 | |||
9d580e363a | |||
9163e5b004 | |||
0252bf2f46 | |||
283bb84134 | |||
0a4f909566 | |||
516aa44aad | |||
b1763f9187 | |||
b03fd782de | |||
b850f3c1dd | |||
789a9df9f6 | |||
bd39ab9365 | |||
1c0cfb17a3 | |||
9491999a95 | |||
e2d30db7e1 | |||
3129e299e4 | |||
0604bbb473 | |||
545feab6db | |||
3794048c91 | |||
beb45f44ac | |||
f1d1852691 | |||
53f09c44f3 | |||
bd237a2d6f | |||
76a7038335 | |||
c24d95c885 | |||
cb0560df92 | |||
ec034a5cb9 | |||
ca99ebaaf4 | |||
b9e878ee80 | |||
33c4c7e511 | |||
b67ac22336 | |||
6ff2572ebe | |||
a539c9ad67 | |||
1997640094 | |||
e7eafbd24e | |||
378a0f511e | |||
9349f90a59 | |||
0f1d6c6271 | |||
8e70f5bf84 | |||
52fc974cdf | |||
6e9d803091 | |||
fc8489a04d | |||
e248efce06 | |||
b4084c6298 | |||
2fdfa98d55 | |||
f506b0a224 | |||
202adb1bf1 | |||
885eeec3ed | |||
5e9f802d7d | |||
e4be57c3b6 | |||
6ab6e6cb9b | |||
2a849ae268 | |||
4808f6a9f8 | |||
96bfe92334 | |||
e7cde846cb | |||
eb90d8d463 | |||
6a8a97f644 | |||
3fc846d789 | |||
0f77531f09 | |||
20b831264e | |||
43bab23651 | |||
906df5e20e | |||
794e961328 | |||
a481822321 | |||
dc42c12f2b | |||
6d82123125 | |||
4f6d7702c5 | |||
97274030b9 | |||
9ce2bc94bf | |||
51502537b1 | |||
7b49c9f09c | |||
4714dc3a5c | |||
44013855d8 | |||
846fdd3b2d | |||
03d6c9a552 | |||
d0be16b49a | |||
3a4018cd03 | |||
5aaaa7f45c | |||
c299dd390e | |||
a3016aebaf | |||
fb55d1c3d4 | |||
9c44c173df | |||
d708982f27 | |||
bb774173bb | |||
3906b1af6a | |||
de1d7ce312 | |||
1654199b23 | |||
2ec9bc9f05 | |||
e8ae603a01 | |||
e4dba03e12 | |||
8ec10d4de9 | |||
baca3e6b6b | |||
fc5fcd6cd4 | |||
33496ffea2 | |||
b8b7de5522 | |||
109101c2dc | |||
534619f72f | |||
44322124c8 | |||
9923c543e8 | |||
41b5899856 | |||
b830449f23 | |||
037fcf6b3d | |||
e1a1296b9b | |||
3f4ff3f7b5 | |||
cd4bccfd12 | |||
9c3e7e40cf | |||
a9a7fc56eb | |||
398b78dd97 | |||
1edf6c361e | |||
b99e3eafdd | |||
e6486b2824 | |||
d22a13257e | |||
f4c5b9ccb0 | |||
a94880574b | |||
0f1582c196 | |||
85159a0eb4 | |||
258cf21416 | |||
2bfad87a5f | |||
95cbb8a5c0 | |||
ce1b72809a | |||
4f3e149a98 | |||
642d3d903f | |||
81cd461591 | |||
ea110efabd | |||
0743f54dfe | |||
176d5e0d37 | |||
16b71a6be0 | |||
13ee8efd42 | |||
5f5d779ee1 | |||
7b849b042c | |||
d32f5b6cca | |||
fcbcf000c4 | |||
2bc939f535 | |||
d5de5bec4f | |||
61beb42797 | |||
e5be3e1dca | |||
986c54de58 | |||
49b7e67585 | |||
db825b6e26 | |||
8e273caf7d | |||
b1a648113f | |||
2782922f7a | |||
041a06b432 | |||
269a82f796 | |||
6b83ce4937 | |||
ae557104a5 | |||
6a34b11dd0 | |||
54417acfba | |||
29d12d9ff1 | |||
4ee857ab7d | |||
771a88665c | |||
a7c18cc0b4 | |||
e30e4cc603 | |||
fdc31e99df | |||
a72325dbc2 | |||
67b6be66c8 | |||
8ec13d557f | |||
31f570a9f4 | |||
46b7b795bf | |||
38273427ad | |||
46fb0b1b94 | |||
224b705f8d | |||
028f41eb51 | |||
c27726e065 | |||
a57fb00584 | |||
360055ad70 | |||
558f10c862 | |||
c53c351759 | |||
7c4473e0aa | |||
7e7b79ef34 | |||
e993d511e3 | |||
251b0957f1 | |||
b9524217fe | |||
6b228df3df | |||
6cf6a1ccc3 | |||
d889e77fba | |||
93d65aa9cc | |||
f216a7179a | |||
434b8a8970 | |||
cc9191f1b0 | |||
567bbecca0 | |||
07e4f9a611 | |||
b41286919d | |||
564057c812 | |||
20e4edec61 | |||
d5f0e49535 | |||
30bccc0c68 | |||
1c44b738fe | |||
217f30f9c3 | |||
fec867539d | |||
d123d86d84 | |||
485ccd20e4 | |||
8d004ee947 | |||
4704aa1f80 | |||
271115a6be | |||
a79caf7795 | |||
404aa63147 | |||
4610706d9f | |||
8e4cd6fcc3 | |||
6eb09a6901 | |||
e04d2379df | |||
5b72a984a3 | |||
cf545e64b8 | |||
ac1e266588 | |||
0f2226901d | |||
dad1511484 | |||
05646d72b8 | |||
7ccd601100 | |||
d23f8a3e99 | |||
0dc5af62ff | |||
855f1823a4 | |||
7fd40f1eb9 | |||
95f2f05f45 | |||
cd976a8082 | |||
163ed40efb | |||
32aaa5fd06 | |||
163874d4da | |||
873007bae1 | |||
a67a88c8ef | |||
6d1b43f1b1 | |||
3a20a20807 | |||
e45559a1a7 | |||
140954a53c | |||
b5d7ac3ce3 | |||
b5d714eec7 | |||
36cdaffe25 | |||
16e2443f61 | |||
9adbc1dd60 | |||
b6ccb475f1 | |||
ca0f16ccc0 | |||
c241a56fb0 | |||
4149f7fd1c | |||
cc68ecdacf | |||
96b349dcbb | |||
5216952691 | |||
c46b2541fe | |||
2158ba5863 | |||
180d297df8 | |||
c276375a0e | |||
130563cd4c | |||
9e2a7921c8 | |||
9539154a4a | |||
84bd9296cd | |||
88ecce12a2 | |||
5a7b99ecc2 | |||
55a76ed4b0 | |||
033a04129a | |||
789fff2ae2 | |||
9750488200 | |||
46ec5cf765 | |||
ee16cc77a3 | |||
a669241cb1 | |||
0174945853 | |||
ea0837973e | |||
85819983d7 | |||
78841532f7 | |||
72214b2b68 | |||
ee83a2ac29 | |||
82c759b6cb | |||
6de5354b8e | |||
87281f6ed5 | |||
a8cd66ffa2 | |||
d1e1258f97 | |||
4d73bbe48f | |||
10ad536e09 | |||
bc2d4c7681 | |||
a7f200847f | |||
411f154827 | |||
6dcb97af9e | |||
9420ba52e9 | |||
ec35c1fc79 | |||
b752511f41 | |||
af206111e2 | |||
ba50e1ac81 | |||
f9f493ee7a | |||
137233b4a1 | |||
3897b66270 | |||
feefdca969 | |||
25690ff078 | |||
897279eddb | |||
5f5725a4ea | |||
6a61f25735 | |||
454c66f988 | |||
3e893ffddc | |||
58eebd7f6c | |||
ba5077701d | |||
2f44555437 | |||
299b642803 | |||
a2bf59cbba | |||
329382f016 | |||
67c9bbc6b2 | |||
6088b3bfc8 | |||
2be7896157 | |||
0b37f530ae | |||
c13ae10d31 | |||
1e15e6375a | |||
ed684c5ec6 | |||
2fbdec59cb | |||
710f88edda | |||
db899a2813 | |||
aad0d90fdd | |||
72b4834446 | |||
ec48c58df1 | |||
0947ec59c9 | |||
d67211305c | |||
c65046e1a2 | |||
ba7d121724 | |||
a1070e9572 | |||
f89e83ae49 | |||
264f502ed7 | |||
c5876ddca9 | |||
fdf6cae6fb | |||
d26f836212 | |||
da98982732 | |||
cc10e84ab7 | |||
6cd91cd7ec | |||
e19dbdc527 | |||
0b8809da6e | |||
35aefdf1db | |||
66891d9d4e | |||
6bca577d6d | |||
f5400ccefc | |||
a56d717ea8 | |||
11c7aab023 | |||
5541eedcc4 | |||
77ea4cd285 | |||
8353b420d1 | |||
71602fe04b | |||
054c12ea0f | |||
0003dbf3ba | |||
c07b6c30a1 | |||
bad48ce83c | |||
2d03ae2fae | |||
3a7008949f | |||
973ad7554e | |||
3be154490d | |||
3610768888 | |||
4602d3bf46 | |||
778583ad08 | |||
fb904e7a29 | |||
b501090443 | |||
f0f55af35b | |||
3e8d96a95b | |||
9713a3ac02 | |||
5c9777970d | |||
c142a82ae0 | |||
18d48f09f8 | |||
deeabb862d | |||
d8f6865338 | |||
4a0c759795 | |||
a131c90260 | |||
fc48062867 | |||
f77788447c | |||
d25fc7a649 | |||
bf3d2bd2ec | |||
60a6ff80ee | |||
9e1c5e1ab0 | |||
20fffd8abf | |||
98ed785711 | |||
7cb695df12 | |||
c94bc2a0b6 | |||
511085b747 | |||
f76ac94d70 | |||
32caa55d67 | |||
b69475937f | |||
f6ff33db8e | |||
dcf1200d2a | |||
40977fa99f | |||
f4df8ff5b3 | |||
080db1c62d | |||
4d5e2c8a4d | |||
13d018e3e1 | |||
59ee2b8892 | |||
0dde79f42b | |||
a4411ef6a1 | |||
3c62e2332e | |||
1cd88968cf | |||
28a53959e0 | |||
7c26a4d0a0 | |||
6ed2e4c187 | |||
a484c87354 | |||
33c7f92f56 | |||
b8f6280fe5 | |||
822bebea46 | |||
582a7192ec | |||
5492aad61e | |||
27f973c923 | |||
3357cebcdb | |||
7ce9c0a2e9 | |||
e9daf57d7f | |||
1c2169aec7 | |||
cf163a9dab | |||
dfcf3f94dc | |||
b13fb6097f | |||
6e24a4aa50 | |||
fb1c6cf4da | |||
af1b8f8a26 | |||
88d6db8537 | |||
6ce2c06fd6 | |||
136f7e4b3b | |||
0a73bb7efd | |||
2cf00021d9 | |||
8d38c2f800 | |||
9848de6cda | |||
19a3606315 | |||
cc2227d943 | |||
a33921ed34 | |||
2e75ff27ac | |||
a27cdf55e7 | |||
3d00992c95 | |||
77cb70dd80 | |||
8daba3e563 | |||
94f9ac0332 | |||
a17903a89f | |||
dda0a1f39b | |||
0ef670a865 | |||
04f54655c2 | |||
dc5590f2bf | |||
bc52fce810 | |||
b9bb92099e | |||
64dcc31ac7 | |||
36546b4c4c | |||
dde886f058 | |||
781f7ef570 | |||
3e8bb32ffd | |||
df310641fb | |||
21ef55f205 | |||
ade36566ea | |||
08d7a0d52d | |||
1fd2885995 | |||
d357640fbf | |||
ad9cd23202 | |||
5916177dc8 | |||
905b1e2775 | |||
377d45c9dd | |||
a444cac2aa | |||
1e714eb6b2 | |||
3f14466965 | |||
e0b8f4202d | |||
11b14bd3ab | |||
90684483e2 | |||
760a82cb08 | |||
0317583489 | |||
1c3f2bba6d | |||
7d62bf9a3d | |||
7c248cd2ef | |||
e4119268ca | |||
fc2760e761 | |||
c57084de36 | |||
907aff3b43 | |||
2793404116 | |||
d850f67979 | |||
8080063024 | |||
f33c6eb95f | |||
4e3d71c2c9 | |||
a074cb78cd | |||
0dbc33f781 | |||
25bbc3bc2a | |||
5f55a9be84 | |||
300e3d151d | |||
2f7911b62a | |||
54dfe708c1 | |||
8166925f04 | |||
64f1d93cc3 | |||
6d67568037 | |||
5003e97479 | |||
858068cdc0 | |||
65fb307d0f | |||
2f1fe726f5 | |||
e9b0e3cb9d | |||
34fceca7ff | |||
c646845cd3 | |||
eb483bc053 | |||
50d3fa7437 | |||
9f7fc5f054 | |||
a27e9cb3c2 | |||
10270dcbad | |||
4ff4fb6c38 | |||
c8c794e340 | |||
97a1e950ef | |||
9fa8105ae8 | |||
d68b6ea7b1 | |||
58f4709362 | |||
f71cd2c6f3 | |||
8ec1f6ea2e | |||
d63c8ae1ae | |||
e39094ac37 | |||
b539389741 | |||
ac35fe9ed1 | |||
3d70afc578 | |||
b919b3e3b2 | |||
7a7349f2ff | |||
07b57735b1 | |||
e42c95a327 | |||
473af78368 | |||
ab6c7f6ca3 | |||
599516473a | |||
83ac075b22 | |||
3548c6c43a | |||
3bfe2e75b5 | |||
97c93629a5 | |||
643384e1ec | |||
1809277e05 | |||
7981865fd2 | |||
4467d5eb4c | |||
38aed0c886 | |||
02801b3e75 | |||
b79d361e6c | |||
9eb8b67b5c | |||
132c664e18 | |||
288645aeb7 | |||
55f06f5bad | |||
a2cb18bfe9 | |||
d35b3754a2 | |||
7f3aca15dd | |||
2c5cbaff25 | |||
134cd7ab04 | |||
c74b8b6df3 | |||
573116e259 | |||
71ab030ea4 | |||
c4125b80ec | |||
626a381ddc | |||
5333bda234 | |||
cceeb8e52d | |||
94a0d10499 | |||
3f6aba23dd | |||
cd9dac4c7e | |||
f478894729 | |||
97790480c9 | |||
9643c39bf6 | |||
0a08d40237 | |||
d029997aef | |||
ceb27b431e | |||
d3761c2435 | |||
b25d8ce764 | |||
34da362ee6 | |||
de6109c599 | |||
736f08815e | |||
106645d9bd | |||
c55ada2f26 | |||
4e4a1643c4 | |||
e1e84d4465 | |||
4a0009365e | |||
3849b8ece4 | |||
f2ab8f17c8 | |||
48671a1728 | |||
72b6ec4aa8 | |||
8790a92f07 | |||
0f8ff07b51 | |||
dca73068c5 | |||
4094e62ed3 | |||
7a0e897960 | |||
e78fc74e03 | |||
5054e74f7f | |||
72e6a39172 | |||
be73db13e0 | |||
cbaba5cbf3 | |||
c1447b2695 | |||
e58f08b60f | |||
662d62f561 | |||
cf4813a1ec | |||
b03636dc33 | |||
6187779d10 | |||
ddc8bfed29 | |||
f1221d724d | |||
aec44e3761 | |||
aed07f0f48 | |||
c178fc7249 | |||
41554f433b | |||
863956d09c | |||
7118178e2c | |||
1eabe66c85 | |||
2de0a9e453 | |||
0bb6940c1a | |||
e341b33f21 | |||
6abdd6401d | |||
6632c7026d | |||
c474cf1eef | |||
e26cd2eb26 | |||
b33becabca | |||
3c8a8640aa | |||
a1b5ea9cb1 | |||
bc162637a6 | |||
8f1b7c3fff | |||
be71f49d80 | |||
8b39eb5e4e | |||
1173cf7ed4 | |||
b4fd141105 | |||
0002b5dd02 | |||
709598541f | |||
aa781811af | |||
b595bf8f44 | |||
f6979a090e | |||
2e1dcd84f9 | |||
144d321193 | |||
d41dec9395 | |||
f977327c7b | |||
aac1a58651 | |||
095afdfe47 | |||
4ae1783b97 | |||
cd92adb1c6 | |||
7dec40ff05 | |||
4b38ecd916 | |||
02c0098d57 | |||
1e58c585d3 | |||
ed4e9febe0 | |||
1c61415cee | |||
c02625f91a | |||
da5b777ee7 | |||
a6aaca814c | |||
ab3dd2a1b3 | |||
7b7a2fc52b | |||
95b28d4d8c | |||
1278396bd5 | |||
0e29868e34 | |||
0115a1f834 | |||
cf103add54 | |||
766af58cd8 | |||
5200435bab | |||
56734dca3b | |||
dbaf8e66ab | |||
6e7c5f205b | |||
e7df3cfe22 | |||
0e8540417f | |||
c3ad0eebec | |||
c82ffaabdc | |||
4e6a9b029a | |||
3e519faaa8 | |||
e2eb7c1ba7 | |||
87ba5b865d | |||
992f2790e7 | |||
e1a099632e | |||
fd7db7a954 | |||
5bb4ac9873 | |||
31b0d14856 | |||
952ab2bde5 | |||
3c6af52a71 | |||
6317bec7aa | |||
eb3ba5ce2d | |||
1f0b3f954a | |||
cdb2a7bef3 | |||
f6515b2b6a | |||
5128d7d6c3 | |||
731e5e1291 | |||
cedee73548 | |||
8136d52c0b | |||
d1945c29d7 | |||
83b40e4f30 | |||
95ac6305bc | |||
ab4828aae7 | |||
c506423e70 | |||
f0843fc5f1 | |||
c87e035302 | |||
abb9a72b27 | |||
acc6bf1564 | |||
db688207a5 | |||
9681c4d468 | |||
d9e2b94d7a | |||
f789038baa | |||
2e23b03f94 | |||
5181a2a9b1 | |||
2d2572d2cb | |||
fa553029d5 | |||
c986a20bcf | |||
c5a74ada05 | |||
73979d8f5a | |||
f90d96367d | |||
5f565c92c9 | |||
7452486c72 | |||
afdf0efd31 | |||
7fc271ef97 | |||
582ba4f173 | |||
0229c97071 | |||
c0b398c7c9 | |||
549f9676f1 | |||
6248624ee7 | |||
0025d36880 | |||
4985b682c3 | |||
85333c5d62 | |||
3feda8a315 | |||
5375c420c1 | |||
ac9f6a77c9 | |||
58f4e0653a | |||
03e6a56b3c | |||
32f19c5c19 | |||
98e893c69b | |||
fea480526b | |||
4aa6695a13 | |||
a7e5423ede | |||
3ff8bbcf65 | |||
9d34ded5f3 | |||
511d8275d6 | |||
0a9226ec8e | |||
9c07a8c26a | |||
6058bfb687 | |||
7a6d730db3 | |||
2985988f0d | |||
d62c9ac309 | |||
85c8af08b3 | |||
21c09073a1 | |||
40acaee446 | |||
d9a22705ce | |||
dad0bfe447 | |||
1b3e7f734a | |||
0e58023794 | |||
4fb9c8a547 | |||
43cce3a8fc | |||
344427c1dc | |||
82a2080e45 | |||
9a4abe96c7 | |||
d87c2eb903 | |||
65708f234d | |||
b6b179af97 | |||
37003da854 | |||
3f323aba1a | |||
29889a90e5 | |||
ed478675ba | |||
9767468b7f | |||
8ba1d5f426 | |||
84567d36cf | |||
32162ef0f1 | |||
2dd20c38b2 | |||
aa1bd603e6 | |||
e104941569 | |||
2754ceec60 | |||
609e915169 | |||
11f1c00ca7 | |||
a74b24fdf0 | |||
e25992a011 | |||
00bb5925e1 | |||
1b50fbbc90 | |||
a746969995 | |||
c536a0bf04 | |||
5b8e7bfcf2 | |||
3cbbceec78 | |||
e684fafb68 | |||
651342b3db | |||
c01290438f | |||
9e9c82869a | |||
494b143453 | |||
8cc1cde0fe | |||
883fc39c80 | |||
1c0758e3bd | |||
668d353add | |||
06a1681fdc | |||
a16e41002e | |||
16e705dc75 | |||
b52228feb9 | |||
25f25d0f82 | |||
85e7046caf | |||
c741a960b9 | |||
34c8b2cc2f | |||
278effad49 | |||
a0bed5375d | |||
9eecd549e4 | |||
a2c3369713 | |||
1f9ab7f58f | |||
3e1a926aa6 | |||
57f82934f2 | |||
f3a8aec64d | |||
e2e5bc65a9 | |||
df136578d4 | |||
ae7f169027 | |||
6da7a784f2 | |||
12cddf725e | |||
d8861c2a5f | |||
145fb3675d | |||
77e8cb2718 | |||
a8ea6471e7 | |||
bfaf5634a1 | |||
53afa64634 | |||
c9bf9ce094 | |||
a2e29fa71f | |||
637f58364a | |||
1bd04b26e5 | |||
29ef9370a6 | |||
2262f279d5 | |||
e4f477cf90 | |||
33f921235d | |||
1bae87d4b3 | |||
1e43fb587e | |||
d65e7b9fcc | |||
4bb6549895 | |||
06e3cd3d2a | |||
e9e01557b7 | |||
e0f046b7a5 | |||
9845aec007 | |||
81c82b5af9 | |||
a9b083e585 | |||
9abc500269 | |||
b9eb7e14e6 | |||
b7be5b9a7a | |||
ce41760fdd | |||
a7503050c2 | |||
d4eb69ca14 | |||
aba9df8457 | |||
6aa80e431d | |||
bae7612f36 | |||
a0bc8b8af3 | |||
73930b5eac | |||
fbeba259b3 | |||
d1bedeae13 | |||
e84f1f6de7 | |||
cc88f9bcd6 | |||
f630b50902 | |||
9a7082d0d5 | |||
8dc9089611 | |||
222d2d7953 | |||
27c10d4468 | |||
a17467aefd | |||
73b10c196e | |||
965dbbe835 | |||
e3ae10bacc | |||
fcda94b673 | |||
b1109b813e | |||
122a5b2f69 | |||
dea20248c4 | |||
ae90ac238c | |||
3b0ca9f478 | |||
61e79e6d02 | |||
1cdab81a3c | |||
dca0ba6a5d | |||
d666ebc558 | |||
c84b796e17 | |||
7204bb40bf | |||
637d5c6691 | |||
3c86f41769 | |||
f37eb533f1 | |||
6e8b69fc88 | |||
cb23070dfe | |||
5d9d83d312 | |||
823252dd41 | |||
35764225ed | |||
c7e5006bcf | |||
b0149a54d8 | |||
e6030d66eb | |||
6611188edf | |||
abbb037888 | |||
132d59ca6a | |||
200d5e62c2 | |||
b748942d6a | |||
648b6597bf | |||
5b73a8eceb | |||
514bf32b99 | |||
2073188345 | |||
c0b472292b | |||
1b15fd1da6 | |||
6883ea0944 | |||
303289777f | |||
a8bf00fe20 | |||
6282c53fe5 | |||
dac28e0961 | |||
4f86563352 | |||
818afc68c1 | |||
443d8ce7c4 | |||
da5cb0b012 | |||
922ffdfc28 | |||
2f1107ff4f | |||
1fd7bd7ede | |||
c0c38463c7 | |||
c1e142d1dc | |||
6933f2bad1 | |||
b03d1d8894 | |||
8e4a86e329 | |||
1f87d9ba4a | |||
14267e172d | |||
95e83cfe3f | |||
e74574706e | |||
b381d9e06d | |||
a416b53d11 | |||
6fd13e3af0 | |||
4b7dc8200c | |||
b83279848a | |||
a48b278c10 | |||
75e19f4f0f | |||
1a5bf0c689 | |||
3e245f16c0 | |||
2698b7614b | |||
b296a9a0c7 | |||
9c8e853567 | |||
825d8ef6c9 | |||
da1201c552 | |||
e0c05bf437 | |||
a84b6bc7e4 | |||
00c4c30d72 | |||
72c7139d8c | |||
e287ba1a7e | |||
8e67a18551 | |||
590b88f718 | |||
00ee8813f7 | |||
e76f2ea89c | |||
c9f57c2d96 | |||
438d36341d | |||
3ab54b1591 | |||
77a2f186ee | |||
526344c9ac | |||
f8bd19f5db | |||
e4c6e4bf26 | |||
8783563176 | |||
6015a0ff15 | |||
63b76c32f9 | |||
c9264ee12c | |||
0d7b1a84cb | |||
81e17bad40 | |||
97d90b99e2 | |||
03d4d1cb36 | |||
3282cb85ae | |||
9354e797b6 | |||
3f9c2bc33b | |||
4369c1a113 | |||
b1e57e2a30 | |||
4d9489aeb1 | |||
45c247fa5b | |||
4e2663023b | |||
fa4608a95d | |||
fec47a09a9 | |||
022a97da99 | |||
e9116736cd | |||
2b549e3af6 | |||
b2afd1ea0b | |||
ef8e5b40b6 | |||
a6773ad442 | |||
c2add08efb | |||
a33c76a456 | |||
11b1bd278a | |||
e3a96ed3fc | |||
447243f994 | |||
710c0c9980 | |||
f0300c1711 | |||
e3c0bd5a3f | |||
8af61f561b | |||
780360834d | |||
74e503da92 | |||
d28b643c84 | |||
dc1049a6e7 | |||
f965b3de46 | |||
eb54a4fe91 | |||
5d3847d14d | |||
5b92286568 | |||
094bc59553 | |||
e9a0b3a8f3 | |||
1724430489 | |||
23c43ed21b | |||
79b334b7f1 | |||
9328ee4f63 | |||
d7594b19fc | |||
d6b3991d49 | |||
ec63bacdc1 | |||
e713ba06f1 | |||
37cb218437 | |||
4f79a8a204 | |||
7341298a11 | |||
b9c27e3a9d | |||
885fe38c01 | |||
2dbe8fc1a9 | |||
7122139e12 | |||
4e6c03c9da | |||
d5f27f9b1e | |||
86f19a3ab3 | |||
be0eefb0af | |||
c1cd92bbee | |||
44b7684d56 | |||
0c90e1eff6 | |||
491bca5e4b | |||
ebd676faaa | |||
045c5e8556 | |||
45b4cf2887 | |||
4b5acc065a | |||
73eca72f14 | |||
28431ff22c | |||
639bed2f6d | |||
77794eebdb | |||
eb37aa2bba | |||
048fe371aa | |||
0b666ad9fd | |||
87c9af142f | |||
6b46c22b42 | |||
94494b64d7 | |||
b648f37b97 | |||
78d3b83900 | |||
56b6ed6730 | |||
e0c68bf9ad | |||
64ebd9a194 | |||
35fe08b3bc | |||
aedab3f83f | |||
5c87ddc80e | |||
f53810fcd2 | |||
56fa3a09c8 | |||
58bca04a3f | |||
3c6afe7707 | |||
09296e0d71 | |||
4b3d64ec9f | |||
a904e15ecc | |||
a82a5ae184 | |||
bafd90807d | |||
08924ea36a | |||
0f8ea6872e | |||
1b7598e351 | |||
d2431128c7 | |||
8e0e12e5c9 | |||
e883117a7d | |||
cd0e08cae5 | |||
1490c42d9f | |||
789ee9f138 | |||
2c52e82352 | |||
0a981a6606 | |||
534f8d7a4e | |||
c4ca76e39e | |||
a8b9899dee | |||
d2cb4e003c | |||
0a0c62f384 | |||
6000df9779 | |||
24963e547c | |||
3ad3dee4ef | |||
46d44ca99c | |||
06d1af8b18 | |||
d34b2c4ffd | |||
0c52df7569 | |||
91bd38504e | |||
71a2b794b4 | |||
373714bf0b | |||
ee769171b9 | |||
6ebadbcca3 | |||
3f60d98163 | |||
ea00c1274e | |||
b7dc9dbc76 | |||
8b357dcb32 | |||
1f6346d880 | |||
b7bd38744c | |||
f8a67e282a | |||
0a7e199c82 | |||
5143f6d6f1 | |||
30b662df39 | |||
33f2d83506 | |||
4244a14ad3 | |||
f031fe58fa | |||
84cc240f34 | |||
b26906df1b | |||
56a3197f7f | |||
0505d7bd32 | |||
a448c0b81e | |||
8116fe8def | |||
7c6dcc8c73 | |||
1a9401e1f3 | |||
00d310f86d | |||
c4259fc8cc | |||
8c5614daa1 | |||
eb668c6466 | |||
a461c5682d | |||
e3478ee2ab | |||
0bea870b22 | |||
5fbdc6450d | |||
1531a1777a | |||
f38345fdad | |||
04d46ea33f | |||
3a2fa9a650 | |||
f5bbc5e961 | |||
95c9fefbd0 | |||
073a48ab85 | |||
753a783ba9 | |||
58f2598d5d | |||
3c835b692b | |||
7f2fa8bbcb | |||
a6fd1ca3db | |||
58a4905916 | |||
2c9607d5da | |||
371cb4f0f3 | |||
b46c809544 | |||
eb29a2898c | |||
c3a74e5e63 | |||
a1759aed19 | |||
1a3387706d | |||
41f8764232 | |||
4bf797c8f1 | |||
7e3b54f826 | |||
23d3a9ae42 | |||
756156e9db | |||
4807fb4c5c | |||
dd25c5b085 | |||
becfd1e9fa | |||
951d6398a0 | |||
7c98545b33 | |||
bb1060bdad | |||
7ad45a91ec | |||
51045962d3 | |||
034c5d0422 | |||
7148c14178 | |||
93fb61dc8f | |||
b36ceb5be4 | |||
37d7ad819b | |||
6bb6785936 | |||
cb70824ed1 | |||
ddc1082e8c | |||
f98d72a30b | |||
71df71c601 | |||
1d0f7c44e2 | |||
d78f19f8bb | |||
0e567381fb | |||
e2225d3b71 | |||
666af1e62d | |||
2fe3402362 | |||
14a236198f | |||
cc1b43b90a | |||
9448f0ce52 | |||
59fdd8f6be | |||
7b20318ee4 | |||
c3c955b02e | |||
6e56e41461 | |||
d74d5e0e44 | |||
cac08171de | |||
6f6c350781 | |||
506724fc93 | |||
b4fe70d3d8 | |||
3efbffe4e3 | |||
cafa873f06 | |||
b4f4347d6e | |||
5c866dd000 | |||
974249f2a5 | |||
a65022aed7 | |||
b101f40c32 | |||
e8e6c70e19 | |||
c8d27f6424 | |||
287e8cefda | |||
db8f2d9f07 | |||
cd6736d70b | |||
0d2e3788ba | |||
c0dcf67ec8 | |||
bc52336a1b | |||
3bfb052b0a | |||
c71d5a111e | |||
437b62c4d9 | |||
cbca0ae264 | |||
e0cde7dfc5 | |||
e720070945 | |||
a8ab6f4caf | |||
b7b1884950 | |||
755064d3e2 | |||
24a984086e | |||
4b831d58b7 | |||
62f36037ea | |||
ffdc1814c6 | |||
29776c0283 | |||
69d7384cc0 | |||
9720ac0019 | |||
fc56e1e517 | |||
0f4837980f | |||
9a6e27ac36 | |||
07202205c4 | |||
dc56bbeec8 | |||
4be537c51a | |||
66c568ba67 | |||
9ff8abaf29 | |||
b7144560c9 | |||
4be6d01dfb | |||
aef84320e0 | |||
9a5195e79e | |||
cc111941bb | |||
74ee1e5087 | |||
e5d1bd6589 | |||
6a0f7a5ceb | |||
554cd03269 | |||
9995194cf1 | |||
1298ab1647 | |||
b8ab3078fb | |||
50e8666a14 | |||
0659971ecf | |||
fd562cb9e2 | |||
aaa5cd4615 | |||
3f835f8ee3 | |||
5bf9a20d42 | |||
eedc8c7812 | |||
f0d1ed0cc4 | |||
8ba1aed5a3 | |||
9ef5e51c0f | |||
fe5566d642 | |||
4a2933b0b6 | |||
8ee0e9632c | |||
8fcb7112ec | |||
6ac466c0a4 | |||
d45fcc4381 | |||
a22e1199cf | |||
79f12d6b55 | |||
483f6702a6 | |||
f6e3464ab9 | |||
708876e9a7 | |||
29d04aa533 | |||
6fcccedb70 | |||
60f3aeb4ef | |||
c1ad987b04 | |||
9d0b7c6b31 | |||
d489cb1a8b | |||
0fe6d61036 | |||
092edabd2d | |||
1a68bce94c | |||
87fe3ade81 | |||
accabca618 | |||
091b21fae7 | |||
85398c728a | |||
7325b19aef | |||
7cdbbfa88e | |||
3ce3f1adc1 | |||
9880a86f80 | |||
647e5d76b0 | |||
7e4af9382e | |||
282d4a3563 | |||
cafeef33c3 | |||
4f48f1a850 | |||
a05a378db4 | |||
245362db96 | |||
b1b190b80d | |||
3408ce89a7 | |||
59a094cb77 | |||
8782b14842 | |||
0f38b4b856 | |||
75f407e191 | |||
4b07778609 | |||
9b81696a09 | |||
80e19e0ad7 | |||
962e8dca1d | |||
8da4be1b34 | |||
f2ef74d1a1 | |||
546c92751b | |||
ae903f190e | |||
bf33d9d703 | |||
3a89d80a61 | |||
fd45e83651 | |||
27e2fd9b06 | |||
9a49ace606 | |||
3413ecc2bd | |||
ad8b095677 | |||
38c72070fb | |||
93fe1af1a8 | |||
504bf4ba84 | |||
9f9c5fcf10 | |||
90a0237457 | |||
c83538a60c | |||
13d4e3f29f | |||
cefbb7c27d | |||
fa98434096 | |||
af3ca02e35 | |||
5c396c222a | |||
088bab61a4 | |||
080d18b06e | |||
54fb4e370c | |||
17f1f40140 | |||
b011ed6358 | |||
acbc6335af | |||
511c84760e | |||
6cbf82dbe0 | |||
896622de64 | |||
1a160a86fa | |||
11abd3cf6e | |||
9552badb16 | |||
6fd41beccd | |||
c679dea1b7 | |||
4788a4f775 | |||
9243bc58db | |||
2238725d1c | |||
bffa9f914c | |||
eeb31074de | |||
af22de2cfa | |||
1d3f05a9d4 | |||
935524f20c | |||
5847961fec | |||
40d7f5eff8 | |||
c57dedb034 | |||
b2d7b34082 | |||
4d67aca919 | |||
e3dfd7b1ab | |||
166945a461 | |||
46866be21d | |||
154e20484d | |||
aeee25e703 | |||
b51bcb55db | |||
b5784de33f | |||
9556a9be17 | |||
01c524ddd2 | |||
5e703dc70a | |||
bc96bd3410 | |||
094f0a8be3 | |||
3d996bf080 | |||
4b05ee6811 | |||
d7032aeb43 | |||
4ea1c030bc | |||
172e511e56 | |||
4481efd51e | |||
337c2bfd29 | |||
ffc82c027e | |||
e8fd5b4600 | |||
67f8916aa8 | |||
96e01f3a79 | |||
1e755f261f | |||
b2ddac610c | |||
ad05f64b13 | |||
9b472d36fc | |||
b54b0a1d25 | |||
f5794de636 | |||
7ae9d9690b | |||
db3cca7fbe | |||
b9743957fa | |||
0ef099421c | |||
f1ae5b1795 | |||
a8d6c75a24 | |||
1c2394227e | |||
c49e2f8bbd | |||
af403ba6fa | |||
ec5a8141eb | |||
92584bd323 | |||
586d9ee850 | |||
2de45a4da5 | |||
f5569e76db | |||
3a13ecba1f | |||
73b9ee9e84 | |||
b1682558a6 | |||
0a7c07977d | |||
0a83b17cdd | |||
2bad6584f6 | |||
872a3317b5 | |||
38901002b0 | |||
1db6a882bb | |||
571522e738 | |||
b5a80d3d49 | |||
3441d3399b | |||
fa288ab197 | |||
af11562627 | |||
286f08f095 | |||
92c3e26c7a | |||
6516c2532d | |||
82a0cc9d27 | |||
fa58da2401 | |||
1ddf93fd86 | |||
cba9c5619e | |||
70c149c7da | |||
b34e197424 | |||
f4b26247c0 | |||
8f0a1e32d5 | |||
c4b8f0cd2f | |||
aecb06cd2a | |||
e3c4f1f586 | |||
97b1156a7a | |||
02bfcd23a9 | |||
cc2f448d92 | |||
b45d07c8cb | |||
f0fe089013 | |||
a20c1b4547 | |||
56ffb4385d | |||
db3c5f91b6 | |||
17204b4696 | |||
8a83c45bc6 | |||
a6312ba98f | |||
4170f11958 | |||
04a0652614 | |||
b880dafe28 | |||
36530fc7c6 | |||
4fd4218178 | |||
632425c7d7 | |||
ad3e36a7ab | |||
a29b307554 | |||
1bcafca690 | |||
5d80edd969 | |||
e21b6d9db3 | |||
9c30bddb88 | |||
7336645501 | |||
59e6bd115e | |||
8597701b0f | |||
15aef079e3 | |||
42689d4842 | |||
6e9b8e21ae | |||
424612ea9d | |||
5afafd9146 | |||
affa76f81d | |||
340d5d557a | |||
214ed3667c | |||
122627dda2 | |||
7af95eadcc | |||
9ee858a00c | |||
27d456bf93 | |||
ea6e042a6f | |||
a594f56c02 | |||
e6fa74fe69 | |||
f184d69c7a | |||
228a5aa75d | |||
9a4f8199d6 | |||
ae0be1e857 | |||
d010cac8a5 | |||
63a758508a | |||
bf2658cee0 | |||
6ecb00a1d8 | |||
1990501786 | |||
963de90b7f | |||
13c7c3b3a6 | |||
e4049f3733 | |||
3cefa59a14 | |||
0cb5ae41c6 | |||
209040e80e | |||
2112c87e13 | |||
da44b0f0f6 | |||
c1c2f1f0a9 | |||
777a0a858e | |||
68e99c18c0 | |||
c99f93e40a | |||
969016b9e4 | |||
4ae58cc854 | |||
1fbbf13ec9 | |||
3f9dc08984 | |||
1ddf9960a6 | |||
9f45c0eb03 | |||
67155861e5 | |||
5111255942 | |||
b405deb55a | |||
9b5368d0ec | |||
f8aa806d77 | |||
e98ef7306d | |||
188904c318 | |||
9594293804 | |||
814801d321 | |||
0896511b14 | |||
222b177745 | |||
4189a30b13 | |||
f6f0a5d448 | |||
b21facab7b | |||
70312ed77f | |||
ee9255cb1d | |||
f045e19ddc | |||
3f1bececdf | |||
34c3a0cc1f | |||
8ef73eee51 | |||
e52f3f34a4 | |||
27b617b340 | |||
21a73d81ee | |||
7c3e6e8e86 | |||
42dc18ddfc | |||
801df72680 | |||
c8f161d17f | |||
549bfe7412 | |||
b00011a3f1 | |||
3ca826a480 | |||
b8ebb4d609 | |||
5321b606c1 | |||
a1ad74a986 | |||
29d95328ce | |||
b2eeccbcc2 | |||
bad0b55ab6 | |||
0878bd53d9 | |||
de910e1169 | |||
f2cf647508 | |||
9684737de7 | |||
ecc87ab1aa | |||
3cc0dd0d1e | |||
fa359c6fc4 | |||
5c71f2a439 | |||
8cc751d1cc | |||
978fd6858f | |||
41689256c6 | |||
99445f475b | |||
070d6a2faa | |||
3de63570f6 | |||
8d1ac37734 | |||
36503ead70 | |||
f4d3b3f0d6 | |||
acee1f7c6c | |||
c242467fdf | |||
47ae25eeb9 | |||
ddc4e7ffa0 | |||
6a2ffafdb9 | |||
0c091c1b24 | |||
55993ef0ce | |||
30a0820cbe | |||
194e3100a9 | |||
8ad4464d4b | |||
e7b0a736f5 | |||
fa4bdb4613 | |||
167eb01735 | |||
8fb5d72b13 | |||
83c0711760 | |||
8947c5a4aa | |||
a7562c9be1 | |||
08dc169f94 | |||
f549d8ac74 | |||
1ac7536286 | |||
ec0a56cb9c | |||
f0d24a68ee | |||
2c529f2118 | |||
af1d9345e0 | |||
03ce45d93a | |||
1695803248 | |||
58e3dd4cb6 | |||
c7f678688d | |||
7bf4c08f70 | |||
69beee5416 | |||
2200a31331 | |||
88e270723f | |||
a13e25f083 | |||
826ac80e62 | |||
4506584c48 | |||
3d3a30e200 | |||
76b83ac0f4 | |||
903a9bfd05 | |||
655ee1a64b | |||
e0e6c3fdb2 | |||
31f00974f2 | |||
c3218bb9c2 | |||
90fb6ed739 | |||
d2972024de | |||
3f9ad1253d | |||
a556a54dc9 | |||
dc0a2ca656 | |||
e9f986e54d | |||
357d852382 | |||
6e00c6790e | |||
f36604357e | |||
c3fb9d5549 | |||
f5b5c54d7d | |||
9f0b06bb86 | |||
57a384d6a0 | |||
69802e141f | |||
6fc02b7424 | |||
30cdd85028 | |||
871dd47019 | |||
37f8dd57e2 | |||
f827bfd83f | |||
b3af930153 | |||
cd488b7d07 | |||
e2373ff51a | |||
b3d2c900cd | |||
d5adec20a3 | |||
942256a647 | |||
ca39486d06 | |||
db632fcc2a | |||
a3321a5d80 | |||
521de13571 | |||
e6f91269ec | |||
3abf6a8a30 | |||
8d7f380dfd | |||
59163e2dd9 | |||
574021041d | |||
872adf1031 | |||
5fc1167802 | |||
c89a09e5d0 | |||
d9dabdfc74 | |||
6b910d1bd4 | |||
1c4f799845 | |||
bbd9ea8c00 | |||
fc67a968e8 | |||
3d113611cc | |||
c1af48bd85 | |||
07667771ef | |||
3822c29415 | |||
ff386d6585 | |||
e3ddfd8dff | |||
f0c79fdbca | |||
88ddb31477 | |||
077d1a41f1 | |||
857ab8662e | |||
a17f9bd0f4 | |||
f4b9e93b11 | |||
2c11bf2e66 | |||
0e33773e92 | |||
719e14b30a | |||
38883d1de4 | |||
c6c8351fca | |||
043f50487a | |||
3a2b91f1b7 | |||
a76d11d486 | |||
d1f01b5209 | |||
7a54dbf7d5 | |||
33a5d5fe93 | |||
201a4b7b2a | |||
591a28d516 | |||
22d160a3c3 | |||
903c82d7f1 | |||
b2e0395f19 | |||
d96a6b42a5 | |||
cf95708c18 | |||
7fe50d6402 | |||
e1c7b99450 | |||
12ae7b9a6b | |||
6ac5700f2e | |||
a0dd8617be | |||
1576072edb | |||
03d206a7ca | |||
c973de1d76 | |||
71336965a6 | |||
e791d0f74d | |||
3543a9a49f | |||
7dd198a99e | |||
e048116ab2 | |||
cda9ad8565 | |||
928f375683 | |||
d3e521f70e | |||
96e03eca14 | |||
659dfbf51f | |||
a7ee428214 | |||
a41254e18c | |||
4a3230904e | |||
c81a3f6ced | |||
a5412fc0cd | |||
83fc3c10cf | |||
6b6c87e510 | |||
267f9115ba | |||
39c87fd103 | |||
2ad2fdd235 | |||
1fda4b77ef | |||
5a8938209b | |||
0bf2ff6138 | |||
e33f3a2562 | |||
bba19ce667 | |||
9bf2d1d7b4 | |||
9fe210c454 | |||
f99fae3c61 | |||
860dcdb449 | |||
70cebaf74a | |||
317fe19da7 | |||
e7b6c8b7e0 | |||
478ba75d6b | |||
4e553ea095 | |||
0c46f15f94 | |||
7b92497d21 | |||
4668a798ca | |||
729d28d910 | |||
66e9d30fda | |||
6335be803c | |||
a77b1ff767 | |||
1f6ece233f | |||
d53077bb3e | |||
2b44d5fb6a | |||
10e1e0c125 | |||
017c281eaf | |||
c5b1bc1128 | |||
dafdab1bbc | |||
d0ebee5e3b | |||
aa7c741ec0 | |||
9e7b9487b0 | |||
c7a67b5a02 | |||
0e749dad4c | |||
fa72160c95 | |||
851e012c6c | |||
7f76403d0a | |||
126f065cc9 | |||
7ee4dec3f1 | |||
c07d09c011 | |||
4d98da44e3 | |||
15c00ea2ef | |||
522876c808 | |||
7d05cc8c5d | |||
49f4be6a2b | |||
e702515312 | |||
5fce8d2ce1 | |||
2696b22348 | |||
5df4754579 | |||
a00284c727 | |||
3832602ec4 | |||
3466f139a4 | |||
def7d156f6 | |||
33aab094ef | |||
cf6f344ccc | |||
b670b9bcde | |||
fea86b2955 | |||
7c610b216b | |||
bec34496f1 | |||
49014393e1 | |||
818d03c835 | |||
cdf1a96e23 | |||
bfcdec95cb | |||
fc55835932 | |||
3772910bf2 | |||
24379c14dc | |||
23846bcf1c | |||
9dd0a6e6a7 | |||
5ca473ac2d | |||
e1a551e8f2 | |||
0926702269 | |||
0a85347a0d | |||
fb59f73c1a | |||
eaa8b9cb1e | |||
b8261d7d83 | |||
f5827d4a83 | |||
b0f8a983c4 | |||
56c77bf482 | |||
d831c5dcc9 | |||
ce474eaf54 | |||
0da1c06b15 | |||
01edc94a4b | |||
f96563c3f2 | |||
30697f63f1 | |||
433fcef70b | |||
34b5b3d9c5 | |||
ea8b19a40f | |||
b0405db5a9 | |||
f34f0af6b1 | |||
51ed48941b | |||
22b6cbb4da | |||
87ac549689 | |||
2a6046de8e | |||
25dd5145bb | |||
f8f11b7f50 | |||
82f914e0dc | |||
3b41eec199 | |||
9359cc69d5 | |||
b02b636b36 | |||
a537154c28 | |||
39e1bdeb71 | |||
43bd28cdfa | |||
6c10458b5b | |||
3ccbf81646 | |||
2e38cd98c0 | |||
7780d9bab8 | |||
8feed96eac | |||
16d23292dc | |||
812a8bcc6c | |||
63807935cb | |||
92a8b646df | |||
d9f9e347ab | |||
2ef8ebe111 | |||
038a46b5ef | |||
3852ad3048 | |||
1075a73902 | |||
863a0c3f8f | |||
f8673931b8 | |||
dd4fb7aa90 | |||
2af5aad032 | |||
9027141ff8 | |||
c4bc331663 | |||
8be7c13d2d | |||
d7ea66b6a1 | |||
371c69d425 | |||
c9c1564d26 | |||
cd18a1b7db | |||
6aac096c77 | |||
7b58bd621a | |||
9b43b00d5c | |||
76694bfcf4 | |||
bfad138bb3 | |||
d8d23c9971 | |||
f77b30e81d | |||
d379478603 | |||
2600684999 | |||
54968b59bb | |||
6b5d12a8bb | |||
c4b9d5d8b9 | |||
f683817b48 | |||
52491b467a | |||
7789fda016 | |||
22abc27be4 | |||
c9138f964b | |||
c4346e6191 | |||
1a7830f460 | |||
b418c1abab | |||
1fbf1d2cf2 | |||
5a85cc4626 | |||
8041461a07 | |||
2ce72a1683 | |||
eae9372a5d | |||
ed09b2bdb8 | |||
1d7722043f | |||
95f9488a70 | |||
e7cbbd8d45 | |||
c8c255ad73 | |||
a264f8fa9b | |||
40e945b0c8 | |||
f3b04894b9 | |||
35b7e50166 | |||
6b3f684e2a | |||
63c66ce765 | |||
0636399b7a | |||
2c74815cc9 | |||
298bd6479a | |||
a8481215fa | |||
b7545b08fa | |||
cf8f3bcbed | |||
b8534a402d | |||
45b9a7f8e9 | |||
879431ebcd | |||
102354c218 | |||
af1283e92c | |||
6b777b066a | |||
1e01088698 | |||
3ea0651078 | |||
776b1c2294 | |||
dffa2eb04f | |||
5ecb9da801 | |||
00889c5139 | |||
af8dc3fd83 | |||
ba884b4e36 | |||
6ddd494826 | |||
aa2fd3f3bb | |||
cf00354f42 | |||
47f1fa3f2e | |||
db98f7e0b4 | |||
38ee5c4dfb | |||
aca2f9666d | |||
b74e085538 | |||
899de2ff56 | |||
cf521a5bd2 | |||
bc13248e1c | |||
0529f36fde | |||
74b4ecb7f3 | |||
333f658eb6 | |||
7cb5c0708b | |||
85869552e0 | |||
6f9843c14b | |||
7d44f60e45 | |||
8d16f69bb9 | |||
3a73a09391 | |||
009c71f7e2 | |||
073d39df44 | |||
ae7222f0df | |||
4d6c54272a | |||
13bfdde228 | |||
3cc78d3a41 | |||
45bb97cad6 | |||
546e4c5696 | |||
6b1917b931 | |||
30b22c8b78 | |||
6f5e92e5b3 | |||
cce5c70f29 | |||
4af7c82ef0 | |||
52e5fb7e0c | |||
a013e8ceb1 | |||
864632b582 | |||
71d6eaacef | |||
4aba05d749 | |||
7d335165ec | |||
37213209c5 | |||
fbde9bb731 | |||
f6b1b5ab37 | |||
7abd456d45 | |||
f12743de38 | |||
77e10ed757 | |||
ebcb9a2103 | |||
6fb2e080bc | |||
3ac5ffc188 | |||
88187ef282 | |||
489894cb32 | |||
be003970b7 | |||
3488ea7d1c | |||
9a6a399a29 | |||
7ab65352be | |||
b28fbfa13e | |||
07c656093c | |||
c9e8346e6a | |||
9e5ac76855 | |||
f671b7f63f | |||
236113e417 | |||
a340b18b19 | |||
f6c8e1a4bf | |||
160cff4a30 | |||
48685cf766 | |||
0f32102684 | |||
d46682d1f2 | |||
55833e20b1 | |||
02cfa76916 | |||
9314eea7e9 | |||
1733beabf7 | |||
471d8f6ff9 | |||
e47fcb196b | |||
3ae53961c8 | |||
113b002095 | |||
9447537d8c | |||
7404b8739e | |||
7239395d95 | |||
926d459c8f | |||
7cabe203dc | |||
1e53f4266a | |||
24b513c3c7 | |||
b982595c73 | |||
af8a36b7fb | |||
208e7d7943 | |||
557736f1cf | |||
61927e1941 | |||
fc75827aaf | |||
2f2531d921 | |||
d5f20980eb | |||
21eae981f9 | |||
ead7f4287a | |||
3b33150cfb | |||
6d34a68e54 | |||
5c483c9928 | |||
a68c99d782 | |||
0aebbae909 | |||
a3a2215bda | |||
eb377993b3 | |||
5ca52d785c | |||
8d9912b4e2 | |||
c77b1c9687 | |||
8849ecd772 | |||
7977b97227 | |||
4f34822900 | |||
bbb38ac106 | |||
ce934a547e | |||
16b19d35dd | |||
45cfa5b574 | |||
df9ccce5b2 | |||
f8516b677a | |||
dfde83bdce | |||
cb0f19e4f1 | |||
26b99d3f85 | |||
2f9c0d1d9e | |||
0423cafbeb | |||
0bd1412562 | |||
0339642e77 | |||
37a0b7b132 | |||
c30b605047 | |||
76076d6fad | |||
0a819ec4e2 | |||
57a717056e | |||
856c48541f | |||
2045091c4f | |||
03ac5a6eef | |||
32fadc9c30 | |||
15a89d4f17 | |||
d0f43e9934 | |||
31e779d3f2 | |||
30c79fd40d | |||
639c93460a | |||
7611730cdb | |||
9df9c1433a | |||
4ea422bcec | |||
6074e4f962 | |||
d52e6d01ec | |||
63caca33be | |||
64efa62a74 | |||
912eb5e8e9 | |||
bb628e8495 | |||
d0c19c2c97 | |||
926fdb7519 | |||
c886625c83 | |||
f6c10d8a2e | |||
2bd877528f | |||
d09889b1dd | |||
1b2e9122d5 | |||
7424388924 | |||
537436bd5e | |||
32fc0cd7e9 | |||
fb99494858 | |||
5b4d4b97bc | |||
c5180c8092 | |||
515c200d86 | |||
32aab82e32 | |||
6aaa350145 | |||
d3b4dfe104 | |||
9fc30f6db4 | |||
2d0f07091d | |||
3828eda507 | |||
1e736ec16d | |||
bba6437ea9 | |||
e5ab9a856c | |||
1515bba9c6 | |||
14a9ef4bbe | |||
041040c659 | |||
47f69f2d24 | |||
9dd4dc2088 | |||
b534c32ee3 | |||
d2712f1457 | |||
183f560d06 | |||
ae150c0897 | |||
606e1396cf | |||
5c85e037f8 | |||
5c523716aa | |||
5f8cbf359e | |||
e83834e6be | |||
02225aa95c | |||
9931ac9780 | |||
2ba2bc72ca | |||
45b8ba9ede | |||
40968e09b7 | |||
262f26cf76 | |||
785c619198 | |||
24a993710d | |||
c240bb12ae | |||
eed3b9db94 | |||
29a8823db1 | |||
a80955eacb | |||
9716c3de71 | |||
34fa3208e0 | |||
9c4e19958b | |||
0403299728 | |||
95701114e3 | |||
a99d17c3ac | |||
517149d325 | |||
32aa2575b5 | |||
8fe7b96629 | |||
9350619afa | |||
d8d8f0bfc8 | |||
0a39722719 | |||
9c0fa4d1d2 | |||
da0404ad03 | |||
b508fdb62c | |||
680f90df21 | |||
1a68807ad9 | |||
d901767b54 | |||
13d4443d4d | |||
74b63c12a0 | |||
cd42f6591a | |||
5491422b12 | |||
23f3ff3cf0 | |||
f90488c77b | |||
beb4536841 | |||
3fa46dd66d | |||
ad5fcf778f | |||
83b000ae88 | |||
33e179caa6 | |||
b1e941cab9 | |||
6db961d256 | |||
83409ded59 | |||
396b2e9772 | |||
94459deb94 | |||
660af84b8d | |||
7b31020903 | |||
9a4143b4d9 | |||
aebc47ad55 | |||
b6b5455917 | |||
5bc01cd51a | |||
c79acac37b | |||
a5f2aa6777 | |||
4169e5c510 | |||
0727c440b3 | |||
19a7ff0c43 | |||
5f18403199 | |||
9f325fca09 | |||
10d08acefa | |||
52d50e6bc4 | |||
e7de7c32db | |||
a5f07638ec | |||
aa2a3fe201 | |||
abd13ba4ca | |||
485ba093b3 | |||
36b18e4fb5 | |||
8d92232949 | |||
e4d8c094a4 | |||
d26e1c51a9 |
42
.appveyor.yml
Normal file
42
.appveyor.yml
Normal file
@ -0,0 +1,42 @@
|
||||
version: '{build}'
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v[0-9.]+\.[0-9.]+/
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.cargo'
|
||||
- '%APPVEYOR_BUILD_FOLDER%\target'
|
||||
|
||||
clone_folder: d:\projects\solana
|
||||
|
||||
build_script:
|
||||
- bash ci/publish-tarball.sh
|
||||
|
||||
notifications:
|
||||
- provider: Slack
|
||||
incoming_webhook:
|
||||
secure: GJsBey+F5apAtUm86MHVJ68Uqa6WN1SImcuIc4TsTZrDhA8K1QWUNw9FFQPybUWDyOcS5dly3kubnUqlGt9ux6Ad2efsfRIQYWv0tOVXKeY=
|
||||
channel: ci-status
|
||||
on_build_success: false
|
||||
on_build_failure: true
|
||||
on_build_status_changed: true
|
||||
|
||||
deploy:
|
||||
- provider: S3
|
||||
access_key_id:
|
||||
secure: fTbJl6JpFebR40J7cOWZ2mXBa3kIvEiXgzxAj6L3N7A=
|
||||
secret_access_key:
|
||||
secure: vItsBXb2rEFLvkWtVn/Rcxu5a5+2EwC+b7GsA0waJy9hXh6XuBAD0lnHd9re3g/4
|
||||
bucket: release.solana.com
|
||||
region: us-west-1
|
||||
set_public: true
|
||||
|
||||
- provider: GitHub
|
||||
auth_token:
|
||||
secure: 81fEmPZ0cV1wLtNuUrcmtgxKF6ROQF1+/ft5m+fHX21z6PoeCbaNo8cTyLioWBj7
|
||||
draft: false
|
||||
prerelease: false
|
||||
on:
|
||||
appveyor_repo_tag: true
|
1
.buildkite/env/.gitignore
vendored
Normal file
1
.buildkite/env/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/secrets_unencrypted.ejson
|
31
.buildkite/env/README.md
vendored
Normal file
31
.buildkite/env/README.md
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
[ejson](https://github.com/Shopify/ejson) and
|
||||
[ejson2env](https://github.com/Shopify/ejson2env) are used to manage access
|
||||
tokens and other secrets required for CI.
|
||||
|
||||
#### Setup
|
||||
```bash
|
||||
$ sudo gem install ejson ejson2env
|
||||
```
|
||||
|
||||
then obtain the necessary keypair and place it in `/opt/ejson/keys/`.
|
||||
|
||||
#### Usage
|
||||
Run the following command to decrypt the secrets into the environment:
|
||||
```bash
|
||||
eval $(ejson2env secrets.ejson)
|
||||
```
|
||||
|
||||
#### Managing secrets.ejson
|
||||
To decrypt `secrets.ejson` for modification, run:
|
||||
```bash
|
||||
$ ejson decrypt secrets.ejson -o secrets_unencrypted.ejson
|
||||
```
|
||||
|
||||
Edit, then run the following to re-encrypt the file **BEFORE COMMITING YOUR
|
||||
CHANGES**:
|
||||
```bash
|
||||
$ ejson encrypt secrets_unencrypted.ejson
|
||||
$ mv secrets_unencrypted.ejson secrets.ejson
|
||||
```
|
||||
|
15
.buildkite/env/secrets.ejson
vendored
Normal file
15
.buildkite/env/secrets.ejson
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f",
|
||||
"environment": {
|
||||
"CODECOV_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:JnxhrIxh09AvqdJgrVSYmb7PxSrh19aE:07WzVExCHEd1lJ1m8QizRRthGri+WBNeZRKjjEvsy5eo4gv3HD7zVEm42tVTGkqITKkBNQ==]",
|
||||
"CRATES_IO_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:d0jJqC32/axwzq/N7kMRmpxKhnRrhtpt:zvcPHwkOzGnjhNkAQSejwdy1Jkr9wR1qXFFCnfIjyt/XQYubzB1tLkoly/qdmeb5]",
|
||||
"GEOLOCATION_API_KEY": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R4gfB6Ey4i50HyfLt4UZDLBqg3qHEUye:UfZCOgt8XI6Y2g+ivCRVoS1fjFycFs7/GSevvCqh1B50mG0+hzpEyzXQLuKG5OeI]",
|
||||
"GITHUB_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:Vq2dkGTOzfEpRht0BAGHFp/hDogMvXJe:tFXHg1epVt2mq9hkuc5sRHe+KAnVREi/p8S+IZu67XRyzdiA/nGak1k860FXYuuzuaE0QWekaEc=]",
|
||||
"INFLUX_DATABASE": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:5KI9WBkXx3R/W4m256mU5MJOE7N8aAT9:Cb8QFELZ9I60t5zhJ9h55Kcs]",
|
||||
"INFLUX_PASSWORD": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:hQRMpLCrav+OYkNphkeM4hagdVoZv5Iw:AUO76rr6+gF1OLJA8ZLSG8wHKXgYCPNk6gRCV8rBhZBJ4KwDaxpvOhMl7bxxXG6jol7v4aRa/Lk=]",
|
||||
"INFLUX_USERNAME": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R7BNmQjfeqoGDAFTJu9bYTGHol2NgnYN:Q2tOT/EBcFvhFk+DKLKmVU7tLCpVC3Ui]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:Egc2dMrHDU0NcZ71LwGv/V66shUhwYUE:04VoIb8CKy7KYhQ5W4cEW9SDKZltxWBL5Hob106lMBbUOD/yUvKYcG3Ep8JfTMwO3K8zowW5HpU/IdGoilX0XWLiJJ6t+p05WWK0TA16nOEtwrEG+UK8wm3sN+xCO20i4jDhpNpgg3FYFHT5rKTHW8+zaBTNUX/SFxkN67Lm+92IM28CXYE43SU1WV6H99hGFFVpTK5JVM3JuYU1ex/dHRE+xCzTr4MYUB/F+nGoNFW8HUDV/y0e1jxT9to3x0SmnytEEuk+5RUzFuEt9cKNFeNml3fOCi4qL+sfj/Y5pjH9xDiUxsvH/8NL35jbLP244aFHgWcp]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_apple_darwin": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:NeOxSoWCvXB9AL4H6OK26l/7bmsKd/oz:Ijfoxtvk2CHlN1ZXHup3Gg/914kbbAkEGWJfvozA8UIe+aUzUObMyTrKkVOeNAH8Q8YH9tNzk7RRnrTcpnzeCCBLlWcVEeruMxHox3mPRzmSeDLxtbzCl9VePlRO3T7jg90K5hW+ZAkd5J/WJNzpAcmr93ts/of3MbvGHSujId/efCTzJEcP6JInnBb8Vrj7TlgKbzUlnqpq1+NjYPSXN3maKa9pKeo2JWxZlGBMoy6QWUUY5GbYEylw9smwh1LJcHZjlaZNMuOl4gNKtaSr38IXQkAXaRUJDPAmPras00YObKzXU8RkTrP4EoP/jx5LPR7f]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_pc_windows_msvc": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:7t+56twjW+jR7fpFNNeRFLPd7E4lbmyN:JuviDpkQrfVcNUGRGsa2e/UhvH6tTYyk1s4cHHE5xZH1NByL7Kpqx36VG/+o1AUGEeSQdsBnKgzYdMoFYbO8o50DoRPc86QIEVXCupD6J9avxLFtQgOWgJp+/mCdUVXlqXiFs/vQgS/L4psrcKdF6WHd77BeUr6ll8DjH+9m5FC9Rcai2pXno6VbPpunHQ0oUdYzhFR64+LiRacBaefQ9igZ+nSEWDLqbaZSyfm9viWkijoVFTq8gAgdXXEh7g0QdxVE5T6bPristJhT6jWBhWunPUCDNFFErWIsbRGctepl4pbCWqh2hNTw9btSgVfeY6uGCOsdy9E=]"
|
||||
}
|
||||
}
|
@ -1,2 +1,42 @@
|
||||
CI_BUILD_START=$(date +%s)
|
||||
export CI_BUILD_START
|
||||
|
||||
source ci/env.sh
|
||||
|
||||
#
|
||||
# Kill any running docker containers, which are potentially left over from the
|
||||
# previous CI job
|
||||
#
|
||||
(
|
||||
containers=$(docker ps -q)
|
||||
if [[ $(hostname) != metrics-solana-com && -n $containers ]]; then
|
||||
echo "+++ Killing stale docker containers"
|
||||
docker ps
|
||||
|
||||
# shellcheck disable=SC2086 # Don't want to double quote $containers
|
||||
docker kill $containers
|
||||
fi
|
||||
)
|
||||
|
||||
# Processes from previously aborted CI jobs seem to loiter, unclear why as one
|
||||
# would expect the buildkite-agent to clean up all child processes of the
|
||||
# aborted CI job.
|
||||
# But as a workaround for now manually kill some known loiterers. These
|
||||
# processes will all have the `init` process as their PPID:
|
||||
(
|
||||
victims=
|
||||
for name in bash cargo docker solana; do
|
||||
victims="$victims $(pgrep -u "$(id -u)" -P 1 -d \ $name)"
|
||||
done
|
||||
for victim in $victims; do
|
||||
echo "Killing pid $victim"
|
||||
kill -9 "$victim" || true
|
||||
done
|
||||
)
|
||||
|
||||
# HACK: These are in our docker images, need to be removed from CARGO_HOME
|
||||
# because we try to cache downloads across builds with CARGO_HOME
|
||||
# cargo lacks a facility for "system" tooling, always tries CARGO_HOME first
|
||||
cargo uninstall cargo-audit || true
|
||||
cargo uninstall svgbob_cli || true
|
||||
cargo uninstall mdbook || true
|
||||
|
@ -3,15 +3,16 @@
|
||||
#
|
||||
# Save target/ for the next CI build on this machine
|
||||
#
|
||||
if [[ -n $CARGO_TARGET_CACHE_NAME ]]; then
|
||||
(
|
||||
d=$HOME/cargo-target-cache/"$CARGO_TARGET_CACHE_NAME"
|
||||
mkdir -p "$d"
|
||||
set -x
|
||||
rsync -a --delete --link-dest="$PWD" target "$d"
|
||||
du -hs "$d"
|
||||
)
|
||||
fi
|
||||
(
|
||||
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"
|
||||
)
|
||||
|
||||
#
|
||||
# Add job_stats data point
|
||||
|
@ -1,4 +1,7 @@
|
||||
#!/bin/bash -e
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
eval "$(ejson2env .buildkite/env/secrets.ejson)"
|
||||
|
||||
# Ensure the pattern "+++ ..." never occurs when |set -x| is set, as buildkite
|
||||
# interprets this as the start of a log group.
|
||||
@ -8,20 +11,23 @@ export PS4="++"
|
||||
#
|
||||
# Restore target/ from the previous CI build on this machine
|
||||
#
|
||||
[[ -n "$CARGO_TARGET_CACHE_NAME" ]] || (
|
||||
d=$HOME/cargo-target-cache/"$CARGO_TARGET_CACHE_NAME"
|
||||
(
|
||||
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=1000000000 "$d")
|
||||
if [[ $cacheSizeInGB -gt 5 ]]; then
|
||||
echo "$d has gotten too large, removing it"
|
||||
read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d")
|
||||
echo "--- ${cacheSizeInGB}GB: $d"
|
||||
if [[ $cacheSizeInGB -gt $MAX_CACHE_SIZE ]]; then
|
||||
echo "--- $d is too large, removing it"
|
||||
rm -rf "$d"
|
||||
fi
|
||||
else
|
||||
echo "--- $d not present"
|
||||
fi
|
||||
|
||||
mkdir -p "$d"/target
|
||||
set -x
|
||||
rsync -a --delete --link-dest="$d" "$d"/target .
|
||||
)
|
||||
|
||||
|
31
.buildkite/pipeline-upload.sh
Executable file
31
.buildkite/pipeline-upload.sh
Executable file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# This script is used to upload the full buildkite pipeline. The steps defined
|
||||
# in the buildkite UI should simply be:
|
||||
#
|
||||
# steps:
|
||||
# - command: ".buildkite/pipeline-upload.sh"
|
||||
#
|
||||
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
if [[ -n $BUILDKITE_TAG ]]; then
|
||||
buildkite-agent annotate --style info --context release-tag \
|
||||
"https://github.com/solana-labs/solana/releases/$BUILDKITE_TAG"
|
||||
buildkite-agent pipeline upload ci/buildkite-release.yml
|
||||
else
|
||||
if [[ $BUILDKITE_BRANCH =~ ^pull ]]; then
|
||||
# Add helpful link back to the corresponding Github Pull Request
|
||||
buildkite-agent annotate --style info --context pr-backlink \
|
||||
"Github Pull Request: https://github.com/solana-labs/solana/$BUILDKITE_BRANCH"
|
||||
fi
|
||||
|
||||
if [[ $BUILDKITE_MESSAGE =~ GitBook: ]]; then
|
||||
buildkite-agent annotate --style info --context gitbook-ci-skip \
|
||||
"GitBook commit detected, CI skipped"
|
||||
exit
|
||||
fi
|
||||
|
||||
buildkite-agent pipeline upload ci/buildkite.yml
|
||||
fi
|
11
.codecov.yml
11
.codecov.yml
@ -1,5 +1,12 @@
|
||||
ignore:
|
||||
- "src/bin"
|
||||
coverage:
|
||||
range: 50..100
|
||||
round: down
|
||||
precision: 1
|
||||
status:
|
||||
project: off
|
||||
patch: off
|
||||
|
||||
comment:
|
||||
layout: "diff"
|
||||
behavior: default
|
||||
require_changes: no
|
||||
|
5
.gitbook.yaml
Normal file
5
.gitbook.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
root: ./docs/src
|
||||
|
||||
structure:
|
||||
readme: introduction.md
|
||||
summary: SUMMARY.md
|
6
.github/ISSUE_TEMPLATE.md
vendored
Normal file
6
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#### Problem
|
||||
|
||||
|
||||
|
||||
#### Proposed Solution
|
||||
|
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
#### Problem
|
||||
|
||||
#### Summary of Changes
|
||||
|
||||
Fixes #
|
24
.github/stale.yml
vendored
Normal file
24
.github/stale.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
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
|
||||
|
||||
# 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.
|
||||
|
||||
# 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.
|
26
.gitignore
vendored
26
.gitignore
vendored
@ -1,16 +1,24 @@
|
||||
Cargo.lock
|
||||
/docs/html/
|
||||
/docs/src/tests.ok
|
||||
/docs/src/.gitbook/assets/*.svg
|
||||
/farf/
|
||||
/solana-release/
|
||||
/solana-release.tar.bz2
|
||||
/solana-metrics/
|
||||
/solana-metrics.tar.bz2
|
||||
/target/
|
||||
|
||||
**/*.rs.bk
|
||||
.cargo
|
||||
|
||||
# node configuration files
|
||||
/config/
|
||||
/config-private/
|
||||
/config-drone/
|
||||
/config-validator/
|
||||
/config-client/
|
||||
/multinode-demo/test/config-client/
|
||||
|
||||
# test temp files, ledgers, etc.
|
||||
/farf/
|
||||
# log files
|
||||
*.log
|
||||
log-*.txt
|
||||
log-*/
|
||||
|
||||
# intellij files
|
||||
/.idea/
|
||||
/solana.iml
|
||||
/.vscode/
|
||||
|
53
.mergify.yml
Normal file
53
.mergify.yml
Normal file
@ -0,0 +1,53 @@
|
||||
# Validate your changes with:
|
||||
#
|
||||
# $ curl -F 'data=@.mergify.yml' https://gh.mergify.io/validate
|
||||
#
|
||||
# https://doc.mergify.io/
|
||||
pull_request_rules:
|
||||
- name: remove outdated reviews
|
||||
conditions:
|
||||
- base=master
|
||||
actions:
|
||||
dismiss_reviews:
|
||||
changes_requested: true
|
||||
- name: set automerge label on mergify backport PRs
|
||||
conditions:
|
||||
- author=mergify[bot]
|
||||
- head~=^mergify/bp/
|
||||
- "#status-failure=0"
|
||||
actions:
|
||||
label:
|
||||
add:
|
||||
- automerge
|
||||
- name: v0.23 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.23
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.23
|
||||
- name: v1.0 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v1.0
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v1.0
|
||||
- name: v1.1 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v1.1
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v1.1
|
||||
- name: v1.2 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v1.2
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v1.2
|
42
.travis.yml
Normal file
42
.travis.yml
Normal file
@ -0,0 +1,42 @@
|
||||
os:
|
||||
- osx
|
||||
|
||||
language: rust
|
||||
rust:
|
||||
- stable
|
||||
|
||||
install:
|
||||
- source ci/rust-version.sh
|
||||
|
||||
script:
|
||||
- source ci/env.sh
|
||||
- ci/publish-tarball.sh
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v\d+\.\d+/
|
||||
|
||||
notifications:
|
||||
slack:
|
||||
on_success: change
|
||||
secure: F4IjOE05MyaMOdPRL+r8qhs7jBvv4yDM3RmFKE1zNXnfUOqV4X38oQM1EI+YVsgpMQLj/pxnEB7wcTE4Bf86N6moLssEULCpvAuMVoXj4QbWdomLX+01WbFa6fLVeNQIg45NHrz2XzVBhoKOrMNnl+QI5mbR2AlS5oqsudHsXDnyLzZtd4Y5SDMdYG1zVWM01+oNNjgNfjcCGmOE/K0CnOMl6GPi3X9C34tJ19P2XT7MTDsz1/IfEF7fro2Q8DHEYL9dchJMoisXSkem5z7IDQkGzXsWdWT4NnndUvmd1MlTCE9qgoXDqRf95Qh8sB1Dz08HtvgfaosP2XjtNTfDI9BBYS15Ibw9y7PchAJE1luteNjF35EOy6OgmCLw/YpnweqfuNViBZz+yOPWXVC0kxnPIXKZ1wyH9ibeH6E4hr7a8o9SV/6SiWIlbYF+IR9jPXyTCLP/cc3sYljPWxDnhWFwFdRVIi3PbVAhVu7uWtVUO17Oc9gtGPgs/GrhOMkJfwQPXaudRJDpVZowxTX4x9kefNotlMAMRgq+Drbmgt4eEBiCNp0ITWgh17BiE1U09WS3myuduhoct85+FoVeaUkp1sxzHVtGsNQH0hcz7WcpZyOM+AwistJA/qzeEDQao5zi1eKWPbO2xAhi2rV1bDH6bPf/4lDBwLRqSiwvlWU=
|
||||
|
||||
deploy:
|
||||
- provider: s3
|
||||
access_key_id: $AWS_ACCESS_KEY_ID
|
||||
secret_access_key: $AWS_SECRET_ACCESS_KEY
|
||||
bucket: release.solana.com
|
||||
region: us-west-1
|
||||
skip_cleanup: true
|
||||
acl: public_read
|
||||
local_dir: travis-s3-upload
|
||||
on:
|
||||
all_branches: true
|
||||
- provider: releases
|
||||
api_key: $GITHUB_TOKEN
|
||||
skip_cleanup: true
|
||||
file_glob: true
|
||||
file: travis-release-upload/*
|
||||
on:
|
||||
tags: true
|
267
CONTRIBUTING.md
267
CONTRIBUTING.md
@ -1,53 +1,248 @@
|
||||
Solana Coding Guidelines
|
||||
===
|
||||
# Solana Coding Guidelines
|
||||
|
||||
The goal of these guidelines is to improve developer productivity by allowing developers to
|
||||
jump any file in the codebase and not need to adapt to inconsistencies in how the code is
|
||||
written. The codebase should appear as if it had been authored by a single developer. If you
|
||||
don't agree with a convention, submit a PR patching this document and let's discuss! Once
|
||||
the PR is accepted, *all* code should be updated as soon as possible to reflect the new
|
||||
The goal of these guidelines is to improve developer productivity by allowing
|
||||
developers to jump into any file in the codebase and not need to adapt to
|
||||
inconsistencies in how the code is written. The codebase should appear as if it
|
||||
had been authored by a single developer. If you don't agree with a convention,
|
||||
submit a PR patching this document and let's discuss! Once the PR is accepted,
|
||||
*all* code should be updated as soon as possible to reflect the new
|
||||
conventions.
|
||||
|
||||
Rust coding conventions
|
||||
---
|
||||
## Pull Requests
|
||||
|
||||
* All Rust code is formatted using the latest version of `rustfmt`. Once installed, it will be
|
||||
updated automatically when you update the compiler with `rustup`.
|
||||
Small, frequent PRs are much preferred to large, infrequent ones. A large PR is
|
||||
difficult to review, can block others from making progress, and can quickly get
|
||||
its author into "rebase hell". A large PR oftentimes arises when one change
|
||||
requires another, which requires another, and then another. When you notice
|
||||
those dependencies, put the fix into a commit of its own, then checkout a new
|
||||
branch, and cherry-pick it.
|
||||
|
||||
* All Rust code is linted with Clippy. If you'd prefer to ignore its advice, do so explicitly:
|
||||
```bash
|
||||
$ git commit -am "Fix foo, needed by bar"
|
||||
$ git checkout master
|
||||
$ git checkout -b fix-foo
|
||||
$ git cherry-pick fix-bar
|
||||
$ git push --set-upstream origin fix-foo
|
||||
```
|
||||
|
||||
```rust
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
|
||||
```
|
||||
Open a PR to start the review process and then jump back to your original
|
||||
branch to keep making progress. Consider rebasing to make your fix the first
|
||||
commit:
|
||||
|
||||
```bash
|
||||
$ git checkout fix-bar
|
||||
$ git rebase -i master <Move fix-foo to top>
|
||||
```
|
||||
|
||||
Once the commit is merged, rebase the original branch to purge the
|
||||
cherry-picked commit:
|
||||
|
||||
```bash
|
||||
$ git pull --rebase upstream master
|
||||
```
|
||||
|
||||
### How big is too big?
|
||||
|
||||
If there are no functional changes, PRs can be very large and that's no
|
||||
problem. If, however, your changes are making meaningful changes or additions,
|
||||
then about 1.0.4 lines of changes is about the most you should ask a Solana
|
||||
maintainer to review.
|
||||
|
||||
### Should I send small PRs as I develop large, new components?
|
||||
|
||||
Add only code to the codebase that is ready to be deployed. If you are building
|
||||
a large library, consider developing it in a separate git repository. When it
|
||||
is ready to be integrated, the Solana maintainers will work with you to decide
|
||||
on a path forward. Smaller libraries may be copied in whereas very large ones
|
||||
may be pulled in with a package manager.
|
||||
|
||||
## Getting Pull Requests Merged
|
||||
|
||||
There is no single person assigned to watching GitHub PR queue and ushering you
|
||||
through the process. Typically, you will ask the person that wrote a component
|
||||
to review changes to it. You can find the author using `git blame` or asking on
|
||||
Discord. When working to get your PR merged, it's most important to understand
|
||||
that changing the code is your priority and not necessarily a priority of the
|
||||
person you need an approval from. Also, while you may interact the most with
|
||||
the component author, you should aim to be inclusive of others. Providing a
|
||||
detailed problem description is the most effective means of engaging both the
|
||||
component author and other potentially interested parties.
|
||||
|
||||
Consider opening all PRs as Draft Pull Requests first. Using a draft PR allows
|
||||
you to kickstart the CI automation, which typically takes between 10 and 30
|
||||
minutes to execute. Use that time to write a detailed problem description. Once
|
||||
the description is written and CI succeeds, click the "Ready to Review" button
|
||||
and add reviewers. Adding reviewers before CI succeeds is a fast path to losing
|
||||
reviewer engagement. Not only will they be notified and see the PR is not yet
|
||||
ready for them, they will also be bombarded them with additional notifications
|
||||
each time you push a commit to get past CI or until they "mute" the PR. Once
|
||||
muted, you'll need to reach out over some other medium, such as Discord, to
|
||||
request they have another look. When you use draft PRs, no notifications are
|
||||
sent when you push commits and edit the PR description. Use draft PRs
|
||||
liberally. Don't bug the humans until you have gotten past the bots.
|
||||
|
||||
### What should be in my PR description?
|
||||
|
||||
Reviewing code is hard work and generally involves an attempt to guess the
|
||||
author's intent at various levels. Please assume reviewer time is scarce and do
|
||||
what you can to make your PR as consumable as possible. Inspired by techniques
|
||||
for writing good whitepapers, the guidance here aims to maximize reviewer
|
||||
engagement.
|
||||
|
||||
Assume the reviewer will spend no more than a few seconds reading the PR title.
|
||||
If it doesn't describe a noteworthy change, don't expect the reviewer to click
|
||||
to see more.
|
||||
|
||||
Next, like the abstract of a whitepaper, the reviewer will spend ~30 seconds
|
||||
reading the PR problem description. If what is described there doesn't look
|
||||
more important than competing issues, don't expect the reviewer to read on.
|
||||
|
||||
Next, the reviewer will read the proposed changes. At this point, the reviewer
|
||||
needs to be convinced the proposed changes are a *good* solution to the problem
|
||||
described above. If the proposed changes, not the code changes, generates
|
||||
discussion, consider closing the PR and returning with a design proposal
|
||||
instead.
|
||||
|
||||
Finally, once the reviewer understands the problem and agrees with the approach
|
||||
to solving it, the reviewer will view the code changes. At this point, the
|
||||
reviewer is simply looking to see if the implementation actually implements
|
||||
what was proposed and if that implementation is maintainable. When a concise,
|
||||
readable test for each new code path is present, the reviewer can safely ignore
|
||||
the details of its implementation. When those tests are missing, expect to
|
||||
either lose engagement or get a pile of review comments as the reviewer
|
||||
attempts to consider every ambiguity in your implementation.
|
||||
|
||||
### The PR Title
|
||||
|
||||
The PR title should contain a brief summary of the change, from the perspective
|
||||
of the user. Examples of good titles:
|
||||
|
||||
* Add rent to accounts
|
||||
* Fix out-of-memory error in validator
|
||||
* Clean up `process_message()` in runtime
|
||||
|
||||
The conventions here are all the same as a good git commit title:
|
||||
|
||||
* First word capitalized and in the imperative mood, not past tense ("add", not
|
||||
"added")
|
||||
* No trailing period
|
||||
* What was done, whom it was done to, and in what context
|
||||
|
||||
### The PR Problem Statement
|
||||
|
||||
The git repo implements a product with various features. The problem statement
|
||||
should describe how the product is missing a feature, how a feature is
|
||||
incomplete, or how the implementation of a feature is somehow undesirable. If
|
||||
an issue being fixed already describes the problem, go ahead and copy-paste it.
|
||||
As mentioned above, reviewer time is scarce. Given a queue of PRs to review,
|
||||
the reviewer may ignore PRs that expect them to click through links to see if
|
||||
the PR warrants attention.
|
||||
|
||||
### The Proposed Changes
|
||||
|
||||
Typically the content under the "Proposed changes" section will be a bulleted
|
||||
list of steps taken to solve the problem. Oftentimes, the list is identical to
|
||||
the subject lines of the git commits contained in the PR. It's especially
|
||||
generous (and not expected) to rebase or reword commits such that each change
|
||||
matches the logical flow in your PR description.
|
||||
|
||||
### When will my PR be reviewed?
|
||||
|
||||
PRs are typically reviewed and merged in under 7 days. If your PR has been open
|
||||
for longer, it's a strong indicator that the reviewers aren't confident the
|
||||
change meets the quality standards of the codebase. You might consider closing
|
||||
it and coming back with smaller PRs and longer descriptions detailing what
|
||||
problem it solves and how it solves it. Old PRs will be marked stale and then
|
||||
closed automatically 7 days later.
|
||||
|
||||
### How to manage review feedback?
|
||||
|
||||
After a reviewer provides feedback, you can quickly say "acknowledged, will
|
||||
fix" using a thumb's up emoji. If you're confident your fix is exactly as
|
||||
prescribed, add a reply "Fixed in COMMIT\_HASH" and mark the comment as
|
||||
resolved. If you're not sure, reply "Is this what you had in mind?
|
||||
COMMIT\_HASH" and if so, the reviewer will reply and mark the conversation as
|
||||
resolved. Marking conversations as resolved is an excellent way to engage more
|
||||
reviewers. Leaving conversations open may imply the PR is not yet ready for
|
||||
additional review.
|
||||
|
||||
### When will my PR be re-reviewed?
|
||||
|
||||
Recall that once your PR is opened, a notification is sent every time you push
|
||||
a commit. After a reviewer adds feedback, they won't be checking on the status
|
||||
of that feedback after every new commit. Instead, directly mention the reviewer
|
||||
when you feel your PR is ready for another pass.
|
||||
|
||||
## Draft Pull Requests
|
||||
|
||||
If you want early feedback on your PR, use GitHub's "Draft Pull Request"
|
||||
mechanism. Draft PRs are a convenient way to collaborate with the Solana
|
||||
maintainers without triggering notifications as you make changes. When you feel
|
||||
your PR is ready for a broader audience, you can transition your draft PR to a
|
||||
standard PR with the click of a button.
|
||||
|
||||
Do not add reviewers to draft PRs. GitHub doesn't automatically clear
|
||||
approvals when you click "Ready for Review", so a review that meant "I approve
|
||||
of the direction" suddenly has the appearance of "I approve of these changes."
|
||||
Instead, add a comment that mentions the usernames that you would like a review
|
||||
from. Ask explicitly what you would like feedback on.
|
||||
|
||||
## Rust coding conventions
|
||||
|
||||
* All Rust code is formatted using the latest version of `rustfmt`. Once
|
||||
installed, it will be updated automatically when you update the compiler with
|
||||
`rustup`.
|
||||
|
||||
* All Rust code is linted with Clippy. If you'd prefer to ignore its advice, do
|
||||
so explicitly:
|
||||
|
||||
```rust #[allow(clippy::too_many_arguments)] ```
|
||||
|
||||
Note: Clippy defaults can be overridden in the top-level file `.clippy.toml`.
|
||||
|
||||
* For variable names, when in doubt, spell it out. The mapping from type names to variable names
|
||||
is to lowercase the type name, putting an underscore before each capital letter. Variable names
|
||||
should *not* be abbreviated unless being used as closure arguments and the brevity improves
|
||||
readability. When a function has multiple instances of the same type, qualify each with a
|
||||
prefix and underscore (i.e. alice_keypair) or a numeric suffix (i.e. tx0).
|
||||
* For variable names, when in doubt, spell it out. The mapping from type names
|
||||
to variable names is to lowercase the type name, putting an underscore before
|
||||
each capital letter. Variable names should *not* be abbreviated unless being
|
||||
used as closure arguments and the brevity improves readability. When a function
|
||||
has multiple instances of the same type, qualify each with a prefix and
|
||||
underscore (i.e. alice\_keypair) or a numeric suffix (i.e. tx0).
|
||||
|
||||
* For function and method names, use `<verb>_<subject>`. For unit tests, that verb should
|
||||
always be `test` and for benchmarks the verb should always be `bench`. Avoid namespacing
|
||||
function names with some arbitrary word. Avoid abreviating words in function names.
|
||||
* For function and method names, use `<verb>_<subject>`. For unit tests, that
|
||||
verb should always be `test` and for benchmarks the verb should always be
|
||||
`bench`. Avoid namespacing function names with some arbitrary word. Avoid
|
||||
abbreviating words in function names.
|
||||
|
||||
* As they say, "When in Rome, do as the Romans do." A good patch should acknowledge the coding
|
||||
conventions of the code that surrounds it, even in the case where that code has not yet been
|
||||
updated to meet the conventions described here.
|
||||
* As they say, "When in Rome, do as the Romans do." A good patch should
|
||||
acknowledge the coding conventions of the code that surrounds it, even in the
|
||||
case where that code has not yet been updated to meet the conventions described
|
||||
here.
|
||||
|
||||
|
||||
Terminology
|
||||
---
|
||||
## Terminology
|
||||
|
||||
Inventing new terms is allowed, but should only be done when the term is widely used and
|
||||
understood. Avoid introducing new 3-letter terms, which can be confused with 3-letter acronyms.
|
||||
Inventing new terms is allowed, but should only be done when the term is widely
|
||||
used and understood. Avoid introducing new 3-letter terms, which can be
|
||||
confused with 3-letter acronyms.
|
||||
|
||||
Some terms we currently use regularly in the codebase:
|
||||
[Terms currently in use](docs/src/terminology.md)
|
||||
|
||||
* fullnode: n. A fully participating network node.
|
||||
* hash: n. A SHA-256 Hash.
|
||||
* keypair: n. A Ed25519 key-pair, containing a public and private key.
|
||||
* pubkey: n. The public key of a Ed25519 key-pair.
|
||||
* sigverify: v. To verify a Ed25519 digital signature.
|
||||
|
||||
## Design Proposals
|
||||
|
||||
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)
|
||||
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
|
||||
`docs/src/proposals` directory and references it from the [table of
|
||||
contents](docs/src/SUMMARY.md). Add any relevant *maintainers* to the PR
|
||||
review.
|
||||
2. The PR being merged indicates your proposed change was accepted and that the
|
||||
maintainers support your plan of attack.
|
||||
3. Submit PRs that implement the proposal. When the implementation reveals the
|
||||
need for tweaks to the proposal, be sure to update the proposal and have that
|
||||
change reviewed by the same people as in step 1.
|
||||
4. Once the implementation is complete, submit a PR that moves the link from
|
||||
the Accepted Proposals to the Implemented Proposals section.
|
||||
|
6732
Cargo.lock
generated
Normal file
6732
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
202
Cargo.toml
202
Cargo.toml
@ -1,148 +1,64 @@
|
||||
[package]
|
||||
name = "solana"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "0.9.0"
|
||||
documentation = "https://docs.rs/solana"
|
||||
homepage = "http://solana.com/"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
authors = [
|
||||
"Anatoly Yakovenko <anatoly@solana.com>",
|
||||
"Greg Fitzgerald <greg@solana.com>",
|
||||
"Stephen Akridge <stephen@solana.com>",
|
||||
"Michael Vines <mvines@solana.com>",
|
||||
"Rob Walker <rob@solana.com>",
|
||||
"Pankaj Garg <pankaj@solana.com>",
|
||||
"Tyera Eulberg <tyera@solana.com>",
|
||||
]
|
||||
license = "Apache-2.0"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-upload-perf"
|
||||
path = "src/bin/upload-perf.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-bench-streamer"
|
||||
path = "src/bin/bench-streamer.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-bench-tps"
|
||||
path = "src/bin/bench-tps.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-drone"
|
||||
path = "src/bin/drone.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-replicator"
|
||||
path = "src/bin/replicator.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-fullnode"
|
||||
path = "src/bin/fullnode.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-fullnode-config"
|
||||
path = "src/bin/fullnode-config.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-genesis"
|
||||
path = "src/bin/genesis.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-ledger-tool"
|
||||
path = "src/bin/ledger-tool.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-keygen"
|
||||
path = "src/bin/keygen.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "solana-wallet"
|
||||
path = "src/bin/wallet.rs"
|
||||
|
||||
[badges]
|
||||
codecov = { repository = "solana-labs/solana", branch = "master", service = "github" }
|
||||
|
||||
[features]
|
||||
unstable = []
|
||||
ipv6 = []
|
||||
cuda = []
|
||||
erasure = []
|
||||
test = []
|
||||
|
||||
[dependencies]
|
||||
atty = "0.2"
|
||||
bincode = "1.0.0"
|
||||
bs58 = "0.2.0"
|
||||
byteorder = "1.2.1"
|
||||
bytes = "0.4"
|
||||
chrono = { version = "0.4.0", features = ["serde"] }
|
||||
clap = "2.31"
|
||||
dirs = "1.0.2"
|
||||
env_logger = "0.5.12"
|
||||
generic-array = { version = "0.12.0", default-features = false, features = ["serde"] }
|
||||
getopts = "0.2"
|
||||
influx_db_client = "0.3.4"
|
||||
solana-jsonrpc-core = "0.1"
|
||||
solana-jsonrpc-http-server = "0.1"
|
||||
solana-jsonrpc-macros = "0.1"
|
||||
ipnetwork = "0.12.7"
|
||||
itertools = "0.7.8"
|
||||
libc = "0.2.43"
|
||||
libloading = "0.5.0"
|
||||
log = "0.4.2"
|
||||
matches = "0.1.6"
|
||||
nix = "0.11.0"
|
||||
pnet_datalink = "0.21.0"
|
||||
rand = "0.5.1"
|
||||
rayon = "1.0.0"
|
||||
reqwest = "0.9.0"
|
||||
ring = "0.13.2"
|
||||
sha2 = "0.7.0"
|
||||
serde = "1.0.27"
|
||||
serde_cbor = "0.9.0"
|
||||
serde_derive = "1.0.27"
|
||||
serde_json = "1.0.10"
|
||||
socket2 = "0.3.8"
|
||||
solana_program_interface = { path = "common" }
|
||||
sys-info = "0.5.6"
|
||||
tokio = "0.1"
|
||||
tokio-codec = "0.1"
|
||||
untrusted = "0.6.2"
|
||||
|
||||
[dev-dependencies]
|
||||
noop = { path = "programs/noop" }
|
||||
print = { path = "programs/print" }
|
||||
move_funds = { path = "programs/move_funds" }
|
||||
|
||||
[[bench]]
|
||||
name = "bank"
|
||||
|
||||
[[bench]]
|
||||
name = "banking_stage"
|
||||
|
||||
[[bench]]
|
||||
name = "ledger"
|
||||
|
||||
[[bench]]
|
||||
name = "signature"
|
||||
|
||||
[[bench]]
|
||||
name = "sigverify"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
".",
|
||||
"common",
|
||||
"bench-exchange",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"banking-bench",
|
||||
"chacha",
|
||||
"chacha-cuda",
|
||||
"chacha-sys",
|
||||
"cli-config",
|
||||
"client",
|
||||
"core",
|
||||
"faucet",
|
||||
"perf",
|
||||
"validator",
|
||||
"genesis",
|
||||
"genesis-programs",
|
||||
"gossip",
|
||||
"install",
|
||||
"keygen",
|
||||
"ledger",
|
||||
"ledger-tool",
|
||||
"local-cluster",
|
||||
"logger",
|
||||
"log-analyzer",
|
||||
"merkle-tree",
|
||||
"measure",
|
||||
"metrics",
|
||||
"net-shaper",
|
||||
"programs/bpf_loader",
|
||||
"programs/budget",
|
||||
"programs/btc_spv",
|
||||
"programs/btc_spv_bin",
|
||||
"programs/config",
|
||||
"programs/exchange",
|
||||
"programs/failure",
|
||||
"programs/noop",
|
||||
"programs/print",
|
||||
"programs/move_funds",
|
||||
"programs/ownable",
|
||||
"programs/stake",
|
||||
"programs/storage",
|
||||
"programs/vest",
|
||||
"programs/vote",
|
||||
"archiver",
|
||||
"archiver-lib",
|
||||
"archiver-utils",
|
||||
"remote-wallet",
|
||||
"runtime",
|
||||
"sdk",
|
||||
"sdk-c",
|
||||
"scripts",
|
||||
"sys-tuner",
|
||||
"upload-perf",
|
||||
"net-utils",
|
||||
"vote-signer",
|
||||
"cli",
|
||||
"rayon-threadlimit",
|
||||
"watchtower",
|
||||
]
|
||||
default-members = [
|
||||
".",
|
||||
"common",
|
||||
"programs/noop",
|
||||
"programs/print",
|
||||
"programs/move_funds",
|
||||
|
||||
exclude = [
|
||||
"programs/bpf",
|
||||
"programs/move_loader",
|
||||
"programs/librapay",
|
||||
]
|
||||
|
406
README.md
406
README.md
@ -1,9 +1,9 @@
|
||||
[](https://crates.io/crates/solana)
|
||||
[](https://docs.rs/solana)
|
||||
[](https://solana-ci-gate.herokuapp.com/buildkite_public_log?https://buildkite.com/solana-labs/solana/builds/latest/master)
|
||||
[](https://crates.io/crates/solana-core)
|
||||
[](https://docs.rs/solana-core)
|
||||
[](https://buildkite.com/solana-labs/solana/builds?branch=master)
|
||||
[](https://codecov.io/gh/solana-labs/solana)
|
||||
|
||||
Blockchain, Rebuilt for Scale
|
||||
Blockchain Rebuilt for Scale
|
||||
===
|
||||
|
||||
Solana™ is a new blockchain architecture built from the ground up for scale. The architecture supports
|
||||
@ -21,246 +21,48 @@ It's possible for a centralized database to process 710,000 transactions per sec
|
||||
|
||||
> Perhaps the most striking difference between algorithms obtained by our method and ones based upon timeout is that using timeout produces a traditional distributed algorithm in which the processes operate asynchronously, while our method produces a globally synchronous one in which every process does the same thing at (approximately) the same time. Our method seems to contradict the whole purpose of distributed processing, which is to permit different processes to operate independently and perform different functions. However, if a distributed system is really a single system, then the processes must be synchronized in some way. Conceptually, the easiest way to synchronize processes is to get them all to do the same thing at the same time. Therefore, our method is used to implement a kernel that performs the necessary synchronization--for example, making sure that two different processes do not try to modify a file at the same time. Processes might spend only a small fraction of their time executing the synchronizing kernel; the rest of the time, they can operate independently--e.g., accessing different files. This is an approach we have advocated even when fault-tolerance is not required. The method's basic simplicity makes it easier to understand the precise properties of a system, which is crucial if one is to know just how fault-tolerant the system is. [\[L.Lamport (1984)\]](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.71.1078)
|
||||
|
||||
Furthermore, and much to our surprise, it can be implemented using a mechanism that has existed in Bitcoin since day one. The Bitcoin feature is called nLocktime and it can be used to postdate transactions using block height instead of a timestamp. As a Bitcoin client, you'd use block height instead of a timestamp if you don't trust the network. Block height turns out to be an instance of what's being called a Verifiable Delay Function in cryptography circles. It's a cryptographically secure way to say time has passed. In Solana, we use a far more granular verifiable delay function, a SHA 256 hash chain, to checkpoint the ledger and coordinate consensus. With it, we implement Optimistic Concurrency Control and are now well in route towards that theoretical limit of 710,000 transactions per second.
|
||||
Furthermore, and much to our surprise, it can be implemented using a mechanism that has existed in Bitcoin since day one. The Bitcoin feature is called nLocktime and it can be used to postdate transactions using block height instead of a timestamp. As a Bitcoin client, you'd use block height instead of a timestamp if you don't trust the network. Block height turns out to be an instance of what's being called a Verifiable Delay Function in cryptography circles. It's a cryptographically secure way to say time has passed. In Solana, we use a far more granular verifiable delay function, a SHA 256 hash chain, to checkpoint the ledger and coordinate consensus. With it, we implement Optimistic Concurrency Control and are now well en route towards that theoretical limit of 710,000 transactions per second.
|
||||
|
||||
|
||||
Testnet Demos
|
||||
Documentation
|
||||
===
|
||||
|
||||
The Solana repo contains all the scripts you might need to spin up your own
|
||||
local testnet. Depending on what you're looking to achieve, you may want to
|
||||
run a different variation, as the full-fledged, performance-enhanced
|
||||
multinode testnet is considerably more complex to set up than a Rust-only,
|
||||
singlenode testnode. If you are looking to develop high-level features, such
|
||||
as experimenting with smart contracts, save yourself some setup headaches and
|
||||
stick to the Rust-only singlenode demo. If you're doing performance optimization
|
||||
of the transaction pipeline, consider the enhanced singlenode demo. If you're
|
||||
doing consensus work, you'll need at least a Rust-only multinode demo. If you want
|
||||
to reproduce our TPS metrics, run the enhanced multinode demo.
|
||||
Before you jump into the code, review the documentation [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com).
|
||||
|
||||
For all four variations, you'd need the latest Rust toolchain and the Solana
|
||||
source code:
|
||||
(The _latest_ development version of the docs is [available here](https://docs.solana.com/v/master).)
|
||||
|
||||
First, install Rust's package manager Cargo.
|
||||
Release Binaries
|
||||
===
|
||||
Official release binaries are available at [Github Releases](https://github.com/solana-labs/solana/releases).
|
||||
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
$ source $HOME/.cargo/env
|
||||
```
|
||||
Additionally we provide pre-release binaries for the latest code on the edge and
|
||||
beta channels. Note that these pre-release binaries may be less stable than an
|
||||
official release.
|
||||
|
||||
Now checkout the code from github:
|
||||
### Edge channel
|
||||
#### Linux (x86_64-unknown-linux-gnu)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
|
||||
#### mac OS (x86_64-apple-darwin)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-apple-darwin.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
|
||||
#### Windows (x86_64-pc-windows-msvc)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-pc-windows-msvc.tar.bz2)
|
||||
* [solana-install-init.exe](http://release.solana.com/edge/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
|
||||
#### All platforms
|
||||
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/edge/solana-metrics.tar.bz2)
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/solana-labs/solana.git
|
||||
$ cd solana
|
||||
```
|
||||
|
||||
The demo code is sometimes broken between releases as we add new low-level
|
||||
features, so if this is your first time running the demo, you'll improve
|
||||
your odds of success if you check out the
|
||||
[latest release](https://github.com/solana-labs/solana/releases)
|
||||
before proceeding:
|
||||
|
||||
```bash
|
||||
$ git checkout v0.8.0
|
||||
```
|
||||
|
||||
Configuration Setup
|
||||
---
|
||||
|
||||
The network is initialized with a genesis ledger and leader/validator configuration files.
|
||||
These files can be generated by running the following script.
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/setup.sh
|
||||
```
|
||||
|
||||
Drone
|
||||
---
|
||||
|
||||
In order for the leader, client and validators to work, we'll need to
|
||||
spin up a drone to give out some test tokens. The drone delivers Milton
|
||||
Friedman-style "air drops" (free tokens to requesting clients) to be used in
|
||||
test transactions.
|
||||
|
||||
Start the drone on the leader node with:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/drone.sh
|
||||
```
|
||||
|
||||
Singlenode Testnet
|
||||
---
|
||||
|
||||
Before you start a fullnode, make sure you know the IP address of the machine you
|
||||
want to be the leader for the demo, and make sure that udp ports 8000-10000 are
|
||||
open on all the machines you want to test with.
|
||||
|
||||
Now start the server in a separate shell:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/leader.sh
|
||||
```
|
||||
|
||||
Wait a few seconds for the server to initialize. It will print "leader ready..." when it's ready to
|
||||
receive transactions. The leader will request some tokens from the drone if it doesn't have any.
|
||||
The drone does not need to be running for subsequent leader starts.
|
||||
|
||||
Multinode Testnet
|
||||
---
|
||||
|
||||
To run a multinode testnet, after starting a leader node, spin up some validator nodes in
|
||||
separate shells:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/validator.sh
|
||||
```
|
||||
|
||||
To run a performance-enhanced leader or validator (on Linux),
|
||||
[CUDA 9.2](https://developer.nvidia.com/cuda-downloads) must be installed on
|
||||
your system:
|
||||
|
||||
```bash
|
||||
$ ./fetch-perf-libs.sh
|
||||
$ SOLANA_CUDA=1 ./multinode-demo/leader.sh
|
||||
$ SOLANA_CUDA=1 ./multinode-demo/validator.sh
|
||||
```
|
||||
|
||||
|
||||
Testnet Client Demo
|
||||
---
|
||||
|
||||
Now that your singlenode or multinode testnet is up and running let's send it
|
||||
some transactions!
|
||||
|
||||
In a separate shell start the client:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/client.sh # runs against localhost by default
|
||||
```
|
||||
|
||||
What just happened? The client demo spins up several threads to send 500,000 transactions
|
||||
to the testnet as quickly as it can. The client then pings the testnet periodically to see
|
||||
how many transactions it processed in that time. Take note that the demo intentionally
|
||||
floods the network with UDP packets, such that the network will almost certainly drop a
|
||||
bunch of them. This ensures the testnet has an opportunity to reach 710k TPS. The client
|
||||
demo completes after it has convinced itself the testnet won't process any additional
|
||||
transactions. You should see several TPS measurements printed to the screen. In the
|
||||
multinode variation, you'll see TPS measurements for each validator node as well.
|
||||
|
||||
Public Testnet
|
||||
--------------
|
||||
In this example the client connects to our public testnet. To run validators on the testnet you would need to open udp ports `8000-10000`.
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/client.sh --network $(dig +short testnet.solana.com):8001 --identity config-private/client-id.json --duration 60
|
||||
```
|
||||
|
||||
You can observe the effects of your client's transactions on our [dashboard](https://metrics.solana.com:3000/d/testnet/testnet-hud?orgId=2&from=now-30m&to=now&refresh=5s&var-testnet=testnet)
|
||||
|
||||
|
||||
Linux Snap
|
||||
---
|
||||
A Linux [Snap](https://snapcraft.io/) is available, which can be used to
|
||||
easily get Solana running on supported Linux systems without building anything
|
||||
from source. The `edge` Snap channel is updated daily with the latest
|
||||
development from the `master` branch. To install:
|
||||
|
||||
```bash
|
||||
$ sudo snap install solana --edge --devmode
|
||||
```
|
||||
|
||||
(`--devmode` flag is required only for `solana.fullnode-cuda`)
|
||||
|
||||
Once installed the usual Solana programs will be available as `solona.*` instead
|
||||
of `solana-*`. For example, `solana.fullnode` instead of `solana-fullnode`.
|
||||
|
||||
Update to the latest version at any time with:
|
||||
|
||||
```bash
|
||||
$ snap info solana
|
||||
$ sudo snap refresh solana --devmode
|
||||
```
|
||||
|
||||
### Daemon support
|
||||
The snap supports running a leader, validator or leader+drone node as a system
|
||||
daemon.
|
||||
|
||||
Run `sudo snap get solana` to view the current daemon configuration. To view
|
||||
daemon logs:
|
||||
1. Run `sudo snap logs -n=all solana` to view the daemon initialization log
|
||||
2. Runtime logging can be found under `/var/snap/solana/current/leader/`,
|
||||
`/var/snap/solana/current/validator/`, or `/var/snap/solana/current/drone/` depending
|
||||
on which `mode=` was selected. Within each log directory the file `current`
|
||||
contains the latest log, and the files `*.s` (if present) contain older rotated
|
||||
logs.
|
||||
|
||||
Disable the daemon at any time by running:
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=
|
||||
```
|
||||
|
||||
Runtime configuration files for the daemon can be found in
|
||||
`/var/snap/solana/current/config`.
|
||||
|
||||
#### Leader daemon
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=leader
|
||||
```
|
||||
|
||||
If CUDA is available:
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=leader enable-cuda=1
|
||||
```
|
||||
|
||||
`rsync` must be configured and running on the leader.
|
||||
|
||||
1. Ensure rsync is installed with `sudo apt-get -y install rsync`
|
||||
2. Edit `/etc/rsyncd.conf` to include the following
|
||||
```
|
||||
[config]
|
||||
path = /var/snap/solana/current/config
|
||||
hosts allow = *
|
||||
read only = true
|
||||
```
|
||||
3. Run `sudo systemctl enable rsync; sudo systemctl start rsync`
|
||||
4. Test by running `rsync -Pzravv rsync://<ip-address-of-leader>/config
|
||||
solana-config` from another machine. **If the leader is running on a cloud
|
||||
provider it may be necessary to configure the Firewall rules to permit ingress
|
||||
to port tcp:873, tcp:9900 and the port range udp:8000-udp:10000**
|
||||
|
||||
|
||||
To run both the Leader and Drone:
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=leader+drone
|
||||
|
||||
```
|
||||
|
||||
#### Validator daemon
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=validator
|
||||
|
||||
```
|
||||
If CUDA is available:
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=validator enable-cuda=1
|
||||
```
|
||||
|
||||
By default the validator will connect to **testnet.solana.com**, override
|
||||
the leader IP address by running:
|
||||
|
||||
```bash
|
||||
$ sudo snap set solana mode=validator leader-address=127.0.0.1 #<-- change IP address
|
||||
```
|
||||
|
||||
It's assumed that the leader will be running `rsync` configured as described in
|
||||
the previous **Leader daemon** section.
|
||||
### Beta channel
|
||||
#### Linux (x86_64-unknown-linux-gnu)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
|
||||
#### mac OS (x86_64-apple-darwin)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-apple-darwin.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
|
||||
#### Windows (x86_64-pc-windows-msvc)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-pc-windows-msvc.tar.bz2)
|
||||
* [solana-install-init.exe](http://release.solana.com/beta/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
|
||||
#### All platforms
|
||||
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/beta/solana-metrics.tar.bz2)
|
||||
|
||||
Developing
|
||||
===
|
||||
@ -273,10 +75,10 @@ Install rustc, cargo and rustfmt:
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
$ source $HOME/.cargo/env
|
||||
$ rustup component add rustfmt-preview
|
||||
$ rustup component add rustfmt
|
||||
```
|
||||
|
||||
If your rustc version is lower than 1.26.1, please update it:
|
||||
If your rustc version is lower than 1.39.0, please update it:
|
||||
|
||||
```bash
|
||||
$ rustup update
|
||||
@ -285,7 +87,8 @@ $ rustup update
|
||||
On Linux systems you may need to install libssl-dev, pkg-config, zlib1g-dev, etc. On Ubuntu:
|
||||
|
||||
```bash
|
||||
$ sudo apt-get install libssl-dev pkg-config zlib1g-dev
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang
|
||||
```
|
||||
|
||||
Download the source code:
|
||||
@ -295,6 +98,17 @@ $ git clone https://github.com/solana-labs/solana.git
|
||||
$ cd solana
|
||||
```
|
||||
|
||||
Build
|
||||
|
||||
```bash
|
||||
$ cargo build
|
||||
```
|
||||
|
||||
Then to run a minimal local cluster
|
||||
```bash
|
||||
$ ./run.sh
|
||||
```
|
||||
|
||||
Testing
|
||||
---
|
||||
|
||||
@ -304,43 +118,88 @@ Run the test suite:
|
||||
$ cargo test
|
||||
```
|
||||
|
||||
To emulate all the tests that will run on a Pull Request, run:
|
||||
|
||||
```bash
|
||||
$ ./ci/run-local.sh
|
||||
```
|
||||
|
||||
Debugging
|
||||
Local Testnet
|
||||
---
|
||||
|
||||
There are some useful debug messages in the code, you can enable them on a per-module and per-level
|
||||
basis. Before running a leader or validator set the normal RUST\_LOG environment variable.
|
||||
Start your own testnet locally, instructions are in the online docs [Solana: Blockchain Rebuild for Scale: Getting Started](https://docs.solana.com/building-from-source).
|
||||
|
||||
For example, to enable info everywhere and debug only in the solana::banking_stage module:
|
||||
Remote Testnets
|
||||
---
|
||||
|
||||
```bash
|
||||
$ export RUST_LOG=info,solana::banking_stage=debug
|
||||
```
|
||||
* `testnet` - public stable testnet accessible via devnet.solana.com. Runs 24/7
|
||||
|
||||
Generally we are using debug for infrequent debug messages, trace for potentially frequent
|
||||
messages and info for performance-related logging.
|
||||
|
||||
You can also attach to a running process with GDB. The leader's process is named
|
||||
_solana-fullnode_:
|
||||
## Deploy process
|
||||
|
||||
```bash
|
||||
$ sudo gdb
|
||||
attach <PID>
|
||||
set logging on
|
||||
thread apply all bt
|
||||
```
|
||||
They are deployed with the `ci/testnet-manager.sh` script through a list of [scheduled
|
||||
buildkite jobs](https://buildkite.com/solana-labs/testnet-management/settings/schedules).
|
||||
Each testnet can be manually manipulated from buildkite as well.
|
||||
|
||||
## How do I reset the testnet?
|
||||
Manually trigger the [testnet-management](https://buildkite.com/solana-labs/testnet-management) pipeline
|
||||
and when prompted select the desired testnet
|
||||
|
||||
## How can I scale the tx generation rate?
|
||||
|
||||
Increase the TX rate by increasing the number of cores on the client machine which is running
|
||||
`bench-tps` or run multiple clients. Decrease by lowering cores or using the rayon env
|
||||
variable `RAYON_NUM_THREADS=<xx>`
|
||||
|
||||
## How can I test a change on the testnet?
|
||||
|
||||
Currently, a merged PR is the only way to test a change on the testnet. But you
|
||||
can run your own testnet using the scripts in the `net/` directory.
|
||||
|
||||
## Adjusting the number of clients or validators on the testnet
|
||||
Edit `ci/testnet-manager.sh`
|
||||
|
||||
|
||||
## Metrics Server Maintenance
|
||||
Sometimes the dashboard becomes unresponsive. This happens due to glitch in the metrics server.
|
||||
The current solution is to reset the metrics server. Use the following steps.
|
||||
|
||||
1. The server is hosted in a GCP VM instance. Check if the VM instance is down by trying to SSH
|
||||
into it from the GCP console. The name of the VM is ```metrics-solana-com```.
|
||||
2. If the VM is inaccessible, reset it from the GCP console.
|
||||
3. Once VM is up (or, was already up), the metrics services can be restarted from build automation.
|
||||
1. Navigate to https://buildkite.com/solana-labs/metrics-dot-solana-dot-com in your web browser
|
||||
2. Click on ```New Build```
|
||||
3. This will show a pop up dialog. Click on ```options``` drop down.
|
||||
4. Type in ```FORCE_START=true``` in ```Environment Variables``` text box.
|
||||
5. Click ```Create Build```
|
||||
6. This will restart the metrics services, and the dashboards should be accessible afterwards.
|
||||
|
||||
## Debugging Testnet
|
||||
Testnet may exhibit different symptoms of failures. Primary statistics to check are
|
||||
1. Rise in Confirmation Time
|
||||
2. Nodes are not voting
|
||||
3. Panics, and OOM notifications
|
||||
|
||||
Check the following if there are any signs of failure.
|
||||
1. Did testnet deployment fail?
|
||||
1. View buildkite logs for the last deployment: https://buildkite.com/solana-labs/testnet-management
|
||||
2. Use the relevant branch
|
||||
3. If the deployment failed, look at the build logs. The build artifacts for each remote node is uploaded.
|
||||
It's a good first step to triage from these logs.
|
||||
2. You may have to log into remote node if the deployment succeeded, but something failed during runtime.
|
||||
1. Get the private key for the testnet deployment from ```metrics-solana-com``` GCP instance.
|
||||
2. SSH into ```metrics-solana-com``` using GCP console and do the following.
|
||||
```bash
|
||||
sudo bash
|
||||
cd ~buildkite-agent/.ssh
|
||||
ls
|
||||
```
|
||||
3. Copy the relevant private key to your local machine
|
||||
4. Find the public IP address of the AWS instance for the remote node using AWS console
|
||||
5. ```ssh -i <private key file> ubuntu@<ip address of remote node>```
|
||||
6. The logs are in ```~solana\solana``` folder
|
||||
|
||||
This will dump all the threads stack traces into gdb.txt
|
||||
|
||||
Benchmarking
|
||||
---
|
||||
|
||||
First install the nightly build of rustc. `cargo bench` requires unstable features:
|
||||
First install the nightly build of rustc. `cargo bench` requires use of the
|
||||
unstable features only available in the nightly build.
|
||||
|
||||
```bash
|
||||
$ rustup install nightly
|
||||
@ -349,33 +208,24 @@ $ rustup install nightly
|
||||
Run the benchmarks:
|
||||
|
||||
```bash
|
||||
$ cargo +nightly bench --features="unstable"
|
||||
$ cargo +nightly bench
|
||||
```
|
||||
|
||||
Release Process
|
||||
---
|
||||
The release process for this project is described [here](rfcs/rfc-005-branches-tags-and-channels.md).
|
||||
The release process for this project is described [here](RELEASE.md).
|
||||
|
||||
|
||||
Code coverage
|
||||
---
|
||||
|
||||
To generate code coverage statistics, install cargo-cov. Note: the tool currently only works
|
||||
in Rust nightly.
|
||||
To generate code coverage statistics:
|
||||
|
||||
```bash
|
||||
$ cargo +nightly install cargo-cov
|
||||
$ scripts/coverage.sh
|
||||
$ open target/cov/lcov-local/index.html
|
||||
```
|
||||
|
||||
Run cargo-cov and generate a report:
|
||||
|
||||
```bash
|
||||
$ cargo +nightly cov test
|
||||
$ cargo +nightly cov report --open
|
||||
```
|
||||
|
||||
The coverage report will be written to `./target/cov/report/index.html`
|
||||
|
||||
|
||||
Why coverage? While most see coverage as a code quality metric, we see it primarily as a developer
|
||||
productivity metric. When a developer makes a change to the codebase, presumably it's a *solution* to
|
||||
|
195
RELEASE.md
195
RELEASE.md
@ -1,32 +1,185 @@
|
||||
# Solana Release process
|
||||
|
||||
## Introduction
|
||||
## Branches and Tags
|
||||
|
||||
Solana uses a channel-oriented, date-based branching process described [here](https://github.com/solana-labs/solana/blob/master/rfcs/rfc-005-branches-tags-and-channels.md).
|
||||
```
|
||||
========================= master branch (edge channel) =======================>
|
||||
\ \ \
|
||||
\___v0.7.0 tag \ \
|
||||
\ \ v0.9.0 tag__\
|
||||
\ v0.8.0 tag__\ \
|
||||
v0.7.1 tag__\ \ v0.9 branch (beta channel)
|
||||
\___v0.7.2 tag \___v0.8.1 tag
|
||||
\ \
|
||||
\ \
|
||||
v0.7 branch v0.8 branch (stable channel)
|
||||
|
||||
## Release Steps
|
||||
```
|
||||
|
||||
### Changing channels
|
||||
### master branch
|
||||
All new development occurs on the `master` branch.
|
||||
|
||||
When cutting a new channel branch these pre-steps are required:
|
||||
Bug fixes that affect a `vX.Y` branch are first made on `master`. This is to
|
||||
allow a fix some soak time on `master` before it is applied to one or more
|
||||
stabilization branches.
|
||||
|
||||
1. Pick your branch point for release on master.
|
||||
2. Create the branch. The name should be "v" + the first 2 "version" fields from Cargo.toml. For example, a Cargo.toml with version = "0.9.0" implies the next branch name is "v0.9".
|
||||
3. Update Cargo.toml to the next semantic version (e.g. 0.9.0 -> 0.10.0).
|
||||
4. Push your new branch to solana.git
|
||||
5. Land your Carto.toml change as a master PR.
|
||||
Merging to `master` first also helps ensure that fixes applied to one release
|
||||
are present for future releases. (Sometimes the joy of landing a critical
|
||||
release blocker in a branch causes you to forget to propagate back to
|
||||
`master`!)"
|
||||
|
||||
At this point, ci/channel-info.sh should show your freshly cut release branch as "BETA_CHANNEL" and the previous release branch as "STABLE_CHANNEL".
|
||||
Once the bug fix lands on `master` it is cherry-picked into the `vX.Y` branch
|
||||
and potentially the `vX.Y-1` branch. The exception to this rule is when a bug
|
||||
fix for `vX.Y` doesn't apply to `master` or `vX.Y-1`.
|
||||
|
||||
### Updating channels (i.e. "making a release")
|
||||
Immediately after a new stabilization branch is forged, the `Cargo.toml` minor
|
||||
version (*Y*) in the `master` branch is incremented by the release engineer.
|
||||
Incrementing the major version of the `master` branch is outside the scope of
|
||||
this document.
|
||||
|
||||
We use [github's Releases UI](https://github.com/solana-labs/solana/releases) for tagging a release.
|
||||
### v*X.Y* stabilization branches
|
||||
These are stabilization branches for a given milestone. They are created off
|
||||
the `master` branch as late as possible prior to the milestone release.
|
||||
|
||||
1. Go [there ;)](https://github.com/solana-labs/solana/releases).
|
||||
2. Click "Draft new release".
|
||||
3. If the first major release on the branch (e.g. v0.8.0), paste in [this template](https://raw.githubusercontent.com/solana-labs/solana/master/.github/RELEASE_TEMPLATE.md) and fill it in.
|
||||
4. Test the release by generating a tag using semver's rules. First try at a release should be <branchname>.X-rc.0.
|
||||
5. Verify release automation:
|
||||
1. [Crates.io](https://crates.io/crates/solana) should have an updated Solana version.
|
||||
2. ...
|
||||
6. After testnet deployment, verify that testnets are running correct software. http://metrics.solana.com should show testnet running on a hash from your newly created branch.
|
||||
### v*X.Y.Z* release tag
|
||||
The release tags are created as desired by the owner of the given stabilization
|
||||
branch, and cause that *X.Y.Z* release to be shipped to https://crates.io
|
||||
|
||||
Immediately after a new v*X.Y.Z* branch tag has been created, the `Cargo.toml`
|
||||
patch version number (*Z*) of the stabilization branch is incremented by the
|
||||
release engineer.
|
||||
|
||||
## Channels
|
||||
Channels are used by end-users (humans and bots) to consume the branches
|
||||
described in the previous section, so they may automatically update to the most
|
||||
recent version matching their desired stability.
|
||||
|
||||
There are three release channels that map to branches as follows:
|
||||
* edge - tracks the `master` branch, least stable.
|
||||
* beta - tracks the largest (and latest) `vX.Y` stabilization branch, more stable.
|
||||
* stable - tracks the second largest `vX.Y` stabilization branch, most stable.
|
||||
|
||||
## Steps to Create a Branch
|
||||
|
||||
### Create the new branch
|
||||
1. Check out the latest commit on `master` branch:
|
||||
```
|
||||
git fetch --all
|
||||
git checkout upstream/master
|
||||
```
|
||||
1. Determine the new branch name. The name should be "v" + the first 2 version fields
|
||||
from Cargo.toml. For example, a Cargo.toml with version = "0.9.0" implies
|
||||
the next branch name is "v0.9".
|
||||
1. Create the new branch and push this branch to the `solana` repository:
|
||||
```
|
||||
git checkout -b <branchname>
|
||||
git push -u origin <branchname>
|
||||
```
|
||||
|
||||
### Update master branch with the next version
|
||||
|
||||
1. After the new branch has been created and pushed, update the Cargo.toml files on **master** to the next semantic version (e.g. 0.9.0 -> 0.10.0) with:
|
||||
```
|
||||
scripts/increment-cargo-version.sh minor
|
||||
```
|
||||
1. Rebuild to get an updated version of `Cargo.lock`:
|
||||
```
|
||||
cargo build
|
||||
```
|
||||
1. Push all the changed Cargo.toml and Cargo.lock files to the `master` branch with something like:
|
||||
```
|
||||
git co -b version_update
|
||||
git ls-files -m | xargs git add
|
||||
git commit -m 'Update Cargo.toml versions from X.Y to X.Y+1'
|
||||
git push -u origin version_update
|
||||
```
|
||||
1. Confirm that your freshly cut release branch is shown as `BETA_CHANNEL` and the previous release branch as `STABLE_CHANNEL`:
|
||||
```
|
||||
ci/channel_info.sh
|
||||
```
|
||||
|
||||
## Steps to Create a Release
|
||||
|
||||
### Create the Release Tag on GitHub
|
||||
|
||||
1. Go to [GitHub's Releases UI](https://github.com/solana-labs/solana/releases) for tagging a release.
|
||||
1. Click "Draft new release". The release tag must exactly match the `version`
|
||||
field in `/Cargo.toml` prefixed by `v`.
|
||||
1. If the Cargo.toml verion 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.
|
||||
1. Click "Save Draft", then confirm the release notes look good and the tag name and branch are correct. Go back into edit the release and click "Publish release" when ready.
|
||||
|
||||
### Update release branch with the next patch version
|
||||
|
||||
1. After the new release has been tagged, update the Cargo.toml files on **release branch** to the next semantic version (e.g. 0.9.0 -> 0.9.1) with:
|
||||
```
|
||||
scripts/increment-cargo-version.sh patch
|
||||
```
|
||||
1. Rebuild to get an updated version of `Cargo.lock`:
|
||||
```
|
||||
cargo build
|
||||
```
|
||||
1. Push all the changed Cargo.toml and Cargo.lock files to the **release branch** with something like:
|
||||
```
|
||||
git co -b version_update
|
||||
git ls-files -m | xargs git add
|
||||
git commit -m 'Update Cargo.toml versions from X.Y.Z to X.Y.Z+1'
|
||||
git push -u origin version_update
|
||||
```
|
||||
|
||||
### Verify release automation success
|
||||
1. Go to [Solana Releases](https://github.com/solana-labs/solana/releases) and click on the latest release that you just published. Verify that all of the build artifacts are present. This can take up to 90 minutes after creating the tag.
|
||||
1. The `solana-secondary` Buildkite pipeline handles creating the binary tarballs and updated crates. Look for a job under the tag name of the release: https://buildkite.com/solana-labs/solana-secondary
|
||||
1. [Crates.io](https://crates.io/crates/solana) should have an updated Solana version.
|
||||
|
||||
### Update documentation
|
||||
TODO: Documentation update procedure is WIP as we move to gitbook
|
||||
|
||||
Document the new recommended version by updating `docs/src/running-archiver.md` and `docs/src/validator-testnet.md` on the release (beta) branch to point at the `solana-install` for the upcoming release version.
|
||||
|
||||
### Update software on devnet.solana.com
|
||||
|
||||
The testnet running on devnet.solana.com is set to use a fixed release tag
|
||||
which is set in the Buildkite testnet-management pipeline.
|
||||
This tag needs to be updated and the testnet restarted after a new release
|
||||
tag is created.
|
||||
|
||||
#### Update testnet schedules
|
||||
|
||||
Go to https://buildkite.com/solana-labs and click through: Pipelines ->
|
||||
testnet-management -> Pipeline Settings -> Schedules
|
||||
Or just click here:
|
||||
https://buildkite.com/solana-labs/testnet-management/settings/schedules
|
||||
|
||||
There are two scheduled jobs for testnet: a daily restart and an hourly sanity-or-restart. \
|
||||
https://buildkite.com/solana-labs/testnet-management/settings/schedules/0efd7856-7143-4713-8817-47e6bdb05387
|
||||
https://buildkite.com/solana-labs/testnet-management/settings/schedules/2a926646-d972-42b5-aeb9-bb6759592a53
|
||||
|
||||
On each schedule:
|
||||
1. Set TESTNET_TAG environment variable to the desired release tag.
|
||||
1. Example, TESTNET_TAG=v0.13.2
|
||||
1. Set the Build Branch to the branch that TESTNET_TAG is from.
|
||||
1. Example: v0.13
|
||||
|
||||
#### Restart the testnet
|
||||
|
||||
Trigger a TESTNET_OP=create-and-start to refresh the cluster with the new version
|
||||
|
||||
1. Go to https://buildkite.com/solana-labs/testnet-management
|
||||
2. Click "New Build" and use the following settings, then click "Create Build"
|
||||
1. Commit: HEAD
|
||||
1. Branch: [channel branch as set in the schedules]
|
||||
1. Environment Variables:
|
||||
```
|
||||
TESTNET=testnet
|
||||
TESTNET_TAG=[same value as used in TESTNET_TAG in the schedules]
|
||||
TESTNET_OP=create-and-start
|
||||
```
|
||||
|
||||
### Alert the community
|
||||
|
||||
Notify Discord users on #validator-support that a new release for
|
||||
devnet.solana.com is available
|
||||
|
39
archiver-lib/Cargo.toml
Normal file
39
archiver-lib/Cargo.toml
Normal file
@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "solana-archiver-lib"
|
||||
version = "1.0.4"
|
||||
description = "Solana Archiver Library"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.2.1"
|
||||
crossbeam-channel = "0.3"
|
||||
ed25519-dalek = "=1.0.0-pre.1"
|
||||
log = "0.4.8"
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
solana-client = { path = "../client", version = "1.0.4" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.4" }
|
||||
thiserror = "1.0"
|
||||
serde = "1.0.104"
|
||||
serde_json = "1.0.46"
|
||||
serde_derive = "1.0.103"
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.4" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.4" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.4" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.4" }
|
||||
solana-logger = { path = "../logger", version = "1.0.4" }
|
||||
solana-perf = { path = "../perf", version = "1.0.4" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.4" }
|
||||
solana-core = { path = "../core", version = "1.0.4" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.4" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.4" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.0"
|
||||
|
||||
[lib]
|
||||
name = "solana_archiver_lib"
|
936
archiver-lib/src/archiver.rs
Normal file
936
archiver-lib/src/archiver.rs
Normal file
@ -0,0 +1,936 @@
|
||||
use crate::result::ArchiverError;
|
||||
use crossbeam_channel::unbounded;
|
||||
use rand::{thread_rng, Rng, SeedableRng};
|
||||
use rand_chacha::ChaChaRng;
|
||||
use solana_archiver_utils::sample_file;
|
||||
use solana_chacha::chacha::{chacha_cbc_encrypt_ledger, CHACHA_BLOCK_SIZE};
|
||||
use solana_client::{
|
||||
rpc_client::RpcClient, rpc_request::RpcRequest, rpc_response::RpcStorageTurn,
|
||||
thin_client::ThinClient,
|
||||
};
|
||||
use solana_core::{
|
||||
cluster_info::{ClusterInfo, Node, VALIDATOR_PORT_RANGE},
|
||||
contact_info::ContactInfo,
|
||||
gossip_service::GossipService,
|
||||
packet::{limited_deserialize, PACKET_DATA_SIZE},
|
||||
repair_service,
|
||||
repair_service::{RepairService, RepairSlotRange, RepairStrategy},
|
||||
serve_repair::ServeRepair,
|
||||
shred_fetch_stage::ShredFetchStage,
|
||||
sigverify_stage::{DisabledSigVerifier, SigVerifyStage},
|
||||
storage_stage::NUM_STORAGE_SAMPLES,
|
||||
streamer::{receiver, responder, PacketReceiver},
|
||||
window_service::WindowService,
|
||||
};
|
||||
use solana_ledger::{
|
||||
blockstore::Blockstore, leader_schedule_cache::LeaderScheduleCache, shred::Shred,
|
||||
};
|
||||
use solana_net_utils::bind_in_range;
|
||||
use solana_perf::packet::Packets;
|
||||
use solana_perf::recycler::Recycler;
|
||||
use solana_sdk::packet::Packet;
|
||||
use solana_sdk::{
|
||||
account_utils::StateMut,
|
||||
client::{AsyncClient, SyncClient},
|
||||
clock::{get_complete_segment_from_slot, get_segment_from_slot, Slot},
|
||||
commitment_config::CommitmentConfig,
|
||||
hash::Hash,
|
||||
message::Message,
|
||||
signature::{Keypair, Signature, Signer},
|
||||
timing::timestamp,
|
||||
transaction::Transaction,
|
||||
transport::TransportError,
|
||||
};
|
||||
use solana_storage_program::{
|
||||
storage_contract::StorageContract,
|
||||
storage_instruction::{self, StorageAccountType},
|
||||
};
|
||||
use std::{
|
||||
io::{self, ErrorKind},
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
||||
path::{Path, PathBuf},
|
||||
result,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
sync::mpsc::{channel, Receiver, Sender},
|
||||
sync::{Arc, RwLock},
|
||||
thread::{sleep, spawn, JoinHandle},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, ArchiverError>;
|
||||
|
||||
static ENCRYPTED_FILENAME: &str = "ledger.enc";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum ArchiverRequest {
|
||||
GetSlotHeight(SocketAddr),
|
||||
}
|
||||
|
||||
pub struct Archiver {
|
||||
thread_handles: Vec<JoinHandle<()>>,
|
||||
exit: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
// Shared Archiver Meta struct used internally
|
||||
#[derive(Default)]
|
||||
struct ArchiverMeta {
|
||||
slot: Slot,
|
||||
slots_per_segment: u64,
|
||||
ledger_path: PathBuf,
|
||||
signature: Signature,
|
||||
ledger_data_file_encrypted: PathBuf,
|
||||
sampling_offsets: Vec<u64>,
|
||||
blockhash: Hash,
|
||||
sha_state: Hash,
|
||||
num_chacha_blocks: usize,
|
||||
client_commitment: CommitmentConfig,
|
||||
}
|
||||
|
||||
fn get_slot_from_signature(
|
||||
signature: &Signature,
|
||||
storage_turn: u64,
|
||||
slots_per_segment: u64,
|
||||
) -> u64 {
|
||||
let signature_vec = signature.as_ref();
|
||||
let mut segment_index = u64::from(signature_vec[0])
|
||||
| (u64::from(signature_vec[1]) << 8)
|
||||
| (u64::from(signature_vec[1]) << 16)
|
||||
| (u64::from(signature_vec[2]) << 24);
|
||||
let max_segment_index =
|
||||
get_complete_segment_from_slot(storage_turn, slots_per_segment).unwrap();
|
||||
segment_index %= max_segment_index as u64;
|
||||
segment_index * slots_per_segment
|
||||
}
|
||||
|
||||
fn create_request_processor(
|
||||
socket: UdpSocket,
|
||||
exit: &Arc<AtomicBool>,
|
||||
slot_receiver: Receiver<u64>,
|
||||
) -> Vec<JoinHandle<()>> {
|
||||
let mut thread_handles = vec![];
|
||||
let (s_reader, r_reader) = channel();
|
||||
let (s_responder, r_responder) = channel();
|
||||
let storage_socket = Arc::new(socket);
|
||||
let recycler = Recycler::default();
|
||||
let t_receiver = receiver(storage_socket.clone(), exit, s_reader, recycler, "archiver");
|
||||
thread_handles.push(t_receiver);
|
||||
|
||||
let t_responder = responder("archiver-responder", storage_socket, r_responder);
|
||||
thread_handles.push(t_responder);
|
||||
|
||||
let exit = exit.clone();
|
||||
let t_processor = spawn(move || {
|
||||
let slot = poll_for_slot(slot_receiver, &exit);
|
||||
|
||||
loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
|
||||
let packets = r_reader.recv_timeout(Duration::from_secs(1));
|
||||
|
||||
if let Ok(packets) = packets {
|
||||
for packet in &packets.packets {
|
||||
let req: result::Result<ArchiverRequest, Box<bincode::ErrorKind>> =
|
||||
limited_deserialize(&packet.data[..packet.meta.size]);
|
||||
match req {
|
||||
Ok(ArchiverRequest::GetSlotHeight(from)) => {
|
||||
let packet = Packet::from_data(&from, slot);
|
||||
let _ = s_responder.send(Packets::new(vec![packet]));
|
||||
}
|
||||
Err(e) => {
|
||||
info!("invalid request: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
thread_handles.push(t_processor);
|
||||
thread_handles
|
||||
}
|
||||
|
||||
fn poll_for_slot(receiver: Receiver<u64>, exit: &Arc<AtomicBool>) -> u64 {
|
||||
loop {
|
||||
let slot = receiver.recv_timeout(Duration::from_secs(1));
|
||||
if let Ok(slot) = slot {
|
||||
return slot;
|
||||
}
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Archiver {
|
||||
/// Returns a Result that contains an archiver on success
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ledger_path` - path to where the ledger will be stored.
|
||||
/// Causes panic if none
|
||||
/// * `node` - The archiver node
|
||||
/// * `cluster_entrypoint` - ContactInfo representing an entry into the network
|
||||
/// * `keypair` - Keypair for this archiver
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(
|
||||
ledger_path: &Path,
|
||||
node: Node,
|
||||
cluster_entrypoint: ContactInfo,
|
||||
keypair: Arc<Keypair>,
|
||||
storage_keypair: Arc<Keypair>,
|
||||
client_commitment: CommitmentConfig,
|
||||
) -> Result<Self> {
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
|
||||
info!("Archiver: id: {}", keypair.pubkey());
|
||||
info!("Creating cluster info....");
|
||||
let mut cluster_info = ClusterInfo::new(node.info.clone(), keypair.clone());
|
||||
cluster_info.set_entrypoint(cluster_entrypoint.clone());
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
|
||||
// Note for now, this ledger will not contain any of the existing entries
|
||||
// in the ledger located at ledger_path, and will only append on newly received
|
||||
// entries after being passed to window_service
|
||||
let blockstore = Arc::new(
|
||||
Blockstore::open(ledger_path).expect("Expected to be able to open database ledger"),
|
||||
);
|
||||
|
||||
let gossip_service = GossipService::new(&cluster_info, None, node.sockets.gossip, &exit);
|
||||
|
||||
info!("Connecting to the cluster via {:?}", cluster_entrypoint);
|
||||
let (nodes, _) =
|
||||
match solana_core::gossip_service::discover_cluster(&cluster_entrypoint.gossip, 1) {
|
||||
Ok(nodes_and_archivers) => nodes_and_archivers,
|
||||
Err(e) => {
|
||||
//shutdown services before exiting
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
gossip_service.join()?;
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
let client = solana_core::gossip_service::get_client(&nodes);
|
||||
|
||||
info!("Setting up mining account...");
|
||||
if let Err(e) =
|
||||
Self::setup_mining_account(&client, &keypair, &storage_keypair, client_commitment)
|
||||
{
|
||||
//shutdown services before exiting
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
gossip_service.join()?;
|
||||
return Err(e);
|
||||
};
|
||||
|
||||
let repair_socket = Arc::new(node.sockets.repair);
|
||||
let shred_sockets: Vec<Arc<UdpSocket>> =
|
||||
node.sockets.tvu.into_iter().map(Arc::new).collect();
|
||||
let shred_forward_sockets: Vec<Arc<UdpSocket>> = node
|
||||
.sockets
|
||||
.tvu_forwards
|
||||
.into_iter()
|
||||
.map(Arc::new)
|
||||
.collect();
|
||||
let (shred_fetch_sender, shred_fetch_receiver) = channel();
|
||||
let fetch_stage = ShredFetchStage::new(
|
||||
shred_sockets,
|
||||
shred_forward_sockets,
|
||||
repair_socket.clone(),
|
||||
&shred_fetch_sender,
|
||||
&exit,
|
||||
);
|
||||
let (slot_sender, slot_receiver) = channel();
|
||||
let request_processor =
|
||||
create_request_processor(node.sockets.storage.unwrap(), &exit, slot_receiver);
|
||||
|
||||
let t_archiver = {
|
||||
let exit = exit.clone();
|
||||
let node_info = node.info.clone();
|
||||
let mut meta = ArchiverMeta {
|
||||
ledger_path: ledger_path.to_path_buf(),
|
||||
client_commitment,
|
||||
..ArchiverMeta::default()
|
||||
};
|
||||
spawn(move || {
|
||||
// setup archiver
|
||||
let window_service = match Self::setup(
|
||||
&mut meta,
|
||||
cluster_info.clone(),
|
||||
&blockstore,
|
||||
&exit,
|
||||
&node_info,
|
||||
&storage_keypair,
|
||||
repair_socket,
|
||||
shred_fetch_receiver,
|
||||
slot_sender,
|
||||
) {
|
||||
Ok(window_service) => window_service,
|
||||
Err(e) => {
|
||||
//shutdown services before exiting
|
||||
error!("setup failed {:?}; archiver thread exiting...", e);
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
request_processor
|
||||
.into_iter()
|
||||
.for_each(|t| t.join().unwrap());
|
||||
fetch_stage.join().unwrap();
|
||||
gossip_service.join().unwrap();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
info!("setup complete");
|
||||
// run archiver
|
||||
Self::run(
|
||||
&mut meta,
|
||||
&blockstore,
|
||||
cluster_info,
|
||||
&keypair,
|
||||
&storage_keypair,
|
||||
&exit,
|
||||
);
|
||||
// wait until exit
|
||||
request_processor
|
||||
.into_iter()
|
||||
.for_each(|t| t.join().unwrap());
|
||||
fetch_stage.join().unwrap();
|
||||
gossip_service.join().unwrap();
|
||||
window_service.join().unwrap()
|
||||
})
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
thread_handles: vec![t_archiver],
|
||||
exit,
|
||||
})
|
||||
}
|
||||
|
||||
fn run(
|
||||
meta: &mut ArchiverMeta,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
archiver_keypair: &Arc<Keypair>,
|
||||
storage_keypair: &Arc<Keypair>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
) {
|
||||
// encrypt segment
|
||||
Self::encrypt_ledger(meta, blockstore).expect("ledger encrypt not successful");
|
||||
let enc_file_path = meta.ledger_data_file_encrypted.clone();
|
||||
// do replicate
|
||||
loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO check if more segments are available - based on space constraints
|
||||
Self::create_sampling_offsets(meta);
|
||||
let sampling_offsets = &meta.sampling_offsets;
|
||||
meta.sha_state =
|
||||
match Self::sample_file_to_create_mining_hash(&enc_file_path, sampling_offsets) {
|
||||
Ok(hash) => hash,
|
||||
Err(err) => {
|
||||
warn!("Error sampling file, exiting: {:?}", err);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Self::submit_mining_proof(meta, &cluster_info, archiver_keypair, storage_keypair);
|
||||
|
||||
// TODO make this a lot more frequent by picking a "new" blockhash instead of picking a storage blockhash
|
||||
// prep the next proof
|
||||
let (storage_blockhash, _) = match Self::poll_for_blockhash_and_slot(
|
||||
&cluster_info,
|
||||
meta.slots_per_segment,
|
||||
&meta.blockhash,
|
||||
exit,
|
||||
) {
|
||||
Ok(blockhash_and_slot) => blockhash_and_slot,
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Error couldn't get a newer blockhash than {:?}. {:?}",
|
||||
meta.blockhash, e
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
meta.blockhash = storage_blockhash;
|
||||
Self::redeem_rewards(
|
||||
&cluster_info,
|
||||
archiver_keypair,
|
||||
storage_keypair,
|
||||
meta.client_commitment,
|
||||
);
|
||||
}
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn redeem_rewards(
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
archiver_keypair: &Arc<Keypair>,
|
||||
storage_keypair: &Arc<Keypair>,
|
||||
client_commitment: CommitmentConfig,
|
||||
) {
|
||||
let nodes = cluster_info.read().unwrap().tvu_peers();
|
||||
let client = solana_core::gossip_service::get_client(&nodes);
|
||||
|
||||
if let Ok(Some(account)) =
|
||||
client.get_account_with_commitment(&storage_keypair.pubkey(), client_commitment)
|
||||
{
|
||||
if let Ok(StorageContract::ArchiverStorage { validations, .. }) = account.state() {
|
||||
if !validations.is_empty() {
|
||||
let ix = storage_instruction::claim_reward(
|
||||
&archiver_keypair.pubkey(),
|
||||
&storage_keypair.pubkey(),
|
||||
);
|
||||
let message =
|
||||
Message::new_with_payer(vec![ix], Some(&archiver_keypair.pubkey()));
|
||||
if let Err(e) = client.send_message(&[archiver_keypair.as_ref()], message) {
|
||||
error!("unable to redeem reward, tx failed: {:?}", e);
|
||||
} else {
|
||||
info!(
|
||||
"collected mining rewards: Account balance {:?}",
|
||||
client.get_balance_with_commitment(
|
||||
&archiver_keypair.pubkey(),
|
||||
client_commitment
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("Redeem mining reward: No account data found");
|
||||
}
|
||||
}
|
||||
|
||||
// Find a segment to replicate and download it.
|
||||
fn setup(
|
||||
meta: &mut ArchiverMeta,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
node_info: &ContactInfo,
|
||||
storage_keypair: &Arc<Keypair>,
|
||||
repair_socket: Arc<UdpSocket>,
|
||||
shred_fetch_receiver: PacketReceiver,
|
||||
slot_sender: Sender<u64>,
|
||||
) -> Result<WindowService> {
|
||||
let slots_per_segment =
|
||||
match Self::get_segment_config(&cluster_info, meta.client_commitment) {
|
||||
Ok(slots_per_segment) => slots_per_segment,
|
||||
Err(e) => {
|
||||
error!("unable to get segment size configuration, exiting...");
|
||||
//shutdown services before exiting
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let (segment_blockhash, segment_slot) = match Self::poll_for_segment(
|
||||
&cluster_info,
|
||||
slots_per_segment,
|
||||
&Hash::default(),
|
||||
exit,
|
||||
) {
|
||||
Ok(blockhash_and_slot) => blockhash_and_slot,
|
||||
Err(e) => {
|
||||
//shutdown services before exiting
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let signature = storage_keypair.sign_message(segment_blockhash.as_ref());
|
||||
let slot = get_slot_from_signature(&signature, segment_slot, slots_per_segment);
|
||||
info!("replicating slot: {}", slot);
|
||||
slot_sender.send(slot)?;
|
||||
meta.slot = slot;
|
||||
meta.slots_per_segment = slots_per_segment;
|
||||
meta.signature = signature;
|
||||
meta.blockhash = segment_blockhash;
|
||||
|
||||
let mut repair_slot_range = RepairSlotRange::default();
|
||||
repair_slot_range.end = slot + slots_per_segment;
|
||||
repair_slot_range.start = slot;
|
||||
|
||||
let (retransmit_sender, _) = channel();
|
||||
|
||||
let (verified_sender, verified_receiver) = unbounded();
|
||||
|
||||
let _sigverify_stage = SigVerifyStage::new(
|
||||
shred_fetch_receiver,
|
||||
verified_sender,
|
||||
DisabledSigVerifier::default(),
|
||||
);
|
||||
|
||||
let window_service = WindowService::new(
|
||||
blockstore.clone(),
|
||||
cluster_info.clone(),
|
||||
verified_receiver,
|
||||
retransmit_sender,
|
||||
repair_socket,
|
||||
&exit,
|
||||
RepairStrategy::RepairRange(repair_slot_range),
|
||||
&Arc::new(LeaderScheduleCache::default()),
|
||||
|_, _, _, _| true,
|
||||
);
|
||||
info!("waiting for ledger download");
|
||||
Self::wait_for_segment_download(
|
||||
slot,
|
||||
slots_per_segment,
|
||||
&blockstore,
|
||||
&exit,
|
||||
&node_info,
|
||||
cluster_info,
|
||||
);
|
||||
Ok(window_service)
|
||||
}
|
||||
|
||||
fn wait_for_segment_download(
|
||||
start_slot: Slot,
|
||||
slots_per_segment: u64,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
node_info: &ContactInfo,
|
||||
cluster_info: Arc<RwLock<ClusterInfo>>,
|
||||
) {
|
||||
info!(
|
||||
"window created, waiting for ledger download starting at slot {:?}",
|
||||
start_slot
|
||||
);
|
||||
let mut current_slot = start_slot;
|
||||
'outer: loop {
|
||||
while blockstore.is_full(current_slot) {
|
||||
current_slot += 1;
|
||||
info!("current slot: {}", current_slot);
|
||||
if current_slot >= start_slot + slots_per_segment {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_secs(1));
|
||||
}
|
||||
|
||||
info!("Done receiving entries from window_service");
|
||||
|
||||
// Remove archiver from the data plane
|
||||
let mut contact_info = node_info.clone();
|
||||
contact_info.tvu = "0.0.0.0:0".parse().unwrap();
|
||||
contact_info.wallclock = timestamp();
|
||||
// copy over the adopted shred_version from the entrypoint
|
||||
contact_info.shred_version = cluster_info.read().unwrap().my_data().shred_version;
|
||||
{
|
||||
let mut cluster_info_w = cluster_info.write().unwrap();
|
||||
cluster_info_w.insert_self(contact_info);
|
||||
}
|
||||
}
|
||||
|
||||
fn encrypt_ledger(meta: &mut ArchiverMeta, blockstore: &Arc<Blockstore>) -> Result<()> {
|
||||
meta.ledger_data_file_encrypted = meta.ledger_path.join(ENCRYPTED_FILENAME);
|
||||
|
||||
{
|
||||
let mut ivec = [0u8; 64];
|
||||
ivec.copy_from_slice(&meta.signature.as_ref());
|
||||
|
||||
let num_encrypted_bytes = chacha_cbc_encrypt_ledger(
|
||||
blockstore,
|
||||
meta.slot,
|
||||
meta.slots_per_segment,
|
||||
&meta.ledger_data_file_encrypted,
|
||||
&mut ivec,
|
||||
)?;
|
||||
|
||||
meta.num_chacha_blocks = num_encrypted_bytes / CHACHA_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
info!(
|
||||
"Done encrypting the ledger: {:?}",
|
||||
meta.ledger_data_file_encrypted
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_sampling_offsets(meta: &mut ArchiverMeta) {
|
||||
meta.sampling_offsets.clear();
|
||||
let mut rng_seed = [0u8; 32];
|
||||
rng_seed.copy_from_slice(&meta.blockhash.as_ref());
|
||||
let mut rng = ChaChaRng::from_seed(rng_seed);
|
||||
for _ in 0..NUM_STORAGE_SAMPLES {
|
||||
meta.sampling_offsets
|
||||
.push(rng.gen_range(0, meta.num_chacha_blocks) as u64);
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_file_to_create_mining_hash(
|
||||
enc_file_path: &Path,
|
||||
sampling_offsets: &[u64],
|
||||
) -> Result<Hash> {
|
||||
let sha_state = sample_file(enc_file_path, sampling_offsets)?;
|
||||
info!("sampled sha_state: {}", sha_state);
|
||||
Ok(sha_state)
|
||||
}
|
||||
|
||||
fn setup_mining_account(
|
||||
client: &ThinClient,
|
||||
keypair: &Keypair,
|
||||
storage_keypair: &Keypair,
|
||||
client_commitment: CommitmentConfig,
|
||||
) -> Result<()> {
|
||||
// make sure archiver has some balance
|
||||
info!("checking archiver keypair...");
|
||||
if client.poll_balance_with_timeout_and_commitment(
|
||||
&keypair.pubkey(),
|
||||
&Duration::from_millis(100),
|
||||
&Duration::from_secs(5),
|
||||
client_commitment,
|
||||
)? == 0
|
||||
{
|
||||
return Err(ArchiverError::EmptyStorageAccountBalance);
|
||||
}
|
||||
|
||||
info!("checking storage account keypair...");
|
||||
// check if the storage account exists
|
||||
let balance =
|
||||
client.poll_get_balance_with_commitment(&storage_keypair.pubkey(), client_commitment);
|
||||
if balance.is_err() || balance.unwrap() == 0 {
|
||||
let blockhash = match client.get_recent_blockhash_with_commitment(client_commitment) {
|
||||
Ok((blockhash, _)) => blockhash,
|
||||
Err(e) => {
|
||||
return Err(ArchiverError::TransportError(e));
|
||||
}
|
||||
};
|
||||
|
||||
let ix = storage_instruction::create_storage_account(
|
||||
&keypair.pubkey(),
|
||||
&keypair.pubkey(),
|
||||
&storage_keypair.pubkey(),
|
||||
1,
|
||||
StorageAccountType::Archiver,
|
||||
);
|
||||
let tx = Transaction::new_signed_instructions(&[keypair], ix, blockhash);
|
||||
let signature = client.async_send_transaction(tx)?;
|
||||
client
|
||||
.poll_for_signature_with_commitment(&signature, client_commitment)
|
||||
.map_err(|err| match err {
|
||||
TransportError::IoError(e) => e,
|
||||
TransportError::TransactionError(_) => io::Error::new(
|
||||
ErrorKind::Other,
|
||||
"setup_mining_account: signature not found",
|
||||
),
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn submit_mining_proof(
|
||||
meta: &ArchiverMeta,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
archiver_keypair: &Arc<Keypair>,
|
||||
storage_keypair: &Arc<Keypair>,
|
||||
) {
|
||||
// No point if we've got no storage account...
|
||||
let nodes = cluster_info.read().unwrap().tvu_peers();
|
||||
let client = solana_core::gossip_service::get_client(&nodes);
|
||||
let storage_balance = client
|
||||
.poll_get_balance_with_commitment(&storage_keypair.pubkey(), meta.client_commitment);
|
||||
if storage_balance.is_err() || storage_balance.unwrap() == 0 {
|
||||
error!("Unable to submit mining proof, no storage account");
|
||||
return;
|
||||
}
|
||||
// ...or no lamports for fees
|
||||
let balance = client
|
||||
.poll_get_balance_with_commitment(&archiver_keypair.pubkey(), meta.client_commitment);
|
||||
if balance.is_err() || balance.unwrap() == 0 {
|
||||
error!("Unable to submit mining proof, insufficient Archiver Account balance");
|
||||
return;
|
||||
}
|
||||
|
||||
let blockhash = match client.get_recent_blockhash_with_commitment(meta.client_commitment) {
|
||||
Ok((blockhash, _)) => blockhash,
|
||||
Err(_) => {
|
||||
error!("unable to get recent blockhash, can't submit proof");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let instruction = storage_instruction::mining_proof(
|
||||
&storage_keypair.pubkey(),
|
||||
meta.sha_state,
|
||||
get_segment_from_slot(meta.slot, meta.slots_per_segment),
|
||||
Signature::new(&meta.signature.as_ref()),
|
||||
meta.blockhash,
|
||||
);
|
||||
let message = Message::new_with_payer(vec![instruction], Some(&archiver_keypair.pubkey()));
|
||||
let mut transaction = Transaction::new(
|
||||
&[archiver_keypair.as_ref(), storage_keypair.as_ref()],
|
||||
message,
|
||||
blockhash,
|
||||
);
|
||||
if let Err(err) = client.send_and_confirm_transaction(
|
||||
&[archiver_keypair.as_ref(), storage_keypair.as_ref()],
|
||||
&mut transaction,
|
||||
10,
|
||||
0,
|
||||
) {
|
||||
error!("Error: {:?}; while sending mining proof", err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(self) {
|
||||
self.exit.store(true, Ordering::Relaxed);
|
||||
self.join()
|
||||
}
|
||||
|
||||
pub fn join(self) {
|
||||
for handle in self.thread_handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_segment_config(
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
client_commitment: CommitmentConfig,
|
||||
) -> Result<u64> {
|
||||
let rpc_peers = {
|
||||
let cluster_info = cluster_info.read().unwrap();
|
||||
cluster_info.all_rpc_peers()
|
||||
};
|
||||
debug!("rpc peers: {:?}", rpc_peers);
|
||||
if !rpc_peers.is_empty() {
|
||||
let rpc_client = {
|
||||
let node_index = thread_rng().gen_range(0, rpc_peers.len());
|
||||
RpcClient::new_socket(rpc_peers[node_index].rpc)
|
||||
};
|
||||
Ok(rpc_client
|
||||
.send(
|
||||
&RpcRequest::GetSlotsPerSegment,
|
||||
serde_json::json!([client_commitment]),
|
||||
0,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("Error while making rpc request {:?}", err);
|
||||
ArchiverError::ClientError(err)
|
||||
})?
|
||||
.as_u64()
|
||||
.unwrap())
|
||||
} else {
|
||||
Err(ArchiverError::NoRpcPeers)
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits until the first segment is ready, and returns the current segment
|
||||
fn poll_for_segment(
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
slots_per_segment: u64,
|
||||
previous_blockhash: &Hash,
|
||||
exit: &Arc<AtomicBool>,
|
||||
) -> Result<(Hash, u64)> {
|
||||
loop {
|
||||
let (blockhash, turn_slot) = Self::poll_for_blockhash_and_slot(
|
||||
cluster_info,
|
||||
slots_per_segment,
|
||||
previous_blockhash,
|
||||
exit,
|
||||
)?;
|
||||
if get_complete_segment_from_slot(turn_slot, slots_per_segment).is_some() {
|
||||
return Ok((blockhash, turn_slot));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Poll for a different blockhash and associated max_slot than `previous_blockhash`
|
||||
fn poll_for_blockhash_and_slot(
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
slots_per_segment: u64,
|
||||
previous_blockhash: &Hash,
|
||||
exit: &Arc<AtomicBool>,
|
||||
) -> Result<(Hash, u64)> {
|
||||
info!("waiting for the next turn...");
|
||||
loop {
|
||||
let rpc_peers = {
|
||||
let cluster_info = cluster_info.read().unwrap();
|
||||
cluster_info.all_rpc_peers()
|
||||
};
|
||||
debug!("rpc peers: {:?}", rpc_peers);
|
||||
if !rpc_peers.is_empty() {
|
||||
let rpc_client = {
|
||||
let node_index = thread_rng().gen_range(0, rpc_peers.len());
|
||||
RpcClient::new_socket(rpc_peers[node_index].rpc)
|
||||
};
|
||||
let response = rpc_client
|
||||
.send(
|
||||
&RpcRequest::GetStorageTurn,
|
||||
serde_json::value::Value::Null,
|
||||
0,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("Error while making rpc request {:?}", err);
|
||||
ArchiverError::ClientError(err)
|
||||
})?;
|
||||
let RpcStorageTurn {
|
||||
blockhash: storage_blockhash,
|
||||
slot: turn_slot,
|
||||
} = serde_json::from_value::<RpcStorageTurn>(response)
|
||||
.map_err(ArchiverError::JsonError)?;
|
||||
let turn_blockhash = storage_blockhash.parse().map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"Blockhash parse failure: {:?} on {:?}",
|
||||
err, storage_blockhash
|
||||
),
|
||||
)
|
||||
})?;
|
||||
if turn_blockhash != *previous_blockhash {
|
||||
info!("turn slot: {}", turn_slot);
|
||||
if get_segment_from_slot(turn_slot, slots_per_segment) != 0 {
|
||||
return Ok((turn_blockhash, turn_slot));
|
||||
}
|
||||
}
|
||||
}
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
return Err(ArchiverError::IO(io::Error::new(
|
||||
ErrorKind::Other,
|
||||
"exit signalled...",
|
||||
)));
|
||||
}
|
||||
sleep(Duration::from_secs(5));
|
||||
}
|
||||
}
|
||||
|
||||
/// Ask an archiver to populate a given blockstore with its segment.
|
||||
/// Return the slot at the start of the archiver's segment
|
||||
///
|
||||
/// It is recommended to use a temporary blockstore for this since the download will not verify
|
||||
/// shreds received and might impact the chaining of shreds across slots
|
||||
pub fn download_from_archiver(
|
||||
serve_repair: &ServeRepair,
|
||||
archiver_info: &ContactInfo,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
slots_per_segment: u64,
|
||||
) -> Result<u64> {
|
||||
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
||||
// Create a client which downloads from the archiver and see that it
|
||||
// can respond with shreds.
|
||||
let start_slot = Self::get_archiver_segment_slot(ip_addr, archiver_info.storage_addr);
|
||||
info!("Archiver download: start at {}", start_slot);
|
||||
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let (s_reader, r_reader) = channel();
|
||||
let repair_socket = Arc::new(bind_in_range(ip_addr, VALIDATOR_PORT_RANGE).unwrap().1);
|
||||
let t_receiver = receiver(
|
||||
repair_socket.clone(),
|
||||
&exit,
|
||||
s_reader,
|
||||
Recycler::default(),
|
||||
"archiver_reeciver",
|
||||
);
|
||||
let id = serve_repair.keypair().pubkey();
|
||||
info!(
|
||||
"Sending repair requests from: {} to: {}",
|
||||
serve_repair.my_info().id,
|
||||
archiver_info.gossip
|
||||
);
|
||||
let repair_slot_range = RepairSlotRange {
|
||||
start: start_slot,
|
||||
end: start_slot + slots_per_segment,
|
||||
};
|
||||
// try for upto 180 seconds //TODO needs tuning if segments are huge
|
||||
for _ in 0..120 {
|
||||
// Strategy used by archivers
|
||||
let repairs = RepairService::generate_repairs_in_range(
|
||||
blockstore,
|
||||
repair_service::MAX_REPAIR_LENGTH,
|
||||
&repair_slot_range,
|
||||
);
|
||||
//iter over the repairs and send them
|
||||
if let Ok(repairs) = repairs {
|
||||
let reqs: Vec<_> = repairs
|
||||
.into_iter()
|
||||
.filter_map(|repair_request| {
|
||||
serve_repair
|
||||
.map_repair_request(&repair_request)
|
||||
.map(|result| ((archiver_info.gossip, result), repair_request))
|
||||
.ok()
|
||||
})
|
||||
.collect();
|
||||
|
||||
for ((to, req), repair_request) in reqs {
|
||||
if let Ok(local_addr) = repair_socket.local_addr() {
|
||||
datapoint_info!(
|
||||
"archiver_download",
|
||||
("repair_request", format!("{:?}", repair_request), String),
|
||||
("to", to.to_string(), String),
|
||||
("from", local_addr.to_string(), String),
|
||||
("id", id.to_string(), String)
|
||||
);
|
||||
}
|
||||
repair_socket
|
||||
.send_to(&req, archiver_info.gossip)
|
||||
.unwrap_or_else(|e| {
|
||||
error!("{} repair req send_to({}) error {:?}", id, to, e);
|
||||
0
|
||||
});
|
||||
}
|
||||
}
|
||||
let res = r_reader.recv_timeout(Duration::new(1, 0));
|
||||
if let Ok(mut packets) = res {
|
||||
while let Ok(mut more) = r_reader.try_recv() {
|
||||
packets.packets.append_pinned(&mut more.packets);
|
||||
}
|
||||
let shreds: Vec<Shred> = packets
|
||||
.packets
|
||||
.into_iter()
|
||||
.filter_map(|p| Shred::new_from_serialized_shred(p.data.to_vec()).ok())
|
||||
.collect();
|
||||
blockstore.insert_shreds(shreds, None, false)?;
|
||||
}
|
||||
// check if all the slots in the segment are complete
|
||||
if Self::segment_complete(start_slot, slots_per_segment, blockstore) {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(500));
|
||||
}
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
t_receiver.join().unwrap();
|
||||
|
||||
// check if all the slots in the segment are complete
|
||||
if !Self::segment_complete(start_slot, slots_per_segment, blockstore) {
|
||||
return Err(ArchiverError::SegmentDownloadError);
|
||||
}
|
||||
Ok(start_slot)
|
||||
}
|
||||
|
||||
fn segment_complete(
|
||||
start_slot: Slot,
|
||||
slots_per_segment: u64,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
) -> bool {
|
||||
for slot in start_slot..(start_slot + slots_per_segment) {
|
||||
if !blockstore.is_full(slot) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn get_archiver_segment_slot(bind_ip_addr: IpAddr, to: SocketAddr) -> u64 {
|
||||
let (_port, socket) = bind_in_range(bind_ip_addr, VALIDATOR_PORT_RANGE).unwrap();
|
||||
socket
|
||||
.set_read_timeout(Some(Duration::from_secs(5)))
|
||||
.unwrap();
|
||||
|
||||
let req = ArchiverRequest::GetSlotHeight(socket.local_addr().unwrap());
|
||||
let serialized_req = bincode::serialize(&req).unwrap();
|
||||
for _ in 0..10 {
|
||||
socket.send_to(&serialized_req, to).unwrap();
|
||||
let mut buf = [0; 1024];
|
||||
if let Ok((size, _addr)) = socket.recv_from(&mut buf) {
|
||||
// Ignore bad packet and try again
|
||||
if let Ok(slot) = bincode::config()
|
||||
.limit(PACKET_DATA_SIZE as u64)
|
||||
.deserialize(&buf[..size])
|
||||
{
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_millis(500));
|
||||
}
|
||||
panic!("Couldn't get segment slot from archiver!");
|
||||
}
|
||||
}
|
11
archiver-lib/src/lib.rs
Normal file
11
archiver-lib/src/lib.rs
Normal file
@ -0,0 +1,11 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
#[macro_use]
|
||||
extern crate solana_metrics;
|
||||
|
||||
pub mod archiver;
|
||||
mod result;
|
48
archiver-lib/src/result.rs
Normal file
48
archiver-lib/src/result.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use serde_json;
|
||||
use solana_client::client_error;
|
||||
use solana_ledger::blockstore;
|
||||
use solana_sdk::transport;
|
||||
use std::any::Any;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ArchiverError {
|
||||
#[error("IO error")]
|
||||
IO(#[from] std::io::Error),
|
||||
|
||||
#[error("blockstore error")]
|
||||
BlockstoreError(#[from] blockstore::BlockstoreError),
|
||||
|
||||
#[error("crossbeam error")]
|
||||
CrossbeamSendError(#[from] crossbeam_channel::SendError<u64>),
|
||||
|
||||
#[error("send error")]
|
||||
SendError(#[from] std::sync::mpsc::SendError<u64>),
|
||||
|
||||
#[error("join error")]
|
||||
JoinError(Box<dyn Any + Send + 'static>),
|
||||
|
||||
#[error("transport error")]
|
||||
TransportError(#[from] transport::TransportError),
|
||||
|
||||
#[error("client error")]
|
||||
ClientError(#[from] client_error::ClientError),
|
||||
|
||||
#[error("Json parsing error")]
|
||||
JsonError(#[from] serde_json::error::Error),
|
||||
|
||||
#[error("Storage account has no balance")]
|
||||
EmptyStorageAccountBalance,
|
||||
|
||||
#[error("No RPC peers..")]
|
||||
NoRpcPeers,
|
||||
|
||||
#[error("Couldn't download full segment")]
|
||||
SegmentDownloadError,
|
||||
}
|
||||
|
||||
impl std::convert::From<Box<dyn Any + Send + 'static>> for ArchiverError {
|
||||
fn from(e: Box<dyn Any + Send + 'static>) -> ArchiverError {
|
||||
ArchiverError::JoinError(e)
|
||||
}
|
||||
}
|
25
archiver-utils/Cargo.toml
Normal file
25
archiver-utils/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "solana-archiver-utils"
|
||||
version = "1.0.4"
|
||||
description = "Solana Archiver Utils"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
rand = "0.6.5"
|
||||
solana-chacha = { path = "../chacha", version = "1.0.4" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.4" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.4" }
|
||||
solana-logger = { path = "../logger", version = "1.0.4" }
|
||||
solana-perf = { path = "../perf", version = "1.0.4" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.4" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.0"
|
||||
|
||||
[lib]
|
||||
name = "solana_archiver_utils"
|
120
archiver-utils/src/lib.rs
Normal file
120
archiver-utils/src/lib.rs
Normal file
@ -0,0 +1,120 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use solana_sdk::hash::{Hash, Hasher};
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufReader, ErrorKind, Read, Seek, SeekFrom};
|
||||
use std::mem::size_of;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn sample_file(in_path: &Path, sample_offsets: &[u64]) -> io::Result<Hash> {
|
||||
let in_file = File::open(in_path)?;
|
||||
let metadata = in_file.metadata()?;
|
||||
let mut buffer_file = BufReader::new(in_file);
|
||||
|
||||
let mut hasher = Hasher::default();
|
||||
let sample_size = size_of::<Hash>();
|
||||
let sample_size64 = sample_size as u64;
|
||||
let mut buf = vec![0; sample_size];
|
||||
|
||||
let file_len = metadata.len();
|
||||
if file_len < sample_size64 {
|
||||
return Err(io::Error::new(ErrorKind::Other, "file too short!"));
|
||||
}
|
||||
for offset in sample_offsets {
|
||||
if *offset > (file_len - sample_size64) / sample_size64 {
|
||||
return Err(io::Error::new(ErrorKind::Other, "offset too large"));
|
||||
}
|
||||
buffer_file.seek(SeekFrom::Start(*offset * sample_size64))?;
|
||||
trace!("sampling @ {} ", *offset);
|
||||
match buffer_file.read(&mut buf) {
|
||||
Ok(size) => {
|
||||
assert_eq!(size, buf.len());
|
||||
hasher.hash(&buf);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Error sampling file");
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(hasher.result())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::fs::{create_dir_all, remove_file};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
extern crate hex;
|
||||
|
||||
fn tmp_file_path(name: &str) -> PathBuf {
|
||||
use std::env;
|
||||
let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
|
||||
let mut rand_bits = [0u8; 32];
|
||||
thread_rng().fill(&mut rand_bits[..]);
|
||||
|
||||
let mut path = PathBuf::new();
|
||||
path.push(out_dir);
|
||||
path.push("tmp");
|
||||
create_dir_all(&path).unwrap();
|
||||
|
||||
path.push(format!("{}-{:?}", name, hex::encode(rand_bits)));
|
||||
println!("path: {:?}", path);
|
||||
path
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sample_file() {
|
||||
solana_logger::setup();
|
||||
let in_path = tmp_file_path("test_sample_file_input.txt");
|
||||
let num_strings = 4096;
|
||||
let string = "12foobar";
|
||||
{
|
||||
let mut in_file = File::create(&in_path).unwrap();
|
||||
for _ in 0..num_strings {
|
||||
in_file.write(string.as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
let num_samples = (string.len() * num_strings / size_of::<Hash>()) as u64;
|
||||
let samples: Vec<_> = (0..num_samples).collect();
|
||||
let res = sample_file(&in_path, samples.as_slice());
|
||||
let ref_hash: Hash = Hash::new(&[
|
||||
173, 251, 182, 165, 10, 54, 33, 150, 133, 226, 106, 150, 99, 192, 179, 1, 230, 144,
|
||||
151, 126, 18, 191, 54, 67, 249, 140, 230, 160, 56, 30, 170, 52,
|
||||
]);
|
||||
let res = res.unwrap();
|
||||
assert_eq!(res, ref_hash);
|
||||
|
||||
// Sample just past the end
|
||||
assert!(sample_file(&in_path, &[num_samples]).is_err());
|
||||
remove_file(&in_path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sample_file_invalid_offset() {
|
||||
let in_path = tmp_file_path("test_sample_file_invalid_offset_input.txt");
|
||||
{
|
||||
let mut in_file = File::create(&in_path).unwrap();
|
||||
for _ in 0..4096 {
|
||||
in_file.write("123456foobar".as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
let samples = [0, 200000];
|
||||
let res = sample_file(&in_path, &samples);
|
||||
assert!(res.is_err());
|
||||
remove_file(in_path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sample_file_missing_file() {
|
||||
let in_path = tmp_file_path("test_sample_file_that_doesnt_exist.txt");
|
||||
let samples = [0, 5];
|
||||
let res = sample_file(&in_path, &samples);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
}
|
2
archiver/.gitignore
vendored
Normal file
2
archiver/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target/
|
||||
/farf/
|
20
archiver/Cargo.toml
Normal file
20
archiver/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-archiver"
|
||||
version = "1.0.4"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
console = "0.9.2"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.4" }
|
||||
solana-core = { path = "../core", version = "1.0.4" }
|
||||
solana-logger = { path = "../logger", version = "1.0.4" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.4" }
|
||||
solana-archiver-lib = { path = "../archiver-lib", version = "1.0.4" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.4" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.4" }
|
||||
|
153
archiver/src/main.rs
Normal file
153
archiver/src/main.rs
Normal file
@ -0,0 +1,153 @@
|
||||
use clap::{crate_description, crate_name, App, Arg};
|
||||
use console::style;
|
||||
use solana_archiver_lib::archiver::Archiver;
|
||||
use solana_clap_utils::{
|
||||
input_validators::is_keypair,
|
||||
keypair::{
|
||||
self, keypair_input, KeypairWithSource, ASK_SEED_PHRASE_ARG,
|
||||
SKIP_SEED_PHRASE_VALIDATION_ARG,
|
||||
},
|
||||
};
|
||||
use solana_core::{
|
||||
cluster_info::{Node, VALIDATOR_PORT_RANGE},
|
||||
contact_info::ContactInfo,
|
||||
};
|
||||
use solana_sdk::{commitment_config::CommitmentConfig, signature::Signer};
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
path::PathBuf,
|
||||
process::exit,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
|
||||
let matches = App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(solana_clap_utils::version!())
|
||||
.arg(
|
||||
Arg::with_name("identity_keypair")
|
||||
.short("i")
|
||||
.long("identity-keypair")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.validator(is_keypair)
|
||||
.help("File containing an identity (keypair)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("entrypoint")
|
||||
.short("n")
|
||||
.long("entrypoint")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(solana_net_utils::is_host_port)
|
||||
.help("Rendezvous with the cluster at this entry point"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ledger")
|
||||
.short("l")
|
||||
.long("ledger")
|
||||
.value_name("DIR")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("use DIR as persistent ledger location"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("storage_keypair")
|
||||
.short("s")
|
||||
.long("storage-keypair")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.validator(is_keypair)
|
||||
.help("File containing the storage account keypair"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(ASK_SEED_PHRASE_ARG.name)
|
||||
.long(ASK_SEED_PHRASE_ARG.long)
|
||||
.value_name("KEYPAIR NAME")
|
||||
.multiple(true)
|
||||
.takes_value(true)
|
||||
.possible_values(&["identity-keypair", "storage-keypair"])
|
||||
.help(ASK_SEED_PHRASE_ARG.help),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
|
||||
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
|
||||
.requires(ASK_SEED_PHRASE_ARG.name)
|
||||
.help(SKIP_SEED_PHRASE_VALIDATION_ARG.help),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let ledger_path = PathBuf::from(matches.value_of("ledger").unwrap());
|
||||
|
||||
let identity_keypair = keypair_input(&matches, "identity_keypair")
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Identity keypair input failed: {}", err);
|
||||
exit(1);
|
||||
})
|
||||
.keypair;
|
||||
let KeypairWithSource {
|
||||
keypair: storage_keypair,
|
||||
source: storage_keypair_source,
|
||||
} = keypair_input(&matches, "storage_keypair").unwrap_or_else(|err| {
|
||||
eprintln!("Storage keypair input failed: {}", err);
|
||||
exit(1);
|
||||
});
|
||||
if storage_keypair_source == keypair::Source::Generated {
|
||||
clap::Error::with_description(
|
||||
"The `storage-keypair` argument was not found",
|
||||
clap::ErrorKind::ArgumentNotFound,
|
||||
)
|
||||
.exit();
|
||||
}
|
||||
|
||||
let entrypoint_addr = matches
|
||||
.value_of("entrypoint")
|
||||
.map(|entrypoint| {
|
||||
solana_net_utils::parse_host_port(entrypoint)
|
||||
.expect("failed to parse entrypoint address")
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let gossip_addr = {
|
||||
let ip = solana_net_utils::get_public_ip_addr(&entrypoint_addr).unwrap();
|
||||
let mut addr = SocketAddr::new(ip, 0);
|
||||
addr.set_ip(solana_net_utils::get_public_ip_addr(&entrypoint_addr).unwrap());
|
||||
addr
|
||||
};
|
||||
let node = Node::new_archiver_with_external_ip(
|
||||
&identity_keypair.pubkey(),
|
||||
&gossip_addr,
|
||||
VALIDATOR_PORT_RANGE,
|
||||
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
||||
);
|
||||
|
||||
println!(
|
||||
"{} version {} (branch={}, commit={})",
|
||||
style(crate_name!()).bold(),
|
||||
solana_clap_utils::version!(),
|
||||
option_env!("CI_BRANCH").unwrap_or("unknown"),
|
||||
option_env!("CI_COMMIT").unwrap_or("unknown")
|
||||
);
|
||||
solana_metrics::set_host_id(identity_keypair.pubkey().to_string());
|
||||
println!(
|
||||
"replicating the data with identity_keypair={:?} gossip_addr={:?}",
|
||||
identity_keypair.pubkey(),
|
||||
gossip_addr
|
||||
);
|
||||
|
||||
let entrypoint_info = ContactInfo::new_gossip_entry_point(&entrypoint_addr);
|
||||
let archiver = Archiver::new(
|
||||
&ledger_path,
|
||||
node,
|
||||
entrypoint_info,
|
||||
Arc::new(identity_keypair),
|
||||
Arc::new(storage_keypair),
|
||||
CommitmentConfig::recent(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
archiver.join();
|
||||
}
|
2
banking-bench/.gitignore
vendored
Normal file
2
banking-bench/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target/
|
||||
/farf/
|
20
banking-bench/Cargo.toml
Normal file
20
banking-bench/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "1.0.4"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.6"
|
||||
rayon = "1.2.0"
|
||||
solana-core = { path = "../core", version = "1.0.4" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.4" }
|
||||
solana-logger = { path = "../logger", version = "1.0.4" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.4" }
|
||||
solana-measure = { path = "../measure", version = "1.0.4" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.4" }
|
||||
rand = "0.6.5"
|
||||
crossbeam-channel = "0.3"
|
306
banking-bench/src/main.rs
Normal file
306
banking-bench/src/main.rs
Normal file
@ -0,0 +1,306 @@
|
||||
use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana_core::banking_stage::{create_test_recorder, BankingStage};
|
||||
use solana_core::cluster_info::ClusterInfo;
|
||||
use solana_core::cluster_info::Node;
|
||||
use solana_core::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_core::packet::to_packets_chunked;
|
||||
use solana_core::poh_recorder::PohRecorder;
|
||||
use solana_core::poh_recorder::WorkingBankEntry;
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::Keypair;
|
||||
use solana_sdk::signature::Signature;
|
||||
use solana_sdk::system_transaction;
|
||||
use solana_sdk::timing::{duration_as_us, timestamp};
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn check_txs(
|
||||
receiver: &Arc<Receiver<WorkingBankEntry>>,
|
||||
ref_tx_count: usize,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
) -> bool {
|
||||
let mut total = 0;
|
||||
let now = Instant::now();
|
||||
let mut no_bank = false;
|
||||
loop {
|
||||
if let Ok((_bank, (entry, _tick_height))) = receiver.recv_timeout(Duration::from_millis(10))
|
||||
{
|
||||
total += entry.transactions.len();
|
||||
}
|
||||
if total >= ref_tx_count {
|
||||
break;
|
||||
}
|
||||
if now.elapsed().as_secs() > 60 {
|
||||
break;
|
||||
}
|
||||
if poh_recorder.lock().unwrap().bank().is_none() {
|
||||
trace!("no bank");
|
||||
no_bank = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !no_bank {
|
||||
assert!(total >= ref_tx_count);
|
||||
}
|
||||
no_bank
|
||||
}
|
||||
|
||||
fn make_accounts_txs(txes: usize, mint_keypair: &Keypair, hash: Hash) -> Vec<Transaction> {
|
||||
let to_pubkey = Pubkey::new_rand();
|
||||
let dummy = system_transaction::transfer(mint_keypair, &to_pubkey, 1, hash);
|
||||
(0..txes)
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let mut new = dummy.clone();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
new.message.account_keys[0] = Pubkey::new_rand();
|
||||
new.message.account_keys[1] = Pubkey::new_rand();
|
||||
new.signatures = vec![Signature::new(&sig[0..64])];
|
||||
new
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
struct Config {
|
||||
packets_per_batch: usize,
|
||||
chunk_len: usize,
|
||||
num_threads: usize,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn get_transactions_index(&self, chunk_index: usize) -> usize {
|
||||
chunk_index * (self.chunk_len / self.num_threads) * self.packets_per_batch
|
||||
}
|
||||
}
|
||||
|
||||
fn bytes_as_usize(bytes: &[u8]) -> usize {
|
||||
bytes[0] as usize | (bytes[1] as usize) << 8
|
||||
}
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
let num_threads = BankingStage::num_threads() as usize;
|
||||
// a multiple of packet chunk duplicates to avoid races
|
||||
const CHUNKS: usize = 8 * 2;
|
||||
const PACKETS_PER_BATCH: usize = 192;
|
||||
let txes = PACKETS_PER_BATCH * num_threads * CHUNKS;
|
||||
let mint_total = 1_000_000_000_000;
|
||||
let GenesisConfigInfo {
|
||||
genesis_config,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_config(mint_total);
|
||||
|
||||
let (verified_sender, verified_receiver) = unbounded();
|
||||
let (vote_sender, vote_receiver) = unbounded();
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
let mut bank_forks = BankForks::new(0, bank0);
|
||||
let mut bank = bank_forks.working_bank();
|
||||
|
||||
info!("threads: {} txs: {}", num_threads, txes);
|
||||
|
||||
let mut transactions = make_accounts_txs(txes, &mint_keypair, genesis_config.hash());
|
||||
|
||||
// fund all the accounts
|
||||
transactions.iter().for_each(|tx| {
|
||||
let fund = system_transaction::transfer(
|
||||
&mint_keypair,
|
||||
&tx.message.account_keys[0],
|
||||
mint_total / txes as u64,
|
||||
genesis_config.hash(),
|
||||
);
|
||||
let x = bank.process_transaction(&fund);
|
||||
x.unwrap();
|
||||
});
|
||||
//sanity check, make sure all the transactions can execute sequentially
|
||||
transactions.iter().for_each(|tx| {
|
||||
let res = bank.process_transaction(&tx);
|
||||
assert!(res.is_ok(), "sanity test transactions");
|
||||
});
|
||||
bank.clear_signatures();
|
||||
//sanity check, make sure all the transactions can execute in parallel
|
||||
let res = bank.process_transactions(&transactions);
|
||||
for r in res {
|
||||
assert!(r.is_ok(), "sanity parallel execution");
|
||||
}
|
||||
bank.clear_signatures();
|
||||
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
{
|
||||
let blockstore = Arc::new(
|
||||
Blockstore::open(&ledger_path).expect("Expected to be able to open database ledger"),
|
||||
);
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, None);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
verified_receiver,
|
||||
vote_receiver,
|
||||
None,
|
||||
);
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
|
||||
let chunk_len = verified.len() / CHUNKS;
|
||||
let mut start = 0;
|
||||
|
||||
// This is so that the signal_receiver does not go out of scope after the closure.
|
||||
// If it is dropped before poh_service, then poh_service will error when
|
||||
// calling send() on the channel.
|
||||
let signal_receiver = Arc::new(signal_receiver);
|
||||
let mut total_us = 0;
|
||||
let mut tx_total_us = 0;
|
||||
let mut txs_processed = 0;
|
||||
let mut root = 1;
|
||||
let collector = Pubkey::new_rand();
|
||||
const ITERS: usize = 1_000;
|
||||
let config = Config {
|
||||
packets_per_batch: PACKETS_PER_BATCH,
|
||||
chunk_len,
|
||||
num_threads,
|
||||
};
|
||||
let mut total_sent = 0;
|
||||
for _ in 0..ITERS {
|
||||
let now = Instant::now();
|
||||
let mut sent = 0;
|
||||
|
||||
for (i, v) in verified[start..start + chunk_len]
|
||||
.chunks(chunk_len / num_threads)
|
||||
.enumerate()
|
||||
{
|
||||
let mut byte = 0;
|
||||
let index = config.get_transactions_index(start + i);
|
||||
if index < transactions.len() {
|
||||
byte = bytes_as_usize(transactions[index].signatures[0].as_ref());
|
||||
}
|
||||
trace!(
|
||||
"sending... {}..{} {} v.len: {} sig: {} transactions.len: {} index: {}",
|
||||
start + i,
|
||||
start + chunk_len,
|
||||
timestamp(),
|
||||
v.len(),
|
||||
byte,
|
||||
transactions.len(),
|
||||
index,
|
||||
);
|
||||
for xv in v {
|
||||
sent += xv.packets.len();
|
||||
}
|
||||
verified_sender.send(v.to_vec()).unwrap();
|
||||
}
|
||||
let start_tx_index = config.get_transactions_index(start);
|
||||
let end_tx_index = config.get_transactions_index(start + chunk_len);
|
||||
for tx in &transactions[start_tx_index..end_tx_index] {
|
||||
loop {
|
||||
if bank.get_signature_status(&tx.signatures[0]).is_some() {
|
||||
break;
|
||||
}
|
||||
if poh_recorder.lock().unwrap().bank().is_none() {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
if check_txs(&signal_receiver, txes / CHUNKS, &poh_recorder) {
|
||||
debug!(
|
||||
"resetting bank {} tx count: {} txs_proc: {}",
|
||||
bank.slot(),
|
||||
bank.transaction_count(),
|
||||
txs_processed
|
||||
);
|
||||
assert!(txs_processed < bank.transaction_count());
|
||||
txs_processed = bank.transaction_count();
|
||||
tx_total_us += duration_as_us(&now.elapsed());
|
||||
|
||||
let mut poh_time = Measure::start("poh_time");
|
||||
poh_recorder.lock().unwrap().reset(
|
||||
bank.last_blockhash(),
|
||||
bank.slot(),
|
||||
Some((bank.slot(), bank.slot() + 1)),
|
||||
);
|
||||
poh_time.stop();
|
||||
|
||||
let mut new_bank_time = Measure::start("new_bank");
|
||||
let new_bank = Bank::new_from_parent(&bank, &collector, bank.slot() + 1);
|
||||
new_bank_time.stop();
|
||||
|
||||
let mut insert_time = Measure::start("insert_time");
|
||||
bank_forks.insert(new_bank);
|
||||
bank = bank_forks.working_bank();
|
||||
insert_time.stop();
|
||||
|
||||
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);
|
||||
root += 1;
|
||||
}
|
||||
debug!(
|
||||
"new_bank_time: {}us insert_time: {}us poh_time: {}us",
|
||||
new_bank_time.as_us(),
|
||||
insert_time.as_us(),
|
||||
poh_time.as_us(),
|
||||
);
|
||||
} else {
|
||||
tx_total_us += duration_as_us(&now.elapsed());
|
||||
}
|
||||
|
||||
// This signature clear may not actually clear the signatures
|
||||
// in this chunk, but since we rotate between CHUNKS then
|
||||
// we should clear them by the time we come around again to re-use that chunk.
|
||||
bank.clear_signatures();
|
||||
total_us += duration_as_us(&now.elapsed());
|
||||
debug!(
|
||||
"time: {} us checked: {} sent: {}",
|
||||
duration_as_us(&now.elapsed()),
|
||||
txes / CHUNKS,
|
||||
sent,
|
||||
);
|
||||
total_sent += sent;
|
||||
|
||||
if bank.slot() > 0 && bank.slot() % 16 == 0 {
|
||||
for tx in transactions.iter_mut() {
|
||||
tx.message.recent_blockhash = bank.last_blockhash();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
tx.signatures[0] = Signature::new(&sig[0..64]);
|
||||
}
|
||||
verified = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
|
||||
}
|
||||
|
||||
start += chunk_len;
|
||||
start %= verified.len();
|
||||
}
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_total', 'median': '{}'}}",
|
||||
(1000.0 * 1000.0 * total_sent as f64) / (total_us as f64),
|
||||
);
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_tx_total', 'median': '{}'}}",
|
||||
(1000.0 * 1000.0 * total_sent as f64) / (tx_total_us as f64),
|
||||
);
|
||||
|
||||
drop(verified_sender);
|
||||
drop(vote_sender);
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
banking_stage.join().unwrap();
|
||||
debug!("waited for banking_stage");
|
||||
poh_service.join().unwrap();
|
||||
sleep(Duration::from_secs(1));
|
||||
debug!("waited for poh_service");
|
||||
}
|
||||
let _unused = Blockstore::destroy(&ledger_path);
|
||||
}
|
4
bench-exchange/.gitignore
vendored
Normal file
4
bench-exchange/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
34
bench-exchange/Cargo.toml
Normal file
34
bench-exchange/Cargo.toml
Normal file
@ -0,0 +1,34 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "1.0.4"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
clap = "2.32.0"
|
||||
itertools = "0.8.2"
|
||||
log = "0.4.8"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
rand = "0.6.5"
|
||||
rayon = "1.2.0"
|
||||
serde_json = "1.0.46"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.4" }
|
||||
solana-core = { path = "../core", version = "1.0.4" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.4" }
|
||||
solana-client = { path = "../client", version = "1.0.4" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.4" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.0.4" }
|
||||
solana-logger = { path = "../logger", version = "1.0.4" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.4" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.4" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.4" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.4" }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.4" }
|
479
bench-exchange/README.md
Normal file
479
bench-exchange/README.md
Normal file
@ -0,0 +1,479 @@
|
||||
# token-exchange
|
||||
Solana Token Exchange Bench
|
||||
|
||||
If you can't wait; jump to [Running the exchange](#Running-the-exchange) to
|
||||
learn how to start and interact with the exchange.
|
||||
|
||||
### Table of Contents
|
||||
[Overview](#Overview)<br>
|
||||
[Premise](#Premise)<br>
|
||||
[Exchange startup](#Exchange-startup)<br>
|
||||
[Order Requests](#Trade-requests)<br>
|
||||
[Order Cancellations](#Trade-cancellations)<br>
|
||||
[Trade swap](#Trade-swap)<br>
|
||||
[Exchange program operations](#Exchange-program-operations)<br>
|
||||
[Quotes and OHLCV](#Quotes-and-OHLCV)<br>
|
||||
[Investor strategies](#Investor-strategies)<br>
|
||||
[Running the exchange](#Running-the-exchange)<br>
|
||||
|
||||
## Overview
|
||||
|
||||
An exchange is a marketplace where one asset can be traded for another. This
|
||||
demo demonstrates one way to host an exchange on the Solana blockchain by
|
||||
emulating a currency exchange.
|
||||
|
||||
The assets are virtual tokens held by investors who may post order requests to
|
||||
the exchange. A Matcher monitors the exchange and posts swap requests for
|
||||
matching orders. All the transactions can execute concurrently.
|
||||
|
||||
## Premise
|
||||
|
||||
- Exchange
|
||||
- An exchange is a marketplace where one asset can be traded for another.
|
||||
The exchange in this demo is the on-chain program that implements the
|
||||
tokens and the policies for trading those tokens.
|
||||
- Token
|
||||
- A virtual asset that can be owned, traded, and holds virtual intrinsic value
|
||||
compared to other assets. There are four types of tokens in this demo, A,
|
||||
B, C, D. Each one may be traded for another.
|
||||
- Token account
|
||||
- An account owned by the exchange that holds a quantity of one type of token.
|
||||
- Account request
|
||||
- A request to create a token account
|
||||
- Token request
|
||||
- A request to deposit tokens of a particular type into a token account.
|
||||
- Asset pair
|
||||
- A struct with fields Base and Quote, representing the two assets which make up a
|
||||
trading pair, which themselves are Tokens. The Base or 'primary' asset is the
|
||||
numerator and the Quote is the denominator for pricing purposes.
|
||||
- Order side
|
||||
- Describes which side of the market an investor wants to place a trade on. Options
|
||||
are "Bid" or "Ask", where a bid represents an offer to purchase the Base asset of
|
||||
the AssetPair for a sum of the Quote Asset and an Ask is an offer to sell Base asset
|
||||
for the Quote asset.
|
||||
- Price ratio
|
||||
- An expression of the relative prices of two tokens. Calculated with the Base
|
||||
Asset as the numerator and the Quote Asset as the denominator. Ratios are
|
||||
represented as fixed point numbers. The fixed point scaler is defined in
|
||||
[exchange_state.rs](https://github.com/solana-labs/solana/blob/c2fdd1362a029dcf89c8907c562d2079d977df11/programs/exchange_api/src/exchange_state.rs#L7)
|
||||
- Order request
|
||||
- A Solana transaction sent by a trader to the exchange to submit an order.
|
||||
Order requests are made up of the token pair, the order side (bid or ask),
|
||||
quantity of the primary token, the price ratio, and the two token accounts
|
||||
to be credited/deducted. An example trade request looks like "T AB 5 2"
|
||||
which reads "Exchange 5 A tokens to B tokens at a price ratio of 1:2" A fulfilled trade would result in 5 A tokens
|
||||
deducted and 10 B tokens credited to the trade initiator's token accounts.
|
||||
Successful order requests result in an order.
|
||||
- Order
|
||||
- The result of a successful order request. orders are stored in
|
||||
accounts owned by the submitter of the order request. They can only be
|
||||
canceled by their owner but can be used by anyone in a trade swap. They
|
||||
contain the same information as the order request.
|
||||
- Price spread
|
||||
- The difference between the two matching orders. The spread is the
|
||||
profit of the Matcher initiating the swap request.
|
||||
- Match requirements
|
||||
- Policies that result in a successful trade swap.
|
||||
- Match request
|
||||
- A request to fill two complementary orders (bid/ask), resulting if successful,
|
||||
in a trade being created.
|
||||
- Trade
|
||||
- A successful trade is created from two matching orders that meet
|
||||
swap requirements which are submitted in a Match Request by a Matcher and
|
||||
executed by the exchange. A trade may not wholly satisfy one or both of the
|
||||
orders in which case the orders are adjusted appropriately. Upon execution,
|
||||
tokens are distributed to the traders' accounts and any overlap or
|
||||
"negative spread" between orders is deposited into the Matcher's profit
|
||||
account. All successful trades are recorded in the data of a new solana
|
||||
account for posterity.
|
||||
- Investor
|
||||
- Individual investors who hold a number of tokens and wish to trade them on
|
||||
the exchange. Investors operate as Solana thin clients who own a set of
|
||||
accounts containing tokens and/or order requests. Investors post
|
||||
transactions to the exchange in order to request tokens and post or cancel
|
||||
order requests.
|
||||
- Matcher
|
||||
- An agent who facilitates trading between investors. Matchers operate as
|
||||
Solana thin clients who monitor all the orders looking for a trade
|
||||
match. Once found, the Matcher issues a swap request to the exchange.
|
||||
Matchers are the engine of the exchange and are rewarded for their efforts by
|
||||
accumulating the price spreads of the swaps they initiate. Matchers also
|
||||
provide current bid/ask price and OHLCV (Open, High, Low, Close, Volume)
|
||||
information on demand via a public network port.
|
||||
- Transaction fees
|
||||
- Solana transaction fees are paid for by the transaction submitters who are
|
||||
the Investors and Matchers.
|
||||
|
||||
## Exchange startup
|
||||
|
||||
The exchange is up and running when it reaches a state where it can take
|
||||
investors' trades and Matchers' match requests. To achieve this state the
|
||||
following must occur in order:
|
||||
|
||||
- Start the Solana blockchain
|
||||
- Start the thin-client
|
||||
- The Matcher subscribes to change notifications for all the accounts owned by
|
||||
the exchange program id. The subscription is managed via Solana's JSON RPC
|
||||
interface.
|
||||
- The Matcher starts responding to queries for bid/ask price and OHLCV
|
||||
|
||||
The Matcher responding successfully to price and OHLCV requests is the signal to
|
||||
the investors that trades submitted after that point will be analyzed. <!--This
|
||||
is not ideal, and instead investors should be able to submit trades at any time,
|
||||
and the Matcher could come and go without missing a trade. One way to achieve
|
||||
this is for the Matcher to read the current state of all accounts looking for all
|
||||
open orders.-->
|
||||
|
||||
Investors will initially query the exchange to discover their current balance
|
||||
for each type of token. If the investor does not already have an account for
|
||||
each type of token, they will submit account requests. Matcher as well will
|
||||
request accounts to hold the tokens they earn by initiating trade swaps.
|
||||
|
||||
```rust
|
||||
/// Supported token types
|
||||
pub enum Token {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
}
|
||||
|
||||
/// Supported token pairs
|
||||
pub enum TokenPair {
|
||||
AB,
|
||||
AC,
|
||||
AD,
|
||||
BC,
|
||||
BD,
|
||||
CD,
|
||||
}
|
||||
|
||||
pub enum ExchangeInstruction {
|
||||
/// New token account
|
||||
/// key 0 - Signer
|
||||
/// key 1 - New token account
|
||||
AccountRequest,
|
||||
}
|
||||
|
||||
/// Token accounts are populated with this structure
|
||||
pub struct TokenAccountInfo {
|
||||
/// Investor who owns this account
|
||||
pub owner: Pubkey,
|
||||
/// Current number of tokens this account holds
|
||||
pub tokens: Tokens,
|
||||
}
|
||||
```
|
||||
|
||||
For this demo investors or Matcher can request more tokens from the exchange at
|
||||
any time by submitting token requests. In non-demos, an exchange of this type
|
||||
would provide another way to exchange a 3rd party asset into tokens.
|
||||
|
||||
To request tokens, investors submit transfer requests:
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// Transfer tokens between two accounts
|
||||
/// key 0 - Account to transfer tokens to
|
||||
/// key 1 - Account to transfer tokens from. This can be the exchange program itself,
|
||||
/// the exchange has a limitless number of tokens it can transfer.
|
||||
TransferRequest(Token, u64),
|
||||
}
|
||||
```
|
||||
|
||||
## Order Requests
|
||||
|
||||
When an investor decides to exchange a token of one type for another, they
|
||||
submit a transaction to the Solana Blockchain containing an order request, which,
|
||||
if successful, is turned into an order. orders do not expire but are
|
||||
cancellable. <!-- orders should have a timestamp to enable trade
|
||||
expiration --> When an order is created, tokens are deducted from a token
|
||||
account and the order acts as an escrow. The tokens are held until the
|
||||
order is fulfilled or canceled. If the direction is `To`, then the number
|
||||
of `tokens` are deducted from the primary account, if `From` then `tokens`
|
||||
multiplied by `price` are deducted from the secondary account. orders are
|
||||
no longer valid when the number of `tokens` goes to zero, at which point they
|
||||
can no longer be used. <!-- Could support refilling orders, so order
|
||||
accounts are refilled rather than accumulating -->
|
||||
|
||||
```rust
|
||||
/// Direction of the exchange between two tokens in a pair
|
||||
pub enum Direction {
|
||||
/// Trade first token type (primary) in the pair 'To' the second
|
||||
To,
|
||||
/// Trade first token type in the pair 'From' the second (secondary)
|
||||
From,
|
||||
}
|
||||
|
||||
pub struct OrderRequestInfo {
|
||||
/// Direction of trade
|
||||
pub direction: Direction,
|
||||
|
||||
/// Token pair to trade
|
||||
pub pair: TokenPair,
|
||||
|
||||
/// Number of tokens to exchange; refers to the primary or the secondary depending on the direction
|
||||
pub tokens: u64,
|
||||
|
||||
/// The price ratio the primary price over the secondary price. The primary price is fixed
|
||||
/// and equal to the variable `SCALER`.
|
||||
pub price: u64,
|
||||
|
||||
/// Token account to deposit tokens on successful swap
|
||||
pub dst_account: Pubkey,
|
||||
}
|
||||
|
||||
pub enum ExchangeInstruction {
|
||||
/// order request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - Token account associated with this trade
|
||||
TradeRequest(TradeRequestInfo),
|
||||
}
|
||||
|
||||
/// Trade accounts are populated with this structure
|
||||
pub struct TradeOrderInfo {
|
||||
/// Owner of the order
|
||||
pub owner: Pubkey,
|
||||
/// Direction of the exchange
|
||||
pub direction: Direction,
|
||||
/// Token pair indicating two tokens to exchange, first is primary
|
||||
pub pair: TokenPair,
|
||||
/// Number of tokens to exchange; primary or secondary depending on direction
|
||||
pub tokens: u64,
|
||||
/// Scaled price of the secondary token given the primary is equal to the scale value
|
||||
/// If scale is 1 and price is 2 then ratio is 1:2 or 1 primary token for 2 secondary tokens
|
||||
pub price: u64,
|
||||
/// account which the tokens were source from. The trade account holds the tokens in escrow
|
||||
/// until either one or more part of a swap or the trade is canceled.
|
||||
pub src_account: Pubkey,
|
||||
/// account which the tokens the tokens will be deposited into on a successful trade
|
||||
pub dst_account: Pubkey,
|
||||
}
|
||||
```
|
||||
|
||||
## Order cancellations
|
||||
|
||||
An investor may cancel a trade at anytime, but only trades they own. If the
|
||||
cancellation is successful, any tokens held in escrow are returned to the
|
||||
account from which they came.
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// order cancellation
|
||||
/// key 0 - Signer
|
||||
/// key 1 -order to cancel
|
||||
TradeCancellation,
|
||||
}
|
||||
```
|
||||
|
||||
## Trade swaps
|
||||
|
||||
The Matcher is monitoring the accounts assigned to the exchange program and
|
||||
building a trade-order table. The order table is used to identify
|
||||
matching orders which could be fulfilled. When a match is found the
|
||||
Matcher should issue a swap request. Swap requests may not satisfy the entirety
|
||||
of either order, but the exchange will greedily fulfill it. Any leftover tokens
|
||||
in either account will keep the order valid for further swap requests in
|
||||
the future.
|
||||
|
||||
Matching orders are defined by the following swap requirements:
|
||||
|
||||
- Opposite polarity (one `To` and one `From`)
|
||||
- Operate on the same token pair
|
||||
- The price ratio of the `From` order is greater than or equal to the `To` order
|
||||
- There are sufficient tokens to perform the trade
|
||||
|
||||
Orders can be written in the following format:
|
||||
|
||||
`investor direction pair quantity price-ratio`
|
||||
|
||||
For example:
|
||||
|
||||
- `1 T AB 2 1`
|
||||
- Investor 1 wishes to exchange 2 A tokens to B tokens at a ratio of 1 A to 1
|
||||
B
|
||||
- `2 F AC 6 1.2`
|
||||
- Investor 2 wishes to exchange A tokens from 6 B tokens at a ratio of 1 A
|
||||
from 1.2 B
|
||||
|
||||
An order table could look something like the following. Notice how the columns
|
||||
are sorted low to high and high to low, respectively. Prices are dramatic and
|
||||
whole for clarity.
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 2 4 | 2 F AB 2 8 |
|
||||
| 2 | 1 T AB 1 4 | 2 F AB 2 8 |
|
||||
| 3 | 1 T AB 6 6 | 2 F AB 2 7 |
|
||||
| 4 | 1 T AB 2 8 | 2 F AB 3 6 |
|
||||
| 5 | 1 T AB 2 10 | 2 F AB 1 5 |
|
||||
|
||||
As part of a successful swap request, the exchange will credit tokens to the
|
||||
Matcher's account equal to the difference in the price ratios or the two orders.
|
||||
These tokens are considered the Matcher's profit for initiating the trade.
|
||||
|
||||
The Matcher would initiate the following swap on the order table above:
|
||||
|
||||
- Row 1, To: Investor 1 trades 2 A tokens to 8 B tokens
|
||||
- Row 1, From: Investor 2 trades 2 A tokens from 8 B tokens
|
||||
- Matcher takes 8 B tokens as profit
|
||||
|
||||
Both row 1 trades are fully realized, table becomes:
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 1 4 | 2 F AB 2 8 |
|
||||
| 2 | 1 T AB 6 6 | 2 F AB 2 7 |
|
||||
| 3 | 1 T AB 2 8 | 2 F AB 3 6 |
|
||||
| 4 | 1 T AB 2 10 | 2 F AB 1 5 |
|
||||
|
||||
The Matcher would initiate the following swap:
|
||||
|
||||
- Row 1, To: Investor 1 trades 1 A token to 4 B tokens
|
||||
- Row 1, From: Investor 2 trades 1 A token from 4 B tokens
|
||||
- Matcher takes 4 B tokens as profit
|
||||
|
||||
Row 1 From is not fully realized, table becomes:
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 6 6 | 2 F AB 1 8 |
|
||||
| 2 | 1 T AB 2 8 | 2 F AB 2 7 |
|
||||
| 3 | 1 T AB 2 10 | 2 F AB 3 6 |
|
||||
| 4 | | 2 F AB 1 5 |
|
||||
|
||||
The Matcher would initiate the following swap:
|
||||
|
||||
- Row 1, To: Investor 1 trades 1 A token to 6 B tokens
|
||||
- Row 1, From: Investor 2 trades 1 A token from 6 B tokens
|
||||
- Matcher takes 2 B tokens as profit
|
||||
|
||||
Row 1 To is now fully realized, table becomes:
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 5 6 | 2 F AB 2 7 |
|
||||
| 2 | 1 T AB 2 8 | 2 F AB 3 5 |
|
||||
| 3 | 1 T AB 2 10 | 2 F AB 1 5 |
|
||||
|
||||
The Matcher would initiate the following last swap:
|
||||
|
||||
- Row 1, To: Investor 1 trades 2 A token to 12 B tokens
|
||||
- Row 1, From: Investor 2 trades 2 A token from 12 B tokens
|
||||
- Matcher takes 2 B tokens as profit
|
||||
|
||||
Table becomes:
|
||||
|
||||
|Row| To | From |
|
||||
|---|-------------|------------|
|
||||
| 1 | 1 T AB 3 6 | 2 F AB 3 5 |
|
||||
| 2 | 1 T AB 2 8 | 2 F AB 1 5 |
|
||||
| 3 | 1 T AB 2 10 | |
|
||||
|
||||
At this point the lowest To's price is larger than the largest From's price so
|
||||
no more swaps would be initiated until new orders came in.
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// Trade swap request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - 'To' order
|
||||
/// key 3 - `From` order
|
||||
/// key 4 - Token account associated with the To Trade
|
||||
/// key 5 - Token account associated with From trade
|
||||
/// key 6 - Token account in which to deposit the Matcher profit from the swap.
|
||||
SwapRequest,
|
||||
}
|
||||
|
||||
/// Swap accounts are populated with this structure
|
||||
pub struct TradeSwapInfo {
|
||||
/// Pair swapped
|
||||
pub pair: TokenPair,
|
||||
/// `To` order
|
||||
pub to_trade_order: Pubkey,
|
||||
/// `From` order
|
||||
pub from_trade_order: Pubkey,
|
||||
/// Number of primary tokens exchanged
|
||||
pub primary_tokens: u64,
|
||||
/// Price the primary tokens were exchanged for
|
||||
pub primary_price: u64,
|
||||
/// Number of secondary tokens exchanged
|
||||
pub secondary_tokens: u64,
|
||||
/// Price the secondary tokens were exchanged for
|
||||
pub secondary_price: u64,
|
||||
}
|
||||
```
|
||||
|
||||
## Exchange program operations
|
||||
|
||||
Putting all the commands together from above, the following operations will be
|
||||
supported by the on-chain exchange program:
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// New token account
|
||||
/// key 0 - Signer
|
||||
/// key 1 - New token account
|
||||
AccountRequest,
|
||||
|
||||
/// Transfer tokens between two accounts
|
||||
/// key 0 - Account to transfer tokens to
|
||||
/// key 1 - Account to transfer tokens from. This can be the exchange program itself,
|
||||
/// the exchange has a limitless number of tokens it can transfer.
|
||||
TransferRequest(Token, u64),
|
||||
|
||||
/// order request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - Token account associated with this trade
|
||||
TradeRequest(TradeRequestInfo),
|
||||
|
||||
/// order cancellation
|
||||
/// key 0 - Signer
|
||||
/// key 1 -order to cancel
|
||||
TradeCancellation,
|
||||
|
||||
/// Trade swap request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - 'To' order
|
||||
/// key 3 - `From` order
|
||||
/// key 4 - Token account associated with the To Trade
|
||||
/// key 5 - Token account associated with From trade
|
||||
/// key 6 - Token account in which to deposit the Matcher profit from the swap.
|
||||
SwapRequest,
|
||||
}
|
||||
```
|
||||
|
||||
## Quotes and OHLCV
|
||||
|
||||
The Matcher will provide current bid/ask price quotes based on trade actively and
|
||||
also provide OHLCV based on some time window. The details of how the bid/ask
|
||||
price quotes are calculated are yet to be decided.
|
||||
|
||||
## Investor strategies
|
||||
|
||||
To make a compelling demo, the investors needs to provide interesting trade
|
||||
behavior. Something as simple as a randomly twiddled baseline would be a
|
||||
minimum starting point.
|
||||
|
||||
## Running the exchange
|
||||
|
||||
The exchange bench posts trades and swaps matches as fast as it can.
|
||||
|
||||
You might want to bump the duration up
|
||||
to 60 seconds and the batch size to 1000 for better numbers. You can modify those
|
||||
in client_demo/src/demo.rs::test_exchange_local_cluster.
|
||||
|
||||
The following command runs the bench:
|
||||
|
||||
```bash
|
||||
$ RUST_LOG=solana_bench_exchange=info cargo test --release -- --nocapture test_exchange_local_cluster
|
||||
```
|
||||
|
||||
To also see the cluster messages:
|
||||
|
||||
```bash
|
||||
$ RUST_LOG=solana_bench_exchange=info,solana=info cargo test --release -- --nocapture test_exchange_local_cluster
|
||||
```
|
1037
bench-exchange/src/bench.rs
Normal file
1037
bench-exchange/src/bench.rs
Normal file
File diff suppressed because it is too large
Load Diff
220
bench-exchange/src/cli.rs
Normal file
220
bench-exchange/src/cli.rs
Normal file
@ -0,0 +1,220 @@
|
||||
use clap::{crate_description, crate_name, value_t, App, Arg, ArgMatches};
|
||||
use solana_core::gen_keys::GenKeys;
|
||||
use solana_faucet::faucet::FAUCET_PORT;
|
||||
use solana_sdk::signature::{read_keypair_file, Keypair};
|
||||
use std::net::SocketAddr;
|
||||
use std::process::exit;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct Config {
|
||||
pub entrypoint_addr: SocketAddr,
|
||||
pub faucet_addr: SocketAddr,
|
||||
pub identity: Keypair,
|
||||
pub threads: usize,
|
||||
pub num_nodes: usize,
|
||||
pub duration: Duration,
|
||||
pub transfer_delay: u64,
|
||||
pub fund_amount: u64,
|
||||
pub batch_size: usize,
|
||||
pub chunk_size: usize,
|
||||
pub account_groups: usize,
|
||||
pub client_ids_and_stake_file: String,
|
||||
pub write_to_client_file: bool,
|
||||
pub read_from_client_file: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
|
||||
faucet_addr: SocketAddr::from(([127, 0, 0, 1], FAUCET_PORT)),
|
||||
identity: Keypair::new(),
|
||||
num_nodes: 1,
|
||||
threads: 4,
|
||||
duration: Duration::new(u64::max_value(), 0),
|
||||
transfer_delay: 0,
|
||||
fund_amount: 100_000,
|
||||
batch_size: 100,
|
||||
chunk_size: 100,
|
||||
account_groups: 100,
|
||||
client_ids_and_stake_file: String::new(),
|
||||
write_to_client_file: false,
|
||||
read_from_client_file: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
|
||||
App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(version)
|
||||
.arg(
|
||||
Arg::with_name("entrypoint")
|
||||
.short("n")
|
||||
.long("entrypoint")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("127.0.0.1:8001")
|
||||
.help("Cluster entry point; defaults to 127.0.0.1:8001"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("faucet")
|
||||
.short("d")
|
||||
.long("faucet")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("127.0.0.1:9900")
|
||||
.help("Location of the faucet; defaults to 127.0.0.1:9900"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("identity")
|
||||
.short("i")
|
||||
.long("identity")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.help("File containing a client identity (keypair)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("threads")
|
||||
.long("threads")
|
||||
.value_name("<threads>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("1")
|
||||
.help("Number of threads submitting transactions"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num-nodes")
|
||||
.long("num-nodes")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("1")
|
||||
.help("Wait for NUM nodes to converge"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("duration")
|
||||
.long("duration")
|
||||
.value_name("SECS")
|
||||
.takes_value(true)
|
||||
.default_value("60")
|
||||
.help("Seconds to run benchmark, then exit; default is forever"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("transfer-delay")
|
||||
.long("transfer-delay")
|
||||
.value_name("<delay>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("0")
|
||||
.help("Delay between each chunk"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("fund-amount")
|
||||
.long("fund-amount")
|
||||
.value_name("<fund>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("100000")
|
||||
.help("Number of lamports to fund to each signer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("batch-size")
|
||||
.long("batch-size")
|
||||
.value_name("<batch>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("1000")
|
||||
.help("Number of transactions before the signer rolls over"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("chunk-size")
|
||||
.long("chunk-size")
|
||||
.value_name("<cunk>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("500")
|
||||
.help("Number of transactions to generate and send at a time"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("account-groups")
|
||||
.long("account-groups")
|
||||
.value_name("<groups>")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("10")
|
||||
.help("Number of account groups to cycle for each batch"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("write-client-keys")
|
||||
.long("write-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Generate client keys and stakes and write the list to YAML file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("read-client-keys")
|
||||
.long("read-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Read client keys and stakes from the YAML file"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
let mut args = Config::default();
|
||||
|
||||
args.entrypoint_addr = solana_net_utils::parse_host_port(
|
||||
matches.value_of("entrypoint").unwrap(),
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse entrypoint address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
|
||||
args.faucet_addr = solana_net_utils::parse_host_port(matches.value_of("faucet").unwrap())
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse faucet address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
|
||||
if matches.is_present("identity") {
|
||||
args.identity = read_keypair_file(matches.value_of("identity").unwrap())
|
||||
.expect("can't read client identity");
|
||||
} else {
|
||||
args.identity = {
|
||||
let seed = [42_u8; 32];
|
||||
let mut rnd = GenKeys::new(seed);
|
||||
rnd.gen_keypair()
|
||||
};
|
||||
}
|
||||
args.threads = value_t!(matches.value_of("threads"), usize).expect("Failed to parse threads");
|
||||
args.num_nodes =
|
||||
value_t!(matches.value_of("num-nodes"), usize).expect("Failed to parse num-nodes");
|
||||
let duration = value_t!(matches.value_of("duration"), u64).expect("Failed to parse duration");
|
||||
args.duration = Duration::from_secs(duration);
|
||||
args.transfer_delay =
|
||||
value_t!(matches.value_of("transfer-delay"), u64).expect("Failed to parse transfer-delay");
|
||||
args.fund_amount =
|
||||
value_t!(matches.value_of("fund-amount"), u64).expect("Failed to parse fund-amount");
|
||||
args.batch_size =
|
||||
value_t!(matches.value_of("batch-size"), usize).expect("Failed to parse batch-size");
|
||||
args.chunk_size =
|
||||
value_t!(matches.value_of("chunk-size"), usize).expect("Failed to parse chunk-size");
|
||||
args.account_groups = value_t!(matches.value_of("account-groups"), usize)
|
||||
.expect("Failed to parse account-groups");
|
||||
|
||||
if let Some(s) = matches.value_of("write-client-keys") {
|
||||
args.write_to_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
|
||||
if let Some(s) = matches.value_of("read-client-keys") {
|
||||
assert!(!args.write_to_client_file);
|
||||
args.read_from_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
args
|
||||
}
|
3
bench-exchange/src/lib.rs
Normal file
3
bench-exchange/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod bench;
|
||||
pub mod cli;
|
||||
mod order_book;
|
83
bench-exchange/src/main.rs
Normal file
83
bench-exchange/src/main.rs
Normal file
@ -0,0 +1,83 @@
|
||||
pub mod bench;
|
||||
mod cli;
|
||||
pub mod order_book;
|
||||
|
||||
use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_exchange, Config};
|
||||
use log::*;
|
||||
use solana_core::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_sdk::signature::Signer;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
solana_metrics::set_panic_hook("bench-exchange");
|
||||
|
||||
let matches = cli::build_args(solana_clap_utils::version!()).get_matches();
|
||||
let cli_config = cli::extract_args(&matches);
|
||||
|
||||
let cli::Config {
|
||||
entrypoint_addr,
|
||||
faucet_addr,
|
||||
identity,
|
||||
threads,
|
||||
num_nodes,
|
||||
duration,
|
||||
transfer_delay,
|
||||
fund_amount,
|
||||
batch_size,
|
||||
chunk_size,
|
||||
account_groups,
|
||||
client_ids_and_stake_file,
|
||||
write_to_client_file,
|
||||
read_from_client_file,
|
||||
..
|
||||
} = cli_config;
|
||||
|
||||
let config = Config {
|
||||
identity,
|
||||
threads,
|
||||
duration,
|
||||
transfer_delay,
|
||||
fund_amount,
|
||||
batch_size,
|
||||
chunk_size,
|
||||
account_groups,
|
||||
client_ids_and_stake_file,
|
||||
read_from_client_file,
|
||||
};
|
||||
|
||||
if write_to_client_file {
|
||||
create_client_accounts_file(
|
||||
&config.client_ids_and_stake_file,
|
||||
config.batch_size,
|
||||
config.account_groups,
|
||||
config.fund_amount,
|
||||
);
|
||||
} else {
|
||||
info!("Connecting to the cluster");
|
||||
let (nodes, _archivers) =
|
||||
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
|
||||
panic!("Failed to discover nodes");
|
||||
});
|
||||
|
||||
let (client, num_clients) = get_multi_client(&nodes);
|
||||
|
||||
info!("{} nodes found", num_clients);
|
||||
if num_clients < num_nodes {
|
||||
panic!("Error: Insufficient nodes discovered");
|
||||
}
|
||||
|
||||
if !read_from_client_file {
|
||||
info!("Funding keypair: {}", config.identity.pubkey());
|
||||
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
const NUM_SIGNERS: u64 = 2;
|
||||
airdrop_lamports(
|
||||
&client,
|
||||
&faucet_addr,
|
||||
&config.identity,
|
||||
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
|
||||
);
|
||||
}
|
||||
do_bench_exchange(vec![client], config);
|
||||
}
|
||||
}
|
134
bench-exchange/src/order_book.rs
Normal file
134
bench-exchange/src/order_book.rs
Normal file
@ -0,0 +1,134 @@
|
||||
use itertools::EitherOrBoth::{Both, Left, Right};
|
||||
use itertools::Itertools;
|
||||
use log::*;
|
||||
use solana_exchange_program::exchange_state::*;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
use std::{error, fmt};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ToOrder {
|
||||
pub pubkey: Pubkey,
|
||||
pub info: OrderInfo,
|
||||
}
|
||||
|
||||
impl Ord for ToOrder {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
other.info.price.cmp(&self.info.price)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for ToOrder {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct FromOrder {
|
||||
pub pubkey: Pubkey,
|
||||
pub info: OrderInfo,
|
||||
}
|
||||
|
||||
impl Ord for FromOrder {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.info.price.cmp(&other.info.price)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for FromOrder {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OrderBook {
|
||||
// TODO scale to x token types
|
||||
to_ab: BinaryHeap<ToOrder>,
|
||||
from_ab: BinaryHeap<FromOrder>,
|
||||
}
|
||||
impl fmt::Display for OrderBook {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"+-Order Book--------------------------+-------------------------------------+"
|
||||
)?;
|
||||
for (i, it) in self
|
||||
.to_ab
|
||||
.iter()
|
||||
.zip_longest(self.from_ab.iter())
|
||||
.enumerate()
|
||||
{
|
||||
match it {
|
||||
Both(to, from) => writeln!(
|
||||
f,
|
||||
"| T AB {:8} for {:8}/{:8} | F AB {:8} for {:8}/{:8} |{}",
|
||||
to.info.tokens,
|
||||
SCALER,
|
||||
to.info.price,
|
||||
from.info.tokens,
|
||||
SCALER,
|
||||
from.info.price,
|
||||
i
|
||||
)?,
|
||||
Left(to) => writeln!(
|
||||
f,
|
||||
"| T AB {:8} for {:8}/{:8} | |{}",
|
||||
to.info.tokens, SCALER, to.info.price, i
|
||||
)?,
|
||||
Right(from) => writeln!(
|
||||
f,
|
||||
"| | F AB {:8} for {:8}/{:8} |{}",
|
||||
from.info.tokens, SCALER, from.info.price, i
|
||||
)?,
|
||||
}
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
"+-------------------------------------+-------------------------------------+"
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl OrderBook {
|
||||
// TODO
|
||||
// pub fn cancel(&mut self, pubkey: Pubkey) -> Result<(), Box<dyn error::Error>> {
|
||||
// Ok(())
|
||||
// }
|
||||
pub fn push(&mut self, pubkey: Pubkey, info: OrderInfo) -> Result<(), Box<dyn error::Error>> {
|
||||
check_trade(info.side, info.tokens, info.price)?;
|
||||
match info.side {
|
||||
OrderSide::Ask => {
|
||||
self.to_ab.push(ToOrder { pubkey, info });
|
||||
}
|
||||
OrderSide::Bid => {
|
||||
self.from_ab.push(FromOrder { pubkey, info });
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn pop(&mut self) -> Option<(ToOrder, FromOrder)> {
|
||||
if let Some(pair) = Self::pop_pair(&mut self.to_ab, &mut self.from_ab) {
|
||||
return Some(pair);
|
||||
}
|
||||
None
|
||||
}
|
||||
pub fn get_num_outstanding(&self) -> (usize, usize) {
|
||||
(self.to_ab.len(), self.from_ab.len())
|
||||
}
|
||||
|
||||
fn pop_pair(
|
||||
to_ab: &mut BinaryHeap<ToOrder>,
|
||||
from_ab: &mut BinaryHeap<FromOrder>,
|
||||
) -> Option<(ToOrder, FromOrder)> {
|
||||
let to = to_ab.peek()?;
|
||||
let from = from_ab.peek()?;
|
||||
if from.info.price < to.info.price {
|
||||
debug!("Trade not viable");
|
||||
return None;
|
||||
}
|
||||
let to = to_ab.pop()?;
|
||||
let from = from_ab.pop()?;
|
||||
Some((to, from))
|
||||
}
|
||||
}
|
103
bench-exchange/tests/bench_exchange.rs
Normal file
103
bench-exchange/tests/bench_exchange.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use log::*;
|
||||
use solana_bench_exchange::bench::{airdrop_lamports, do_bench_exchange, Config};
|
||||
use solana_core::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_core::validator::ValidatorConfig;
|
||||
use solana_exchange_program::exchange_processor::process_instruction;
|
||||
use solana_exchange_program::id;
|
||||
use solana_exchange_program::solana_exchange_program;
|
||||
use solana_faucet::faucet::run_local_faucet;
|
||||
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_runtime::bank_client::BankClient;
|
||||
use solana_sdk::genesis_config::create_genesis_config;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use std::process::exit;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_exchange_local_cluster() {
|
||||
solana_logger::setup();
|
||||
|
||||
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 {
|
||||
fund_amount,
|
||||
batch_size,
|
||||
account_groups,
|
||||
..
|
||||
} = config;
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
|
||||
let cluster = LocalCluster::new(&ClusterConfig {
|
||||
node_stakes: vec![100_000; NUM_NODES],
|
||||
cluster_lamports: 100_000_000_000_000,
|
||||
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
|
||||
native_instruction_processors: [solana_exchange_program!()].to_vec(),
|
||||
..ClusterConfig::default()
|
||||
});
|
||||
|
||||
let faucet_keypair = Keypair::new();
|
||||
cluster.transfer(
|
||||
&cluster.funding_keypair,
|
||||
&faucet_keypair.pubkey(),
|
||||
2_000_000_000_000,
|
||||
);
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
run_local_faucet(faucet_keypair, addr_sender, Some(1_000_000_000_000));
|
||||
let faucet_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
|
||||
info!("Connecting to the cluster");
|
||||
let (nodes, _) =
|
||||
discover_cluster(&cluster.entry_point_info.gossip, NUM_NODES).unwrap_or_else(|err| {
|
||||
error!("Failed to discover {} nodes: {:?}", NUM_NODES, err);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
let (client, num_clients) = get_multi_client(&nodes);
|
||||
|
||||
info!("clients: {}", num_clients);
|
||||
assert!(num_clients >= NUM_NODES);
|
||||
|
||||
const NUM_SIGNERS: u64 = 2;
|
||||
airdrop_lamports(
|
||||
&client,
|
||||
&faucet_addr,
|
||||
&config.identity,
|
||||
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
|
||||
);
|
||||
|
||||
do_bench_exchange(vec![client], config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exchange_bank_client() {
|
||||
solana_logger::setup();
|
||||
let (genesis_config, identity) = create_genesis_config(100_000_000_000_000);
|
||||
let mut bank = Bank::new(&genesis_config);
|
||||
bank.add_instruction_processor(id(), process_instruction);
|
||||
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);
|
||||
}
|
2
bench-streamer/.gitignore
vendored
Normal file
2
bench-streamer/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target/
|
||||
/farf/
|
15
bench-streamer/Cargo.toml
Normal file
15
bench-streamer/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "1.0.4"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.4" }
|
||||
solana-core = { path = "../core", version = "1.0.4" }
|
||||
solana-logger = { path = "../logger", version = "1.0.4" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.4" }
|
@ -1,38 +1,33 @@
|
||||
extern crate clap;
|
||||
extern crate solana;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use solana::netutil::bind_to;
|
||||
use solana::packet::{Packet, SharedPackets, BLOB_SIZE, PACKET_DATA_SIZE};
|
||||
use solana::result::Result;
|
||||
use solana::streamer::{receiver, PacketReceiver};
|
||||
use clap::{crate_description, crate_name, App, Arg};
|
||||
use solana_core::packet::{Packet, Packets, PacketsRecycler, PACKET_DATA_SIZE};
|
||||
use solana_core::streamer::{receiver, PacketReceiver};
|
||||
use std::cmp::max;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::Arc;
|
||||
use std::thread::sleep;
|
||||
use std::thread::{spawn, JoinHandle};
|
||||
use std::thread::{spawn, JoinHandle, Result};
|
||||
use std::time::Duration;
|
||||
use std::time::SystemTime;
|
||||
|
||||
fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
|
||||
let send = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
let msgs = SharedPackets::default();
|
||||
let msgs_ = msgs.clone();
|
||||
msgs.write().unwrap().packets.resize(10, Packet::default());
|
||||
for w in &mut msgs.write().unwrap().packets {
|
||||
let mut msgs = Packets::default();
|
||||
msgs.packets.resize(10, Packet::default());
|
||||
for w in msgs.packets.iter_mut() {
|
||||
w.meta.size = PACKET_DATA_SIZE;
|
||||
w.meta.set_addr(&addr);
|
||||
}
|
||||
let msgs = Arc::new(msgs);
|
||||
spawn(move || loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
let mut num = 0;
|
||||
for p in &msgs_.read().unwrap().packets {
|
||||
for p in &msgs.packets {
|
||||
let a = p.meta.addr();
|
||||
assert!(p.meta.size < BLOB_SIZE);
|
||||
assert!(p.meta.size < PACKET_DATA_SIZE);
|
||||
send.send_to(&p.data[..p.meta.size], &a).unwrap();
|
||||
num += 1;
|
||||
}
|
||||
@ -47,7 +42,7 @@ fn sink(exit: Arc<AtomicBool>, rvs: Arc<AtomicUsize>, r: PacketReceiver) -> Join
|
||||
}
|
||||
let timer = Duration::new(1, 0);
|
||||
if let Ok(msgs) = r.recv_timeout(timer) {
|
||||
rvs.fetch_add(msgs.read().unwrap().packets.len(), Ordering::Relaxed);
|
||||
rvs.fetch_add(msgs.packets.len(), Ordering::Relaxed);
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -55,28 +50,33 @@ fn sink(exit: Arc<AtomicBool>, rvs: Arc<AtomicUsize>, r: PacketReceiver) -> Join
|
||||
fn main() -> Result<()> {
|
||||
let mut num_sockets = 1usize;
|
||||
|
||||
let matches = App::new("solana-bench-streamer")
|
||||
let matches = App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(solana_clap_utils::version!())
|
||||
.arg(
|
||||
Arg::with_name("num-recv-sockets")
|
||||
.long("num-recv-sockets")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("Use NUM receive sockets"),
|
||||
).get_matches();
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if let Some(n) = matches.value_of("num-recv-sockets") {
|
||||
num_sockets = max(num_sockets, n.to_string().parse().expect("integer"));
|
||||
}
|
||||
|
||||
let mut port = 0;
|
||||
let mut addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0);
|
||||
let ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
||||
let mut addr = SocketAddr::new(ip_addr, 0);
|
||||
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let mut read_channels = Vec::new();
|
||||
let mut read_threads = Vec::new();
|
||||
let recycler = PacketsRecycler::default();
|
||||
for _ in 0..num_sockets {
|
||||
let read = bind_to(port, false).unwrap();
|
||||
let read = solana_net_utils::bind_to(ip_addr, port, false).unwrap();
|
||||
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
|
||||
|
||||
addr = read.local_addr().unwrap();
|
||||
@ -86,9 +86,10 @@ fn main() -> Result<()> {
|
||||
read_channels.push(r_reader);
|
||||
read_threads.push(receiver(
|
||||
Arc::new(read),
|
||||
exit.clone(),
|
||||
&exit,
|
||||
s_reader,
|
||||
"bench-streamer",
|
||||
recycler.clone(),
|
||||
"bench-streamer-test",
|
||||
));
|
||||
}
|
||||
|
4
bench-tps/.gitignore
vendored
Normal file
4
bench-tps/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
37
bench-tps/Cargo.toml
Normal file
37
bench-tps/Cargo.toml
Normal file
@ -0,0 +1,37 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "1.0.4"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.2.1"
|
||||
clap = "2.33.0"
|
||||
log = "0.4.8"
|
||||
rayon = "1.2.0"
|
||||
serde_json = "1.0.46"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.4" }
|
||||
solana-core = { path = "../core", version = "1.0.4" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.4" }
|
||||
solana-client = { path = "../client", version = "1.0.4" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.4" }
|
||||
solana-librapay = { path = "../programs/librapay", version = "1.0.4", optional = true }
|
||||
solana-logger = { path = "../logger", version = "1.0.4" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.4" }
|
||||
solana-measure = { path = "../measure", version = "1.0.4" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.4" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.4" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.4" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader", version = "1.0.4", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.3.2"
|
||||
serial_test_derive = "0.4.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.4" }
|
||||
|
||||
[features]
|
||||
move = ["solana-librapay", "solana-move-loader-program"]
|
1199
bench-tps/src/bench.rs
Normal file
1199
bench-tps/src/bench.rs
Normal file
File diff suppressed because it is too large
Load Diff
263
bench-tps/src/cli.rs
Normal file
263
bench-tps/src/cli.rs
Normal file
@ -0,0 +1,263 @@
|
||||
use clap::{crate_description, crate_name, App, Arg, ArgMatches};
|
||||
use solana_faucet::faucet::FAUCET_PORT;
|
||||
use solana_sdk::fee_calculator::FeeRateGovernor;
|
||||
use solana_sdk::signature::{read_keypair_file, Keypair};
|
||||
use std::{net::SocketAddr, process::exit, time::Duration};
|
||||
|
||||
const NUM_LAMPORTS_PER_ACCOUNT_DEFAULT: u64 = solana_sdk::native_token::LAMPORTS_PER_SOL;
|
||||
|
||||
/// Holds the configuration for a single run of the benchmark
|
||||
pub struct Config {
|
||||
pub entrypoint_addr: SocketAddr,
|
||||
pub faucet_addr: SocketAddr,
|
||||
pub id: Keypair,
|
||||
pub threads: usize,
|
||||
pub num_nodes: usize,
|
||||
pub duration: Duration,
|
||||
pub tx_count: usize,
|
||||
pub keypair_multiplier: usize,
|
||||
pub thread_batch_sleep_ms: usize,
|
||||
pub sustained: bool,
|
||||
pub client_ids_and_stake_file: String,
|
||||
pub write_to_client_file: bool,
|
||||
pub read_from_client_file: bool,
|
||||
pub target_lamports_per_signature: u64,
|
||||
pub multi_client: bool,
|
||||
pub use_move: bool,
|
||||
pub num_lamports_per_account: u64,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
|
||||
faucet_addr: SocketAddr::from(([127, 0, 0, 1], FAUCET_PORT)),
|
||||
id: Keypair::new(),
|
||||
threads: 4,
|
||||
num_nodes: 1,
|
||||
duration: Duration::new(std::u64::MAX, 0),
|
||||
tx_count: 50_000,
|
||||
keypair_multiplier: 8,
|
||||
thread_batch_sleep_ms: 1000,
|
||||
sustained: false,
|
||||
client_ids_and_stake_file: String::new(),
|
||||
write_to_client_file: false,
|
||||
read_from_client_file: false,
|
||||
target_lamports_per_signature: FeeRateGovernor::default().target_lamports_per_signature,
|
||||
multi_client: true,
|
||||
use_move: false,
|
||||
num_lamports_per_account: NUM_LAMPORTS_PER_ACCOUNT_DEFAULT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines and builds the CLI args for a run of the benchmark
|
||||
pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
|
||||
App::new(crate_name!()).about(crate_description!())
|
||||
.version(version)
|
||||
.arg(
|
||||
Arg::with_name("entrypoint")
|
||||
.short("n")
|
||||
.long("entrypoint")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.help("Rendezvous with the cluster at this entry point; defaults to 127.0.0.1:8001"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("faucet")
|
||||
.short("d")
|
||||
.long("faucet")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.help("Location of the faucet; defaults to entrypoint:FAUCET_PORT"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("identity")
|
||||
.short("i")
|
||||
.long("identity")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.help("File containing a client identity (keypair)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num-nodes")
|
||||
.short("N")
|
||||
.long("num-nodes")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("Wait for NUM nodes to converge"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("threads")
|
||||
.short("t")
|
||||
.long("threads")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("Number of threads"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("duration")
|
||||
.long("duration")
|
||||
.value_name("SECS")
|
||||
.takes_value(true)
|
||||
.help("Seconds to run benchmark, then exit; default is forever"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("sustained")
|
||||
.long("sustained")
|
||||
.help("Use sustained performance mode vs. peak mode. This overlaps the tx generation with transfers."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("use-move")
|
||||
.long("use-move")
|
||||
.help("Use Move language transactions to perform transfers."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-multi-client")
|
||||
.long("no-multi-client")
|
||||
.help("Disable multi-client support, only transact with the entrypoint."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("tx_count")
|
||||
.long("tx_count")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("Number of transactions to send per batch")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("keypair_multiplier")
|
||||
.long("keypair-multiplier")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("Multiply by transaction count to determine number of keypairs to create")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("thread-batch-sleep-ms")
|
||||
.short("z")
|
||||
.long("thread-batch-sleep-ms")
|
||||
.value_name("NUM")
|
||||
.takes_value(true)
|
||||
.help("Per-thread-per-iteration sleep in ms"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("write-client-keys")
|
||||
.long("write-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Generate client keys and stakes and write the list to YAML file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("read-client-keys")
|
||||
.long("read-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Read client keys and stakes from the YAML file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("target_lamports_per_signature")
|
||||
.long("target-lamports-per-signature")
|
||||
.value_name("LAMPORTS")
|
||||
.takes_value(true)
|
||||
.help(
|
||||
"The cost in lamports that the cluster will charge for signature \
|
||||
verification when the cluster is operating at target-signatures-per-slot",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num_lamports_per_account")
|
||||
.long("num-lamports-per-account")
|
||||
.value_name("LAMPORTS")
|
||||
.takes_value(true)
|
||||
.help(
|
||||
"Number of lamports per account.",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// Parses a clap `ArgMatches` structure into a `Config`
|
||||
/// # Arguments
|
||||
/// * `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 {
|
||||
let mut args = Config::default();
|
||||
|
||||
if let Some(addr) = matches.value_of("entrypoint") {
|
||||
args.entrypoint_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse entrypoint address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(addr) = matches.value_of("faucet") {
|
||||
args.faucet_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse faucet address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
}
|
||||
|
||||
if matches.is_present("identity") {
|
||||
args.id = read_keypair_file(matches.value_of("identity").unwrap())
|
||||
.expect("can't read client identity");
|
||||
}
|
||||
|
||||
if let Some(t) = matches.value_of("threads") {
|
||||
args.threads = t.to_string().parse().expect("can't parse threads");
|
||||
}
|
||||
|
||||
if let Some(n) = matches.value_of("num-nodes") {
|
||||
args.num_nodes = n.to_string().parse().expect("can't parse num-nodes");
|
||||
}
|
||||
|
||||
if let Some(duration) = matches.value_of("duration") {
|
||||
args.duration = Duration::new(
|
||||
duration.to_string().parse().expect("can't parse duration"),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(s) = matches.value_of("tx_count") {
|
||||
args.tx_count = s.to_string().parse().expect("can't parse tx_count");
|
||||
}
|
||||
|
||||
if let Some(s) = matches.value_of("keypair_multiplier") {
|
||||
args.keypair_multiplier = s
|
||||
.to_string()
|
||||
.parse()
|
||||
.expect("can't parse keypair-multiplier");
|
||||
assert!(args.keypair_multiplier >= 2);
|
||||
}
|
||||
|
||||
if let Some(t) = matches.value_of("thread-batch-sleep-ms") {
|
||||
args.thread_batch_sleep_ms = t
|
||||
.to_string()
|
||||
.parse()
|
||||
.expect("can't parse thread-batch-sleep-ms");
|
||||
}
|
||||
|
||||
args.sustained = matches.is_present("sustained");
|
||||
|
||||
if let Some(s) = matches.value_of("write-client-keys") {
|
||||
args.write_to_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
|
||||
if let Some(s) = matches.value_of("read-client-keys") {
|
||||
assert!(!args.write_to_client_file);
|
||||
args.read_from_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
|
||||
if let Some(v) = matches.value_of("target_lamports_per_signature") {
|
||||
args.target_lamports_per_signature = v.to_string().parse().expect("can't parse lamports");
|
||||
}
|
||||
|
||||
args.use_move = matches.is_present("use-move");
|
||||
args.multi_client = !matches.is_present("no-multi-client");
|
||||
|
||||
if let Some(v) = matches.value_of("num_lamports_per_account") {
|
||||
args.num_lamports_per_account = v.to_string().parse().expect("can't parse lamports");
|
||||
}
|
||||
|
||||
args
|
||||
}
|
2
bench-tps/src/lib.rs
Normal file
2
bench-tps/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod bench;
|
||||
pub mod cli;
|
137
bench-tps/src/main.rs
Normal file
137
bench-tps/src/main.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use log::*;
|
||||
use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs, generate_keypairs};
|
||||
use solana_bench_tps::cli;
|
||||
use solana_core::gossip_service::{discover_cluster, get_client, get_multi_client};
|
||||
use solana_genesis::Base64Account;
|
||||
use solana_sdk::fee_calculator::FeeRateGovernor;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::system_program;
|
||||
use std::{collections::HashMap, fs::File, io::prelude::*, path::Path, process::exit, sync::Arc};
|
||||
|
||||
/// Number of signatures for all transactions in ~1 week at ~100K TPS
|
||||
pub const NUM_SIGNATURES_FOR_TXS: u64 = 100_000 * 60 * 60 * 24 * 7;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup_with_default("solana=info");
|
||||
solana_metrics::set_panic_hook("bench-tps");
|
||||
|
||||
let matches = cli::build_args(solana_clap_utils::version!()).get_matches();
|
||||
let cli_config = cli::extract_args(&matches);
|
||||
|
||||
let cli::Config {
|
||||
entrypoint_addr,
|
||||
faucet_addr,
|
||||
id,
|
||||
num_nodes,
|
||||
tx_count,
|
||||
keypair_multiplier,
|
||||
client_ids_and_stake_file,
|
||||
write_to_client_file,
|
||||
read_from_client_file,
|
||||
target_lamports_per_signature,
|
||||
use_move,
|
||||
multi_client,
|
||||
num_lamports_per_account,
|
||||
..
|
||||
} = &cli_config;
|
||||
|
||||
let keypair_count = *tx_count * keypair_multiplier;
|
||||
if *write_to_client_file {
|
||||
info!("Generating {} keypairs", keypair_count);
|
||||
let (keypairs, _) = generate_keypairs(&id, keypair_count as u64);
|
||||
let num_accounts = keypairs.len() as u64;
|
||||
let max_fee =
|
||||
FeeRateGovernor::new(*target_lamports_per_signature, 0).max_lamports_per_signature;
|
||||
let num_lamports_per_account = (num_accounts - 1 + NUM_SIGNATURES_FOR_TXS * max_fee)
|
||||
/ num_accounts
|
||||
+ num_lamports_per_account;
|
||||
let mut accounts = HashMap::new();
|
||||
keypairs.iter().for_each(|keypair| {
|
||||
accounts.insert(
|
||||
serde_json::to_string(&keypair.to_bytes().to_vec()).unwrap(),
|
||||
Base64Account {
|
||||
balance: num_lamports_per_account,
|
||||
executable: false,
|
||||
owner: system_program::id().to_string(),
|
||||
data: String::new(),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
info!("Writing {}", client_ids_and_stake_file);
|
||||
let serialized = serde_yaml::to_string(&accounts).unwrap();
|
||||
let path = Path::new(&client_ids_and_stake_file);
|
||||
let mut file = File::create(path).unwrap();
|
||||
file.write_all(&serialized.into_bytes()).unwrap();
|
||||
return;
|
||||
}
|
||||
|
||||
info!("Connecting to the cluster");
|
||||
let (nodes, _archivers) =
|
||||
discover_cluster(&entrypoint_addr, *num_nodes).unwrap_or_else(|err| {
|
||||
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
let client = if *multi_client {
|
||||
let (client, num_clients) = get_multi_client(&nodes);
|
||||
if nodes.len() < num_clients {
|
||||
eprintln!(
|
||||
"Error: Insufficient nodes discovered. Expecting {} or more",
|
||||
num_nodes
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
Arc::new(client)
|
||||
} else {
|
||||
Arc::new(get_client(&nodes))
|
||||
};
|
||||
|
||||
let (keypairs, move_keypairs) = if *read_from_client_file && !use_move {
|
||||
let path = Path::new(&client_ids_and_stake_file);
|
||||
let file = File::open(path).unwrap();
|
||||
|
||||
info!("Reading {}", client_ids_and_stake_file);
|
||||
let accounts: HashMap<String, Base64Account> = serde_yaml::from_reader(file).unwrap();
|
||||
let mut keypairs = vec![];
|
||||
let mut last_balance = 0;
|
||||
|
||||
accounts
|
||||
.into_iter()
|
||||
.for_each(|(keypair, primordial_account)| {
|
||||
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
|
||||
keypairs.push(Keypair::from_bytes(&bytes).unwrap());
|
||||
last_balance = primordial_account.balance;
|
||||
});
|
||||
|
||||
if keypairs.len() < keypair_count {
|
||||
eprintln!(
|
||||
"Expected {} accounts in {}, only received {} (--tx_count mismatch?)",
|
||||
keypair_count,
|
||||
client_ids_and_stake_file,
|
||||
keypairs.len(),
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
// Sort keypairs so that do_bench_tps() uses the same subset of accounts for each run.
|
||||
// This prevents the amount of storage needed for bench-tps accounts from creeping up
|
||||
// across multiple runs.
|
||||
keypairs.sort_by(|x, y| x.pubkey().to_string().cmp(&y.pubkey().to_string()));
|
||||
(keypairs, None)
|
||||
} else {
|
||||
generate_and_fund_keypairs(
|
||||
client.clone(),
|
||||
Some(*faucet_addr),
|
||||
&id,
|
||||
keypair_count,
|
||||
*num_lamports_per_account,
|
||||
*use_move,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("Error could not fund keys: {:?}", e);
|
||||
exit(1);
|
||||
})
|
||||
};
|
||||
|
||||
do_bench_tps(client, cli_config, keypairs, move_keypairs);
|
||||
}
|
86
bench-tps/tests/bench_tps.rs
Normal file
86
bench-tps/tests/bench_tps.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use serial_test_derive::serial;
|
||||
use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs};
|
||||
use solana_bench_tps::cli::Config;
|
||||
use solana_client::thin_client::create_client;
|
||||
use solana_core::cluster_info::VALIDATOR_PORT_RANGE;
|
||||
use solana_core::validator::ValidatorConfig;
|
||||
use solana_faucet::faucet::run_local_faucet;
|
||||
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
|
||||
#[cfg(feature = "move")]
|
||||
use solana_sdk::move_loader::solana_move_loader_program;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use std::sync::{mpsc::channel, Arc};
|
||||
use std::time::Duration;
|
||||
|
||||
fn test_bench_tps_local_cluster(config: Config) {
|
||||
#[cfg(feature = "move")]
|
||||
let native_instruction_processors = vec![solana_move_loader_program()];
|
||||
|
||||
#[cfg(not(feature = "move"))]
|
||||
let native_instruction_processors = vec![];
|
||||
|
||||
solana_logger::setup();
|
||||
const NUM_NODES: usize = 1;
|
||||
let cluster = LocalCluster::new(&ClusterConfig {
|
||||
node_stakes: vec![999_990; NUM_NODES],
|
||||
cluster_lamports: 200_000_000,
|
||||
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
|
||||
native_instruction_processors,
|
||||
..ClusterConfig::default()
|
||||
});
|
||||
|
||||
let faucet_keypair = Keypair::new();
|
||||
cluster.transfer(
|
||||
&cluster.funding_keypair,
|
||||
&faucet_keypair.pubkey(),
|
||||
100_000_000,
|
||||
);
|
||||
|
||||
let client = Arc::new(create_client(
|
||||
(cluster.entry_point_info.rpc, cluster.entry_point_info.tpu),
|
||||
VALIDATOR_PORT_RANGE,
|
||||
));
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
run_local_faucet(faucet_keypair, addr_sender, None);
|
||||
let faucet_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
|
||||
let lamports_per_account = 100;
|
||||
|
||||
let keypair_count = config.tx_count * config.keypair_multiplier;
|
||||
let (keypairs, move_keypairs) = generate_and_fund_keypairs(
|
||||
client.clone(),
|
||||
Some(faucet_addr),
|
||||
&config.id,
|
||||
keypair_count,
|
||||
lamports_per_account,
|
||||
config.use_move,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _total = do_bench_tps(client, config, keypairs, move_keypairs);
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
assert!(_total > 100);
|
||||
}
|
||||
|
||||
#[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]
|
||||
#[serial]
|
||||
fn test_bench_tps_local_cluster_move() {
|
||||
let mut config = Config::default();
|
||||
config.tx_count = 100;
|
||||
config.duration = Duration::from_secs(10);
|
||||
config.use_move = true;
|
||||
|
||||
test_bench_tps_local_cluster(config);
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
#![feature(test)]
|
||||
extern crate bincode;
|
||||
extern crate rayon;
|
||||
extern crate solana;
|
||||
extern crate test;
|
||||
|
||||
use bincode::serialize;
|
||||
use rayon::prelude::*;
|
||||
use solana::bank::*;
|
||||
use solana::hash::hash;
|
||||
use solana::mint::Mint;
|
||||
use solana::signature::{Keypair, KeypairUtil};
|
||||
use solana::system_transaction::SystemTransaction;
|
||||
use solana::transaction::Transaction;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_process_transaction(bencher: &mut Bencher) {
|
||||
let mint = Mint::new(100_000_000);
|
||||
let bank = Bank::new(&mint);
|
||||
|
||||
// Create transactions between unrelated parties.
|
||||
let transactions: Vec<_> = (0..4096)
|
||||
.into_par_iter()
|
||||
.map(|i| {
|
||||
// Seed the 'from' account.
|
||||
let rando0 = Keypair::new();
|
||||
let tx = Transaction::system_move(
|
||||
&mint.keypair(),
|
||||
rando0.pubkey(),
|
||||
10_000,
|
||||
mint.last_id(),
|
||||
0,
|
||||
);
|
||||
assert!(bank.process_transaction(&tx).is_ok());
|
||||
|
||||
// Seed the 'to' account and a cell for its signature.
|
||||
let last_id = hash(&serialize(&i).unwrap()); // Unique hash
|
||||
bank.register_entry_id(&last_id);
|
||||
|
||||
let rando1 = Keypair::new();
|
||||
let tx = Transaction::system_move(&rando0, rando1.pubkey(), 1, last_id, 0);
|
||||
assert!(bank.process_transaction(&tx).is_ok());
|
||||
|
||||
// Finally, return the transaction to the benchmark.
|
||||
tx
|
||||
}).collect();
|
||||
|
||||
bencher.iter(|| {
|
||||
// Since benchmarker runs this multiple times, we need to clear the signatures.
|
||||
bank.clear_signatures();
|
||||
let results = bank.process_transactions(&transactions);
|
||||
assert!(results.iter().all(Result::is_ok));
|
||||
})
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
#![feature(test)]
|
||||
extern crate bincode;
|
||||
extern crate rand;
|
||||
extern crate rayon;
|
||||
extern crate solana;
|
||||
extern crate solana_program_interface;
|
||||
extern crate test;
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana::bank::Bank;
|
||||
use solana::banking_stage::{BankingStage, NUM_THREADS};
|
||||
use solana::entry::Entry;
|
||||
use solana::mint::Mint;
|
||||
use solana::packet::to_packets_chunked;
|
||||
use solana::signature::{KeypairUtil, Signature};
|
||||
use solana::system_transaction::SystemTransaction;
|
||||
use solana::transaction::Transaction;
|
||||
use solana_program_interface::pubkey::Pubkey;
|
||||
use std::iter;
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use test::Bencher;
|
||||
|
||||
fn check_txs(receiver: &Receiver<Vec<Entry>>, ref_tx_count: usize) {
|
||||
let mut total = 0;
|
||||
loop {
|
||||
let entries = receiver.recv_timeout(Duration::new(1, 0));
|
||||
if let Ok(entries) = entries {
|
||||
for entry in &entries {
|
||||
total += entry.transactions.len();
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if total >= ref_tx_count {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_eq!(total, ref_tx_count);
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
|
||||
let txes = 1000 * NUM_THREADS;
|
||||
let mint_total = 1_000_000_000_000;
|
||||
let mint = Mint::new(mint_total);
|
||||
|
||||
let (verified_sender, verified_receiver) = channel();
|
||||
let bank = Arc::new(Bank::new(&mint));
|
||||
let dummy = Transaction::system_move(
|
||||
&mint.keypair(),
|
||||
mint.keypair().pubkey(),
|
||||
1,
|
||||
mint.last_id(),
|
||||
0,
|
||||
);
|
||||
let transactions: Vec<_> = (0..txes)
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let mut new = dummy.clone();
|
||||
let from: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
let to: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
new.keys[0] = Pubkey::new(&from[0..32]);
|
||||
new.keys[1] = Pubkey::new(&to[0..32]);
|
||||
new.signature = Signature::new(&sig[0..64]);
|
||||
new
|
||||
}).collect();
|
||||
// fund all the accounts
|
||||
transactions.iter().for_each(|tx| {
|
||||
let fund = Transaction::system_move(
|
||||
&mint.keypair(),
|
||||
tx.keys[0],
|
||||
mint_total / txes as i64,
|
||||
mint.last_id(),
|
||||
0,
|
||||
);
|
||||
assert!(bank.process_transaction(&fund).is_ok());
|
||||
});
|
||||
//sanity check, make sure all the transactions can execute sequentially
|
||||
transactions.iter().for_each(|tx| {
|
||||
let res = bank.process_transaction(&tx);
|
||||
assert!(res.is_ok(), "sanity test transactions");
|
||||
});
|
||||
bank.clear_signatures();
|
||||
//sanity check, make sure all the transactions can execute in parallel
|
||||
let res = bank.process_transactions(&transactions);
|
||||
for r in res {
|
||||
assert!(r.is_ok(), "sanity parallel execution");
|
||||
}
|
||||
bank.clear_signatures();
|
||||
let verified: Vec<_> = to_packets_chunked(&transactions.clone(), 192)
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let len = x.read().unwrap().packets.len();
|
||||
(x, iter::repeat(1).take(len).collect())
|
||||
}).collect();
|
||||
let (_stage, signal_receiver) = BankingStage::new(&bank, verified_receiver, Default::default());
|
||||
bencher.iter(move || {
|
||||
for v in verified.chunks(verified.len() / NUM_THREADS) {
|
||||
verified_sender.send(v.to_vec()).unwrap();
|
||||
}
|
||||
check_txs(&signal_receiver, txes);
|
||||
bank.clear_signatures();
|
||||
// make sure the tx last id is still registered
|
||||
bank.register_entry_id(&mint.last_id());
|
||||
});
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
#![feature(test)]
|
||||
extern crate solana;
|
||||
extern crate test;
|
||||
|
||||
use solana::hash::{hash, Hash};
|
||||
use solana::ledger::{next_entries, reconstruct_entries_from_blobs, Block};
|
||||
use solana::signature::{Keypair, KeypairUtil};
|
||||
use solana::system_transaction::SystemTransaction;
|
||||
use solana::transaction::Transaction;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_block_to_blobs_to_block(bencher: &mut Bencher) {
|
||||
let zero = Hash::default();
|
||||
let one = hash(&zero.as_ref());
|
||||
let keypair = Keypair::new();
|
||||
let tx0 = Transaction::system_move(&keypair, keypair.pubkey(), 1, one, 0);
|
||||
let transactions = vec![tx0; 10];
|
||||
let entries = next_entries(&zero, 1, transactions);
|
||||
|
||||
bencher.iter(|| {
|
||||
let blobs = entries.to_blobs();
|
||||
assert_eq!(reconstruct_entries_from_blobs(blobs).unwrap(), entries);
|
||||
});
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
#![feature(test)]
|
||||
extern crate bincode;
|
||||
extern crate rayon;
|
||||
extern crate solana;
|
||||
extern crate test;
|
||||
|
||||
use solana::packet::to_packets;
|
||||
use solana::sigverify;
|
||||
use solana::system_transaction::test_tx;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_sigverify(bencher: &mut Bencher) {
|
||||
let tx = test_tx();
|
||||
|
||||
// generate packet vector
|
||||
let batches = to_packets(&vec![tx; 128]);
|
||||
|
||||
// verify packets
|
||||
bencher.iter(|| {
|
||||
let _ans = sigverify::ed25519_verify(&batches);
|
||||
})
|
||||
}
|
34
build.rs
34
build.rs
@ -1,34 +0,0 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=target/perf-libs");
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// Ensure target/perf-libs/ exists. It's been observed that
|
||||
// a cargo:rerun-if-changed= directive with a non-existent
|
||||
// directory triggers a rebuild on every |cargo build| invocation
|
||||
fs::create_dir("target/perf-libs").unwrap_or_else(|err| {
|
||||
if err.kind() != std::io::ErrorKind::AlreadyExists {
|
||||
panic!("Unable to create target/perf-libs: {:?}", err);
|
||||
}
|
||||
});
|
||||
|
||||
let cuda = !env::var("CARGO_FEATURE_CUDA").is_err();
|
||||
let erasure = !env::var("CARGO_FEATURE_ERASURE").is_err();
|
||||
|
||||
if cuda || erasure {
|
||||
println!("cargo:rustc-link-search=native=target/perf-libs");
|
||||
}
|
||||
if cuda {
|
||||
println!("cargo:rustc-link-lib=static=cuda_verify_ed25519");
|
||||
println!("cargo:rustc-link-search=native=/usr/local/cuda/lib64");
|
||||
println!("cargo:rustc-link-lib=dylib=cudart");
|
||||
println!("cargo:rustc-link-lib=dylib=cuda");
|
||||
println!("cargo:rustc-link-lib=dylib=cudadevrt");
|
||||
}
|
||||
if erasure {
|
||||
println!("cargo:rustc-link-lib=dylib=Jerasure");
|
||||
println!("cargo:rustc-link-lib=dylib=gf_complete");
|
||||
}
|
||||
}
|
24
chacha-cuda/Cargo.toml
Normal file
24
chacha-cuda/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "solana-chacha-cuda"
|
||||
version = "1.0.4"
|
||||
description = "Solana Chacha Cuda APIs"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.4" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.4" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.4" }
|
||||
solana-logger = { path = "../logger", version = "1.0.4" }
|
||||
solana-perf = { path = "../perf", version = "1.0.4" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.4" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2.1"
|
||||
|
||||
[lib]
|
||||
name = "solana_chacha_cuda"
|
280
chacha-cuda/src/chacha_cuda.rs
Normal file
280
chacha-cuda/src/chacha_cuda.rs
Normal file
@ -0,0 +1,280 @@
|
||||
// Module used by validators to approve storage mining proofs in parallel using the GPU
|
||||
|
||||
use solana_chacha::chacha::{CHACHA_BLOCK_SIZE, CHACHA_KEY_SIZE};
|
||||
use solana_ledger::blockstore::Blockstore;
|
||||
use solana_perf::perf_libs;
|
||||
use solana_sdk::hash::Hash;
|
||||
use std::io;
|
||||
use std::mem::size_of;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Encrypt a file with multiple starting IV states, determined by ivecs.len()
|
||||
//
|
||||
// Then sample each block at the offsets provided by samples argument with sha256
|
||||
// and return the vec of sha states
|
||||
pub fn chacha_cbc_encrypt_file_many_keys(
|
||||
blockstore: &Arc<Blockstore>,
|
||||
segment: u64,
|
||||
slots_per_segment: u64,
|
||||
ivecs: &mut [u8],
|
||||
samples: &[u64],
|
||||
) -> io::Result<Vec<Hash>> {
|
||||
let api = perf_libs::api().expect("no perf libs");
|
||||
if ivecs.len() % CHACHA_BLOCK_SIZE != 0 {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"bad IV length({}) not divisible by {} ",
|
||||
ivecs.len(),
|
||||
CHACHA_BLOCK_SIZE,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
const BUFFER_SIZE: usize = 8 * 1024;
|
||||
let mut buffer = [0; BUFFER_SIZE];
|
||||
let num_keys = ivecs.len() / CHACHA_BLOCK_SIZE;
|
||||
let mut sha_states = vec![0; num_keys * size_of::<Hash>()];
|
||||
let mut int_sha_states = vec![0; num_keys * 112];
|
||||
let keys: Vec<u8> = vec![0; num_keys * CHACHA_KEY_SIZE]; // keys not used ATM, uniqueness comes from IV
|
||||
let mut current_slot = segment * slots_per_segment;
|
||||
let mut start_index = 0;
|
||||
let start_slot = current_slot;
|
||||
let mut total_size = 0;
|
||||
let mut time: f32 = 0.0;
|
||||
unsafe {
|
||||
(api.chacha_init_sha_state)(int_sha_states.as_mut_ptr(), num_keys as u32);
|
||||
}
|
||||
loop {
|
||||
match blockstore.get_data_shreds(current_slot, start_index, std::u64::MAX, &mut buffer) {
|
||||
Ok((last_index, mut size)) => {
|
||||
debug!(
|
||||
"chacha_cuda: encrypting segment: {} num_shreds: {} data_len: {}",
|
||||
segment,
|
||||
last_index.saturating_sub(start_index),
|
||||
size
|
||||
);
|
||||
|
||||
if size == 0 {
|
||||
if current_slot.saturating_sub(start_slot) < slots_per_segment {
|
||||
current_slot += 1;
|
||||
start_index = 0;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if size < BUFFER_SIZE {
|
||||
// round to the nearest key_size boundary
|
||||
size = (size + CHACHA_KEY_SIZE - 1) & !(CHACHA_KEY_SIZE - 1);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(api.chacha_cbc_encrypt_many_sample)(
|
||||
buffer[..size].as_ptr(),
|
||||
int_sha_states.as_mut_ptr(),
|
||||
size,
|
||||
keys.as_ptr(),
|
||||
ivecs.as_mut_ptr(),
|
||||
num_keys as u32,
|
||||
samples.as_ptr(),
|
||||
samples.len() as u32,
|
||||
total_size,
|
||||
&mut time,
|
||||
);
|
||||
}
|
||||
|
||||
total_size += size as u64;
|
||||
start_index = last_index + 1;
|
||||
}
|
||||
Err(e) => {
|
||||
info!("Error encrypting file: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
(api.chacha_end_sha_state)(
|
||||
int_sha_states.as_ptr(),
|
||||
sha_states.as_mut_ptr(),
|
||||
num_keys as u32,
|
||||
);
|
||||
}
|
||||
let mut res = Vec::new();
|
||||
for x in 0..num_keys {
|
||||
let start = x * size_of::<Hash>();
|
||||
let end = start + size_of::<Hash>();
|
||||
res.push(Hash::new(&sha_states[start..end]));
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_archiver_utils::sample_file;
|
||||
use solana_chacha::chacha::chacha_cbc_encrypt_ledger;
|
||||
use solana_ledger::entry::create_ticks;
|
||||
use solana_ledger::get_tmp_ledger_path;
|
||||
use solana_sdk::clock::DEFAULT_SLOTS_PER_SEGMENT;
|
||||
use solana_sdk::signature::Keypair;
|
||||
use std::fs::{remove_dir_all, remove_file};
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_file_many_keys_single() {
|
||||
solana_logger::setup();
|
||||
if perf_libs::api().is_none() {
|
||||
info!("perf-libs unavailable, skipped");
|
||||
return;
|
||||
}
|
||||
|
||||
let slots_per_segment = 32;
|
||||
let entries = create_ticks(slots_per_segment, 0, Hash::default());
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let ticks_per_slot = 16;
|
||||
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||
|
||||
blockstore
|
||||
.write_entries(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
ticks_per_slot,
|
||||
Some(0),
|
||||
true,
|
||||
&Arc::new(Keypair::new()),
|
||||
entries,
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let out_path = Path::new("test_chacha_encrypt_file_many_keys_single_output.txt.enc");
|
||||
|
||||
let samples = [0];
|
||||
let mut ivecs = hex!(
|
||||
"abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234
|
||||
abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234"
|
||||
);
|
||||
|
||||
let mut cpu_iv = ivecs.clone();
|
||||
chacha_cbc_encrypt_ledger(
|
||||
&blockstore,
|
||||
0,
|
||||
slots_per_segment as u64,
|
||||
out_path,
|
||||
&mut cpu_iv,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ref_hash = sample_file(&out_path, &samples).unwrap();
|
||||
|
||||
let hashes = chacha_cbc_encrypt_file_many_keys(
|
||||
&blockstore,
|
||||
0,
|
||||
slots_per_segment as u64,
|
||||
&mut ivecs,
|
||||
&samples,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hashes[0], ref_hash);
|
||||
|
||||
let _ignored = remove_dir_all(&ledger_path);
|
||||
let _ignored = remove_file(out_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_file_many_keys_multiple_keys() {
|
||||
solana_logger::setup();
|
||||
if perf_libs::api().is_none() {
|
||||
info!("perf-libs unavailable, skipped");
|
||||
return;
|
||||
}
|
||||
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let ticks_per_slot = 90;
|
||||
let entries = create_ticks(2 * ticks_per_slot, 0, Hash::default());
|
||||
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||
blockstore
|
||||
.write_entries(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
ticks_per_slot,
|
||||
Some(0),
|
||||
true,
|
||||
&Arc::new(Keypair::new()),
|
||||
entries,
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let out_path = Path::new("test_chacha_encrypt_file_many_keys_multiple_output.txt.enc");
|
||||
|
||||
let samples = [0, 1, 3, 4, 5, 150];
|
||||
let mut ivecs = Vec::new();
|
||||
let mut ref_hashes: Vec<Hash> = vec![];
|
||||
for i in 0..2 {
|
||||
let mut ivec = hex!(
|
||||
"abc123abc123abc123abc123abc123abc123abababababababababababababab
|
||||
abc123abc123abc123abc123abc123abc123abababababababababababababab"
|
||||
);
|
||||
ivec[0] = i;
|
||||
ivecs.extend(ivec.clone().iter());
|
||||
chacha_cbc_encrypt_ledger(
|
||||
&blockstore.clone(),
|
||||
0,
|
||||
DEFAULT_SLOTS_PER_SEGMENT,
|
||||
out_path,
|
||||
&mut ivec,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
ref_hashes.push(sample_file(&out_path, &samples).unwrap());
|
||||
info!(
|
||||
"ivec: {:?} hash: {:?} ivecs: {:?}",
|
||||
ivec.to_vec(),
|
||||
ref_hashes.last(),
|
||||
ivecs
|
||||
);
|
||||
}
|
||||
|
||||
let hashes = chacha_cbc_encrypt_file_many_keys(
|
||||
&blockstore,
|
||||
0,
|
||||
DEFAULT_SLOTS_PER_SEGMENT,
|
||||
&mut ivecs,
|
||||
&samples,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hashes, ref_hashes);
|
||||
|
||||
let _ignored = remove_dir_all(&ledger_path);
|
||||
let _ignored = remove_file(out_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_file_many_keys_bad_key_length() {
|
||||
solana_logger::setup();
|
||||
if perf_libs::api().is_none() {
|
||||
info!("perf-libs unavailable, skipped");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut keys = hex!("abc123");
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let samples = [0];
|
||||
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||
assert!(chacha_cbc_encrypt_file_many_keys(
|
||||
&blockstore,
|
||||
0,
|
||||
DEFAULT_SLOTS_PER_SEGMENT,
|
||||
&mut keys,
|
||||
&samples,
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
}
|
8
chacha-cuda/src/lib.rs
Normal file
8
chacha-cuda/src/lib.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate hex_literal;
|
||||
|
||||
pub mod chacha_cuda;
|
2
chacha-sys/.gitignore
vendored
Normal file
2
chacha-sys/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target/
|
||||
/farf/
|
12
chacha-sys/Cargo.toml
Normal file
12
chacha-sys/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "solana-chacha-sys"
|
||||
version = "1.0.4"
|
||||
description = "Solana chacha-sys"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.49"
|
8
chacha-sys/build.rs
Normal file
8
chacha-sys/build.rs
Normal file
@ -0,0 +1,8 @@
|
||||
extern crate cc;
|
||||
|
||||
fn main() {
|
||||
cc::Build::new()
|
||||
.file("cpu-crypt/chacha20_core.c")
|
||||
.file("cpu-crypt/chacha_cbc.c")
|
||||
.compile("libcpu-crypt");
|
||||
}
|
1
chacha-sys/cpu-crypt/.gitignore
vendored
Normal file
1
chacha-sys/cpu-crypt/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
release/
|
25
chacha-sys/cpu-crypt/Makefile
Normal file
25
chacha-sys/cpu-crypt/Makefile
Normal file
@ -0,0 +1,25 @@
|
||||
V:=debug
|
||||
|
||||
LIB:=cpu-crypt
|
||||
|
||||
CFLAGS_common:=-Wall -Werror -pedantic -fPIC
|
||||
CFLAGS_release:=-march=native -O3 $(CFLAGS_common)
|
||||
CFLAGS_debug:=-g $(CFLAGS_common)
|
||||
CFLAGS:=$(CFLAGS_$V)
|
||||
|
||||
all: $V/lib$(LIB).a
|
||||
|
||||
$V/chacha20_core.o: chacha20_core.c chacha.h
|
||||
@mkdir -p $(@D)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$V/chacha_cbc.o: chacha_cbc.c chacha.h
|
||||
@mkdir -p $(@D)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$V/lib$(LIB).a: $V/chacha20_core.o $V/chacha_cbc.o
|
||||
$(AR) rcs $@ $^
|
||||
|
||||
.PHONY:clean
|
||||
clean:
|
||||
rm -rf $V
|
35
chacha-sys/cpu-crypt/chacha.h
Normal file
35
chacha-sys/cpu-crypt/chacha.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef HEADER_CHACHA_H
|
||||
# define HEADER_CHACHA_H
|
||||
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
# include <stddef.h>
|
||||
# ifdef __cplusplus
|
||||
extern "C" {
|
||||
# endif
|
||||
|
||||
typedef unsigned int u32;
|
||||
|
||||
#define CHACHA_KEY_SIZE 32
|
||||
#define CHACHA_NONCE_SIZE 12
|
||||
#define CHACHA_BLOCK_SIZE 64
|
||||
#define CHACHA_ROUNDS 500
|
||||
|
||||
void chacha20_encrypt(const u32 input[16],
|
||||
unsigned char output[64],
|
||||
int num_rounds);
|
||||
|
||||
void chacha20_encrypt_ctr(const uint8_t *in, uint8_t *out, size_t in_len,
|
||||
const uint8_t key[CHACHA_KEY_SIZE], const uint8_t nonce[CHACHA_NONCE_SIZE],
|
||||
uint32_t counter);
|
||||
|
||||
void chacha20_cbc128_encrypt(const unsigned char* in, unsigned char* out,
|
||||
uint32_t len, const uint8_t* key,
|
||||
unsigned char* ivec);
|
||||
|
||||
|
||||
# ifdef __cplusplus
|
||||
}
|
||||
# endif
|
||||
|
||||
#endif
|
102
chacha-sys/cpu-crypt/chacha20_core.c
Normal file
102
chacha-sys/cpu-crypt/chacha20_core.c
Normal file
@ -0,0 +1,102 @@
|
||||
#include "chacha.h"
|
||||
|
||||
#define ROTL32(v, n) (((v) << (n)) | ((v) >> (32 - (n))))
|
||||
|
||||
#define ROTATE(v, c) ROTL32((v), (c))
|
||||
|
||||
#define XOR(v, w) ((v) ^ (w))
|
||||
|
||||
#define PLUS(x, y) ((x) + (y))
|
||||
|
||||
#define U32TO8_LITTLE(p, v) \
|
||||
{ (p)[0] = ((v) ) & 0xff; (p)[1] = ((v) >> 8) & 0xff; \
|
||||
(p)[2] = ((v) >> 16) & 0xff; (p)[3] = ((v) >> 24) & 0xff; }
|
||||
|
||||
#define U8TO32_LITTLE(p) \
|
||||
(((u32)((p)[0]) ) | ((u32)((p)[1]) << 8) | \
|
||||
((u32)((p)[2]) << 16) | ((u32)((p)[3]) << 24) )
|
||||
|
||||
#define QUARTERROUND(a,b,c,d) \
|
||||
x[a] = PLUS(x[a],x[b]); x[d] = ROTATE(XOR(x[d],x[a]),16); \
|
||||
x[c] = PLUS(x[c],x[d]); x[b] = ROTATE(XOR(x[b],x[c]),12); \
|
||||
x[a] = PLUS(x[a],x[b]); x[d] = ROTATE(XOR(x[d],x[a]), 8); \
|
||||
x[c] = PLUS(x[c],x[d]); x[b] = ROTATE(XOR(x[b],x[c]), 7);
|
||||
|
||||
// sigma contains the ChaCha constants, which happen to be an ASCII string.
|
||||
static const uint8_t sigma[16] = { 'e', 'x', 'p', 'a', 'n', 'd', ' ', '3',
|
||||
'2', '-', 'b', 'y', 't', 'e', ' ', 'k' };
|
||||
|
||||
void chacha20_encrypt(const u32 input[16],
|
||||
unsigned char output[64],
|
||||
int num_rounds)
|
||||
{
|
||||
u32 x[16];
|
||||
int i;
|
||||
memcpy(x, input, sizeof(u32) * 16);
|
||||
for (i = num_rounds; i > 0; i -= 2) {
|
||||
QUARTERROUND( 0, 4, 8,12)
|
||||
QUARTERROUND( 1, 5, 9,13)
|
||||
QUARTERROUND( 2, 6,10,14)
|
||||
QUARTERROUND( 3, 7,11,15)
|
||||
QUARTERROUND( 0, 5,10,15)
|
||||
QUARTERROUND( 1, 6,11,12)
|
||||
QUARTERROUND( 2, 7, 8,13)
|
||||
QUARTERROUND( 3, 4, 9,14)
|
||||
}
|
||||
for (i = 0; i < 16; ++i) {
|
||||
x[i] = PLUS(x[i], input[i]);
|
||||
}
|
||||
for (i = 0; i < 16; ++i) {
|
||||
U32TO8_LITTLE(output + 4 * i, x[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void chacha20_encrypt_ctr(const uint8_t *in, uint8_t *out, size_t in_len,
|
||||
const uint8_t key[CHACHA_KEY_SIZE],
|
||||
const uint8_t nonce[CHACHA_NONCE_SIZE],
|
||||
uint32_t counter)
|
||||
{
|
||||
uint32_t input[16];
|
||||
uint8_t buf[64];
|
||||
size_t todo, i;
|
||||
|
||||
input[0] = U8TO32_LITTLE(sigma + 0);
|
||||
input[1] = U8TO32_LITTLE(sigma + 4);
|
||||
input[2] = U8TO32_LITTLE(sigma + 8);
|
||||
input[3] = U8TO32_LITTLE(sigma + 12);
|
||||
|
||||
input[4] = U8TO32_LITTLE(key + 0);
|
||||
input[5] = U8TO32_LITTLE(key + 4);
|
||||
input[6] = U8TO32_LITTLE(key + 8);
|
||||
input[7] = U8TO32_LITTLE(key + 12);
|
||||
|
||||
input[8] = U8TO32_LITTLE(key + 16);
|
||||
input[9] = U8TO32_LITTLE(key + 20);
|
||||
input[10] = U8TO32_LITTLE(key + 24);
|
||||
input[11] = U8TO32_LITTLE(key + 28);
|
||||
|
||||
input[12] = counter;
|
||||
input[13] = U8TO32_LITTLE(nonce + 0);
|
||||
input[14] = U8TO32_LITTLE(nonce + 4);
|
||||
input[15] = U8TO32_LITTLE(nonce + 8);
|
||||
|
||||
while (in_len > 0) {
|
||||
todo = sizeof(buf);
|
||||
if (in_len < todo) {
|
||||
todo = in_len;
|
||||
}
|
||||
|
||||
chacha20_encrypt(input, buf, 20);
|
||||
for (i = 0; i < todo; i++) {
|
||||
out[i] = in[i] ^ buf[i];
|
||||
}
|
||||
|
||||
out += todo;
|
||||
in += todo;
|
||||
in_len -= todo;
|
||||
|
||||
input[12]++;
|
||||
}
|
||||
}
|
||||
|
||||
|
72
chacha-sys/cpu-crypt/chacha_cbc.c
Normal file
72
chacha-sys/cpu-crypt/chacha_cbc.c
Normal file
@ -0,0 +1,72 @@
|
||||
#include "chacha.h"
|
||||
|
||||
#if !defined(STRICT_ALIGNMENT) && !defined(PEDANTIC)
|
||||
# define STRICT_ALIGNMENT 0
|
||||
#endif
|
||||
|
||||
void chacha20_cbc128_encrypt(const unsigned char* in, unsigned char* out,
|
||||
uint32_t len, const uint8_t* key,
|
||||
unsigned char* ivec)
|
||||
{
|
||||
size_t n;
|
||||
unsigned char *iv = ivec;
|
||||
(void)key;
|
||||
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if !defined(OPENSSL_SMALL_FOOTPRINT)
|
||||
if (STRICT_ALIGNMENT &&
|
||||
((size_t)in | (size_t)out | (size_t)ivec) % sizeof(size_t) != 0) {
|
||||
while (len >= CHACHA_BLOCK_SIZE) {
|
||||
for (n = 0; n < CHACHA_BLOCK_SIZE; ++n) {
|
||||
out[n] = in[n] ^ iv[n];
|
||||
//printf("%x ", out[n]);
|
||||
}
|
||||
chacha20_encrypt((const u32*)out, out, CHACHA_ROUNDS);
|
||||
iv = out;
|
||||
len -= CHACHA_BLOCK_SIZE;
|
||||
in += CHACHA_BLOCK_SIZE;
|
||||
out += CHACHA_BLOCK_SIZE;
|
||||
}
|
||||
} else {
|
||||
while (len >= CHACHA_BLOCK_SIZE) {
|
||||
for (n = 0; n < CHACHA_BLOCK_SIZE; n += sizeof(size_t)) {
|
||||
*(size_t *)(out + n) =
|
||||
*(size_t *)(in + n) ^ *(size_t *)(iv + n);
|
||||
//printf("%zu ", *(size_t *)(iv + n));
|
||||
}
|
||||
chacha20_encrypt((const u32*)out, out, CHACHA_ROUNDS);
|
||||
iv = out;
|
||||
len -= CHACHA_BLOCK_SIZE;
|
||||
in += CHACHA_BLOCK_SIZE;
|
||||
out += CHACHA_BLOCK_SIZE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
while (len) {
|
||||
for (n = 0; n < CHACHA_BLOCK_SIZE && n < len; ++n) {
|
||||
out[n] = in[n] ^ iv[n];
|
||||
}
|
||||
for (; n < CHACHA_BLOCK_SIZE; ++n) {
|
||||
out[n] = iv[n];
|
||||
}
|
||||
chacha20_encrypt((const u32*)out, out, CHACHA_ROUNDS);
|
||||
iv = out;
|
||||
if (len <= CHACHA_BLOCK_SIZE) {
|
||||
break;
|
||||
}
|
||||
len -= CHACHA_BLOCK_SIZE;
|
||||
in += CHACHA_BLOCK_SIZE;
|
||||
out += CHACHA_BLOCK_SIZE;
|
||||
}
|
||||
memcpy(ivec, iv, CHACHA_BLOCK_SIZE);
|
||||
|
||||
}
|
||||
|
||||
void chacha20_cbc_encrypt(const uint8_t *in, uint8_t *out, size_t in_len,
|
||||
const uint8_t key[CHACHA_KEY_SIZE], uint8_t* ivec)
|
||||
{
|
||||
chacha20_cbc128_encrypt(in, out, in_len, key, ivec);
|
||||
}
|
21
chacha-sys/src/lib.rs
Normal file
21
chacha-sys/src/lib.rs
Normal file
@ -0,0 +1,21 @@
|
||||
extern "C" {
|
||||
fn chacha20_cbc_encrypt(
|
||||
input: *const u8,
|
||||
output: *mut u8,
|
||||
in_len: usize,
|
||||
key: *const u8,
|
||||
ivec: *mut u8,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn chacha_cbc_encrypt(input: &[u8], output: &mut [u8], key: &[u8], ivec: &mut [u8]) {
|
||||
unsafe {
|
||||
chacha20_cbc_encrypt(
|
||||
input.as_ptr(),
|
||||
output.as_mut_ptr(),
|
||||
input.len(),
|
||||
key.as_ptr(),
|
||||
ivec.as_mut_ptr(),
|
||||
);
|
||||
}
|
||||
}
|
1
chacha/.gitignore
vendored
Normal file
1
chacha/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/farf/
|
25
chacha/Cargo.toml
Normal file
25
chacha/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "solana-chacha"
|
||||
version = "1.0.4"
|
||||
description = "Solana Chacha APIs"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.4" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.4" }
|
||||
solana-logger = { path = "../logger", version = "1.0.4" }
|
||||
solana-perf = { path = "../perf", version = "1.0.4" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.4" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2.1"
|
||||
|
||||
[lib]
|
||||
name = "solana_chacha"
|
185
chacha/src/chacha.rs
Normal file
185
chacha/src/chacha.rs
Normal file
@ -0,0 +1,185 @@
|
||||
use solana_ledger::blockstore::Blockstore;
|
||||
use solana_sdk::clock::Slot;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{BufWriter, Write};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use solana_chacha_sys::chacha_cbc_encrypt;
|
||||
|
||||
pub const CHACHA_BLOCK_SIZE: usize = 64;
|
||||
pub const CHACHA_KEY_SIZE: usize = 32;
|
||||
|
||||
pub fn chacha_cbc_encrypt_ledger(
|
||||
blockstore: &Arc<Blockstore>,
|
||||
start_slot: Slot,
|
||||
slots_per_segment: u64,
|
||||
out_path: &Path,
|
||||
ivec: &mut [u8; CHACHA_BLOCK_SIZE],
|
||||
) -> io::Result<usize> {
|
||||
let mut out_file =
|
||||
BufWriter::new(File::create(out_path).expect("Can't open ledger encrypted data file"));
|
||||
const BUFFER_SIZE: usize = 8 * 1024;
|
||||
let mut buffer = [0; BUFFER_SIZE];
|
||||
let mut encrypted_buffer = [0; BUFFER_SIZE];
|
||||
let key = [0; CHACHA_KEY_SIZE];
|
||||
let mut total_size = 0;
|
||||
let mut current_slot = start_slot;
|
||||
let mut start_index = 0;
|
||||
loop {
|
||||
match blockstore.get_data_shreds(current_slot, start_index, std::u64::MAX, &mut buffer) {
|
||||
Ok((last_index, mut size)) => {
|
||||
debug!(
|
||||
"chacha: encrypting slice: {} num_shreds: {} data_len: {}",
|
||||
current_slot,
|
||||
last_index.saturating_sub(start_index),
|
||||
size
|
||||
);
|
||||
debug!("read {} bytes", size);
|
||||
|
||||
if size == 0 {
|
||||
if current_slot.saturating_sub(start_slot) < slots_per_segment {
|
||||
current_slot += 1;
|
||||
start_index = 0;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if size < BUFFER_SIZE {
|
||||
// round to the nearest key_size boundary
|
||||
size = (size + CHACHA_KEY_SIZE - 1) & !(CHACHA_KEY_SIZE - 1);
|
||||
}
|
||||
total_size += size;
|
||||
|
||||
chacha_cbc_encrypt(&buffer[..size], &mut encrypted_buffer[..size], &key, ivec);
|
||||
if let Err(res) = out_file.write(&encrypted_buffer[..size]) {
|
||||
warn!("Error writing file! {:?}", res);
|
||||
return Err(res);
|
||||
}
|
||||
|
||||
start_index = last_index + 1;
|
||||
}
|
||||
Err(e) => {
|
||||
info!("Error encrypting file: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(total_size)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::chacha::chacha_cbc_encrypt_ledger;
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaChaRng;
|
||||
use solana_ledger::blockstore::Blockstore;
|
||||
use solana_ledger::entry::Entry;
|
||||
use solana_ledger::get_tmp_ledger_path;
|
||||
use solana_sdk::hash::{hash, Hash, Hasher};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::system_transaction;
|
||||
use std::fs::remove_file;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn make_tiny_deterministic_test_entries(num: usize) -> Vec<Entry> {
|
||||
let zero = Hash::default();
|
||||
let one = hash(&zero.as_ref());
|
||||
|
||||
let seed = [2u8; 32];
|
||||
|
||||
let mut generator = ChaChaRng::from_seed(seed);
|
||||
let keypair = Keypair::generate(&mut generator);
|
||||
|
||||
let mut id = one;
|
||||
let mut num_hashes = 0;
|
||||
(0..num)
|
||||
.map(|_| {
|
||||
Entry::new_mut(
|
||||
&mut id,
|
||||
&mut num_hashes,
|
||||
vec![system_transaction::transfer(
|
||||
&keypair,
|
||||
&keypair.pubkey(),
|
||||
1,
|
||||
one,
|
||||
)],
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
use std::{env, fs::create_dir_all, path::PathBuf};
|
||||
fn tmp_file_path(name: &str) -> PathBuf {
|
||||
let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
|
||||
let mut path = PathBuf::new();
|
||||
path.push(out_dir);
|
||||
path.push("tmp");
|
||||
create_dir_all(&path).unwrap();
|
||||
|
||||
path.push(format!("{}-{}", name, Pubkey::new_rand()));
|
||||
path
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_ledger() {
|
||||
solana_logger::setup();
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let ticks_per_slot = 16;
|
||||
let slots_per_segment = 32;
|
||||
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||
let out_path = tmp_file_path("test_encrypt_ledger");
|
||||
|
||||
let seed = [2u8; 32];
|
||||
|
||||
let mut generator = ChaChaRng::from_seed(seed);
|
||||
let keypair = Keypair::generate(&mut generator);
|
||||
|
||||
let entries = make_tiny_deterministic_test_entries(slots_per_segment);
|
||||
blockstore
|
||||
.write_entries(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
ticks_per_slot,
|
||||
None,
|
||||
true,
|
||||
&Arc::new(keypair),
|
||||
entries,
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut key = hex!(
|
||||
"abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234
|
||||
abcd1234abcd1234abcd1234abcd1234 abcd1234abcd1234abcd1234abcd1234"
|
||||
);
|
||||
chacha_cbc_encrypt_ledger(
|
||||
&blockstore,
|
||||
0,
|
||||
slots_per_segment as u64,
|
||||
&out_path,
|
||||
&mut key,
|
||||
)
|
||||
.unwrap();
|
||||
let mut out_file = File::open(&out_path).unwrap();
|
||||
let mut buf = vec![];
|
||||
let size = out_file.read_to_end(&mut buf).unwrap();
|
||||
let mut hasher = Hasher::default();
|
||||
hasher.hash(&buf[..size]);
|
||||
|
||||
// golden needs to be updated if shred structure changes....
|
||||
let golden: Hash = "2rq8nR6rns2T5zsbQAGBDZb41NVtacneLgkCH17CVxZm"
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hasher.result(), golden);
|
||||
remove_file(&out_path).unwrap();
|
||||
}
|
||||
}
|
8
chacha/src/lib.rs
Normal file
8
chacha/src/lib.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate hex_literal;
|
||||
|
||||
pub mod chacha;
|
104
ci/README.md
104
ci/README.md
@ -14,47 +14,45 @@ products over to a GPU instance for testing.
|
||||
|
||||
## Buildkite Agent Management
|
||||
|
||||
### Buildkite GCP Setup
|
||||
### Buildkite Azure Setup
|
||||
|
||||
CI runs on Google Cloud Platform via two Compute Engine Instance groups:
|
||||
`ci-default` and `ci-cuda`. Autoscaling is currently disabled and the number of
|
||||
VM Instances in each group is manually adjusted.
|
||||
|
||||
#### Updating a CI Disk Image
|
||||
|
||||
Each Instance group has its own disk image, `ci-default-vX` and
|
||||
`ci-cuda-vY`, where *X* and *Y* are incremented each time the image is changed.
|
||||
|
||||
The process to update a disk image is as follows (TODO: make this less manual):
|
||||
|
||||
1. Create a new VM Instance using the disk image to modify.
|
||||
2. Once the VM boots, ssh to it and modify the disk as desired.
|
||||
3. Stop the VM Instance running the modified disk. Remember the name of the VM disk
|
||||
4. From another machine, `gcloud auth login`, then create a new Disk Image based
|
||||
off the modified VM Instance:
|
||||
Create a new Azure-based "queue=default" agent by running the following command:
|
||||
```
|
||||
$ az vm create \
|
||||
--resource-group ci \
|
||||
--name XYZ \
|
||||
--image boilerplate \
|
||||
--admin-username $(whoami) \
|
||||
--ssh-key-value ~/.ssh/id_rsa.pub
|
||||
```
|
||||
$ gcloud compute images create ci-default-$(date +%Y%m%d%H%M) --source-disk xxx --source-disk-zone us-east1-b --family ci-default
|
||||
|
||||
```
|
||||
or
|
||||
```
|
||||
$ gcloud compute images create ci-cuda-$(date +%Y%m%d%H%M) --source-disk xxx --source-disk-zone us-east1-b --family ci-cuda
|
||||
```
|
||||
5. Delete the new VM instance.
|
||||
6. Go to the Instance templates tab, find the existing template named
|
||||
`ci-default-vX` or `ci-cuda-vY` and select it. Use the "Copy" button to create
|
||||
a new Instance template called `ci-default-vX+1` or `ci-cuda-vY+1` with the
|
||||
newly created Disk image.
|
||||
7. Go to the Instance Groups tag and find the applicable group, `ci-default` or
|
||||
`ci-cuda`. Edit the Instance Group in two steps: (a) Set the number of
|
||||
instances to 0 and wait for them all to terminate, (b) Update the Instance
|
||||
template and restore the number of instances to the original value.
|
||||
8. Clean up the previous version by deleting it from Instance Templates and
|
||||
Images.
|
||||
The "boilerplate" image contains all the required packages pre-installed so the
|
||||
new machine should immediately show up in the Buildkite agent list once it has
|
||||
been provisioned and be ready for service.
|
||||
|
||||
Creating a "queue=cuda" agent follows the same process but additionally:
|
||||
1. Resize the image from the Azure port to include a GPU
|
||||
2. Edit the tags field in /etc/buildkite-agent/buildkite-agent.cfg to `tags="queue=cuda,queue=default"`
|
||||
and decrease the value of the priority field by one
|
||||
|
||||
#### Updating the CI Disk Image
|
||||
|
||||
1. Create a new VM Instance as described above
|
||||
1. Modify it as required
|
||||
1. When ready, ssh into the instance and start a root shell with `sudo -i`. Then
|
||||
prepare it for deallocation by running:
|
||||
`waagent -deprovision+user; cd /etc; ln -s ../run/systemd/resolve/stub-resolv.conf resolv.conf`
|
||||
1. Run `az vm deallocate --resource-group ci --name XYZ`
|
||||
1. Run `az vm generalize --resource-group ci --name XYZ`
|
||||
1. Run `az image create --resource-group ci --source XYZ --name boilerplate`
|
||||
1. Goto the `ci` resource group in the Azure portal and remove all resources
|
||||
with the XYZ name in them
|
||||
|
||||
## Reference
|
||||
|
||||
This section contains details regarding previous CI setups that have been used,
|
||||
and that we may return to one day.
|
||||
|
||||
### Buildkite AWS CloudFormation Setup
|
||||
|
||||
**AWS CloudFormation is currently inactive, although it may be restored in the
|
||||
@ -86,4 +84,42 @@ The new AMI should also now be visible in your EC2 Dashboard. Go to the desired
|
||||
AWS CloudFormation stack, update the **ImageId** field to the new AMI id, and
|
||||
*apply* the stack changes.
|
||||
|
||||
### Buildkite GCP Setup
|
||||
|
||||
CI runs on Google Cloud Platform via two Compute Engine Instance groups:
|
||||
`ci-default` and `ci-cuda`. Autoscaling is currently disabled and the number of
|
||||
VM Instances in each group is manually adjusted.
|
||||
|
||||
#### Updating a CI Disk Image
|
||||
|
||||
Each Instance group has its own disk image, `ci-default-vX` and
|
||||
`ci-cuda-vY`, where *X* and *Y* are incremented each time the image is changed.
|
||||
|
||||
The manual process to update a disk image is as follows:
|
||||
|
||||
1. Create a new VM Instance using the disk image to modify.
|
||||
2. Once the VM boots, ssh to it and modify the disk as desired.
|
||||
3. Stop the VM Instance running the modified disk. Remember the name of the VM disk
|
||||
4. From another machine, `gcloud auth login`, then create a new Disk Image based
|
||||
off the modified VM Instance:
|
||||
```
|
||||
$ gcloud compute images create ci-default-$(date +%Y%m%d%H%M) --source-disk xxx --source-disk-zone us-east1-b --family ci-default
|
||||
|
||||
```
|
||||
or
|
||||
```
|
||||
$ gcloud compute images create ci-cuda-$(date +%Y%m%d%H%M) --source-disk xxx --source-disk-zone us-east1-b --family ci-cuda
|
||||
```
|
||||
5. Delete the new VM instance.
|
||||
6. Go to the Instance templates tab, find the existing template named
|
||||
`ci-default-vX` or `ci-cuda-vY` and select it. Use the "Copy" button to create
|
||||
a new Instance template called `ci-default-vX+1` or `ci-cuda-vY+1` with the
|
||||
newly created Disk image.
|
||||
7. Go to the Instance Groups tag and find the applicable group, `ci-default` or
|
||||
`ci-cuda`. Edit the Instance Group in two steps: (a) Set the number of
|
||||
instances to 0 and wait for them all to terminate, (b) Update the Instance
|
||||
template and restore the number of instances to the original value.
|
||||
8. Clean up the previous version by deleting it from Instance Templates and
|
||||
Images.
|
||||
|
||||
|
||||
|
11
ci/_
Normal file
11
ci/_
Normal file
@ -0,0 +1,11 @@
|
||||
# Buildkite log management helper
|
||||
#
|
||||
# See https://buildkite.com/docs/pipelines/managing-log-output
|
||||
#
|
||||
# |source| me
|
||||
#
|
||||
|
||||
_() {
|
||||
echo "--- $*"
|
||||
"$@"
|
||||
}
|
43
ci/affects-files.sh
Executable file
43
ci/affects-files.sh
Executable file
@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Checks if a CI build affects one or more path patterns. Each command-line
|
||||
# argument is checked in series.
|
||||
#
|
||||
# Bash regular expressions are permitted in the pattern:
|
||||
# ./affects-files.sh .rs$ -- any file or directory ending in .rs
|
||||
# ./affects-files.sh .rs -- also matches foo.rs.bar
|
||||
# ./affects-files.sh ^snap/ -- anything under the snap/ subdirectory
|
||||
# ./affects-files.sh snap/ -- also matches foo/snap/
|
||||
# Any pattern starting with the ! character will be negated:
|
||||
# ./affects-files.sh !^docs/ -- anything *not* under the docs/ subdirectory
|
||||
#
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
if [[ -n $CI_PULL_REQUEST ]]; then
|
||||
affectedFiles="$(buildkite-agent meta-data get affected_files)"
|
||||
echo "Affected files in this PR: $affectedFiles"
|
||||
|
||||
IFS=':' read -ra files <<< "$affectedFiles"
|
||||
for pattern in "$@"; do
|
||||
if [[ ${pattern:0:1} = "!" ]]; then
|
||||
for file in "${files[@]}"; do
|
||||
if [[ ! $file =~ ${pattern:1} ]]; then
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
else
|
||||
for file in "${files[@]}"; do
|
||||
if [[ $file =~ $pattern ]]; then
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# affected_files metadata is not currently available for non-PR builds, so assume
|
||||
# the worse (affected)
|
||||
exit 0
|
32
ci/audit.sh
32
ci/audit.sh
@ -1,32 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Audits project dependencies for security vulnerabilities
|
||||
#
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
rustc --version
|
||||
cargo --version
|
||||
|
||||
_() {
|
||||
echo "--- $*"
|
||||
"$@"
|
||||
}
|
||||
|
||||
maybe_cargo_install() {
|
||||
for cmd in "$@"; do
|
||||
set +e
|
||||
cargo "$cmd" --help > /dev/null 2>&1
|
||||
declare exitcode=$?
|
||||
set -e
|
||||
if [[ $exitcode -eq 101 ]]; then
|
||||
_ cargo install cargo-"$cmd"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
maybe_cargo_install audit tree
|
||||
|
||||
_ cargo tree
|
||||
_ cargo audit || true
|
15
ci/buildkite-release.yml
Normal file
15
ci/buildkite-release.yml
Normal file
@ -0,0 +1,15 @@
|
||||
# Build steps that run on a release tag
|
||||
#
|
||||
# All the steps in `buildkite.yml` are skipped and we jump directly to the
|
||||
# secondary build steps since it's assumed the commit that was tagged is known
|
||||
# to be good so there's no need to rebuild and retest it.
|
||||
steps:
|
||||
- trigger: "solana-secondary"
|
||||
branches: "!pull/*"
|
||||
async: true
|
||||
build:
|
||||
message: "${BUILDKITE_MESSAGE}"
|
||||
commit: "${BUILDKITE_COMMIT}"
|
||||
branch: "${BUILDKITE_BRANCH}"
|
||||
env:
|
||||
TRIGGERED_BUILDKITE_TAG: "${BUILDKITE_TAG}"
|
20
ci/buildkite-secondary.yml
Normal file
20
ci/buildkite-secondary.yml
Normal file
@ -0,0 +1,20 @@
|
||||
#
|
||||
# Build steps that run after the primary pipeline on pushes and tags.
|
||||
# Pull requests to not run these steps.
|
||||
steps:
|
||||
- command: "sdk/docker-solana/build.sh"
|
||||
timeout_in_minutes: 60
|
||||
name: "publish docker"
|
||||
- command: "ci/publish-crate.sh"
|
||||
timeout_in_minutes: 240
|
||||
name: "publish crate"
|
||||
branches: "!master"
|
||||
- command: "ci/publish-bpf-sdk.sh"
|
||||
timeout_in_minutes: 5
|
||||
name: "publish bpf sdk"
|
||||
- command: "ci/publish-tarball.sh"
|
||||
timeout_in_minutes: 60
|
||||
name: "publish tarball"
|
||||
- command: "ci/publish-docs.sh"
|
||||
timeout_in_minutes: 15
|
||||
name: "publish docs"
|
@ -1,4 +0,0 @@
|
||||
steps:
|
||||
- command: "ci/snap.sh"
|
||||
timeout_in_minutes: 40
|
||||
name: "snap [public]"
|
@ -1,45 +1,39 @@
|
||||
# Build steps that run on pushes and pull requests.
|
||||
#
|
||||
# Release tags use buildkite-release.yml instead
|
||||
steps:
|
||||
- command: "ci/docker-run.sh solanalabs/rust:1.29.1 ci/test-stable.sh"
|
||||
name: "stable [public]"
|
||||
env:
|
||||
CARGO_TARGET_CACHE_NAME: "stable"
|
||||
timeout_in_minutes: 30
|
||||
- command: "ci/docker-run.sh solanalabs/rust-nightly ci/test-bench.sh"
|
||||
name: "bench [public]"
|
||||
env:
|
||||
CARGO_TARGET_CACHE_NAME: "nightly"
|
||||
timeout_in_minutes: 30
|
||||
- command: "ci/shellcheck.sh"
|
||||
name: "shellcheck [public]"
|
||||
name: "shellcheck"
|
||||
timeout_in_minutes: 5
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-checks.sh"
|
||||
name: "checks"
|
||||
timeout_in_minutes: 20
|
||||
- command: "ci/docker-run.sh solanalabs/rust-nightly:2018-09-03 ci/test-nightly.sh || true"
|
||||
name: "nightly [public]"
|
||||
env:
|
||||
CARGO_TARGET_CACHE_NAME: "nightly"
|
||||
timeout_in_minutes: 30
|
||||
- wait
|
||||
- command: "ci/test-stable-perf.sh"
|
||||
name: "stable-perf [public]"
|
||||
env:
|
||||
CARGO_TARGET_CACHE_NAME: "stable-perf"
|
||||
timeout_in_minutes: 20
|
||||
name: "stable-perf"
|
||||
timeout_in_minutes: 40
|
||||
artifact_paths: "log-*.txt"
|
||||
agents:
|
||||
- "queue=cuda"
|
||||
# TODO: Fix and re-enable test-large-network.sh
|
||||
# - command: "ci/test-large-network.sh || true"
|
||||
# name: "large-network [public] [ignored]"
|
||||
# env:
|
||||
# CARGO_TARGET_CACHE_NAME: "stable"
|
||||
# timeout_in_minutes: 20
|
||||
# agents:
|
||||
# - "queue=large"
|
||||
- command: "ci/pr-snap.sh"
|
||||
- command: "ci/test-bench.sh"
|
||||
name: "bench"
|
||||
timeout_in_minutes: 30
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-stable.sh"
|
||||
name: "stable"
|
||||
timeout_in_minutes: 60
|
||||
artifact_paths: "log-*.txt"
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-move.sh"
|
||||
name: "move"
|
||||
timeout_in_minutes: 20
|
||||
name: "snap [public]"
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-local-cluster.sh"
|
||||
name: "local-cluster"
|
||||
timeout_in_minutes: 30
|
||||
artifact_paths: "log-*.txt"
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
|
||||
name: "coverage"
|
||||
timeout_in_minutes: 30
|
||||
- wait
|
||||
- command: "ci/publish-crate.sh"
|
||||
timeout_in_minutes: 20
|
||||
name: "publish crate [public]"
|
||||
- trigger: "solana-snap"
|
||||
- trigger: "solana-secondary"
|
||||
branches: "!pull/*"
|
||||
async: true
|
||||
build:
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Computes the current branch names of the edge, beta and stable
|
||||
# channels, as well as the latest tagged release for beta and stable.
|
||||
@ -82,10 +82,26 @@ for tag in "${tags[@]}"; do
|
||||
fi
|
||||
done
|
||||
|
||||
echo EDGE_CHANNEL=master
|
||||
echo BETA_CHANNEL="${beta:+v$beta}"
|
||||
echo STABLE_CHANNEL="${stable:+v$stable}"
|
||||
echo BETA_CHANNEL_LATEST_TAG="${beta_tag:+v$beta_tag}"
|
||||
echo STABLE_CHANNEL_LATEST_TAG="${stable_tag:+v$stable_tag}"
|
||||
EDGE_CHANNEL=master
|
||||
BETA_CHANNEL=${beta:+v$beta}
|
||||
STABLE_CHANNEL=${stable:+v$stable}
|
||||
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
|
||||
fi
|
||||
|
||||
echo EDGE_CHANNEL="$EDGE_CHANNEL"
|
||||
echo BETA_CHANNEL="$BETA_CHANNEL"
|
||||
echo BETA_CHANNEL_LATEST_TAG="$BETA_CHANNEL_LATEST_TAG"
|
||||
echo STABLE_CHANNEL="$STABLE_CHANNEL"
|
||||
echo STABLE_CHANNEL_LATEST_TAG="$STABLE_CHANNEL_LATEST_TAG"
|
||||
echo CHANNEL="$CHANNEL"
|
||||
|
||||
exit 0
|
||||
|
4
ci/check-ssh-keys.sh
Executable file
4
ci/check-ssh-keys.sh
Executable file
@ -0,0 +1,4 @@
|
||||
if grep -q rsa net/scripts/solana-user-authorized_keys.sh; then
|
||||
echo "No rsa keys allowed, small key sizes are insecure."
|
||||
exit 1
|
||||
fi
|
26
ci/crate-version.sh
Executable file
26
ci/crate-version.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Outputs the current crate version from a given Cargo.toml
|
||||
#
|
||||
set -e
|
||||
|
||||
Cargo_toml=$1
|
||||
[[ -n $Cargo_toml ]] || {
|
||||
echo "Usage: $0 path/to/Cargo.toml"
|
||||
exit 0
|
||||
}
|
||||
|
||||
[[ -r $Cargo_toml ]] || {
|
||||
echo "Error: unable to read $Cargo_toml"
|
||||
exit 1
|
||||
}
|
||||
|
||||
while read -r name equals value _; do
|
||||
if [[ $name = version && $equals = = ]]; then
|
||||
echo "${value//\"/}"
|
||||
exit 0
|
||||
fi
|
||||
done < <(cat "$Cargo_toml")
|
||||
|
||||
echo Unable to locate version in Cargo.toml 1>&2
|
||||
exit 1
|
@ -1,4 +1,5 @@
|
||||
#!/bin/bash -e
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [--nopull] [docker image name] [command]"
|
||||
@ -63,14 +64,24 @@ fi
|
||||
ARGS+=(
|
||||
--env BUILDKITE
|
||||
--env BUILDKITE_AGENT_ACCESS_TOKEN
|
||||
--env BUILDKITE_BRANCH
|
||||
--env BUILDKITE_JOB_ID
|
||||
--env BUILDKITE_TAG
|
||||
--env CODECOV_TOKEN
|
||||
--env CI
|
||||
--env CI_BRANCH
|
||||
--env CI_BUILD_ID
|
||||
--env CI_COMMIT
|
||||
--env CI_JOB_ID
|
||||
--env CI_PULL_REQUEST
|
||||
--env CI_REPO_SLUG
|
||||
--env CRATES_IO_TOKEN
|
||||
--env SNAPCRAFT_CREDENTIALS_KEY
|
||||
)
|
||||
|
||||
# Also propagate environment variables needed for codecov
|
||||
# https://docs.codecov.io/docs/testing-with-docker#section-codecov-inside-docker
|
||||
# We normalize CI to `1`; but codecov expects it to be `true` to detect Buildkite...
|
||||
# Unfortunately, codecov.io fails sometimes:
|
||||
# curl: (7) Failed to connect to codecov.io port 443: Connection timed out
|
||||
CODECOV_ENVS=$(CI=true bash <(while ! curl -sS --retry 5 --retry-delay 2 --retry-connrefused https://codecov.io/env; do sleep 10; done))
|
||||
|
||||
if $INTERACTIVE; then
|
||||
if [[ -n $1 ]]; then
|
||||
echo
|
||||
@ -78,8 +89,10 @@ if $INTERACTIVE; then
|
||||
echo
|
||||
fi
|
||||
set -x
|
||||
exec docker run --interactive --tty "${ARGS[@]}" "$IMAGE" bash
|
||||
# shellcheck disable=SC2086
|
||||
exec docker run --interactive --tty "${ARGS[@]}" $CODECOV_ENVS "$IMAGE" bash
|
||||
fi
|
||||
|
||||
set -x
|
||||
exec docker run "${ARGS[@]}" "$IMAGE" "$@"
|
||||
# shellcheck disable=SC2086
|
||||
exec docker run "${ARGS[@]}" $CODECOV_ENVS "$IMAGE" "$@"
|
||||
|
@ -1,10 +1,12 @@
|
||||
FROM solanalabs/rust
|
||||
FROM solanalabs/rust:1.41.1
|
||||
ARG date
|
||||
|
||||
RUN set -x && \
|
||||
rustup install nightly-$date && \
|
||||
rustup default nightly-$date && \
|
||||
rustup component add clippy-preview --toolchain=nightly-$date && \
|
||||
rustc --version && \
|
||||
cargo --version && \
|
||||
cargo +nightly-$date install cargo-cov
|
||||
RUN set -x \
|
||||
&& rustup install nightly-$date \
|
||||
&& rustup component add clippy --toolchain=nightly-$date \
|
||||
&& rustup show \
|
||||
&& rustc --version \
|
||||
&& cargo --version \
|
||||
&& cargo install grcov \
|
||||
&& rustc +nightly-$date --version \
|
||||
&& cargo +nightly-$date --version
|
||||
|
@ -15,12 +15,12 @@ To update the pinned version:
|
||||
1. Run `ci/docker-rust-nightly/build.sh` to rebuild the nightly image locally,
|
||||
or potentially `ci/docker-rust-nightly/build.sh YYYY-MM-DD` if there's a
|
||||
specific YYYY-MM-DD that is desired (default is today's build).
|
||||
1. Run `SOLANA_DOCKER_RUN_NOSETUID=1 ci/docker-run.sh --nopull solanalabs/rust-nightly:YYYY-MM-DD ci/test-nightly.sh`
|
||||
1. Update `ci/rust-version.sh` to reflect the new nightly `YYY-MM-DD`
|
||||
1. Run `SOLANA_DOCKER_RUN_NOSETUID=1 ci/docker-run.sh --nopull solanalabs/rust-nightly:YYYY-MM-DD ci/test-coverage.sh`
|
||||
to confirm the new nightly image builds. Fix any issues as needed
|
||||
1. Run `docker login` to enable pushing images to Docker Hub, if you're authorized.
|
||||
1. Run `CI=true ci/docker-rust-nightly/build.sh YYYY-MM-DD` to push the new nightly image to dockerhub.com.
|
||||
1. Modify the `solanalabs/rust-nightly:YYYY-MM-DD` reference in `ci/buildkite.yml` from the previous to
|
||||
new *YYYY-MM-DD* value, send a PR with this change and any codebase adjustments needed.
|
||||
1. Send a PR with the `ci/rust-version.sh` change and any codebase adjustments needed.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
#!/bin/bash -ex
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
|
@ -1,24 +1,40 @@
|
||||
# Note: when the rust version is changed also modify
|
||||
# ci/buildkite.yml to pick up the new image tag
|
||||
FROM rust:1.29.1
|
||||
# ci/rust-version.sh to pick up the new image tag
|
||||
FROM rust:1.41.1
|
||||
|
||||
RUN set -x && \
|
||||
apt update && \
|
||||
apt-get install apt-transport-https && \
|
||||
echo deb https://apt.buildkite.com/buildkite-agent stable main > /etc/apt/sources.list.d/buildkite-agent.list && \
|
||||
echo deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main > /etc/apt/sources.list.d/llvm.list && \
|
||||
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 32A37959C2FA5C3C99EFBC32A79206696452D198 && \
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \
|
||||
apt update && \
|
||||
apt install -y \
|
||||
# Add Google Protocol Buffers for Libra's metrics library.
|
||||
ENV PROTOC_VERSION 3.8.0
|
||||
ENV PROTOC_ZIP protoc-$PROTOC_VERSION-linux-x86_64.zip
|
||||
|
||||
RUN set -x \
|
||||
&& apt update \
|
||||
&& apt-get install apt-transport-https \
|
||||
&& echo deb https://apt.buildkite.com/buildkite-agent stable main > /etc/apt/sources.list.d/buildkite-agent.list \
|
||||
&& apt-key adv --no-tty --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 32A37959C2FA5C3C99EFBC32A79206696452D198 \
|
||||
&& apt update \
|
||||
&& apt install -y \
|
||||
buildkite-agent \
|
||||
clang-7 \
|
||||
cmake \
|
||||
llvm-6.0 \
|
||||
lcov \
|
||||
libudev-dev \
|
||||
libclang-common-7-dev \
|
||||
mscgen \
|
||||
net-tools \
|
||||
rsync \
|
||||
sudo \
|
||||
&& \
|
||||
rustup component add rustfmt-preview && \
|
||||
rustup component add clippy-preview && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
rustc --version && \
|
||||
cargo --version
|
||||
golang \
|
||||
unzip \
|
||||
\
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rustup component add rustfmt \
|
||||
&& rustup component add clippy \
|
||||
&& cargo install cargo-audit \
|
||||
&& cargo install svgbob_cli \
|
||||
&& cargo install mdbook \
|
||||
&& rustc --version \
|
||||
&& cargo --version \
|
||||
&& curl -OL https://github.com/google/protobuf/releases/download/v$PROTOC_VERSION/$PROTOC_ZIP \
|
||||
&& unzip -o $PROTOC_ZIP -d /usr/local bin/protoc \
|
||||
&& unzip -o $PROTOC_ZIP -d /usr/local include/* \
|
||||
&& rm -f $PROTOC_ZIP
|
||||
|
@ -1,6 +1,7 @@
|
||||
Docker image containing rust and some preinstalled packages used in CI.
|
||||
|
||||
This image may be manually updated by running `./build.sh` if you are a member
|
||||
of the [Solana Labs](https://hub.docker.com/u/solanalabs/) Docker Hub
|
||||
organization, but it is also automatically updated periodically by
|
||||
[this automation](https://buildkite.com/solana-labs/solana-ci-docker-rust).
|
||||
This image manually maintained:
|
||||
1. Edit `Dockerfile` to match the desired rust version
|
||||
2. Run `./build.sh` to publish the new image, if you are a member of the [Solana
|
||||
Labs](https://hub.docker.com/u/solanalabs/) Docker Hub organization.
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
#!/bin/bash -ex
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
@ -7,5 +8,5 @@ docker build -t solanalabs/rust .
|
||||
read -r rustc version _ < <(docker run solanalabs/rust rustc --version)
|
||||
[[ $rustc = rustc ]]
|
||||
docker tag solanalabs/rust:latest solanalabs/rust:"$version"
|
||||
|
||||
docker push solanalabs/rust
|
||||
docker push solanalabs/rust:"$version"
|
||||
docker push solanalabs/rust:latest
|
||||
|
@ -1,7 +0,0 @@
|
||||
FROM snapcraft/xenial-amd64
|
||||
|
||||
# Update snapcraft to latest version
|
||||
RUN apt-get update -qq \
|
||||
&& apt-get install -y snapcraft daemontools \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& snapcraft --version
|
@ -1,6 +0,0 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
docker build -t solanalabs/snapcraft .
|
||||
docker push solanalabs/snapcraft
|
89
ci/env.sh
Normal file
89
ci/env.sh
Normal file
@ -0,0 +1,89 @@
|
||||
#
|
||||
# Normalized CI environment variables
|
||||
#
|
||||
# |source| me
|
||||
#
|
||||
|
||||
if [[ -n $CI ]]; then
|
||||
export CI=1
|
||||
if [[ -n $TRAVIS ]]; then
|
||||
export CI_BRANCH=$TRAVIS_BRANCH
|
||||
export CI_BUILD_ID=$TRAVIS_BUILD_ID
|
||||
export CI_COMMIT=$TRAVIS_COMMIT
|
||||
export CI_JOB_ID=$TRAVIS_JOB_ID
|
||||
if $TRAVIS_PULL_REQUEST; then
|
||||
export CI_PULL_REQUEST=true
|
||||
else
|
||||
export CI_PULL_REQUEST=
|
||||
fi
|
||||
export CI_OS_NAME=$TRAVIS_OS_NAME
|
||||
export CI_REPO_SLUG=$TRAVIS_REPO_SLUG
|
||||
export CI_TAG=$TRAVIS_TAG
|
||||
elif [[ -n $BUILDKITE ]]; then
|
||||
export CI_BRANCH=$BUILDKITE_BRANCH
|
||||
export CI_BUILD_ID=$BUILDKITE_BUILD_ID
|
||||
export CI_COMMIT=$BUILDKITE_COMMIT
|
||||
export CI_JOB_ID=$BUILDKITE_JOB_ID
|
||||
# The standard BUILDKITE_PULL_REQUEST environment variable is always "false" due
|
||||
# to how solana-ci-gate is used to trigger PR builds rather than using the
|
||||
# standard Buildkite PR trigger.
|
||||
if [[ $CI_BRANCH =~ pull/* ]]; then
|
||||
export CI_PULL_REQUEST=true
|
||||
else
|
||||
export CI_PULL_REQUEST=
|
||||
fi
|
||||
export CI_OS_NAME=linux
|
||||
if [[ -n $BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG ]]; then
|
||||
# The solana-secondary pipeline should use the slug of the pipeline that
|
||||
# triggered it
|
||||
export CI_REPO_SLUG=$BUILDKITE_ORGANIZATION_SLUG/$BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG
|
||||
else
|
||||
export CI_REPO_SLUG=$BUILDKITE_ORGANIZATION_SLUG/$BUILDKITE_PIPELINE_SLUG
|
||||
fi
|
||||
# TRIGGERED_BUILDKITE_TAG is a workaround to propagate BUILDKITE_TAG into
|
||||
# the solana-secondary pipeline
|
||||
if [[ -n $TRIGGERED_BUILDKITE_TAG ]]; then
|
||||
export CI_TAG=$TRIGGERED_BUILDKITE_TAG
|
||||
else
|
||||
export CI_TAG=$BUILDKITE_TAG
|
||||
fi
|
||||
elif [[ -n $APPVEYOR ]]; then
|
||||
export CI_BRANCH=$APPVEYOR_REPO_BRANCH
|
||||
export CI_BUILD_ID=$APPVEYOR_BUILD_ID
|
||||
export CI_COMMIT=$APPVEYOR_REPO_COMMIT
|
||||
export CI_JOB_ID=$APPVEYOR_JOB_ID
|
||||
if [[ -n $APPVEYOR_PULL_REQUEST_NUMBER ]]; then
|
||||
export CI_PULL_REQUEST=true
|
||||
else
|
||||
export CI_PULL_REQUEST=
|
||||
fi
|
||||
if [[ $CI_LINUX = True ]]; then
|
||||
export CI_OS_NAME=linux
|
||||
else
|
||||
export CI_OS_NAME=windows
|
||||
fi
|
||||
export CI_REPO_SLUG=$APPVEYOR_REPO_NAME
|
||||
export CI_TAG=$APPVEYOR_REPO_TAG_NAME
|
||||
fi
|
||||
else
|
||||
export CI=
|
||||
export CI_BRANCH=
|
||||
export CI_BUILD_ID=
|
||||
export CI_COMMIT=
|
||||
export CI_JOB_ID=
|
||||
export CI_OS_NAME=
|
||||
export CI_PULL_REQUEST=
|
||||
export CI_REPO_SLUG=
|
||||
export CI_TAG=
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
CI=$CI
|
||||
CI_BRANCH=$CI_BRANCH
|
||||
CI_BUILD_ID=$CI_BUILD_ID
|
||||
CI_COMMIT=$CI_COMMIT
|
||||
CI_JOB_ID=$CI_JOB_ID
|
||||
CI_OS_NAME=$CI_OS_NAME
|
||||
CI_PULL_REQUEST=$CI_PULL_REQUEST
|
||||
CI_TAG=$CI_TAG
|
||||
EOF
|
20
ci/format-url.sh
Executable file
20
ci/format-url.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Formats a URL to be clickable from a Buildkite log
|
||||
#
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Usage: $0 url"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z $BUILDKITE ]]; then
|
||||
echo "$1"
|
||||
else
|
||||
# shellcheck disable=SC2001
|
||||
URL="$(echo "$1" | sed 's/;/%3b/g')" # Escape ;
|
||||
|
||||
printf '\033]1339;url='
|
||||
echo -n "$URL"
|
||||
printf '\a\n'
|
||||
fi
|
@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Regular maintenance performed on a buildkite agent to control disk usage
|
||||
#
|
||||
|
@ -1,8 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# The standard BUILDKITE_PULL_REQUEST environment variable is always "false" due
|
||||
# to how solana-ci-gate is used to trigger PR builds rather than using the
|
||||
# standard Buildkite PR trigger.
|
||||
#
|
||||
|
||||
[[ $BUILDKITE_BRANCH =~ pull/* ]]
|
61
ci/iterations-localnet.sh
Executable file
61
ci/iterations-localnet.sh
Executable file
@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
testCmd="$*"
|
||||
genPipeline=false
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Clear cached json keypair files
|
||||
rm -rf "$HOME/.config/solana"
|
||||
|
||||
source ci/_
|
||||
export RUST_BACKTRACE=1
|
||||
export RUSTFLAGS="-D warnings"
|
||||
export PATH=$PWD/target/debug:$PATH
|
||||
export USE_INSTALL=1
|
||||
|
||||
if [[ -n $BUILDKITE && -z $testCmd ]]; then
|
||||
genPipeline=true
|
||||
echo "
|
||||
steps:
|
||||
"
|
||||
fi
|
||||
|
||||
build() {
|
||||
$genPipeline && return
|
||||
source ci/rust-version.sh stable
|
||||
source scripts/ulimit-n.sh
|
||||
_ cargo +$rust_stable build
|
||||
}
|
||||
|
||||
runTest() {
|
||||
declare runTestName="$1"
|
||||
declare runTestCmd="$2"
|
||||
if $genPipeline; then
|
||||
echo "
|
||||
- command: \"$0 '$runTestCmd'\"
|
||||
name: \"$runTestName\"
|
||||
timeout_in_minutes: 45
|
||||
"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -n $testCmd && "$testCmd" != "$runTestCmd" ]]; then
|
||||
echo Skipped "$runTestName"...
|
||||
return
|
||||
fi
|
||||
#shellcheck disable=SC2068 # Don't want to double quote $runTestCmd
|
||||
$runTestCmd
|
||||
}
|
||||
|
||||
build
|
||||
|
||||
runTest "basic" \
|
||||
"ci/localnet-sanity.sh -i 128"
|
||||
|
||||
runTest "restart" \
|
||||
"ci/localnet-sanity.sh -i 128 -k 16"
|
||||
|
||||
runTest "incremental restart, extra node" \
|
||||
"ci/localnet-sanity.sh -i 128 -k 16 -R -x"
|
@ -1,51 +1,301 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
# Perform a quick sanity test on a leader, drone, validator and client running
|
||||
# locally on the same machine
|
||||
#
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
skipSetup=false
|
||||
iterations=1
|
||||
restartInterval=never
|
||||
rollingRestart=false
|
||||
extraNodes=0
|
||||
walletRpcPort=:8899
|
||||
|
||||
usage() {
|
||||
exitcode=0
|
||||
if [[ -n "$1" ]]; then
|
||||
exitcode=1
|
||||
echo "Error: $*"
|
||||
fi
|
||||
cat <<EOF
|
||||
usage: $0 [options...]
|
||||
|
||||
Start a local cluster and run sanity on it
|
||||
|
||||
options:
|
||||
-i [number] - Number of times to run sanity (default: $iterations)
|
||||
-k [number] - Restart the cluster after this number of sanity iterations (default: $restartInterval)
|
||||
-R - Restart the cluster by incrementially stopping and restarting
|
||||
nodes (at the cadence specified by -k). When disabled all
|
||||
nodes will be first killed then restarted (default: $rollingRestart)
|
||||
-b - Disable leader rotation
|
||||
-x - Add an extra validator (may be supplied multiple times)
|
||||
-r - Select the RPC endpoint hosted by a node that starts as
|
||||
a validator node. If unspecified the RPC endpoint hosted by
|
||||
the bootstrap validator will be used.
|
||||
-c - Reuse existing node/ledger configuration from a previous sanity
|
||||
run
|
||||
|
||||
EOF
|
||||
exit $exitcode
|
||||
}
|
||||
|
||||
cd "$(dirname "$0")"/..
|
||||
source ci/upload_ci_artifact.sh
|
||||
|
||||
while getopts "ch?i:k:brxR" opt; do
|
||||
case $opt in
|
||||
h | \?)
|
||||
usage
|
||||
;;
|
||||
c)
|
||||
skipSetup=true
|
||||
;;
|
||||
i)
|
||||
iterations=$OPTARG
|
||||
;;
|
||||
k)
|
||||
restartInterval=$OPTARG
|
||||
;;
|
||||
x)
|
||||
extraNodes=$((extraNodes + 1))
|
||||
;;
|
||||
r)
|
||||
walletRpcPort=":18899"
|
||||
;;
|
||||
R)
|
||||
rollingRestart=true
|
||||
;;
|
||||
*)
|
||||
usage "Error: unhandled option: $opt"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
source ci/upload-ci-artifact.sh
|
||||
source scripts/configure-metrics.sh
|
||||
source multinode-demo/common.sh
|
||||
|
||||
multinode-demo/setup.sh
|
||||
nodes=(
|
||||
"multinode-demo/faucet.sh"
|
||||
"multinode-demo/bootstrap-validator.sh \
|
||||
--no-restart \
|
||||
--init-complete-file init-complete-node1.log \
|
||||
--dynamic-port-range 8000-8050"
|
||||
"multinode-demo/validator.sh \
|
||||
--enable-rpc-exit \
|
||||
--no-restart \
|
||||
--dynamic-port-range 8050-8100
|
||||
--init-complete-file init-complete-node2.log \
|
||||
--rpc-port 18899"
|
||||
)
|
||||
|
||||
if [[ extraNodes -gt 0 ]]; then
|
||||
for i in $(seq 1 $extraNodes); do
|
||||
portStart=$((8100 + i * 50))
|
||||
portEnd=$((portStart + 49))
|
||||
nodes+=(
|
||||
"multinode-demo/validator.sh \
|
||||
--no-restart \
|
||||
--dynamic-port-range $portStart-$portEnd
|
||||
--label dyn$i \
|
||||
--init-complete-file init-complete-node$((2 + i)).log"
|
||||
)
|
||||
done
|
||||
fi
|
||||
numNodes=$((2 + extraNodes))
|
||||
|
||||
backgroundCommands="drone leader validator validator-x"
|
||||
pids=()
|
||||
logs=()
|
||||
|
||||
for cmd in $backgroundCommands; do
|
||||
getNodeLogFile() {
|
||||
declare nodeIndex=$1
|
||||
declare cmd=$2
|
||||
declare baseCmd
|
||||
baseCmd=$(basename "${cmd// */}" .sh)
|
||||
echo "log-$baseCmd-$nodeIndex.txt"
|
||||
}
|
||||
|
||||
startNode() {
|
||||
declare nodeIndex=$1
|
||||
declare cmd=$2
|
||||
echo "--- Start $cmd"
|
||||
rm -f log-"$cmd".txt
|
||||
multinode-demo/"$cmd".sh > log-"$cmd".txt 2>&1 &
|
||||
declare log
|
||||
log=$(getNodeLogFile "$nodeIndex" "$cmd")
|
||||
rm -f "$log"
|
||||
$cmd > "$log" 2>&1 &
|
||||
declare pid=$!
|
||||
pids+=("$pid")
|
||||
echo "pid: $pid"
|
||||
done
|
||||
echo "log: $log"
|
||||
}
|
||||
|
||||
killBackgroundCommands() {
|
||||
set +e
|
||||
for pid in "${pids[@]}"; do
|
||||
if kill "$pid"; then
|
||||
wait "$pid"
|
||||
else
|
||||
echo -e "^^^ +++\\nWarning: unable to kill $pid"
|
||||
waitForNodeToInit() {
|
||||
declare initCompleteFile=$1
|
||||
while [[ ! -r $initCompleteFile ]]; do
|
||||
if [[ $SECONDS -ge 240 ]]; then
|
||||
echo "^^^ +++"
|
||||
echo "Error: $initCompleteFile not found in $SECONDS seconds"
|
||||
exit 1
|
||||
fi
|
||||
echo "Waiting for $initCompleteFile ($SECONDS)..."
|
||||
sleep 2
|
||||
done
|
||||
echo "Found $initCompleteFile"
|
||||
}
|
||||
|
||||
initCompleteFiles=()
|
||||
waitForAllNodesToInit() {
|
||||
echo "--- ${#initCompleteFiles[@]} nodes booting"
|
||||
SECONDS=
|
||||
for initCompleteFile in "${initCompleteFiles[@]}"; do
|
||||
waitForNodeToInit "$initCompleteFile"
|
||||
done
|
||||
echo "All nodes finished booting in $SECONDS seconds"
|
||||
}
|
||||
|
||||
startNodes() {
|
||||
declare addLogs=false
|
||||
if [[ ${#logs[@]} -eq 0 ]]; then
|
||||
addLogs=true
|
||||
fi
|
||||
initCompleteFiles=()
|
||||
maybeExpectedGenesisHash=
|
||||
for i in $(seq 0 $((${#nodes[@]} - 1))); do
|
||||
declare cmd=${nodes[$i]}
|
||||
|
||||
if [[ "$i" -ne 0 ]]; then # 0 == faucet, skip it
|
||||
declare initCompleteFile="init-complete-node$i.log"
|
||||
rm -f "$initCompleteFile"
|
||||
initCompleteFiles+=("$initCompleteFile")
|
||||
fi
|
||||
startNode "$i" "$cmd $maybeExpectedGenesisHash"
|
||||
if $addLogs; then
|
||||
logs+=("$(getNodeLogFile "$i" "$cmd")")
|
||||
fi
|
||||
|
||||
# 1 == bootstrap validator, wait until it boots before starting
|
||||
# other validators
|
||||
if [[ "$i" -eq 1 ]]; then
|
||||
SECONDS=
|
||||
waitForNodeToInit "$initCompleteFile"
|
||||
|
||||
(
|
||||
set -x
|
||||
$solana_cli --keypair config/bootstrap-validator/identity-keypair.json \
|
||||
--url http://127.0.0.1:8899 genesis-hash
|
||||
) | tee genesis-hash.log
|
||||
maybeExpectedGenesisHash="--expected-genesis-hash $(tail -n1 genesis-hash.log)"
|
||||
fi
|
||||
done
|
||||
|
||||
waitForAllNodesToInit
|
||||
}
|
||||
|
||||
killNode() {
|
||||
declare pid=$1
|
||||
set +e
|
||||
if kill "$pid"; then
|
||||
echo "Waiting for $pid to exit..."
|
||||
wait "$pid"
|
||||
echo "$pid exited with $?"
|
||||
fi
|
||||
set -e
|
||||
}
|
||||
|
||||
killNodes() {
|
||||
[[ ${#pids[@]} -gt 0 ]] || return
|
||||
|
||||
# Try to use the RPC exit API to cleanly exit the first two nodes
|
||||
# (dynamic nodes, -x, are just killed since their RPC port is not known)
|
||||
echo "--- RPC exit"
|
||||
for port in 8899 18899; do
|
||||
(
|
||||
set -x
|
||||
curl --retry 5 --retry-delay 2 --retry-connrefused \
|
||||
-X POST -H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":1, "method":"validatorExit"}' \
|
||||
http://localhost:$port
|
||||
)
|
||||
done
|
||||
|
||||
# Give the nodes a splash of time to cleanly exit before killing them
|
||||
sleep 2
|
||||
|
||||
echo "--- Killing nodes: ${pids[*]}"
|
||||
for pid in "${pids[@]}"; do
|
||||
killNode "$pid"
|
||||
done
|
||||
echo "done killing nodes"
|
||||
pids=()
|
||||
}
|
||||
|
||||
rollingNodeRestart() {
|
||||
if [[ ${#logs[@]} -ne ${#nodes[@]} ]]; then
|
||||
echo "^^^ +++"
|
||||
echo "Error: log/nodes array length mismatch"
|
||||
exit 1
|
||||
fi
|
||||
if [[ ${#pids[@]} -ne ${#nodes[@]} ]]; then
|
||||
echo "^^^ +++"
|
||||
echo "Error: pids/nodes array length mismatch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
declare oldPids=("${pids[@]}")
|
||||
for i in $(seq 0 $((${#logs[@]} - 1))); do
|
||||
declare pid=${oldPids[$i]}
|
||||
declare cmd=${nodes[$i]}
|
||||
if [[ $i -eq 0 ]]; then
|
||||
# First cmd should be the faucet, don't restart it.
|
||||
[[ "$cmd" = "multinode-demo/faucet.sh" ]]
|
||||
pids+=("$pid")
|
||||
else
|
||||
echo "--- Restarting $pid: $cmd"
|
||||
killNode "$pid"
|
||||
# Delay 20 seconds to ensure the remaining cluster nodes will
|
||||
# hit CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS (currently 15 seconds) for the
|
||||
# node that was just stopped
|
||||
echo "(sleeping for 20 seconds)"
|
||||
sleep 20
|
||||
|
||||
declare initCompleteFile="init-complete-node$i.log"
|
||||
rm -f "$initCompleteFile"
|
||||
initCompleteFiles+=("$initCompleteFile")
|
||||
startNode "$i" "$cmd"
|
||||
fi
|
||||
done
|
||||
|
||||
# 'Atomically' remove the old pids from the pids array
|
||||
declare oldPidsList
|
||||
oldPidsList="$(printf ":%s" "${oldPids[@]}"):"
|
||||
declare newPids=("${pids[0]}") # 0 = faucet pid
|
||||
for pid in "${pids[@]}"; do
|
||||
[[ $oldPidsList =~ :$pid: ]] || {
|
||||
newPids+=("$pid")
|
||||
}
|
||||
done
|
||||
pids=("${newPids[@]}")
|
||||
|
||||
waitForAllNodesToInit
|
||||
}
|
||||
|
||||
verifyLedger() {
|
||||
for ledger in bootstrap-validator validator; do
|
||||
echo "--- $ledger ledger verification"
|
||||
(
|
||||
set -x
|
||||
$solana_ledger_tool --ledger "$SOLANA_CONFIG_DIR"/$ledger verify
|
||||
) || flag_error
|
||||
done
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
exitcode=$?
|
||||
killBackgroundCommands
|
||||
killNodes
|
||||
|
||||
set +e
|
||||
|
||||
echo "--- Upload artifacts"
|
||||
for cmd in $backgroundCommands; do
|
||||
declare logfile=log-$cmd.txt
|
||||
upload_ci_artifact "$logfile"
|
||||
tail "$logfile"
|
||||
for log in "${logs[@]}"; do
|
||||
upload-ci-artifact "$log"
|
||||
tail "$log"
|
||||
done
|
||||
|
||||
exit $exitcode
|
||||
@ -55,39 +305,88 @@ trap shutdown EXIT INT
|
||||
|
||||
set -e
|
||||
|
||||
declare iteration=1
|
||||
|
||||
flag_error() {
|
||||
echo Failed
|
||||
echo "Failed (iteration: $iteration/$iterations)"
|
||||
echo "^^^ +++"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "--- Wallet sanity"
|
||||
(
|
||||
set -x
|
||||
scripts/wallet-sanity.sh
|
||||
) || flag_error
|
||||
if ! $skipSetup; then
|
||||
clear_config_dir "$SOLANA_CONFIG_DIR"
|
||||
multinode-demo/setup.sh --hashes-per-tick sleep
|
||||
else
|
||||
verifyLedger
|
||||
fi
|
||||
startNodes
|
||||
lastTransactionCount=
|
||||
while [[ $iteration -le $iterations ]]; do
|
||||
echo "--- Node count ($iteration)"
|
||||
(
|
||||
set -x
|
||||
client_keypair=/tmp/client-id.json-$$
|
||||
$solana_keygen new --no-passphrase -fso $client_keypair || exit $?
|
||||
$solana_gossip spy -n 127.0.0.1:8001 --num-nodes-exactly $numNodes || exit $?
|
||||
rm -rf $client_keypair
|
||||
) || flag_error
|
||||
|
||||
echo "--- Node count"
|
||||
(
|
||||
source multinode-demo/common.sh
|
||||
set -x
|
||||
client_id=/tmp/client-id.json-$$
|
||||
$solana_keygen -o $client_id
|
||||
$solana_bench_tps --identity $client_id --num-nodes 3 --reject-extra-nodes --converge-only
|
||||
rm -rf $client_id
|
||||
) || flag_error
|
||||
echo "--- RPC API: bootstrap-validator getTransactionCount ($iteration)"
|
||||
(
|
||||
set -x
|
||||
curl --retry 5 --retry-delay 2 --retry-connrefused \
|
||||
-X POST -H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' \
|
||||
-o log-transactionCount.txt \
|
||||
http://localhost:8899
|
||||
cat log-transactionCount.txt
|
||||
) || flag_error
|
||||
|
||||
killBackgroundCommands
|
||||
echo "--- RPC API: validator getTransactionCount ($iteration)"
|
||||
(
|
||||
set -x
|
||||
curl --retry 5 --retry-delay 2 --retry-connrefused \
|
||||
-X POST -H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' \
|
||||
http://localhost:18899
|
||||
) || flag_error
|
||||
|
||||
echo "--- Ledger verification"
|
||||
(
|
||||
source multinode-demo/common.sh
|
||||
set -x
|
||||
cp -R "$SOLANA_CONFIG_DIR"/ledger /tmp/ledger-$$
|
||||
$solana_ledger_tool --ledger /tmp/ledger-$$ verify
|
||||
rm -rf /tmp/ledger-$$
|
||||
) || flag_error
|
||||
# Verify transaction count as reported by the bootstrap-validator node is advancing
|
||||
transactionCount=$(sed -e 's/{"jsonrpc":"2.0","result":\([0-9]*\),"id":1}/\1/' log-transactionCount.txt)
|
||||
if [[ -n $lastTransactionCount ]]; then
|
||||
echo "--- Transaction count check: $lastTransactionCount < $transactionCount"
|
||||
if [[ $lastTransactionCount -ge $transactionCount ]]; then
|
||||
echo "Error: Transaction count is not advancing"
|
||||
echo "* lastTransactionCount: $lastTransactionCount"
|
||||
echo "* transactionCount: $transactionCount"
|
||||
flag_error
|
||||
fi
|
||||
fi
|
||||
lastTransactionCount=$transactionCount
|
||||
|
||||
echo "--- Wallet sanity ($iteration)"
|
||||
(
|
||||
set -x
|
||||
timeout 60s scripts/wallet-sanity.sh --url http://127.0.0.1"$walletRpcPort"
|
||||
) || flag_error
|
||||
|
||||
iteration=$((iteration + 1))
|
||||
|
||||
if [[ $restartInterval != never && $((iteration % restartInterval)) -eq 0 ]]; then
|
||||
if $rollingRestart; then
|
||||
rollingNodeRestart
|
||||
else
|
||||
killNodes
|
||||
verifyLedger
|
||||
startNodes
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
killNodes
|
||||
verifyLedger
|
||||
|
||||
echo +++
|
||||
echo Ok
|
||||
echo "Ok ($iterations iterations)"
|
||||
|
||||
exit 0
|
||||
|
71
ci/nits.sh
Executable file
71
ci/nits.sh
Executable file
@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Project nits enforced here
|
||||
#
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
source ci/_
|
||||
|
||||
# Logging hygiene: Please don't print from --lib, use the `log` crate instead
|
||||
declare prints=(
|
||||
'print!'
|
||||
'println!'
|
||||
'eprint!'
|
||||
'eprintln!'
|
||||
'dbg!'
|
||||
)
|
||||
|
||||
# Parts of the tree that are expected to be print free
|
||||
declare print_free_tree=(
|
||||
':core/src/**.rs'
|
||||
':faucet/src/**.rs'
|
||||
':ledger/src/**.rs'
|
||||
':metrics/src/**.rs'
|
||||
':net-utils/src/**.rs'
|
||||
':runtime/src/**.rs'
|
||||
':sdk/bpf/rust/rust-utils/**.rs'
|
||||
':sdk/**.rs'
|
||||
':programs/**.rs'
|
||||
':^**bin**.rs'
|
||||
':^**bench**.rs'
|
||||
':^**test**.rs'
|
||||
':^**/build.rs'
|
||||
)
|
||||
|
||||
if _ git --no-pager grep -n "${prints[@]/#/-e}" -- "${print_free_tree[@]}"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Code readability: please be explicit about the type instead of using
|
||||
# Default::default()
|
||||
#
|
||||
# Ref: https://github.com/solana-labs/solana/issues/2630
|
||||
if _ git --no-pager grep -n 'Default::default()' -- '*.rs'; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Github Issues should be used to track outstanding work items instead of
|
||||
# marking up the code
|
||||
#
|
||||
# Ref: https://github.com/solana-labs/solana/issues/6474
|
||||
#
|
||||
# shellcheck disable=1001
|
||||
declare useGithubIssueInsteadOf=(
|
||||
X\XX
|
||||
T\BD
|
||||
F\IXME
|
||||
#T\ODO # TODO: Disable TODOs once all other TODOs are purged
|
||||
)
|
||||
|
||||
if _ git --no-pager grep -n --max-depth=0 "${useGithubIssueInsteadOf[@]/#/-e }" -- '*.rs' '*.sh' '*.md'; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# TODO: Remove this `git grep` once TODOs are banned above
|
||||
# (this command is only used to highlight the current offenders)
|
||||
_ git --no-pager grep -n --max-depth=0 "-e TODO" -- '*.rs' '*.sh' '*.md' || true
|
||||
echo "^^^ +++"
|
||||
# END TODO
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user