Compare commits
1527 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
19
.buildkite/env/secrets.ejson
vendored
@ -1,14 +1,15 @@
|
||||
{
|
||||
"_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f",
|
||||
"environment": {
|
||||
"CODECOV_TOKEN": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:ks2/ElgxwgxqgmFcxTHANNLmj23YH74h:U4uzRONRfiQyqy6HrPQ/e7OnBUY4HkW37R0iekkF3KJ9UGnHqT1UvwgVbDqLahtDIJ4rWw==]",
|
||||
"CRATES_IO_TOKEN": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:lKMh3aLW+jyRrfS/c7yvkpB+TaPhXqLq:j0v27EbaPgwRdHZAbsM0FlAnt3r9ScQrFbWJYOAZtM3qestEiByTlKpZ0eyF/823]",
|
||||
"GITHUB_TOKEN": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:Ll78c3jGpYqnTwR7HJq3mNNUC7pOv9Lu:GrInO2r8MjmP5c54szkyygdsrW5KQYkDgJQUVyFEPyG8SWfchyM9Gur8RV0a+cdwuxNkHLi4U2M=]",
|
||||
"INFLUX_DATABASE": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:IlH/ZLTXv3SwlY3TVyAPCX2KzLRY6iG3:gGmUGSU/kCfR/mTwKONaUC/X]",
|
||||
"INFLUX_PASSWORD": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:o2qm95GU4VrrcC4OU06jjPvCwKZy/CZF:OW2ga3kLOQJvaDEdGRJ+gn3L2ckFm8AJZtv9wj/GeUIKDH2A4uBPTHsAH9PMe6zujpuHGk3qbeg=]",
|
||||
"INFLUX_USERNAME": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:yDWW/uIHsJqOTDYskZoSx3pzoB1vztWY:2z31oTA3g0Xs9fCczGNJRcx8xf/hFCed]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:RqRaHlYUvGPNFJa6gmciaYM3tRJTURUH:q78/3GTHCN3Uqx9z4nOBjPZcO1lOazNoB/mdhGRDFsnAqVd2hU8zbKkqLrZfLlGqyD8WQOFuw5oTJR9qWg6L9LcOyj3pGL8jWF2yjgZxdtNMXnkbSrCWLooWBBLT61jYQnEwg73gT8ld3Q8EVv3T+MeSMu6FnPz+0+bqQCAGgfqksP4hsUAJGzgZu+i0tNOdlT7fxnh5KJK/yFM/CKgN2sRwEjukA9hXsffyB61g2zqzTDJxCUDLbCVrCkA/bfUk7Of/t0W5t0nK1H3oyGZEc/lRMauCknDBka3Gz11dVss2QT19WQNh0u7bHVaT/U4lepX1j9Zv]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_apple_darwin": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:wFDl3INEnA3EQDHRX40avqGe1OMoJxyy:6ncCRVRTIRuYI5o/gayeuWCudWvmKNYr8KEHAWeTq34a5bdcKInBdKhjmjX+wLHqsEwQ5gcyhcxy4Ri2mbuN6AHazfZOZlubQkGlyUOAIYO5D5jkbyIh40DAtjVzo1MD/0HsW9zdGOzqUKp5xJJeDsbR4F153jbxa7fvwF90Q4UQjYFTKAtExEmHtDGSJG48ToVwTabTV/OnISMIggDZBviIv2QWHvXgK07b2mUj34rHJywEDGN1nj5rITTDdUeRcB1x4BAMOe94kTFPSTaj/OszvYlGECt8rkKFqbm092qL+XLfiBaImqe/WJHRCnAj6Don]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_pc_windows_msvc": "EJ[1:8iZ6baJB4fbBV+XDsrUooyGAnGL/8Ol+4Qd0zKh5YjI=:wAh+dBuZopv6vruVOYegUcq/aBnbksT1:qIJfCfDvDWiqicMOkmbJs/0n7UJLKNmgMQaKzeQ8J7Q60YpXbtWzKVW3tS6lzlgf64m3MrPXyo1C+mWh6jkjsb18T/OfggZy1ZHM4AcsOC6/ldUkV5YtuxUQuAmd5jCuV/R7iuYY8Z66AcfAevlb+bnLpgIifdA8fh/IktOo58nZUQwZDdppAacmftsLc6Frn5Er6A6+EXpxK1nmnlmLJ4AJztqlh6X0r+JvE2O7qeoZUXrIegnkxo7Aay7I/dd8zdYpp7ICSiTEtfVN/xNIu/5QmTRU7gWoz7cPl9epq4aiEALzPOzb6KVOiRcsOg+TlFvLQ71Ik5o=]"
|
||||
"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,4 +1,4 @@
|
||||
root: ./book/src
|
||||
root: ./docs/src
|
||||
|
||||
structure:
|
||||
readme: introduction.md
|
||||
|
2
.github/stale.yml
vendored
@ -1,7 +1,7 @@
|
||||
only: pulls
|
||||
|
||||
# Number of days of inactivity before a pull request becomes stale
|
||||
daysUntilStale: 30
|
||||
daysUntilStale: 7
|
||||
|
||||
# Number of days of inactivity before a stale pull request is closed
|
||||
daysUntilClose: 7
|
||||
|
6
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
/book/html/
|
||||
/book/src/tests.ok
|
||||
/docs/html/
|
||||
/docs/src/tests.ok
|
||||
/docs/src/.gitbook/assets/*.svg
|
||||
/farf/
|
||||
/solana-release/
|
||||
/solana-release.tar.bz2
|
||||
@ -15,6 +16,7 @@
|
||||
# log files
|
||||
*.log
|
||||
log-*.txt
|
||||
log-*/
|
||||
|
||||
# intellij files
|
||||
/.idea/
|
||||
|
48
.mergify.yml
@ -19,59 +19,35 @@ pull_request_rules:
|
||||
label:
|
||||
add:
|
||||
- automerge
|
||||
- name: v0.16 backport
|
||||
- name: v0.23 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.16
|
||||
- label=v0.23
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.16
|
||||
- name: v0.17 backport
|
||||
- v0.23
|
||||
- name: v1.0 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.17
|
||||
- label=v1.0
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.17
|
||||
- name: v0.18 backport
|
||||
- v1.0
|
||||
- name: v1.1 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.18
|
||||
- label=v1.1
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.18
|
||||
- name: v0.19 backport
|
||||
- v1.1
|
||||
- name: v1.2 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.19
|
||||
- label=v1.2
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.19
|
||||
- name: v0.20 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.20
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.20
|
||||
- name: v0.21 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.21
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.21
|
||||
- name: v0.22 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.22
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.22
|
||||
- v1.2
|
||||
|
@ -3,11 +3,10 @@ os:
|
||||
|
||||
language: rust
|
||||
rust:
|
||||
- 1.37.0
|
||||
- stable
|
||||
|
||||
install:
|
||||
- source ci/rust-version.sh
|
||||
- test $rust_stable = $TRAVIS_RUST_VERSION # Update .travis.yml rust version above when this fails
|
||||
|
||||
script:
|
||||
- source ci/env.sh
|
||||
@ -16,7 +15,7 @@ script:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v\d+\.\d+$/
|
||||
- /^v\d+\.\d+/
|
||||
|
||||
notifications:
|
||||
slack:
|
||||
|
270
CONTRIBUTING.md
@ -1,23 +1,41 @@
|
||||
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.
|
||||
|
||||
Pull Requests
|
||||
---
|
||||
## Pull Requests
|
||||
|
||||
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 cherrypick it. Open a PR to start the review
|
||||
process and then jump back to your original branch to keep making progress. Once the commit
|
||||
is merged, you can use git-rebase to purge it from your original branch.
|
||||
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.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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
|
||||
@ -25,26 +43,137 @@ $ 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,000 lines of
|
||||
changes is about the most you should ask a Solana maintainer to review.
|
||||
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.2 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.
|
||||
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.
|
||||
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.
|
||||
|
||||
Draft Pull Requests
|
||||
---
|
||||
### 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
|
||||
@ -52,67 +181,68 @@ 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.
|
||||
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
|
||||
---
|
||||
## 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 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:
|
||||
* All Rust code is linted with Clippy. If you'd prefer to ignore its advice, do
|
||||
so explicitly:
|
||||
|
||||
```rust
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
```
|
||||
```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 abbreviating 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.
|
||||
|
||||
[Terms currently in use](book/src/terminology.md)
|
||||
[Terms currently in use](docs/src/terminology.md)
|
||||
|
||||
|
||||
Design Proposals
|
||||
---
|
||||
## Design Proposals
|
||||
|
||||
Solana's architecture is described by a book generated from markdown files in
|
||||
the `book/src/` directory, maintained by an *editor* (currently @garious). To
|
||||
add a design proposal, you'll need to at least propose a change the content
|
||||
under the [Accepted Design
|
||||
Proposals](https://docs.solana.com/book/v/master/proposals) chapter.
|
||||
Here's the full process:
|
||||
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
|
||||
directory `book/src/` and references it from the [table of
|
||||
contents](book/src/SUMMARY.md). Add any relevant *maintainers* to the PR review.
|
||||
`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.
|
||||
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.
|
||||
|
4262
Cargo.lock
generated
129
Cargo.toml
@ -1,127 +1,64 @@
|
||||
[workspace]
|
||||
# The members list excluding the `validator-cuda` crate
|
||||
default-members = [
|
||||
"bench-exchange",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"banking_bench",
|
||||
"chacha-sys",
|
||||
"client",
|
||||
"core",
|
||||
"drone",
|
||||
"validator",
|
||||
"genesis",
|
||||
"genesis_programs",
|
||||
"gossip",
|
||||
"install",
|
||||
"keygen",
|
||||
"kvstore",
|
||||
"ledger-tool",
|
||||
"local_cluster",
|
||||
"logger",
|
||||
"merkle-tree",
|
||||
"measure",
|
||||
"metrics",
|
||||
"programs/bpf_loader_api",
|
||||
"programs/bpf_loader_program",
|
||||
"programs/budget_api",
|
||||
"programs/budget_program",
|
||||
"programs/btc_spv_program",
|
||||
"programs/btc_spv_api",
|
||||
"programs/btc_spv_bin",
|
||||
"programs/config_api",
|
||||
"programs/config_program",
|
||||
"programs/config_tests",
|
||||
"programs/exchange_api",
|
||||
"programs/exchange_program",
|
||||
"programs/failure_program",
|
||||
"programs/move_loader_api",
|
||||
"programs/move_loader_program",
|
||||
"programs/librapay_api",
|
||||
"programs/noop_program",
|
||||
"programs/stake_api",
|
||||
"programs/stake_program",
|
||||
"programs/stake_tests",
|
||||
"programs/storage_api",
|
||||
"programs/storage_program",
|
||||
"programs/token_api",
|
||||
"programs/token_program",
|
||||
"programs/vote_api",
|
||||
"programs/vote_program",
|
||||
"replicator",
|
||||
"runtime",
|
||||
"sdk",
|
||||
"sdk-c",
|
||||
"upload-perf",
|
||||
"netutil",
|
||||
"fixed-buf",
|
||||
"vote-signer",
|
||||
"cli",
|
||||
"rayon-threadlimit",
|
||||
]
|
||||
|
||||
# The default-members list and the `validator-cuda` crate
|
||||
members = [
|
||||
"bench-exchange",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"banking_bench",
|
||||
"banking-bench",
|
||||
"chacha",
|
||||
"chacha-cuda",
|
||||
"chacha-sys",
|
||||
"cli-config",
|
||||
"client",
|
||||
"core",
|
||||
"drone",
|
||||
"faucet",
|
||||
"perf",
|
||||
"validator",
|
||||
"genesis",
|
||||
"genesis_programs",
|
||||
"genesis-programs",
|
||||
"gossip",
|
||||
"install",
|
||||
"keygen",
|
||||
"kvstore",
|
||||
"ledger",
|
||||
"ledger-tool",
|
||||
"local_cluster",
|
||||
"local-cluster",
|
||||
"logger",
|
||||
"log-analyzer",
|
||||
"merkle-tree",
|
||||
"measure",
|
||||
"metrics",
|
||||
"programs/bpf_loader_api",
|
||||
"programs/bpf_loader_program",
|
||||
"programs/budget_api",
|
||||
"programs/budget_program",
|
||||
"programs/btc_spv_program",
|
||||
"programs/btc_spv_api",
|
||||
"net-shaper",
|
||||
"programs/bpf_loader",
|
||||
"programs/budget",
|
||||
"programs/btc_spv",
|
||||
"programs/btc_spv_bin",
|
||||
"programs/config_api",
|
||||
"programs/config_program",
|
||||
"programs/config_tests",
|
||||
"programs/exchange_api",
|
||||
"programs/exchange_program",
|
||||
"programs/failure_program",
|
||||
"programs/move_loader_api",
|
||||
"programs/move_loader_program",
|
||||
"programs/librapay_api",
|
||||
"programs/noop_program",
|
||||
"programs/stake_api",
|
||||
"programs/stake_program",
|
||||
"programs/stake_tests",
|
||||
"programs/storage_api",
|
||||
"programs/storage_program",
|
||||
"programs/token_api",
|
||||
"programs/token_program",
|
||||
"programs/vote_api",
|
||||
"programs/vote_program",
|
||||
"replicator",
|
||||
"programs/config",
|
||||
"programs/exchange",
|
||||
"programs/failure",
|
||||
"programs/noop",
|
||||
"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",
|
||||
"netutil",
|
||||
"fixed-buf",
|
||||
"net-utils",
|
||||
"vote-signer",
|
||||
"cli",
|
||||
"rayon-threadlimit",
|
||||
"validator-cuda",
|
||||
"watchtower",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"programs/bpf",
|
||||
"programs/move_loader",
|
||||
"programs/librapay",
|
||||
]
|
||||
|
18
README.md
@ -23,12 +23,12 @@ It's possible for a centralized database to process 710,000 transactions per sec
|
||||
|
||||
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.
|
||||
|
||||
Architecture
|
||||
Documentation
|
||||
===
|
||||
|
||||
Before you jump into the code, review the online book [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com/book/).
|
||||
Before you jump into the code, review the documentation [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com).
|
||||
|
||||
(The _latest_ development version of the online book is also [available here](https://docs.solana.com/book/v/master/).)
|
||||
(The _latest_ development version of the docs is [available here](https://docs.solana.com/v/master).)
|
||||
|
||||
Release Binaries
|
||||
===
|
||||
@ -78,7 +78,7 @@ $ source $HOME/.cargo/env
|
||||
$ rustup component add rustfmt
|
||||
```
|
||||
|
||||
If your rustc version is lower than 1.37.0, please update it:
|
||||
If your rustc version is lower than 1.39.0, please update it:
|
||||
|
||||
```bash
|
||||
$ rustup update
|
||||
@ -87,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 llvm clang
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang
|
||||
```
|
||||
|
||||
Download the source code:
|
||||
@ -120,16 +121,13 @@ $ cargo test
|
||||
Local Testnet
|
||||
---
|
||||
|
||||
Start your own testnet locally, instructions are in the book [Solana: Blockchain Rebuild for Scale: Getting Started](https://docs.solana.com/book/getting-started).
|
||||
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).
|
||||
|
||||
Remote Testnets
|
||||
---
|
||||
|
||||
We maintain several testnets:
|
||||
* `testnet` - public stable testnet accessible via devnet.solana.com. Runs 24/7
|
||||
|
||||
* `testnet` - public stable testnet accessible via testnet.solana.com. Runs 24/7
|
||||
* `testnet-beta` - public beta channel testnet accessible via beta.testnet.solana.com. Runs 24/7
|
||||
* `testnet-edge` - public edge channel testnet accessible via edge.testnet.solana.com. Runs 24/7
|
||||
|
||||
## Deploy process
|
||||
|
||||
|
27
RELEASE.md
@ -138,30 +138,11 @@ There are three release channels that map to branches as follows:
|
||||
### Update documentation
|
||||
TODO: Documentation update procedure is WIP as we move to gitbook
|
||||
|
||||
Document the new recommended version by updating `book/src/running-replicator.md` and `book/src/validator-testnet.md` on the release (beta) branch to point at the `solana-install` for the upcoming release version.
|
||||
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.
|
||||
|
||||
#### Publish updated Book
|
||||
We maintain three copies of the "book" as official documentation:
|
||||
### Update software on devnet.solana.com
|
||||
|
||||
1) "Book" is the documentation for the latest official release. This should get manually updated whenever a new release is made. It is published here:
|
||||
https://solana-labs.github.io/book/
|
||||
|
||||
2) "Book-edge" tracks the tip of the master branch and updates automatically.
|
||||
https://solana-labs.github.io/book-edge/
|
||||
|
||||
3) "Book-beta" tracks the tip of the beta branch and updates automatically.
|
||||
https://solana-labs.github.io/book-beta/
|
||||
|
||||
To manually trigger an update of the "Book", create a new job of the manual-update-book pipeline.
|
||||
Set the tag of the latest release as the PUBLISH_BOOK_TAG environment variable.
|
||||
```bash
|
||||
PUBLISH_BOOK_TAG=v0.16.6
|
||||
```
|
||||
https://buildkite.com/solana-labs/manual-update-book
|
||||
|
||||
### Update software on testnet.solana.com
|
||||
|
||||
The testnet running on testnet.solana.com is set to use a fixed release tag
|
||||
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.
|
||||
@ -201,4 +182,4 @@ TESTNET_OP=create-and-start
|
||||
### Alert the community
|
||||
|
||||
Notify Discord users on #validator-support that a new release for
|
||||
testnet.solana.com is available
|
||||
devnet.solana.com is available
|
||||
|
39
archiver-lib/Cargo.toml
Normal file
@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "solana-archiver-lib"
|
||||
version = "1.0.2"
|
||||
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.2" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.2" }
|
||||
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.2" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.2" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.2" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.2" }
|
||||
solana-logger = { path = "../logger", version = "1.0.2" }
|
||||
solana-perf = { path = "../perf", version = "1.0.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.2" }
|
||||
solana-core = { path = "../core", version = "1.0.2" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.2" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.2" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.0"
|
||||
|
||||
[lib]
|
||||
name = "solana_archiver_lib"
|
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
@ -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
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "solana-archiver-utils"
|
||||
version = "1.0.2"
|
||||
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.2" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.2" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.2" }
|
||||
solana-logger = { path = "../logger", version = "1.0.2" }
|
||||
solana-perf = { path = "../perf", version = "1.0.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.2" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.0"
|
||||
|
||||
[lib]
|
||||
name = "solana_archiver_utils"
|
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());
|
||||
}
|
||||
}
|
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.2"
|
||||
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.2" }
|
||||
solana-core = { path = "../core", version = "1.0.2" }
|
||||
solana-logger = { path = "../logger", version = "1.0.2" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.2" }
|
||||
solana-archiver-lib = { path = "../archiver-lib", version = "1.0.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.2" }
|
||||
|
147
archiver/src/main.rs
Normal file
@ -0,0 +1,147 @@
|
||||
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::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,
|
||||
);
|
||||
|
||||
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();
|
||||
}
|
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.2"
|
||||
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.2" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.2" }
|
||||
solana-logger = { path = "../logger", version = "1.0.2" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.2" }
|
||||
solana-measure = { path = "../measure", version = "1.0.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.2" }
|
||||
rand = "0.6.5"
|
||||
crossbeam-channel = "0.3"
|
@ -1,21 +1,16 @@
|
||||
#[macro_use]
|
||||
extern crate solana_core;
|
||||
extern crate crossbeam_channel;
|
||||
|
||||
use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana_core::bank_forks::BankForks;
|
||||
use solana_core::banking_stage::{create_test_recorder, BankingStage};
|
||||
use solana_core::blocktree::{get_tmp_ledger_path, Blocktree};
|
||||
use solana_core::cluster_info::ClusterInfo;
|
||||
use solana_core::cluster_info::Node;
|
||||
use solana_core::genesis_utils::{create_genesis_block, GenesisBlockInfo};
|
||||
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_core::service::Service;
|
||||
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;
|
||||
@ -25,7 +20,6 @@ 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::iter;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
@ -41,7 +35,7 @@ fn check_txs(
|
||||
let now = Instant::now();
|
||||
let mut no_bank = false;
|
||||
loop {
|
||||
if let Ok((_bank, (entry, _tick_count))) = receiver.recv_timeout(Duration::from_millis(10))
|
||||
if let Ok((_bank, (entry, _tick_height))) = receiver.recv_timeout(Duration::from_millis(10))
|
||||
{
|
||||
total += entry.transactions.len();
|
||||
}
|
||||
@ -103,21 +97,21 @@ fn main() {
|
||||
const PACKETS_PER_BATCH: usize = 192;
|
||||
let txes = PACKETS_PER_BATCH * num_threads * CHUNKS;
|
||||
let mint_total = 1_000_000_000_000;
|
||||
let GenesisBlockInfo {
|
||||
genesis_block,
|
||||
let GenesisConfigInfo {
|
||||
genesis_config,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_block(mint_total);
|
||||
} = create_genesis_config(mint_total);
|
||||
|
||||
let (verified_sender, verified_receiver) = unbounded();
|
||||
let (vote_sender, vote_receiver) = unbounded();
|
||||
let bank0 = Bank::new(&genesis_block);
|
||||
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_block.hash());
|
||||
let mut transactions = make_accounts_txs(txes, &mint_keypair, genesis_config.hash());
|
||||
|
||||
// fund all the accounts
|
||||
transactions.iter().for_each(|tx| {
|
||||
@ -125,7 +119,7 @@ fn main() {
|
||||
&mint_keypair,
|
||||
&tx.message.account_keys[0],
|
||||
mint_total / txes as u64,
|
||||
genesis_block.hash(),
|
||||
genesis_config.hash(),
|
||||
);
|
||||
let x = bank.process_transaction(&fund);
|
||||
x.unwrap();
|
||||
@ -142,27 +136,22 @@ fn main() {
|
||||
assert!(r.is_ok(), "sanity parallel execution");
|
||||
}
|
||||
bank.clear_signatures();
|
||||
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH)
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let len = x.packets.len();
|
||||
(x, iter::repeat(1).take(len).collect())
|
||||
})
|
||||
.collect();
|
||||
let mut verified: Vec<_> = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
{
|
||||
let blocktree = Arc::new(
|
||||
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"),
|
||||
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, &blocktree);
|
||||
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(
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
verified_receiver,
|
||||
vote_receiver,
|
||||
None,
|
||||
);
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
|
||||
@ -173,9 +162,8 @@ fn main() {
|
||||
// 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 signal_receiver2 = signal_receiver.clone();
|
||||
let mut total = 0;
|
||||
let mut tx_total = 0;
|
||||
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();
|
||||
@ -185,6 +173,7 @@ fn main() {
|
||||
chunk_len,
|
||||
num_threads,
|
||||
};
|
||||
let mut total_sent = 0;
|
||||
for _ in 0..ITERS {
|
||||
let now = Instant::now();
|
||||
let mut sent = 0;
|
||||
@ -209,7 +198,7 @@ fn main() {
|
||||
index,
|
||||
);
|
||||
for xv in v {
|
||||
sent += xv.0.packets.len();
|
||||
sent += xv.packets.len();
|
||||
}
|
||||
verified_sender.send(v.to_vec()).unwrap();
|
||||
}
|
||||
@ -226,7 +215,7 @@ fn main() {
|
||||
sleep(Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
if check_txs(&signal_receiver2, txes / CHUNKS, &poh_recorder) {
|
||||
if check_txs(&signal_receiver, txes / CHUNKS, &poh_recorder) {
|
||||
debug!(
|
||||
"resetting bank {} tx count: {} txs_proc: {}",
|
||||
bank.slot(),
|
||||
@ -235,7 +224,7 @@ fn main() {
|
||||
);
|
||||
assert!(txs_processed < bank.transaction_count());
|
||||
txs_processed = bank.transaction_count();
|
||||
tx_total += duration_as_us(&now.elapsed());
|
||||
tx_total_us += duration_as_us(&now.elapsed());
|
||||
|
||||
let mut poh_time = Measure::start("poh_time");
|
||||
poh_recorder.lock().unwrap().reset(
|
||||
@ -267,20 +256,21 @@ fn main() {
|
||||
poh_time.as_us(),
|
||||
);
|
||||
} else {
|
||||
tx_total += duration_as_us(&now.elapsed());
|
||||
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 += duration_as_us(&now.elapsed());
|
||||
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() {
|
||||
@ -288,13 +278,7 @@ fn main() {
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
tx.signatures[0] = Signature::new(&sig[0..64]);
|
||||
}
|
||||
verified = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH)
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let len = x.packets.len();
|
||||
(x, iter::repeat(1).take(len).collect())
|
||||
})
|
||||
.collect();
|
||||
verified = to_packets_chunked(&transactions.clone(), PACKETS_PER_BATCH);
|
||||
}
|
||||
|
||||
start += chunk_len;
|
||||
@ -302,18 +286,21 @@ fn main() {
|
||||
}
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_total', 'median': '{}'}}",
|
||||
total / ITERS as u64,
|
||||
(1000.0 * 1000.0 * total_sent as f64) / (total_us as f64),
|
||||
);
|
||||
eprintln!(
|
||||
"{{'name': 'banking_bench_tx_total', 'median': '{}'}}",
|
||||
tx_total / ITERS as u64,
|
||||
(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 = Blocktree::destroy(&ledger_path);
|
||||
let _unused = Blockstore::destroy(&ledger_path);
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
[package]
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "0.19.0-pre0"
|
||||
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 = "0.19.0-pre0" }
|
||||
solana-logger = { path = "../logger", version = "0.19.0-pre0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.19.0-pre0" }
|
||||
solana-measure = { path = "../measure", version = "0.19.0-pre0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
|
||||
rand = "0.6.5"
|
||||
crossbeam-channel = "0.3"
|
@ -2,38 +2,33 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "0.19.0-pre0"
|
||||
version = "1.0.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.1.4"
|
||||
bs58 = "0.3.0"
|
||||
clap = "2.32.0"
|
||||
env_logger = "0.6.2"
|
||||
itertools = "0.8.0"
|
||||
itertools = "0.8.2"
|
||||
log = "0.4.8"
|
||||
num-derive = "0.2"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
rand = "0.6.5"
|
||||
rayon = "1.2.0"
|
||||
serde = "1.0.101"
|
||||
serde_derive = "1.0.101"
|
||||
serde_json = "1.0.40"
|
||||
serde_yaml = "0.8.9"
|
||||
# solana-runtime = { path = "../solana/runtime"}
|
||||
solana-core = { path = "../core", version = "0.19.0-pre0" }
|
||||
solana-genesis = { path = "../genesis", version = "0.19.0-pre0" }
|
||||
solana-client = { path = "../client", version = "0.19.0-pre0" }
|
||||
solana-drone = { path = "../drone", version = "0.19.0-pre0" }
|
||||
solana-exchange-api = { path = "../programs/exchange_api", version = "0.19.0-pre0" }
|
||||
solana-exchange-program = { path = "../programs/exchange_program", version = "0.19.0-pre0" }
|
||||
solana-logger = { path = "../logger", version = "0.19.0-pre0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.19.0-pre0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.19.0-pre0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.19.0-pre0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
|
||||
untrusted = "0.7.0"
|
||||
ws = "0.9.0"
|
||||
serde_json = "1.0.46"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.2" }
|
||||
solana-core = { path = "../core", version = "1.0.2" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.2" }
|
||||
solana-client = { path = "../client", version = "1.0.2" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.2" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.0.2" }
|
||||
solana-logger = { path = "../logger", version = "1.0.2" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.2" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.2" }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.2" }
|
||||
|
@ -360,7 +360,7 @@ 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 4 B tokens as profit
|
||||
- Matcher takes 2 B tokens as profit
|
||||
|
||||
Table becomes:
|
||||
|
||||
|
@ -7,32 +7,36 @@ use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana_client::perf_utils::{sample_txs, SampleStats};
|
||||
use solana_core::gen_keys::GenKeys;
|
||||
use solana_drone::drone::request_airdrop_transaction;
|
||||
use solana_exchange_api::exchange_instruction;
|
||||
use solana_exchange_api::exchange_state::*;
|
||||
use solana_exchange_api::id;
|
||||
use solana_genesis::PrimordialAccountDetails;
|
||||
use solana_exchange_program::{exchange_instruction, exchange_state::*, id};
|
||||
use solana_faucet::faucet::request_airdrop_transaction;
|
||||
use solana_genesis::Base64Account;
|
||||
use solana_metrics::datapoint_info;
|
||||
use solana_sdk::client::Client;
|
||||
use solana_sdk::client::SyncClient;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::timing::{duration_as_ms, duration_as_s};
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use solana_sdk::{system_instruction, system_program};
|
||||
use std::cmp;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::mem;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::{sleep, Builder};
|
||||
use std::time::{Duration, Instant};
|
||||
use solana_sdk::{
|
||||
client::{Client, SyncClient},
|
||||
commitment_config::CommitmentConfig,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
timing::{duration_as_ms, duration_as_s},
|
||||
transaction::Transaction,
|
||||
{system_instruction, system_program},
|
||||
};
|
||||
use std::{
|
||||
cmp,
|
||||
collections::{HashMap, VecDeque},
|
||||
fs::File,
|
||||
io::prelude::*,
|
||||
mem,
|
||||
net::SocketAddr,
|
||||
path::Path,
|
||||
process::exit,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
Arc, RwLock,
|
||||
},
|
||||
thread::{sleep, Builder},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
// TODO Chunk length as specified results in a bunch of failures, divide by 10 helps...
|
||||
// Assume 4MB network buffers, and 512 byte packets
|
||||
@ -89,7 +93,7 @@ pub fn create_client_accounts_file(
|
||||
keypairs.iter().for_each(|keypair| {
|
||||
accounts.insert(
|
||||
serde_json::to_string(&keypair.to_bytes().to_vec()).unwrap(),
|
||||
PrimordialAccountDetails {
|
||||
Base64Account {
|
||||
balance: fund_amount,
|
||||
executable: false,
|
||||
owner: system_program::id().to_string(),
|
||||
@ -140,8 +144,7 @@ where
|
||||
let path = Path::new(&client_ids_and_stake_file);
|
||||
let file = File::open(path).unwrap();
|
||||
|
||||
let accounts: HashMap<String, PrimordialAccountDetails> =
|
||||
serde_yaml::from_reader(file).unwrap();
|
||||
let accounts: HashMap<String, Base64Account> = serde_yaml::from_reader(file).unwrap();
|
||||
accounts
|
||||
.into_iter()
|
||||
.map(|(keypair, _)| {
|
||||
@ -175,19 +178,28 @@ where
|
||||
|
||||
info!("Generating {:?} account keys", total_keys);
|
||||
let mut account_keypairs = generate_keypairs(total_keys);
|
||||
let src_pubkeys: Vec<_> = account_keypairs
|
||||
let src_keypairs: Vec<_> = account_keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(|keypair| keypair)
|
||||
.collect();
|
||||
let src_pubkeys: Vec<Pubkey> = src_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.pubkey())
|
||||
.collect();
|
||||
let profit_pubkeys: Vec<_> = account_keypairs
|
||||
|
||||
let profit_keypairs: Vec<_> = account_keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(|keypair| keypair)
|
||||
.collect();
|
||||
let profit_pubkeys: Vec<Pubkey> = profit_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.pubkey())
|
||||
.collect();
|
||||
|
||||
info!("Create {:?} source token accounts", src_pubkeys.len());
|
||||
create_token_accounts(client, &trader_signers, &src_pubkeys);
|
||||
create_token_accounts(client, &trader_signers, &src_keypairs);
|
||||
info!("Create {:?} profit token accounts", profit_pubkeys.len());
|
||||
create_token_accounts(client, &swapper_signers, &profit_pubkeys);
|
||||
create_token_accounts(client, &swapper_signers, &profit_keypairs);
|
||||
|
||||
// Collect the max transaction rate and total tx count seen (single node only)
|
||||
let sample_stats = Arc::new(RwLock::new(Vec::new()));
|
||||
@ -244,7 +256,7 @@ where
|
||||
trace!("Start trader thread");
|
||||
let trader_thread = {
|
||||
let exit_signal = exit_signal.clone();
|
||||
let shared_txs = shared_txs.clone();
|
||||
|
||||
let client = clients[0].clone();
|
||||
Builder::new()
|
||||
.name("solana-exchange-trader".to_string())
|
||||
@ -381,7 +393,10 @@ fn swapper<T>(
|
||||
let mut tries = 0;
|
||||
let mut trade_index = 0;
|
||||
while client
|
||||
.get_balance(&trade_infos[trade_index].trade_account)
|
||||
.get_balance_with_commitment(
|
||||
&trade_infos[trade_index].trade_account,
|
||||
CommitmentConfig::recent(),
|
||||
)
|
||||
.unwrap_or(0)
|
||||
== 0
|
||||
{
|
||||
@ -435,7 +450,7 @@ fn swapper<T>(
|
||||
account_group = (account_group + 1) % account_groups as usize;
|
||||
|
||||
let (blockhash, _fee_calculator) = client
|
||||
.get_recent_blockhash()
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.expect("Failed to get blockhash");
|
||||
let to_swap_txs: Vec<_> = to_swap
|
||||
.par_iter()
|
||||
@ -558,27 +573,39 @@ fn trader<T>(
|
||||
trade_account: trade.pubkey(),
|
||||
order_info,
|
||||
});
|
||||
trades.push((signer, trade.pubkey(), side, src));
|
||||
trades.push((signer, trade, side, src));
|
||||
}
|
||||
account_group = (account_group + 1) % account_groups as usize;
|
||||
|
||||
let (blockhash, _fee_calculator) = client
|
||||
.get_recent_blockhash()
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.expect("Failed to get blockhash");
|
||||
|
||||
trades.chunks(chunk_size).for_each(|chunk| {
|
||||
let trades_txs: Vec<_> = chunk
|
||||
.par_iter()
|
||||
.map(|(signer, trade, side, src)| {
|
||||
let s: &Keypair = &signer;
|
||||
let owner = &signer.pubkey();
|
||||
.map(|(owner, trade, side, src)| {
|
||||
let owner_pubkey = &owner.pubkey();
|
||||
let trade_pubkey = &trade.pubkey();
|
||||
let space = mem::size_of::<ExchangeState>() as u64;
|
||||
Transaction::new_signed_instructions(
|
||||
&[s],
|
||||
&[owner.as_ref(), trade],
|
||||
vec![
|
||||
system_instruction::create_account(owner, trade, 1, space, &id()),
|
||||
system_instruction::create_account(
|
||||
owner_pubkey,
|
||||
trade_pubkey,
|
||||
1,
|
||||
space,
|
||||
&id(),
|
||||
),
|
||||
exchange_instruction::trade_request(
|
||||
owner, trade, *side, pair, tokens, price, src,
|
||||
owner_pubkey,
|
||||
trade_pubkey,
|
||||
*side,
|
||||
pair,
|
||||
tokens,
|
||||
price,
|
||||
src,
|
||||
),
|
||||
],
|
||||
blockhash,
|
||||
@ -634,12 +661,14 @@ fn trader<T>(
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_transfer<T>(sync_client: &T, tx: &Transaction) -> bool
|
||||
fn verify_transaction<T>(sync_client: &T, tx: &Transaction) -> bool
|
||||
where
|
||||
T: SyncClient + ?Sized,
|
||||
{
|
||||
for s in &tx.signatures {
|
||||
if let Ok(Some(r)) = sync_client.get_signature_status(s) {
|
||||
if let Ok(Some(r)) =
|
||||
sync_client.get_signature_status_with_commitment(s, CommitmentConfig::recent())
|
||||
{
|
||||
match r {
|
||||
Ok(_) => {
|
||||
return true;
|
||||
@ -658,16 +687,21 @@ fn verify_funding_transfer<T: SyncClient + ?Sized>(
|
||||
tx: &Transaction,
|
||||
amount: u64,
|
||||
) -> bool {
|
||||
for a in &tx.message().account_keys[1..] {
|
||||
if client.get_balance(a).unwrap_or(0) >= amount {
|
||||
return true;
|
||||
if verify_transaction(client, tx) {
|
||||
for a in &tx.message().account_keys[1..] {
|
||||
if client
|
||||
.get_balance_with_commitment(a, CommitmentConfig::recent())
|
||||
.unwrap_or(0)
|
||||
>= amount
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
|
||||
pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
|
||||
let total = lamports * (dests.len() as u64 + 1);
|
||||
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
|
||||
let mut notfunded: Vec<&Arc<Keypair>> = dests.iter().collect();
|
||||
@ -741,8 +775,9 @@ pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc<Keypair>],
|
||||
to_fund_txs.len(),
|
||||
);
|
||||
|
||||
let (blockhash, _fee_calculator) =
|
||||
client.get_recent_blockhash().expect("blockhash");
|
||||
let (blockhash, _fee_calculator) = client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.expect("blockhash");
|
||||
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
|
||||
tx.sign(&[*k], blockhash);
|
||||
});
|
||||
@ -779,27 +814,41 @@ pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc<Keypair>],
|
||||
});
|
||||
funded.append(&mut new_funded);
|
||||
funded.retain(|(k, b)| {
|
||||
client.get_balance(&k.pubkey()).unwrap_or(0) > lamports && *b > lamports
|
||||
client
|
||||
.get_balance_with_commitment(&k.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap_or(0)
|
||||
> lamports
|
||||
&& *b > lamports
|
||||
});
|
||||
debug!(" Funded: {} left: {}", funded.len(), notfunded.len());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], accounts: &[Pubkey]) {
|
||||
let mut notfunded: Vec<(&Arc<Keypair>, &Pubkey)> = signers.iter().zip(accounts).collect();
|
||||
pub fn create_token_accounts<T: Client>(
|
||||
client: &T,
|
||||
signers: &[Arc<Keypair>],
|
||||
accounts: &[Keypair],
|
||||
) {
|
||||
let mut notfunded: Vec<(&Arc<Keypair>, &Keypair)> = signers.iter().zip(accounts).collect();
|
||||
|
||||
while !notfunded.is_empty() {
|
||||
notfunded.chunks(FUND_CHUNK_LEN).for_each(|chunk| {
|
||||
let mut to_create_txs: Vec<_> = chunk
|
||||
.par_iter()
|
||||
.map(|(signer, new)| {
|
||||
let owner_pubkey = &signer.pubkey();
|
||||
.map(|(from_keypair, new_keypair)| {
|
||||
let owner_pubkey = &from_keypair.pubkey();
|
||||
let space = mem::size_of::<ExchangeState>() as u64;
|
||||
let create_ix =
|
||||
system_instruction::create_account(owner_pubkey, new, 1, space, &id());
|
||||
let request_ix = exchange_instruction::account_request(owner_pubkey, new);
|
||||
let create_ix = system_instruction::create_account(
|
||||
owner_pubkey,
|
||||
&new_keypair.pubkey(),
|
||||
1,
|
||||
space,
|
||||
&id(),
|
||||
);
|
||||
let request_ix =
|
||||
exchange_instruction::account_request(owner_pubkey, &new_keypair.pubkey());
|
||||
(
|
||||
signer,
|
||||
(from_keypair, new_keypair),
|
||||
Transaction::new_unsigned_instructions(vec![create_ix, request_ix]),
|
||||
)
|
||||
})
|
||||
@ -818,12 +867,13 @@ pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], acco
|
||||
let mut retries = 0;
|
||||
while !to_create_txs.is_empty() {
|
||||
let (blockhash, _fee_calculator) = client
|
||||
.get_recent_blockhash()
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.expect("Failed to get blockhash");
|
||||
to_create_txs.par_iter_mut().for_each(|(k, tx)| {
|
||||
let kp: &Keypair = k;
|
||||
tx.sign(&[kp], blockhash);
|
||||
});
|
||||
to_create_txs
|
||||
.par_iter_mut()
|
||||
.for_each(|((from_keypair, to_keypair), tx)| {
|
||||
tx.sign(&[from_keypair.as_ref(), to_keypair], blockhash);
|
||||
});
|
||||
to_create_txs.iter().for_each(|(_, tx)| {
|
||||
client.async_send_transaction(tx.clone()).expect("transfer");
|
||||
});
|
||||
@ -831,11 +881,11 @@ pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], acco
|
||||
let mut waits = 0;
|
||||
while !to_create_txs.is_empty() {
|
||||
sleep(Duration::from_millis(200));
|
||||
to_create_txs.retain(|(_, tx)| !verify_transfer(client, &tx));
|
||||
to_create_txs.retain(|(_, tx)| !verify_transaction(client, &tx));
|
||||
if to_create_txs.is_empty() {
|
||||
break;
|
||||
}
|
||||
debug!(
|
||||
info!(
|
||||
" {} transactions outstanding, waits {:?}",
|
||||
to_create_txs.len(),
|
||||
waits
|
||||
@ -848,7 +898,7 @@ pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], acco
|
||||
|
||||
if !to_create_txs.is_empty() {
|
||||
retries += 1;
|
||||
debug!(" Retry {:?}", retries);
|
||||
info!(" Retry {:?} {} txes left", retries, to_create_txs.len());
|
||||
if retries >= 20 {
|
||||
error!(
|
||||
"create_token_accounts: Too many retries ({}), give up",
|
||||
@ -860,9 +910,13 @@ pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], acco
|
||||
}
|
||||
});
|
||||
|
||||
let mut new_notfunded: Vec<(&Arc<Keypair>, &Pubkey)> = vec![];
|
||||
let mut new_notfunded: Vec<(&Arc<Keypair>, &Keypair)> = vec![];
|
||||
for f in ¬funded {
|
||||
if client.get_balance(&f.1).unwrap_or(0) == 0 {
|
||||
if client
|
||||
.get_balance_with_commitment(&f.1.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap_or(0)
|
||||
== 0
|
||||
{
|
||||
new_notfunded.push(*f)
|
||||
}
|
||||
}
|
||||
@ -918,8 +972,13 @@ fn generate_keypairs(num: u64) -> Vec<Keypair> {
|
||||
rnd.gen_n_keypairs(num)
|
||||
}
|
||||
|
||||
pub fn airdrop_lamports(client: &dyn Client, drone_addr: &SocketAddr, id: &Keypair, amount: u64) {
|
||||
let balance = client.get_balance(&id.pubkey());
|
||||
pub fn airdrop_lamports<T: Client>(
|
||||
client: &T,
|
||||
faucet_addr: &SocketAddr,
|
||||
id: &Keypair,
|
||||
amount: u64,
|
||||
) {
|
||||
let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent());
|
||||
let balance = balance.unwrap_or(0);
|
||||
if balance >= amount {
|
||||
return;
|
||||
@ -930,33 +989,40 @@ pub fn airdrop_lamports(client: &dyn Client, drone_addr: &SocketAddr, id: &Keypa
|
||||
info!(
|
||||
"Airdropping {:?} lamports from {} for {}",
|
||||
amount_to_drop,
|
||||
drone_addr,
|
||||
faucet_addr,
|
||||
id.pubkey(),
|
||||
);
|
||||
|
||||
let mut tries = 0;
|
||||
loop {
|
||||
let (blockhash, _fee_calculator) = client
|
||||
.get_recent_blockhash()
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.expect("Failed to get blockhash");
|
||||
match request_airdrop_transaction(&drone_addr, &id.pubkey(), amount_to_drop, blockhash) {
|
||||
match request_airdrop_transaction(&faucet_addr, &id.pubkey(), amount_to_drop, blockhash) {
|
||||
Ok(transaction) => {
|
||||
let signature = client.async_send_transaction(transaction).unwrap();
|
||||
|
||||
for _ in 0..30 {
|
||||
if let Ok(Some(_)) = client.get_signature_status(&signature) {
|
||||
if let Ok(Some(_)) = client.get_signature_status_with_commitment(
|
||||
&signature,
|
||||
CommitmentConfig::recent(),
|
||||
) {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(100));
|
||||
}
|
||||
if client.get_balance(&id.pubkey()).unwrap_or(0) >= amount {
|
||||
if client
|
||||
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap_or(0)
|
||||
>= amount
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
panic!(
|
||||
"Error requesting airdrop: {:?} to addr: {:?} amount: {}",
|
||||
err, drone_addr, amount
|
||||
err, faucet_addr, amount
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,14 +1,14 @@
|
||||
use clap::{crate_description, crate_name, crate_version, value_t, App, Arg, ArgMatches};
|
||||
use clap::{crate_description, crate_name, value_t, App, Arg, ArgMatches};
|
||||
use solana_core::gen_keys::GenKeys;
|
||||
use solana_drone::drone::DRONE_PORT;
|
||||
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
|
||||
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 drone_addr: SocketAddr,
|
||||
pub faucet_addr: SocketAddr,
|
||||
pub identity: Keypair,
|
||||
pub threads: usize,
|
||||
pub num_nodes: usize,
|
||||
@ -27,7 +27,7 @@ impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
|
||||
drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
|
||||
faucet_addr: SocketAddr::from(([127, 0, 0, 1], FAUCET_PORT)),
|
||||
identity: Keypair::new(),
|
||||
num_nodes: 1,
|
||||
threads: 4,
|
||||
@ -44,10 +44,10 @@ impl Default for Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
|
||||
App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(crate_version!())
|
||||
.version(version)
|
||||
.arg(
|
||||
Arg::with_name("entrypoint")
|
||||
.short("n")
|
||||
@ -59,14 +59,14 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
.help("Cluster entry point; defaults to 127.0.0.1:8001"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("drone")
|
||||
Arg::with_name("faucet")
|
||||
.short("d")
|
||||
.long("drone")
|
||||
.long("faucet")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.default_value("127.0.0.1:9900")
|
||||
.help("Location of the drone; defaults to 127.0.0.1:9900"),
|
||||
.help("Location of the faucet; defaults to 127.0.0.1:9900"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("identity")
|
||||
@ -166,20 +166,22 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
let mut args = Config::default();
|
||||
|
||||
args.entrypoint_addr = solana_netutil::parse_host_port(matches.value_of("entrypoint").unwrap())
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse entrypoint address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
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.drone_addr = solana_netutil::parse_host_port(matches.value_of("drone").unwrap())
|
||||
args.faucet_addr = solana_net_utils::parse_host_port(matches.value_of("faucet").unwrap())
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse drone address: {}", e);
|
||||
eprintln!("failed to parse faucet address: {}", e);
|
||||
exit(1)
|
||||
});
|
||||
|
||||
if matches.is_present("identity") {
|
||||
args.identity = read_keypair(matches.value_of("identity").unwrap())
|
||||
args.identity = read_keypair_file(matches.value_of("identity").unwrap())
|
||||
.expect("can't read client identity");
|
||||
} else {
|
||||
args.identity = {
|
||||
|
@ -5,18 +5,18 @@ 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::KeypairUtil;
|
||||
use solana_sdk::signature::Signer;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
solana_metrics::set_panic_hook("bench-exchange");
|
||||
|
||||
let matches = cli::build_args().get_matches();
|
||||
let matches = cli::build_args(solana_clap_utils::version!()).get_matches();
|
||||
let cli_config = cli::extract_args(&matches);
|
||||
|
||||
let cli::Config {
|
||||
entrypoint_addr,
|
||||
drone_addr,
|
||||
faucet_addr,
|
||||
identity,
|
||||
threads,
|
||||
num_nodes,
|
||||
@ -54,7 +54,7 @@ fn main() {
|
||||
);
|
||||
} else {
|
||||
info!("Connecting to the cluster");
|
||||
let (nodes, _replicators) =
|
||||
let (nodes, _archivers) =
|
||||
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
|
||||
panic!("Failed to discover nodes");
|
||||
});
|
||||
@ -73,7 +73,7 @@ fn main() {
|
||||
const NUM_SIGNERS: u64 = 2;
|
||||
airdrop_lamports(
|
||||
&client,
|
||||
&drone_addr,
|
||||
&faucet_addr,
|
||||
&config.identity,
|
||||
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
use itertools::EitherOrBoth::{Both, Left, Right};
|
||||
use itertools::Itertools;
|
||||
use log::*;
|
||||
use solana_exchange_api::exchange_state::*;
|
||||
use solana_exchange_program::exchange_state::*;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BinaryHeap;
|
||||
|
@ -1,19 +1,22 @@
|
||||
use crate::local_cluster::{ClusterConfig, LocalCluster};
|
||||
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_drone::drone::run_local_drone;
|
||||
use solana_exchange_api::exchange_processor::process_instruction;
|
||||
use solana_exchange_api::id;
|
||||
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_block::create_genesis_block;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
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();
|
||||
|
||||
@ -44,16 +47,16 @@ fn test_exchange_local_cluster() {
|
||||
..ClusterConfig::default()
|
||||
});
|
||||
|
||||
let drone_keypair = Keypair::new();
|
||||
let faucet_keypair = Keypair::new();
|
||||
cluster.transfer(
|
||||
&cluster.funding_keypair,
|
||||
&drone_keypair.pubkey(),
|
||||
&faucet_keypair.pubkey(),
|
||||
2_000_000_000_000,
|
||||
);
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
run_local_drone(drone_keypair, addr_sender, Some(1_000_000_000_000));
|
||||
let drone_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
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, _) =
|
||||
@ -70,7 +73,7 @@ fn test_exchange_local_cluster() {
|
||||
const NUM_SIGNERS: u64 = 2;
|
||||
airdrop_lamports(
|
||||
&client,
|
||||
&drone_addr,
|
||||
&faucet_addr,
|
||||
&config.identity,
|
||||
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
|
||||
);
|
||||
@ -81,8 +84,8 @@ fn test_exchange_local_cluster() {
|
||||
#[test]
|
||||
fn test_exchange_bank_client() {
|
||||
solana_logger::setup();
|
||||
let (genesis_block, identity) = create_genesis_block(100_000_000_000_000);
|
||||
let mut bank = Bank::new(&genesis_block);
|
||||
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)];
|
||||
|
@ -2,13 +2,14 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "0.19.0-pre0"
|
||||
version = "1.0.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
solana-core = { path = "../core", version = "0.19.0-pre0" }
|
||||
solana-logger = { path = "../logger", version = "0.19.0-pre0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.19.0-pre0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.2" }
|
||||
solana-core = { path = "../core", version = "1.0.2" }
|
||||
solana-logger = { path = "../logger", version = "1.0.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.2" }
|
||||
|
@ -1,7 +1,5 @@
|
||||
use clap::{crate_description, crate_name, crate_version, App, Arg};
|
||||
use solana_core::packet::PacketsRecycler;
|
||||
use solana_core::packet::{Packet, Packets, BLOB_SIZE, PACKET_DATA_SIZE};
|
||||
use solana_core::result::Result;
|
||||
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};
|
||||
@ -9,7 +7,7 @@ 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;
|
||||
|
||||
@ -29,7 +27,7 @@ fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
|
||||
let mut num = 0;
|
||||
for p in &msgs.packets {
|
||||
let a = p.meta.addr();
|
||||
assert!(p.meta.size < BLOB_SIZE);
|
||||
assert!(p.meta.size < PACKET_DATA_SIZE);
|
||||
send.send_to(&p.data[..p.meta.size], &a).unwrap();
|
||||
num += 1;
|
||||
}
|
||||
@ -54,7 +52,7 @@ fn main() -> Result<()> {
|
||||
|
||||
let matches = App::new(crate_name!())
|
||||
.about(crate_description!())
|
||||
.version(crate_version!())
|
||||
.version(solana_clap_utils::version!())
|
||||
.arg(
|
||||
Arg::with_name("num-recv-sockets")
|
||||
.long("num-recv-sockets")
|
||||
@ -77,7 +75,7 @@ fn main() -> Result<()> {
|
||||
let mut read_threads = Vec::new();
|
||||
let recycler = PacketsRecycler::default();
|
||||
for _ in 0..num_sockets {
|
||||
let read = solana_netutil::bind_to(port, false).unwrap();
|
||||
let read = solana_net_utils::bind_to(port, false).unwrap();
|
||||
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
|
||||
|
||||
addr = read.local_addr().unwrap();
|
||||
|
@ -2,34 +2,36 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "0.19.0-pre0"
|
||||
version = "1.0.2"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.1.4"
|
||||
bincode = "1.2.1"
|
||||
clap = "2.33.0"
|
||||
log = "0.4.8"
|
||||
rayon = "1.2.0"
|
||||
serde = "1.0.101"
|
||||
serde_derive = "1.0.101"
|
||||
serde_json = "1.0.40"
|
||||
serde_yaml = "0.8.9"
|
||||
solana-core = { path = "../core", version = "0.19.0-pre0" }
|
||||
solana-genesis = { path = "../genesis", version = "0.19.0-pre0" }
|
||||
solana-client = { path = "../client", version = "0.19.0-pre0" }
|
||||
solana-drone = { path = "../drone", version = "0.19.0-pre0" }
|
||||
solana-librapay-api = { path = "../programs/librapay_api", version = "0.19.0-pre0" }
|
||||
solana-logger = { path = "../logger", version = "0.19.0-pre0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.19.0-pre0" }
|
||||
solana-measure = { path = "../measure", version = "0.19.0-pre0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.19.0-pre0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.19.0-pre0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.19.0-pre0" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader_program", version = "0.19.0-pre0" }
|
||||
solana-move-loader-api = { path = "../programs/move_loader_api", version = "0.19.0-pre0" }
|
||||
serde_json = "1.0.46"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.2" }
|
||||
solana-core = { path = "../core", version = "1.0.2" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.2" }
|
||||
solana-client = { path = "../client", version = "1.0.2" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.2" }
|
||||
solana-librapay = { path = "../programs/librapay", version = "1.0.2", optional = true }
|
||||
solana-logger = { path = "../logger", version = "1.0.2" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.2" }
|
||||
solana-measure = { path = "../measure", version = "1.0.2" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.2" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.2" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.2" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader", version = "1.0.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.2.0"
|
||||
serial_test_derive = "0.2.0"
|
||||
serial_test = "0.3.2"
|
||||
serial_test_derive = "0.4.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.2" }
|
||||
|
||||
[features]
|
||||
move = ["solana-librapay", "solana-move-loader-program"]
|
||||
|
@ -1,29 +1,28 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::process::exit;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::{crate_description, crate_name, crate_version, App, Arg, ArgMatches};
|
||||
use solana_drone::drone::DRONE_PORT;
|
||||
use clap::{crate_description, crate_name, App, Arg, ArgMatches};
|
||||
use solana_faucet::faucet::FAUCET_PORT;
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
|
||||
use solana_sdk::signature::{read_keypair_file, Keypair};
|
||||
use std::{net::SocketAddr, process::exit, time::Duration};
|
||||
|
||||
const NUM_LAMPORTS_PER_ACCOUNT_DEFAULT: u64 = 64 * 1024;
|
||||
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 drone_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,
|
||||
}
|
||||
@ -32,18 +31,20 @@ impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
|
||||
drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
|
||||
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: 500_000,
|
||||
thread_batch_sleep_ms: 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: FeeCalculator::default().target_lamports_per_signature,
|
||||
multi_client: true,
|
||||
use_move: false,
|
||||
num_lamports_per_account: NUM_LAMPORTS_PER_ACCOUNT_DEFAULT,
|
||||
}
|
||||
@ -51,9 +52,9 @@ impl Default for Config {
|
||||
}
|
||||
|
||||
/// Defines and builds the CLI args for a run of the benchmark
|
||||
pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> {
|
||||
App::new(crate_name!()).about(crate_description!())
|
||||
.version(crate_version!())
|
||||
.version(version)
|
||||
.arg(
|
||||
Arg::with_name("entrypoint")
|
||||
.short("n")
|
||||
@ -63,12 +64,12 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
.help("Rendezvous with the cluster at this entry point; defaults to 127.0.0.1:8001"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("drone")
|
||||
Arg::with_name("faucet")
|
||||
.short("d")
|
||||
.long("drone")
|
||||
.long("faucet")
|
||||
.value_name("HOST:PORT")
|
||||
.takes_value(true)
|
||||
.help("Location of the drone; defaults to entrypoint:DRONE_PORT"),
|
||||
.help("Location of the faucet; defaults to entrypoint:FAUCET_PORT"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("identity")
|
||||
@ -111,6 +112,11 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
.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")
|
||||
@ -118,6 +124,13 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
.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")
|
||||
@ -170,21 +183,21 @@ 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_netutil::parse_host_port(addr).unwrap_or_else(|e| {
|
||||
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("drone") {
|
||||
args.drone_addr = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| {
|
||||
eprintln!("failed to parse drone address: {}", e);
|
||||
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(matches.value_of("identity").unwrap())
|
||||
args.id = read_keypair_file(matches.value_of("identity").unwrap())
|
||||
.expect("can't read client identity");
|
||||
}
|
||||
|
||||
@ -204,7 +217,15 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
}
|
||||
|
||||
if let Some(s) = matches.value_of("tx_count") {
|
||||
args.tx_count = s.to_string().parse().expect("can't parse tx_account");
|
||||
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") {
|
||||
@ -232,6 +253,7 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
}
|
||||
|
||||
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");
|
||||
|
@ -1,45 +1,47 @@
|
||||
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_multi_client};
|
||||
use solana_genesis::PrimordialAccountDetails;
|
||||
use solana_core::gossip_service::{discover_cluster, get_client, get_multi_client};
|
||||
use solana_genesis::Base64Account;
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::system_program;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
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_filter("solana=info");
|
||||
solana_logger::setup_with_default("solana=info");
|
||||
solana_metrics::set_panic_hook("bench-tps");
|
||||
|
||||
let matches = cli::build_args().get_matches();
|
||||
let matches = cli::build_args(solana_clap_utils::version!()).get_matches();
|
||||
let cli_config = cli::extract_args(&matches);
|
||||
|
||||
let cli::Config {
|
||||
entrypoint_addr,
|
||||
drone_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 {
|
||||
let (keypairs, _) = generate_keypairs(&id, *tx_count as u64 * 2);
|
||||
info!("Generating {} keypairs", keypair_count);
|
||||
let (keypairs, _) = generate_keypairs(&id, keypair_count as u64);
|
||||
let num_accounts = keypairs.len() as u64;
|
||||
let max_fee = FeeCalculator::new(*target_lamports_per_signature).max_lamports_per_signature;
|
||||
let max_fee =
|
||||
FeeCalculator::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;
|
||||
@ -47,7 +49,7 @@ fn main() {
|
||||
keypairs.iter().for_each(|keypair| {
|
||||
accounts.insert(
|
||||
serde_json::to_string(&keypair.to_bytes().to_vec()).unwrap(),
|
||||
PrimordialAccountDetails {
|
||||
Base64Account {
|
||||
balance: num_lamports_per_account,
|
||||
executable: false,
|
||||
owner: system_program::id().to_string(),
|
||||
@ -56,6 +58,7 @@ fn main() {
|
||||
);
|
||||
});
|
||||
|
||||
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();
|
||||
@ -63,29 +66,33 @@ fn main() {
|
||||
return;
|
||||
}
|
||||
|
||||
println!("Connecting to the cluster");
|
||||
let (nodes, _replicators) =
|
||||
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, num_clients) = get_multi_client(&nodes);
|
||||
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))
|
||||
};
|
||||
|
||||
if nodes.len() < num_clients {
|
||||
eprintln!(
|
||||
"Error: Insufficient nodes discovered. Expecting {} or more",
|
||||
num_nodes
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let (keypairs, move_keypairs, keypair_balance) = if *read_from_client_file && !use_move {
|
||||
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();
|
||||
|
||||
let accounts: HashMap<String, PrimordialAccountDetails> =
|
||||
serde_yaml::from_reader(file).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;
|
||||
|
||||
@ -96,17 +103,27 @@ fn main() {
|
||||
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, last_balance)
|
||||
(keypairs, None)
|
||||
} else {
|
||||
generate_and_fund_keypairs(
|
||||
&client,
|
||||
Some(*drone_addr),
|
||||
client.clone(),
|
||||
Some(*faucet_addr),
|
||||
&id,
|
||||
*tx_count,
|
||||
keypair_count,
|
||||
*num_lamports_per_account,
|
||||
*use_move,
|
||||
)
|
||||
@ -116,11 +133,5 @@ fn main() {
|
||||
})
|
||||
};
|
||||
|
||||
do_bench_tps(
|
||||
vec![client],
|
||||
cli_config,
|
||||
keypairs,
|
||||
keypair_balance,
|
||||
move_keypairs,
|
||||
);
|
||||
do_bench_tps(client, cli_config, keypairs, move_keypairs);
|
||||
}
|
||||
|
@ -1,57 +1,67 @@
|
||||
use crate::local_cluster::{ClusterConfig, LocalCluster};
|
||||
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::FULLNODE_PORT_RANGE;
|
||||
use solana_core::cluster_info::VALIDATOR_PORT_RANGE;
|
||||
use solana_core::validator::ValidatorConfig;
|
||||
use solana_drone::drone::run_local_drone;
|
||||
use solana_move_loader_program;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use std::sync::mpsc::channel;
|
||||
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: vec![solana_move_loader_program!()],
|
||||
native_instruction_processors,
|
||||
..ClusterConfig::default()
|
||||
});
|
||||
|
||||
let drone_keypair = Keypair::new();
|
||||
let faucet_keypair = Keypair::new();
|
||||
cluster.transfer(
|
||||
&cluster.funding_keypair,
|
||||
&drone_keypair.pubkey(),
|
||||
&faucet_keypair.pubkey(),
|
||||
100_000_000,
|
||||
);
|
||||
|
||||
let client = create_client(
|
||||
let client = Arc::new(create_client(
|
||||
(cluster.entry_point_info.rpc, cluster.entry_point_info.tpu),
|
||||
FULLNODE_PORT_RANGE,
|
||||
);
|
||||
VALIDATOR_PORT_RANGE,
|
||||
));
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
run_local_drone(drone_keypair, addr_sender, None);
|
||||
let drone_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
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 (keypairs, move_keypairs, _keypair_balance) = generate_and_fund_keypairs(
|
||||
&client,
|
||||
Some(drone_addr),
|
||||
let keypair_count = config.tx_count * config.keypair_multiplier;
|
||||
let (keypairs, move_keypairs) = generate_and_fund_keypairs(
|
||||
client.clone(),
|
||||
Some(faucet_addr),
|
||||
&config.id,
|
||||
config.tx_count,
|
||||
keypair_count,
|
||||
lamports_per_account,
|
||||
config.use_move,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let total = do_bench_tps(vec![client], config, keypairs, 0, move_keypairs);
|
||||
assert!(total > 100);
|
||||
let _total = do_bench_tps(client, config, keypairs, move_keypairs);
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
assert!(_total > 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -69,7 +79,7 @@ fn test_bench_tps_local_cluster_solana() {
|
||||
fn test_bench_tps_local_cluster_move() {
|
||||
let mut config = Config::default();
|
||||
config.tx_count = 100;
|
||||
config.duration = Duration::from_secs(20);
|
||||
config.duration = Duration::from_secs(10);
|
||||
config.use_move = true;
|
||||
|
||||
test_bench_tps_local_cluster(config);
|
@ -1,15 +0,0 @@
|
||||
msc {
|
||||
client,leader,verifier_a,verifier_b,verifier_c;
|
||||
|
||||
client=>leader [ label = "SUBMIT" ] ;
|
||||
leader=>client [ label = "CONFIRMED" ] ;
|
||||
leader=>verifier_a [ label = "CONFIRMED" ] ;
|
||||
leader=>verifier_b [ label = "CONFIRMED" ] ;
|
||||
leader=>verifier_c [ label = "CONFIRMED" ] ;
|
||||
verifier_a=>leader [ label = "VERIFIED" ] ;
|
||||
verifier_b=>leader [ label = "VERIFIED" ] ;
|
||||
leader=>client [ label = "FINALIZED" ] ;
|
||||
leader=>verifier_a [ label = "FINALIZED" ] ;
|
||||
leader=>verifier_b [ label = "FINALIZED" ] ;
|
||||
leader=>verifier_c [ label = "FINALIZED" ] ;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
+------------+
|
||||
| Bank-Merkle|
|
||||
+------------+
|
||||
^ ^
|
||||
/ \
|
||||
+-----------------+ +-------------+
|
||||
| Bank-Diff-Merkle| | Block-Merkle|
|
||||
+-----------------+ +-------------+
|
||||
^ ^
|
||||
/ \
|
||||
+------+ +--------------------------+
|
||||
| Hash | | Previous Bank-Diff-Merkle|
|
||||
+------+ +--------------------------+
|
||||
^ ^
|
||||
/ \
|
||||
+---------------+ +---------------+
|
||||
| Hash(Account1)| | Hash(Account2)|
|
||||
+---------------+ +---------------+
|
@ -1,30 +0,0 @@
|
||||
.--------------------------------------.
|
||||
| Validator |
|
||||
| |
|
||||
.--------. | .-------------------. |
|
||||
| |---->| | |
|
||||
| Client | | | JSON RPC Service | |
|
||||
| |<----| | |
|
||||
`----+---` | `-------------------` |
|
||||
| | ^ |
|
||||
| | | .----------------. | .------------------.
|
||||
| | | | Gossip Service |<----------| Validators |
|
||||
| | | `----------------` | | |
|
||||
| | | ^ | | |
|
||||
| | | | | | .------------. |
|
||||
| | .---+---. .----+---. .-----------. | | | | |
|
||||
| | | Bank |<-+ Replay | | BlobFetch |<------+ Upstream | |
|
||||
| | | Forks | | Stage | | Stage | | | | Validators | |
|
||||
| | `-------` `--------` `--+--------` | | | | |
|
||||
| | ^ ^ | | | `------------` |
|
||||
| | | | v | | |
|
||||
| | | .--+--------. | | |
|
||||
| | | | Blocktree | | | |
|
||||
| | | `-----------` | | .------------. |
|
||||
| | | ^ | | | | |
|
||||
| | | | | | | Downstream | |
|
||||
| | .--+--. .-------+---. | | | Validators | |
|
||||
`-------->| TPU +---->| Broadcast +--------------->| | |
|
||||
| `-----` | Stage | | | `------------` |
|
||||
| `-----------` | `------------------`
|
||||
`--------------------------------------`
|
@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
make -j"$(nproc)" -B svg
|
||||
|
||||
if [[ -n $CI ]]; then
|
||||
# In CI confirm that no svgs need to be built
|
||||
git diff --exit-code
|
||||
fi
|
@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
make -j"$(nproc)" test
|
@ -1,159 +0,0 @@
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1"
|
||||
width="600px" height="330px"
|
||||
viewBox="0 0 600 330"
|
||||
xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges"
|
||||
stroke-width="1" text-rendering="geometricPrecision">
|
||||
<polygon fill="white" points="44,7 74,7 74,16 44,16"/>
|
||||
<text x="60" y="16" textLength="28" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
client
|
||||
</text>
|
||||
<polygon fill="white" points="162,7 196,7 196,16 162,16"/>
|
||||
<text x="180" y="16" textLength="33" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
leader
|
||||
</text>
|
||||
<polygon fill="white" points="274,7 324,7 324,16 274,16"/>
|
||||
<text x="300" y="16" textLength="49" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
verifier_a
|
||||
</text>
|
||||
<polygon fill="white" points="394,7 444,7 444,16 394,16"/>
|
||||
<text x="420" y="16" textLength="49" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
verifier_b
|
||||
</text>
|
||||
<polygon fill="white" points="514,7 564,7 564,16 514,16"/>
|
||||
<text x="540" y="16" textLength="49" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
verifier_c
|
||||
</text>
|
||||
<line x1="60" y1="22" x2="60" y2="50" stroke="black"/>
|
||||
<line x1="180" y1="22" x2="180" y2="50" stroke="black"/>
|
||||
<line x1="300" y1="22" x2="300" y2="50" stroke="black"/>
|
||||
<line x1="420" y1="22" x2="420" y2="50" stroke="black"/>
|
||||
<line x1="540" y1="22" x2="540" y2="50" stroke="black"/>
|
||||
<line x1="60" y1="33" x2="180" y2="33" stroke="black"/>
|
||||
<polygon fill="black" points="180,33 170,39 170,27"/>
|
||||
<polygon fill="white" points="96,23 143,23 143,32 96,32"/>
|
||||
<text x="97" y="32" textLength="45" font-family="Helvetica" font-size="12" fill="black">
|
||||
SUBMIT
|
||||
</text>
|
||||
<line x1="60" y1="50" x2="60" y2="78" stroke="black"/>
|
||||
<line x1="180" y1="50" x2="180" y2="78" stroke="black"/>
|
||||
<line x1="300" y1="50" x2="300" y2="78" stroke="black"/>
|
||||
<line x1="420" y1="50" x2="420" y2="78" stroke="black"/>
|
||||
<line x1="540" y1="50" x2="540" y2="78" stroke="black"/>
|
||||
<line x1="180" y1="61" x2="60" y2="61" stroke="black"/>
|
||||
<polygon fill="black" points="60,61 70,67 70,55"/>
|
||||
<polygon fill="white" points="82,51 157,51 157,60 82,60"/>
|
||||
<text x="83" y="60" textLength="73" font-family="Helvetica" font-size="12" fill="black">
|
||||
CONFIRMED
|
||||
</text>
|
||||
<line x1="60" y1="78" x2="60" y2="106" stroke="black"/>
|
||||
<line x1="180" y1="78" x2="180" y2="106" stroke="black"/>
|
||||
<line x1="300" y1="78" x2="300" y2="106" stroke="black"/>
|
||||
<line x1="420" y1="78" x2="420" y2="106" stroke="black"/>
|
||||
<line x1="540" y1="78" x2="540" y2="106" stroke="black"/>
|
||||
<line x1="180" y1="89" x2="300" y2="89" stroke="black"/>
|
||||
<polygon fill="black" points="300,89 290,95 290,83"/>
|
||||
<polygon fill="white" points="202,79 277,79 277,88 202,88"/>
|
||||
<text x="203" y="88" textLength="73" font-family="Helvetica" font-size="12" fill="black">
|
||||
CONFIRMED
|
||||
</text>
|
||||
<line x1="60" y1="106" x2="60" y2="134" stroke="black"/>
|
||||
<line x1="180" y1="106" x2="180" y2="134" stroke="black"/>
|
||||
<line x1="300" y1="106" x2="300" y2="134" stroke="black"/>
|
||||
<line x1="420" y1="106" x2="420" y2="134" stroke="black"/>
|
||||
<line x1="540" y1="106" x2="540" y2="134" stroke="black"/>
|
||||
<line x1="180" y1="117" x2="420" y2="117" stroke="black"/>
|
||||
<polygon fill="black" points="420,117 410,123 410,111"/>
|
||||
<polygon fill="white" points="262,107 337,107 337,116 262,116"/>
|
||||
<text x="263" y="116" textLength="73" font-family="Helvetica" font-size="12" fill="black">
|
||||
CONFIRMED
|
||||
</text>
|
||||
<line x1="60" y1="134" x2="60" y2="162" stroke="black"/>
|
||||
<line x1="180" y1="134" x2="180" y2="162" stroke="black"/>
|
||||
<line x1="300" y1="134" x2="300" y2="162" stroke="black"/>
|
||||
<line x1="420" y1="134" x2="420" y2="162" stroke="black"/>
|
||||
<line x1="540" y1="134" x2="540" y2="162" stroke="black"/>
|
||||
<line x1="180" y1="145" x2="540" y2="145" stroke="black"/>
|
||||
<polygon fill="black" points="540,145 530,151 530,139"/>
|
||||
<polygon fill="white" points="322,135 397,135 397,144 322,144"/>
|
||||
<text x="323" y="144" textLength="73" font-family="Helvetica" font-size="12" fill="black">
|
||||
CONFIRMED
|
||||
</text>
|
||||
<line x1="60" y1="162" x2="60" y2="190" stroke="black"/>
|
||||
<line x1="180" y1="162" x2="180" y2="190" stroke="black"/>
|
||||
<line x1="300" y1="162" x2="300" y2="190" stroke="black"/>
|
||||
<line x1="420" y1="162" x2="420" y2="190" stroke="black"/>
|
||||
<line x1="540" y1="162" x2="540" y2="190" stroke="black"/>
|
||||
<line x1="300" y1="173" x2="180" y2="173" stroke="black"/>
|
||||
<polygon fill="black" points="180,173 190,179 190,167"/>
|
||||
<polygon fill="white" points="211,163 268,163 268,172 211,172"/>
|
||||
<text x="212" y="172" textLength="55" font-family="Helvetica" font-size="12" fill="black">
|
||||
VERIFIED
|
||||
</text>
|
||||
<line x1="60" y1="190" x2="60" y2="218" stroke="black"/>
|
||||
<line x1="180" y1="190" x2="180" y2="218" stroke="black"/>
|
||||
<line x1="300" y1="190" x2="300" y2="218" stroke="black"/>
|
||||
<line x1="420" y1="190" x2="420" y2="218" stroke="black"/>
|
||||
<line x1="540" y1="190" x2="540" y2="218" stroke="black"/>
|
||||
<line x1="420" y1="201" x2="180" y2="201" stroke="black"/>
|
||||
<polygon fill="black" points="180,201 190,207 190,195"/>
|
||||
<polygon fill="white" points="271,191 328,191 328,200 271,200"/>
|
||||
<text x="272" y="200" textLength="55" font-family="Helvetica" font-size="12" fill="black">
|
||||
VERIFIED
|
||||
</text>
|
||||
<line x1="60" y1="218" x2="60" y2="246" stroke="black"/>
|
||||
<line x1="180" y1="218" x2="180" y2="246" stroke="black"/>
|
||||
<line x1="300" y1="218" x2="300" y2="246" stroke="black"/>
|
||||
<line x1="420" y1="218" x2="420" y2="246" stroke="black"/>
|
||||
<line x1="540" y1="218" x2="540" y2="246" stroke="black"/>
|
||||
<line x1="180" y1="229" x2="60" y2="229" stroke="black"/>
|
||||
<polygon fill="black" points="60,229 70,235 70,223"/>
|
||||
<polygon fill="white" points="88,219 151,219 151,228 88,228"/>
|
||||
<text x="89" y="228" textLength="61" font-family="Helvetica" font-size="12" fill="black">
|
||||
FINALIZED
|
||||
</text>
|
||||
<line x1="60" y1="246" x2="60" y2="274" stroke="black"/>
|
||||
<line x1="180" y1="246" x2="180" y2="274" stroke="black"/>
|
||||
<line x1="300" y1="246" x2="300" y2="274" stroke="black"/>
|
||||
<line x1="420" y1="246" x2="420" y2="274" stroke="black"/>
|
||||
<line x1="540" y1="246" x2="540" y2="274" stroke="black"/>
|
||||
<line x1="180" y1="257" x2="300" y2="257" stroke="black"/>
|
||||
<polygon fill="black" points="300,257 290,263 290,251"/>
|
||||
<polygon fill="white" points="208,247 271,247 271,256 208,256"/>
|
||||
<text x="209" y="256" textLength="61" font-family="Helvetica" font-size="12" fill="black">
|
||||
FINALIZED
|
||||
</text>
|
||||
<line x1="60" y1="274" x2="60" y2="302" stroke="black"/>
|
||||
<line x1="180" y1="274" x2="180" y2="302" stroke="black"/>
|
||||
<line x1="300" y1="274" x2="300" y2="302" stroke="black"/>
|
||||
<line x1="420" y1="274" x2="420" y2="302" stroke="black"/>
|
||||
<line x1="540" y1="274" x2="540" y2="302" stroke="black"/>
|
||||
<line x1="180" y1="285" x2="420" y2="285" stroke="black"/>
|
||||
<polygon fill="black" points="420,285 410,291 410,279"/>
|
||||
<polygon fill="white" points="268,275 331,275 331,284 268,284"/>
|
||||
<text x="269" y="284" textLength="61" font-family="Helvetica" font-size="12" fill="black">
|
||||
FINALIZED
|
||||
</text>
|
||||
<line x1="60" y1="302" x2="60" y2="330" stroke="black"/>
|
||||
<line x1="180" y1="302" x2="180" y2="330" stroke="black"/>
|
||||
<line x1="300" y1="302" x2="300" y2="330" stroke="black"/>
|
||||
<line x1="420" y1="302" x2="420" y2="330" stroke="black"/>
|
||||
<line x1="540" y1="302" x2="540" y2="330" stroke="black"/>
|
||||
<line x1="180" y1="313" x2="540" y2="313" stroke="black"/>
|
||||
<polygon fill="black" points="540,313 530,319 530,307"/>
|
||||
<polygon fill="white" points="328,303 391,303 391,312 328,312"/>
|
||||
<text x="329" y="312" textLength="61" font-family="Helvetica" font-size="12" fill="black">
|
||||
FINALIZED
|
||||
</text>
|
||||
<line x1="60" y1="324" x2="60" y2="330" stroke="black"/>
|
||||
<line x1="180" y1="324" x2="180" y2="330" stroke="black"/>
|
||||
<line x1="300" y1="324" x2="300" y2="330" stroke="black"/>
|
||||
<line x1="420" y1="324" x2="420" y2="330" stroke="black"/>
|
||||
<line x1="540" y1="324" x2="540" y2="330" stroke="black"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 7.7 KiB |
@ -1,183 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="304" width="544" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="304" width="544" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="4" x2="4" y1="8" y2="184"/>
|
||||
<line x1="4" x2="540" y1="8" y2="8"/>
|
||||
<line x1="4" x2="540" y1="184" y2="184"/>
|
||||
<line x1="540" x2="540" y1="8" y2="184"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="28" x2="28" y1="232" y2="296"/>
|
||||
<line x1="28" x2="108" y1="232" y2="232"/>
|
||||
<line x1="28" x2="196" y1="296" y2="296"/>
|
||||
<line x1="108" x2="164" y1="232" y2="232"/>
|
||||
<line x1="164" x2="196" y1="232" y2="232"/>
|
||||
<line x1="196" x2="196" y1="232" y2="296"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="36" x2="36" y1="40" y2="104"/>
|
||||
<line x1="36" x2="180" y1="40" y2="40"/>
|
||||
<line x1="36" x2="108" y1="104" y2="104"/>
|
||||
<line x1="108" x2="108" y1="104" y2="176"/>
|
||||
<line x1="108" x2="124" y1="104" y2="104"/>
|
||||
<line x1="124" x2="124" y1="104" y2="136"/>
|
||||
<line x1="124" x2="180" y1="104" y2="104"/>
|
||||
<line x1="124" x2="364" y1="136" y2="136"/>
|
||||
<line x1="180" x2="180" y1="40" y2="56"/>
|
||||
<line x1="180" x2="180" y1="56" y2="88"/>
|
||||
<line marker-end="url(#triangle)" x1="180" x2="356" y1="56" y2="56"/>
|
||||
<line x1="180" x2="180" y1="88" y2="104"/>
|
||||
<line x1="180" x2="184" y1="88" y2="88"/>
|
||||
<line x1="364" x2="364" y1="136" y2="152"/>
|
||||
<line x1="364" x2="364" y1="152" y2="176"/>
|
||||
<line x1="364" x2="420" y1="152" y2="152"/>
|
||||
<line x1="420" x2="420" y1="104" y2="152"/>
|
||||
<line x1="420" x2="436" y1="104" y2="104"/>
|
||||
<line x1="436" x2="436" y1="104" y2="176"/>
|
||||
<line x1="436" x2="508" y1="104" y2="104"/>
|
||||
<line x1="508" x2="508" y1="40" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="108" x2="108" y1="192" y2="220"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="164" x2="164" y1="152" y2="176"/>
|
||||
<line x1="164" x2="364" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="164" x2="164" y1="192" y2="220"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="192" x2="188" y1="88" y2="88"/>
|
||||
<line x1="192" x2="364" y1="88" y2="88"/>
|
||||
<line x1="364" x2="364" y1="56" y2="88"/>
|
||||
<line x1="364" x2="364" y1="88" y2="104"/>
|
||||
<line x1="364" x2="420" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="348" x2="348" y1="232" y2="296"/>
|
||||
<line x1="348" x2="364" y1="232" y2="232"/>
|
||||
<line x1="348" x2="516" y1="296" y2="296"/>
|
||||
<line x1="364" x2="436" y1="232" y2="232"/>
|
||||
<line x1="436" x2="516" y1="232" y2="232"/>
|
||||
<line x1="516" x2="516" y1="232" y2="296"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="364" x2="364" y1="40" y2="56"/>
|
||||
<line x1="364" x2="508" y1="40" y2="40"/>
|
||||
<line x1="364" x2="360" y1="56" y2="56"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="364" x2="364" y1="192" y2="220"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="436" x2="436" y1="192" y2="220"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="57" y="268">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="65" y="76">
|
||||
Validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="76">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="161" y="268">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="217" y="44">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="321" y="44">
|
||||
0
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="377" y="268">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="393" y="76">
|
||||
Validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="76">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="481" y="268">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 4.8 KiB |
@ -1,322 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="400" width="856" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="400" width="856" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="4" x2="4" y1="8" y2="152"/>
|
||||
<line x1="4" x2="852" y1="8" y2="8"/>
|
||||
<line x1="4" x2="852" y1="152" y2="152"/>
|
||||
<line x1="852" x2="852" y1="8" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="4" x2="4" y1="248" y2="392"/>
|
||||
<line x1="4" x2="852" y1="248" y2="248"/>
|
||||
<line x1="4" x2="852" y1="392" y2="392"/>
|
||||
<line x1="852" x2="852" y1="248" y2="392"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="60" x2="60" y1="56" y2="120"/>
|
||||
<line x1="60" x2="196" y1="56" y2="56"/>
|
||||
<line x1="60" x2="84" y1="120" y2="120"/>
|
||||
<line x1="84" x2="84" y1="120" y2="144"/>
|
||||
<line x1="84" x2="196" y1="120" y2="120"/>
|
||||
<line x1="196" x2="196" y1="56" y2="72"/>
|
||||
<line x1="196" x2="196" y1="72" y2="104"/>
|
||||
<line marker-end="url(#triangle)" x1="196" x2="252" y1="72" y2="72"/>
|
||||
<line x1="196" x2="196" y1="104" y2="120"/>
|
||||
<line x1="196" x2="200" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="60" x2="60" y1="296" y2="360"/>
|
||||
<line x1="60" x2="84" y1="296" y2="296"/>
|
||||
<line x1="60" x2="196" y1="360" y2="360"/>
|
||||
<line x1="84" x2="196" y1="296" y2="296"/>
|
||||
<line x1="196" x2="196" y1="296" y2="312"/>
|
||||
<line x1="196" x2="196" y1="312" y2="344"/>
|
||||
<line marker-end="url(#triangle)" x1="196" x2="252" y1="312" y2="312"/>
|
||||
<line x1="196" x2="196" y1="344" y2="360"/>
|
||||
<line x1="196" x2="200" y1="344" y2="344"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="84" x2="84" y1="160" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="84" x2="84" y1="256" y2="284"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="208" x2="204" y1="104" y2="104"/>
|
||||
<line x1="208" x2="260" y1="104" y2="104"/>
|
||||
<line x1="260" x2="260" y1="72" y2="104"/>
|
||||
<line x1="260" x2="260" y1="104" y2="120"/>
|
||||
<line x1="260" x2="284" y1="120" y2="120"/>
|
||||
<line x1="284" x2="284" y1="120" y2="144"/>
|
||||
<line x1="284" x2="396" y1="120" y2="120"/>
|
||||
<line x1="396" x2="396" y1="104" y2="120"/>
|
||||
<line x1="396" x2="400" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="208" x2="204" y1="344" y2="344"/>
|
||||
<line x1="208" x2="260" y1="344" y2="344"/>
|
||||
<line x1="260" x2="260" y1="312" y2="344"/>
|
||||
<line x1="260" x2="260" y1="344" y2="360"/>
|
||||
<line x1="260" x2="396" y1="360" y2="360"/>
|
||||
<line x1="396" x2="396" y1="344" y2="360"/>
|
||||
<line x1="396" x2="400" y1="344" y2="344"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="260" x2="260" y1="56" y2="72"/>
|
||||
<line x1="260" x2="396" y1="56" y2="56"/>
|
||||
<line x1="260" x2="256" y1="72" y2="72"/>
|
||||
<line x1="396" x2="396" y1="56" y2="72"/>
|
||||
<line x1="396" x2="396" y1="72" y2="104"/>
|
||||
<line marker-end="url(#triangle)" x1="396" x2="452" y1="72" y2="72"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="260" x2="260" y1="296" y2="312"/>
|
||||
<line x1="260" x2="284" y1="296" y2="296"/>
|
||||
<line x1="260" x2="256" y1="312" y2="312"/>
|
||||
<line x1="284" x2="396" y1="296" y2="296"/>
|
||||
<line x1="396" x2="396" y1="296" y2="312"/>
|
||||
<line x1="396" x2="396" y1="312" y2="344"/>
|
||||
<line marker-end="url(#triangle)" x1="396" x2="452" y1="312" y2="312"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="284" x2="284" y1="160" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="284" x2="284" y1="256" y2="284"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="408" x2="404" y1="104" y2="104"/>
|
||||
<line x1="408" x2="460" y1="104" y2="104"/>
|
||||
<line x1="460" x2="460" y1="72" y2="104"/>
|
||||
<line x1="460" x2="460" y1="104" y2="120"/>
|
||||
<line x1="460" x2="508" y1="120" y2="120"/>
|
||||
<line x1="508" x2="508" y1="120" y2="144"/>
|
||||
<line x1="508" x2="596" y1="120" y2="120"/>
|
||||
<line x1="596" x2="596" y1="104" y2="120"/>
|
||||
<line x1="596" x2="600" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="408" x2="404" y1="344" y2="344"/>
|
||||
<line x1="408" x2="460" y1="344" y2="344"/>
|
||||
<line x1="460" x2="460" y1="312" y2="344"/>
|
||||
<line x1="460" x2="460" y1="344" y2="360"/>
|
||||
<line x1="460" x2="596" y1="360" y2="360"/>
|
||||
<line x1="596" x2="596" y1="344" y2="360"/>
|
||||
<line x1="596" x2="600" y1="344" y2="344"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="460" x2="460" y1="56" y2="72"/>
|
||||
<line x1="460" x2="596" y1="56" y2="56"/>
|
||||
<line x1="460" x2="456" y1="72" y2="72"/>
|
||||
<line x1="596" x2="596" y1="56" y2="72"/>
|
||||
<line x1="596" x2="596" y1="72" y2="104"/>
|
||||
<line marker-end="url(#triangle)" x1="596" x2="652" y1="72" y2="72"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="460" x2="460" y1="296" y2="312"/>
|
||||
<line x1="460" x2="508" y1="296" y2="296"/>
|
||||
<line x1="460" x2="456" y1="312" y2="312"/>
|
||||
<line x1="508" x2="596" y1="296" y2="296"/>
|
||||
<line x1="596" x2="596" y1="296" y2="312"/>
|
||||
<line x1="596" x2="596" y1="312" y2="344"/>
|
||||
<line marker-end="url(#triangle)" x1="596" x2="652" y1="312" y2="312"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="508" x2="508" y1="160" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="508" x2="508" y1="256" y2="284"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="608" x2="604" y1="104" y2="104"/>
|
||||
<line x1="608" x2="660" y1="104" y2="104"/>
|
||||
<line x1="660" x2="660" y1="72" y2="104"/>
|
||||
<line x1="660" x2="660" y1="104" y2="120"/>
|
||||
<line x1="660" x2="684" y1="120" y2="120"/>
|
||||
<line x1="684" x2="684" y1="120" y2="144"/>
|
||||
<line x1="684" x2="796" y1="120" y2="120"/>
|
||||
<line x1="796" x2="796" y1="56" y2="120"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="608" x2="604" y1="344" y2="344"/>
|
||||
<line x1="608" x2="660" y1="344" y2="344"/>
|
||||
<line x1="660" x2="660" y1="312" y2="344"/>
|
||||
<line x1="660" x2="660" y1="344" y2="360"/>
|
||||
<line x1="660" x2="796" y1="360" y2="360"/>
|
||||
<line x1="796" x2="796" y1="296" y2="360"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="660" x2="660" y1="56" y2="72"/>
|
||||
<line x1="660" x2="796" y1="56" y2="56"/>
|
||||
<line x1="660" x2="656" y1="72" y2="72"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="660" x2="660" y1="296" y2="312"/>
|
||||
<line x1="660" x2="684" y1="296" y2="296"/>
|
||||
<line x1="660" x2="656" y1="312" y2="312"/>
|
||||
<line x1="684" x2="796" y1="296" y2="296"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="684" x2="684" y1="160" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="684" x2="684" y1="256" y2="284"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="89" y="92">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="89" y="332">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="161" y="92">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="161" y="332">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="289" y="92">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="289" y="332">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="353" y="28">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="353" y="268">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="361" y="92">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="361" y="332">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="457" y="28">
|
||||
Above
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="457" y="268">
|
||||
Below
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="489" y="92">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="489" y="332">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="561" y="92">
|
||||
3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="561" y="332">
|
||||
3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="689" y="92">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="689" y="332">
|
||||
Neighbor
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="761" y="92">
|
||||
4
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="761" y="332">
|
||||
4
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 8.3 KiB |
@ -1,138 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="240" width="544" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="240" width="544" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="4" x2="4" y1="104" y2="232"/>
|
||||
<line x1="4" x2="108" y1="104" y2="104"/>
|
||||
<line x1="4" x2="540" y1="232" y2="232"/>
|
||||
<line x1="108" x2="436" y1="104" y2="104"/>
|
||||
<line x1="436" x2="540" y1="104" y2="104"/>
|
||||
<line x1="540" x2="540" y1="104" y2="232"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="36" x2="36" y1="136" y2="200"/>
|
||||
<line x1="36" x2="180" y1="136" y2="136"/>
|
||||
<line x1="36" x2="180" y1="200" y2="200"/>
|
||||
<line x1="180" x2="180" y1="136" y2="152"/>
|
||||
<line x1="180" x2="180" y1="152" y2="184"/>
|
||||
<line marker-end="url(#triangle)" x1="180" x2="356" y1="152" y2="152"/>
|
||||
<line x1="180" x2="180" y1="184" y2="200"/>
|
||||
<line x1="180" x2="184" y1="184" y2="184"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="108" x2="108" y1="40" y2="92"/>
|
||||
<line x1="108" x2="212" y1="40" y2="40"/>
|
||||
<line x1="212" x2="212" y1="8" y2="40"/>
|
||||
<line x1="212" x2="332" y1="8" y2="8"/>
|
||||
<line x1="212" x2="212" y1="40" y2="72"/>
|
||||
<line x1="212" x2="332" y1="72" y2="72"/>
|
||||
<line x1="332" x2="332" y1="8" y2="40"/>
|
||||
<line x1="332" x2="332" y1="40" y2="72"/>
|
||||
<line x1="332" x2="436" y1="40" y2="40"/>
|
||||
<line marker-end="url(#triangle)" x1="436" x2="436" y1="40" y2="92"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="192" x2="188" y1="184" y2="184"/>
|
||||
<line x1="192" x2="364" y1="184" y2="184"/>
|
||||
<line x1="364" x2="364" y1="152" y2="184"/>
|
||||
<line x1="364" x2="364" y1="184" y2="200"/>
|
||||
<line x1="364" x2="508" y1="200" y2="200"/>
|
||||
<line x1="508" x2="508" y1="136" y2="200"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="364" x2="364" y1="136" y2="152"/>
|
||||
<line x1="364" x2="508" y1="136" y2="136"/>
|
||||
<line x1="364" x2="360" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="65" y="172">
|
||||
Validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="172">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="217" y="140">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="249" y="44">
|
||||
Leader
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="321" y="140">
|
||||
0
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="393" y="172">
|
||||
Validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="172">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.8 KiB |
@ -1,192 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="288" width="736" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="288" width="736" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="4" x2="4" y1="216" y2="280"/>
|
||||
<line x1="4" x2="156" y1="216" y2="216"/>
|
||||
<line x1="4" x2="172" y1="280" y2="280"/>
|
||||
<line x1="156" x2="172" y1="216" y2="216"/>
|
||||
<line x1="172" x2="172" y1="216" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="124" x2="124" y1="104" y2="168"/>
|
||||
<line x1="124" x2="204" y1="104" y2="104"/>
|
||||
<line x1="124" x2="156" y1="168" y2="168"/>
|
||||
<line marker-end="url(#triangle)" x1="156" x2="156" y1="168" y2="204"/>
|
||||
<line x1="156" x2="204" y1="168" y2="168"/>
|
||||
<line x1="204" x2="292" y1="104" y2="104"/>
|
||||
<line marker-end="url(#triangle)" x1="204" x2="204" y1="168" y2="204"/>
|
||||
<line x1="204" x2="292" y1="168" y2="168"/>
|
||||
<line x1="292" x2="292" y1="104" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="188" x2="188" y1="216" y2="280"/>
|
||||
<line x1="188" x2="204" y1="216" y2="216"/>
|
||||
<line x1="188" x2="356" y1="280" y2="280"/>
|
||||
<line x1="204" x2="356" y1="216" y2="216"/>
|
||||
<line x1="356" x2="356" y1="216" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="204" x2="204" y1="40" y2="92"/>
|
||||
<line x1="204" x2="276" y1="40" y2="40"/>
|
||||
<line x1="276" x2="276" y1="8" y2="40"/>
|
||||
<line x1="276" x2="444" y1="8" y2="8"/>
|
||||
<line x1="276" x2="276" y1="40" y2="72"/>
|
||||
<line x1="276" x2="444" y1="72" y2="72"/>
|
||||
<line x1="444" x2="444" y1="8" y2="40"/>
|
||||
<line x1="444" x2="444" y1="40" y2="72"/>
|
||||
<line x1="444" x2="532" y1="40" y2="40"/>
|
||||
<line marker-end="url(#triangle)" x1="532" x2="532" y1="40" y2="92"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="380" x2="380" y1="216" y2="280"/>
|
||||
<line x1="380" x2="532" y1="216" y2="216"/>
|
||||
<line x1="380" x2="548" y1="280" y2="280"/>
|
||||
<line x1="532" x2="548" y1="216" y2="216"/>
|
||||
<line x1="548" x2="548" y1="216" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="444" x2="444" y1="104" y2="168"/>
|
||||
<line x1="444" x2="532" y1="104" y2="104"/>
|
||||
<line x1="444" x2="532" y1="168" y2="168"/>
|
||||
<line x1="532" x2="612" y1="104" y2="104"/>
|
||||
<line marker-end="url(#triangle)" x1="532" x2="532" y1="168" y2="204"/>
|
||||
<line x1="532" x2="580" y1="168" y2="168"/>
|
||||
<line marker-end="url(#triangle)" x1="580" x2="580" y1="168" y2="204"/>
|
||||
<line x1="580" x2="612" y1="168" y2="168"/>
|
||||
<line x1="612" x2="612" y1="104" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="564" x2="564" y1="216" y2="280"/>
|
||||
<line x1="564" x2="580" y1="216" y2="216"/>
|
||||
<line x1="564" x2="732" y1="280" y2="280"/>
|
||||
<line x1="580" x2="732" y1="216" y2="216"/>
|
||||
<line x1="732" x2="732" y1="216" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="33" y="252">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="252">
|
||||
3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="153" y="140">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="217" y="252">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="257" y="140">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="305" y="44">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="321" y="252">
|
||||
4
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="409" y="44">
|
||||
0
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="409" y="252">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="140">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="252">
|
||||
5
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="577" y="140">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="593" y="252">
|
||||
Neighborhood
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="697" y="252">
|
||||
6
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 4.9 KiB |
@ -1,330 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="208" width="768" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="208" width="768" x="0" y="0"/>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="76" x2="76" y1="32" y2="172"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="124" x2="124" y1="24" y2="56"/>
|
||||
<line x1="124" x2="164" y1="24" y2="24"/>
|
||||
<line x1="124" x2="124" y1="56" y2="88"/>
|
||||
<line x1="124" x2="164" y1="56" y2="56"/>
|
||||
<line x1="124" x2="124" y1="88" y2="120"/>
|
||||
<line x1="124" x2="164" y1="88" y2="88"/>
|
||||
<line x1="124" x2="124" y1="120" y2="152"/>
|
||||
<line x1="124" x2="164" y1="120" y2="120"/>
|
||||
<line x1="124" x2="124" y1="152" y2="184"/>
|
||||
<line x1="124" x2="164" y1="152" y2="152"/>
|
||||
<line x1="124" x2="164" y1="184" y2="184"/>
|
||||
<line x1="164" x2="164" y1="24" y2="56"/>
|
||||
<line x1="164" x2="164" y1="56" y2="88"/>
|
||||
<line x1="164" x2="164" y1="88" y2="120"/>
|
||||
<line x1="164" x2="164" y1="120" y2="152"/>
|
||||
<line x1="164" x2="164" y1="152" y2="184"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="188" x2="188" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="200" x2="208" y1="128" y2="112"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="224" x2="236" y1="112" y2="136"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="232" x2="240" y1="96" y2="80"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="236" x2="236" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="260" x2="260" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="264" x2="272" y1="64" y2="48"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="280" y1="80" y2="96"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="280" y1="128" y2="112"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="304" x2="316" y1="112" y2="136"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="316" x2="316" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="348" x2="360" y1="136" y2="112"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="348" x2="348" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="368" x2="376" y1="96" y2="80"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="376" x2="384" y1="48" y2="64"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="376" x2="388" y1="112" y2="136"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="388" x2="388" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="416" x2="424" y1="80" y2="96"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="436" x2="420" y1="104" y2="136"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="448" x2="460" y1="112" y2="136"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="460" x2="460" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="512" x2="640" y1="24" y2="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="17" y="108">
|
||||
time
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="44">
|
||||
L1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="76">
|
||||
L2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="108">
|
||||
L3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="140">
|
||||
L4
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="172">
|
||||
L5
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="140">
|
||||
x
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="209" y="108">
|
||||
E3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="225" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="249" y="76">
|
||||
E2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="257" y="140">
|
||||
E4
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="257" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="289" y="108">
|
||||
x
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="305" y="172">
|
||||
E5
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="313" y="44">
|
||||
E1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="337" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="361" y="108">
|
||||
E3'
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="377" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="393" y="76">
|
||||
x
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="409" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="449" y="172">
|
||||
xx
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="12">
|
||||
validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="60">
|
||||
vote(E1)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="92">
|
||||
vote(E2)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="124">
|
||||
slash(E3)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="156">
|
||||
vote(E4)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="513" y="188">
|
||||
hang
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="553" y="188">
|
||||
on
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="577" y="188">
|
||||
to
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="593" y="12">
|
||||
action
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="601" y="188">
|
||||
E4
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="625" y="188">
|
||||
and
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="657" y="188">
|
||||
E5
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="681" y="188">
|
||||
for
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="713" y="188">
|
||||
more...
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 5.5 KiB |
@ -1,92 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="144" width="48" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="144" width="48" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="20" x2="24" y1="88" y2="80"/>
|
||||
<line x1="20" x2="20" y1="96" y2="88"/>
|
||||
<line x1="20" x2="20" y1="96" y2="128"/>
|
||||
<line x1="24" x2="40" y1="80" y2="48"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="44" x2="44" y1="16" y2="32"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="44" x2="44" y1="48" y2="96"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="17" y="140">
|
||||
5
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="12">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="44">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="108">
|
||||
4
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.4 KiB |
@ -1,92 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="176" width="40" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="176" width="40" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="16" y2="32"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="48" y2="128"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="32" y1="48" y2="80"/>
|
||||
<line x1="36" x2="32" y1="88" y2="80"/>
|
||||
<line x1="36" x2="36" y1="96" y2="88"/>
|
||||
<line x1="36" x2="36" y1="96" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="9" y="12">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="9" y="44">
|
||||
3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="9" y="140">
|
||||
6
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="33" y="172">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.4 KiB |
@ -1,122 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="208" width="96" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="208" width="96" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="20" x2="24" y1="88" y2="80"/>
|
||||
<line x1="20" x2="20" y1="96" y2="88"/>
|
||||
<line x1="20" x2="20" y1="96" y2="128"/>
|
||||
<line x1="24" x2="40" y1="80" y2="48"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="44" x2="44" y1="16" y2="32"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="44" x2="44" y1="48" y2="96"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="48" x2="64" y1="16" y2="48"/>
|
||||
<line x1="68" x2="64" y1="56" y2="48"/>
|
||||
<line x1="68" x2="68" y1="64" y2="56"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="68" x2="68" y1="80" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="72" x2="80" y1="80" y2="96"/>
|
||||
<line x1="80" x2="88" y1="96" y2="112"/>
|
||||
<line x1="92" x2="88" y1="120" y2="112"/>
|
||||
<line x1="92" x2="92" y1="128" y2="120"/>
|
||||
<line x1="92" x2="92" y1="128" y2="192"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="17" y="140">
|
||||
5
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="12">
|
||||
1
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="44">
|
||||
2
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="108">
|
||||
4
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="65" y="76">
|
||||
3
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="65" y="172">
|
||||
6
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="89" y="204">
|
||||
7
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.9 KiB |
@ -1,238 +0,0 @@
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1"
|
||||
width="1320px" height="487px"
|
||||
viewBox="0 0 1320 487"
|
||||
xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges"
|
||||
stroke-width="1" text-rendering="geometricPrecision">
|
||||
<polygon fill="white" points="101,7 161,7 161,16 101,16"/>
|
||||
<text x="132" y="16" textLength="59" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
VoteSigner
|
||||
</text>
|
||||
<polygon fill="white" points="371,7 419,7 419,16 371,16"/>
|
||||
<text x="396" y="16" textLength="47" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
Validator
|
||||
</text>
|
||||
<polygon fill="white" points="639,7 679,7 679,16 639,16"/>
|
||||
<text x="660" y="16" textLength="38" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
Cluster
|
||||
</text>
|
||||
<polygon fill="white" points="901,7 945,7 945,16 901,16"/>
|
||||
<text x="924" y="16" textLength="43" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
StakerX
|
||||
</text>
|
||||
<polygon fill="white" points="1165,7 1209,7 1209,16 1165,16"/>
|
||||
<text x="1188" y="16" textLength="43" font-family="Helvetica" font-size="12" fill="black" text-anchor="middle">
|
||||
|
||||
StakerY
|
||||
</text>
|
||||
<line x1="132" y1="22" x2="132" y2="39" stroke="black"/>
|
||||
<line x1="396" y1="22" x2="396" y2="39" stroke="black"/>
|
||||
<line x1="660" y1="22" x2="660" y2="39" stroke="black"/>
|
||||
<line x1="924" y1="22" x2="924" y2="39" stroke="black"/>
|
||||
<line x1="1188" y1="22" x2="1188" y2="39" stroke="black"/>
|
||||
<line x1="132" y1="39" x2="132" y2="67" stroke="black"/>
|
||||
<line x1="396" y1="39" x2="396" y2="67" stroke="black"/>
|
||||
<line x1="660" y1="39" x2="660" y2="67" stroke="black"/>
|
||||
<line x1="924" y1="39" x2="924" y2="67" stroke="black"/>
|
||||
<line x1="1188" y1="39" x2="1188" y2="67" stroke="black"/>
|
||||
<polygon fill="white" points="272,39 520,39 520,61 272,61"/>
|
||||
<line x1="272" y1="39" x2="520" y2="39" stroke="black"/>
|
||||
<line x1="272" y1="61" x2="520" y2="61" stroke="black"/>
|
||||
<line x1="272" y1="39" x2="272" y2="61" stroke="black"/>
|
||||
<line x1="520" y1="39" x2="520" y2="61" stroke="black"/>
|
||||
<polygon fill="white" points="379,46 411,46 411,55 379,55"/>
|
||||
<text x="380" y="55" textLength="30" font-family="Helvetica" font-size="12" fill="black">
|
||||
boot..
|
||||
</text>
|
||||
<line x1="132" y1="67" x2="132" y2="106" stroke="black"/>
|
||||
<line x1="396" y1="67" x2="396" y2="106" stroke="black"/>
|
||||
<line x1="660" y1="67" x2="660" y2="106" stroke="black"/>
|
||||
<line x1="924" y1="67" x2="924" y2="106" stroke="black"/>
|
||||
<line x1="1188" y1="67" x2="1188" y2="106" stroke="black"/>
|
||||
<line x1="132" y1="82" x2="396" y2="82" stroke="black"/>
|
||||
<line x1="132" y1="84" x2="396" y2="84" stroke="black"/>
|
||||
<polygon fill="black" points="396,83 386,89 386,77"/>
|
||||
<polygon fill="black" points="132,83 142,89 142,77"/>
|
||||
<polygon fill="white" points="242,68 284,68 284,77 242,77"/>
|
||||
<text x="243" y="77" textLength="40" font-family="Helvetica" font-size="12" fill="black">
|
||||
register
|
||||
</text>
|
||||
<polygon fill="white" points="262,79 264,79 264,88 262,88"/>
|
||||
<text x="263" y="88" textLength="0" font-family="Helvetica" font-size="12" fill="black">
|
||||
|
||||
</text>
|
||||
<polygon fill="white" points="237,90 289,90 289,99 237,99"/>
|
||||
<text x="238" y="99" textLength="50" font-family="Helvetica" font-size="12" fill="black">
|
||||
(optional)
|
||||
</text>
|
||||
<line x1="132" y1="106" x2="132" y2="134" stroke="black"/>
|
||||
<line x1="396" y1="106" x2="396" y2="134" stroke="black"/>
|
||||
<line x1="660" y1="106" x2="660" y2="134" stroke="black"/>
|
||||
<line x1="924" y1="106" x2="924" y2="134" stroke="black"/>
|
||||
<line x1="1188" y1="106" x2="1188" y2="134" stroke="black"/>
|
||||
<line x1="396" y1="117" x2="660" y2="117" stroke="black"/>
|
||||
<polygon fill="black" points="660,117 650,123 650,111"/>
|
||||
<polygon fill="white" points="441,107 613,107 613,116 441,116"/>
|
||||
<text x="442" y="116" textLength="170" font-family="Helvetica" font-size="12" fill="black">
|
||||
VoteState::Initialize(VoteSigner)
|
||||
</text>
|
||||
<line x1="132" y1="134" x2="132" y2="162" stroke="black"/>
|
||||
<line x1="396" y1="134" x2="396" y2="162" stroke="black"/>
|
||||
<line x1="660" y1="134" x2="660" y2="162" stroke="black"/>
|
||||
<line x1="924" y1="134" x2="924" y2="162" stroke="black"/>
|
||||
<line x1="1188" y1="134" x2="1188" y2="162" stroke="black"/>
|
||||
<line x1="924" y1="145" x2="660" y2="145" stroke="black"/>
|
||||
<polygon fill="black" points="660,145 670,151 670,139"/>
|
||||
<polygon fill="white" points="706,135 877,135 877,144 706,144"/>
|
||||
<text x="707" y="144" textLength="169" font-family="Helvetica" font-size="12" fill="black">
|
||||
StakeState::Delegate(Validator)
|
||||
</text>
|
||||
<line x1="132" y1="162" x2="132" y2="190" stroke="black"/>
|
||||
<line x1="396" y1="162" x2="396" y2="190" stroke="black"/>
|
||||
<line x1="660" y1="162" x2="660" y2="190" stroke="black"/>
|
||||
<line x1="924" y1="162" x2="924" y2="190" stroke="black"/>
|
||||
<line x1="1188" y1="162" x2="1188" y2="190" stroke="black"/>
|
||||
<line x1="1188" y1="173" x2="660" y2="173" stroke="black"/>
|
||||
<polygon fill="black" points="660,173 670,179 670,167"/>
|
||||
<polygon fill="white" points="838,163 1009,163 1009,172 838,172"/>
|
||||
<text x="839" y="172" textLength="169" font-family="Helvetica" font-size="12" fill="black">
|
||||
StakeState::Delegate(Validator)
|
||||
</text>
|
||||
<line x1="132" y1="190" x2="132" y2="207" stroke="black"/>
|
||||
<line x1="396" y1="190" x2="396" y2="207" stroke="black"/>
|
||||
<line x1="660" y1="190" x2="660" y2="207" stroke="black"/>
|
||||
<line x1="924" y1="190" x2="924" y2="207" stroke="black"/>
|
||||
<line x1="1188" y1="190" x2="1188" y2="207" stroke="black"/>
|
||||
<line x1="132" y1="207" x2="132" y2="246" stroke="black"/>
|
||||
<line x1="396" y1="207" x2="396" y2="246" stroke="black"/>
|
||||
<line x1="660" y1="207" x2="660" y2="246" stroke="black"/>
|
||||
<line x1="924" y1="207" x2="924" y2="246" stroke="black"/>
|
||||
<line x1="1188" y1="207" x2="1188" y2="246" stroke="black"/>
|
||||
<polygon fill="white" points="272,207 784,207 784,240 272,240"/>
|
||||
<line x1="272" y1="207" x2="784" y2="207" stroke="black"/>
|
||||
<line x1="272" y1="240" x2="784" y2="240" stroke="black"/>
|
||||
<line x1="272" y1="207" x2="272" y2="240" stroke="black"/>
|
||||
<line x1="784" y1="207" x2="784" y2="240" stroke="black"/>
|
||||
<polygon fill="white" points="526,208 528,208 528,217 526,217"/>
|
||||
<text x="527" y="217" textLength="0" font-family="Helvetica" font-size="12" fill="black">
|
||||
|
||||
</text>
|
||||
<polygon fill="white" points="506,219 549,219 549,228 506,228"/>
|
||||
<text x="507" y="228" textLength="41" font-family="Helvetica" font-size="12" fill="black">
|
||||
validate
|
||||
</text>
|
||||
<polygon fill="white" points="526,230 528,230 528,239 526,239"/>
|
||||
<text x="527" y="239" textLength="0" font-family="Helvetica" font-size="12" fill="black">
|
||||
|
||||
</text>
|
||||
<line x1="132" y1="246" x2="132" y2="274" stroke="black"/>
|
||||
<line x1="396" y1="246" x2="396" y2="274" stroke="black"/>
|
||||
<line x1="660" y1="246" x2="660" y2="274" stroke="black"/>
|
||||
<line x1="924" y1="246" x2="924" y2="274" stroke="black"/>
|
||||
<line x1="1188" y1="246" x2="1188" y2="274" stroke="black"/>
|
||||
<line x1="396" y1="257" x2="132" y2="257" stroke="black"/>
|
||||
<polygon fill="black" points="132,257 142,263 142,251"/>
|
||||
<polygon fill="white" points="236,247 291,247 291,256 236,256"/>
|
||||
<text x="237" y="256" textLength="53" font-family="Helvetica" font-size="12" fill="black">
|
||||
sign(vote)
|
||||
</text>
|
||||
<line x1="132" y1="274" x2="132" y2="302" stroke="black"/>
|
||||
<line x1="396" y1="274" x2="396" y2="302" stroke="black"/>
|
||||
<line x1="660" y1="274" x2="660" y2="302" stroke="black"/>
|
||||
<line x1="924" y1="274" x2="924" y2="302" stroke="black"/>
|
||||
<line x1="1188" y1="274" x2="1188" y2="302" stroke="black"/>
|
||||
<line x1="132" y1="285" x2="396" y2="285" stroke="black" stroke-dasharray="2,2"/>
|
||||
<polygon fill="black" points="396,285 386,291 386,279"/>
|
||||
<polygon fill="white" points="232,275 295,275 295,284 232,284"/>
|
||||
<text x="233" y="284" textLength="61" font-family="Helvetica" font-size="12" fill="black">
|
||||
signed vote
|
||||
</text>
|
||||
<line x1="132" y1="302" x2="132" y2="330" stroke="black"/>
|
||||
<line x1="396" y1="302" x2="396" y2="330" stroke="black"/>
|
||||
<line x1="660" y1="302" x2="660" y2="330" stroke="black"/>
|
||||
<line x1="924" y1="302" x2="924" y2="330" stroke="black"/>
|
||||
<line x1="1188" y1="302" x2="1188" y2="330" stroke="black"/>
|
||||
<line x1="396" y1="313" x2="660" y2="313" stroke="black"/>
|
||||
<polygon fill="black" points="660,313 650,319 650,307"/>
|
||||
<polygon fill="white" points="494,303 561,303 561,312 494,312"/>
|
||||
<text x="495" y="312" textLength="65" font-family="Helvetica" font-size="12" fill="black">
|
||||
gossip(vote)
|
||||
</text>
|
||||
<line x1="132" y1="330" x2="132" y2="347" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="396" y1="330" x2="396" y2="347" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="660" y1="330" x2="660" y2="347" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="924" y1="330" x2="924" y2="347" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="1188" y1="330" x2="1188" y2="347" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="132" y1="347" x2="132" y2="364" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="396" y1="347" x2="396" y2="364" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="660" y1="347" x2="660" y2="364" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="924" y1="347" x2="924" y2="364" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="1188" y1="347" x2="1188" y2="364" stroke="black" stroke-dasharray="2,2"/>
|
||||
<line x1="132" y1="364" x2="132" y2="414" stroke="black"/>
|
||||
<line x1="396" y1="364" x2="396" y2="414" stroke="black"/>
|
||||
<line x1="660" y1="364" x2="660" y2="414" stroke="black"/>
|
||||
<line x1="924" y1="364" x2="924" y2="414" stroke="black"/>
|
||||
<line x1="1188" y1="364" x2="1188" y2="414" stroke="black"/>
|
||||
<polygon fill="white" points="278,364 514,364 514,408 278,408"/>
|
||||
<polygon fill="white" points="278,364 278,408 272,386"/>
|
||||
<polygon fill="white" points="514,364 514,408 520,386"/>
|
||||
<line x1="278" y1="364" x2="514" y2="364" stroke="black"/>
|
||||
<line x1="278" y1="408" x2="514" y2="408" stroke="black"/>
|
||||
<line x1="278" y1="364" x2="272" y2="386" stroke="black"/>
|
||||
<line x1="272" y1="386" x2="278" y2="408" stroke="black"/>
|
||||
<line x1="514" y1="364" x2="520" y2="386" stroke="black"/>
|
||||
<line x1="520" y1="386" x2="514" y2="408" stroke="black"/>
|
||||
<polygon fill="white" points="394,365 396,365 396,374 394,374"/>
|
||||
<text x="395" y="374" textLength="0" font-family="Helvetica" font-size="12" fill="black">
|
||||
|
||||
</text>
|
||||
<polygon fill="white" points="383,376 408,376 408,385 383,385"/>
|
||||
<text x="384" y="385" textLength="23" font-family="Helvetica" font-size="12" fill="black">
|
||||
max
|
||||
</text>
|
||||
<polygon fill="white" points="375,387 415,387 415,396 375,396"/>
|
||||
<text x="376" y="396" textLength="38" font-family="Helvetica" font-size="12" fill="black">
|
||||
lockout
|
||||
</text>
|
||||
<polygon fill="white" points="394,398 396,398 396,407 394,407"/>
|
||||
<text x="395" y="407" textLength="0" font-family="Helvetica" font-size="12" fill="black">
|
||||
|
||||
</text>
|
||||
<line x1="132" y1="414" x2="132" y2="431" stroke="black"/>
|
||||
<line x1="396" y1="414" x2="396" y2="431" stroke="black"/>
|
||||
<line x1="660" y1="414" x2="660" y2="431" stroke="black"/>
|
||||
<line x1="924" y1="414" x2="924" y2="431" stroke="black"/>
|
||||
<line x1="1188" y1="414" x2="1188" y2="431" stroke="black"/>
|
||||
<line x1="132" y1="431" x2="132" y2="459" stroke="black"/>
|
||||
<line x1="396" y1="431" x2="396" y2="459" stroke="black"/>
|
||||
<line x1="660" y1="431" x2="660" y2="459" stroke="black"/>
|
||||
<line x1="924" y1="431" x2="924" y2="459" stroke="black"/>
|
||||
<line x1="1188" y1="431" x2="1188" y2="459" stroke="black"/>
|
||||
<line x1="924" y1="442" x2="660" y2="442" stroke="black"/>
|
||||
<polygon fill="black" points="660,442 670,448 670,436"/>
|
||||
<polygon fill="white" points="712,432 871,432 871,441 712,441"/>
|
||||
<text x="713" y="441" textLength="157" font-family="Helvetica" font-size="12" fill="black">
|
||||
StakeState::RedeemCredits()
|
||||
</text>
|
||||
<line x1="132" y1="459" x2="132" y2="487" stroke="black"/>
|
||||
<line x1="396" y1="459" x2="396" y2="487" stroke="black"/>
|
||||
<line x1="660" y1="459" x2="660" y2="487" stroke="black"/>
|
||||
<line x1="924" y1="459" x2="924" y2="487" stroke="black"/>
|
||||
<line x1="1188" y1="459" x2="1188" y2="487" stroke="black"/>
|
||||
<line x1="1188" y1="470" x2="660" y2="470" stroke="black"/>
|
||||
<polygon fill="black" points="660,470 670,476 670,464"/>
|
||||
<polygon fill="white" points="844,460 1003,460 1003,469 844,469"/>
|
||||
<text x="845" y="469" textLength="157" font-family="Helvetica" font-size="12" fill="black">
|
||||
StakeState::RedeemCredits()
|
||||
</text>
|
||||
<line x1="132" y1="481" x2="132" y2="487" stroke="black"/>
|
||||
<line x1="396" y1="481" x2="396" y2="487" stroke="black"/>
|
||||
<line x1="660" y1="481" x2="660" y2="487" stroke="black"/>
|
||||
<line x1="924" y1="481" x2="924" y2="487" stroke="black"/>
|
||||
<line x1="1188" y1="481" x2="1188" y2="487" stroke="black"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 12 KiB |
@ -1,346 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="160" width="848" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="160" width="848" x="0" y="0"/>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="0" x2="28" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="4" x2="4" y1="12" y2="52"/>
|
||||
<path d="M 4 52 A 4 4 0 0 0 8 56" fill="none"/>
|
||||
<path d="M 8 8 A 4 4 0 0 0 4 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="8" x2="104" y1="8" y2="8"/>
|
||||
<path d="M 108 12 A 4 4 0 0 0 104 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="8" x2="104" y1="56" y2="56"/>
|
||||
<path d="M 104 56 A 4 4 0 0 0 108 52" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="36" x2="36" y1="92" y2="116"/>
|
||||
<path d="M 36 116 A 4 4 0 0 0 40 120" fill="none"/>
|
||||
<path d="M 40 88 A 4 4 0 0 0 36 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="40" x2="160" y1="88" y2="88"/>
|
||||
<path d="M 164 92 A 4 4 0 0 0 160 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="40" x2="160" y1="120" y2="120"/>
|
||||
<path d="M 160 120 A 4 4 0 0 0 164 116" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="12" y2="24"/>
|
||||
<line x1="108" x2="108" y1="24" y2="52"/>
|
||||
<line marker-end="url(#triangle)" x1="108" x2="140" y1="24" y2="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="156" x2="156" y1="12" y2="36"/>
|
||||
<path d="M 156 36 A 4 4 0 0 0 160 40" fill="none"/>
|
||||
<path d="M 160 8 A 4 4 0 0 0 156 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="160" x2="248" y1="8" y2="8"/>
|
||||
<path d="M 252 12 A 4 4 0 0 0 248 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="160" x2="248" y1="40" y2="40"/>
|
||||
<path d="M 248 40 A 4 4 0 0 0 252 36" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="164" x2="164" y1="92" y2="104"/>
|
||||
<line x1="164" x2="164" y1="104" y2="116"/>
|
||||
<line marker-end="url(#triangle)" x1="164" x2="196" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="204" x2="204" y1="92" y2="116"/>
|
||||
<path d="M 204 116 A 4 4 0 0 0 208 120" fill="none"/>
|
||||
<path d="M 208 88 A 4 4 0 0 0 204 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="208" x2="280" y1="88" y2="88"/>
|
||||
<path d="M 284 92 A 4 4 0 0 0 280 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="208" x2="280" y1="120" y2="120"/>
|
||||
<path d="M 280 120 A 4 4 0 0 0 284 116" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="252" x2="252" y1="12" y2="24"/>
|
||||
<line x1="252" x2="252" y1="24" y2="36"/>
|
||||
<line marker-end="url(#triangle)" x1="252" x2="284" y1="24" y2="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="284" x2="284" y1="92" y2="104"/>
|
||||
<line x1="284" x2="284" y1="104" y2="116"/>
|
||||
<line marker-end="url(#triangle)" x1="284" x2="316" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="292" x2="292" y1="12" y2="36"/>
|
||||
<path d="M 292 36 A 4 4 0 0 0 296 40" fill="none"/>
|
||||
<path d="M 296 8 A 4 4 0 0 0 292 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="296" x2="416" y1="8" y2="8"/>
|
||||
<path d="M 420 12 A 4 4 0 0 0 416 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="296" x2="416" y1="40" y2="40"/>
|
||||
<path d="M 416 40 A 4 4 0 0 0 420 36" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="324" x2="324" y1="92" y2="132"/>
|
||||
<path d="M 324 132 A 4 4 0 0 0 328 136" fill="none"/>
|
||||
<path d="M 328 88 A 4 4 0 0 0 324 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="328" x2="424" y1="88" y2="88"/>
|
||||
<path d="M 428 92 A 4 4 0 0 0 424 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="328" x2="424" y1="136" y2="136"/>
|
||||
<path d="M 424 136 A 4 4 0 0 0 428 132" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="12" y2="24"/>
|
||||
<line x1="420" x2="420" y1="24" y2="36"/>
|
||||
<line marker-end="url(#triangle)" x1="420" x2="452" y1="24" y2="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="428" x2="428" y1="92" y2="104"/>
|
||||
<line x1="428" x2="428" y1="104" y2="132"/>
|
||||
<line marker-end="url(#triangle)" x1="428" x2="460" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="460" x2="460" y1="12" y2="36"/>
|
||||
<path d="M 460 36 A 4 4 0 0 0 464 40" fill="none"/>
|
||||
<path d="M 464 8 A 4 4 0 0 0 460 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="464" x2="576" y1="8" y2="8"/>
|
||||
<path d="M 580 12 A 4 4 0 0 0 576 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="464" x2="576" y1="40" y2="40"/>
|
||||
<path d="M 576 40 A 4 4 0 0 0 580 36" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="468" x2="468" y1="92" y2="116"/>
|
||||
<path d="M 468 116 A 4 4 0 0 0 472 120" fill="none"/>
|
||||
<path d="M 472 88 A 4 4 0 0 0 468 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="472" x2="608" y1="88" y2="88"/>
|
||||
<path d="M 612 92 A 4 4 0 0 0 608 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="472" x2="608" y1="120" y2="120"/>
|
||||
<path d="M 608 120 A 4 4 0 0 0 612 116" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="580" x2="580" y1="12" y2="24"/>
|
||||
<line x1="580" x2="580" y1="24" y2="36"/>
|
||||
<line marker-end="url(#triangle)" x1="580" x2="612" y1="24" y2="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="612" x2="612" y1="92" y2="104"/>
|
||||
<line x1="612" x2="612" y1="104" y2="116"/>
|
||||
<line marker-end="url(#triangle)" x1="612" x2="636" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="620" x2="620" y1="12" y2="36"/>
|
||||
<path d="M 620 36 A 4 4 0 0 0 624 40" fill="none"/>
|
||||
<path d="M 624 8 A 4 4 0 0 0 620 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="624" x2="808" y1="8" y2="8"/>
|
||||
<path d="M 812 12 A 4 4 0 0 0 808 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="624" x2="808" y1="40" y2="40"/>
|
||||
<path d="M 808 40 A 4 4 0 0 0 812 36" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="644" x2="644" y1="92" y2="116"/>
|
||||
<path d="M 644 116 A 4 4 0 0 0 648 120" fill="none"/>
|
||||
<path d="M 648 88 A 4 4 0 0 0 644 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="648" x2="784" y1="88" y2="88"/>
|
||||
<path d="M 788 92 A 4 4 0 0 0 784 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="648" x2="784" y1="120" y2="120"/>
|
||||
<path d="M 784 120 A 4 4 0 0 0 788 116" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="788" x2="788" y1="92" y2="116"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="812" x2="812" y1="12" y2="24"/>
|
||||
<line x1="812" x2="812" y1="24" y2="36"/>
|
||||
<line marker-end="url(#triangle)" x1="812" x2="844" y1="24" y2="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="17" y="28">
|
||||
PoH
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="49" y="28">
|
||||
verify
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="49" y="44">
|
||||
TVU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="49" y="108">
|
||||
load
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="89" y="108">
|
||||
accounts
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="169" y="28">
|
||||
sigverify
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="217" y="108">
|
||||
execute
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="305" y="28">
|
||||
lock
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="337" y="108">
|
||||
PoH
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="345" y="28">
|
||||
accounts
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="361" y="124">
|
||||
TPU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="369" y="108">
|
||||
record
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="28">
|
||||
validate
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="481" y="108">
|
||||
commit
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="537" y="108">
|
||||
accounts
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="545" y="28">
|
||||
fee
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="633" y="28">
|
||||
allocate
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="657" y="108">
|
||||
unlock
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="705" y="28">
|
||||
new
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="713" y="108">
|
||||
accounts
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="737" y="28">
|
||||
accounts
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 8.0 KiB |
@ -1,237 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="320" width="560" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="320" width="560" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="20" x2="20" y1="140" y2="196"/>
|
||||
<path d="M 20 196 A 4 4 0 0 0 24 200" fill="none"/>
|
||||
<path d="M 24 136 A 4 4 0 0 0 20 140" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="24" x2="104" y1="136" y2="136"/>
|
||||
<path d="M 108 140 A 4 4 0 0 0 104 136" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="24" x2="104" y1="200" y2="200"/>
|
||||
<path d="M 104 200 A 4 4 0 0 0 108 196" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="140" y2="152"/>
|
||||
<line x1="108" x2="108" y1="152" y2="184"/>
|
||||
<line x1="108" x2="176" y1="152" y2="152"/>
|
||||
<line x1="108" x2="108" y1="184" y2="196"/>
|
||||
<line x1="108" x2="176" y1="184" y2="184"/>
|
||||
<path d="M 176 152 A 4 4 0 0 0 180 148" fill="none"/>
|
||||
<path d="M 180 188 A 4 4 0 0 0 176 184" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="180" x2="180" y1="108" y2="148"/>
|
||||
<path d="M 184 104 A 4 4 0 0 0 180 108" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="180" x2="180" y1="188" y2="244"/>
|
||||
<path d="M 180 244 A 4 4 0 0 0 184 248" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="184" x2="252" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="184" x2="252" y1="248" y2="248"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="28" y2="96"/>
|
||||
<path d="M 232 24 A 4 4 0 0 0 228 28" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="112" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="256" y2="308"/>
|
||||
<path d="M 228 308 A 4 4 0 0 0 232 312" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="232" x2="552" y1="24" y2="24"/>
|
||||
<path d="M 556 28 A 4 4 0 0 0 552 24" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="232" x2="552" y1="312" y2="312"/>
|
||||
<path d="M 552 312 A 4 4 0 0 0 556 308" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="260" x2="260" y1="76" y2="132"/>
|
||||
<path d="M 260 132 A 4 4 0 0 0 264 136" fill="none"/>
|
||||
<path d="M 264 72 A 4 4 0 0 0 260 76" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="260" x2="260" y1="220" y2="276"/>
|
||||
<path d="M 260 276 A 4 4 0 0 0 264 280" fill="none"/>
|
||||
<path d="M 264 216 A 4 4 0 0 0 260 220" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="264" x2="360" y1="72" y2="72"/>
|
||||
<path d="M 364 76 A 4 4 0 0 0 360 72" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="264" x2="360" y1="136" y2="136"/>
|
||||
<path d="M 360 136 A 4 4 0 0 0 364 132" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="264" x2="316" y1="216" y2="216"/>
|
||||
<line x1="316" x2="316" y1="188" y2="216"/>
|
||||
<line x1="316" x2="360" y1="216" y2="216"/>
|
||||
<path d="M 320 184 A 4 4 0 0 0 316 188" fill="none"/>
|
||||
<path d="M 364 220 A 4 4 0 0 0 360 216" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="264" x2="360" y1="280" y2="280"/>
|
||||
<path d="M 360 280 A 4 4 0 0 0 364 276" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="320" x2="448" y1="184" y2="184"/>
|
||||
<path d="M 448 184 A 4 4 0 0 0 452 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="364" x2="364" y1="76" y2="104"/>
|
||||
<line x1="364" x2="364" y1="104" y2="132"/>
|
||||
<line marker-end="url(#triangle)" x1="364" x2="388" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="364" x2="364" y1="220" y2="248"/>
|
||||
<line x1="364" x2="364" y1="248" y2="276"/>
|
||||
<line marker-end="url(#triangle)" x1="364" x2="388" y1="248" y2="248"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="76" y2="132"/>
|
||||
<path d="M 396 132 A 4 4 0 0 0 400 136" fill="none"/>
|
||||
<path d="M 400 72 A 4 4 0 0 0 396 76" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="220" y2="276"/>
|
||||
<path d="M 396 276 A 4 4 0 0 0 400 280" fill="none"/>
|
||||
<path d="M 400 216 A 4 4 0 0 0 396 220" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="400" x2="496" y1="72" y2="72"/>
|
||||
<path d="M 500 76 A 4 4 0 0 0 496 72" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="400" x2="496" y1="136" y2="136"/>
|
||||
<path d="M 496 136 A 4 4 0 0 0 500 132" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="400" x2="504" y1="216" y2="216"/>
|
||||
<path d="M 508 220 A 4 4 0 0 0 504 216" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="400" x2="504" y1="280" y2="280"/>
|
||||
<path d="M 504 280 A 4 4 0 0 0 508 276" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="452" x2="452" y1="160" y2="148"/>
|
||||
<line x1="452" x2="452" y1="160" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="500" x2="500" y1="76" y2="132"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="508" x2="508" y1="220" y2="276"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="556" x2="556" y1="28" y2="308"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="41" y="172">
|
||||
Client
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="108">
|
||||
Verifier
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="252">
|
||||
Loader
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="329" y="44">
|
||||
Solana
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="337" y="172">
|
||||
LoadAccounts
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="385" y="44">
|
||||
Runtime
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="409" y="252">
|
||||
Interpreter
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="417" y="108">
|
||||
Accounts
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 6.2 KiB |
@ -1,163 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="288" width="432" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="288" width="432" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="248" y2="280"/>
|
||||
<line x1="12" x2="140" y1="248" y2="248"/>
|
||||
<line x1="12" x2="140" y1="280" y2="280"/>
|
||||
<line x1="140" x2="140" y1="248" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="112" x2="126" y1="240" y2="212"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="124" x2="124" y1="88" y2="120"/>
|
||||
<line x1="124" x2="268" y1="88" y2="88"/>
|
||||
<line x1="124" x2="268" y1="120" y2="120"/>
|
||||
<line x1="268" x2="268" y1="88" y2="120"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="124" x2="124" y1="168" y2="200"/>
|
||||
<line x1="124" x2="180" y1="168" y2="168"/>
|
||||
<line x1="124" x2="180" y1="200" y2="200"/>
|
||||
<line x1="180" x2="180" y1="168" y2="200"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="160" x2="174" y1="160" y2="132"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="172" x2="172" y1="248" y2="280"/>
|
||||
<line x1="172" x2="300" y1="248" y2="248"/>
|
||||
<line x1="172" x2="300" y1="280" y2="280"/>
|
||||
<line x1="300" x2="300" y1="248" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="184" x2="178" y1="224" y2="212"/>
|
||||
<line x1="184" x2="192" y1="224" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="212" x2="212" y1="168" y2="200"/>
|
||||
<line x1="212" x2="428" y1="168" y2="168"/>
|
||||
<line x1="212" x2="428" y1="200" y2="200"/>
|
||||
<line x1="428" x2="428" y1="168" y2="200"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="224" x2="218" y1="144" y2="132"/>
|
||||
<line x1="224" x2="232" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="236" x2="236" y1="8" y2="40"/>
|
||||
<line x1="236" x2="340" y1="8" y2="8"/>
|
||||
<line x1="236" x2="340" y1="40" y2="40"/>
|
||||
<line x1="340" x2="340" y1="8" y2="40"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="248" x2="262" y1="80" y2="52"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="308" x2="308" y1="88" y2="120"/>
|
||||
<line x1="308" x2="420" y1="88" y2="88"/>
|
||||
<line x1="308" x2="420" y1="120" y2="120"/>
|
||||
<line x1="420" x2="420" y1="88" y2="120"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="320" x2="314" y1="64" y2="52"/>
|
||||
<line x1="320" x2="328" y1="64" y2="80"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="268">
|
||||
Hash(Account1)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="108">
|
||||
Bank-Diff-Merkle
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="188">
|
||||
Hash
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="268">
|
||||
Hash(Account2)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="225" y="188">
|
||||
Previous
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="249" y="28">
|
||||
Bank-Merkle
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="297" y="188">
|
||||
Bank-Diff-Merkle
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="321" y="108">
|
||||
Block-Merkle
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 4.2 KiB |
@ -1,203 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="304" width="584" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="304" width="584" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="248" y2="280"/>
|
||||
<line x1="12" x2="156" y1="248" y2="248"/>
|
||||
<line x1="12" x2="156" y1="280" y2="280"/>
|
||||
<line x1="156" x2="156" y1="248" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="128" x2="142" y1="240" y2="212"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="148" x2="148" y1="168" y2="200"/>
|
||||
<line x1="148" x2="212" y1="168" y2="168"/>
|
||||
<line x1="148" x2="212" y1="200" y2="200"/>
|
||||
<line x1="212" x2="212" y1="168" y2="200"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="180" x2="180" y1="88" y2="120"/>
|
||||
<line x1="180" x2="292" y1="88" y2="88"/>
|
||||
<line x1="180" x2="292" y1="120" y2="120"/>
|
||||
<line x1="292" x2="292" y1="88" y2="120"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="188" x2="188" y1="248" y2="280"/>
|
||||
<line x1="188" x2="332" y1="248" y2="248"/>
|
||||
<line x1="188" x2="332" y1="280" y2="280"/>
|
||||
<line x1="332" x2="332" y1="248" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="192" x2="206" y1="160" y2="132"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="212" x2="212" y1="224" y2="212"/>
|
||||
<line x1="212" x2="212" y1="224" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="264" x2="278" y1="80" y2="52"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="284" x2="284" y1="8" y2="40"/>
|
||||
<line x1="284" x2="412" y1="8" y2="8"/>
|
||||
<line x1="284" x2="412" y1="40" y2="40"/>
|
||||
<line x1="412" x2="412" y1="8" y2="40"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="364" x2="364" y1="248" y2="280"/>
|
||||
<line x1="364" x2="508" y1="248" y2="248"/>
|
||||
<line x1="364" x2="508" y1="280" y2="280"/>
|
||||
<line x1="508" x2="508" y1="248" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="88" y2="120"/>
|
||||
<line x1="396" x2="508" y1="88" y2="88"/>
|
||||
<line x1="396" x2="508" y1="120" y2="120"/>
|
||||
<line x1="508" x2="508" y1="88" y2="120"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="416" x2="410" y1="64" y2="52"/>
|
||||
<line x1="416" x2="424" y1="64" y2="80"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="468" x2="468" y1="168" y2="200"/>
|
||||
<line x1="468" x2="532" y1="168" y2="168"/>
|
||||
<line x1="468" x2="532" y1="200" y2="200"/>
|
||||
<line x1="532" x2="532" y1="168" y2="200"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="476" x2="476" y1="224" y2="212"/>
|
||||
<line x1="476" x2="476" y1="224" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="480" x2="474" y1="144" y2="132"/>
|
||||
<line x1="480" x2="488" y1="144" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="536" x2="530" y1="224" y2="212"/>
|
||||
<line x1="536" x2="544" y1="224" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="540" x2="540" y1="248" y2="280"/>
|
||||
<line x1="540" x2="572" y1="248" y2="248"/>
|
||||
<line x1="540" x2="572" y1="280" y2="280"/>
|
||||
<line x1="572" x2="572" y1="248" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="268">
|
||||
Hash(T1,
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="97" y="268">
|
||||
status)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="169" y="188">
|
||||
Hash
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="193" y="108">
|
||||
Entry-Merkle
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="201" y="268">
|
||||
Hash(T2,
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="273" y="268">
|
||||
status)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="305" y="28">
|
||||
Block-Merkle
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="377" y="268">
|
||||
Hash(T3,
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="409" y="108">
|
||||
Entry-Merkle
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="449" y="268">
|
||||
status)
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="489" y="188">
|
||||
Hash
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="553" y="268">
|
||||
0
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 5.0 KiB |
@ -1,312 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="304" width="696" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="304" width="696" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="140" y2="164"/>
|
||||
<path d="M 12 164 A 4 4 0 0 0 16 168" fill="none"/>
|
||||
<path d="M 16 136 A 4 4 0 0 0 12 140" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="88" y1="136" y2="136"/>
|
||||
<path d="M 92 140 A 4 4 0 0 0 88 136" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="88" y1="168" y2="168"/>
|
||||
<path d="M 88 168 A 4 4 0 0 0 92 164" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="92" x2="92" y1="140" y2="164"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="92" x2="124" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="92" y2="144"/>
|
||||
<path d="M 112 88 A 4 4 0 0 0 108 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="160" y2="212"/>
|
||||
<path d="M 108 212 A 4 4 0 0 0 112 216" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="356" y1="88" y2="88"/>
|
||||
<line x1="356" x2="396" y1="88" y2="88"/>
|
||||
<line x1="396" x2="560" y1="88" y2="88"/>
|
||||
<path d="M 564 92 A 4 4 0 0 0 560 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="380" y1="216" y2="216"/>
|
||||
<line x1="380" x2="560" y1="216" y2="216"/>
|
||||
<path d="M 560 216 A 4 4 0 0 0 564 212" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="132" x2="132" y1="124" y2="180"/>
|
||||
<path d="M 132 180 A 4 4 0 0 0 136 184" fill="none"/>
|
||||
<path d="M 136 120 A 4 4 0 0 0 132 124" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="192" y1="120" y2="120"/>
|
||||
<path d="M 196 124 A 4 4 0 0 0 192 120" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="192" y1="184" y2="184"/>
|
||||
<path d="M 192 184 A 4 4 0 0 0 196 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="196" x2="196" y1="124" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="196" x2="212" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="220" x2="220" y1="124" y2="180"/>
|
||||
<path d="M 220 180 A 4 4 0 0 0 224 184" fill="none"/>
|
||||
<path d="M 224 120 A 4 4 0 0 0 220 124" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="224" x2="312" y1="120" y2="120"/>
|
||||
<path d="M 316 124 A 4 4 0 0 0 312 120" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="224" x2="312" y1="184" y2="184"/>
|
||||
<path d="M 312 184 A 4 4 0 0 0 316 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="316" x2="316" y1="124" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="316" x2="332" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="324" x2="324" y1="28" y2="52"/>
|
||||
<path d="M 324 52 A 4 4 0 0 0 328 56" fill="none"/>
|
||||
<path d="M 328 24 A 4 4 0 0 0 324 28" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="328" x2="432" y1="24" y2="24"/>
|
||||
<path d="M 436 28 A 4 4 0 0 0 432 24" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="328" x2="396" y1="56" y2="56"/>
|
||||
<line marker-end="url(#triangle)" x1="396" x2="396" y1="56" y2="108"/>
|
||||
<line x1="396" x2="432" y1="56" y2="56"/>
|
||||
<path d="M 432 56 A 4 4 0 0 0 436 52" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="340" x2="340" y1="124" y2="180"/>
|
||||
<path d="M 340 180 A 4 4 0 0 0 344 184" fill="none"/>
|
||||
<path d="M 344 120 A 4 4 0 0 0 340 124" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="344" x2="356" y1="120" y2="120"/>
|
||||
<line x1="356" x2="356" y1="80" y2="120"/>
|
||||
<line x1="356" x2="416" y1="120" y2="120"/>
|
||||
<path d="M 420 124 A 4 4 0 0 0 416 120" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="344" x2="380" y1="184" y2="184"/>
|
||||
<line marker-end="url(#triangle)" x1="380" x2="380" y1="184" y2="252"/>
|
||||
<line x1="380" x2="416" y1="184" y2="184"/>
|
||||
<path d="M 416 184 A 4 4 0 0 0 420 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="356" x2="356" y1="80" y2="68"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="356" x2="356" y1="268" y2="292"/>
|
||||
<path d="M 356 292 A 4 4 0 0 0 360 296" fill="none"/>
|
||||
<path d="M 360 264 A 4 4 0 0 0 356 268" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="360" x2="408" y1="264" y2="264"/>
|
||||
<path d="M 412 268 A 4 4 0 0 0 408 264" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="360" x2="408" y1="296" y2="296"/>
|
||||
<path d="M 408 296 A 4 4 0 0 0 412 292" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="412" x2="412" y1="268" y2="292"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="124" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="420" x2="436" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="436" x2="436" y1="28" y2="52"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="444" x2="444" y1="124" y2="180"/>
|
||||
<path d="M 444 180 A 4 4 0 0 0 448 184" fill="none"/>
|
||||
<path d="M 448 120 A 4 4 0 0 0 444 124" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="448" x2="536" y1="120" y2="120"/>
|
||||
<path d="M 540 124 A 4 4 0 0 0 536 120" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="448" x2="536" y1="184" y2="184"/>
|
||||
<path d="M 536 184 A 4 4 0 0 0 540 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="540" x2="540" y1="124" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="540" x2="580" y1="152" y2="152"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="564" x2="564" y1="92" y2="144"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="564" x2="564" y1="160" y2="212"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="588" x2="588" y1="124" y2="180"/>
|
||||
<path d="M 588 180 A 4 4 0 0 0 592 184" fill="none"/>
|
||||
<path d="M 592 120 A 4 4 0 0 0 588 124" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="592" x2="688" y1="120" y2="120"/>
|
||||
<path d="M 692 124 A 4 4 0 0 0 688 120" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="592" x2="688" y1="184" y2="184"/>
|
||||
<path d="M 688 184 A 4 4 0 0 0 692 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="692" x2="692" y1="124" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="156">
|
||||
Clients
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="121" y="108">
|
||||
TPU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="140">
|
||||
Fetch
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="156">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="233" y="140">
|
||||
SigVerify
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="233" y="156">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="337" y="44">
|
||||
PoH
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="353" y="140">
|
||||
Banking
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="353" y="156">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="369" y="44">
|
||||
Service
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="369" y="284">
|
||||
Bank
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="457" y="140">
|
||||
Broadcast
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="457" y="156">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="601" y="140">
|
||||
Downstream
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="601" y="156">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 7.4 KiB |
@ -1,311 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="352" width="616" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="352" width="616" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="156" y2="196"/>
|
||||
<path d="M 12 196 A 4 4 0 0 0 16 200" fill="none"/>
|
||||
<path d="M 16 152 A 4 4 0 0 0 12 156" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="112" y1="152" y2="152"/>
|
||||
<path d="M 116 156 A 4 4 0 0 0 112 152" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="112" y1="200" y2="200"/>
|
||||
<path d="M 112 200 A 4 4 0 0 0 116 196" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="116" x2="116" y1="156" y2="168"/>
|
||||
<line x1="116" x2="116" y1="168" y2="196"/>
|
||||
<line marker-end="url(#triangle)" x1="116" x2="164" y1="168" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="148" x2="148" y1="92" y2="160"/>
|
||||
<path d="M 152 88 A 4 4 0 0 0 148 92" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="148" x2="148" y1="176" y2="244"/>
|
||||
<path d="M 148 244 A 4 4 0 0 0 152 248" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="152" x2="444" y1="88" y2="88"/>
|
||||
<line x1="444" x2="608" y1="88" y2="88"/>
|
||||
<path d="M 612 92 A 4 4 0 0 0 608 88" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="152" x2="220" y1="248" y2="248"/>
|
||||
<line x1="220" x2="308" y1="248" y2="248"/>
|
||||
<line x1="308" x2="444" y1="248" y2="248"/>
|
||||
<line x1="444" x2="608" y1="248" y2="248"/>
|
||||
<path d="M 608 248 A 4 4 0 0 0 612 244" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="172" x2="172" y1="140" y2="196"/>
|
||||
<path d="M 172 196 A 4 4 0 0 0 176 200" fill="none"/>
|
||||
<path d="M 176 136 A 4 4 0 0 0 172 140" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="176" x2="232" y1="136" y2="136"/>
|
||||
<path d="M 236 140 A 4 4 0 0 0 232 136" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="176" x2="232" y1="200" y2="200"/>
|
||||
<path d="M 232 200 A 4 4 0 0 0 236 196" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="212" x2="212" y1="300" y2="340"/>
|
||||
<path d="M 212 340 A 4 4 0 0 0 216 344" fill="none"/>
|
||||
<path d="M 216 296 A 4 4 0 0 0 212 300" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="216" x2="312" y1="344" y2="344"/>
|
||||
<path d="M 312 344 A 4 4 0 0 0 316 340" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="220" x2="220" y1="224" y2="212"/>
|
||||
<line x1="220" x2="220" y1="224" y2="296"/>
|
||||
<line x1="220" x2="216" y1="296" y2="296"/>
|
||||
<line x1="220" x2="312" y1="296" y2="296"/>
|
||||
<path d="M 316 300 A 4 4 0 0 0 312 296" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="236" x2="236" y1="140" y2="168"/>
|
||||
<line x1="236" x2="236" y1="168" y2="196"/>
|
||||
<line marker-end="url(#triangle)" x1="236" x2="260" y1="168" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="140" y2="196"/>
|
||||
<path d="M 268 196 A 4 4 0 0 0 272 200" fill="none"/>
|
||||
<path d="M 272 136 A 4 4 0 0 0 268 140" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="136" y2="136"/>
|
||||
<path d="M 372 140 A 4 4 0 0 0 368 136" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="308" y1="200" y2="200"/>
|
||||
<line marker-end="url(#triangle)" x1="308" x2="308" y1="200" y2="284"/>
|
||||
<line x1="308" x2="368" y1="200" y2="200"/>
|
||||
<path d="M 368 200 A 4 4 0 0 0 372 196" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="316" x2="316" y1="300" y2="340"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="140" y2="168"/>
|
||||
<line x1="372" x2="372" y1="168" y2="196"/>
|
||||
<line marker-end="url(#triangle)" x1="372" x2="396" y1="168" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="404" x2="404" y1="12" y2="36"/>
|
||||
<path d="M 404 36 A 4 4 0 0 0 408 40" fill="none"/>
|
||||
<path d="M 408 8 A 4 4 0 0 0 404 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="404" x2="404" y1="140" y2="196"/>
|
||||
<path d="M 404 196 A 4 4 0 0 0 408 200" fill="none"/>
|
||||
<path d="M 408 136 A 4 4 0 0 0 404 140" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="408" x2="472" y1="8" y2="8"/>
|
||||
<path d="M 476 12 A 4 4 0 0 0 472 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="408" x2="472" y1="40" y2="40"/>
|
||||
<path d="M 472 40 A 4 4 0 0 0 476 36" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="408" x2="444" y1="136" y2="136"/>
|
||||
<line x1="444" x2="444" y1="64" y2="136"/>
|
||||
<line x1="444" x2="472" y1="136" y2="136"/>
|
||||
<path d="M 476 140 A 4 4 0 0 0 472 136" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="408" x2="444" y1="200" y2="200"/>
|
||||
<line marker-end="url(#triangle)" x1="444" x2="444" y1="200" y2="284"/>
|
||||
<line x1="444" x2="472" y1="200" y2="200"/>
|
||||
<path d="M 472 200 A 4 4 0 0 0 476 196" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="300" y2="324"/>
|
||||
<path d="M 420 324 A 4 4 0 0 0 424 328" fill="none"/>
|
||||
<path d="M 424 296 A 4 4 0 0 0 420 300" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="424" x2="472" y1="296" y2="296"/>
|
||||
<path d="M 476 300 A 4 4 0 0 0 472 296" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="424" x2="472" y1="328" y2="328"/>
|
||||
<path d="M 472 328 A 4 4 0 0 0 476 324" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="444" x2="444" y1="64" y2="52"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="476" x2="476" y1="12" y2="36"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="476" x2="476" y1="140" y2="168"/>
|
||||
<line x1="476" x2="476" y1="168" y2="196"/>
|
||||
<line marker-end="url(#triangle)" x1="476" x2="500" y1="168" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="476" x2="476" y1="300" y2="324"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="508" x2="508" y1="140" y2="196"/>
|
||||
<path d="M 508 196 A 4 4 0 0 0 512 200" fill="none"/>
|
||||
<path d="M 512 136 A 4 4 0 0 0 508 140" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="512" x2="584" y1="136" y2="136"/>
|
||||
<path d="M 588 140 A 4 4 0 0 0 584 136" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="512" x2="584" y1="200" y2="200"/>
|
||||
<path d="M 584 200 A 4 4 0 0 0 588 196" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="588" x2="588" y1="140" y2="196"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="612" x2="612" y1="92" y2="244"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="172">
|
||||
Upstream
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="188">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="169" y="108">
|
||||
TVU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="156">
|
||||
Blob
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="172">
|
||||
Fetch
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="188">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="225" y="316">
|
||||
Gossip
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="225" y="332">
|
||||
Service
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="156">
|
||||
Retransmit
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="172">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="417" y="28">
|
||||
Leader
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="417" y="156">
|
||||
Replay
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="417" y="172">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="433" y="316">
|
||||
Bank
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="521" y="156">
|
||||
Storage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="521" y="172">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 401 KiB |
@ -1,496 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="960" width="528" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="960" width="528" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="188" y2="212"/>
|
||||
<path d="M 12 212 A 4 4 0 0 0 16 216" fill="none"/>
|
||||
<path d="M 16 184 A 4 4 0 0 0 12 188" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="80" y1="184" y2="184"/>
|
||||
<path d="M 84 188 A 4 4 0 0 0 80 184" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="80" y1="216" y2="216"/>
|
||||
<path d="M 80 216 A 4 4 0 0 0 84 212" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="20" x2="20" y1="748" y2="788"/>
|
||||
<path d="M 20 788 A 4 4 0 0 0 24 792" fill="none"/>
|
||||
<path d="M 24 744 A 4 4 0 0 0 20 748" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="24" x2="80" y1="744" y2="744"/>
|
||||
<path d="M 84 748 A 4 4 0 0 0 80 744" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="24" x2="80" y1="792" y2="792"/>
|
||||
<path d="M 80 792 A 4 4 0 0 0 84 788" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="84" x2="84" y1="188" y2="200"/>
|
||||
<line x1="84" x2="84" y1="200" y2="212"/>
|
||||
<line marker-end="url(#triangle)" x1="84" x2="124" y1="200" y2="200"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="84" x2="84" y1="748" y2="760"/>
|
||||
<line x1="84" x2="84" y1="760" y2="788"/>
|
||||
<line marker-end="url(#triangle)" x1="84" x2="124" y1="760" y2="760"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="124" y2="192"/>
|
||||
<path d="M 112 120 A 4 4 0 0 0 108 124" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="208" y2="436"/>
|
||||
<path d="M 108 436 A 4 4 0 0 0 112 440" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="700" y2="752"/>
|
||||
<path d="M 112 696 A 4 4 0 0 0 108 700" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="768" y2="836"/>
|
||||
<path d="M 108 836 A 4 4 0 0 0 112 840" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="392" y1="120" y2="120"/>
|
||||
<path d="M 396 124 A 4 4 0 0 0 392 120" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="392" y1="440" y2="440"/>
|
||||
<path d="M 392 440 A 4 4 0 0 0 396 436" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="392" y1="696" y2="696"/>
|
||||
<path d="M 396 700 A 4 4 0 0 0 392 696" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="392" y1="840" y2="840"/>
|
||||
<path d="M 392 840 A 4 4 0 0 0 396 836" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="132" x2="132" y1="172" y2="212"/>
|
||||
<path d="M 132 212 A 4 4 0 0 0 136 216" fill="none"/>
|
||||
<path d="M 136 168 A 4 4 0 0 0 132 172" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="132" x2="132" y1="268" y2="308"/>
|
||||
<path d="M 132 308 A 4 4 0 0 0 136 312" fill="none"/>
|
||||
<path d="M 136 264 A 4 4 0 0 0 132 268" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="132" x2="132" y1="748" y2="788"/>
|
||||
<path d="M 132 788 A 4 4 0 0 0 136 792" fill="none"/>
|
||||
<path d="M 136 744 A 4 4 0 0 0 132 748" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="224" y1="168" y2="168"/>
|
||||
<path d="M 228 172 A 4 4 0 0 0 224 168" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="164" y1="216" y2="216"/>
|
||||
<line marker-end="url(#triangle)" x1="164" x2="164" y1="216" y2="252"/>
|
||||
<line x1="164" x2="224" y1="216" y2="216"/>
|
||||
<path d="M 224 216 A 4 4 0 0 0 228 212" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="224" y1="264" y2="264"/>
|
||||
<path d="M 228 268 A 4 4 0 0 0 224 264" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="224" y1="312" y2="312"/>
|
||||
<path d="M 224 312 A 4 4 0 0 0 228 308" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="224" y1="744" y2="744"/>
|
||||
<path d="M 228 748 A 4 4 0 0 0 224 744" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="224" y1="792" y2="792"/>
|
||||
<path d="M 224 792 A 4 4 0 0 0 228 788" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="172" y2="212"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="268" y2="308"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="748" y2="760"/>
|
||||
<line x1="228" x2="228" y1="760" y2="788"/>
|
||||
<line marker-end="url(#triangle)" x1="228" x2="260" y1="760" y2="760"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="240" x2="236" y1="280" y2="280"/>
|
||||
<line marker-end="url(#triangle)" x1="240" x2="260" y1="280" y2="280"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="28" y2="68"/>
|
||||
<path d="M 268 68 A 4 4 0 0 0 272 72" fill="none"/>
|
||||
<path d="M 272 24 A 4 4 0 0 0 268 28" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="172" y2="212"/>
|
||||
<path d="M 268 212 A 4 4 0 0 0 272 216" fill="none"/>
|
||||
<path d="M 272 168 A 4 4 0 0 0 268 172" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="268" y2="308"/>
|
||||
<path d="M 268 308 A 4 4 0 0 0 272 312" fill="none"/>
|
||||
<path d="M 272 264 A 4 4 0 0 0 268 268" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="364" y2="404"/>
|
||||
<path d="M 268 404 A 4 4 0 0 0 272 408" fill="none"/>
|
||||
<path d="M 272 360 A 4 4 0 0 0 268 364" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="492" y2="532"/>
|
||||
<path d="M 268 532 A 4 4 0 0 0 272 536" fill="none"/>
|
||||
<path d="M 272 488 A 4 4 0 0 0 268 492" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="604" y2="644"/>
|
||||
<path d="M 268 644 A 4 4 0 0 0 272 648" fill="none"/>
|
||||
<path d="M 272 600 A 4 4 0 0 0 268 604" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="748" y2="788"/>
|
||||
<path d="M 268 788 A 4 4 0 0 0 272 792" fill="none"/>
|
||||
<path d="M 272 744 A 4 4 0 0 0 268 748" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="268" x2="268" y1="892" y2="932"/>
|
||||
<path d="M 268 932 A 4 4 0 0 0 272 936" fill="none"/>
|
||||
<path d="M 272 888 A 4 4 0 0 0 268 892" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="24" y2="24"/>
|
||||
<path d="M 372 28 A 4 4 0 0 0 368 24" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="308" y1="72" y2="72"/>
|
||||
<line x1="308" x2="308" y1="72" y2="112"/>
|
||||
<line x1="308" x2="368" y1="72" y2="72"/>
|
||||
<path d="M 368 72 A 4 4 0 0 0 372 68" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="168" y2="168"/>
|
||||
<path d="M 372 172 A 4 4 0 0 0 368 168" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="308" y1="216" y2="216"/>
|
||||
<line marker-end="url(#triangle)" x1="308" x2="308" y1="216" y2="252"/>
|
||||
<line x1="308" x2="368" y1="216" y2="216"/>
|
||||
<path d="M 368 216 A 4 4 0 0 0 372 212" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="264" y2="264"/>
|
||||
<path d="M 372 268 A 4 4 0 0 0 368 264" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="308" y1="312" y2="312"/>
|
||||
<line marker-end="url(#triangle)" x1="308" x2="308" y1="312" y2="348"/>
|
||||
<line x1="308" x2="368" y1="312" y2="312"/>
|
||||
<path d="M 368 312 A 4 4 0 0 0 372 308" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="360" y2="360"/>
|
||||
<path d="M 372 364 A 4 4 0 0 0 368 360" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="308" y1="408" y2="408"/>
|
||||
<line x1="308" x2="308" y1="408" y2="432"/>
|
||||
<line x1="308" x2="368" y1="408" y2="408"/>
|
||||
<path d="M 368 408 A 4 4 0 0 0 372 404" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="488" y2="488"/>
|
||||
<path d="M 372 492 A 4 4 0 0 0 368 488" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="536" y2="536"/>
|
||||
<path d="M 368 536 A 4 4 0 0 0 372 532" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="600" y2="600"/>
|
||||
<path d="M 372 604 A 4 4 0 0 0 368 600" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="332" y1="648" y2="648"/>
|
||||
<line x1="332" x2="332" y1="648" y2="688"/>
|
||||
<line x1="332" x2="368" y1="648" y2="648"/>
|
||||
<path d="M 368 648 A 4 4 0 0 0 372 644" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="300" y1="744" y2="744"/>
|
||||
<line x1="300" x2="300" y1="704" y2="744"/>
|
||||
<line x1="300" x2="368" y1="744" y2="744"/>
|
||||
<path d="M 372 748 A 4 4 0 0 0 368 744" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="316" y1="792" y2="792"/>
|
||||
<line x1="316" x2="316" y1="792" y2="832"/>
|
||||
<line x1="316" x2="368" y1="792" y2="792"/>
|
||||
<path d="M 368 792 A 4 4 0 0 0 372 788" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="888" y2="888"/>
|
||||
<path d="M 372 892 A 4 4 0 0 0 368 888" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="272" x2="368" y1="936" y2="936"/>
|
||||
<path d="M 368 936 A 4 4 0 0 0 372 932" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="300" x2="300" y1="672" y2="660"/>
|
||||
<line x1="300" x2="300" y1="672" y2="688"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="308" x2="308" y1="128" y2="156"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="308" x2="308" y1="448" y2="476"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="316" x2="316" y1="848" y2="876"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="332" x2="332" y1="704" y2="732"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="28" y2="68"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="172" y2="212"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="268" y2="308"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="364" y2="404"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="492" y2="532"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="604" y2="644"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="748" y2="788"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="372" x2="372" y1="892" y2="932"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="384" x2="380" y1="760" y2="760"/>
|
||||
<line marker-end="url(#triangle)" x1="384" x2="412" y1="760" y2="760"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="124" y2="436"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="700" y2="752"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="768" y2="836"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="748" y2="788"/>
|
||||
<path d="M 420 788 A 4 4 0 0 0 424 792" fill="none"/>
|
||||
<path d="M 424 744 A 4 4 0 0 0 420 748" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="424" x2="520" y1="744" y2="744"/>
|
||||
<path d="M 524 748 A 4 4 0 0 0 520 744" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="424" x2="520" y1="792" y2="792"/>
|
||||
<path d="M 520 792 A 4 4 0 0 0 524 788" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="524" x2="524" y1="748" y2="788"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="204">
|
||||
Client
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="33" y="764">
|
||||
Fetch
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="33" y="780">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="121" y="140">
|
||||
Validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="121" y="716">
|
||||
TPU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="188">
|
||||
Fetch
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="204">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="284">
|
||||
TPU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="764">
|
||||
SigVerify
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="780">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="44">
|
||||
Upstream
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="60">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="188">
|
||||
Repair
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="204">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="284">
|
||||
Blockstore
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="380">
|
||||
Multicast
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="396">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="508">
|
||||
Downstream
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="524">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="620">
|
||||
PoH
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="636">
|
||||
Service
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="764">
|
||||
Banking
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="780">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="281" y="908">
|
||||
Banktree
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="433" y="764">
|
||||
Blockstore
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 12 KiB |
@ -1,456 +0,0 @@
|
||||
<svg class="bob" font-family="arial" font-size="14" height="480" width="592" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="triangle" markerHeight="8" markerWidth="8" orient="auto" refX="4" refY="2" viewBox="0 0 8 4">
|
||||
<polygon fill="black" points="0,0 0,4 8,2 0,0"/>
|
||||
</marker>
|
||||
<marker id="clear_triangle" markerHeight="10" markerWidth="10" orient="auto" refX="1" refY="7" viewBox="0 0 20 14">
|
||||
<polygon fill="none" points="2,2 2,12 18,7 2,2" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="circle" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="black" r="8"/>
|
||||
</marker>
|
||||
<marker id="square" markerHeight="5" markerWidth="5" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<rect fill="black" height="20" width="20" x="0" y="0"/>
|
||||
</marker>
|
||||
<marker id="open_circle" markerHeight="10" markerWidth="10" orient="auto" refX="10" refY="10" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" fill="white" r="4" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
<marker id="big_open_circle" markerHeight="20" markerWidth="20" orient="auto" refX="20" refY="20" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" fill="white" r="6" stroke="black" stroke-width="2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<style type="text/css">
|
||||
|
||||
line,path {
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5;
|
||||
}
|
||||
circle.solid {
|
||||
fill:black;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
circle.open {
|
||||
fill:none;
|
||||
stroke: black;
|
||||
stroke-width: 2;
|
||||
stroke-opacity: 1;
|
||||
fill-opacity: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: miter;
|
||||
}
|
||||
tspan.head{
|
||||
fill: none;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<rect fill="white" height="480" width="592" x="0" y="0"/>
|
||||
<g>
|
||||
<line x1="12" x2="12" y1="60" y2="116"/>
|
||||
<path d="M 12 116 A 4 4 0 0 0 16 120" fill="none"/>
|
||||
<path d="M 16 56 A 4 4 0 0 0 12 60" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="80" y1="56" y2="56"/>
|
||||
<path d="M 84 60 A 4 4 0 0 0 80 56" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="16" x2="52" y1="120" y2="120"/>
|
||||
<line x1="52" x2="52" y1="120" y2="420"/>
|
||||
<line x1="52" x2="80" y1="120" y2="120"/>
|
||||
<path d="M 52 420 A 4 4 0 0 0 56 424" fill="none"/>
|
||||
<path d="M 80 120 A 4 4 0 0 0 84 116" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="56" x2="124" y1="424" y2="424"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="84" x2="84" y1="60" y2="116"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="84" x2="124" y1="72" y2="72"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="96" x2="92" y1="104" y2="104"/>
|
||||
<line x1="96" x2="132" y1="104" y2="104"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="12" y2="64"/>
|
||||
<path d="M 112 8 A 4 4 0 0 0 108 12" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="80" y2="96"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="112" y2="416"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="108" x2="108" y1="432" y2="468"/>
|
||||
<path d="M 108 468 A 4 4 0 0 0 112 472" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="416" y1="8" y2="8"/>
|
||||
<path d="M 420 12 A 4 4 0 0 0 416 8" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="112" x2="416" y1="472" y2="472"/>
|
||||
<path d="M 416 472 A 4 4 0 0 0 420 468" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="124" x2="124" y1="236" y2="276"/>
|
||||
<path d="M 124 276 A 4 4 0 0 0 128 280" fill="none"/>
|
||||
<path d="M 128 232 A 4 4 0 0 0 124 236" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="128" x2="156" y1="232" y2="232"/>
|
||||
<line x1="156" x2="156" y1="144" y2="232"/>
|
||||
<line x1="156" x2="184" y1="232" y2="232"/>
|
||||
<path d="M 188 236 A 4 4 0 0 0 184 232" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="128" x2="184" y1="280" y2="280"/>
|
||||
<path d="M 184 280 A 4 4 0 0 0 188 276" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="132" x2="132" y1="60" y2="116"/>
|
||||
<path d="M 132 116 A 4 4 0 0 0 136 120" fill="none"/>
|
||||
<path d="M 136 56 A 4 4 0 0 0 132 60" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="132" x2="132" y1="412" y2="436"/>
|
||||
<path d="M 132 436 A 4 4 0 0 0 136 440" fill="none"/>
|
||||
<path d="M 136 408 A 4 4 0 0 0 132 412" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="288" y1="56" y2="56"/>
|
||||
<path d="M 292 60 A 4 4 0 0 0 288 56" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="288" y1="120" y2="120"/>
|
||||
<path d="M 288 120 A 4 4 0 0 0 292 116" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="156" y1="408" y2="408"/>
|
||||
<line x1="156" x2="156" y1="304" y2="408"/>
|
||||
<line x1="156" x2="176" y1="408" y2="408"/>
|
||||
<path d="M 180 412 A 4 4 0 0 0 176 408" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="136" x2="176" y1="440" y2="440"/>
|
||||
<path d="M 176 440 A 4 4 0 0 0 180 436" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="156" x2="156" y1="144" y2="132"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="156" x2="156" y1="304" y2="292"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="180" x2="180" y1="412" y2="424"/>
|
||||
<line x1="180" x2="180" y1="424" y2="436"/>
|
||||
<line marker-end="url(#triangle)" x1="180" x2="220" y1="424" y2="424"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="188" x2="188" y1="236" y2="276"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="200" x2="196" y1="248" y2="248"/>
|
||||
<line x1="200" x2="212" y1="248" y2="248"/>
|
||||
<line x1="212" x2="212" y1="236" y2="248"/>
|
||||
<line x1="212" x2="212" y1="248" y2="276"/>
|
||||
<path d="M 212 276 A 4 4 0 0 0 216 280" fill="none"/>
|
||||
<path d="M 216 232 A 4 4 0 0 0 212 236" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="204" x2="204" y1="156" y2="180"/>
|
||||
<path d="M 204 180 A 4 4 0 0 0 208 184" fill="none"/>
|
||||
<path d="M 208 152 A 4 4 0 0 0 204 156" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="208" x2="336" y1="152" y2="152"/>
|
||||
<path d="M 340 156 A 4 4 0 0 0 336 152" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="208" x2="336" y1="184" y2="184"/>
|
||||
<path d="M 336 184 A 4 4 0 0 0 340 180" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="216" x2="252" y1="232" y2="232"/>
|
||||
<line x1="252" x2="252" y1="208" y2="232"/>
|
||||
<line x1="252" x2="280" y1="232" y2="232"/>
|
||||
<path d="M 284 236 A 4 4 0 0 0 280 232" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="216" x2="280" y1="280" y2="280"/>
|
||||
<path d="M 280 280 A 4 4 0 0 0 284 276" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="228" x2="228" y1="412" y2="452"/>
|
||||
<path d="M 228 452 A 4 4 0 0 0 232 456" fill="none"/>
|
||||
<path d="M 232 408 A 4 4 0 0 0 228 412" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="232" x2="292" y1="408" y2="408"/>
|
||||
<line x1="292" x2="292" y1="384" y2="408"/>
|
||||
<line x1="292" x2="320" y1="408" y2="408"/>
|
||||
<path d="M 324 412 A 4 4 0 0 0 320 408" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="232" x2="320" y1="456" y2="456"/>
|
||||
<path d="M 320 456 A 4 4 0 0 0 324 452" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="252" x2="252" y1="208" y2="196"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="252" x2="252" y1="332" y2="356"/>
|
||||
<path d="M 252 356 A 4 4 0 0 0 256 360" fill="none"/>
|
||||
<path d="M 256 328 A 4 4 0 0 0 252 332" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="256" x2="276" y1="328" y2="328"/>
|
||||
<line x1="276" x2="276" y1="304" y2="328"/>
|
||||
<line x1="276" x2="344" y1="328" y2="328"/>
|
||||
<path d="M 348 332 A 4 4 0 0 0 344 328" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="256" x2="344" y1="360" y2="360"/>
|
||||
<path d="M 344 360 A 4 4 0 0 0 348 356" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="276" x2="276" y1="304" y2="292"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="284" x2="284" y1="236" y2="276"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="292" x2="292" y1="60" y2="116"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="292" x2="292" y1="384" y2="372"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="300" x2="300" y1="236" y2="276"/>
|
||||
<path d="M 300 276 A 4 4 0 0 0 304 280" fill="none"/>
|
||||
<path d="M 304 232 A 4 4 0 0 0 300 236" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="304" x2="392" y1="232" y2="232"/>
|
||||
<path d="M 396 236 A 4 4 0 0 0 392 232" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="304" x2="324" y1="280" y2="280"/>
|
||||
<line marker-end="url(#triangle)" x1="324" x2="324" y1="280" y2="316"/>
|
||||
<line x1="324" x2="392" y1="280" y2="280"/>
|
||||
<path d="M 392 280 A 4 4 0 0 0 396 276" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="324" x2="324" y1="412" y2="424"/>
|
||||
<line x1="324" x2="324" y1="424" y2="452"/>
|
||||
<line marker-end="url(#triangle)" x1="324" x2="452" y1="424" y2="424"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="340" x2="340" y1="156" y2="180"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="348" x2="348" y1="332" y2="356"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="352" x2="348" y1="168" y2="168"/>
|
||||
<line x1="352" x2="436" y1="168" y2="168"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="396" x2="396" y1="236" y2="276"/>
|
||||
</g>
|
||||
<g>
|
||||
<line marker-end="url(#triangle)" x1="408" x2="404" y1="248" y2="248"/>
|
||||
<line x1="408" x2="460" y1="248" y2="248"/>
|
||||
<line x1="460" x2="460" y1="220" y2="248"/>
|
||||
<line x1="460" x2="460" y1="248" y2="292"/>
|
||||
<path d="M 460 292 A 4 4 0 0 0 464 296" fill="none"/>
|
||||
<path d="M 464 216 A 4 4 0 0 0 460 220" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="12" y2="160"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="176" y2="240"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="256" y2="416"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="420" x2="420" y1="432" y2="468"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="436" x2="436" y1="156" y2="240"/>
|
||||
<path d="M 440 152 A 4 4 0 0 0 436 156" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="436" x2="436" y1="256" y2="416"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="436" x2="436" y1="432" y2="452"/>
|
||||
<path d="M 436 452 A 4 4 0 0 0 440 456" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="440" x2="584" y1="152" y2="152"/>
|
||||
<path d="M 588 156 A 4 4 0 0 0 584 152" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="440" x2="584" y1="456" y2="456"/>
|
||||
<path d="M 584 456 A 4 4 0 0 0 588 452" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="460" x2="460" y1="364" y2="436"/>
|
||||
<path d="M 460 436 A 4 4 0 0 0 464 440" fill="none"/>
|
||||
<path d="M 464 360 A 4 4 0 0 0 460 364" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="464" x2="560" y1="216" y2="216"/>
|
||||
<path d="M 564 220 A 4 4 0 0 0 560 216" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="464" x2="560" y1="296" y2="296"/>
|
||||
<path d="M 560 296 A 4 4 0 0 0 564 292" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="464" x2="560" y1="360" y2="360"/>
|
||||
<path d="M 564 364 A 4 4 0 0 0 560 360" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="464" x2="560" y1="440" y2="440"/>
|
||||
<path d="M 560 440 A 4 4 0 0 0 564 436" fill="none"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="564" x2="564" y1="220" y2="292"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="564" x2="564" y1="364" y2="436"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="588" x2="588" y1="156" y2="452"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="25" y="92">
|
||||
Client
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="129" y="28">
|
||||
Validator
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="252">
|
||||
Bank
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="137" y="268">
|
||||
Forks
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="92">
|
||||
JSON
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="145" y="428">
|
||||
TPU
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="185" y="92">
|
||||
RPC
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="217" y="92">
|
||||
Service
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="217" y="172">
|
||||
Gossip
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="225" y="252">
|
||||
Replay
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="225" y="268">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="241" y="428">
|
||||
Broadcast
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="241" y="444">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="265" y="348">
|
||||
Blocktree
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="273" y="172">
|
||||
Service
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="313" y="252">
|
||||
BlobFetch
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="321" y="268">
|
||||
Stage
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="449" y="172">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="252">
|
||||
Upstream
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="268">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="396">
|
||||
Downstream
|
||||
</text>
|
||||
</g>
|
||||
<g>
|
||||
<text x="473" y="412">
|
||||
Validators
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 11 KiB |
@ -1,93 +0,0 @@
|
||||
# Solana Architecture
|
||||
|
||||
- [Introduction](introduction.md)
|
||||
|
||||
- [Terminology](terminology.md)
|
||||
|
||||
- [Getting Started](getting-started.md)
|
||||
- [Testnet Participation](testnet-participation.md)
|
||||
- [Example Client: Web Wallet](webwallet.md)
|
||||
|
||||
- [Programming Model](programs.md)
|
||||
- [Example: Tic-Tac-Toe](tictactoe.md)
|
||||
- [Drones](drones.md)
|
||||
|
||||
- [A Solana Cluster](cluster.md)
|
||||
- [Synchronization](synchronization.md)
|
||||
- [Leader Rotation](leader-rotation.md)
|
||||
- [Fork Generation](fork-generation.md)
|
||||
- [Managing Forks](managing-forks.md)
|
||||
- [Turbine Block Propagation](turbine-block-propagation.md)
|
||||
- [Ledger Replication](ledger-replication.md)
|
||||
- [Secure Vote Signing](vote-signing.md)
|
||||
- [Stake Delegation and Rewards](stake-delegation-and-rewards.md)
|
||||
- [Performance Metrics](performance-metrics.md)
|
||||
|
||||
- [Anatomy of a Validator](validator.md)
|
||||
- [TPU](tpu.md)
|
||||
- [TVU](tvu.md)
|
||||
- [Blocktree](blocktree.md)
|
||||
- [Gossip Service](gossip.md)
|
||||
- [The Runtime](runtime.md)
|
||||
|
||||
- [Anatomy of a Transaction](transaction.md)
|
||||
|
||||
- [Running a Validator](running-validator.md)
|
||||
- [Hardware Requirements](validator-hardware.md)
|
||||
- [Choosing a Testnet](validator-testnet.md)
|
||||
- [Installing the Validator Software](validator-software.md)
|
||||
- [Starting a Validator](validator-start.md)
|
||||
- [Staking](validator-stake.md)
|
||||
- [Monitoring a Validator](validator-monitor.md)
|
||||
- [Publishing Validator Info](validator-info.md)
|
||||
- [Troubleshooting](validator-troubleshoot.md)
|
||||
- [FAQ](validator-faq.md)
|
||||
|
||||
- [Running a Replicator](running-replicator.md)
|
||||
|
||||
- [API Reference](api-reference.md)
|
||||
- [Transaction](transaction-api.md)
|
||||
- [Instruction](instruction-api.md)
|
||||
- [Blockstreamer](blockstreamer.md)
|
||||
- [JSON RPC API](jsonrpc-api.md)
|
||||
- [JavaScript API](javascript-api.md)
|
||||
- [solana CLI](cli.md)
|
||||
|
||||
- [Accepted Design Proposals](proposals.md)
|
||||
- [Ledger Replication](ledger-replication-to-implement.md)
|
||||
- [Secure Vote Signing](vote-signing-to-implement.md)
|
||||
- [Staking Rewards](staking-rewards.md)
|
||||
- [Cluster Test Framework](cluster-test-framework.md)
|
||||
- [Validator](validator-proposal.md)
|
||||
- [Simple Payment and State Verification](simple-payment-and-state-verification.md)
|
||||
- [Cross-Program Invocation](cross-program-invocation.md)
|
||||
- [Rent](rent.md)
|
||||
- [Inter-chain Transaction Verification](interchain-transaction-verification.md)
|
||||
- [Snapshot Verification](snapshot-verification.md)
|
||||
|
||||
- [Implemented Design Proposals](implemented-proposals.md)
|
||||
- [Blocktree](blocktree.md)
|
||||
- [Cluster Software Installation and Updates](installer.md)
|
||||
- [Cluster Economics](ed_overview.md)
|
||||
- [Validation-client Economics](ed_validation_client_economics.md)
|
||||
- [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md)
|
||||
- [State-validation Transaction Fees](ed_vce_state_validation_transaction_fees.md)
|
||||
- [Replication-validation Transaction Fees](ed_vce_replication_validation_transaction_fees.md)
|
||||
- [Validation Stake Delegation](ed_vce_validation_stake_delegation.md)
|
||||
- [Replication-client Economics](ed_replication_client_economics.md)
|
||||
- [Storage-replication Rewards](ed_rce_storage_replication_rewards.md)
|
||||
- [Replication-client Reward Auto-delegation](ed_rce_replication_client_reward_auto_delegation.md)
|
||||
- [Economic Sustainability](ed_economic_sustainability.md)
|
||||
- [Attack Vectors](ed_attack_vectors.md)
|
||||
- [Economic Design MVP](ed_mvp.md)
|
||||
- [References](ed_references.md)
|
||||
- [Deterministic Transaction Fees](transaction-fees.md)
|
||||
- [Tower BFT](tower-bft.md)
|
||||
- [Leader-to-Leader Transition](leader-leader-transition.md)
|
||||
- [Leader-to-Validator Transition](leader-validator-transition.md)
|
||||
- [Persistent Account Storage](persistent-account-storage.md)
|
||||
- [Reliable Vote Transmission](reliable-vote-transmission.md)
|
||||
- [Repair Service](repair-service.md)
|
||||
- [Testing Programs](testing-programs.md)
|
||||
- [Credit-only Accounts](credit-only-credit-debit-accounts.md)
|
||||
- [Embedding the Move Langauge](embedding-move.md)
|
@ -1,4 +0,0 @@
|
||||
# API Reference
|
||||
|
||||
The following sections contain API references material you may find useful
|
||||
when developing applications utilizing a Solana cluster.
|
@ -1,37 +0,0 @@
|
||||
# Blockstreamer
|
||||
|
||||
Solana supports a node type called an *blockstreamer*. This fullnode variation
|
||||
is intended for applications that need to observe the data plane without
|
||||
participating in transaction validation or ledger replication.
|
||||
|
||||
A blockstreamer runs without a vote signer, and can optionally stream ledger
|
||||
entries out to a Unix domain socket as they are processed. The JSON-RPC service
|
||||
still functions as on any other node.
|
||||
|
||||
To run a blockstreamer, include the argument `no-signer` and (optional)
|
||||
`blockstream` socket location:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/validator-x.sh --no-signer --blockstream <SOCKET>
|
||||
```
|
||||
|
||||
The stream will output a series of JSON objects:
|
||||
- An Entry event JSON object is sent when each ledger entry is processed, with
|
||||
the following fields:
|
||||
|
||||
* `dt`, the system datetime, as RFC3339-formatted string
|
||||
* `t`, the event type, always "entry"
|
||||
* `s`, the slot height, as unsigned 64-bit integer
|
||||
* `h`, the tick height, as unsigned 64-bit integer
|
||||
* `entry`, the entry, as JSON object
|
||||
|
||||
|
||||
- A Block event JSON object is sent when a block is complete, with the
|
||||
following fields:
|
||||
|
||||
* `dt`, the system datetime, as RFC3339-formatted string
|
||||
* `t`, the event type, always "block"
|
||||
* `s`, the slot height, as unsigned 64-bit integer
|
||||
* `h`, the tick height, as unsigned 64-bit integer
|
||||
* `l`, the slot leader id, as base-58 encoded string
|
||||
* `id`, the block id, as base-58 encoded string
|
@ -1,102 +0,0 @@
|
||||
# Blocktree
|
||||
|
||||
After a block reaches finality, all blocks from that one on down
|
||||
to the genesis block form a linear chain with the familiar name
|
||||
blockchain. Until that point, however, the validator must maintain all
|
||||
potentially valid chains, called *forks*. The process by which forks
|
||||
naturally form as a result of leader rotation is described in
|
||||
[fork generation](fork-generation.md). The *blocktree* data structure
|
||||
described here is how a validator copes with those forks until blocks
|
||||
are finalized.
|
||||
|
||||
The blocktree allows a validator to record every shred it observes
|
||||
on the network, in any order, as long as the shred is signed by the expected
|
||||
leader for a given slot.
|
||||
|
||||
Shreds are moved to a fork-able key space the tuple of `leader slot` + `shred
|
||||
index` (within the slot). This permits the skip-list structure of the Solana
|
||||
protocol to be stored in its entirety, without a-priori choosing which fork to
|
||||
follow, which Entries to persist or when to persist them.
|
||||
|
||||
Repair requests for recent shreds are served out of RAM or recent files and out
|
||||
of deeper storage for less recent shreds, as implemented by the store backing
|
||||
Blocktree.
|
||||
|
||||
### Functionalities of Blocktree
|
||||
|
||||
1. Persistence: the Blocktree lives in the front of the nodes verification
|
||||
pipeline, right behind network receive and signature verification. If the
|
||||
shred received is consistent with the leader schedule (i.e. was signed by the
|
||||
leader for the indicated slot), it is immediately stored.
|
||||
2. Repair: repair is the same as window repair above, but able to serve any
|
||||
shred that's been received. Blocktree stores shreds with signatures,
|
||||
preserving the chain of origination.
|
||||
3. Forks: Blocktree supports random access of shreds, so can support a
|
||||
validator's need to rollback and replay from a Bank checkpoint.
|
||||
4. Restart: with proper pruning/culling, the Blocktree can be replayed by
|
||||
ordered enumeration of entries from slot 0. The logic of the replay stage
|
||||
(i.e. dealing with forks) will have to be used for the most recent entries in
|
||||
the Blocktree.
|
||||
|
||||
### Blocktree Design
|
||||
|
||||
1. Entries in the Blocktree are stored as key-value pairs, where the key is the concatenated
|
||||
slot index and shred index for an entry, and the value is the entry data. Note shred indexes are zero-based for each slot (i.e. they're slot-relative).
|
||||
|
||||
2. The Blocktree maintains metadata for each slot, in the `SlotMeta` struct containing:
|
||||
* `slot_index` - The index of this slot
|
||||
* `num_blocks` - The number of blocks in the slot (used for chaining to a previous slot)
|
||||
* `consumed` - The highest shred index `n`, such that for all `m < n`, there exists a shred in this slot with shred index equal to `n` (i.e. the highest consecutive shred index).
|
||||
* `received` - The highest received shred index for the slot
|
||||
* `next_slots` - A list of future slots this slot could chain to. Used when rebuilding
|
||||
the ledger to find possible fork points.
|
||||
* `last_index` - The index of the shred that is flagged as the last shred for this slot. This flag on a shred will be set by the leader for a slot when they are transmitting the last shred for a slot.
|
||||
* `is_rooted` - True iff every block from 0...slot forms a full sequence without any holes. We can derive is_rooted for each slot with the following rules. Let slot(n) be the slot with index `n`, and slot(n).is_full() is true if the slot with index `n` has all the ticks expected for that slot. Let is_rooted(n) be the statement that "the slot(n).is_rooted is true". Then:
|
||||
|
||||
is_rooted(0)
|
||||
is_rooted(n+1) iff (is_rooted(n) and slot(n).is_full()
|
||||
|
||||
3. Chaining - When a shred for a new slot `x` arrives, we check the number of blocks (`num_blocks`) for that new slot (this information is encoded in the shred). We then know that this new slot chains to slot `x - num_blocks`.
|
||||
|
||||
4. Subscriptions - The Blocktree records a set of slots that have been "subscribed" to. This means entries that chain to these slots will be sent on the Blocktree channel for consumption by the ReplayStage. See the `Blocktree APIs` for details.
|
||||
|
||||
5. Update notifications - The Blocktree notifies listeners when slot(n).is_rooted is flipped from false to true for any `n`.
|
||||
|
||||
### Blocktree APIs
|
||||
|
||||
The Blocktree offers a subscription based API that ReplayStage uses to ask for entries it's interested in. The entries will be sent on a channel exposed by the Blocktree. These subscription API's are as follows:
|
||||
1. `fn get_slots_since(slot_indexes: &[u64]) -> Vec<SlotMeta>`: Returns new slots connecting to any element of the list `slot_indexes`.
|
||||
|
||||
2. `fn get_slot_entries(slot_index: u64, entry_start_index: usize, max_entries: Option<u64>) -> Vec<Entry>`: Returns the entry vector for the slot starting with `entry_start_index`, capping the result at `max` if `max_entries == Some(max)`, otherwise, no upper limit on the length of the return vector is imposed.
|
||||
|
||||
Note: Cumulatively, this means that the replay stage will now have to know when a slot is finished, and subscribe to the next slot it's interested in to get the next set of entries. Previously, the burden of chaining slots fell on the Blocktree.
|
||||
|
||||
### Interfacing with Bank
|
||||
|
||||
The bank exposes to replay stage:
|
||||
|
||||
1. `prev_hash`: which PoH chain it's working on as indicated by the hash of the last
|
||||
entry it processed
|
||||
2. `tick_height`: the ticks in the PoH chain currently being verified by this
|
||||
bank
|
||||
3. `votes`: a stack of records that contain:
|
||||
|
||||
1. `prev_hashes`: what anything after this vote must chain to in PoH
|
||||
2. `tick_height`: the tick height at which this vote was cast
|
||||
3. `lockout period`: how long a chain must be observed to be in the ledger to
|
||||
be able to be chained below this vote
|
||||
|
||||
Replay stage uses Blocktree APIs to find the longest chain of entries it can
|
||||
hang off a previous vote. If that chain of entries does not hang off the
|
||||
latest vote, the replay stage rolls back the bank to that vote and replays the
|
||||
chain from there.
|
||||
|
||||
### Pruning Blocktree
|
||||
|
||||
Once Blocktree entries are old enough, representing all the possible forks
|
||||
becomes less useful, perhaps even problematic for replay upon restart. Once a
|
||||
validator's votes have reached max lockout, however, any Blocktree contents
|
||||
that are not on the PoH chain for that vote for can be pruned, expunged.
|
||||
|
||||
Replicator nodes will be responsible for storing really old ledger contents,
|
||||
and validators need only persist their bank periodically.
|
865
book/src/cli.md
@ -1,865 +0,0 @@
|
||||
## solana CLI
|
||||
|
||||
The [solana-cli crate](https://crates.io/crates/solana-cli) provides a command-line interface tool for Solana
|
||||
|
||||
### Examples
|
||||
|
||||
#### Get Pubkey
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana address
|
||||
|
||||
// Return
|
||||
<PUBKEY>
|
||||
```
|
||||
|
||||
#### Airdrop SOL/Lamports
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana airdrop 2
|
||||
|
||||
// Return
|
||||
"2.00000000 SOL"
|
||||
|
||||
// Command
|
||||
$ solana airdrop 123 --lamports
|
||||
|
||||
// Return
|
||||
"123 lamports"
|
||||
```
|
||||
|
||||
#### Get Balance
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana balance
|
||||
|
||||
// Return
|
||||
"3.00050001 SOL"
|
||||
```
|
||||
|
||||
#### Confirm Transaction
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana confirm <TX_SIGNATURE>
|
||||
|
||||
// Return
|
||||
"Confirmed" / "Not found" / "Transaction failed with error <ERR>"
|
||||
```
|
||||
|
||||
#### Deploy program
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana deploy <PATH>
|
||||
|
||||
// Return
|
||||
<PROGRAM_ID>
|
||||
```
|
||||
|
||||
#### Unconditional Immediate Transfer
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
#### Post-Dated Transfer
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--after 2018-12-24T23:59:00 --require-timestamp-from <PUBKEY>
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
*`require-timestamp-from` is optional. If not provided, the transaction will expect a timestamp signed by this wallet's private key*
|
||||
|
||||
#### Authorized Transfer
|
||||
|
||||
A third party must send a signature to unlock the lamports.
|
||||
```sh
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--require-signature-from <PUBKEY>
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
|
||||
#### Post-Dated and Authorized Transfer
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--after 2018-12-24T23:59 --require-timestamp-from <PUBKEY> \
|
||||
--require-signature-from <PUBKEY>
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
|
||||
#### Multiple Witnesses
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--require-signature-from <PUBKEY> \
|
||||
--require-signature-from <PUBKEY>
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
|
||||
#### Cancelable Transfer
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana pay <PUBKEY> 123 \
|
||||
--require-signature-from <PUBKEY> \
|
||||
--cancelable
|
||||
|
||||
// Return
|
||||
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
|
||||
```
|
||||
|
||||
#### Cancel Transfer
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana cancel <PROCESS_ID>
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
#### Send Signature
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana send-signature <PUBKEY> <PROCESS_ID>
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
#### Indicate Elapsed Time
|
||||
|
||||
Use the current system time:
|
||||
```sh
|
||||
// Command
|
||||
$ solana send-timestamp <PUBKEY> <PROCESS_ID>
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
Or specify some other arbitrary timestamp:
|
||||
|
||||
```sh
|
||||
// Command
|
||||
$ solana send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
|
||||
|
||||
// Return
|
||||
<TX_SIGNATURE>
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```manpage
|
||||
solana 0.12.0
|
||||
|
||||
USAGE:
|
||||
solana [FLAGS] [OPTIONS] [SUBCOMMAND]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
SUBCOMMANDS:
|
||||
address Get your public key
|
||||
airdrop Request lamports
|
||||
authorize-voter Authorize a new vote signing keypair for the given vote account
|
||||
balance Get your balance
|
||||
cancel Cancel a transfer
|
||||
claim-storage-reward Redeem storage reward credits
|
||||
cluster-version Get the version of the cluster entrypoint
|
||||
confirm Confirm transaction by signature
|
||||
create-replicator-storage-account Create a replicator storage account
|
||||
create-storage-mining-pool-account Create mining pool account
|
||||
create-validator-storage-account Create a validator storage account
|
||||
create-vote-account Create a vote account
|
||||
deactivate-stake Deactivate the delegated stake from the stake account
|
||||
delegate-stake Delegate stake to a vote account
|
||||
deploy Deploy a program
|
||||
fees Display current cluster fees
|
||||
get Get wallet config settings
|
||||
get-slot Get current slot
|
||||
get-transaction-count Get current transaction count
|
||||
help Prints this message or the help of the given subcommand(s)
|
||||
pay Send a payment
|
||||
ping Submit transactions sequentially
|
||||
redeem-vote-credits Redeem credits in the stake account
|
||||
send-signature Send a signature to authorize a transfer
|
||||
send-timestamp Send a timestamp to unlock a transfer
|
||||
set Set a wallet config setting
|
||||
show-account Show the contents of an account
|
||||
show-stake-account Show the contents of a stake account
|
||||
show-storage-account Show the contents of a storage account
|
||||
show-vote-account Show the contents of a vote account
|
||||
validator-info Publish/get Validator info on Solana
|
||||
withdraw-stake Withdraw the unstaked lamports from the stake account
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-address
|
||||
Get your public key
|
||||
|
||||
USAGE:
|
||||
solana address [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-airdrop
|
||||
Request a batch of lamports
|
||||
|
||||
USAGE:
|
||||
solana airdrop [OPTIONS] <AMOUNT> [unit]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
|
||||
--drone-host <HOST> Drone host to use [default: the --url host]
|
||||
--drone-port <PORT> Drone port to use [default: 9900]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<AMOUNT> The airdrop amount to request (default unit SOL)
|
||||
<unit> Specify unit to use for request and balance display [possible values: SOL, lamports]
|
||||
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-authorize-voter
|
||||
Authorize a new vote signing keypair for the given vote account
|
||||
|
||||
USAGE:
|
||||
solana authorize-voter [OPTIONS] <VOTE ACCOUNT PUBKEY> <CURRENT VOTER KEYPAIR FILE> <NEW VOTER PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<VOTE ACCOUNT PUBKEY> Vote account in which to set the authorized voter
|
||||
<CURRENT VOTER KEYPAIR FILE> Keypair file for the currently authorized vote signer
|
||||
<NEW VOTER PUBKEY> New vote signer to authorize
|
||||
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-balance
|
||||
Get your balance
|
||||
|
||||
USAGE:
|
||||
solana balance [FLAGS] [OPTIONS] [PUBKEY]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
--lamports Display balance in lamports instead of SOL
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<PUBKEY> The public key of the balance to check
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-cancel
|
||||
Cancel a transfer
|
||||
|
||||
USAGE:
|
||||
solana cancel [OPTIONS] <PROCESS ID>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<PROCESS ID> The process id of the transfer to cancel
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-claim-storage-reward
|
||||
Redeem storage reward credits
|
||||
|
||||
USAGE:
|
||||
solana claim-storage-reward [OPTIONS] <NODE PUBKEY> <STORAGE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<NODE PUBKEY> The node account to credit the rewards to
|
||||
<STORAGE ACCOUNT PUBKEY> Storage account address to redeem credits for
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-cluster-version
|
||||
Get the version of the cluster entrypoint
|
||||
|
||||
USAGE:
|
||||
solana cluster-version [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-confirm
|
||||
Confirm transaction by signature
|
||||
|
||||
USAGE:
|
||||
solana confirm [OPTIONS] <SIGNATURE>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<SIGNATURE> The transaction signature to confirm
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-create-replicator-storage-account
|
||||
Create a replicator storage account
|
||||
|
||||
USAGE:
|
||||
solana create-replicator-storage-account [OPTIONS] <STORAGE ACCOUNT OWNER PUBKEY> <STORAGE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STORAGE ACCOUNT OWNER PUBKEY>
|
||||
<STORAGE ACCOUNT PUBKEY>
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-create-storage-mining-pool-account
|
||||
Create mining pool account
|
||||
|
||||
USAGE:
|
||||
solana create-storage-mining-pool-account [OPTIONS] <STORAGE ACCOUNT PUBKEY> <AMOUNT> [unit]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STORAGE ACCOUNT PUBKEY> Storage mining pool account address to fund
|
||||
<AMOUNT> The amount to assign to the storage mining pool account (default unit SOL)
|
||||
<unit> Specify unit to use for request [possible values: SOL, lamports]
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-create-validator-storage-account
|
||||
Create a validator storage account
|
||||
|
||||
USAGE:
|
||||
solana create-validator-storage-account [OPTIONS] <STORAGE ACCOUNT OWNER PUBKEY> <STORAGE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STORAGE ACCOUNT OWNER PUBKEY>
|
||||
<STORAGE ACCOUNT PUBKEY>
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-create-vote-account
|
||||
Create a vote account
|
||||
|
||||
USAGE:
|
||||
solana create-vote-account [OPTIONS] <VOTE ACCOUNT PUBKEY> <VALIDATOR PUBKEY> <LAMPORTS>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
--commission <NUM> The commission taken on reward redemption (0-255), default: 0
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<VOTE ACCOUNT PUBKEY> Vote account address to fund
|
||||
<VALIDATOR PUBKEY> Validator that will vote with this account
|
||||
<LAMPORTS> The amount of lamports to send to the vote account
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-deactivate-stake
|
||||
Deactivate the delegated stake from the stake account
|
||||
|
||||
USAGE:
|
||||
solana deactivate-stake [OPTIONS] <STAKE ACCOUNT KEYPAIR FILE> <PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STAKE ACCOUNT KEYPAIR FILE> Keypair file for the stake account, for signing the delegate transaction.
|
||||
<PUBKEY> The vote account to which the stake is currently delegated
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-delegate-stake
|
||||
Delegate stake to a vote account
|
||||
|
||||
USAGE:
|
||||
solana delegate-stake [OPTIONS] <STAKE ACCOUNT KEYPAIR FILE> <VOTE ACCOUNT PUBKEY> <AMOUNT> [unit]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STAKE ACCOUNT KEYPAIR FILE> Keypair file for the new stake account
|
||||
<VOTE ACCOUNT PUBKEY> The vote account to which the stake will be delegated
|
||||
<AMOUNT> The amount to delegate (default unit SOL)
|
||||
<unit> Specify unit to use for request [possible values: SOL, lamports]
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-deploy
|
||||
Deploy a program
|
||||
|
||||
USAGE:
|
||||
solana deploy [OPTIONS] <PATH TO PROGRAM>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<PATH TO PROGRAM> /path/to/program.o
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-fees
|
||||
Display current cluster fees
|
||||
|
||||
USAGE:
|
||||
solana fees [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-get
|
||||
Get wallet config settings
|
||||
|
||||
USAGE:
|
||||
solana get [OPTIONS] [CONFIG_FIELD]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<CONFIG_FIELD> Return a specific config setting [possible values: url, keypair]
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-get-slot
|
||||
Get current slot
|
||||
|
||||
USAGE:
|
||||
solana get-slot [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-get-transaction-count
|
||||
Get current transaction count
|
||||
|
||||
USAGE:
|
||||
solana get-transaction-count [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-pay
|
||||
Send a payment
|
||||
|
||||
USAGE:
|
||||
solana pay [FLAGS] [OPTIONS] <PUBKEY> <AMOUNT> [--] [unit]
|
||||
|
||||
FLAGS:
|
||||
--cancelable
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default:
|
||||
/Users/tyeraeulberg/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
--after <DATETIME> A timestamp after which transaction will execute
|
||||
--require-timestamp-from <PUBKEY> Require timestamp from this third party
|
||||
--require-signature-from <PUBKEY>... Any third party signatures required to unlock the lamports
|
||||
|
||||
ARGS:
|
||||
<PUBKEY> The public key of recipient
|
||||
<AMOUNT> The amount to send (default unit SOL)
|
||||
<unit> Specify unit to use for request [possible values: SOL, lamports]
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-ping
|
||||
Submit transactions sequentially
|
||||
|
||||
USAGE:
|
||||
solana ping [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default:
|
||||
~/.config/solana/wallet/config.yml]
|
||||
-c, --count <NUMBER> Stop after submitting count transactions
|
||||
-i, --interval <SECONDS> Wait interval seconds between submitting the next transaction [default: 2]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
-t, --timeout <SECONDS> Wait up to timeout seconds for transaction confirmation [default: 10]
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-redeem-vote-credits
|
||||
Redeem credits in the stake account
|
||||
|
||||
USAGE:
|
||||
solana redeem-vote-credits [OPTIONS] <STAKING ACCOUNT PUBKEY> <VOTE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STAKING ACCOUNT PUBKEY> Staking account address to redeem credits for
|
||||
<VOTE ACCOUNT PUBKEY> The vote account to which the stake was previously delegated.
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-send-signature
|
||||
Send a signature to authorize a transfer
|
||||
|
||||
USAGE:
|
||||
solana send-signature [OPTIONS] <PUBKEY> <PROCESS ID>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<PUBKEY> The public key of recipient
|
||||
<PROCESS ID> The process id of the transfer to authorize
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-send-timestamp
|
||||
Send a timestamp to unlock a transfer
|
||||
|
||||
USAGE:
|
||||
solana send-timestamp [OPTIONS] <PUBKEY> <PROCESS ID>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
--date <DATETIME> Optional arbitrary timestamp to apply
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<PUBKEY> The public key of recipient
|
||||
<PROCESS ID> The process id of the transfer to unlock
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-set
|
||||
Set a wallet config setting
|
||||
|
||||
USAGE:
|
||||
solana set [OPTIONS] <--url <URL>|--keypair <PATH>>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-show-account
|
||||
Show the contents of an account
|
||||
|
||||
USAGE:
|
||||
solana show-account [FLAGS] [OPTIONS] <ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
--lamports Display balance in lamports instead of SOL
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
-o, --output <FILE> Write the account data to this file
|
||||
|
||||
ARGS:
|
||||
<ACCOUNT PUBKEY> Account public key
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-show-stake-account
|
||||
Show the contents of a stake account
|
||||
|
||||
USAGE:
|
||||
solana show-stake-account [OPTIONS] <STAKE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STAKE ACCOUNT PUBKEY> Stake account public key
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-show-storage-account
|
||||
Show the contents of a storage account
|
||||
|
||||
USAGE:
|
||||
solana show-storage-account [OPTIONS] <STORAGE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STORAGE ACCOUNT PUBKEY> Storage account public key
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-show-vote-account
|
||||
Show the contents of a vote account
|
||||
|
||||
USAGE:
|
||||
solana show-vote-account [OPTIONS] <VOTE ACCOUNT PUBKEY>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<VOTE ACCOUNT PUBKEY> Vote account public key
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-validator-info
|
||||
Publish/get Validator info on Solana
|
||||
|
||||
USAGE:
|
||||
solana validator-info [OPTIONS] [SUBCOMMAND]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
SUBCOMMANDS:
|
||||
get Get and parse Solana Validator info
|
||||
help Prints this message or the help of the given subcommand(s)
|
||||
publish Publish Validator info on Solana
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-withdraw-stake
|
||||
Withdraw the unstaked lamports from the stake account
|
||||
|
||||
USAGE:
|
||||
solana withdraw-stake [OPTIONS] <STAKE ACCOUNT KEYPAIR FILE> <DESTINATION PUBKEY> <AMOUNT> [unit]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster
|
||||
-k, --keypair <PATH> /path/to/id.json
|
||||
|
||||
ARGS:
|
||||
<STAKE ACCOUNT KEYPAIR FILE> Keypair file for the stake account, for signing the withdraw transaction.
|
||||
<DESTINATION PUBKEY> The account where the lamports should be transfered
|
||||
<AMOUNT> The amount to withdraw from the stake account (default unit SOL)
|
||||
<unit> Specify unit to use for request [possible values: SOL, lamports]
|
||||
```
|
@ -1,100 +0,0 @@
|
||||
# A Solana Cluster
|
||||
|
||||
A Solana cluster is a set of fullnodes working together to serve client
|
||||
transactions and maintain the integrity of the ledger. Many clusters may
|
||||
coexist. When two clusters share a common genesis block, they attempt to
|
||||
converge. Otherwise, they simply ignore the existence of the other.
|
||||
Transactions sent to the wrong one are quietly rejected. In this chapter, we'll
|
||||
discuss how a cluster is created, how nodes join the cluster, how they share
|
||||
the ledger, how they ensure the ledger is replicated, and how they cope with
|
||||
buggy and malicious nodes.
|
||||
|
||||
## Creating a Cluster
|
||||
|
||||
Before starting any fullnodes, one first needs to create a *genesis block*.
|
||||
The block contains entries referencing two public keys, a *mint* and a
|
||||
*bootstrap leader*. The fullnode holding the bootstrap leader's private key is
|
||||
responsible for appending the first entries to the ledger. It initializes its
|
||||
internal state with the mint's account. That account will hold the number of
|
||||
native tokens defined by the genesis block. The second fullnode then contacts
|
||||
the bootstrap leader to register as a *validator* or *replicator*. Additional
|
||||
fullnodes then register with any registered member of the cluster.
|
||||
|
||||
A validator receives all entries from the leader and submits votes confirming
|
||||
those entries are valid. After voting, the validator is expected to store those
|
||||
entries until replicator nodes submit proofs that they have stored copies of
|
||||
it. Once the validator observes a sufficient number of copies exist, it deletes
|
||||
its copy.
|
||||
|
||||
## Joining a Cluster
|
||||
|
||||
Validators and replicators enter the cluster via registration messages sent to
|
||||
its *control plane*. The control plane is implemented using a *gossip*
|
||||
protocol, meaning that a node may register with any existing node, and expect
|
||||
its registration to propagate to all nodes in the cluster. The time it takes
|
||||
for all nodes to synchronize is proportional to the square of the number of
|
||||
nodes participating in the cluster. Algorithmically, that's considered very
|
||||
slow, but in exchange for that time, a node is assured that it eventually has
|
||||
all the same information as every other node, and that that information cannot
|
||||
be censored by any one node.
|
||||
|
||||
## Sending Transactions to a Cluster
|
||||
|
||||
Clients send transactions to any fullnode's Transaction Processing Unit (TPU)
|
||||
port. If the node is in the validator role, it forwards the transaction to the
|
||||
designated leader. If in the leader role, the node bundles incoming
|
||||
transactions, timestamps them creating an *entry*, and pushes them onto the
|
||||
cluster's *data plane*. Once on the data plane, the transactions are validated
|
||||
by validator nodes and replicated by replicator nodes, effectively appending
|
||||
them to the ledger.
|
||||
|
||||
## Confirming Transactions
|
||||
|
||||
A Solana cluster is capable of subsecond *confirmation* for up to 150 nodes
|
||||
with plans to scale up to hundreds of thousands of nodes. Once fully
|
||||
implemented, confirmation times are expected to increase only with the
|
||||
logarithm of the number of validators, where the logarithm's base is very high.
|
||||
If the base is one thousand, for example, it means that for the first thousand
|
||||
nodes, confirmation will be the duration of three network hops plus the time it
|
||||
takes the slowest validator of a supermajority to vote. For the next million
|
||||
nodes, confirmation increases by only one network hop.
|
||||
|
||||
Solana defines confirmation as the duration of time from when the leader
|
||||
timestamps a new entry to the moment when it recognizes a supermajority of
|
||||
ledger votes.
|
||||
|
||||
A gossip network is much too slow to achieve subsecond confirmation once the
|
||||
network grows beyond a certain size. The time it takes to send messages to all
|
||||
nodes is proportional to the square of the number of nodes. If a blockchain
|
||||
wants to achieve low confirmation and attempts to do it using a gossip network,
|
||||
it will be forced to centralize to just a handful of nodes.
|
||||
|
||||
Scalable confirmation can be achieved using the follow combination of
|
||||
techniques:
|
||||
|
||||
1. Timestamp transactions with a VDF sample and sign the timestamp.
|
||||
2. Split the transactions into batches, send each to separate nodes and have
|
||||
each node share its batch with its peers.
|
||||
3. Repeat the previous step recursively until all nodes have all batches.
|
||||
|
||||
Solana rotates leaders at fixed intervals, called *slots*. Each leader may only
|
||||
produce entries during its allotted slot. The leader therefore timestamps
|
||||
transactions so that validators may lookup the public key of the designated
|
||||
leader. The leader then signs the timestamp so that a validator may verify the
|
||||
signature, proving the signer is owner of the designated leader's public key.
|
||||
|
||||
Next, transactions are broken into batches so that a node can send transactions
|
||||
to multiple parties without making multiple copies. If, for example, the leader
|
||||
needed to send 60 transactions to 6 nodes, it would break that collection of 60
|
||||
into batches of 10 transactions and send one to each node. This allows the
|
||||
leader to put 60 transactions on the wire, not 60 transactions for each node.
|
||||
Each node then shares its batch with its peers. Once the node has collected all
|
||||
6 batches, it reconstructs the original set of 60 transactions.
|
||||
|
||||
A batch of transactions can only be split so many times before it is so small
|
||||
that header information becomes the primary consumer of network bandwidth. At
|
||||
the time of this writing, the approach is scaling well up to about 150
|
||||
validators. To scale up to hundreds of thousands of validators, each node can
|
||||
apply the same technique as the leader node to another set of nodes of equal
|
||||
size. We call the technique *data plane fanout*; learn more in the [data plan
|
||||
fanout](data-plane-fanout.md) section.
|
@ -1,140 +0,0 @@
|
||||
# Credit-Only Accounts
|
||||
|
||||
This design covers the handling of credit-only and credit-debit accounts in the
|
||||
[runtime](runtime.md). Accounts already distinguish themselves as credit-only or
|
||||
credit-debit based on the program ID specified by the transaction's instruction.
|
||||
Programs must treat accounts that are not owned by them as credit-only.
|
||||
|
||||
To identify credit-only accounts by program id would require the account to be
|
||||
fetched and loaded from disk. This operation is expensive, and while it is
|
||||
occurring, the runtime would have to reject any transactions referencing the same
|
||||
account.
|
||||
|
||||
The proposal introduces a `num_readonly_accounts` field to the transaction
|
||||
structure, and removes the `program_ids` dedicated vector for program accounts.
|
||||
|
||||
This design doesn't change the runtime transaction processing rules.
|
||||
Programs still can't write or spend accounts that they do not own, but it
|
||||
allows the runtime to optimistically take the correct lock for each account
|
||||
specified in the transaction before loading the accounts from storage.
|
||||
|
||||
Accounts selected as credit-debit by the transaction can still be treated as
|
||||
credit-only by the instructions.
|
||||
|
||||
## Runtime handling
|
||||
|
||||
credit-only accounts have the following properties:
|
||||
|
||||
* Can be deposited into: Deposits can be implemented as a simple `atomic_add`.
|
||||
* read-only access to account data.
|
||||
|
||||
Instructions that debit or modify the credit-only account data will fail.
|
||||
|
||||
## Account Lock Optimizations
|
||||
|
||||
The Accounts module keeps track of current locked accounts in the runtime,
|
||||
which separates credit-only accounts from the credit-debit accounts. The credit-only
|
||||
accounts can be cached in memory and shared between all the threads executing
|
||||
transactions.
|
||||
|
||||
The current runtime can't predict whether an account is credit-only or credit-debit when
|
||||
the transaction account keys are locked at the start of the transaction
|
||||
processing pipeline. Accounts referenced by the transaction have not been
|
||||
loaded from the disk yet.
|
||||
|
||||
An ideal design would cache the credit-only accounts while they are referenced by
|
||||
any transaction moving through the runtime, and release the cache when the last
|
||||
transaction exits the runtime.
|
||||
|
||||
## Credit-only accounts and read-only account data
|
||||
|
||||
Credit-only account data can be treated as read-only. Credit-debit
|
||||
account data is treated as read-write.
|
||||
|
||||
## Transaction changes
|
||||
|
||||
To enable the possibility of caching accounts only while they are in the
|
||||
runtime, the Transaction structure should be changed in the following way:
|
||||
|
||||
* `program_ids: Vec<Pubkey>` - This vector is removed. Program keys can be
|
||||
placed at the end of the `account_keys` vector within the `num_readonly_accounts`
|
||||
number set to the number of programs.
|
||||
|
||||
* `num_readonly_accounts: u8` - The number of keys from the **end** of the
|
||||
transaction's `account_keys` array that is credit-only.
|
||||
|
||||
The following possible accounts are present in an transaction:
|
||||
|
||||
* paying account
|
||||
* RW accounts
|
||||
* R accounts
|
||||
* Program IDs
|
||||
|
||||
The paying account must be credit-debit, and program IDs must be credit-only. The
|
||||
first account in the `account_keys` array is always the account that pays for
|
||||
the transaction fee, therefore it cannot be credit-only. For these reasons the
|
||||
credit-only accounts are all grouped together at the end of the `account_keys`
|
||||
vector. Counting credit-only accounts from the end allow for the default `0`
|
||||
value to still be functionally correct, since a transaction will succeed with
|
||||
all credit-debit accounts.
|
||||
|
||||
Since accounts can only appear once in the transaction's `account_keys` array,
|
||||
an account can only be credit-only or credit-debit in a single transaction, not
|
||||
both. The runtime treats a transaction as one atomic unit of execution. If any
|
||||
instruction needs credit-debit access to an account, a copy needs to be made. The
|
||||
write lock is held for the entire time the transaction is being processed by
|
||||
the runtime.
|
||||
|
||||
## Starvation
|
||||
|
||||
Read locks for credit-only accounts can keep the runtime from executing
|
||||
transactions requesting a write lock to a credit-debit account.
|
||||
|
||||
When a request for a write lock is made while a read lock is open, the
|
||||
transaction requesting the write lock should be cached. Upon closing the read
|
||||
lock, the pending transactions can be pushed through the runtime.
|
||||
|
||||
While a pending write transaction exists, any additional read lock requests for
|
||||
that account should fail. It follows that any other write lock requests will also
|
||||
fail. Currently, clients must retransmit when a transaction fails because of
|
||||
a pending transaction. This approach would mimic that behavior as closely as
|
||||
possible while preventing write starvation.
|
||||
|
||||
## Program execution with credit-only accounts
|
||||
|
||||
Before handing off the accounts to program execution, the runtime can mark each
|
||||
account in each instruction as a credit-only account. The credit-only accounts can
|
||||
be passed as references without an extra copy. The transaction will abort on a
|
||||
write to credit-only.
|
||||
|
||||
An alternative is to detect writes to credit-only accounts and fail the
|
||||
transactions before commit.
|
||||
|
||||
## Alternative design
|
||||
|
||||
This design attempts to cache a credit-only account after loading without the use
|
||||
of a transaction-specified credit-only accounts list. Instead, the credit-only
|
||||
accounts are held in a reference-counted table inside the runtime as the
|
||||
transactions are processed.
|
||||
|
||||
1. Transaction accounts are locked.
|
||||
a. If the account is present in the ‘credit-only' table, the TX does not fail.
|
||||
The pending state for this TX is marked NeedReadLock.
|
||||
2. Transaction accounts are loaded.
|
||||
a. Transaction accounts that are credit-only increase their reference
|
||||
count in the `credit-only` table.
|
||||
b. Transaction accounts that need a write lock and are present in the
|
||||
`credit-only` table fail.
|
||||
3. Transaction accounts are unlocked.
|
||||
a. Decrement the `credit-only` lock table reference count; remove if its 0
|
||||
b. Remove from the `lock` set if the account is not in the `credit-only`
|
||||
table.
|
||||
|
||||
The downside with this approach is that if the `lock` set mutex is released
|
||||
between lock and load to allow better pipelining of transactions, a request for
|
||||
a credit-only account may fail. Therefore, this approach is not suitable for
|
||||
treating programs as credit-only accounts.
|
||||
|
||||
Holding the accounts lock mutex while fetching the account from disk would
|
||||
potentially have a significant performance hit on the runtime. Fetching from
|
||||
disk is expected to be slow, but can be parallelized between multiple disks.
|
@ -1,111 +0,0 @@
|
||||
# Cross-Program Invocation
|
||||
|
||||
## Problem
|
||||
|
||||
In today's implementation a client can create a transaction that modifies two
|
||||
accounts, each owned by a separate on-chain program:
|
||||
|
||||
```rust,ignore
|
||||
let message = Message::new(vec![
|
||||
token_instruction::pay(&alice_pubkey),
|
||||
acme_instruction::launch_missiles(&bob_pubkey),
|
||||
]);
|
||||
client.send_message(&[&alice_keypair, &bob_keypair], &message);
|
||||
```
|
||||
|
||||
The current implementation does not, however, allow the `acme` program to
|
||||
conveniently invoke `token` instructions on the client's behalf:
|
||||
|
||||
```rust,ignore
|
||||
let message = Message::new(vec![
|
||||
acme_instruction::pay_and_launch_missiles(&alice_pubkey, &bob_pubkey),
|
||||
]);
|
||||
client.send_message(&[&alice_keypair, &bob_keypair], &message);
|
||||
```
|
||||
|
||||
Currently, there is no way to create instruction `pay_and_launch_missiles` that executes
|
||||
`token_instruction::pay` from the `acme` program. The workaround is to extend the
|
||||
`acme` program with the implementation of the `token` program, and create `token`
|
||||
accounts with `ACME_PROGRAM_ID`, which the `acme` program is permitted to modify.
|
||||
With that workaround, `acme` can modify token-like accounts created by the `acme`
|
||||
program, but not token accounts created by the `token` program.
|
||||
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
The goal of this design is to modify Solana's runtime such that an on-chain
|
||||
program can invoke an instruction from another program.
|
||||
|
||||
Given two on-chain programs `token` and `acme`, each implementing instructions
|
||||
`pay()` and `launch_missiles()` respectively, we would ideally like to implement
|
||||
the `acme` module with a call to a function defined in the `token` module:
|
||||
|
||||
```rust,ignore
|
||||
use token;
|
||||
|
||||
fn launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||
...
|
||||
}
|
||||
|
||||
fn pay_and_launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||
token::pay(&keyed_accounts[1..])?;
|
||||
|
||||
launch_missiles(keyed_accounts)?;
|
||||
}
|
||||
```
|
||||
|
||||
The above code would require that the `token` crate be dynamically linked,
|
||||
so that a custom linker could intercept calls and validate accesses to
|
||||
`keyed_accounts`. That is, even though the client intends to modify both
|
||||
`token` and `acme` accounts, only `token` program is permitted to modify
|
||||
the `token` account, and only the `acme` program is permitted to modify
|
||||
the `acme` account.
|
||||
|
||||
Backing off from that ideal cross-program call, a slightly more
|
||||
verbose solution is to expose token's existing `process_instruction()`
|
||||
entrypoint to the acme program:
|
||||
|
||||
```rust,ignore
|
||||
use token_instruction;
|
||||
|
||||
fn launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||
...
|
||||
}
|
||||
|
||||
fn pay_and_launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
|
||||
let alice_pubkey = keyed_accounts[1].key;
|
||||
let instruction = token_instruction::pay(&alice_pubkey);
|
||||
process_instruction(&instruction)?;
|
||||
|
||||
launch_missiles(keyed_accounts)?;
|
||||
}
|
||||
```
|
||||
|
||||
where `process_instruction()` is built into Solana's runtime and responsible
|
||||
for routing the given instruction to the `token` program via the instruction's
|
||||
`program_id` field. Before invoking `pay()`, the runtime must also ensure that
|
||||
`acme` didn't modify any accounts owned by `token`. It does this by calling
|
||||
`runtime::verify_instruction()` and then afterward updating all the `pre_*`
|
||||
variables to tentatively commit `acme`'s account modifications. After `pay()`
|
||||
completes, the runtime must again ensure that `token` didn't modify any
|
||||
accounts owned by `acme`. It should call `verify_instruction()` again, but this
|
||||
time with the `token` program ID. Lastly, after `pay_and_launch_missiles()`
|
||||
completes, the runtime must call `verify_instruction()` one more time, where it
|
||||
normally would, but using all updated `pre_*` variables. If executing
|
||||
`pay_and_launch_missiles()` up to `pay()` made no invalid account changes,
|
||||
`pay()` made no invalid changes, and executing from `pay()` until
|
||||
`pay_and_launch_missiles()` returns made no invalid changes, then the runtime
|
||||
can transitively assume `pay_and_launch_missiles()` as whole made no invalid
|
||||
account changes, and therefore commit all account modifications.
|
||||
|
||||
### Setting `KeyedAccount.is_signer`
|
||||
|
||||
When `process_instruction()` is invoked, the runtime must create a new
|
||||
`KeyedAccounts` parameter using the signatures from the *original* transaction
|
||||
data. Since the `token` program is immutable and existed on-chain prior to the
|
||||
`acme` program, the runtime can safely treat the transaction signature as a
|
||||
signature of a transaction with a `token` instruction. When the runtime sees
|
||||
the given instruction references `alice_pubkey`, it looks up the key in the
|
||||
transaction to see if that key corresponds to a transaction signature. In this
|
||||
case it does and so sets `KeyedAccount.is_signer`, thereby authorizing the
|
||||
`token` program to modify Alice's account.
|
@ -1,86 +0,0 @@
|
||||
# Creating Signing Services with Drones
|
||||
|
||||
This chapter defines an off-chain service called a *drone*, which acts as
|
||||
custodian of a user's private key. In its simplest form, it can be used to
|
||||
create *airdrop* transactions, a token transfer from the drone's account to a
|
||||
client's account.
|
||||
|
||||
## Signing Service
|
||||
|
||||
A drone is a simple signing service. It listens for requests to sign
|
||||
*transaction data*. Once received, the drone validates the request however it
|
||||
sees fit. It may, for example, only accept transaction data with a
|
||||
`SystemInstruction::Transfer` instruction transferring only up to a certain amount
|
||||
of tokens. If the drone accepts the transaction, it returns an `Ok(Signature)`
|
||||
where `Signature` is a signature of the transaction data using the drone's
|
||||
private key. If it rejects the transaction data, it returns a `DroneError`
|
||||
describing why.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### Granting access to an on-chain game
|
||||
|
||||
Creator of on-chain game tic-tac-toe hosts a drone that responds to airdrop
|
||||
requests containing an `InitGame` instruction. The drone signs the transaction
|
||||
data in the request and returns it, thereby authorizing its account to pay the
|
||||
transaction fee and as well as seeding the game's account with enough tokens to
|
||||
play it. The user then creates a transaction for its transaction data and the
|
||||
drones signature and submits it to the Solana cluster. Each time the user
|
||||
interacts with the game, the game pays the user enough tokens to pay the next
|
||||
transaction fee to advance the game. At that point, the user may choose to keep
|
||||
the tokens instead of advancing the game. If the creator wants to defend
|
||||
against that case, they could require the user to return to the drone to sign
|
||||
each instruction.
|
||||
|
||||
### Worldwide airdrop of a new token
|
||||
|
||||
Creator of a new on-chain token (ERC-20 interface), may wish to do a worldwide
|
||||
airdrop to distribute its tokens to millions of users over just a few seconds.
|
||||
That drone cannot spend resources interacting with the Solana cluster. Instead,
|
||||
the drone should only verify the client is unique and human, and then return
|
||||
the signature. It may also want to listen to the Solana cluster for recent
|
||||
entry IDs to support client retries and to ensure the airdrop is targeting the
|
||||
desired cluster.
|
||||
|
||||
|
||||
## Attack vectors
|
||||
|
||||
### Invalid recent_blockhash
|
||||
|
||||
The drone may prefer its airdrops only target a particular Solana cluster. To
|
||||
do that, it listens to the cluster for new entry IDs and ensure any requests
|
||||
reference a recent one.
|
||||
|
||||
Note: to listen for new entry IDs assumes the drone is either a fullnode or a
|
||||
*light* client. At the time of this writing, light clients have not been
|
||||
implemented and no proposal describes them. This document assumes one of the
|
||||
following approaches be taken:
|
||||
|
||||
1. Define and implement a light client
|
||||
2. Embed a fullnode
|
||||
3. Query the jsonrpc API for the latest last id at a rate slightly faster than
|
||||
ticks are produced.
|
||||
|
||||
### Double spends
|
||||
|
||||
A client may request multiple airdrops before the first has been submitted to
|
||||
the ledger. The client may do this maliciously or simply because it thinks the
|
||||
first request was dropped. The drone should not simply query the cluster to
|
||||
ensure the client has not already received an airdrop. Instead, it should use
|
||||
`recent_blockhash` to ensure the previous request is expired before signing another.
|
||||
Note that the Solana cluster will reject any transaction with a `recent_blockhash`
|
||||
beyond a certain *age*.
|
||||
|
||||
### Denial of Service
|
||||
|
||||
If the transaction data size is smaller than the size of the returned signature
|
||||
(or descriptive error), a single client can flood the network. Considering
|
||||
that a simple `Transfer` operation requires two public keys (each 32 bytes) and a
|
||||
`fee` field, and that the returned signature is 64 bytes (and a byte to
|
||||
indicate `Ok`), consideration for this attack may not be required.
|
||||
|
||||
In the current design, the drone accepts TCP connections. This allows clients
|
||||
to DoS the service by simply opening lots of idle connections. Switching to UDP
|
||||
may be preferred. The transaction data will be smaller than a UDP packet since
|
||||
the transaction sent to the Solana cluster is already pinned to using UDP.
|
@ -1,11 +0,0 @@
|
||||
## Attack Vectors
|
||||
|
||||
### Colluding validation and replication clients
|
||||
|
||||
A colluding validation-client, may take the strategy to mark PoReps from non-colluding replicator nodes as invalid as an attempt to maximize the rewards for the colluding replicator nodes. In this case, it isn’t feasible for the offended-against replicator nodes to petition the network for resolution as this would result in a network-wide vote on each offending PoRep and create too much overhead for the network to progress adequately. Also, this mitigation attempt would still be vulnerable to a >= 51% staked colluder.
|
||||
|
||||
Alternatively, transaction fees from submitted PoReps are pooled and distributed across validation-clients in proportion to the number of valid PoReps discounted by the number of invalid PoReps as voted by each validator-client. Thus invalid votes are directly dis-incentivized through this reward channel. Invalid votes that are revealed by replicator nodes as fishing PoReps, will not be discounted from the payout PoRep count.
|
||||
|
||||
Another collusion attack involves a validator-client who may take the strategy to ignore invalid PoReps from colluding replicator and vote them as valid. In this case, colluding replicator-clients would not have to store the data while still receiving rewards for validated PoReps. Additionally, colluding validator nodes would also receive rewards for validating these PoReps. To mitigate this attack, validators must randomly sample PoReps corresponding to the ledger block they are validating and because of this, there will be multiple validators that will receive the colluding replicator’s invalid submissions. These non-colluding validators will be incentivized to mark these PoReps as invalid as they have no way to determine whether the proposed invalid PoRep is actually a fishing PoRep, for which a confirmation vote would result in the validator’s stake being slashed.
|
||||
|
||||
In this case, the proportion of time a colluding pair will be successful has an upper limit determined by the % of stake of the network claimed by the colluding validator. This also sets bounds to the value of such an attack. For example, if a colluding validator controls 10% of the total validator stake, transaction fees will be lost (likely sent to mining pool) by the colluding replicator 90% of the time and so the attack vector is only profitable if the per-PoRep reward at least 90% higher than the average PoRep transaction fee. While, probabilistically, some colluding replicator-client PoReps will find their way to colluding validation-clients, the network can also monitor rates of paired (validator + replicator) discrepancies in voting patterns and censor identified colluders in these cases.
|
@ -1,18 +0,0 @@
|
||||
## Economic Sustainability
|
||||
|
||||
Long term economic sustainability is one of the guiding principles of Solana’s economic design. While it is impossible to predict how decentralized economies will develop over time, especially economies with flexible decentralized governances, we can arrange economic components such that, under certain conditions, a sustainable economy may take shape in the long term. In the case of Solana’s network, these components take the form of token issuance (via inflation) and token burning’.
|
||||
|
||||
The dominant remittances from the Solana mining pool are validator and replicator rewards. The disinflationary mechanism is a flat, protocol-specified and adjusted, % of each transaction fee.
|
||||
|
||||
The Replicator rewards are to be delivered to replicators as a portion of the network inflation after successful PoRep validation. The per-PoRep reward amount is determined as a function of the total network storage redundancy at the time of the PoRep validation and the network goal redundancy. This function is likely to take the form of a discount from a base reward to be delivered when the network has achieved and maintained its goal redundancy. An example of such a reward function is shown in **Figure 3**
|
||||
|
||||
<!--  -->
|
||||
<p style="text-align:center;"><img src=".gitbook/assets/porep_reward.png" alt="==PoRep Reward Curve ==" width="800"/></p>
|
||||
|
||||
**Figure 3**: Example PoRep reward design as a function of global network storage redundancy.
|
||||
|
||||
In the example shown in Figure 1, multiple per PoRep base rewards are explored (as a % of Tx Fee) to be delivered when the global ledger replication redundancy meets 10X. When the global ledger replication redundancy is less than 10X, the base reward is discounted as a function of the square of the ratio of the actual ledger replication redundancy to the goal redundancy (i.e. 10X).
|
||||
|
||||
<!-- The other protocol-based remittance goes to validation-clients as a reward distributed in proportion to stake-weight for voting to validate the ledger state. The functional issuance of this reward is described in [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md) and is designed to reduce over time until validators are incentivized solely through collection of transaction fees. Therefore, in the long-run, protocol-based rewards to replication-nodes will be the only remittances from the mining pool, and will have to be countered by the portion of each non-PoRep transaction fee that is directed back into the mining pool. I.e. for a long-term self-sustaining economy, replicator-client rewards must be subsidized through a minimum fee on each non-PoRep transaction pre-allocated to the mining pool. Through this constraint, we can write the following inequality:
|
||||
-->
|
||||
<!-- **== WIP [here](https://docs.google.com/document/d/1HBDasdkjS4Ja9wC_tIUsZPVcxGAWTuYOq9zf6xoQNps/edit?usp=sharing) ==** -->
|
@ -1,13 +0,0 @@
|
||||
## Proposed MVP of Economic Design
|
||||
|
||||
The preceeding sections, outlined in the [Economic Design Overview](ed_overview.md), describe a long-term vision of a sustainable Solana economy. Of course, we don't expect the final implementation to perfectly match what has been described above. We intend to fully engage with network stakeholders throughout the implementation phases (i.e. pre-testnet, testnet, mainnet) to ensure the system supports, and is representative of, the various network participants' interests. The first step toward this goal, however, is outlining a some desired MVP economic features to be available for early pre-testnet and testnet participants. Below is a rough sketch outlining basic economic functionality from which a more complete and functional system can be developed.
|
||||
|
||||
### MVP Economic Features
|
||||
|
||||
* Faucet to deliver testnet SOLs to validators for staking and dapp development.
|
||||
* Mechanism by which validators are rewarded via network inflation.
|
||||
* Ability to delegate tokens to validator nodes
|
||||
* Validator set commission fees on interest from delegated tokens.
|
||||
* Replicators to receive fixed, arbitrary reward for submitting validated PoReps. Reward size mechanism (i.e. PoRep reward as a function of total ledger redundancy) to come later.
|
||||
* Pooling of replicator PoRep transaction fees and weighted distribution to validators based on PoRep verification (see [Replication-validation Transaction Fees](ed_vce_replication_validation_transaction_fees.md). It will be useful to test this protection against attacks on testnet.
|
||||
* Nice-to-have: auto-delegation of replicator rewards to validator.
|
@ -1,16 +0,0 @@
|
||||
## Economic Design Overview
|
||||
|
||||
Solana’s crypto-economic system is designed to promote a healthy, long term self-sustaining economy with participant incentives aligned to the security and decentralization of the network. The main participants in this economy are validation-clients and replication-clients. Their contributions to the network, state validation and data storage respectively, and their requisite incentive mechanisms are discussed below.
|
||||
|
||||
The main channels of participant remittances are referred to as protocol-based rewards and transaction fees. Protocol-based rewards are issuances from a global, protocol-defined, inflation rate. These rewards will constitute the total reward delivered to replication and validation clients, the remaining sourced from transaction fees. In the early days of the network, it is likely that protocol-based rewards, deployed based on predefined issuance schedule, will drive the majority of participant incentives to participate in the network.
|
||||
|
||||
These protocol-based rewards, to be distributed to participating validation and replication clients, are to be a result of a global supply inflation rate, calculated per Solana epoch and distributed amongst the active validator set. As discussed further below, the per annum inflation rate is based on a pre-determined disinflationary schedule. This provides the network with monetary supply predictability which supports long term economic stability and security.
|
||||
|
||||
Transaction fees are market-based participant-to-participant transfers, attached to network interactions as a necessary motivation and compensation for the inclusion and execution of a proposed transaction (be it a state execution or proof-of-replication verification). A mechanism for long-term economic stability and forking protection through partial burning of each transaction fee is also discussed below.
|
||||
|
||||
A high-level schematic of Solana’s crypto-economic design is shown below in **Figure 1**. The specifics of validation-client economics are described in sections: [Validation-client Economics](ed_validation_client_economics.md), [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md), [State-validation Transaction Fees](ed_vce_state_validation_transaction_fees.md) and [Replication-validation Transaction Fees](ed_vce_replication_validation_transaction_fees.md). Also, the chapter titled [Validation Stake Delegation](ed_vce_validation_stake_delegation.md) closes with a discussion of validator delegation opportunties and marketplace. Additionally, in [Storage Rent Economics](ed_storage_rent_economics.md), we describe an implementation of storage rent to account for the externality costs of maintaining the active state of the ledger. The [Replication-client Economics](ed_replication_client_economics.md) chapter will review the Solana network design for global ledger storage/redundancy and replicator-client economics ([Storage-replication rewards](ed_rce_storage_replication_rewards.md)) along with a replicator-to-validator delegation mechanism designed to aide participant on-boarding into the Solana economy discussed in [Replication-client Reward Auto-delegation](ed_rce_replication_client_reward_auto_delegation.md). <!-- The [Economic Sustainability](ed_economic_sustainability.md) section dives deeper into Solana’s design for long-term economic sustainability and outlines the constraints and conditions for a self-sustaining economy.--> An outline of features for an MVP economic design is discussed in the [Economic Design MVP](ed_mvp.md) section. Finally, in chapter [Attack Vectors](ed_attack_vectors.md), various attack vectors will be described and potential vulnerabilities explored and parameterized.
|
||||
|
||||
<!--  -->
|
||||
<p style="text-align:center;"><img src=".gitbook/assets/economic_design_infl_230719.png" alt="== Solana Economic Design Diagram ==" width="800"/></p>
|
||||
|
||||
**Figure 1**: Schematic overview of Solana economic incentive design.
|
@ -1,5 +0,0 @@
|
||||
### Storage-replication Rewards
|
||||
|
||||
Replicator-clients download, encrypt and submit PoReps for ledger block sections.3 PoReps submitted to the PoH stream, and subsequently validated, function as evidence that the submitting replicator client is indeed storing the assigned ledger block sections on local hard drive space as a service to the network. Therefore, replicator clients should earn protocol rewards proportional to the amount of storage, and the number of successfully validated PoReps, that they are verifiably providing to the network.
|
||||
|
||||
Additionally, replicator clients have the opportunity to capture a portion of slashed bounties [TBD] of dishonest validator clients. This can be accomplished by a replicator client submitting a verifiably false PoRep for which a dishonest validator client receives and signs as a valid PoRep. This reward incentive is to prevent lazy validators and minimize validator-replicator collusion attacks, more on this below.
|
@ -1,3 +0,0 @@
|
||||
## Replication-client economics
|
||||
|
||||
Replication-clients should be rewarded for providing the network with storage space. Incentivization of the set of replicators provides data security through redundancy of the historical ledger. Replication nodes are rewarded in proportion to the amount of ledger data storage provided, as proved by successfully submitting Proofs-of-Replication to the cluster.. These rewards are captured by generating and entering Proofs of Replication (PoReps) into the PoH stream which can be validated by Validation nodes as described above in the [Replication-validation Transaction Fees](ed_vce_replication_validation_transaction_fees.md) chapter.
|
@ -1,6 +0,0 @@
|
||||
## Validation-client Economics
|
||||
|
||||
Validator-clients are eligible to receive protocol-based (i.e. inflation-based) rewards issued via stake-based annual interest rates (calculated per epoch) by providing compute (CPU+GPU) resources to validate and vote on a given PoH state. These protocol-based rewards are determined through an algorithmic disinflationary schedule as a function of total amount of circulating tokens.
|
||||
The network is expected to launch with an annual inflation rate around 15%, set to decrease by 15% per year until a long-term stable rate of 1-2% is reached. These issuances are to be split and distributed to participating validators and replicators, with around 90% of the issued tokens allocated for validator rewards. Because the network will be distributing a fixed amount of inflation rewards across the stake-weighted valdiator set, any individual validator's interest rate will be a function of the amount of staked SOL in relation to the circulating SOL.
|
||||
|
||||
Additionally, validator clients may earn revenue through fees via state-validation transactions and Proof-of-Replication (PoRep) transactions. For clarity, we separately describe the design and motivation of these revenue distriubutions for validation-clients below: state-validation protocol-based rewards, state-validation transaction fees and rent, and PoRep-validation transaction fees.
|
@ -1,9 +0,0 @@
|
||||
### Replication-validation Transaction Fees
|
||||
|
||||
As previously mentioned, validator-clients will also be responsible for validating PoReps submitted into the PoH stream by replicator-clients. In this case, validators are providing compute (CPU/GPU) and light storage resources to confirm that these replication proofs could only be generated by a client that is storing the referenced PoH leger block.
|
||||
|
||||
While replication-clients are incentivized and rewarded through protocol-based rewards schedule (see [Replication-client Economics](ed_replication_client_economics.md)), validator-clients will be incentivized to include and validate PoReps in PoH through collection of transaction fees associated with the submitted PoReps and distribution of protocol rewards proportional to the validated PoReps. As will be described in detail in the Section 3.1, replication-client rewards are protocol-based and designed to reward based on a global data redundancy factor. I.e. the protocol will incentivize replication-client participation through rewards based on a target ledger redundancy (e.g. 10x data redundancy).
|
||||
|
||||
The validation of PoReps by validation-clients is computationally more expensive than state-validation (detail in the [Economic Sustainability](ed_economic_sustainability.md) chapter), thus the transaction fees are expected to be proportionally higher.
|
||||
|
||||
There are various attack vectors available for colluding validation and replication clients, also described in detail below in [Economic Sustainability](ed_economic_sustainability). To protect against various collusion attack vectors, for a given epoch, validator rewards are distributed across participating validation-clients in proportion to the number of validated PoReps in the epoch less the number of PoReps that mismatch the replicators challenge. The PoRep challenge game is described in [Ledger Replication](https://github.com/solana-labs/solana/blob/master/book/src/ledger-replication.md#the-porep-game). This design rewards validators proportional to the number of PoReps they process and validate, while providing negative pressure for validation-clients to submit lazy or malicious invalid votes on submitted PoReps (note that it is computationally prohibitive to determine whether a validator-client has marked a valid PoRep as invalid).
|
@ -1,40 +0,0 @@
|
||||
### State-validation protocol-based rewards
|
||||
|
||||
Validator-clients have two functional roles in the Solana network:
|
||||
|
||||
* Validate (vote) the current global state of that PoH along with any Proofs-of-Replication (see [Replication Client Economics](ed_replication_client_economics.md)) that they are eligible to validate.
|
||||
|
||||
* Be elected as ‘leader’ on a stake-weighted round-robin schedule during which time they are responsible for collecting outstanding transactions and Proofs-of-Replication and incorporating them into the PoH, thus updating the global state of the network and providing chain continuity.
|
||||
|
||||
Validator-client rewards for these services are to be distributed at the end of each Solana epoch. As previously discussed, compensation for validator-clients is provided via a protocol-based annual inflation rate dispersed in proportion to the stake-weight of each validator (see below) along with leader-claimed transaction fees available during each leader rotation. I.e. during the time a given validator-client is elected as leader, it has the opportunity to keep a portion of each transaction fee, less a protocol-specified amount that is destroyed (see [Validation-client State Transaction Fees](ed_vce_state_validation_transaction_fees.md)). PoRep transaction fees are also collected by the leader client and validator PoRep rewards are distributed in proportion to the number of validated PoReps less the number of PoReps that mismatch a replicator's challenge. (see [Replication-client Transaction Fees](ed_vce_replication_validation_transaction_fees.md))
|
||||
|
||||
|
||||
The effective protocol-based annual interest rate (%) per epoch received by validation-clients is to be a function of:
|
||||
|
||||
* the current global inflation rate, derived from the pre-determined dis-inflationary issuance schedule (see [Validation-client Economics](ed_validartion_client_economics.md))
|
||||
|
||||
* the fraction of staked SOLs out of the current total circulating supply,
|
||||
|
||||
* the up-time/participation [% of available slots that validator had opportunity to vote on] of a given validator over the previous epoch.
|
||||
|
||||
The first factor is a function of protocol parameters only (i.e. independent of validator behavior in a given epoch) and results in a global validation reward schedule designed to incentivize early participation, provide clear montetary stability and provide optimal security in the network.
|
||||
|
||||
At any given point in time, a specific validator's interest rate can be determined based on the porportion of circulating supply that is staked by the network and the validator's uptime/activity in the previous epoch. For example, consider a hypothetical instance of the network with an initial circulating token supply of 250MM tokens with an additional 250MM vesting over 3 years. Additionally an inflation rate is specified at network launch of 7.5%, and a disinflationary schedule of 20% decrease in inflation rate per year (the actual rates to be implemented are to be worked out during the testnet experimentation phase of mainnet launch). With these broad assumptions, the 10-year inflation rate (adjusted daily for this example) is shown in **Figure 2**, while the total circulating token supply is illustrated in **Figure 3**. Neglected in this toy-model is the inflation supression due to the portion of each transaction fee that is to be destroyed.
|
||||
|
||||
<p style="text-align:center;"><img src=".gitbook/assets/p_ex_schedule.png" alt="drawing" width="800"/></p>
|
||||
**Figure 2:** In this example schedule, the annual inflation rate [%] reduces at around 20% per year, until it reaches the long-term, fixed, 1.5% rate.
|
||||
|
||||
<p style="text-align:center;"><img src=".gitbook/assets/p_ex_supply.png" alt="drawing" width="800"/></p>
|
||||
**Figure 3:** The total token supply over a 10-year period, based on an initial 250MM tokens with the disinflationary inflation schedule as shown in **Figure 2**
|
||||
|
||||
Over time, the interest rate, at a fixed network staked percentage, will reduce concordant with network inflation. Validation-client interest rates are designed to be higher in the early days of the network to incentivize participation and jumpstart the network economy. As previously mentioned, the inflation rate is expected to stabalize near 1-2% which also results in a fixed, long-term, interest rate to be provided to validator-clients. This value does not represent the total interest available to validator-clients as transaction fees for state-validation and ledger storage replication (PoReps) are not accounted for here.
|
||||
|
||||
Given these example parameters, annualized validator-specific interest rates can be determined based on the global fraction of tokens bonded as stake, as well as their uptime/activity in the previous epoch. For the purpose of this example, we assume 100% uptime for all validators and a split in interest-based rewards between validators and replicator nodes of 80%/20%. Additionally, the fraction of staked circulating supply is assummed to be constant. Based on these assumptions, an annualized validation-client interest rate schedule as a function of % circulating token supply that is staked is shown in** Figure 4**.
|
||||
|
||||
<!--  -->
|
||||
|
||||
<p style="text-align:center;"><img src=".gitbook/assets/p_ex_interest.png" alt="drawing" width="800"/></p>
|
||||
|
||||
**Figure 4:** Shown here are example validator interest rates over time, neglecting transaction fees, segmented by fraction of total circulating supply bonded as stake.
|
||||
|
||||
This epoch-specific protocol-defined interest rate sets an upper limit of *protocol-generated* annual interest rate (not absolute total interest rate) possible to be delivered to any validator-client per epoch. The distributed interest rate per epoch is then discounted from this value based on the participation of the validator-client during the previous epoch.
|
@ -1,20 +0,0 @@
|
||||
### State-validation Transaction Fees
|
||||
|
||||
Each transaction sent through the network, to be processed by the current leader validation-client and confirmed as a global state transaction, must contain a transaction fee. Transaction fees offer many benefits in the Solana economic design, for example they:
|
||||
|
||||
* provide unit compensation to the validator network for the CPU/GPU resources necessary to process the state transaction,
|
||||
|
||||
* reduce network spam by introducing real cost to transactions,
|
||||
|
||||
* open avenues for a transaction market to incentivize validation-client to collect and process submitted transactions in their function as leader,
|
||||
|
||||
* and provide potential long-term economic stability of the network through a protocol-captured minimum fee amount per transaction, as described below.
|
||||
|
||||
Many current blockchain economies (e.g. Bitcoin, Ethereum), rely on protocol-based rewards to support the economy in the short term, with the assumption that the revenue generated through transaction fees will support the economy in the long term, when the protocol derived rewards expire. In an attempt to create a sustainable economy through protocol-based rewards and transaction fees, a fixed portion of each transaction fee is destroyed, with the remaining fee going to the current leader processing the transaction. A scheduled global inflation rate provides a source for rewards distributed to validation-clients, through the process described above, and replication-clients, as discussed below.
|
||||
|
||||
Transaction fees are set by the network cluster based on recent historical throughput, see [Congestion Driven Fees](transaction-fees.md#congestion-driven-fees). This minimum portion of each transaction fee can be dynamically adjusted depending on historical gas usage. In this way, the protocol can use the minimum fee to target a desired hardware utilisation. By monitoring a protocol specified gas usage with respect to a desired, target usage amount, the minimum fee can be raised/lowered which should, in turn, lower/raise the actual gas usage per block until it reaches the target amount. This adjustment process can be thought of as similar to the difficulty adjustment algorithm in the Bitcoin protocol, however in this case it is adjusting the minimum transaction fee to guide the transaction processing hardware usage to a desired level.
|
||||
|
||||
As mentioned, a fixed-proportion of each transaction fee is to be destroyed. The intent of this design is to retain leader incentive to include as many transactions as possible within the leader-slot time, while providing an inflation limiting mechansim that protects against "tax evasion" attacks (i.e. side-channel fee payments)<sup>[1](ed_referenced.md)</sup>.
|
||||
|
||||
Additionally, the burnt fees can be a consideration in fork selection. In the case of a PoH fork with a malicious, censoring leader, we would expect the total fees destroyed to be less than a comparable honest fork, due to the fees lost from censoring. If the censoring leader is to compensate for these lost protocol fees, they would have to replace the burnt fees on their fork themselves, thus potentially reducing the incentive to censor in the first place.
|
||||
|
@ -1,66 +0,0 @@
|
||||
# Embedding the Move Language
|
||||
|
||||
## Problem
|
||||
|
||||
Solana enables developers to write on-chain programs in general purpose
|
||||
programming languages such as C or Rust, but those programs contain
|
||||
Solana-specific mechanisms. For example, there isn't another chain that asks
|
||||
developers to create a Rust module with a `process_instruction(KeyedAccounts)`
|
||||
function. Whenever practical, Solana should offer dApp developers more portable
|
||||
options.
|
||||
|
||||
Until just recently, no popular blockchain offered a language that could expose
|
||||
the value of Solana's massively parallel [runtime](runtime.md). Solidity
|
||||
contracts, for example, do not separate references to shared data from contract
|
||||
code, and therefore need to be executed serially to ensure deterministic
|
||||
behavior. In practice we see that the most aggressively optimized EVM-based
|
||||
blockchains all seem to peak out around 1,200 TPS - a small fraction of what
|
||||
Solana can do. The Libra project, on the other hand, designed an on-chain
|
||||
programming language called Move that is more suitable for parallel execution.
|
||||
Like Solana's runtime, Move programs depend on accounts for all shared state.
|
||||
|
||||
The biggest design difference between Solana's runtime and Libra's Move VM is
|
||||
how they manage safe invocations between modules. Solana took an operating
|
||||
systems approach and Libra took the domain-specific language approach. In the
|
||||
runtime, a module must trap back into the runtime to ensure the caller's module
|
||||
did not write to data owned by the callee. Likewise, when the callee completes,
|
||||
it must again trap back to the runtime to ensure the callee did not write to
|
||||
data owned by the caller. Move, on the other hand, includes an advanced type
|
||||
system that allows these checks to be run by its bytecode verifier. Because
|
||||
Move bytecode can be verified, the cost of verification is paid just once, at
|
||||
the time the module is loaded on-chain. In the runtime, the cost is paid each
|
||||
time a transaction crosses between modules. The difference is similar in spirit
|
||||
to the difference between a dynamically-typed language like Python versus a
|
||||
statically-typed language like Java. Solana's runtime allows dApps to be
|
||||
written in general purpose programming languages, but that comes with the cost
|
||||
of runtime checks when jumping between programs.
|
||||
|
||||
This proposal attempts to define a way to embed the Move VM such that:
|
||||
|
||||
* cross-module invocations within Move do not require the runtime's
|
||||
cross-program runtime checks
|
||||
* Move programs can leverage functionality in other Solana programs and vice
|
||||
versa
|
||||
* Solana's runtime parallelism is exposed to batches of Move and non-Move
|
||||
transactions
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
### Move VM as a Solana loader
|
||||
|
||||
The Move VM shall be embedded as a Solana loader under the identifier
|
||||
`MOVE_PROGRAM_ID`, so that Move modules can be marked as `executable` with the
|
||||
VM as its `owner`. This will allow modules to load module dependencies, as well
|
||||
as allow for parallel execution of Move scripts.
|
||||
|
||||
All data accounts owned by Move modules must set their owners to the loader,
|
||||
`MOVE_PROGRAM_ID`. Since Move modules encapsulate their account data in the
|
||||
same way Solana programs encapsulate theirs, the Move module owner should be
|
||||
embedded in the account data. The runtime will grant write access to the Move
|
||||
VM, and Move grants access to the module accounts.
|
||||
|
||||
### Interacting with Solana programs
|
||||
|
||||
To invoke instructions in non-Move programs, Solana would need to extend the
|
||||
Move VM with a `process_instruction()` system call. It would work the same as
|
||||
`process_instruction()` Rust BPF programs.
|
@ -1,104 +0,0 @@
|
||||
# Fork Generation
|
||||
|
||||
The chapter describes how forks naturally occur as a consequence of [leader
|
||||
rotation](leader-rotation.md).
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
Nodes take turns being leader and generating the PoH that encodes state
|
||||
changes. The cluster can tolerate loss of connection to any leader by
|
||||
synthesizing what the leader ***would*** have generated had it been connected
|
||||
but not ingesting any state changes. The possible number of forks is thereby
|
||||
limited to a "there/not-there" skip list of forks that may arise on leader
|
||||
rotation slot boundaries. At any given slot, only a single leader's
|
||||
transactions will be accepted.
|
||||
|
||||
## Message Flow
|
||||
|
||||
1. Transactions are ingested by the current leader.
|
||||
2. Leader filters valid transactions.
|
||||
3. Leader executes valid transactions updating its state.
|
||||
4. Leader packages transactions into entries based off its current PoH slot.
|
||||
5. Leader transmits the entries to validator nodes (in signed blobs)
|
||||
1. The PoH stream includes ticks; empty entries that indicate liveness of
|
||||
the leader and the passage of time on the cluster.
|
||||
2. A leader's stream begins with the tick entries necessary complete the PoH
|
||||
back to the leaders most recently observed prior leader slot.
|
||||
6. Validators retransmit entries to peers in their set and to further
|
||||
downstream nodes.
|
||||
7. Validators validate the transactions and execute them on their state.
|
||||
8. Validators compute the hash of the state.
|
||||
9. At specific times, i.e. specific PoH tick counts, validators transmit votes
|
||||
to the leader.
|
||||
1. Votes are signatures of the hash of the computed state at that PoH tick
|
||||
count
|
||||
2. Votes are also propagated via gossip
|
||||
10. Leader executes the votes as any other transaction and broadcasts them to
|
||||
the cluster.
|
||||
11. Validators observe their votes and all the votes from the cluster.
|
||||
|
||||
## Partitions, Forks
|
||||
|
||||
Forks can arise at PoH tick counts that correspond to a vote. The next leader
|
||||
may not have observed the last vote slot and may start their slot with
|
||||
generated virtual PoH entries. These empty ticks are generated by all nodes in
|
||||
the cluster at a cluster-configured rate for hashes/per/tick `Z`.
|
||||
|
||||
There are only two possible versions of the PoH during a voting slot: PoH with
|
||||
`T` ticks and entries generated by the current leader, or PoH with just ticks.
|
||||
The "just ticks" version of the PoH can be thought of as a virtual ledger, one
|
||||
that all nodes in the cluster can derive from the last tick in the previous
|
||||
slot.
|
||||
|
||||
Validators can ignore forks at other points (e.g. from the wrong leader), or
|
||||
slash the leader responsible for the fork.
|
||||
|
||||
Validators vote based on a greedy choice to maximize their reward described in
|
||||
[Tower BFT](tower-bft.md).
|
||||
|
||||
### Validator's View
|
||||
|
||||
#### Time Progression
|
||||
|
||||
The diagram below represents a validator's view of the
|
||||
PoH stream with possible forks over time. L1, L2, etc. are leader slots, and
|
||||
`E`s represent entries from that leader during that leader's slot. The `x`s
|
||||
represent ticks only, and time flows downwards in the diagram.
|
||||
|
||||
|
||||
<img alt="Fork generation" src=".gitbook/assets/fork-generation.svg" class="center"/>
|
||||
|
||||
Note that an `E` appearing on 2 forks at the same slot is a slashable
|
||||
condition, so a validator observing `E3` and `E3'` can slash L3 and safely
|
||||
choose `x` for that slot. Once a validator commits to a forks, other forks can
|
||||
be discarded below that tick count. For any slot, validators need only
|
||||
consider a single "has entries" chain or a "ticks only" chain to be proposed by
|
||||
a leader. But multiple virtual entries may overlap as they link back to the a
|
||||
previous slot.
|
||||
|
||||
#### Time Division
|
||||
|
||||
It's useful to consider leader rotation over PoH tick count as time division of
|
||||
the job of encoding state for the cluster. The following table presents the
|
||||
above tree of forks as a time-divided ledger.
|
||||
|
||||
leader slot | L1 | L2 | L3 | L4 | L5
|
||||
-------|----|----|----|----|----
|
||||
data | E1| E2 | E3 | E4 | E5
|
||||
ticks since prev | | | | x | xx
|
||||
|
||||
Note that only data from leader L3 will be accepted during leader slot L3.
|
||||
Data from L3 may include "catchup" ticks back to a slot other than L2 if L3 did
|
||||
not observe L2's data. L4 and L5's transmissions include the "ticks to prev"
|
||||
PoH entries.
|
||||
|
||||
This arrangement of the network data streams permits nodes to save exactly this
|
||||
to the ledger for replay, restart, and checkpoints.
|
||||
|
||||
### Leader's View
|
||||
|
||||
When a new leader begins a slot, it must first transmit any PoH (ticks)
|
||||
required to link the new slot with the most recently observed and voted slot.
|
||||
The fork the leader proposes would link the current slot to a previous fork
|
||||
that the leader has voted on with virtual ticks.
|
@ -1,168 +0,0 @@
|
||||
# Getting Started
|
||||
|
||||
The Solana git repository 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.
|
||||
|
||||
For all four variations, you'd need the latest Rust toolchain and the Solana
|
||||
source code:
|
||||
|
||||
First, install Rust's package manager Cargo.
|
||||
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
$ source $HOME/.cargo/env
|
||||
```
|
||||
|
||||
Now checkout the code from github:
|
||||
|
||||
```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
|
||||
$ TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
|
||||
$ git checkout $TAG
|
||||
```
|
||||
|
||||
### Configuration Setup
|
||||
|
||||
Ensure important programs such as the vote program are built before any
|
||||
nodes are started
|
||||
```bash
|
||||
$ cargo build
|
||||
```
|
||||
|
||||
The network is initialized with a genesis ledger generated by running the
|
||||
following script.
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/setup.sh
|
||||
```
|
||||
|
||||
### Drone
|
||||
|
||||
In order for the fullnodes and clients 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 with:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/drone.sh
|
||||
```
|
||||
|
||||
### Singlenode Testnet
|
||||
|
||||
Before you start a validator, make sure you know the IP address of the machine you
|
||||
want to be the bootstrap 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 bootstrap leader in a separate shell:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/bootstrap-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
|
||||
additional validators in separate shells:
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/validator-x.sh
|
||||
```
|
||||
|
||||
To run a performance-enhanced full node on Linux,
|
||||
[CUDA 10.0](https://developer.nvidia.com/cuda-downloads) must be installed on
|
||||
your system:
|
||||
|
||||
```bash
|
||||
$ ./fetch-perf-libs.sh
|
||||
$ SOLANA_CUDA=1 ./multinode-demo/bootstrap-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/bench-tps.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.
|
||||
|
||||
### Testnet Debugging
|
||||
|
||||
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.
|
||||
|
||||
For example
|
||||
|
||||
* To enable `info` everywhere and `debug` only in the solana::banking_stage module:
|
||||
|
||||
```bash
|
||||
$ export RUST_LOG=solana=info,solana::banking_stage=debug
|
||||
```
|
||||
|
||||
* To enable BPF program logging:
|
||||
|
||||
```bash
|
||||
$ export RUST_LOG=solana_bpf_loader=trace
|
||||
```
|
||||
|
||||
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-validator_:
|
||||
|
||||
```bash
|
||||
$ sudo gdb
|
||||
attach <PID>
|
||||
set logging on
|
||||
thread apply all bt
|
||||
```
|
||||
|
||||
This will dump all the threads stack traces into gdb.txt
|
||||
|
||||
## 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/bench-tps.sh --entrypoint testnet.solana.com:8001 --drone testnet.solana.com:9900 --duration 60 --tx_count 50
|
||||
```
|
||||
|
||||
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)
|
||||
|
@ -1,128 +0,0 @@
|
||||
# Gossip Service
|
||||
|
||||
The Gossip Service acts as a gateway to nodes in the control plane. Validators
|
||||
use the service to ensure information is available to all other nodes in a cluster.
|
||||
The service broadcasts information using a gossip protocol.
|
||||
|
||||
## Gossip Overview
|
||||
|
||||
Nodes continuously share signed data objects among themselves in order to
|
||||
manage a cluster. For example, they share their contact information, ledger
|
||||
height, and votes.
|
||||
|
||||
Every tenth of a second, each node sends a "push" message and/or a "pull"
|
||||
message. Push and pull messages may elicit responses, and push messages may be
|
||||
forwarded on to others in the cluster.
|
||||
|
||||
Gossip runs on a well-known UDP/IP port or a port in a well-known range. Once
|
||||
a cluster is bootstrapped, nodes advertise to each other where to find their
|
||||
gossip endpoint (a socket address).
|
||||
|
||||
## Gossip Records
|
||||
|
||||
Records shared over gossip are arbitrary, but signed and versioned (with a
|
||||
timestamp) as needed to make sense to the node receiving them. If a node
|
||||
receives two records from the same source, it updates its own copy with the
|
||||
record with the most recent timestamp.
|
||||
|
||||
## Gossip Service Interface
|
||||
|
||||
### Push Message
|
||||
|
||||
A node sends a push message to tells the cluster it has information to share.
|
||||
Nodes send push messages to `PUSH_FANOUT` push peers.
|
||||
|
||||
Upon receiving a push message, a node examines the message for:
|
||||
|
||||
1. Duplication: if the message has been seen before, the node drops the message
|
||||
and may respond with `PushMessagePrune` if forwarded from a low staked node
|
||||
|
||||
2. New data: if the message is new to the node
|
||||
* Stores the new information with an updated version in its cluster info and
|
||||
purges any previous older value
|
||||
* Stores the message in `pushed_once` (used for detecting duplicates,
|
||||
purged after `PUSH_MSG_TIMEOUT * 5` ms)
|
||||
* Retransmits the messages to its own push peers
|
||||
|
||||
3. Expiration: nodes drop push messages that are older than `PUSH_MSG_TIMEOUT`
|
||||
|
||||
### Push Peers, Prune Message
|
||||
|
||||
A nodes selects its push peers at random from the active set of known peers.
|
||||
The node keeps this selection for a relatively long time. When a prune message
|
||||
is received, the node drops the push peer that sent the prune. Prune is an
|
||||
indication that there is another, higher stake weighted path to that node than direct push.
|
||||
|
||||
The set of push peers is kept fresh by rotating a new node into the set every
|
||||
`PUSH_MSG_TIMEOUT/2` milliseconds.
|
||||
|
||||
### Pull Message
|
||||
|
||||
A node sends a pull message to ask the cluster if there is any new information.
|
||||
A pull message is sent to a single peer at random and comprises a Bloom filter
|
||||
that represents things it already has. A node receiving a pull message
|
||||
iterates over its values and constructs a pull response of things that miss the
|
||||
filter and would fit in a message.
|
||||
|
||||
A node constructs the pull Bloom filter by iterating over current values and
|
||||
recently purged values.
|
||||
|
||||
A node handles items in a pull response the same way it handles new data in a
|
||||
push message.
|
||||
|
||||
|
||||
## Purging
|
||||
|
||||
Nodes retain prior versions of values (those updated by a pull or push) and
|
||||
expired values (those older than `GOSSIP_PULL_CRDS_TIMEOUT_MS`) in
|
||||
`purged_values` (things I recently had). Nodes purge `purged_values` that are
|
||||
older than `5 * GOSSIP_PULL_CRDS_TIMEOUT_MS`.
|
||||
|
||||
## Eclipse Attacks
|
||||
|
||||
An eclipse attack is an attempt to take over the set of node connections with
|
||||
adversarial endpoints.
|
||||
|
||||
This is relevant to our implementation in the following ways.
|
||||
|
||||
* Pull messages select a random node from the network. An eclipse attack on
|
||||
*pull* would require an attacker to influence the random selection in such a way
|
||||
that only adversarial nodes are selected for pull.
|
||||
|
||||
* Push messages maintain an active set of nodes and select a random fanout for
|
||||
every push message. An eclipse attack on *push* would influence the active set
|
||||
selection, or the random fanout selection.
|
||||
|
||||
### Time and Stake based weights
|
||||
|
||||
Weights are calculated based on `time since last picked` and the `natural log` of the `stake weight`.
|
||||
|
||||
Taking the `ln` of the stake weight allows giving all nodes a fairer chance of network
|
||||
coverage in a reasonable amount of time. It helps normalize the large possible `stake weight` differences between nodes.
|
||||
This way a node with low `stake weight`, compared to a node with large `stake weight` will only have to wait a
|
||||
few multiples of ln(`stake`) seconds before it gets picked.
|
||||
|
||||
There is no way for an adversary to influence these parameters.
|
||||
|
||||
### Pull Message
|
||||
|
||||
A node is selected as a pull target based on the weights described above.
|
||||
|
||||
### Push Message
|
||||
|
||||
A prune message can only remove an adversary from a potential connection.
|
||||
|
||||
Just like *pull message*, nodes are selected into the active set based on weights.
|
||||
|
||||
## Notable differences from PlumTree
|
||||
|
||||
The active push protocol described here is based on [Plum
|
||||
Tree](https://haslab.uminho.pt/jop/files/lpr07a.pdf). The main differences are:
|
||||
|
||||
* Push messages have a wallclock that is signed by the originator. Once the
|
||||
wallclock expires the message is dropped. A hop limit is difficult to implement
|
||||
in an adversarial setting.
|
||||
|
||||
* Lazy Push is not implemented because its not obvious how to prevent an
|
||||
adversary from forging the message fingerprint. A naive approach would allow an
|
||||
adversary to be prioritized for pull based on their input.
|
@ -1,25 +0,0 @@
|
||||
# Instructions
|
||||
|
||||
For the purposes of building a [Transaction](transaction.md), a more
|
||||
verbose instruction format is used:
|
||||
|
||||
* **Instruction:**
|
||||
* **program_id:** The pubkey of the on-chain program that executes the
|
||||
instruction
|
||||
* **accounts:** An ordered list of accounts that should be passed to
|
||||
the program processing the instruction, including metadata detailing
|
||||
if an account is a signer of the transaction and if it is a credit
|
||||
only account.
|
||||
* **data:** A byte array that is passed to the program executing the
|
||||
instruction
|
||||
|
||||
A more compact form is actually included in a `Transaction`:
|
||||
|
||||
* **CompiledInstruction:**
|
||||
* **program_id_index:** The index of the `program_id` in the
|
||||
`account_keys` list
|
||||
* **accounts:** An ordered list of indices into `account_keys`
|
||||
specifying the accounds that should be passed to the program
|
||||
processing the instruction.
|
||||
* **data:** A byte array that is passed to the program executing the
|
||||
instruction
|
@ -1,204 +0,0 @@
|
||||
|
||||
# Inter-chain Transaction Verification Program
|
||||
|
||||
## Problem
|
||||
|
||||
Inter-chain applications are not new to the digital asset ecosystem; in fact, even
|
||||
the smaller centralized exchanges still categorically dwarf all single chain dapps
|
||||
put together in terms of users and volume. They command massive valuations and
|
||||
have spent years effectively optimizing their core products for a broad range of
|
||||
end users. However, their basic operations center around mechanisms that require
|
||||
their users to unilaterally trust them, typically with little to no recourse
|
||||
or protection from accidental loss. This has led to the broader digital asset
|
||||
ecosystem being fractured along network lines because interoperability solutions typically:
|
||||
* Are technically complex to fully implement
|
||||
* Create unstable network scale incentive structures
|
||||
* Require consistent and high level cooperation between stakeholders
|
||||
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
Simple Payment Verification (SPV) is a generic term for a range of different
|
||||
methodologies used by light clients on most major blockchain networks to verify
|
||||
aspects of the network state without the burden of fully storing and maintaining
|
||||
the chain itself. In most cases, this means relying on a form of hash tree to
|
||||
supply a proof of the presence of a given transaction in a certain block by
|
||||
comparing against a root hash in that block’s header or equivalent. This allows
|
||||
a light client or wallet to reach a probabilistic level of certainty about
|
||||
on-chain events by itself with a minimum of trust required with regard to network nodes.
|
||||
|
||||
Traditionally the process of assembling and validating these proofs is carried
|
||||
out off chain by nodes, wallets, or other clients, but it also offers a potential
|
||||
mechanism for inter-chain state verification. However, by moving the capability
|
||||
to validate SPV proofs on-chain as a smart contract while leveraging the archival
|
||||
properties inherent to the blockchain, it is possible to construct a system for
|
||||
programmatically detecting and verifying transactions on other networks without
|
||||
the involvement of any type of trusted oracle or complex multi-stage consensus
|
||||
mechanism. This concept is broadly generalisable to any network with an SPV
|
||||
mechanism and can even be operated bilaterally on other smart contract platforms,
|
||||
opening up the possibility of cheap, fast, inter-chain transfer of value without
|
||||
relying on collateral, hashlocks, or trusted intermediaries.
|
||||
|
||||
Opting to take advantage of well established and developmentally stable mechanisms
|
||||
already common to all major blockchains allows SPV based interoperability solutions
|
||||
to be dramatically simpler than orchestrated multi-stage approaches. As part of
|
||||
this, they dispense with the need for widely agreed upon cross chain communication
|
||||
standards and the large multi-party organizations that write them in favor of a
|
||||
set of discrete contract-based services that can be easily utilized by caller
|
||||
contracts through a common abstraction format. This will set the groundwork for
|
||||
a broad range of dapps and contracts able to interoperate across the variegated
|
||||
and every growing platform ecosystem.
|
||||
|
||||
## Terminology
|
||||
|
||||
SPV Program - Client-facing interface for the inter-chain SPV system, manages participant roles.
|
||||
SPV Engine - Validates transaction proofs, subset of the SPV Program.
|
||||
Client - The caller to the SPV Program, typically another solana contract.
|
||||
Prover - Party who generates proofs for transactions and submits them to the SPV Program.
|
||||
Transaction Proof - Created by Provers, contains a merkle proof, transaction, and blockheader reference.
|
||||
Merkle Proof - Basic SPV proof that validates the presence of a transaction in a certain block.
|
||||
Block Header - Represents the basic parameters and relative position of a given block.
|
||||
Proof Request - An order placed by a client for verification of transaction(s) by provers.
|
||||
Header Store - A data structure for storing and referencing ranges of block headers in proofs.
|
||||
Client Request - Transaction from the client to the SPV Program to trigger creation of a Proof Request.
|
||||
Sub-account - A Solana account owned by another contract account, without its own private key.
|
||||
|
||||
|
||||
## Service
|
||||
|
||||
SPV Programs run as contracts deployed on the Solana network and maintain a type
|
||||
of public marketplace for SPV proofs that allows any party to submit both requests
|
||||
for proofs as well as proofs themselves for verification in response to requests.
|
||||
There will be multiple SPV Program instances active at any given time, at least
|
||||
one for each connected external network and potentially multiple instances per
|
||||
network. SPV program instances will be relatively consistent in their high level
|
||||
API and feature sets with some variation between currency platforms (Bitcoin,
|
||||
Litecoin) and smart contract platforms owing to the potential for verification of
|
||||
network state changes beyond simply transactions. In every case regardless of
|
||||
network, the SPV Program relies on an internal component called an SPV engine to
|
||||
provide stateless verification of the actual SPV proofs upon which the higher
|
||||
level client facing features and api are built. The SPV engine requires a
|
||||
network specific implementation, but allows easy extension of the larger
|
||||
inter-chain ecosystem by any team who chooses to carry out that implementation
|
||||
and drop it into the standard SPV program for deployment.
|
||||
|
||||
For purposes of Proof Requests, the requester is referred to as the program client,
|
||||
which in most if not all cases will be another Solana Contract. The client can
|
||||
choose to submit a request pertaining to a specific transaction or to include a
|
||||
broader filter that can apply to any of a range of parameters of a transaction
|
||||
including its inputs, outputs, and amount. For example, A client could submit a
|
||||
request for any transaction sent from a given address A to address B with the
|
||||
amount X after a certain time. This structure can be used in a range of
|
||||
applications, such as verifying a specific intended payment in the case of an
|
||||
atomic swap or detecting the movement of collateral assets for a loan.
|
||||
|
||||
Following submission of a Client Request, assuming that it is successfully
|
||||
validated, a proof request account is created by the SPV program to track the
|
||||
progress of the request. Provers use the account to specify the request they
|
||||
intend to fill in the proofs they submit for validation, at which point the
|
||||
SPV program validates those proofs and if successful, saves them to the account
|
||||
data of the request account. Clients can monitor the status of their requests
|
||||
and see any applicable transactions alongside their proofs by querying the
|
||||
account data of the request account. In future iterations when supported by
|
||||
Solana, this process will be simplified by contracts publishing events rather
|
||||
than requiring a polling style process as described.
|
||||
|
||||
## Implementation
|
||||
|
||||
The Solana Inter-chain SPV mechanism consists of the following components and participants:
|
||||
|
||||
#### SPV engine
|
||||
A contract deployed on Solana which statelessly verifies SPV proofs for the caller.
|
||||
It takes as arguments for validation:
|
||||
* An SPV proof in the correct format of the blockchain associated with the program
|
||||
* Reference(s) to the relevant block headers to compare that proof against
|
||||
* The necessary parameters of the transaction to verify
|
||||
If the proof in question is successfully validated, the SPV program saves proof
|
||||
of that verification to the request account, which can be saved by the caller to
|
||||
its account data or otherwise handled as necessary. SPV programs also expose
|
||||
utilities and structs used for representation and validation of headers,
|
||||
transactions, hashes, etc. on a chain by chain basis.
|
||||
|
||||
#### SPV program
|
||||
A contract deployed on Solana which coordinates and intermediates the interaction
|
||||
between Clients and Provers and manages the validation of requests, headers,
|
||||
proofs, etc. It is the primary point of access for Client contracts to access the
|
||||
inter-chain. SPV mechanism. It offers the following core features:
|
||||
* Submit Proof Request - allows client to place a request for a specific proof or set of proofs
|
||||
* Cancel Proof Request - allows client to invalidate a pending request
|
||||
* Fill Proof Request - used by Provers to submit for validation a proof corresponding to a given Proof Request
|
||||
The SPV program maintains a publicly available listing of valid pending Proof
|
||||
Requests in its account data for the benefit of the Provers, who monitor it and
|
||||
enclose references to target requests with their submitted proofs.
|
||||
|
||||
|
||||
#### Proof Request
|
||||
A message sent by the Client to the SPV engine denoting a request for a proof of
|
||||
a specific transaction or set of transactions. Proof Requests can either manually
|
||||
specify a certain transaction by its hash or can elect to submit a filter that
|
||||
matches multiple transactions or classes of transactions. For example, a filter
|
||||
matching “any transaction from address xxx to address yyy” could be used to detect
|
||||
payment of a debt or settlement of an inter-chain swap. Likewise, a filter matching
|
||||
“any transaction from address xxx” could be used by a lending or synthetic token
|
||||
minting contract to monitor and react to changes in collateralization. Proof
|
||||
Requests are sent with a fee, which is disbursed by the SPV engine contract to
|
||||
the appropriate Prover once a proof matching that request is validated.
|
||||
|
||||
#### Request Book
|
||||
The public listing of valid, open Proof Requests available to provers to fill or
|
||||
for clients to cancel. Roughly analogous to an orderbook in an exchange, but with
|
||||
a single type of listing rather than two separate sides. It is stored in the
|
||||
account data of the SPV program.
|
||||
|
||||
#### Proof
|
||||
A proof of the presence of a given transaction in the blockchain in question.
|
||||
Proofs encompass both the actual merkle proof and reference(s) to a chain of valid
|
||||
sequential block headers. They are constructed and submitted by Provers in
|
||||
accordance with the specifications of the publicly available Proof Requests
|
||||
hosted on the request book by the SPV program. Upon Validation, they are saved
|
||||
to the account data of the relevant Proof Request, which can be used by the
|
||||
Client to monitor the state of the request.
|
||||
|
||||
#### Client
|
||||
The originator of a request for a transaction proof. Clients will most often be
|
||||
other contracts as parts of dapps or specific financial products like loans,
|
||||
swaps, escrow, etc. The client in any given verification process cycle initially
|
||||
submits a ClientRequest which communicates the parameters and fee and if
|
||||
successfully validated, results in the creation of a Proof Request account by
|
||||
the SPV program. The Client may also submit a CancelRequest referencing an active
|
||||
Proof Request in order to denote it as invalid for purposes of proof submission.
|
||||
|
||||
#### Prover
|
||||
The submitter of a proof that fills a Proof Request. Provers monitor the request
|
||||
book of the SPV program for outstanding Proof Requests and generate matching
|
||||
proofs, which they submit to the SPV program for validation. If the proof is
|
||||
accepted, the fee associated with the Proof Request in question is disbursed to
|
||||
the Prover. Provers typically operate as Solana Blockstreamer nodes that also
|
||||
have access to a Bitcoin node, which they use for purposes of constructing proofs
|
||||
and accessing block headers.
|
||||
|
||||
#### Header Store
|
||||
An account-based data structure used to maintain block headers for the purpose
|
||||
of inclusion in submitted proofs by reference to the header store account.
|
||||
header stores can be maintained by independent entities, since header chain
|
||||
validation is a component of the SPV program proof validation mechanism. Fees
|
||||
that are paid out by Proof Requests to Provers are split between the submitter
|
||||
of the merkle proof itself and the header store that is referenced in the
|
||||
submitted proof. Due to the current inability to grow already allocated account
|
||||
data capacity, the use case necessitates a data structure that can grow
|
||||
indefinitely without rebalancing. Sub-accounts are accounts owned by the SPV
|
||||
program without their own private keys that are used for storage by allocating
|
||||
blockheaders to their account data. Multiple potential approaches to the
|
||||
implementation of the header store system are feasible:
|
||||
|
||||
Store Headers in program sub-accounts indexed by Public address:
|
||||
* Each sub-account holds one header and has a public key matching the blockhash
|
||||
* Requires same number of account data lookups as confirmations per verification
|
||||
* Limit on number of confirmations (15-20) via max transaction data ceiling
|
||||
* No network-wide duplication of individual headers
|
||||
|
||||
Linked List of multiple sub-accounts storing headers:
|
||||
* Maintain sequential index of storage accounts, many headers per storage account
|
||||
* Max 2 account data lookups for >99.9% of verifications (1 for most)
|
||||
* Compact sequential data address format allows any number of confirmations and fast lookups
|
||||
* Facilitates network-wide header duplication inefficiencies
|
@ -1,116 +0,0 @@
|
||||
# What is Solana?
|
||||
|
||||
Solana is an open source project implementing a new,
|
||||
high-performance, permissionless blockchain. Solana is also the name of a
|
||||
company headquartered in San Francisco that maintains the open source project.
|
||||
|
||||
# About this Book
|
||||
|
||||
This book describes the Solana open source project, a blockchain built from the
|
||||
ground up for scale. The book covers why Solana is useful, how to use it, how it
|
||||
works, and why it will continue to work long after the company Solana closes
|
||||
its doors. The goal of the Solana architecture is to demonstrate there exists a
|
||||
set of software algorithms that when used in combination to implement a
|
||||
blockchain, removes software as a performance bottleneck, allowing transaction
|
||||
throughput to scale proportionally with network bandwidth. The architecture
|
||||
goes on to satisfy all three desirable properties of a proper blockchain:
|
||||
it is scalable, secure and decentralized.
|
||||
|
||||
The architecture describes a theoretical upper bound of 710 thousand
|
||||
transactions per second (tps) on a standard gigabit network and 28.4 million
|
||||
tps on 40 gigabit. Furthermore, the architecture supports safe, concurrent
|
||||
execution of programs authored in general purpose programming languages such as
|
||||
C or Rust.
|
||||
|
||||
# Disclaimer
|
||||
|
||||
All claims, content, designs, algorithms, estimates, roadmaps, specifications,
|
||||
and performance measurements described in this project are done with the
|
||||
author's best effort. It is up to the reader to check and validate their
|
||||
accuracy and truthfulness. Furthermore, nothing in this project constitutes a
|
||||
solicitation for investment.
|
||||
|
||||
# History of the Solana Codebase
|
||||
|
||||
In November of 2017, Anatoly Yakovenko published a whitepaper describing Proof
|
||||
of History, a technique for keeping time between computers that do not trust
|
||||
one another. From Anatoly's previous experience designing distributed systems
|
||||
at Qualcomm, Mesosphere and Dropbox, he knew that a reliable clock makes
|
||||
network synchronization very simple. When synchronization is simple the
|
||||
resulting network can be blazing fast, bound only by network bandwidth.
|
||||
|
||||
Anatoly watched as blockchain systems without clocks, such as Bitcoin and
|
||||
Ethereum, struggled to scale beyond 15 transactions per second worldwide when
|
||||
centralized payment systems such as Visa required peaks of 65,000 tps. Without a
|
||||
clock, it was clear they'd never graduate to being the global payment system or
|
||||
global supercomputer most had dreamed them to be. When Anatoly solved the problem of
|
||||
getting computers that don’t trust each other to agree on time, he knew he had
|
||||
the key to bring 40 years of distributed systems research to the world of
|
||||
blockchain. The resulting cluster wouldn't be just 10 times faster, or a 100
|
||||
times, or a 1,000 times, but 10,000 times faster, right out of the gate!
|
||||
|
||||
Anatoly's implementation began in a private codebase and was implemented in the
|
||||
C programming language. Greg Fitzgerald, who had previously worked with Anatoly
|
||||
at semiconductor giant Qualcomm Incorporated, encouraged him to reimplement the
|
||||
project in the Rust programming language. Greg had worked on the LLVM compiler
|
||||
infrastructure, which underlies both the Clang C/C++ compiler as well as the
|
||||
Rust compiler. Greg claimed that the language's safety guarantees would improve
|
||||
software productivity and that its lack of a garbage collector would allow
|
||||
programs to perform as well as those written in C. Anatoly gave it a shot and
|
||||
just two weeks later, had migrated his entire codebase to Rust. Sold. With
|
||||
plans to weave all the world's transactions together on a single, scalable
|
||||
blockchain, Anatoly called the project Loom.
|
||||
|
||||
On February 13th of 2018, Greg began prototyping the first open source
|
||||
implementation of Anatoly's whitepaper. The project was published to GitHub
|
||||
under the name Silk in the loomprotocol organization. On February 28th, Greg
|
||||
made his first release, demonstrating 10 thousand signed transactions could be
|
||||
verified and processed in just over half a second. Shortly after, another
|
||||
former Qualcomm cohort, Stephen Akridge, demonstrated throughput could be
|
||||
massively improved by offloading signature verification to graphics processors.
|
||||
Anatoly recruited Greg, Stephen and three others to co-found a company, then
|
||||
called Loom.
|
||||
|
||||
Around the same time, Ethereum-based project Loom Network sprung up and many
|
||||
people were confused about whether they were the same project. The Loom team decided it
|
||||
would rebrand. They chose the name Solana, a nod to a small beach town North of
|
||||
San Diego called Solana Beach, where Anatoly, Greg and Stephen lived and surfed
|
||||
for three years when they worked for Qualcomm. On March 28th, the team created
|
||||
the Solana Labs GitHub organization and renamed Greg's prototype Silk to
|
||||
Solana.
|
||||
|
||||
In June of 2018, the team scaled up the technology to run on cloud-based
|
||||
networks and on July 19th, published a 50-node, permissioned, public testnet
|
||||
consistently supporting bursts of 250,000 transactions per second. In a later release in
|
||||
December, called v0.10 Pillbox, the team published a permissioned testnet
|
||||
running 150 nodes on a gigabit network and demonstrated soak tests processing
|
||||
an *average* of 200 thousand transactions per second with bursts over 500
|
||||
thousand. The project was also extended to support on-chain programs written in
|
||||
the C programming language and run concurrently in a safe execution environment
|
||||
called BPF.
|
||||
|
||||
# What is a Solana Cluster?
|
||||
|
||||
A cluster is a set of computers that work together and can be viewed from the
|
||||
outside as a single system. A Solana cluster is a set of independently owned
|
||||
computers working together (and sometimes against each other) to verify the
|
||||
output of untrusted, user-submitted programs. A Solana cluster can be utilized
|
||||
any time a user wants to preserve an immutable record of events in time or
|
||||
programmatic interpretations of those events. One use is to track which of the
|
||||
computers did meaningful work to keep the cluster running. Another use might be
|
||||
to track the possession of real-world assets. In each case, the cluster
|
||||
produces a record of events called the ledger. It will be preserved for the
|
||||
lifetime of the cluster. As long as someone somewhere in the world maintains a
|
||||
copy of the ledger, the output of its programs (which may contain a record of
|
||||
who possesses what) will forever be reproducible, independent of the
|
||||
organization that launched it.
|
||||
|
||||
# What are Sols?
|
||||
|
||||
A sol is the name of Solana's native token, which can be passed to nodes in a
|
||||
Solana cluster in exchange for running an on-chain program or validating its
|
||||
output. The system may perform micropayments of fractional sols and a sol
|
||||
may be split as many as 34 times. The fractional sol is called a *lamport*. It
|
||||
is named in honor of Solana's biggest technical influence, [Leslie
|
||||
Lamport](https://en.wikipedia.org/wiki/Leslie_Lamport). A lamport has a value
|
||||
of approximately 0.0000000000582 sol (2^-34).
|
@ -1,728 +0,0 @@
|
||||
JSON RPC API
|
||||
===
|
||||
|
||||
Solana nodes accept HTTP requests using the [JSON-RPC 2.0](https://www.jsonrpc.org/specification) specification.
|
||||
|
||||
To interact with a Solana node inside a JavaScript application, use the [solana-web3.js](https://github.com/solana-labs/solana-web3.js) library, which gives a convenient interface for the RPC methods.
|
||||
|
||||
RPC HTTP Endpoint
|
||||
---
|
||||
|
||||
**Default port:** 8899
|
||||
eg. http://localhost:8899, http://192.168.1.88:8899
|
||||
|
||||
RPC PubSub WebSocket Endpoint
|
||||
---
|
||||
|
||||
**Default port:** 8900
|
||||
eg. ws://localhost:8900, http://192.168.1.88:8900
|
||||
|
||||
|
||||
Methods
|
||||
---
|
||||
|
||||
* [confirmTransaction](#confirmtransaction)
|
||||
* [getAccountInfo](#getaccountinfo)
|
||||
* [getBalance](#getbalance)
|
||||
* [getClusterNodes](#getclusternodes)
|
||||
* [getEpochInfo](#getepochinfo)
|
||||
* [getGenesisBlockhash](#getgenesisblockhash)
|
||||
* [getLeaderSchedule](#getleaderschedule)
|
||||
* [getProgramAccounts](#getprogramaccounts)
|
||||
* [getRecentBlockhash](#getrecentblockhash)
|
||||
* [getSignatureStatus](#getsignaturestatus)
|
||||
* [getSlot](#getslot)
|
||||
* [getSlotLeader](#getslotleader)
|
||||
* [getSlotsPerSegment](#getslotspersegment)
|
||||
* [getStorageTurn](#getstorageturn)
|
||||
* [getStorageTurnRate](#getstorageturnrate)
|
||||
* [getNumBlocksSinceSignatureConfirmation](#getnumblockssincesignatureconfirmation)
|
||||
* [getTransactionCount](#gettransactioncount)
|
||||
* [getTotalSupply](#gettotalsupply)
|
||||
* [getVersion](#getversion)
|
||||
* [getVoteAccounts](#getvoteaccounts)
|
||||
* [requestAirdrop](#requestairdrop)
|
||||
* [sendTransaction](#sendtransaction)
|
||||
* [startSubscriptionChannel](#startsubscriptionchannel)
|
||||
|
||||
* [Subscription Websocket](#subscription-websocket)
|
||||
* [accountSubscribe](#accountsubscribe)
|
||||
* [accountUnsubscribe](#accountunsubscribe)
|
||||
* [programSubscribe](#programsubscribe)
|
||||
* [programUnsubscribe](#programunsubscribe)
|
||||
* [signatureSubscribe](#signaturesubscribe)
|
||||
* [signatureUnsubscribe](#signatureunsubscribe)
|
||||
|
||||
Request Formatting
|
||||
---
|
||||
|
||||
To make a JSON-RPC request, send an HTTP POST request with a `Content-Type: application/json` header. The JSON request data should contain 4 fields:
|
||||
|
||||
* `jsonrpc`, set to `"2.0"`
|
||||
* `id`, a unique client-generated identifying integer
|
||||
* `method`, a string containing the method to be invoked
|
||||
* `params`, a JSON array of ordered parameter values
|
||||
|
||||
Example using curl:
|
||||
```bash
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' 192.168.1.88:8899
|
||||
```
|
||||
|
||||
The response output will be a JSON object with the following fields:
|
||||
|
||||
* `jsonrpc`, matching the request specification
|
||||
* `id`, matching the request identifier
|
||||
* `result`, requested data or success confirmation
|
||||
|
||||
Requests can be sent in batches by sending an array of JSON-RPC request objects as the data for a single POST.
|
||||
|
||||
Definitions
|
||||
---
|
||||
|
||||
* Hash: A SHA-256 hash of a chunk of data.
|
||||
* Pubkey: The public key of a Ed25519 key-pair.
|
||||
* Signature: An Ed25519 signature of a chunk of data.
|
||||
* Transaction: A Solana instruction signed by a client key-pair.
|
||||
|
||||
JSON RPC API Reference
|
||||
---
|
||||
|
||||
### confirmTransaction
|
||||
Returns a transaction receipt
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Signature of Transaction to confirm, as base-58 encoded string
|
||||
|
||||
##### Results:
|
||||
* `boolean` - Transaction status, true if Transaction is confirmed
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"confirmTransaction", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":true,"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getAccountInfo
|
||||
Returns all information associated with the account of provided Pubkey
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Pubkey of account to query, as base-58 encoded string
|
||||
|
||||
##### Results:
|
||||
The result field will be a JSON object with the following sub fields:
|
||||
|
||||
* `lamports`, number of lamports assigned to this account, as a signed 64-bit integer
|
||||
* `owner`, array of 32 bytes representing the program this account has been assigned to
|
||||
* `data`, array of bytes representing any data associated with the account
|
||||
* `executable`, boolean indicating if the account contains a program (and is strictly read-only)
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"id":1}
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
### getBalance
|
||||
Returns the balance of the account of provided Pubkey
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Pubkey of account to query, as base-58 encoded string
|
||||
|
||||
##### Results:
|
||||
* `integer` - quantity, as a signed 64-bit integer
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":0,"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getClusterNodes
|
||||
Returns information about all the nodes participating in the cluster
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
The result field will be an array of JSON objects, each with the following sub fields:
|
||||
* `pubkey` - Node public key, as base-58 encoded string
|
||||
* `gossip` - Gossip network address for the node
|
||||
* `tpu` - TPU network address for the node
|
||||
* `rpc` - JSON RPC network address for the node, or `null` if the JSON RPC service is not enabled
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getClusterNodes"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":[{"gossip":"10.239.6.48:8001","pubkey":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"}],"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getEpochInfo
|
||||
Returns information about the current epoch
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
The result field will be an object with the following fields:
|
||||
* `epoch`, the current epoch
|
||||
* `slotIndex`, the current slot relative to the start of the current epoch
|
||||
* `slotsInEpoch`, the number of slots in this epoch
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"epoch":3,"slotIndex":126,"slotsInEpoch":256},"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
### getGenesisBlockhash
|
||||
Returns the genesis block hash
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `string` - a Hash as base-58 encoded string
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getGenesisBlockhash"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getLeaderSchedule
|
||||
Returns the leader schedule for the current epoch
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
The result field will be an array of leader public keys (as base-58 encoded
|
||||
strings) for each slot in the current epoch
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLeaderSchedule"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":[...],"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getProgramAccounts
|
||||
Returns all accounts owned by the provided program Pubkey
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Pubkey of program, as base-58 encoded string
|
||||
|
||||
##### Results:
|
||||
The result field will be an array of arrays. Each sub array will contain:
|
||||
* `string` - the account Pubkey as base-58 encoded string
|
||||
and a JSON object, with the following sub fields:
|
||||
|
||||
* `lamports`, number of lamports assigned to this account, as a signed 64-bit integer
|
||||
* `owner`, array of 32 bytes representing the program this account has been assigned to
|
||||
* `data`, array of bytes representing any data associated with the account
|
||||
* `executable`, boolean indicating if the account contains a program (and is strictly read-only)
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getProgramAccounts", "params":["8nQwAgzN2yyUzrukXsCa3JELBYqDQrqJ3UyHiWazWxHR"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":[["BqGKYtAKu69ZdWEBtZHh4xgJY1BYa2YBiBReQE3pe383", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":1,"data":[]], ["4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", {"executable":false,"owner":[50,28,250,90,221,24,94,136,147,165,253,136,1,62,196,215,225,34,222,212,99,84,202,223,245,13,149,99,149,231,91,96],"lamports":10,"data":[]]]},"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getRecentBlockhash
|
||||
Returns a recent block hash from the ledger, and a fee schedule that can be used
|
||||
to compute the cost of submitting a transaction using it.
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
An array consisting of
|
||||
* `string` - a Hash as base-58 encoded string
|
||||
* `FeeCalculator object` - the fee schedule for this block hash
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC",{"lamportsPerSignature": 0}],"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getSignatureStatus
|
||||
Returns the status of a given signature. This method is similar to
|
||||
[confirmTransaction](#confirmtransaction) but provides more resolution for error
|
||||
events.
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Signature of Transaction to confirm, as base-58 encoded string
|
||||
|
||||
##### Results:
|
||||
* `null` - Unknown transaction
|
||||
* `object` - Transaction status:
|
||||
* `"Ok": null` - Transaction was successful
|
||||
* `"Err": <ERR>` - Transaction failed with TransactionError <ERR> [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"SignatureNotFound","id":1}
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
### getSlot
|
||||
Returns the current slot the node is processing
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `u64` - Current slot
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlot"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"1234","id":1}
|
||||
```
|
||||
-----
|
||||
|
||||
### getSlotLeader
|
||||
Returns the current slot leader
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `string` - Node Id as base-58 encoded string
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotLeader"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"ENvAW7JScgYq6o4zKZwewtkzzJgDzuJAFxYasvmEQdpS","id":1}
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### getSlotsPerSegment
|
||||
Returns the current storage segment size in terms of slots
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `u64` - Number of slots in a storage segment
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlotsPerSegment"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"1024","id":1}
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### getStorageTurn
|
||||
Returns the current storage turn's blockhash and slot
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
An array consisting of
|
||||
* `string` - a Hash as base-58 encoded string indicating the blockhash of the turn slot
|
||||
* `u64` - the current storage turn slot
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurn"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC", "2048"],"id":1}
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### getStorageTurnRate
|
||||
Returns the current storage turn rate in terms of slots per turn
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `u64` - Number of slots in storage turn
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getStorageTurnRate"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"1024","id":1}
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### getNumBlocksSinceSignatureConfirmation
|
||||
Returns the current number of blocks since signature has been confirmed.
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Signature of Transaction to confirm, as base-58 encoded string
|
||||
|
||||
##### Results:
|
||||
* `integer` - count, as unsigned 64-bit integer
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getNumBlocksSinceSignatureConfirmation", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":8,"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getTransactionCount
|
||||
Returns the current Transaction count from the ledger
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `integer` - count, as unsigned 64-bit integer
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":268,"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getTotalSupply
|
||||
Returns the current total supply in Lamports
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
* `integer` - Total supply, as unsigned 64-bit integer
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getTotalSupply"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":10126,"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getVersion
|
||||
Returns the current solana versions running on the node
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
The result field will be a JSON object with the following sub fields:
|
||||
* `solana-core`, software version of solana-core
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"solana-core": "0.17.2"},"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getVoteAccounts
|
||||
Returns the account info and associated stake for all the voting accounts in the current bank.
|
||||
|
||||
##### Parameters:
|
||||
None
|
||||
|
||||
##### Results:
|
||||
The result field will be a JSON object of `current` and `delinquent` accounts,
|
||||
each containing an array of JSON objects with the following sub fields:
|
||||
* `votePubkey` - Vote account public key, as base-58 encoded string
|
||||
* `nodePubkey` - Node public key, as base-58 encoded string
|
||||
* `activatedStake` - the stake, in lamports, delegated to this vote account and active in this epoch
|
||||
* `epochVoteAccount` - bool, whether the vote account is staked for this epoch
|
||||
* `commission`, an 8-bit integer used as a fraction (commission/MAX_U8) for rewards payout
|
||||
* `lastVote` - Most recent slot voted on by this vote account
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVoteAccounts"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"current":[{"commission":0,"epochVoteAccount":true,"nodePubkey":"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD","lastVote":147,"activatedStake":42,"votePubkey":"3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"}],"delinquent":[{"commission":127,"epochVoteAccount":false,"nodePubkey":"6ZPxeQaDo4bkZLRsdNrCzchNQr5LN9QMc9sipXv9Kw8f","lastVote":0,"activatedStake":0,"votePubkey":"CmgCk4aMS7KW1SHX3s9K5tBJ6Yng2LBaC8MFov4wx9sm"}]},"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### requestAirdrop
|
||||
Requests an airdrop of lamports to a Pubkey
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Pubkey of account to receive lamports, as base-58 encoded string
|
||||
* `integer` - lamports, as a signed 64-bit integer
|
||||
|
||||
##### Results:
|
||||
* `string` - Transaction Signature of airdrop, as base-58 encoded string
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"requestAirdrop", "params":["83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcri", 50]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW","id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### sendTransaction
|
||||
Creates new transaction
|
||||
|
||||
##### Parameters:
|
||||
* `array` - array of octets containing a fully-signed Transaction
|
||||
|
||||
##### Results:
|
||||
* `string` - Transaction Signature, as base-58 encoded string
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"sendTransaction", "params":[[61, 98, 55, 49, 15, 187, 41, 215, 176, 49, 234, 229, 228, 77, 129, 221, 239, 88, 145, 227, 81, 158, 223, 123, 14, 229, 235, 247, 191, 115, 199, 71, 121, 17, 32, 67, 63, 209, 239, 160, 161, 2, 94, 105, 48, 159, 235, 235, 93, 98, 172, 97, 63, 197, 160, 164, 192, 20, 92, 111, 57, 145, 251, 6, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 13, 39, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 40, 240, 124, 194, 149, 155, 16, 138, 31, 113, 119, 101, 212, 128, 103, 78, 191, 80, 182, 234, 216, 21, 121, 243, 35, 100, 122, 68, 47, 57, 11, 12, 106, 49, 74, 226, 201, 16, 161, 192, 28, 84, 124, 97, 190, 201, 171, 186, 6, 18, 70, 142, 89, 185, 176, 154, 115, 61, 26, 163, 77, 1, 88, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b","id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Subscription Websocket
|
||||
After connect to the RPC PubSub websocket at `ws://<ADDRESS>/`:
|
||||
- Submit subscription requests to the websocket using the methods below
|
||||
- Multiple subscriptions may be active at once
|
||||
- All subscriptions take an optional `confirmations` parameter, which defines
|
||||
how many confirmed blocks the node should wait before sending a notification.
|
||||
The greater the number, the more likely the notification is to represent
|
||||
consensus across the cluster, and the less likely it is to be affected by
|
||||
forking or rollbacks. If unspecified, the default value is 0; the node will
|
||||
send a notification as soon as it witnesses the event. The maximum
|
||||
`confirmations` wait length is the cluster's `MAX_LOCKOUT_HISTORY`, which
|
||||
represents the economic finality of the chain.
|
||||
|
||||
---
|
||||
|
||||
### accountSubscribe
|
||||
Subscribe to an account to receive notifications when the lamports or data
|
||||
for a given account public key changes
|
||||
|
||||
##### Parameters:
|
||||
* `string` - account Pubkey, as base-58 encoded string
|
||||
* `integer` - optional, number of confirmed blocks to wait before notification.
|
||||
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
|
||||
|
||||
##### Results:
|
||||
* `integer` - Subscription id (needed to unsubscribe)
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", 15]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": 0,"id": 1}
|
||||
```
|
||||
|
||||
##### Notification Format:
|
||||
```bash
|
||||
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"owner":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"lamports":1,"data":[3,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,53,48,45,48,49,45,48,49,84,48,48,58,48,48,58,48,48,90,252,10,7,28,246,140,88,177,98,82,10,227,89,81,18,30,194,101,199,16,11,73,133,20,246,62,114,39,20,113,189,32,50,0,0,0,0,0,0,0,247,15,36,102,167,83,225,42,133,127,82,34,36,224,207,130,109,230,224,188,163,33,213,13,5,117,211,251,65,159,197,51,0,0,0,0,0,0]},"subscription":0}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### accountUnsubscribe
|
||||
Unsubscribe from account change notifications
|
||||
|
||||
##### Parameters:
|
||||
* `integer` - id of account Subscription to cancel
|
||||
|
||||
##### Results:
|
||||
* `bool` - unsubscribe success message
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"accountUnsubscribe", "params":[0]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": true,"id": 1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### programSubscribe
|
||||
Subscribe to a program to receive notifications when the lamports or data
|
||||
for a given account owned by the program changes
|
||||
|
||||
##### Parameters:
|
||||
* `string` - program_id Pubkey, as base-58 encoded string
|
||||
* `integer` - optional, number of confirmed blocks to wait before notification.
|
||||
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
|
||||
|
||||
##### Results:
|
||||
* `integer` - Subscription id (needed to unsubscribe)
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV"]}
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV", 15]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": 0,"id": 1}
|
||||
```
|
||||
|
||||
##### Notification Format:
|
||||
* `string` - account Pubkey, as base-58 encoded string
|
||||
* `object` - account info JSON object (see [getAccountInfo](#getaccountinfo) for field details)
|
||||
```bash
|
||||
{"jsonrpc":"2.0","method":"programNotification","params":{{"result":["8Rshv2oMkPu5E4opXTRyuyBeZBqQ4S477VG26wUTFxUM",{"executable":false,"lamports":1,"owner":[129,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"data":[1,1,1,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,50,48,49,56,45,49,50,45,50,52,84,50,51,58,53,57,58,48,48,90,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,55,89,0,0,0,0,50,0,0,0,0,0,0,0,235,233,39,152,15,44,117,176,41,89,100,86,45,61,2,44,251,46,212,37,35,118,163,189,247,84,27,235,178,62,45,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}],"subscription":0}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### programUnsubscribe
|
||||
Unsubscribe from program-owned account change notifications
|
||||
|
||||
##### Parameters:
|
||||
* `integer` - id of account Subscription to cancel
|
||||
|
||||
##### Results:
|
||||
* `bool` - unsubscribe success message
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"programUnsubscribe", "params":[0]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": true,"id": 1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### signatureSubscribe
|
||||
Subscribe to a transaction signature to receive notification when the transaction is confirmed
|
||||
On `signatureNotification`, the subscription is automatically cancelled
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Transaction Signature, as base-58 encoded string
|
||||
* `integer` - optional, number of confirmed blocks to wait before notification.
|
||||
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
|
||||
|
||||
##### Results:
|
||||
* `integer` - subscription id (needed to unsubscribe)
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
|
||||
|
||||
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b", 15]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": 0,"id": 1}
|
||||
```
|
||||
|
||||
##### Notification Format:
|
||||
```bash
|
||||
{"jsonrpc": "2.0","method": "signatureNotification", "params": {"result": "Confirmed","subscription":0}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### signatureUnsubscribe
|
||||
Unsubscribe from signature confirmation notification
|
||||
|
||||
##### Parameters:
|
||||
* `integer` - subscription id to cancel
|
||||
|
||||
##### Results:
|
||||
* `bool` - unsubscribe success message
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"signatureUnsubscribe", "params":[0]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": true,"id": 1}
|
||||
```
|
@ -1,101 +0,0 @@
|
||||
# Leader to Leader Transition
|
||||
|
||||
This design describes how leaders transition production of the PoH ledger
|
||||
between each other as each leader generates its own slot.
|
||||
|
||||
## Challenges
|
||||
|
||||
Current leader and the next leader are both racing to generate the final tick
|
||||
for the current slot. The next leader may arrive at that slot while still
|
||||
processing the current leader's entries.
|
||||
|
||||
The ideal scenario would be that the next leader generated its own slot right
|
||||
after it was able to vote for the current leader. It is very likely that the
|
||||
next leader will arrive at their PoH slot height before the current leader
|
||||
finishes broadcasting the entire block.
|
||||
|
||||
The next leader has to make the decision of attaching its own block to the last
|
||||
completed block, or wait to finalize the pending block. It is possible that the
|
||||
next leader will produce a block that proposes that the current leader failed,
|
||||
even though the rest of the network observes that block succeeding.
|
||||
|
||||
The current leader has incentives to start its slot as early as possible to
|
||||
capture economic rewards. Those incentives need to be balanced by the leader's
|
||||
need to attach its block to a block that has the most commitment from the rest
|
||||
of the network.
|
||||
|
||||
## Leader timeout
|
||||
|
||||
While a leader is actively receiving entries for the previous slot, the leader
|
||||
can delay broadcasting the start of its block in real time. The delay is
|
||||
locally configurable by each leader, and can be dynamically based on the
|
||||
previous leader's behavior. If the previous leader's block is confirmed by the
|
||||
leader's TVU before the timeout, the PoH is reset to the start of the slot and
|
||||
this leader produces its block immediately.
|
||||
|
||||
The downsides:
|
||||
|
||||
* Leader delays its own slot, potentially allowing the next leader more time to
|
||||
catch up.
|
||||
|
||||
The upsides compared to guards:
|
||||
|
||||
* All the space in a block is used for entries.
|
||||
|
||||
* The timeout is not fixed.
|
||||
|
||||
* The timeout is local to the leader, and therefore can be clever. The leader's
|
||||
heuristic can take into account turbine performance.
|
||||
|
||||
* This design doesn't require a ledger hard fork to update.
|
||||
|
||||
* The previous leader can redundantly transmit the last entry in the block to
|
||||
the next leader, and the next leader can speculatively decide to trust it to
|
||||
generate its block without verification of the previous block.
|
||||
|
||||
* The leader can speculatively generate the last tick from the last received
|
||||
entry.
|
||||
|
||||
* The leader can speculatively process transactions and guess which ones are not
|
||||
going to be encoded by the previous leader. This is also a censorship attack
|
||||
vector. The current leader may withhold transactions that it receives from the
|
||||
clients so it can encode them into its own slot. Once processed, entries can be
|
||||
replayed into PoH quickly.
|
||||
|
||||
## Alternative design options
|
||||
|
||||
### Guard tick at the end of the slot
|
||||
|
||||
A leader does not produce entries in its block after the *penultimate tick*,
|
||||
which is the last tick before the first tick of the next slot. The network
|
||||
votes on the *last tick*, so the time difference between the *penultimate tick*
|
||||
and the *last tick* is the forced delay for the entire network, as well as the
|
||||
next leader before a new slot can be generated. The network can produce the
|
||||
*last tick* from the *penultimate tick*.
|
||||
|
||||
If the next leader receives the *penultimate tick* before it produces its own
|
||||
*first tick*, it will reset its PoH and produce the *first tick* from the
|
||||
previous leader's *penultimate tick*. The rest of the network will also reset
|
||||
its PoH to produce the *last tick* as the id to vote on.
|
||||
|
||||
The downsides:
|
||||
|
||||
* Every vote, and therefore confirmation, is delayed by a fixed timeout. 1 tick,
|
||||
or around 100ms.
|
||||
|
||||
* Average case confirmation time for a transaction would be at least 50ms worse.
|
||||
|
||||
* It is part of the ledger definition, so to change this behavior would require
|
||||
a hard fork.
|
||||
|
||||
* Not all the available space is used for entries.
|
||||
|
||||
The upsides compared to leader timeout:
|
||||
|
||||
* The next leader has received all the previous entries, so it can start
|
||||
processing transactions without recording them into PoH.
|
||||
|
||||
* The previous leader can redundantly transmit the last entry containing the
|
||||
*penultimate tick* to the next leader. The next leader can speculatively
|
||||
generate the *last tick* as soon as it receives the *penultimate tick*, even
|
||||
before verifying it.
|
@ -1,167 +0,0 @@
|
||||
# Leader Rotation
|
||||
|
||||
At any given moment, a cluster expects only one fullnode to produce ledger
|
||||
entries. By having only one leader at a time, all validators are able to replay
|
||||
identical copies of the ledger. The drawback of only one leader at a time,
|
||||
however, is that a malicious leader is capable of censoring votes and
|
||||
transactions. Since censoring cannot be distinguished from the network dropping
|
||||
packets, the cluster cannot simply elect a single node to hold the leader role
|
||||
indefinitely. Instead, the cluster minimizes the influence of a malicious
|
||||
leader by rotating which node takes the lead.
|
||||
|
||||
Each validator selects the expected leader using the same algorithm, described
|
||||
below. When the validator receives a new signed ledger entry, it can be certain
|
||||
that entry was produced by the expected leader. The order of slots which each
|
||||
leader is assigned a slot is called a *leader schedule*.
|
||||
|
||||
## Leader Schedule Rotation
|
||||
|
||||
A validator rejects blocks that are not signed by the *slot leader*. The list
|
||||
of identities of all slot leaders is called a *leader schedule*. The leader
|
||||
schedule is recomputed locally and periodically. It assigns slot leaders for a
|
||||
duration of time called an _epoch_. The schedule must be computed far in advance
|
||||
of the slots it assigns, such that the ledger state it uses to compute the
|
||||
schedule is finalized. That duration is called the *leader schedule offset*.
|
||||
Solana sets the offset to the duration of slots until the next epoch. That is,
|
||||
the leader schedule for an epoch is calculated from the ledger state at the
|
||||
start of the previous epoch. The offset of one epoch is fairly arbitrary and
|
||||
assumed to be sufficiently long such that all validators will have finalized
|
||||
their ledger state before the next schedule is generated. A cluster may choose
|
||||
to shorten the offset to reduce the time between stake changes and leader
|
||||
schedule updates.
|
||||
|
||||
While operating without partitions lasting longer than an epoch, the schedule
|
||||
only needs to be generated when the root fork crosses the epoch boundary. Since
|
||||
the schedule is for the next epoch, any new stakes committed to the root fork
|
||||
will not be active until the next epoch. The block used for generating the
|
||||
leader schedule is the first block to cross the epoch boundary.
|
||||
|
||||
Without a partition lasting longer than an epoch, the cluster will work as
|
||||
follows:
|
||||
|
||||
1. A validator continuously updates its own root fork as it votes.
|
||||
|
||||
2. The validator updates its leader schedule each time the slot height crosses
|
||||
an epoch boundary.
|
||||
|
||||
For example:
|
||||
|
||||
The epoch duration is 100 slots. The root fork is updated from fork computed at
|
||||
slot height 99 to a fork computed at slot height 102. Forks with slots at height
|
||||
100,101 were skipped because of failures. The new leader schedule is computed
|
||||
using fork at slot height 102. It is active from slot 200 until it is updated
|
||||
again.
|
||||
|
||||
No inconsistency can exist because every validator that is voting with the
|
||||
cluster has skipped 100 and 101 when its root passes 102. All validators,
|
||||
regardless of voting pattern, would be committing to a root that is either 102,
|
||||
or a descendant of 102.
|
||||
|
||||
### Leader Schedule Rotation with Epoch Sized Partitions.
|
||||
|
||||
The duration of the leader schedule offset has a direct relationship to the
|
||||
likelihood of a cluster having an inconsistent view of the correct leader
|
||||
schedule.
|
||||
|
||||
Consider the following scenario:
|
||||
|
||||
Two partitions that are generating half of the blocks each. Neither is coming
|
||||
to a definitive supermajority fork. Both will cross epoch 100 and 200 without
|
||||
actually committing to a root and therefore a cluster wide commitment to a new
|
||||
leader schedule.
|
||||
|
||||
In this unstable scenario, multiple valid leader schedules exist.
|
||||
|
||||
* A leader schedule is generated for every fork whose direct parent is in the
|
||||
previous epoch.
|
||||
|
||||
* The leader schedule is valid after the start of the next epoch for descendant
|
||||
forks until it is updated.
|
||||
|
||||
Each partition's schedule will diverge after the partition lasts more than an
|
||||
epoch. For this reason, the epoch duration should be selected to be much much
|
||||
larger then slot time and the expected length for a fork to be committed to
|
||||
root.
|
||||
|
||||
After observing the cluster for a sufficient amount of time, the leader schedule
|
||||
offset can be selected based on the median partition duration and its standard
|
||||
deviation. For example, an offset longer then the median partition duration
|
||||
plus six standard deviations would reduce the likelihood of an inconsistent
|
||||
ledger schedule in the cluster to 1 in 1 million.
|
||||
|
||||
## Leader Schedule Generation at Genesis
|
||||
|
||||
The genesis block declares the first leader for the first epoch. This leader
|
||||
ends up scheduled for the first two epochs because the leader schedule is also
|
||||
generated at slot 0 for the next epoch. The length of the first two epochs can
|
||||
be specified in the genesis block as well. The minimum length of the first
|
||||
epochs must be greater than or equal to the maximum rollback depth as defined in
|
||||
[Tower BFT](tower-bft.md).
|
||||
|
||||
## Leader Schedule Generation Algorithm
|
||||
|
||||
Leader schedule is generated using a predefined seed. The process is as follows:
|
||||
|
||||
1. Periodically use the PoH tick height (a monotonically increasing counter) to
|
||||
seed a stable pseudo-random algorithm.
|
||||
2. At that height, sample the bank for all the staked accounts with leader
|
||||
identities that have voted within a cluster-configured number of ticks. The
|
||||
sample is called the *active set*.
|
||||
3. Sort the active set by stake weight.
|
||||
4. Use the random seed to select nodes weighted by stake to create a
|
||||
stake-weighted ordering.
|
||||
5. This ordering becomes valid after a cluster-configured number of ticks.
|
||||
|
||||
## Schedule Attack Vectors
|
||||
|
||||
### Seed
|
||||
|
||||
The seed that is selected is predictable but unbiasable. There is no grinding
|
||||
attack to influence its outcome.
|
||||
|
||||
### Active Set
|
||||
|
||||
A leader can bias the active set by censoring validator votes. Two possible
|
||||
ways exist for leaders to censor the active set:
|
||||
|
||||
* Ignore votes from validators
|
||||
* Refuse to vote for blocks with votes from validators
|
||||
|
||||
To reduce the likelihood of censorship, the active set is calculated at the
|
||||
leader schedule offset boundary over an *active set sampling duration*. The
|
||||
active set sampling duration is long enough such that votes will have been
|
||||
collected by multiple leaders.
|
||||
|
||||
### Staking
|
||||
|
||||
Leaders can censor new staking transactions or refuse to validate blocks with
|
||||
new stakes. This attack is similar to censorship of validator votes.
|
||||
|
||||
### Validator operational key loss
|
||||
|
||||
Leaders and validators are expected to use ephemeral keys for operation, and
|
||||
stake owners authorize the validators to do work with their stake via
|
||||
delegation.
|
||||
|
||||
The cluster should be able to recover from the loss of all the ephemeral keys
|
||||
used by leaders and validators, which could occur through a common software
|
||||
vulnerability shared by all the nodes. Stake owners should be able to vote
|
||||
directly co-sign a validator vote even though the stake is currently delegated
|
||||
to a validator.
|
||||
|
||||
## Appending Entries
|
||||
|
||||
The lifetime of a leader schedule is called an *epoch*. The epoch is split into
|
||||
*slots*, where each slot has a duration of `T` PoH ticks.
|
||||
|
||||
A leader transmits entries during its slot. After `T` ticks, all the
|
||||
validators switch to the next scheduled leader. Validators must ignore entries
|
||||
sent outside a leader's assigned slot.
|
||||
|
||||
All `T` ticks must be observed by the next leader for it to build its own
|
||||
entries on. If entries are not observed (leader is down) or entries are invalid
|
||||
(leader is buggy or malicious), the next leader must produce ticks to fill the
|
||||
previous leader's slot. Note that the next leader should do repair requests in
|
||||
parallel, and postpone sending ticks until it is confident other validators
|
||||
also failed to observe the previous leader's entries. If a leader incorrectly
|
||||
builds on its own ticks, the leader following it must replace all its ticks.
|
@ -1,84 +0,0 @@
|
||||
# Leader-to-Validator Transition
|
||||
|
||||
A fullnode typically operates as a validator. If, however, a staker delegates
|
||||
its stake to a fullnode, it will occasionally be selected as a *slot leader*.
|
||||
As a slot leader, the fullnode is responsible for producing blocks during an
|
||||
assigned *slot*. A slot has a duration of some number of preconfigured *ticks*.
|
||||
The duration of those ticks are estimated with a *PoH Recorder* described later
|
||||
in this document.
|
||||
|
||||
## BankFork
|
||||
|
||||
BankFork tracks changes to the bank state over a specific slot. Once the final
|
||||
tick has been registered the state is frozen. Any attempts to write to are
|
||||
rejected.
|
||||
|
||||
## Validator
|
||||
|
||||
A validator operates on many different concurrent forks of the bank state until
|
||||
it generates a PoH hash with a height within its leader slot.
|
||||
|
||||
## Slot Leader
|
||||
|
||||
A slot leader builds blocks on top of only one fork, the one it last voted on.
|
||||
|
||||
## PoH Recorder
|
||||
|
||||
Slot leaders and validators use a PoH Recorder for both estimating slot height
|
||||
and for recording transactions.
|
||||
|
||||
### PoH Recorder when Validating
|
||||
|
||||
The PoH Recorder acts as a simple VDF when validating. It tells the validator
|
||||
when it needs to switch to the slot leader role. Every time the validator votes
|
||||
on a fork, it should use the fork's latest block id to re-seed the VDF.
|
||||
Re-seeding solves two problems. First, it synchronizes its VDF to the leader's,
|
||||
allowing it to more accurately determine when its leader slot begins. Second,
|
||||
if the previous leader goes down, all wallclock time is accounted for in the
|
||||
next leader's PoH stream. For example, if one block is missing when the leader
|
||||
starts, the block it produces should have a PoH duration of two blocks. The
|
||||
longer duration ensures the following leader isn't attempting to snip all the
|
||||
transactions from the previous leader's slot.
|
||||
|
||||
### PoH Recorder when Leading
|
||||
|
||||
A slot leader use the PoH Recorder to record transactions, locking their
|
||||
positions in time. The PoH hash must be derived from a previous leader's last
|
||||
block. If it isn't, its block will fail PoH verification and be rejected by
|
||||
the cluster.
|
||||
|
||||
The PoH Recorder also serves to inform the slot leader when its slot is over.
|
||||
The leader needs to take care not to modify its bank if recording the
|
||||
transaction would generate a PoH height outside its designated slot. The
|
||||
leader, therefore, should not commit account changes until after it generates
|
||||
the entry's PoH hash. When the PoH height falls outside its slot any
|
||||
transactions in its pipeline may be dropped or forwarded to the next leader.
|
||||
Forwarding is preferred, as it would minimize network congestion, allowing the
|
||||
cluster to advertise higher TPS capacity.
|
||||
|
||||
|
||||
## Validator Loop
|
||||
|
||||
The PoH Recorder manages the transition between modes. Once a ledger is
|
||||
replayed, the validator can run until the recorder indicates it should be
|
||||
the slot leader. As a slot leader, the node can then execute and record
|
||||
transactions.
|
||||
|
||||
The loop is synchronized to PoH and does a synchronous start and stop of the
|
||||
slot leader functionality. After stopping, the validator's TVU should find
|
||||
itself in the same state as if a different leader had sent it the same block.
|
||||
The following is pseudocode for the loop:
|
||||
|
||||
1. Query the LeaderScheduler for the next assigned slot.
|
||||
2. Run the TVU over all the forks.
|
||||
1. TVU will send votes to what it believes is the "best" fork.
|
||||
2. After each vote, restart the PoH Recorder to run until the next assigned
|
||||
slot.
|
||||
3. When time to be a slot leader, start the TPU. Point it to the last fork the
|
||||
TVU voted on.
|
||||
4. Produce entries until the end of the slot.
|
||||
1. For the duration of the slot, the TVU must not vote on other forks.
|
||||
2. After the slot ends, the TPU freezes its BankFork. After freezing,
|
||||
the TVU may resume voting.
|
||||
5. Goto 1.
|
||||
|
@ -1,142 +0,0 @@
|
||||
# Ledger Replication
|
||||
|
||||
Replication behavior yet to be implemented.
|
||||
|
||||
### Storage epoch
|
||||
|
||||
The storage epoch should be the number of slots which results in around 100GB-1TB of
|
||||
ledger to be generated for replicators to store. Replicators will start storing ledger
|
||||
when a given fork has a high probability of not being rolled back.
|
||||
|
||||
### Validator behavior
|
||||
|
||||
3. Every NUM\_KEY\_ROTATION\_TICKS it also validates samples received from
|
||||
replicators. It signs the PoH hash at that point and uses the following
|
||||
algorithm with the signature as the input:
|
||||
- The low 5 bits of the first byte of the signature creates an index into
|
||||
another starting byte of the signature.
|
||||
- The validator then looks at the set of storage proofs where the byte of
|
||||
the proof's sha state vector starting from the low byte matches exactly
|
||||
with the chosen byte(s) of the signature.
|
||||
- If the set of proofs is larger than the validator can handle, then it
|
||||
increases to matching 2 bytes in the signature.
|
||||
- Validator continues to increase the number of matching bytes until a
|
||||
workable set is found.
|
||||
- It then creates a mask of valid proofs and fake proofs and sends it to
|
||||
the leader. This is a storage proof confirmation transaction.
|
||||
5. After a lockout period of NUM\_SECONDS\_STORAGE\_LOCKOUT seconds, the
|
||||
validator then submits a storage proof claim transaction which then causes the
|
||||
distribution of the storage reward if no challenges were seen for the proof to
|
||||
the validators and replicators party to the proofs.
|
||||
|
||||
### Replicator behavior
|
||||
|
||||
9. The replicator then generates another set of offsets which it submits a fake
|
||||
proof with an incorrect sha state. It can be proven to be fake by providing the
|
||||
seed for the hash result.
|
||||
- A fake proof should consist of a replicator hash of a signature of a PoH
|
||||
value. That way when the replicator reveals the fake proof, it can be
|
||||
verified on chain.
|
||||
10. The replicator monitors the ledger, if it sees a fake proof integrated, it
|
||||
creates a challenge transaction and submits it to the current leader. The
|
||||
transacation proves the validator incorrectly validated a fake storage proof.
|
||||
The replicator is rewarded and the validator's staking balance is slashed or
|
||||
frozen.
|
||||
|
||||
### Storage proof contract logic
|
||||
|
||||
Each replicator and validator will have their own storage account. The validator's
|
||||
account would be separate from their gossip id similiar to their vote account.
|
||||
These should be implemented as two programs one which handles the validator as the keysigner
|
||||
and one for the replicator. In that way when the programs reference other accounts, they
|
||||
can check the program id to ensure it is a validator or replicator account they are
|
||||
referencing.
|
||||
|
||||
#### SubmitMiningProof
|
||||
```rust,ignore
|
||||
SubmitMiningProof {
|
||||
slot: u64,
|
||||
sha_state: Hash,
|
||||
signature: Signature,
|
||||
};
|
||||
keys = [replicator_keypair]
|
||||
```
|
||||
Replicators create these after mining their stored ledger data for a certain hash value.
|
||||
The slot is the end slot of the segment of ledger they are storing, the sha\_state
|
||||
the result of the replicator using the hash function to sample their encrypted ledger segment.
|
||||
The signature is the signature that was created when they signed a PoH value for the
|
||||
current storage epoch. The list of proofs from the current storage epoch should be saved
|
||||
in the account state, and then transfered to a list of proofs for the previous epoch when
|
||||
the epoch passes. In a given storage epoch a given replicator should only submit proofs
|
||||
for one segment.
|
||||
|
||||
The program should have a list of slots which are valid storage mining slots.
|
||||
This list should be maintained by keeping track of slots which are rooted slots in which a significant
|
||||
portion of the network has voted on with a high lockout value, maybe 32-votes old. Every SLOTS\_PER\_SEGMENT
|
||||
number of slots would be added to this set. The program should check that the slot is in this set. The set can
|
||||
be maintained by receiving a AdvertiseStorageRecentBlockHash and checking with its bank/Tower BFT state.
|
||||
|
||||
The program should do a signature verify check on the signature, public key from the transaction submitter and the message of
|
||||
the previous storage epoch PoH value.
|
||||
|
||||
#### ProofValidation
|
||||
```rust,ignore
|
||||
ProofValidation {
|
||||
proof_mask: Vec<ProofStatus>,
|
||||
}
|
||||
keys = [validator_keypair, replicator_keypair(s) (unsigned)]
|
||||
```
|
||||
A validator will submit this transaction to indicate that a set of proofs for a given
|
||||
segment are valid/not-valid or skipped where the validator did not look at it. The
|
||||
keypairs for the replicators that it looked at should be referenced in the keys so the program
|
||||
logic can go to those accounts and see that the proofs are generated in the previous epoch. The
|
||||
sampling of the storage proofs should be verified ensuring that the correct proofs are skipped by
|
||||
the validator according to the logic outlined in the validator behavior of sampling.
|
||||
|
||||
The included replicator keys will indicate the the storage samples which are being referenced; the
|
||||
length of the proof\_mask should be verified against the set of storage proofs in the referenced
|
||||
replicator account(s), and should match with the number of proofs submitted in the previous storage
|
||||
epoch in the state of said replicator account.
|
||||
|
||||
#### ClaimStorageReward
|
||||
```rust,ignore
|
||||
ClaimStorageReward {
|
||||
}
|
||||
keys = [validator_keypair or replicator_keypair, validator/replicator_keypairs (unsigned)]
|
||||
```
|
||||
Replicators and validators will use this transaction to get paid tokens from a program state
|
||||
where SubmitStorageProof, ProofValidation and ChallengeProofValidations are in a state where
|
||||
proofs have been submitted and validated and there are no ChallengeProofValidations referencing
|
||||
those proofs. For a validator, it should reference the replicator keypairs to which it has validated
|
||||
proofs in the relevant epoch. And for a replicator it should reference validator keypairs for which it
|
||||
has validated and wants to be rewarded.
|
||||
|
||||
#### ChallengeProofValidation
|
||||
```rust,ignore
|
||||
ChallengeProofValidation {
|
||||
proof_index: u64,
|
||||
hash_seed_value: Vec<u8>,
|
||||
}
|
||||
keys = [replicator_keypair, validator_keypair]
|
||||
```
|
||||
|
||||
This transaction is for catching lazy validators who are not doing the work to validate proofs.
|
||||
A replicator will submit this transaction when it sees a validator has approved a fake SubmitMiningProof
|
||||
transaction. Since the replicator is a light client not looking at the full chain, it will have to ask
|
||||
a validator or some set of validators for this information maybe via RPC call to obtain all ProofValidations for
|
||||
a certain segment in the previous storage epoch. The program will look in the validator account
|
||||
state see that a ProofValidation is submitted in the previous storage epoch and hash the hash\_seed\_value and
|
||||
see that the hash matches the SubmitMiningProof transaction and that the validator marked it as valid. If so,
|
||||
then it will save the challenge to the list of challenges that it has in its state.
|
||||
|
||||
#### AdvertiseStorageRecentBlockhash
|
||||
```rust,ignore
|
||||
AdvertiseStorageRecentBlockhash {
|
||||
hash: Hash,
|
||||
slot: u64,
|
||||
}
|
||||
```
|
||||
|
||||
Validators and replicators will submit this to indicate that a new storage epoch has passed and that the
|
||||
storage proofs which are current proofs should now be for the previous epoch. Other transactions should
|
||||
check to see that the epoch that they are referencing is accurate according to current chain state.
|
@ -1,219 +0,0 @@
|
||||
# Ledger Replication
|
||||
|
||||
At full capacity on a 1gbps network solana will generate 4 petabytes of data
|
||||
per year. To prevent the network from centralizing around validators that have
|
||||
to store the full data set this protocol proposes a way for mining nodes to
|
||||
provide storage capacity for pieces of the data.
|
||||
|
||||
The basic idea to Proof of Replication is encrypting a dataset with a public
|
||||
symmetric key using CBC encryption, then hash the encrypted dataset. The main
|
||||
problem with the naive approach is that a dishonest storage node can stream the
|
||||
encryption and delete the data as it's hashed. The simple solution is to periodically
|
||||
regenerate the hash based on a signed PoH value. This ensures that all the data is present
|
||||
during the generation of the proof and it also requires validators to have the
|
||||
entirety of the encrypted data present for verification of every proof of every identity.
|
||||
So the space required to validate is `number_of_proofs * data_size`
|
||||
|
||||
## Optimization with PoH
|
||||
|
||||
Our improvement on this approach is to randomly sample the encrypted segments
|
||||
faster than it takes to encrypt, and record the hash of those samples into the
|
||||
PoH ledger. Thus the segments stay in the exact same order for every PoRep and
|
||||
verification can stream the data and verify all the proofs in a single batch.
|
||||
This way we can verify multiple proofs concurrently, each one on its own CUDA
|
||||
core. The total space required for verification is `1_ledger_segment +
|
||||
2_cbc_blocks * number_of_identities` with core count equal to
|
||||
`number_of_identities`. We use a 64-byte chacha CBC block size.
|
||||
|
||||
## Network
|
||||
|
||||
Validators for PoRep are the same validators that are verifying transactions.
|
||||
If a replicator can prove that a validator verified a fake PoRep, then the
|
||||
validator will not receive a reward for that storage epoch.
|
||||
|
||||
Replicators are specialized *light clients*. They download a part of the
|
||||
ledger (a.k.a Segment) and store it, and provide PoReps of storing the ledger.
|
||||
For each verified PoRep replicators earn a reward of sol from the mining pool.
|
||||
|
||||
## Constraints
|
||||
|
||||
We have the following constraints:
|
||||
* Verification requires generating the CBC blocks. That requires space of 2
|
||||
blocks per identity, and 1 CUDA core per identity for the same dataset. So as
|
||||
many identities at once should be batched with as many proofs for those
|
||||
identities verified concurrently for the same dataset.
|
||||
* Validators will randomly sample the set of storage proofs to the set that
|
||||
they can handle, and only the creators of those chosen proofs will be
|
||||
rewarded. The validator can run a benchmark whenever its hardware configuration
|
||||
changes to determine what rate it can validate storage proofs.
|
||||
|
||||
## Validation and Replication Protocol
|
||||
|
||||
### Constants
|
||||
|
||||
1. SLOTS\_PER\_SEGMENT: Number of slots in a segment of ledger data. The
|
||||
unit of storage for a replicator.
|
||||
2. NUM\_KEY\_ROTATION\_SEGMENTS: Number of segments after which replicators
|
||||
regenerate their encryption keys and select a new dataset to store.
|
||||
3. NUM\_STORAGE\_PROOFS: Number of storage proofs required for a storage proof
|
||||
claim to be successfully rewarded.
|
||||
4. RATIO\_OF\_FAKE\_PROOFS: Ratio of fake proofs to real proofs that a storage
|
||||
mining proof claim has to contain to be valid for a reward.
|
||||
5. NUM\_STORAGE\_SAMPLES: Number of samples required for a storage mining
|
||||
proof.
|
||||
6. NUM\_CHACHA\_ROUNDS: Number of encryption rounds performed to generate
|
||||
encrypted state.
|
||||
7. NUM\_SLOTS\_PER\_TURN: Number of slots that define a single storage epoch or
|
||||
a "turn" of the PoRep game.
|
||||
|
||||
### Validator behavior
|
||||
|
||||
1. Validators join the network and begin looking for replicator accounts at each
|
||||
storage epoch/turn boundary.
|
||||
2. Every turn, Validators sign the PoH value at the boundary and use that signature
|
||||
to randomly pick proofs to verify from each storage account found in the turn boundary.
|
||||
This signed value is also submitted to the validator's storage account and will be used by
|
||||
replicators at a later stage to cross-verify.
|
||||
3. Every `NUM_SLOTS_PER_TURN` slots the validator advertises the PoH value. This is value
|
||||
is also served to Replicators via RPC interfaces.
|
||||
4. For a given turn N, all validations get locked out until turn N+3 (a gap of 2 turn/epoch).
|
||||
At which point all validations during that turn are available for reward collection.
|
||||
5. Any incorrect validations will be marked during the turn in between.
|
||||
|
||||
|
||||
### Replicator behavior
|
||||
|
||||
1. Since a replicator is somewhat of a light client and not downloading all the
|
||||
ledger data, they have to rely on other validators and replicators for information.
|
||||
Any given validator may or may not be malicious and give incorrect information, although
|
||||
there are not any obvious attack vectors that this could accomplish besides having the
|
||||
replicator do extra wasted work. For many of the operations there are a number of options
|
||||
depending on how paranoid a replicator is:
|
||||
- (a) replicator can ask a validator
|
||||
- (b) replicator can ask multiple validators
|
||||
- (c) replicator can ask other replicators
|
||||
- (d) replicator can subscribe to the full transaction stream and generate
|
||||
the information itself (assuming the slot is recent enough)
|
||||
- (e) replicator can subscribe to an abbreviated transaction stream to
|
||||
generate the information itself (assuming the slot is recent enough)
|
||||
2. A replicator obtains the PoH hash corresponding to the last turn with its slot.
|
||||
3. The replicator signs the PoH hash with its keypair. That signature is the
|
||||
seed used to pick the segment to replicate and also the encryption key. The
|
||||
replicator mods the signature with the slot to get which segment to
|
||||
replicate.
|
||||
4. The replicator retrives the ledger by asking peer validators and
|
||||
replicators. See 6.5.
|
||||
5. The replicator then encrypts that segment with the key with chacha algorithm
|
||||
in CBC mode with `NUM_CHACHA_ROUNDS` of encryption.
|
||||
6. The replicator initializes a chacha rng with the a signed recent PoH value as
|
||||
the seed.
|
||||
7. The replicator generates `NUM_STORAGE_SAMPLES` samples in the range of the
|
||||
entry size and samples the encrypted segment with sha256 for 32-bytes at each
|
||||
offset value. Sampling the state should be faster than generating the encrypted
|
||||
segment.
|
||||
8. The replicator sends a PoRep proof transaction which contains its sha state
|
||||
at the end of the sampling operation, its seed and the samples it used to the
|
||||
current leader and it is put onto the ledger.
|
||||
9. During a given turn the replicator should submit many proofs for the same segment
|
||||
and based on the `RATIO_OF_FAKE_PROOFS` some of those proofs must be fake.
|
||||
10. As the PoRep game enters the next turn, the replicator must submit a
|
||||
transaction with the mask of which proofs were fake during the last turn. This
|
||||
transaction will define the rewards for both replicators and validators.
|
||||
11. Finally for a turn N, as the PoRep game enters turn N + 3, replicator's proofs for
|
||||
turn N will be counted towards their rewards.
|
||||
|
||||
|
||||
### The PoRep Game
|
||||
|
||||
The Proof of Replication game has 4 primary stages. For each "turn" multiple PoRep
|
||||
games can be in progress but each in a different stage.
|
||||
|
||||
The 4 stages of the PoRep Game are as follows:
|
||||
|
||||
1. Proof submission stage
|
||||
- Replicators: submit as many proofs as possible during this stage
|
||||
- Validators: No-op
|
||||
2. Proof verification stage
|
||||
- Replicators: No-op
|
||||
- Validators: Select replicators and verify their proofs from the previous turn
|
||||
3. Proof challenge stage
|
||||
- Replicators: Submit the proof mask with justifications (for fake proofs submitted 2 turns ago)
|
||||
- Validators: No-op
|
||||
4. Reward collection stage
|
||||
- Replicators: Collect rewards for 3 turns ago
|
||||
- Validators: Collect rewards for 3 turns ago
|
||||
|
||||
|
||||
For each turn of the PoRep game, both Validators and Replicators evaluate each
|
||||
stage. The stages are run as separate transactions on the storage program.
|
||||
|
||||
### Finding who has a given block of ledger
|
||||
|
||||
1. Validators monitor the turns in the PoRep game and look at the rooted bank
|
||||
at turn boundaries for any proofs.
|
||||
2. Validators maintain a map of ledger segments and corresponding replicator public keys.
|
||||
The map is updated when a Validator processes a replicator's proofs for a segment.
|
||||
The validator provides an RPC interface to access the this map. Using this API, clients
|
||||
can map a segment to a replicator's network address (correlating it via cluster_info table).
|
||||
The clients can then send repair requests to the replicator to retrieve segments.
|
||||
3. Validators would need to invalidate this list every N turns.
|
||||
|
||||
## Sybil attacks
|
||||
|
||||
For any random seed, we force everyone to use a signature that is derived from
|
||||
a PoH hash at the turn boundary. Everyone uses the same count, so the same PoH
|
||||
hash is signed by every participant. The signatures are then each cryptographically
|
||||
tied to the keypair, which prevents a leader from grinding on the resulting
|
||||
value for more than 1 identity.
|
||||
|
||||
Since there are many more client identities then encryption identities, we need
|
||||
to split the reward for multiple clients, and prevent Sybil attacks from
|
||||
generating many clients to acquire the same block of data. To remain BFT we
|
||||
want to avoid a single human entity from storing all the replications of a
|
||||
single chunk of the ledger.
|
||||
|
||||
Our solution to this is to force the clients to continue using the same
|
||||
identity. If the first round is used to acquire the same block for many client
|
||||
identities, the second round for the same client identities will force a
|
||||
redistribution of the signatures, and therefore PoRep identities and blocks.
|
||||
Thus to get a reward for replicators need to store the first block for free and
|
||||
the network can reward long lived client identities more than new ones.
|
||||
|
||||
## Validator attacks
|
||||
|
||||
- If a validator approves fake proofs, replicator can easily out them by
|
||||
showing the initial state for the hash.
|
||||
- If a validator marks real proofs as fake, no on-chain computation can be done
|
||||
to distinguish who is correct. Rewards would have to rely on the results from
|
||||
multiple validators to catch bad actors and replicators from being denied rewards.
|
||||
- Validator stealing mining proof results for itself. The proofs are derived
|
||||
from a signature from a replicator, since the validator does not know the
|
||||
private key used to generate the encryption key, it cannot be the generator of
|
||||
the proof.
|
||||
|
||||
## Reward incentives
|
||||
|
||||
Fake proofs are easy to generate but difficult to verify. For this reason,
|
||||
PoRep proof transactions generated by replicators may require a higher fee than
|
||||
a normal transaction to represent the computational cost required by
|
||||
validators.
|
||||
|
||||
Some percentage of fake proofs are also necessary to receive a reward from
|
||||
storage mining.
|
||||
|
||||
## Notes
|
||||
|
||||
* We can reduce the costs of verification of PoRep by using PoH, and actually
|
||||
make it feasible to verify a large number of proofs for a global dataset.
|
||||
* We can eliminate grinding by forcing everyone to sign the same PoH hash and
|
||||
use the signatures as the seed
|
||||
* The game between validators and replicators is over random blocks and random
|
||||
encryption identities and random data samples. The goal of randomization is
|
||||
to prevent colluding groups from having overlap on data or validation.
|
||||
* Replicator clients fish for lazy validators by submitting fake proofs that
|
||||
they can prove are fake.
|
||||
* To defend against Sybil client identities that try to store the same block we
|
||||
force the clients to store for multiple rounds before receiving a reward.
|
||||
* Validators should also get rewarded for validating submitted storage proofs
|
||||
as incentive for storing the ledger. They can only validate proofs if they
|
||||
are storing that slice of the ledger.
|
@ -1,63 +0,0 @@
|
||||
# Managing Forks in the Ledger
|
||||
|
||||
The ledger is permitted to fork at slot boundaries. The resulting data
|
||||
structure forms a tree called a *blocktree*. When the fullnode interprets the
|
||||
blocktree, it must maintain state for each fork in the chain. We call each
|
||||
instance an *active fork*. It is the responsibility of a fullnode to weigh
|
||||
those forks, such that it may eventually select a fork.
|
||||
|
||||
A fullnode selects a fork by submiting a vote to a slot leader on that fork.
|
||||
The vote commits the fullnode for a duration of time called a *lockout period*.
|
||||
The fullnode is not permitted to vote on a different fork until that lockout
|
||||
period expires. Each subsequent vote on the same fork doubles the length of the
|
||||
lockout period. After some cluster-configured number of votes (currently 32),
|
||||
the length of the lockout period reaches what's called *max lockout*. Until the
|
||||
max lockout is reached, the fullnode has the option to wait until the lockout
|
||||
period is over and then vote on another fork. When it votes on another fork, it
|
||||
performs a operation called *rollback*, whereby the state rolls back in time to
|
||||
a shared checkpoint and then jumps forward to the tip of the fork that it just
|
||||
voted on. The maximum distance that a fork may roll back is called the
|
||||
*rollback depth*. Rollback depth is the number of votes required to achieve
|
||||
max lockout. Whenever a fullnode votes, any checkpoints beyond the rollback
|
||||
depth become unreachable. That is, there is no scenario in which the fullnode
|
||||
will need to roll back beyond rollback depth. It therefore may safely *prune*
|
||||
unreachable forks and *squash* all checkpoints beyond rollback depth into the
|
||||
root checkpoint.
|
||||
|
||||
## Active Forks
|
||||
|
||||
An active fork is as a sequence of checkpoints that has a length at least one
|
||||
longer than the rollback depth. The shortest fork will have a length exactly
|
||||
one longer than the rollback depth. For example:
|
||||
|
||||
<img alt="Forks" src=".gitbook/assets/forks.svg" class="center"/>
|
||||
|
||||
The following sequences are *active forks*:
|
||||
|
||||
* {4, 2, 1}
|
||||
* {5, 2, 1}
|
||||
* {6, 3, 1}
|
||||
* {7, 3, 1}
|
||||
|
||||
## Pruning and Squashing
|
||||
|
||||
A fullnode may vote on any checkpoint in the tree. In the diagram above,
|
||||
that's every node except the leaves of the tree. After voting, the fullnode
|
||||
prunes nodes that fork from a distance farther than the rollback depth and then
|
||||
takes the opportunity to minimize its memory usage by squashing any nodes it
|
||||
can into the root.
|
||||
|
||||
Starting from the example above, wth a rollback depth of 2, consider a vote on
|
||||
5 versus a vote on 6. First, a vote on 5:
|
||||
|
||||
<img alt="Forks after pruning" src=".gitbook/assets/forks-pruned.svg" class="center"/>
|
||||
|
||||
The new root is 2, and any active forks that are not descendants from 2 are
|
||||
pruned.
|
||||
|
||||
Alternatively, a vote on 6:
|
||||
|
||||
<img alt="Forks" src=".gitbook/assets/forks-pruned2.svg" class="center"/>
|
||||
|
||||
The tree remains with a root of 1, since the active fork starting at 6 is only
|
||||
2 checkpoints from the root.
|
@ -1,31 +0,0 @@
|
||||
# Performance Metrics
|
||||
|
||||
Solana cluster performance is measured as average number of transactions per second
|
||||
that the network can sustain (TPS). And, how long it takes for a transaction to be
|
||||
confirmed by super majority of the cluster (Confirmation Time).
|
||||
|
||||
Each cluster node maintains various counters that are incremented on certain events.
|
||||
These counters are periodically uploaded to a cloud based database. Solana's metrics
|
||||
dashboard fetches these counters, and computes the performance metrics and displays
|
||||
it on the dashboard.
|
||||
|
||||
## TPS
|
||||
|
||||
Each node's bank runtime maintains a count of transactions that it has processed.
|
||||
The dashboard first calculates the median count of transactions across all metrics
|
||||
enabled nodes in the cluster. The median cluster transaction count is then averaged
|
||||
over a 2 second period and displayed in the TPS time series graph. The dashboard also
|
||||
shows the Mean TPS, Max TPS and Total Transaction Count stats which are all calculated from
|
||||
the median transaction count.
|
||||
|
||||
## Confirmation Time
|
||||
|
||||
Each validator node maintains a list of active ledger forks that are visible to the node.
|
||||
A fork is considered to be frozen when the node has received and processed all entries
|
||||
corresponding to the fork. A fork is considered to be confirmed when it receives cumulative
|
||||
super majority vote, and when one of its children forks is frozen.
|
||||
|
||||
The node assigns a timestamp to every new fork, and computes the time it took to confirm
|
||||
the fork. This time is reflected as validator confirmation time in performance metrics.
|
||||
The performance dashboard displays the average of each validator node's confirmation time
|
||||
as a time series graph.
|
@ -1,153 +0,0 @@
|
||||
# Persistent Account Storage
|
||||
|
||||
The set of Accounts represent the current computed state of all the transactions
|
||||
that have been processed by a fullnode. Each fullnode needs to maintain this
|
||||
entire set. Each block that is proposed by the network represents a change to
|
||||
this set, and since each block is a potential rollback point the changes need to
|
||||
be reversible.
|
||||
|
||||
Persistent storage like NVMEs are 20 to 40 times cheaper than DDR. The problem
|
||||
with persistent storage is that write and read performance is much slower than
|
||||
DDR and care must be taken in how data is read or written to. Both reads and
|
||||
writes can be split between multiple storage drives and accessed in parallel.
|
||||
This design proposes a data structure that allows for concurrent reads and
|
||||
concurrent writes of storage. Writes are optimized by using an AppendVec data
|
||||
structure, which allows a single writer to append while allowing access to many
|
||||
concurrent readers. The accounts index maintains a pointer to a spot where the
|
||||
account was appended to every fork, thus removing the need for explicit
|
||||
checkpointing of state.
|
||||
|
||||
# AppendVec
|
||||
|
||||
AppendVec is a data structure that allows for random reads concurrent with a
|
||||
single append-only writer. Growing or resizing the capacity of the AppendVec
|
||||
requires exclusive access. This is implemented with an atomic `offset`, which
|
||||
is updated at the end of a completed append.
|
||||
|
||||
The underlying memory for an AppendVec is a memory-mapped file. Memory-mapped
|
||||
files allow for fast random access and paging is handled by the OS.
|
||||
|
||||
# Account Index
|
||||
|
||||
The account index is designed to support a single index for all the currently
|
||||
forked Accounts.
|
||||
|
||||
```rust,ignore
|
||||
type AppendVecId = usize;
|
||||
|
||||
type Fork = u64;
|
||||
|
||||
struct AccountMap(Hashmap<Fork, (AppendVecId, u64)>);
|
||||
|
||||
type AccountIndex = HashMap<Pubkey, AccountMap>;
|
||||
|
||||
```
|
||||
|
||||
The index is a map of account Pubkeys to a map of Forks and the location of the
|
||||
Account data in an AppendVec. To get the version of an account for a specific Fork:
|
||||
|
||||
```rust,ignore
|
||||
/// Load the account for the pubkey.
|
||||
/// This function will load the account from the specified fork, falling back to the fork's parents
|
||||
/// * fork - a virtual Accounts instance, keyed by Fork. Accounts keep track of their parents with Forks,
|
||||
/// the persistent store
|
||||
/// * pubkey - The Account's public key.
|
||||
pub fn load_slow(&self, id: Fork, pubkey: &Pubkey) -> Option<&Account>
|
||||
```
|
||||
|
||||
The read is satisfied by pointing to a memory-mapped location in the
|
||||
`AppendVecId` at the stored offset. A reference can be returned without a copy.
|
||||
|
||||
## Root Forks
|
||||
|
||||
[Tower BFT](tower-bft.md) eventually selects a fork as a
|
||||
root fork and the fork is squashed. A squashed/root fork cannot be rolled back.
|
||||
|
||||
When a fork is squashed, all accounts in its parents not already present in the
|
||||
fork are pulled up into the fork by updating the indexes. Accounts with zero
|
||||
balance in the squashed fork are removed from fork by updating the indexes.
|
||||
|
||||
An account can be *garbage-collected* when squashing makes it unreachable.
|
||||
|
||||
Three possible options exist:
|
||||
|
||||
* Maintain a HashSet<u64> of root forks. One is expected to be created every
|
||||
second. The entire tree can be garbage-collected later. Alternatively, if
|
||||
every fork keeps a reference count of accounts, garbage collection could occur
|
||||
any time an index location is updated.
|
||||
|
||||
* Remove any pruned forks from the index. Any remaining forks lower in number
|
||||
than the root are can be considered root.
|
||||
|
||||
* Scan the index, migrate any old roots into the new one. Any remaining forks
|
||||
lower than the new root can be deleted later.
|
||||
|
||||
# Append-only Writes
|
||||
|
||||
All the updates to Accounts occur as append-only updates. For every account
|
||||
update, a new version is stored in the AppendVec.
|
||||
|
||||
It is possible to optimize updates within a single fork by returning a mutable
|
||||
reference to an already stored account in a fork. The Bank already tracks
|
||||
concurrent access of accounts and guarantees that a write to a specific account
|
||||
fork will not be concurrent with a read to an account at that fork. To support
|
||||
this operation, AppendVec should implement this function:
|
||||
|
||||
```rust,ignore
|
||||
fn get_mut(&self, index: u64) -> &mut T;
|
||||
```
|
||||
|
||||
This API allows for concurrent mutable access to a memory region at `index`. It
|
||||
relies on the Bank to guarantee exclusive access to that index.
|
||||
|
||||
# Garbage collection
|
||||
|
||||
As accounts get updated, they move to the end of the AppendVec. Once capacity
|
||||
has run out, a new AppendVec can be created and updates can be stored there.
|
||||
Eventually references to an older AppendVec will disappear because all the
|
||||
accounts have been updated, and the old AppendVec can be deleted.
|
||||
|
||||
To speed up this process, it's possible to move Accounts that have not been
|
||||
recently updated to the front of a new AppendVec. This form of garbage
|
||||
collection can be done without requiring exclusive locks to any of the data
|
||||
structures except for the index update.
|
||||
|
||||
The initial implementation for garbage collection is that once all the accounts in
|
||||
an AppendVec become stale versions, it gets reused. The accounts are not updated
|
||||
or moved around once appended.
|
||||
|
||||
# Index Recovery
|
||||
|
||||
Each bank thread has exclusive access to the accounts during append, since the
|
||||
accounts locks cannot be released until the data is committed. But there is no
|
||||
explicit order of writes between the separate AppendVec files. To create an
|
||||
ordering, the index maintains an atomic write version counter. Each append to
|
||||
the AppendVec records the index write version number for that append in the
|
||||
entry for the Account in the AppendVec.
|
||||
|
||||
To recover the index, all the AppendVec files can be read in any order, and the
|
||||
latest write version for every fork should be stored in the index.
|
||||
|
||||
# Snapshots
|
||||
|
||||
To snapshot, the underlying memory-mapped files in the AppendVec need to be
|
||||
flushed to disk. The index can be written out to disk as well.
|
||||
|
||||
# Performance
|
||||
|
||||
* Append-only writes are fast. SSDs and NVMEs, as well as all the OS level
|
||||
kernel data structures, allow for appends to run as fast as PCI or NVMe bandwidth
|
||||
will allow (2,700 MB/s).
|
||||
|
||||
* Each replay and banking thread writes concurrently to its own AppendVec.
|
||||
|
||||
* Each AppendVec could potentially be hosted on a separate NVMe.
|
||||
|
||||
* Each replay and banking thread has concurrent read access to all the
|
||||
AppendVecs without blocking writes.
|
||||
|
||||
* Index requires an exclusive write lock for writes. Single-thread performance
|
||||
for HashMap updates is on the order of 10m per second.
|
||||
|
||||
* Banking and Replay stages should use 32 threads per NVMe. NVMes have
|
||||
optimal performance with 32 concurrent readers or writers.
|
@ -1,77 +0,0 @@
|
||||
# Programming Model
|
||||
|
||||
A client *app* interacts with a Solana cluster by sending it *transactions*
|
||||
with one or more *instructions*. The Solana *runtime* passes those instructions
|
||||
to user-contributed *programs*. An instruction might, for example, tell a
|
||||
program to transfer *lamports* from one *account* to another or create an interactive
|
||||
contract that governs how lamports are transfered. Instructions are executed
|
||||
atomically. If any instruction is invalid, any changes made within the
|
||||
transaction are discarded.
|
||||
|
||||
## Deploying Programs to a Cluster
|
||||
|
||||
<img alt="SDK tools" src=".gitbook/assets/sdk-tools.svg" class="center"/>
|
||||
|
||||
As shown in the diagram above a client creates a program and compiles it to an
|
||||
ELF shared object containing BPF bytecode and sends it to the Solana cluster.
|
||||
The cluster stores the program locally and makes it available to clients via a
|
||||
*program ID*. The program ID is a *public key* generated by the client and is
|
||||
used to reference the program in subsequent transactions.
|
||||
|
||||
A program may be written in any programming language that can target the
|
||||
Berkley Packet Filter (BPF) safe execution environment. The Solana SDK offers
|
||||
the best support for C programs, which is compiled to BPF using the [LLVM
|
||||
compiler infrastructure](https://llvm.org).
|
||||
|
||||
## Storing State between Transactions
|
||||
|
||||
If the program needs to store state between transactions, it does so using
|
||||
*accounts*. Accounts are similar to files in operating systems such as Linux.
|
||||
Like a file, an account may hold arbitrary data and that data persists beyond
|
||||
the lifetime of a program. Also like a file, an account includes metadata that
|
||||
tells the runtime who is allowed to access the data and how. Unlike a file, the
|
||||
account includes metadata for the lifetime of the file. That lifetime is
|
||||
expressed in "tokens", which is a number of fractional native tokens, called
|
||||
*lamports*. Accounts are held in validator memory and pay "rent" to stay there.
|
||||
Each fullnode periodically scan all accounts and collects rent. Any account
|
||||
that drops to zero lamports is purged.
|
||||
|
||||
If an account is marked "executable", it will only be used by a *loader* to run
|
||||
programs. For example, a BPF-compiled program is marked executable and loaded
|
||||
by the BPF loader. No program is allowed to modify the contents of an
|
||||
executable account.
|
||||
|
||||
An account also includes "owner" metadata. The owner is a program ID. The
|
||||
runtime grants the program write access to the account if its ID matches the
|
||||
owner. If an account is not owned by a program, the program is permitted to
|
||||
read its data and credit the account.
|
||||
|
||||
In the same way that a Linux user uses a path to look up a file, a Solana
|
||||
client uses public keys to look up accounts. To create an account, the client
|
||||
generates a *keypair* and registers its public key using the `CreateAccount`
|
||||
instruction. The account created by `CreateAccount` is called a *system
|
||||
account* and is owned by a built-in program called the System program. The
|
||||
System program allows clients to transfer lamports and assign account
|
||||
ownership.
|
||||
|
||||
The runtime only permits the owner to debit the account or modify its data. The
|
||||
program then defines additional rules for whether the client can modify
|
||||
accounts it owns. In the case of the System program, it allows users to
|
||||
transfer lamports by recognizing transaction signatures. If it sees the client
|
||||
signed the transaction using the keypair's *private key*, it knows the client
|
||||
authorized the token transfer.
|
||||
|
||||
After the runtime executes each of the transaction's instructions, it uses the
|
||||
account metadata to verify that none of the access rules were violated. If a
|
||||
program violates an access rule, the runtime discards all account changes made
|
||||
by all instructions and marks the transaction as failed.
|
||||
|
||||
## Smart Contracts
|
||||
|
||||
Programs don't always require transaction signatures, as the System program
|
||||
does. Instead, the program may manage *smart contracts*. A smart contract is a
|
||||
set of constraints that once satisfied, signal to a program that a token
|
||||
transfer or account update is permitted. For example, one could use the Budget
|
||||
program to create a smart contract that authorizes a token transfer only after
|
||||
some date. Once evidence that the date has past, the contract progresses, and
|
||||
token transfer completes.
|
@ -1,7 +0,0 @@
|
||||
# Proposed Architectural Changes
|
||||
|
||||
The following architectural proposals have been accepted by the Solana team, but
|
||||
are not yet fully implemented. The proposals may be implemented as described,
|
||||
implemented differently as issues in the designs become evident, or not
|
||||
implemented at all. If implemented, the descriptions will be moved from this
|
||||
section to earlier chapters in a future version of this book.
|
@ -1,124 +0,0 @@
|
||||
# Reliable Vote Transmission
|
||||
|
||||
Validator votes are messages that have a critical function for consensus and
|
||||
continuous operation of the network. Therefore it is critical that they are
|
||||
reliably delivered and encoded into the ledger.
|
||||
|
||||
## Challenges
|
||||
|
||||
1. Leader rotation is triggered by PoH, which is clock with high drift. So many
|
||||
nodes are likely to have an incorrect view if the next leader is active in
|
||||
realtime or not.
|
||||
|
||||
2. The next leader may be easily be flooded. Thus a DDOS would not only prevent
|
||||
delivery of regular transactions, but also consensus messages.
|
||||
|
||||
3. UDP is unreliable, and our asynchronous protocol requires any message that is
|
||||
transmitted to be retransmitted until it is observed in the ledger.
|
||||
Retransmittion could potentially cause an unintentional *thundering herd*
|
||||
against the leader with a large number of validators. Worst case flood would be
|
||||
`(num_nodes * num_retransmits)`.
|
||||
|
||||
4. Tracking if the vote has been transmitted or not via the ledger does not
|
||||
guarantee it will appear in a confirmed block. The current observed block may
|
||||
be unrolled. Validators would need to maintain state for each vote and fork.
|
||||
|
||||
|
||||
## Design
|
||||
|
||||
1. Send votes as a push message through gossip. This ensures delivery of the
|
||||
vote to all the next leaders, not just the next future one.
|
||||
|
||||
2. Leaders will read the Crds table for new votes and encode any new received
|
||||
votes into the blocks they propose. This allows for validator votes to be
|
||||
included in rollback forks by all the future leaders.
|
||||
|
||||
3. Validators that receive votes in the ledger will add them to their local crds
|
||||
table, not as a push request, but simply add them to the table. This shortcuts
|
||||
the push message protocol, so the validation messages do not need to be
|
||||
retransmitted twice around the network.
|
||||
|
||||
4. CrdsValue for vote should look like this ``` Votes(Vec<Transaction>) ```
|
||||
|
||||
Each vote transaction should maintain a `wallclock` in its data. The merge
|
||||
strategy for Votes will keep the last N set of votes as configured by the local
|
||||
client. For push/pull the vector is traversed recursively and each Transaction
|
||||
is treated as an individual CrdsValue with its own local wallclock and
|
||||
signature.
|
||||
|
||||
Gossip is designed for efficient propagation of state. Messages that are sent
|
||||
through gossip-push are batched and propagated with a minimum spanning tree to
|
||||
the rest of the network. Any partial failures in the tree are actively repaired
|
||||
with the gossip-pull protocol while minimizing the amount of data transfered
|
||||
between any nodes.
|
||||
|
||||
|
||||
## How this design solves the Challenges
|
||||
|
||||
1. Because there is no easy way for validators to be in sync with leaders on the
|
||||
leader's "active" state, gossip allows for eventual delivery regardless of that
|
||||
state.
|
||||
|
||||
2. Gossip will deliver the messages to all the subsequent leaders, so if the
|
||||
current leader is flooded the next leader would have already received these
|
||||
votes and is able to encode them.
|
||||
|
||||
3. Gossip minimizes the number of requests through the network by maintaining an
|
||||
efficient spanning tree, and using bloom filters to repair state. So retransmit
|
||||
back-off is not necessary and messages are batched.
|
||||
|
||||
4. Leaders that read the crds table for votes will encode all the new valid
|
||||
votes that appear in the table. Even if this leader's block is unrolled, the
|
||||
next leader will try to add the same votes without any additional work done by
|
||||
the validator. Thus ensuring not only eventual delivery, but eventual encoding
|
||||
into the ledger.
|
||||
|
||||
|
||||
## Performance
|
||||
|
||||
1. Worst case propagation time to the next leader is Log(N) hops with a base
|
||||
depending on the fanout. With our current default fanout of 6, it is about 6
|
||||
hops to 20k nodes.
|
||||
|
||||
2. The leader should receive 20k validation votes aggregated by gossip-push into
|
||||
64kb blobs. Which would reduce the number of packets for 20k network to 80
|
||||
blobs.
|
||||
|
||||
3. Each validators votes is replicated across the entire network. To maintain a
|
||||
queue of 5 previous votes the Crds table would grow by 25 megabytes. `(20,000
|
||||
nodes * 256 bytes * 5)`.
|
||||
|
||||
## Two step implementation rollout
|
||||
|
||||
Initially the network can perform reliably with just 1 vote transmitted and
|
||||
maintained through the network with the current Vote implementation. For small
|
||||
networks a fanout of 6 is sufficient. With small network the memory and push
|
||||
overhead is minor.
|
||||
|
||||
### Sub 1k validator network
|
||||
|
||||
1. Crds just maintains the validators latest vote.
|
||||
|
||||
2. Votes are pushed and retransmitted regardless if they are appearing in the
|
||||
ledger.
|
||||
|
||||
3. Fanout of 6.
|
||||
|
||||
* Worst case 256kb memory overhead per node.
|
||||
* Worst case 4 hops to propagate to every node.
|
||||
* Leader should receive the entire validator vote set in 4 push message blobs.
|
||||
|
||||
### Sub 20k network
|
||||
|
||||
Everything above plus the following:
|
||||
|
||||
1. CRDS table maintains a vector of 5 latest validator votes.
|
||||
|
||||
2. Votes encode a wallclock. CrdsValue::Votes is a type that recurses into the
|
||||
transaction vector for all the gossip protocols.
|
||||
|
||||
3. Increase fanout to 20.
|
||||
|
||||
* Worst case 25mb memory overhead per node.
|
||||
* Sub 4 hops worst case to deliver to the entire network.
|
||||
* 80 blobs received by the leader for all the validator messages.
|