Compare commits
585 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
beb8e89bf4 | ||
|
54ff4529b1 | ||
|
0942cbd89b | ||
|
88bbaf7add | ||
|
d1447b5e52 | ||
|
c00bb42ecd | ||
|
1a0003fbcc | ||
|
a584ce6472 | ||
|
63d1f029a9 | ||
|
24ebf70016 | ||
|
d18dc94209 | ||
|
8242fd19eb | ||
|
469e91cd8d | ||
|
4889c2a29c | ||
|
3c6115c94a | ||
|
70b15317a9 | ||
|
a834e9ae10 | ||
|
7796e87814 | ||
|
64c770275b | ||
|
855f7ff352 | ||
|
b59a99111c | ||
|
252257fe66 | ||
|
e2c9d87d91 | ||
|
9d34b80ed6 | ||
|
c63a38ae57 | ||
|
20da2604f8 | ||
|
33de2cad6d | ||
|
aef7bae60d | ||
|
54ac7ed1ea | ||
|
0180246680 | ||
|
dab7de7496 | ||
|
feaf29792f | ||
|
5f09aa36b3 | ||
|
d6c74f438a | ||
|
349ebec629 | ||
|
f4554be72c | ||
|
8537da19bb | ||
|
d1eff5d607 | ||
|
19e4f70244 | ||
|
a233a1c822 | ||
|
27bc0a22dd | ||
|
7ee8383e02 | ||
|
bab0f6be1e | ||
|
535df0026d | ||
|
3bd35dd7cc | ||
|
39d29fab82 | ||
|
fbfe1a59a6 | ||
|
77c79effc1 | ||
|
83540087c3 | ||
|
937816e67b | ||
|
c3a941086d | ||
|
1046c5e32c | ||
|
baac8d2590 | ||
|
610a02c518 | ||
|
444bd7a702 | ||
|
7afc61e0b9 | ||
|
d4d9bec2a9 | ||
|
d647a4ec57 | ||
|
536b4c1a25 | ||
|
547a7a345f | ||
|
26e380e53c | ||
|
8a12ed029c | ||
|
b41e8333b1 | ||
|
8f646e21d7 | ||
|
5608af0246 | ||
|
17b9ea3e3b | ||
|
88d4d1db7a | ||
|
cab4c88c71 | ||
|
4ec5a899f5 | ||
|
c2f74330ef | ||
|
2c8e0bcf87 | ||
|
4966ab528e | ||
|
5f81a67298 | ||
|
a0ccdccff1 | ||
|
735c7c9841 | ||
|
3a69459645 | ||
|
21cef2fe21 | ||
|
038c6ea0a7 | ||
|
81f4fd56c7 | ||
|
264a3d7dde | ||
|
43bf176fab | ||
|
baec17fdf4 | ||
|
186b514ebb | ||
|
2d42c1e33e | ||
|
9cef522eee | ||
|
a6302acfd5 | ||
|
ac72265c6b | ||
|
09da6b4b48 | ||
|
0d8f5379a0 | ||
|
02c7b89a8f | ||
|
90ae33c200 | ||
|
55c879ce2d | ||
|
1b5a332239 | ||
|
595017499e | ||
|
9b1471acae | ||
|
b766ac0899 | ||
|
e6b525a614 | ||
|
a07b17b9b5 | ||
|
9d2940d487 | ||
|
6969ece2dd | ||
|
48fc35884c | ||
|
0958905df8 | ||
|
c95cda51c9 | ||
|
3f54c0f1a6 | ||
|
4684faa5e8 | ||
|
111d0eb89b | ||
|
8b69998379 | ||
|
a21251dfea | ||
|
06cd7c1020 | ||
|
782846f295 | ||
|
19e131d710 | ||
|
9fd34cd985 | ||
|
adfb8ff2a1 | ||
|
83aa609540 | ||
|
1e1cb7c57c | ||
|
cdbd1b908a | ||
|
a12e7a2e33 | ||
|
25080f1a33 | ||
|
afa05acb32 | ||
|
d47caf2af8 | ||
|
a3a91ba222 | ||
|
751b54b60b | ||
|
488dd0e563 | ||
|
b58558ea4e | ||
|
6ad9dc18d8 | ||
|
027ebb6670 | ||
|
0ffd91df27 | ||
|
10d85f8366 | ||
|
7aad427511 | ||
|
bbd0455418 | ||
|
5174b3bc3f | ||
|
f88c72c41e | ||
|
9f678cc32a | ||
|
57036fbcc1 | ||
|
349e5001d6 | ||
|
94db9cd412 | ||
|
b505a0df22 | ||
|
acf096c5f7 | ||
|
e8583f5cfe | ||
|
5825b967d2 | ||
|
bf5bce50a4 | ||
|
77ea8b9b3e | ||
|
176cec6215 | ||
|
5ab4975c44 | ||
|
7e60ee39d9 | ||
|
3ea2933e2d | ||
|
fe87c05423 | ||
|
6b86f85916 | ||
|
04649de6a6 | ||
|
92d78451b1 | ||
|
0c87928132 | ||
|
db7e78bf99 | ||
|
adecd4cfdc | ||
|
40faaef9da | ||
|
9b54528c8e | ||
|
440d006ec1 | ||
|
6c49b10784 | ||
|
c858d1dbb3 | ||
|
741a0a8a4e | ||
|
16b6576839 | ||
|
6accf21229 | ||
|
d2b21ce8d0 | ||
|
b01990d480 | ||
|
d7fdfb7e21 | ||
|
19fe468dbc | ||
|
259a5130a8 | ||
|
0d27515d09 | ||
|
1c966aac25 | ||
|
d2b6c2e0ce | ||
|
7aecb87bce | ||
|
4a02914b30 | ||
|
7c12ecbe81 | ||
|
f093377805 | ||
|
5ac173d208 | ||
|
9f58318fc5 | ||
|
ebcdc06dc3 | ||
|
22315d88e7 | ||
|
0a36a78133 | ||
|
a25446f045 | ||
|
2860d2fe27 | ||
|
e4861f52e0 | ||
|
5698d48dc8 | ||
|
5b95685e12 | ||
|
4c90898f0b | ||
|
a191f3fd90 | ||
|
b2c776eabc | ||
|
2c8d6f87e6 | ||
|
08f6de0acd | ||
|
bd92f37553 | ||
|
2abbc89dcd | ||
|
8cad992170 | ||
|
41d0db078e | ||
|
8781aebe06 | ||
|
727c15ef8a | ||
|
e4926e4110 | ||
|
b50a3bae72 | ||
|
35ec7a5156 | ||
|
a383ea532f | ||
|
b1a678b2db | ||
|
e563a4dda3 | ||
|
dbe533385e | ||
|
f537482c86 | ||
|
aebd70ddce | ||
|
7d80cfb17a | ||
|
b8e7736af2 | ||
|
32b55e6703 | ||
|
0a949677f0 | ||
|
f777a1a74c | ||
|
d111223085 | ||
|
1ca7e9f67b | ||
|
bc8f435d45 | ||
|
5e221bf219 | ||
|
fc58b3e8c3 | ||
|
1033f52877 | ||
|
4771177f9d | ||
|
50c6b5d62d | ||
|
f9a2254688 | ||
|
49250f62aa | ||
|
22ef3c7c54 | ||
|
417e8d5064 | ||
|
1feb9bea21 | ||
|
563c42b829 | ||
|
841e5e326c | ||
|
281deae102 | ||
|
c5ba2e0883 | ||
|
eb4edd75e6 | ||
|
bb6bcd79c0 | ||
|
ef7022d638 | ||
|
2aac094f63 | ||
|
fc180f4cbf | ||
|
e26a0bf840 | ||
|
3557975c1f | ||
|
b4aebbd991 | ||
|
db13b52e6a | ||
|
f1f6537837 | ||
|
2ec5d2c7f5 | ||
|
42e5623e26 | ||
|
ab9f2adc69 | ||
|
f551b34725 | ||
|
55b8ff72d0 | ||
|
bf319ab06d | ||
|
12ef0c25b5 | ||
|
8620d0a3b2 | ||
|
933ae51fcc | ||
|
c1201e54fa | ||
|
3615445a12 | ||
|
091999a17e | ||
|
417066ad30 | ||
|
2abe051a1f | ||
|
65adce65fa | ||
|
0c8f187993 | ||
|
cbd2938035 | ||
|
0999225794 | ||
|
38b44f2496 | ||
|
c1953dca8f | ||
|
19ea5fe0c0 | ||
|
d7ed3b8024 | ||
|
a89589a1d5 | ||
|
41bda18046 | ||
|
0c832f4668 | ||
|
75b494d4a3 | ||
|
f0191a98ab | ||
|
76413cbfac | ||
|
0fa1af5d47 | ||
|
af1c70f032 | ||
|
278614fc7c | ||
|
baca35ef4d | ||
|
66552d7047 | ||
|
979df17328 | ||
|
6cec61dcfc | ||
|
a9b044f0ab | ||
|
fbea9d8621 | ||
|
44a572416d | ||
|
97c97db97e | ||
|
b8ae025f90 | ||
|
27221e28f6 | ||
|
9a52b01171 | ||
|
8cea650535 | ||
|
531679eeaf | ||
|
850f77ab3b | ||
|
4a10fd3272 | ||
|
9e2eb9e4f9 | ||
|
8120b57f17 | ||
|
f651c0922a | ||
|
8d2ec20201 | ||
|
dce1f80aac | ||
|
df1c473341 | ||
|
8a64e1ddc3 | ||
|
eb47538a82 | ||
|
861d6468ca | ||
|
d6737b8cc9 | ||
|
30592f2b12 | ||
|
1f950781c2 | ||
|
f20ba423ca | ||
|
c5e6ebb496 | ||
|
9e7f618cff | ||
|
74a06e4230 | ||
|
70f93cc126 | ||
|
3f8ff23125 | ||
|
29611fb61d | ||
|
407b1d3e6f | ||
|
206e62271b | ||
|
4e78354ab6 | ||
|
1561d9c8d4 | ||
|
0e1480b84e | ||
|
fd6047d1c5 | ||
|
b0467be393 | ||
|
1b0b095813 | ||
|
bd43724dfc | ||
|
11992946a4 | ||
|
0cc8a841ab | ||
|
23b6b85bf0 | ||
|
96b56fa6f7 | ||
|
405ca1bcb2 | ||
|
c6316bb24b | ||
|
b7f169e06e | ||
|
e4b466874c | ||
|
9911942dbd | ||
|
8acbb4ab2f | ||
|
a49f5378e2 | ||
|
f39e74f0d7 | ||
|
22b767308a | ||
|
36aa876833 | ||
|
06ba0b7279 | ||
|
a38e1a81ef | ||
|
da925142d1 | ||
|
5feeb257bb | ||
|
06c547094a | ||
|
a40c5cf185 | ||
|
deb83cdef6 | ||
|
20db335aed | ||
|
407db65336 | ||
|
9c5a3cd277 | ||
|
138a49e820 | ||
|
36c9e22e3d | ||
|
aa0f8538ed | ||
|
4177c56c51 | ||
|
425ac8d520 | ||
|
ada4d16c4c | ||
|
4069ef2e02 | ||
|
ace98bba08 | ||
|
e59b53dfa8 | ||
|
aacb38864c | ||
|
33d13a3aea | ||
|
1f0f947ed2 | ||
|
6854c64a09 | ||
|
4a32bc48d2 | ||
|
b430762a23 | ||
|
f8523db51d | ||
|
48b11d1841 | ||
|
3600a926df | ||
|
c228792967 | ||
|
7ea522e851 | ||
|
63503ad589 | ||
|
9800e09431 | ||
|
2e2b1881f5 | ||
|
61483c18ca | ||
|
a5279bb835 | ||
|
357554b209 | ||
|
41fbdc6e08 | ||
|
8bd1c57448 | ||
|
2562e48b9d | ||
|
46bb79df29 | ||
|
6bc0d2a0cb | ||
|
465cd45833 | ||
|
b4484b89c3 | ||
|
c029f069f0 | ||
|
fdb57bc5db | ||
|
e43a634944 | ||
|
2da7c7fbd3 | ||
|
5683282c94 | ||
|
44967abd1c | ||
|
8b41a5d725 | ||
|
07c183bb84 | ||
|
7fd879b417 | ||
|
dc5c6e7cf8 | ||
|
bd633d2b81 | ||
|
feeaad619a | ||
|
b44d8c394e | ||
|
0ff9c4cd8e | ||
|
9cafd1f85e | ||
|
7fe10ba060 | ||
|
cc48773b03 | ||
|
8fbf0e2d9f | ||
|
d86358eedc | ||
|
fe04fb4cd3 | ||
|
de3f7e9634 | ||
|
5e8fcdbe1d | ||
|
3ee7256c0c | ||
|
2a7a9fdf03 | ||
|
5bf87de136 | ||
|
97a136ea20 | ||
|
735dfab02e | ||
|
b5f65ce49c | ||
|
a283863694 | ||
|
25908feef9 | ||
|
b91ad6fd96 | ||
|
02abf422df | ||
|
3fe5f886d7 | ||
|
4c6a6d63bf | ||
|
589a9d3a72 | ||
|
bd884a56bf | ||
|
119467df59 | ||
|
ee68b9800e | ||
|
c6b4a3a706 | ||
|
b1ac8f933b | ||
|
9e3758983d | ||
|
34c0537e9b | ||
|
8628f33d0b | ||
|
ed05aeaef8 | ||
|
e1444a9b00 | ||
|
9514169bf6 | ||
|
fa8394f526 | ||
|
1cd8c1865e | ||
|
e3f895d7d4 | ||
|
8abf22f34b | ||
|
a016bc2736 | ||
|
470debef16 | ||
|
c147dc3028 | ||
|
bdd95b2286 | ||
|
efe676bc94 | ||
|
fc34687687 | ||
|
6042ccf496 | ||
|
f1197e1b1f | ||
|
8c1b9a0b67 | ||
|
0da9ac1a47 | ||
|
c1f316721a | ||
|
8e86014311 | ||
|
d807217be7 | ||
|
bc44516eb4 | ||
|
b78a13d42c | ||
|
0dcdc37fec | ||
|
dd1c3514a8 | ||
|
767efab941 | ||
|
288a3bdcd9 | ||
|
8019bff391 | ||
|
575a897ffc | ||
|
697228a484 | ||
|
ca907f37c3 | ||
|
439e7cc26a | ||
|
3217a1d70c | ||
|
6dbba86cc6 | ||
|
8cc863ea6c | ||
|
1d957b6b80 | ||
|
e56430c9fb | ||
|
e4d8ea11ac | ||
|
a4035a3c65 | ||
|
807c69d97c | ||
|
9259d342ac | ||
|
b4d4edb645 | ||
|
966b6999d1 | ||
|
73491e3ca1 | ||
|
d1d53c3fb6 | ||
|
a77e576cd9 | ||
|
9e14cde461 | ||
|
a2a7c86c0d | ||
|
38aeed02fc | ||
|
64d63966c7 | ||
|
38ae54b720 | ||
|
a18c0e34f4 | ||
|
be3a0b6b10 | ||
|
9f6496d38a | ||
|
1fa31c9410 | ||
|
2b5e757d57 | ||
|
0dbe5ee559 | ||
|
6926e89e86 | ||
|
ec0007217d | ||
|
91b23f8316 | ||
|
2fd8d57504 | ||
|
0595109f98 | ||
|
9f46b2a6ce | ||
|
a357d08524 | ||
|
177c9cc026 | ||
|
0c4cb76acf | ||
|
8676b5d40c | ||
|
efab896c9e | ||
|
97b9d57b62 | ||
|
487826a539 | ||
|
4acb764589 | ||
|
9de4c1dcd9 | ||
|
e8c4302d6d | ||
|
a9f73ea321 | ||
|
66c41b3e8c | ||
|
8435fbfa0b | ||
|
9a4c449135 | ||
|
ac6dbf8f04 | ||
|
b55927370b | ||
|
002fbc4d53 | ||
|
53deb7919c | ||
|
8e46c44f3e | ||
|
37c2fa1d8d | ||
|
fdaa939892 | ||
|
c9d63204eb | ||
|
cfab54511b | ||
|
492cc93850 | ||
|
fd9fd43e83 | ||
|
191483f4ee | ||
|
688f8a669a | ||
|
46eea85022 | ||
|
1c765124e7 | ||
|
194491ae96 | ||
|
2ae595294c | ||
|
ead947e710 | ||
|
82df267ec9 | ||
|
53275cc678 | ||
|
44835a91db | ||
|
ee42040e6b | ||
|
2b98a16ec6 | ||
|
aa4a7b0c73 | ||
|
8f50c3dd2e | ||
|
9c47ce30a7 | ||
|
3433b08b8c | ||
|
d26fd27bf9 | ||
|
5c98c1d306 | ||
|
51aacfe3ca | ||
|
82bd2df986 | ||
|
aa88c40a9e | ||
|
8ec5a47027 | ||
|
5bd3eb4557 | ||
|
e9cb4a12dc | ||
|
de5cad9211 | ||
|
e3365529de | ||
|
ce2ce76958 | ||
|
16f2fb5c09 | ||
|
d77c98530f | ||
|
fe40b75ac6 | ||
|
e7129757c9 | ||
|
3635a68129 | ||
|
70a16e91a5 | ||
|
41daf1ef0c | ||
|
ff77789718 | ||
|
a77775cb58 | ||
|
167e15a5ae | ||
|
dea663d509 | ||
|
9754e551cb | ||
|
40a4ac15f1 | ||
|
c56052ff16 | ||
|
482ef51502 | ||
|
e4ca3900ae | ||
|
3574469052 | ||
|
e15246746d | ||
|
ec5cca41bc | ||
|
bc1368ba3e | ||
|
c0a161afe8 | ||
|
d343c409e6 | ||
|
64e8a21d73 | ||
|
ce04d2bfc2 | ||
|
1c1d83bd56 | ||
|
028e111fbc | ||
|
9670788bf5 | ||
|
d2f9625878 | ||
|
182096dc1a | ||
|
2d284ba6db | ||
|
1de805e7cd | ||
|
d642125f68 | ||
|
b8aff218e2 | ||
|
045d4d5294 | ||
|
d67dd8ce1f | ||
|
4d6679906b | ||
|
4537f54532 | ||
|
39b40dfff8 | ||
|
c82f4a1b6d | ||
|
7a021dff05 | ||
|
348c2263ba | ||
|
b5324063f1 | ||
|
6ed071c4dd | ||
|
4404634b14 | ||
|
6a1de33138 | ||
|
c05c3e69ca | ||
|
534244b322 | ||
|
335dfdc4d5 | ||
|
a7ef409c2b | ||
|
14594217db | ||
|
c8a03c7b3d | ||
|
9fcd162412 | ||
|
441fed7a5b | ||
|
ff31ffbd54 | ||
|
0e26ee854b | ||
|
5340800cea | ||
|
13c2e50b38 | ||
|
dd39b2b056 | ||
|
65f89d6729 | ||
|
1eceb4831d | ||
|
50303c9ede | ||
|
ed6a438c51 |
41
.appveyor.yml
Normal file
41
.appveyor.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
os: Visual Studio 2017
|
||||
version: '{build}'
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v[0-9.]+/
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.cargo'
|
||||
- '%APPVEYOR_BUILD_FOLDER%\target'
|
||||
|
||||
build_script:
|
||||
- bash ci/publish-tarball.sh
|
||||
|
||||
notifications:
|
||||
- provider: Slack
|
||||
incoming_webhook:
|
||||
secure: 6HnLbeS6/Iv7JSMrrHQ7V9OSIjH/3KFzvZiinNWgQqEN0e9A6zaE4MwEXUYDWbcvVJiQneWit6dswY8Scoms2rS1PWEN5N6sjgLgyzroptc=
|
||||
channel: ci-status
|
||||
on_build_success: false
|
||||
on_build_failure: true
|
||||
on_build_status_changed: true
|
||||
|
||||
deploy:
|
||||
- provider: S3
|
||||
access_key_id:
|
||||
secure: G6uzyGqbkMCXS2+sCeBCT/+s/11AHLWXCuGayfKcMEE=
|
||||
secret_access_key:
|
||||
secure: Lc+aVrbcPSXoDV7h2J7gqKT+HX0n3eEzp3JIrSP2pcKxbAikGnCtOogCiHO9/er2
|
||||
bucket: release.solana.com
|
||||
region: us-west-1
|
||||
set_public: true
|
||||
|
||||
- provider: GitHub
|
||||
auth_token:
|
||||
secure: JdggY+mrznklWDcV0yvetHhD9eRcNdc627q6NcZdZAJsDidYcGgZ/tgYJiXb9D1A
|
||||
draft: false
|
||||
prerelease: false
|
||||
on:
|
||||
appveyor_repo_tag: true
|
1
.buildkite/env/.gitignore
vendored
Normal file
1
.buildkite/env/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/secrets_unencrypted.ejson
|
16
.buildkite/env/secrets.ejson
vendored
16
.buildkite/env/secrets.ejson
vendored
@@ -1,12 +1,14 @@
|
||||
{
|
||||
"_public_key": "ae29f4f7ad2fc92de70d470e411c8426d5d48db8817c9e3dae574b122192335f",
|
||||
"environment": {
|
||||
"CODECOV_TOKEN": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:jA0WqO70coUtF0iokRdgtCR/lF/lETAI:d/Wl8Tdl6xVh/B39cTf1DaQkomR7I/2vMhvxd1msJ++BjI2l3p2dFoGsXqWT+/os8VgiPg==]",
|
||||
"CRATES_IO_TOKEN": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:2FaZ6k4RGH8luyNRaN6yeZUQDNAu2KwC:XeYe0tCAivYE0F9HEWM79mAI6kNbfYaqP7k7yY+SBDvs0341U9BdGZp7SErbHleS]",
|
||||
"GITHUB_TOKEN": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:9kh4DGPiGDcUU7ejSFWg3gTW8nrOM09Q:b+GE07Wu6/bEnkDZcUtf48vTKAFphrCSt3tNNER9h6A+wZ80k499edw4pbDdl9kEvxB30fFwrLQ=]",
|
||||
"INFLUX_DATABASE": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:rCHsYi0rc7dmvr1V3wEgNoaNIyr+9ClM:omjVcOqM7vwt44kJ+As4BjJL]",
|
||||
"INFLUX_PASSWORD": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:bP5Gw1Vy66viKFKO41o2Gho998XajH/5:khkCYz2LFvkJkk7R4xY1Hfz1yU3/NENjauiUkPhXA+dmg1qOIToxEagCgIkRwyeCiYaoCR6CZyw=]",
|
||||
"INFLUX_USERNAME": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:ZamCvza2W9/bZRGSkqDu55xNN04XKKhp:5jlmCOdFbpL7EFez41zCbLfk3ZZlfmhI]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu": "EJ[1:eSGdiZR0Qi0g7qnsI+qJ5H+/ik+H2qL3ned/cBdv/SY=:Oi2nsRxnvWnnBYsB6KwEDzLPcYgpYojU:ELbvjXkXKlgFCMES45R+fxG7Ex43WHWErjMbxZoqasxyr7GSH66hQzUWqiQSJyT4ukYrRhRC9YrsKKGkjACLU57X4EGIy9TuLgTnyBYhPnxLYStC3y/7o/MB5FCTt5wHJw3/A9p+me5+T4UmyZ7OeP21NhDUCGQcb0040VwYWS78klW2aQESJJ6wTI1xboE8/zC0vtnB/u50+LydbKEyb21r6y3OH9FYNEpSwIspWKcgpruJdQSCnDoKxP9YR1yzvk2rabss13LJNdV1Y6mQNIdP4OIFQhCs6dXT253RTl5qdZ0MruHwlp8wX4btOuYDcCoM5exr]"
|
||||
"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=]"
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
CI_BUILD_START=$(date +%s)
|
||||
export CI_BUILD_START
|
||||
|
||||
source ci/env.sh
|
||||
|
||||
#
|
||||
# Kill any running docker containers, which are potentially left over from the
|
||||
# previous CI job
|
||||
@@ -31,3 +33,10 @@ export CI_BUILD_START
|
||||
kill -9 "$victim" || true
|
||||
done
|
||||
)
|
||||
|
||||
# HACK: These are in our docker images, need to be removed from CARGO_HOME
|
||||
# because we try to cache downloads across builds with CARGO_HOME
|
||||
# cargo lacks a facility for "system" tooling, always tries CARGO_HOME first
|
||||
cargo uninstall cargo-audit || true
|
||||
cargo uninstall svgbob_cli || true
|
||||
cargo uninstall mdbook || true
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -2,9 +2,10 @@
|
||||
/book/src/img/
|
||||
/book/src/tests.ok
|
||||
/farf/
|
||||
/metrics/scripts/lib/
|
||||
/solana-release/
|
||||
solana-release.tar.bz2
|
||||
/solana-release.tar.bz2
|
||||
/solana-metrics/
|
||||
/solana-metrics.tar.bz2
|
||||
/target/
|
||||
|
||||
**/*.rs.bk
|
||||
@@ -22,3 +23,4 @@ log-*.txt
|
||||
# intellij files
|
||||
/.idea/
|
||||
/solana.iml
|
||||
/.vscode/
|
||||
|
45
.mergify.yml
Normal file
45
.mergify.yml
Normal file
@@ -0,0 +1,45 @@
|
||||
# Validate your changes with:
|
||||
#
|
||||
# $ curl -F 'data=@.mergify.yml' https://gh.mergify.io/validate
|
||||
#
|
||||
# https://doc.mergify.io/
|
||||
pull_request_rules:
|
||||
- name: remove outdated reviews
|
||||
conditions:
|
||||
- base=master
|
||||
actions:
|
||||
dismiss_reviews:
|
||||
changes_requested: true
|
||||
- name: set automerge label on mergify backport PRs
|
||||
conditions:
|
||||
- author=mergify[bot]
|
||||
- head~=^mergify/bp/
|
||||
- "#status-failure=0"
|
||||
actions:
|
||||
label:
|
||||
add:
|
||||
- automerge
|
||||
- name: v0.16 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.16
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.16
|
||||
- name: v0.17 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.17
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.17
|
||||
- name: v0.18 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.18
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.18
|
44
.travis.yml
Normal file
44
.travis.yml
Normal file
@@ -0,0 +1,44 @@
|
||||
os:
|
||||
- osx
|
||||
|
||||
language: rust
|
||||
cache: cargo
|
||||
rust:
|
||||
- 1.36.0
|
||||
|
||||
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
|
||||
- ci/publish-tarball.sh
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
|
||||
|
||||
notifications:
|
||||
slack:
|
||||
on_success: change
|
||||
secure: F4IjOE05MyaMOdPRL+r8qhs7jBvv4yDM3RmFKE1zNXnfUOqV4X38oQM1EI+YVsgpMQLj/pxnEB7wcTE4Bf86N6moLssEULCpvAuMVoXj4QbWdomLX+01WbFa6fLVeNQIg45NHrz2XzVBhoKOrMNnl+QI5mbR2AlS5oqsudHsXDnyLzZtd4Y5SDMdYG1zVWM01+oNNjgNfjcCGmOE/K0CnOMl6GPi3X9C34tJ19P2XT7MTDsz1/IfEF7fro2Q8DHEYL9dchJMoisXSkem5z7IDQkGzXsWdWT4NnndUvmd1MlTCE9qgoXDqRf95Qh8sB1Dz08HtvgfaosP2XjtNTfDI9BBYS15Ibw9y7PchAJE1luteNjF35EOy6OgmCLw/YpnweqfuNViBZz+yOPWXVC0kxnPIXKZ1wyH9ibeH6E4hr7a8o9SV/6SiWIlbYF+IR9jPXyTCLP/cc3sYljPWxDnhWFwFdRVIi3PbVAhVu7uWtVUO17Oc9gtGPgs/GrhOMkJfwQPXaudRJDpVZowxTX4x9kefNotlMAMRgq+Drbmgt4eEBiCNp0ITWgh17BiE1U09WS3myuduhoct85+FoVeaUkp1sxzHVtGsNQH0hcz7WcpZyOM+AwistJA/qzeEDQao5zi1eKWPbO2xAhi2rV1bDH6bPf/4lDBwLRqSiwvlWU=
|
||||
|
||||
deploy:
|
||||
- provider: s3
|
||||
access_key_id: $AWS_ACCESS_KEY_ID
|
||||
secret_access_key: $AWS_SECRET_ACCESS_KEY
|
||||
bucket: release.solana.com
|
||||
region: us-west-1
|
||||
skip_cleanup: true
|
||||
acl: public_read
|
||||
local_dir: travis-s3-upload
|
||||
on:
|
||||
all_branches: true
|
||||
- provider: releases
|
||||
api_key: $GITHUB_TOKEN
|
||||
skip_cleanup: true
|
||||
file_glob: true
|
||||
file: travis-release-upload/*
|
||||
on:
|
||||
tags: true
|
3989
Cargo.lock
generated
3989
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
18
Cargo.toml
18
Cargo.toml
@@ -3,21 +3,27 @@ members = [
|
||||
"bench-exchange",
|
||||
"bench-streamer",
|
||||
"bench-tps",
|
||||
"sdk-c",
|
||||
"chacha-sys",
|
||||
"client",
|
||||
"core",
|
||||
"drone",
|
||||
"validator",
|
||||
"genesis",
|
||||
"genesis_programs",
|
||||
"gossip",
|
||||
"install",
|
||||
"keygen",
|
||||
"kvstore",
|
||||
"ledger-tool",
|
||||
"logger",
|
||||
"merkle-tree",
|
||||
"measure",
|
||||
"metrics",
|
||||
"netutil",
|
||||
"programs/bpf",
|
||||
"programs/bpf_loader",
|
||||
"programs/bpf_loader_api",
|
||||
"programs/bpf_loader_program",
|
||||
"programs/budget_api",
|
||||
"programs/budget_program",
|
||||
"programs/config_api",
|
||||
@@ -25,9 +31,13 @@ members = [
|
||||
"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",
|
||||
@@ -38,7 +48,11 @@ members = [
|
||||
"runtime",
|
||||
"sdk",
|
||||
"upload-perf",
|
||||
"validator-info",
|
||||
"vote-signer",
|
||||
"wallet",
|
||||
]
|
||||
exclude = ["programs/bpf/rust/noop"]
|
||||
|
||||
exclude = [
|
||||
"programs/bpf/rust/noop",
|
||||
]
|
||||
|
39
README.md
39
README.md
@@ -30,6 +30,40 @@ Before you jump into the code, review the online book [Solana: Blockchain Rebuil
|
||||
|
||||
(The _latest_ development version of the online book is also [available here](https://solana-labs.github.io/book-edge/).)
|
||||
|
||||
Release Binaries
|
||||
===
|
||||
Official release binaries are available at [Github Releases](https://github.com/solana-labs/solana/releases).
|
||||
|
||||
Additionally we provide pre-release binaries for the latest code on the edge and
|
||||
beta channels. Note that these pre-release binaries may be less stable than an
|
||||
official release.
|
||||
|
||||
### Edge channel
|
||||
#### Linux (x86_64-unknown-linux-gnu)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
|
||||
#### mac OS (x86_64-apple-darwin)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-apple-darwin.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/edge/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
|
||||
#### Windows (x86_64-pc-windows-msvc)
|
||||
* [solana.tar.bz2](http://release.solana.com/edge/solana-release-x86_64-pc-windows-msvc.tar.bz2)
|
||||
* [solana-install-init.exe](http://release.solana.com/edge/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
|
||||
#### All platforms
|
||||
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/edge/solana-metrics.tar.bz2)
|
||||
|
||||
### Beta channel
|
||||
#### Linux (x86_64-unknown-linux-gnu)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-unknown-linux-gnu.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-unknown-linux-gnu) as a stand-alone executable
|
||||
#### mac OS (x86_64-apple-darwin)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-apple-darwin.tar.bz2)
|
||||
* [solana-install-init](http://release.solana.com/beta/solana-install-init-x86_64-apple-darwin) as a stand-alone executable
|
||||
#### Windows (x86_64-pc-windows-msvc)
|
||||
* [solana.tar.bz2](http://release.solana.com/beta/solana-release-x86_64-pc-windows-msvc.tar.bz2)
|
||||
* [solana-install-init.exe](http://release.solana.com/beta/solana-install-init-x86_64-pc-windows-msvc.exe) as a stand-alone executable
|
||||
#### All platforms
|
||||
* [solana-metrics.tar.bz2](http://release.solana.com.s3.amazonaws.com/beta/solana-metrics.tar.bz2)
|
||||
|
||||
Developing
|
||||
===
|
||||
|
||||
@@ -93,12 +127,9 @@ Remote Testnets
|
||||
|
||||
We maintain several testnets:
|
||||
|
||||
* `testnet` - public stable testnet accessible via testnet.solana.com, with an https proxy for web apps at api.testnet.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
|
||||
* `testnet-perf` - permissioned stable testnet running a 24/7 soak test
|
||||
* `testnet-beta-perf` - permissioned beta channel testnet running a multi-hour soak test weekday mornings
|
||||
* `testnet-edge-perf` - permissioned edge channel testnet running a multi-hour soak test weekday mornings
|
||||
|
||||
## Deploy process
|
||||
|
||||
|
33
RELEASE.md
33
RELEASE.md
@@ -61,7 +61,7 @@ There are three release channels that map to branches as follows:
|
||||
|
||||
## Release Steps
|
||||
|
||||
### Advance the Channels
|
||||
### Creating a new branch from master
|
||||
|
||||
#### Create the new branch
|
||||
1. Pick your branch point for release on master.
|
||||
@@ -84,6 +84,12 @@ There are three release channels that map to branches as follows:
|
||||
At this point, `ci/channel-info.sh` should show your freshly cut release branch as
|
||||
"BETA_CHANNEL" and the previous release branch as "STABLE_CHANNEL".
|
||||
|
||||
### Update documentation
|
||||
|
||||
Document the new recommended version by updating
|
||||
```export SOLANA_RELEASE=[new scheduled TESTNET_TAG value]```
|
||||
in book/src/testnet-participation.md on the release (beta) branch.
|
||||
|
||||
### Make the Release
|
||||
|
||||
We use [github's Releases UI](https://github.com/solana-labs/solana/releases) for tagging a release.
|
||||
@@ -106,6 +112,25 @@ We use [github's Releases UI](https://github.com/solana-labs/solana/releases) fo
|
||||
1. Push your Cargo.toml change and the autogenerated Cargo.lock changes to the
|
||||
release branch.
|
||||
|
||||
### Publish updated Book
|
||||
We maintain three copies of the "book" as official documentation:
|
||||
|
||||
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
|
||||
@@ -145,12 +170,6 @@ TESTNET_TAG=[same value as used in TESTNET_TAG in the schedules]
|
||||
TESTNET_OP=create-and-start
|
||||
```
|
||||
|
||||
#### Update documentation
|
||||
|
||||
Document the new recommended version by updating
|
||||
```export SOLANA_RELEASE=[new scheduled TESTNET_TAG value]```
|
||||
in book/src/testnet-participation.md for both edge and beta channel branches.
|
||||
|
||||
### Alert the community
|
||||
|
||||
Notify Discord users on #validator-support that a new release for
|
||||
|
1
bench-exchange/.gitignore
vendored
1
bench-exchange/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
||||
|
@@ -2,39 +2,41 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "0.15.0"
|
||||
version = "0.17.1"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.1.4"
|
||||
bs58 = "0.2.0"
|
||||
clap = "2.32.0"
|
||||
bincode = "1.1.4"
|
||||
env_logger = "0.6.0"
|
||||
env_logger = "0.6.2"
|
||||
itertools = "0.8.0"
|
||||
log = "0.4.6"
|
||||
num-traits = "0.2"
|
||||
log = "0.4.7"
|
||||
num-derive = "0.2"
|
||||
num-traits = "0.2"
|
||||
rand = "0.6.5"
|
||||
rayon = "1.0.3"
|
||||
serde = "1.0.91"
|
||||
serde_derive = "1.0.91"
|
||||
serde_json = "1.0.38"
|
||||
rayon = "1.1.0"
|
||||
serde = "1.0.97"
|
||||
serde_derive = "1.0.97"
|
||||
serde_json = "1.0.40"
|
||||
serde_yaml = "0.8.9"
|
||||
# solana-runtime = { path = "../solana/runtime"}
|
||||
solana = { path = "../core", version = "0.15.0" }
|
||||
solana-client = { path = "../client", version = "0.15.0" }
|
||||
solana-drone = { path = "../drone", version = "0.15.0" }
|
||||
solana-exchange-api = { path = "../programs/exchange_api", version = "0.15.0" }
|
||||
solana-exchange-program = { path = "../programs/exchange_program", version = "0.15.0" }
|
||||
solana-logger = { path = "../logger", version = "0.15.0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.15.0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.15.0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.15.0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.15.0" }
|
||||
solana = { path = "../core", version = "0.17.1" }
|
||||
solana-client = { path = "../client", version = "0.17.1" }
|
||||
solana-drone = { path = "../drone", version = "0.17.1" }
|
||||
solana-exchange-api = { path = "../programs/exchange_api", version = "0.17.1" }
|
||||
solana-exchange-program = { path = "../programs/exchange_program", version = "0.17.1" }
|
||||
solana-logger = { path = "../logger", version = "0.17.1" }
|
||||
solana-metrics = { path = "../metrics", version = "0.17.1" }
|
||||
solana-netutil = { path = "../netutil", version = "0.17.1" }
|
||||
solana-runtime = { path = "../runtime", version = "0.17.1" }
|
||||
solana-sdk = { path = "../sdk", version = "0.17.1" }
|
||||
untrusted = "0.7.0"
|
||||
ws = "0.8.1"
|
||||
untrusted = "0.6.2"
|
||||
|
||||
[features]
|
||||
cuda = ["solana/cuda"]
|
||||
erasure = []
|
||||
|
||||
|
@@ -6,10 +6,10 @@ learn how to start and interact with the exchange.
|
||||
|
||||
### Table of Contents
|
||||
[Overview](#Overview)<br>
|
||||
[Premiss](#Premiss)<br>
|
||||
[Premise](#Premise)<br>
|
||||
[Exchange startup](#Exchange-startup)<br>
|
||||
[Trade requests](#Trade-requests)<br>
|
||||
[Trade cancellations](#Trade-cancellations)<br>
|
||||
[Order Requests](#Trade-requests)<br>
|
||||
[Order Cancellations](#Trade-cancellations)<br>
|
||||
[Trade swap](#Trade-swap)<br>
|
||||
[Exchange program operations](#Exchange-program-operations)<br>
|
||||
[Quotes and OHLCV](#Quotes-and-OHLCV)<br>
|
||||
@@ -22,9 +22,9 @@ An exchange is a marketplace where one asset can be traded for another. This
|
||||
demo demonstrates one way to host an exchange on the Solana blockchain by
|
||||
emulating a currency exchange.
|
||||
|
||||
The assets are virtual tokens held by investors who may post trade requests to
|
||||
The assets are virtual tokens held by investors who may post order requests to
|
||||
the exchange. A Swapper monitors the exchange and posts swap requests for
|
||||
matching trade orders. All the transactions can execute concurrently.
|
||||
matching orders. All the transactions can execute concurrently.
|
||||
|
||||
## Premise
|
||||
|
||||
@@ -59,43 +59,43 @@ matching trade orders. All the transactions can execute concurrently.
|
||||
ratios are represented as fixed point numbers. The fixed point scaler is
|
||||
defined in
|
||||
[exchange_state.rs](https://github.com/solana-labs/solana/blob/c2fdd1362a029dcf89c8907c562d2079d977df11/programs/exchange_api/src/exchange_state.rs#L7)
|
||||
- Trade request
|
||||
- Order request
|
||||
- A Solana transaction executed by the exchange requesting the trade of one
|
||||
type of token for another. Trade requests are made up of the token pair,
|
||||
type of token for another. order requests are made up of the token pair,
|
||||
the direction of the trade, quantity of the primary token, the price ratio,
|
||||
and the two token accounts to be credited/deducted. An example trade
|
||||
request looks like "T AB 5 2" which reads "Exchange 5 A tokens to B tokens
|
||||
at a price ratio of 1:2" A fulfilled trade would result in 5 A tokens
|
||||
deducted and 10 B tokens credited to the trade initiator's token accounts.
|
||||
Successful trade requests result in a trade order.
|
||||
- Trade order
|
||||
- The result of a successful trade request. Trade orders are stored in
|
||||
accounts owned by the submitter of the trade request. They can only be
|
||||
Successful order requests result in an order.
|
||||
- Order
|
||||
- The result of a successful order request. orders are stored in
|
||||
accounts owned by the submitter of the order request. They can only be
|
||||
canceled by their owner but can be used by anyone in a trade swap. They
|
||||
contain the same information as the trade request.
|
||||
contain the same information as the order request.
|
||||
- Price spread
|
||||
- The difference between the two matching trade orders. The spread is the
|
||||
- The difference between the two matching orders. The spread is the
|
||||
profit of the Swapper initiating the swap request.
|
||||
- Swap requirements
|
||||
- Policies that result in a successful trade swap.
|
||||
- Swap request
|
||||
- A request to exchange tokens between to trade orders
|
||||
- A request to exchange tokens between to orders
|
||||
- Trade swap
|
||||
- A successful trade. A swap consists of two matching trade orders that meet
|
||||
- A successful trade. A swap consists of two matching orders that meet
|
||||
swap requirements. A trade swap may not wholly satisfy one or both of the
|
||||
trade orders in which case the trade orders are adjusted appropriately. As
|
||||
orders in which case the orders are adjusted appropriately. As
|
||||
long as the swap requirements are met there will be an exchange of tokens
|
||||
between accounts. Any price spread is deposited into the Swapper's profit
|
||||
account. All trade swaps are recorded in a new account for posterity.
|
||||
- Investor
|
||||
- Individual investors who hold a number of tokens and wish to trade them on
|
||||
the exchange. Investors operate as Solana thin clients who own a set of
|
||||
accounts containing tokens and/or trade requests. Investors post
|
||||
accounts containing tokens and/or order requests. Investors post
|
||||
transactions to the exchange in order to request tokens and post or cancel
|
||||
trade requests.
|
||||
order requests.
|
||||
- Swapper
|
||||
- An agent who facilitates trading between investors. Swappers operate as
|
||||
Solana thin clients who monitor all the trade orders looking for a trade
|
||||
Solana thin clients who monitor all the orders looking for a trade
|
||||
match. Once found, the Swapper issues a swap request to the exchange.
|
||||
Swappers are the engine of the exchange and are rewarded for their efforts by
|
||||
accumulating the price spreads of the swaps they initiate. Swappers also
|
||||
@@ -123,7 +123,7 @@ the investors that trades submitted after that point will be analyzed. <!--This
|
||||
is not ideal, and instead investors should be able to submit trades at any time,
|
||||
and the Swapper could come and go without missing a trade. One way to achieve
|
||||
this is for the Swapper to read the current state of all accounts looking for all
|
||||
open trade orders.-->
|
||||
open orders.-->
|
||||
|
||||
Investors will initially query the exchange to discover their current balance
|
||||
for each type of token. If the investor does not already have an account for
|
||||
@@ -181,19 +181,19 @@ pub enum ExchangeInstruction {
|
||||
}
|
||||
```
|
||||
|
||||
## Trade requests
|
||||
## Order Requests
|
||||
|
||||
When an investor decides to exchange a token of one type for another, they
|
||||
submit a transaction to the Solana Blockchain containing a trade request, which,
|
||||
if successful, is turned into a trade order. Trade orders do not expire but are
|
||||
cancellable. <!-- Trade orders should have a timestamp to enable trade
|
||||
expiration --> When a trade order is created, tokens are deducted from a token
|
||||
account and the trade order acts as an escrow. The tokens are held until the
|
||||
trade order is fulfilled or canceled. If the direction is `To`, then the number
|
||||
submit a transaction to the Solana Blockchain containing an order request, which,
|
||||
if successful, is turned into an order. orders do not expire but are
|
||||
cancellable. <!-- orders should have a timestamp to enable trade
|
||||
expiration --> When an order is created, tokens are deducted from a token
|
||||
account and the order acts as an escrow. The tokens are held until the
|
||||
order is fulfilled or canceled. If the direction is `To`, then the number
|
||||
of `tokens` are deducted from the primary account, if `From` then `tokens`
|
||||
multiplied by `price` are deducted from the secondary account. Trade orders are
|
||||
multiplied by `price` are deducted from the secondary account. orders are
|
||||
no longer valid when the number of `tokens` goes to zero, at which point they
|
||||
can no longer be used. <!-- Could support refilling trade orders, so trade order
|
||||
can no longer be used. <!-- Could support refilling orders, so order
|
||||
accounts are refilled rather than accumulating -->
|
||||
|
||||
```rust
|
||||
@@ -205,7 +205,7 @@ pub enum Direction {
|
||||
From,
|
||||
}
|
||||
|
||||
pub struct TradeRequestInfo {
|
||||
pub struct OrderRequestInfo {
|
||||
/// Direction of trade
|
||||
pub direction: Direction,
|
||||
|
||||
@@ -224,7 +224,7 @@ pub struct TradeRequestInfo {
|
||||
}
|
||||
|
||||
pub enum ExchangeInstruction {
|
||||
/// Trade request
|
||||
/// order request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - Token account associated with this trade
|
||||
@@ -233,7 +233,7 @@ pub enum ExchangeInstruction {
|
||||
|
||||
/// Trade accounts are populated with this structure
|
||||
pub struct TradeOrderInfo {
|
||||
/// Owner of the trade order
|
||||
/// Owner of the order
|
||||
pub owner: Pubkey,
|
||||
/// Direction of the exchange
|
||||
pub direction: Direction,
|
||||
@@ -252,7 +252,7 @@ pub struct TradeOrderInfo {
|
||||
}
|
||||
```
|
||||
|
||||
## Trade cancellations
|
||||
## Order cancellations
|
||||
|
||||
An investor may cancel a trade at anytime, but only trades they own. If the
|
||||
cancellation is successful, any tokens held in escrow are returned to the
|
||||
@@ -260,9 +260,9 @@ account from which they came.
|
||||
|
||||
```rust
|
||||
pub enum ExchangeInstruction {
|
||||
/// Trade cancellation
|
||||
/// order cancellation
|
||||
/// key 0 - Signer
|
||||
/// key 1 -Trade order to cancel
|
||||
/// key 1 -order to cancel
|
||||
TradeCancellation,
|
||||
}
|
||||
```
|
||||
@@ -270,14 +270,14 @@ pub enum ExchangeInstruction {
|
||||
## Trade swaps
|
||||
|
||||
The Swapper is monitoring the accounts assigned to the exchange program and
|
||||
building a trade-order table. The trade order table is used to identify
|
||||
matching trade orders which could be fulfilled. When a match is found the
|
||||
building a trade-order table. The order table is used to identify
|
||||
matching orders which could be fulfilled. When a match is found the
|
||||
Swapper should issue a swap request. Swap requests may not satisfy the entirety
|
||||
of either order, but the exchange will greedily fulfill it. Any leftover tokens
|
||||
in either account will keep the trade order valid for further swap requests in
|
||||
in either account will keep the order valid for further swap requests in
|
||||
the future.
|
||||
|
||||
Matching trade orders are defined by the following swap requirements:
|
||||
Matching orders are defined by the following swap requirements:
|
||||
|
||||
- Opposite polarity (one `To` and one `From`)
|
||||
- Operate on the same token pair
|
||||
@@ -379,8 +379,8 @@ pub enum ExchangeInstruction {
|
||||
/// Trade swap request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - 'To' trade order
|
||||
/// key 3 - `From` trade order
|
||||
/// key 2 - 'To' order
|
||||
/// key 3 - `From` order
|
||||
/// key 4 - Token account associated with the To Trade
|
||||
/// key 5 - Token account associated with From trade
|
||||
/// key 6 - Token account in which to deposit the Swappers profit from the swap.
|
||||
@@ -391,9 +391,9 @@ pub enum ExchangeInstruction {
|
||||
pub struct TradeSwapInfo {
|
||||
/// Pair swapped
|
||||
pub pair: TokenPair,
|
||||
/// `To` trade order
|
||||
/// `To` order
|
||||
pub to_trade_order: Pubkey,
|
||||
/// `From` trade order
|
||||
/// `From` order
|
||||
pub from_trade_order: Pubkey,
|
||||
/// Number of primary tokens exchanged
|
||||
pub primary_tokens: u64,
|
||||
@@ -424,22 +424,22 @@ pub enum ExchangeInstruction {
|
||||
/// the exchange has a limitless number of tokens it can transfer.
|
||||
TransferRequest(Token, u64),
|
||||
|
||||
/// Trade request
|
||||
/// order request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - Token account associated with this trade
|
||||
TradeRequest(TradeRequestInfo),
|
||||
|
||||
/// Trade cancellation
|
||||
/// order cancellation
|
||||
/// key 0 - Signer
|
||||
/// key 1 -Trade order to cancel
|
||||
/// key 1 -order to cancel
|
||||
TradeCancellation,
|
||||
|
||||
/// Trade swap request
|
||||
/// key 0 - Signer
|
||||
/// key 1 - Account in which to record the swap
|
||||
/// key 2 - 'To' trade order
|
||||
/// key 3 - `From` trade order
|
||||
/// key 2 - 'To' order
|
||||
/// key 3 - `From` order
|
||||
/// key 4 - Token account associated with the To Trade
|
||||
/// key 5 - Token account associated with From trade
|
||||
/// key 6 - Token account in which to deposit the Swappers profit from the swap.
|
||||
@@ -478,6 +478,3 @@ To also see the cluster messages:
|
||||
```bash
|
||||
$ RUST_LOG=solana_bench_exchange=info,solana=info cargo test --release -- --nocapture test_exchange_local_cluster
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@@ -20,9 +20,12 @@ use solana_sdk::system_instruction;
|
||||
use solana_sdk::timing::{duration_as_ms, duration_as_s};
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use std::cmp;
|
||||
use std::collections::VecDeque;
|
||||
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};
|
||||
@@ -48,6 +51,8 @@ pub struct Config {
|
||||
pub batch_size: usize,
|
||||
pub chunk_size: usize,
|
||||
pub account_groups: usize,
|
||||
pub client_ids_and_stake_file: String,
|
||||
pub read_from_client_file: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -61,10 +66,38 @@ impl Default for Config {
|
||||
batch_size: 10,
|
||||
chunk_size: 10,
|
||||
account_groups: 100,
|
||||
client_ids_and_stake_file: String::new(),
|
||||
read_from_client_file: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_client_accounts_file(
|
||||
client_ids_and_stake_file: &str,
|
||||
batch_size: usize,
|
||||
account_groups: usize,
|
||||
fund_amount: u64,
|
||||
) {
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
const NUM_KEYPAIR_GROUPS: u64 = 2;
|
||||
let total_keys = accounts_in_groups as u64 * NUM_KEYPAIR_GROUPS;
|
||||
|
||||
let keypairs = generate_keypairs(total_keys);
|
||||
|
||||
let mut accounts = HashMap::new();
|
||||
keypairs.iter().for_each(|keypair| {
|
||||
accounts.insert(
|
||||
serde_json::to_string(&keypair.to_bytes().to_vec()).unwrap(),
|
||||
fund_amount,
|
||||
);
|
||||
});
|
||||
|
||||
let serialized = serde_yaml::to_string(&accounts).unwrap();
|
||||
let path = Path::new(&client_ids_and_stake_file);
|
||||
let mut file = File::create(path).unwrap();
|
||||
file.write_all(&serialized.into_bytes()).unwrap();
|
||||
}
|
||||
|
||||
pub fn do_bench_exchange<T>(clients: Vec<T>, config: Config)
|
||||
where
|
||||
T: 'static + Client + Send + Sync,
|
||||
@@ -78,6 +111,8 @@ where
|
||||
batch_size,
|
||||
chunk_size,
|
||||
account_groups,
|
||||
client_ids_and_stake_file,
|
||||
read_from_client_file,
|
||||
} = config;
|
||||
|
||||
info!(
|
||||
@@ -92,35 +127,55 @@ where
|
||||
);
|
||||
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
let exit_signal = Arc::new(AtomicBool::new(false));
|
||||
const NUM_KEYPAIR_GROUPS: u64 = 2;
|
||||
let total_keys = accounts_in_groups as u64 * NUM_KEYPAIR_GROUPS;
|
||||
|
||||
let mut signer_keypairs = if read_from_client_file {
|
||||
let path = Path::new(&client_ids_and_stake_file);
|
||||
let file = File::open(path).unwrap();
|
||||
|
||||
let accounts: HashMap<String, u64> = serde_yaml::from_reader(file).unwrap();
|
||||
accounts
|
||||
.into_iter()
|
||||
.map(|(keypair, _)| {
|
||||
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
|
||||
Keypair::from_bytes(&bytes).unwrap()
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
info!("Generating {:?} signer keys", total_keys);
|
||||
generate_keypairs(total_keys)
|
||||
};
|
||||
|
||||
let trader_signers: Vec<_> = signer_keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(Arc::new)
|
||||
.collect();
|
||||
let swapper_signers: Vec<_> = signer_keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(Arc::new)
|
||||
.collect();
|
||||
|
||||
let clients: Vec<_> = clients.into_iter().map(Arc::new).collect();
|
||||
let client = clients[0].as_ref();
|
||||
|
||||
const NUM_KEYPAIR_GROUPS: u64 = 4;
|
||||
let total_keys = accounts_in_groups as u64 * NUM_KEYPAIR_GROUPS;
|
||||
info!("Generating {:?} keys", total_keys);
|
||||
let mut keypairs = generate_keypairs(total_keys);
|
||||
let trader_signers: Vec<_> = keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(Arc::new)
|
||||
.collect();
|
||||
let swapper_signers: Vec<_> = keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(Arc::new)
|
||||
.collect();
|
||||
let src_pubkeys: Vec<_> = keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(|keypair| keypair.pubkey())
|
||||
.collect();
|
||||
let profit_pubkeys: Vec<_> = keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(|keypair| keypair.pubkey())
|
||||
.collect();
|
||||
|
||||
if !read_from_client_file {
|
||||
info!("Fund trader accounts");
|
||||
fund_keys(client, &identity, &trader_signers, fund_amount);
|
||||
info!("Fund swapper accounts");
|
||||
fund_keys(client, &identity, &swapper_signers, fund_amount);
|
||||
}
|
||||
|
||||
info!("Generating {:?} account keys", total_keys);
|
||||
let mut account_keypairs = generate_keypairs(total_keys);
|
||||
let src_pubkeys: Vec<_> = account_keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(|keypair| keypair.pubkey())
|
||||
.collect();
|
||||
let profit_pubkeys: Vec<_> = account_keypairs
|
||||
.drain(0..accounts_in_groups)
|
||||
.map(|keypair| keypair.pubkey())
|
||||
.collect();
|
||||
|
||||
info!("Create {:?} source token accounts", src_pubkeys.len());
|
||||
create_token_accounts(client, &trader_signers, &src_pubkeys);
|
||||
@@ -136,6 +191,7 @@ where
|
||||
transfer_delay
|
||||
);
|
||||
|
||||
let exit_signal = Arc::new(AtomicBool::new(false));
|
||||
let shared_txs: SharedTransactions = Arc::new(RwLock::new(VecDeque::new()));
|
||||
let total_txs_sent_count = Arc::new(AtomicUsize::new(0));
|
||||
let s_threads: Vec<_> = (0..threads)
|
||||
@@ -276,7 +332,7 @@ fn do_tx_transfers<T>(
|
||||
|
||||
struct TradeInfo {
|
||||
trade_account: Pubkey,
|
||||
order_info: TradeOrderInfo,
|
||||
order_info: OrderInfo,
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn swapper<T>(
|
||||
@@ -453,7 +509,7 @@ fn trader<T>(
|
||||
T: Client,
|
||||
{
|
||||
// TODO Hard coded for now
|
||||
let pair = TokenPair::AB;
|
||||
let pair = AssetPair::default();
|
||||
let tokens = 1;
|
||||
let price = 1000;
|
||||
let mut account_group: usize = 0;
|
||||
@@ -482,7 +538,7 @@ fn trader<T>(
|
||||
} else {
|
||||
Direction::To
|
||||
};
|
||||
let order_info = TradeOrderInfo {
|
||||
let order_info = OrderInfo {
|
||||
/// Owner of the trade order
|
||||
owner: Pubkey::default(), // don't care
|
||||
direction,
|
||||
@@ -590,6 +646,20 @@ where
|
||||
false
|
||||
}
|
||||
|
||||
fn verify_funding_transfer<T: SyncClient + ?Sized>(
|
||||
client: &T,
|
||||
tx: &Transaction,
|
||||
amount: u64,
|
||||
) -> bool {
|
||||
for a in &tx.message().account_keys[1..] {
|
||||
if client.get_balance(a).unwrap_or(0) >= amount {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn fund_keys(client: &Client, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
|
||||
let total = lamports * (dests.len() as u64 + 1);
|
||||
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
|
||||
@@ -647,6 +717,7 @@ pub fn fund_keys(client: &Client, source: &Keypair, dests: &[Arc<Keypair>], lamp
|
||||
.collect();
|
||||
|
||||
let mut retries = 0;
|
||||
let amount = chunk[0].1[0].1;
|
||||
while !to_fund_txs.is_empty() {
|
||||
let receivers = to_fund_txs
|
||||
.iter()
|
||||
@@ -675,7 +746,7 @@ pub fn fund_keys(client: &Client, source: &Keypair, dests: &[Arc<Keypair>], lamp
|
||||
let mut waits = 0;
|
||||
loop {
|
||||
sleep(Duration::from_millis(200));
|
||||
to_fund_txs.retain(|(_, tx)| !verify_transfer(client, &tx));
|
||||
to_fund_txs.retain(|(_, tx)| !verify_funding_transfer(client, &tx, amount));
|
||||
if to_fund_txs.is_empty() {
|
||||
break;
|
||||
}
|
||||
@@ -892,7 +963,7 @@ pub fn airdrop_lamports(client: &Client, drone_addr: &SocketAddr, id: &Keypair,
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana::gossip_service::{discover_cluster, get_clients};
|
||||
use solana::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana::local_cluster::{ClusterConfig, LocalCluster};
|
||||
use solana::validator::ValidatorConfig;
|
||||
use solana_drone::drone::run_local_drone;
|
||||
@@ -907,7 +978,6 @@ mod tests {
|
||||
solana_logger::setup();
|
||||
|
||||
const NUM_NODES: usize = 1;
|
||||
let validator_config = ValidatorConfig::default();
|
||||
|
||||
let mut config = Config::default();
|
||||
config.identity = Keypair::new();
|
||||
@@ -929,7 +999,7 @@ mod tests {
|
||||
let cluster = LocalCluster::new(&ClusterConfig {
|
||||
node_stakes: vec![100_000; NUM_NODES],
|
||||
cluster_lamports: 100_000_000_000_000,
|
||||
validator_config,
|
||||
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
|
||||
native_instruction_processors: [solana_exchange_program!()].to_vec(),
|
||||
..ClusterConfig::default()
|
||||
});
|
||||
@@ -952,25 +1022,20 @@ mod tests {
|
||||
exit(1);
|
||||
});
|
||||
|
||||
let clients = get_clients(&nodes);
|
||||
let (client, num_clients) = get_multi_client(&nodes);
|
||||
|
||||
if clients.len() < NUM_NODES {
|
||||
error!(
|
||||
"Error: Insufficient nodes discovered. Expecting {} or more",
|
||||
NUM_NODES
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
info!("clients: {}", num_clients);
|
||||
assert!(num_clients >= NUM_NODES);
|
||||
|
||||
const NUM_SIGNERS: u64 = 2;
|
||||
airdrop_lamports(
|
||||
&clients[0],
|
||||
&client,
|
||||
&drone_addr,
|
||||
&config.identity,
|
||||
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
|
||||
);
|
||||
|
||||
do_bench_exchange(clients, config);
|
||||
do_bench_exchange(vec![client], config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@@ -18,6 +18,9 @@ pub struct Config {
|
||||
pub batch_size: usize,
|
||||
pub chunk_size: usize,
|
||||
pub account_groups: usize,
|
||||
pub client_ids_and_stake_file: String,
|
||||
pub write_to_client_file: bool,
|
||||
pub read_from_client_file: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -34,6 +37,9 @@ impl Default for Config {
|
||||
batch_size: 100,
|
||||
chunk_size: 100,
|
||||
account_groups: 100,
|
||||
client_ids_and_stake_file: String::new(),
|
||||
write_to_client_file: false,
|
||||
read_from_client_file: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,6 +147,20 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
.default_value("10")
|
||||
.help("Number of account groups to cycle for each batch"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("write-client-keys")
|
||||
.long("write-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Generate client keys and stakes and write the list to YAML file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("read-client-keys")
|
||||
.long("read-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Read client keys and stakes from the YAML file"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
@@ -184,5 +204,15 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
args.account_groups = value_t!(matches.value_of("account-groups"), usize)
|
||||
.expect("Failed to parse account-groups");
|
||||
|
||||
if let Some(s) = matches.value_of("write-client-keys") {
|
||||
args.write_to_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
|
||||
if let Some(s) = matches.value_of("read-client-keys") {
|
||||
assert!(!args.write_to_client_file);
|
||||
args.read_from_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
args
|
||||
}
|
||||
|
@@ -6,9 +6,9 @@ pub mod order_book;
|
||||
#[macro_use]
|
||||
extern crate solana_exchange_program;
|
||||
|
||||
use crate::bench::{airdrop_lamports, do_bench_exchange, Config};
|
||||
use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_exchange, Config};
|
||||
use log::*;
|
||||
use solana::gossip_service::{discover_cluster, get_clients};
|
||||
use solana::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_sdk::signature::KeypairUtil;
|
||||
|
||||
fn main() {
|
||||
@@ -30,33 +30,12 @@ fn main() {
|
||||
batch_size,
|
||||
chunk_size,
|
||||
account_groups,
|
||||
client_ids_and_stake_file,
|
||||
write_to_client_file,
|
||||
read_from_client_file,
|
||||
..
|
||||
} = cli_config;
|
||||
|
||||
info!("Connecting to the cluster");
|
||||
let (nodes, _replicators) =
|
||||
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
|
||||
panic!("Failed to discover nodes");
|
||||
});
|
||||
|
||||
let clients = get_clients(&nodes);
|
||||
|
||||
info!("{} nodes found", clients.len());
|
||||
if clients.len() < num_nodes {
|
||||
panic!("Error: Insufficient nodes discovered");
|
||||
}
|
||||
|
||||
info!("Funding keypair: {}", identity.pubkey());
|
||||
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
const NUM_SIGNERS: u64 = 2;
|
||||
airdrop_lamports(
|
||||
&clients[0],
|
||||
&drone_addr,
|
||||
&identity,
|
||||
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
|
||||
);
|
||||
|
||||
let config = Config {
|
||||
identity,
|
||||
threads,
|
||||
@@ -66,7 +45,43 @@ fn main() {
|
||||
batch_size,
|
||||
chunk_size,
|
||||
account_groups,
|
||||
client_ids_and_stake_file,
|
||||
read_from_client_file,
|
||||
};
|
||||
|
||||
do_bench_exchange(clients, config);
|
||||
if write_to_client_file {
|
||||
create_client_accounts_file(
|
||||
&config.client_ids_and_stake_file,
|
||||
config.batch_size,
|
||||
config.account_groups,
|
||||
config.fund_amount,
|
||||
);
|
||||
} else {
|
||||
info!("Connecting to the cluster");
|
||||
let (nodes, _replicators) =
|
||||
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
|
||||
panic!("Failed to discover nodes");
|
||||
});
|
||||
|
||||
let (client, num_clients) = get_multi_client(&nodes);
|
||||
|
||||
info!("{} nodes found", num_clients);
|
||||
if num_clients < num_nodes {
|
||||
panic!("Error: Insufficient nodes discovered");
|
||||
}
|
||||
|
||||
if !read_from_client_file {
|
||||
info!("Funding keypair: {}", config.identity.pubkey());
|
||||
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
const NUM_SIGNERS: u64 = 2;
|
||||
airdrop_lamports(
|
||||
&client,
|
||||
&drone_addr,
|
||||
&config.identity,
|
||||
fund_amount * (accounts_in_groups + 1) as u64 * NUM_SIGNERS,
|
||||
);
|
||||
}
|
||||
do_bench_exchange(vec![client], config);
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ use std::{error, fmt};
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ToOrder {
|
||||
pub pubkey: Pubkey,
|
||||
pub info: TradeOrderInfo,
|
||||
pub info: OrderInfo,
|
||||
}
|
||||
|
||||
impl Ord for ToOrder {
|
||||
@@ -26,7 +26,7 @@ impl PartialOrd for ToOrder {
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct FromOrder {
|
||||
pub pubkey: Pubkey,
|
||||
pub info: TradeOrderInfo,
|
||||
pub info: OrderInfo,
|
||||
}
|
||||
|
||||
impl Ord for FromOrder {
|
||||
@@ -95,11 +95,7 @@ impl OrderBook {
|
||||
// pub fn cancel(&mut self, pubkey: Pubkey) -> Result<(), Box<dyn error::Error>> {
|
||||
// Ok(())
|
||||
// }
|
||||
pub fn push(
|
||||
&mut self,
|
||||
pubkey: Pubkey,
|
||||
info: TradeOrderInfo,
|
||||
) -> Result<(), Box<dyn error::Error>> {
|
||||
pub fn push(&mut self, pubkey: Pubkey, info: OrderInfo) -> Result<(), Box<dyn error::Error>> {
|
||||
check_trade(info.direction, info.tokens, info.price)?;
|
||||
match info.direction {
|
||||
Direction::To => {
|
||||
|
1
bench-streamer/.gitignore
vendored
1
bench-streamer/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/target/
|
||||
/farf/
|
||||
|
@@ -2,17 +2,17 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "0.15.0"
|
||||
version = "0.17.1"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
solana = { path = "../core", version = "0.15.0" }
|
||||
solana-logger = { path = "../logger", version = "0.15.0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.15.0" }
|
||||
solana = { path = "../core", version = "0.17.1" }
|
||||
solana-logger = { path = "../logger", version = "0.17.1" }
|
||||
solana-netutil = { path = "../netutil", version = "0.17.1" }
|
||||
|
||||
[features]
|
||||
cuda = ["solana/cuda"]
|
||||
erasure = []
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
use clap::{crate_description, crate_name, crate_version, App, Arg};
|
||||
use solana::packet::PacketsRecycler;
|
||||
use solana::packet::{Packet, Packets, BLOB_SIZE, PACKET_DATA_SIZE};
|
||||
use solana::result::Result;
|
||||
use solana::streamer::{receiver, PacketReceiver};
|
||||
@@ -16,7 +17,7 @@ fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
|
||||
let send = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
let mut msgs = Packets::default();
|
||||
msgs.packets.resize(10, Packet::default());
|
||||
for w in &mut msgs.packets {
|
||||
for w in msgs.packets.iter_mut() {
|
||||
w.meta.size = PACKET_DATA_SIZE;
|
||||
w.meta.set_addr(&addr);
|
||||
}
|
||||
@@ -74,6 +75,7 @@ fn main() -> Result<()> {
|
||||
|
||||
let mut read_channels = Vec::new();
|
||||
let mut read_threads = Vec::new();
|
||||
let recycler = PacketsRecycler::default();
|
||||
for _ in 0..num_sockets {
|
||||
let read = solana_netutil::bind_to(port, false).unwrap();
|
||||
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
|
||||
@@ -83,7 +85,13 @@ fn main() -> Result<()> {
|
||||
|
||||
let (s_reader, r_reader) = channel();
|
||||
read_channels.push(r_reader);
|
||||
read_threads.push(receiver(Arc::new(read), &exit, s_reader));
|
||||
read_threads.push(receiver(
|
||||
Arc::new(read),
|
||||
&exit,
|
||||
s_reader,
|
||||
recycler.clone(),
|
||||
"bench-streamer-test",
|
||||
));
|
||||
}
|
||||
|
||||
let t_producer1 = producer(&addr, exit.clone());
|
||||
|
1
bench-tps/.gitignore
vendored
1
bench-tps/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/target/
|
||||
/config/
|
||||
/config-local/
|
||||
/farf/
|
||||
|
@@ -2,25 +2,33 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "0.15.0"
|
||||
version = "0.17.1"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.1.4"
|
||||
clap = "2.33.0"
|
||||
log = "0.4.6"
|
||||
rayon = "1.0.3"
|
||||
serde_json = "1.0.39"
|
||||
solana = { path = "../core", version = "0.15.0" }
|
||||
solana-client = { path = "../client", version = "0.15.0" }
|
||||
solana-drone = { path = "../drone", version = "0.15.0" }
|
||||
solana-logger = { path = "../logger", version = "0.15.0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.15.0" }
|
||||
solana-netutil = { path = "../netutil", version = "0.15.0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.15.0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.15.0" }
|
||||
log = "0.4.7"
|
||||
rayon = "1.1.0"
|
||||
serde = "1.0.97"
|
||||
serde_derive = "1.0.97"
|
||||
serde_json = "1.0.40"
|
||||
serde_yaml = "0.8.9"
|
||||
solana = { path = "../core", version = "0.17.1" }
|
||||
solana-client = { path = "../client", version = "0.17.1" }
|
||||
solana-drone = { path = "../drone", version = "0.17.1" }
|
||||
solana-librapay-api = { path = "../programs/librapay_api", version = "0.17.1" }
|
||||
solana-logger = { path = "../logger", version = "0.17.1" }
|
||||
solana-metrics = { path = "../metrics", version = "0.17.1" }
|
||||
solana-measure = { path = "../measure", version = "0.17.1" }
|
||||
solana-netutil = { path = "../netutil", version = "0.17.1" }
|
||||
solana-runtime = { path = "../runtime", version = "0.17.1" }
|
||||
solana-sdk = { path = "../sdk", version = "0.17.1" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader_program", version = "0.17.1" }
|
||||
solana-move-loader-api = { path = "../programs/move_loader_api", version = "0.17.1" }
|
||||
|
||||
[features]
|
||||
cuda = ["solana/cuda"]
|
||||
erasure = []
|
||||
|
||||
|
@@ -1,13 +1,17 @@
|
||||
use solana_metrics;
|
||||
|
||||
use bincode;
|
||||
use log::*;
|
||||
use rayon::prelude::*;
|
||||
use solana::gen_keys::GenKeys;
|
||||
use solana_client::perf_utils::{sample_txs, SampleStats};
|
||||
use solana_drone::drone::request_airdrop_transaction;
|
||||
use solana_librapay_api::{create_genesis, upload_mint_program, upload_payment_program};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_metrics::datapoint_info;
|
||||
use solana_sdk::client::Client;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::system_instruction;
|
||||
use solana_sdk::system_transaction;
|
||||
@@ -17,7 +21,6 @@ use solana_sdk::transaction::Transaction;
|
||||
use std::cmp;
|
||||
use std::collections::VecDeque;
|
||||
use std::net::SocketAddr;
|
||||
use std::process::exit;
|
||||
use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::sleep;
|
||||
@@ -25,8 +28,17 @@ use std::thread::Builder;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
pub const MAX_SPENDS_PER_TX: usize = 4;
|
||||
pub const NUM_LAMPORTS_PER_ACCOUNT: u64 = 20;
|
||||
use solana_librapay_api::librapay_transaction;
|
||||
|
||||
pub const MAX_SPENDS_PER_TX: u64 = 4;
|
||||
pub const NUM_LAMPORTS_PER_ACCOUNT: u64 = 128;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BenchTpsError {
|
||||
AirdropFailure,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, BenchTpsError>;
|
||||
|
||||
pub type SharedTransactions = Arc<RwLock<VecDeque<Vec<(Transaction, u64)>>>>;
|
||||
|
||||
@@ -37,6 +49,7 @@ pub struct Config {
|
||||
pub duration: Duration,
|
||||
pub tx_count: usize,
|
||||
pub sustained: bool,
|
||||
pub use_move: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -48,15 +61,19 @@ impl Default for Config {
|
||||
duration: Duration::new(std::u64::MAX, 0),
|
||||
tx_count: 500_000,
|
||||
sustained: false,
|
||||
use_move: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type LibraKeys = (Keypair, Pubkey, Pubkey, Vec<Keypair>);
|
||||
|
||||
pub fn do_bench_tps<T>(
|
||||
clients: Vec<T>,
|
||||
config: Config,
|
||||
gen_keypairs: Vec<Keypair>,
|
||||
keypair0_balance: u64,
|
||||
libra_args: Option<LibraKeys>,
|
||||
) -> u64
|
||||
where
|
||||
T: 'static + Client + Send + Sync,
|
||||
@@ -68,6 +85,7 @@ where
|
||||
duration,
|
||||
tx_count,
|
||||
sustained,
|
||||
..
|
||||
} = config;
|
||||
|
||||
let clients: Vec<_> = clients.into_iter().map(Arc::new).collect();
|
||||
@@ -159,6 +177,7 @@ where
|
||||
&keypairs[len..],
|
||||
threads,
|
||||
reclaim_lamports_back_to_source_account,
|
||||
&libra_args,
|
||||
);
|
||||
// In sustained mode overlap the transfers with generation
|
||||
// this has higher average performance but lower peak performance
|
||||
@@ -215,6 +234,74 @@ fn metrics_submit_lamport_balance(lamport_balance: u64) {
|
||||
);
|
||||
}
|
||||
|
||||
fn generate_move_txs(
|
||||
source: &[Keypair],
|
||||
dest: &[Keypair],
|
||||
reclaim: bool,
|
||||
move_keypairs: &[Keypair],
|
||||
libra_pay_program_id: &Pubkey,
|
||||
libra_mint_id: &Pubkey,
|
||||
blockhash: &Hash,
|
||||
) -> Vec<(Transaction, u64)> {
|
||||
let count = move_keypairs.len() / 2;
|
||||
let source_move = &move_keypairs[..count];
|
||||
let dest_move = &move_keypairs[count..];
|
||||
let pairs: Vec<_> = if !reclaim {
|
||||
source_move
|
||||
.iter()
|
||||
.zip(dest_move.iter())
|
||||
.zip(source.iter())
|
||||
.collect()
|
||||
} else {
|
||||
dest_move
|
||||
.iter()
|
||||
.zip(source_move.iter())
|
||||
.zip(dest.iter())
|
||||
.collect()
|
||||
};
|
||||
|
||||
pairs
|
||||
.par_iter()
|
||||
.map(|((from, to), payer)| {
|
||||
(
|
||||
librapay_transaction::transfer(
|
||||
libra_pay_program_id,
|
||||
libra_mint_id,
|
||||
&payer,
|
||||
&from,
|
||||
&to.pubkey(),
|
||||
1,
|
||||
*blockhash,
|
||||
),
|
||||
timestamp(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn generate_system_txs(
|
||||
source: &[Keypair],
|
||||
dest: &[Keypair],
|
||||
reclaim: bool,
|
||||
blockhash: &Hash,
|
||||
) -> Vec<(Transaction, u64)> {
|
||||
let pairs: Vec<_> = if !reclaim {
|
||||
source.iter().zip(dest.iter()).collect()
|
||||
} else {
|
||||
dest.iter().zip(source.iter()).collect()
|
||||
};
|
||||
|
||||
pairs
|
||||
.par_iter()
|
||||
.map(|(from, to)| {
|
||||
(
|
||||
system_transaction::create_user_account(from, &to.pubkey(), 1, *blockhash),
|
||||
timestamp(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn generate_txs(
|
||||
shared_txs: &SharedTransactions,
|
||||
blockhash: &Hash,
|
||||
@@ -222,25 +309,31 @@ fn generate_txs(
|
||||
dest: &[Keypair],
|
||||
threads: usize,
|
||||
reclaim: bool,
|
||||
libra_args: &Option<LibraKeys>,
|
||||
) {
|
||||
let tx_count = source.len();
|
||||
println!("Signing transactions... {} (reclaim={})", tx_count, reclaim);
|
||||
let signing_start = Instant::now();
|
||||
|
||||
let pairs: Vec<_> = if !reclaim {
|
||||
source.iter().zip(dest.iter()).collect()
|
||||
} else {
|
||||
dest.iter().zip(source.iter()).collect()
|
||||
};
|
||||
let transactions: Vec<_> = pairs
|
||||
.par_iter()
|
||||
.map(|(id, keypair)| {
|
||||
(
|
||||
system_transaction::create_user_account(id, &keypair.pubkey(), 1, *blockhash),
|
||||
timestamp(),
|
||||
let transactions = if let Some((
|
||||
libra_genesis_keypair,
|
||||
libra_pay_program_id,
|
||||
_libra_mint_program_id,
|
||||
libra_keys,
|
||||
)) = libra_args
|
||||
{
|
||||
generate_move_txs(
|
||||
source,
|
||||
dest,
|
||||
reclaim,
|
||||
&libra_keys,
|
||||
libra_pay_program_id,
|
||||
&libra_genesis_keypair.pubkey(),
|
||||
blockhash,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
} else {
|
||||
generate_system_txs(source, dest, reclaim, blockhash)
|
||||
};
|
||||
|
||||
let duration = signing_start.elapsed();
|
||||
let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos());
|
||||
@@ -335,10 +428,17 @@ fn verify_funding_transfer<T: Client>(client: &T, tx: &Transaction, amount: u64)
|
||||
/// fund the dests keys by spending all of the source keys into MAX_SPENDS_PER_TX
|
||||
/// on every iteration. This allows us to replay the transfers because the source is either empty,
|
||||
/// or full
|
||||
pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Keypair], lamports: u64) {
|
||||
let total = lamports * dests.len() as u64;
|
||||
pub fn fund_keys<T: Client>(
|
||||
client: &T,
|
||||
source: &Keypair,
|
||||
dests: &[Keypair],
|
||||
total: u64,
|
||||
max_fee: u64,
|
||||
mut extra: u64,
|
||||
) {
|
||||
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
|
||||
let mut notfunded: Vec<&Keypair> = dests.iter().collect();
|
||||
let lamports_per_account = (total - (extra * max_fee)) / (notfunded.len() as u64 + 1);
|
||||
|
||||
println!("funding keys {}", dests.len());
|
||||
while !notfunded.is_empty() {
|
||||
@@ -346,12 +446,13 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Keypair], lam
|
||||
let mut to_fund = vec![];
|
||||
println!("creating from... {}", funded.len());
|
||||
for f in &mut funded {
|
||||
let max_units = cmp::min(notfunded.len(), MAX_SPENDS_PER_TX);
|
||||
let max_units = cmp::min(notfunded.len() as u64, MAX_SPENDS_PER_TX);
|
||||
if max_units == 0 {
|
||||
break;
|
||||
}
|
||||
let start = notfunded.len() - max_units;
|
||||
let per_unit = f.1 / (max_units as u64);
|
||||
let start = notfunded.len() - max_units as usize;
|
||||
let fees = if extra > 0 { max_fee } else { 0 };
|
||||
let per_unit = (f.1 - lamports_per_account - fees) / max_units;
|
||||
let moves: Vec<_> = notfunded[start..]
|
||||
.iter()
|
||||
.map(|k| (k.pubkey(), per_unit))
|
||||
@@ -363,6 +464,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Keypair], lam
|
||||
if !moves.is_empty() {
|
||||
to_fund.push((f.0, moves));
|
||||
}
|
||||
extra -= 1;
|
||||
}
|
||||
|
||||
// try to transfer a "few" at a time with recent blockhash
|
||||
@@ -377,13 +479,10 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Keypair], lam
|
||||
let mut to_fund_txs: Vec<_> = chunk
|
||||
.par_iter()
|
||||
.map(|(k, m)| {
|
||||
(
|
||||
k.clone(),
|
||||
Transaction::new_unsigned_instructions(system_instruction::transfer_many(
|
||||
&k.pubkey(),
|
||||
&m,
|
||||
)),
|
||||
)
|
||||
let tx = Transaction::new_unsigned_instructions(
|
||||
system_instruction::transfer_many(&k.pubkey(), &m),
|
||||
);
|
||||
(k.clone(), tx)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -442,7 +541,7 @@ pub fn airdrop_lamports<T: Client>(
|
||||
drone_addr: &SocketAddr,
|
||||
id: &Keypair,
|
||||
tx_count: u64,
|
||||
) {
|
||||
) -> Result<()> {
|
||||
let starting_balance = client.get_balance(&id.pubkey()).unwrap_or(0);
|
||||
metrics_submit_lamport_balance(starting_balance);
|
||||
println!("starting balance {}", starting_balance);
|
||||
@@ -491,9 +590,10 @@ pub fn airdrop_lamports<T: Client>(
|
||||
current_balance,
|
||||
starting_balance
|
||||
);
|
||||
exit(1);
|
||||
return Err(BenchTpsError::AirdropFailure);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_and_report_stats(
|
||||
@@ -570,31 +670,172 @@ fn should_switch_directions(num_lamports_per_account: u64, i: u64) -> bool {
|
||||
i % (num_lamports_per_account / 4) == 0 && (i >= (3 * num_lamports_per_account) / 4)
|
||||
}
|
||||
|
||||
pub fn generate_keypairs(seed_keypair: &Keypair, count: usize) -> Vec<Keypair> {
|
||||
pub fn generate_keypairs(seed_keypair: &Keypair, count: u64) -> (Vec<Keypair>, u64) {
|
||||
let mut seed = [0u8; 32];
|
||||
seed.copy_from_slice(&seed_keypair.to_bytes()[..32]);
|
||||
let mut rnd = GenKeys::new(seed);
|
||||
|
||||
let mut total_keys = 0;
|
||||
let mut target = count;
|
||||
while target > 1 {
|
||||
total_keys += target;
|
||||
// Use the upper bound for this division otherwise it may not generate enough keys
|
||||
target = (target + MAX_SPENDS_PER_TX - 1) / MAX_SPENDS_PER_TX;
|
||||
let mut extra = 0; // This variable tracks the number of keypairs needing extra transaction fees funded
|
||||
let mut delta = 1;
|
||||
while total_keys < count {
|
||||
extra += delta;
|
||||
delta *= MAX_SPENDS_PER_TX;
|
||||
total_keys += delta;
|
||||
}
|
||||
rnd.gen_n_keypairs(total_keys as u64)
|
||||
(rnd.gen_n_keypairs(total_keys), extra)
|
||||
}
|
||||
|
||||
fn fund_move_keys<T: Client>(
|
||||
client: &T,
|
||||
funding_key: &Keypair,
|
||||
keypairs: &[Keypair],
|
||||
total: u64,
|
||||
libra_pay_program_id: &Pubkey,
|
||||
libra_mint_program_id: &Pubkey,
|
||||
libra_mint_key: &Keypair,
|
||||
) {
|
||||
let (mut blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
|
||||
|
||||
info!("creating the libra funding account..");
|
||||
let libra_funding_key = Keypair::new();
|
||||
let tx = librapay_transaction::create_account(
|
||||
funding_key,
|
||||
&libra_funding_key.pubkey(),
|
||||
1,
|
||||
blockhash,
|
||||
);
|
||||
let sig = client
|
||||
.async_send_transaction(tx)
|
||||
.expect("create_account in generate_and_fund_keypairs");
|
||||
client.poll_for_signature(&sig).unwrap();
|
||||
|
||||
info!("minting to funding keypair");
|
||||
let tx = librapay_transaction::mint_tokens(
|
||||
&libra_mint_program_id,
|
||||
funding_key,
|
||||
libra_mint_key,
|
||||
&libra_funding_key.pubkey(),
|
||||
total,
|
||||
blockhash,
|
||||
);
|
||||
let sig = client
|
||||
.async_send_transaction(tx)
|
||||
.expect("create_account in generate_and_fund_keypairs");
|
||||
client.poll_for_signature(&sig).unwrap();
|
||||
|
||||
info!("creating move accounts.. {}", keypairs.len());
|
||||
let create_len = 8;
|
||||
let mut funding_time = Measure::start("funding_time");
|
||||
for (i, keys) in keypairs.chunks(create_len).enumerate() {
|
||||
if client.get_balance(&keys[0].pubkey()).unwrap_or(0) > 0 {
|
||||
// already created these accounts.
|
||||
break;
|
||||
}
|
||||
|
||||
let mut tx_send = Measure::start("poll");
|
||||
|
||||
let pubkeys: Vec<_> = keys.iter().map(|k| k.pubkey()).collect();
|
||||
let tx = librapay_transaction::create_accounts(funding_key, &pubkeys, 1, blockhash);
|
||||
let ser_size = bincode::serialized_size(&tx).unwrap();
|
||||
let sig = client
|
||||
.async_send_transaction(tx)
|
||||
.expect("create_account in generate_and_fund_keypairs");
|
||||
tx_send.stop();
|
||||
let mut poll = Measure::start("poll");
|
||||
client.poll_for_signature(&sig).unwrap();
|
||||
poll.stop();
|
||||
if i % 10 == 0 {
|
||||
blockhash = client.get_recent_blockhash().unwrap().0;
|
||||
info!(
|
||||
"size: {} created {} accounts of {} sig: {}us send: {}us",
|
||||
ser_size,
|
||||
i,
|
||||
(keypairs.len() / create_len),
|
||||
poll.as_us(),
|
||||
tx_send.as_us()
|
||||
);
|
||||
}
|
||||
}
|
||||
funding_time.stop();
|
||||
info!("funding accounts {}ms", funding_time.as_ms());
|
||||
let mut sigs = vec![];
|
||||
let tx_count = keypairs.len();
|
||||
let amount = total / (tx_count as u64);
|
||||
for (i, key) in keypairs[..tx_count].iter().enumerate() {
|
||||
let tx = librapay_transaction::transfer(
|
||||
libra_pay_program_id,
|
||||
&libra_mint_key.pubkey(),
|
||||
funding_key,
|
||||
&libra_funding_key,
|
||||
&key.pubkey(),
|
||||
amount,
|
||||
blockhash,
|
||||
);
|
||||
|
||||
let sig = client
|
||||
.async_send_transaction(tx.clone())
|
||||
.expect("create_account in generate_and_fund_keypairs");
|
||||
|
||||
let mut poll_time = Measure::start("poll_start");
|
||||
let poll_status = client.poll_for_signature(&sig);
|
||||
poll_time.stop();
|
||||
info!(
|
||||
"i: {} poll: {:?} time: {}ms",
|
||||
i,
|
||||
poll_status,
|
||||
poll_time.as_ms()
|
||||
);
|
||||
|
||||
sigs.push((sig, key));
|
||||
|
||||
if i % 50 == 0 {
|
||||
blockhash = client.get_recent_blockhash().unwrap().0;
|
||||
}
|
||||
}
|
||||
|
||||
for (i, (sig, key)) in sigs.iter().enumerate() {
|
||||
let mut times = 0;
|
||||
loop {
|
||||
match client.poll_for_signature(&sig) {
|
||||
Ok(_) => {
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
info!("e :{:?} waiting times: {} sig: {}", e, times, sig);
|
||||
times += 1;
|
||||
sleep(Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
times = 0;
|
||||
loop {
|
||||
let balance = librapay_transaction::get_libra_balance(client, &key.pubkey()).unwrap();
|
||||
if balance < amount {
|
||||
info!("i: {} balance: {} times: {}", i, balance, times);
|
||||
times += 1;
|
||||
sleep(Duration::from_secs(1));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if i % 10 == 0 {
|
||||
info!("funding {} of {}", i, tx_count);
|
||||
}
|
||||
}
|
||||
info!("done..");
|
||||
}
|
||||
|
||||
pub fn generate_and_fund_keypairs<T: Client>(
|
||||
client: &T,
|
||||
drone_addr: Option<SocketAddr>,
|
||||
funding_pubkey: &Keypair,
|
||||
funding_key: &Keypair,
|
||||
tx_count: usize,
|
||||
lamports_per_account: u64,
|
||||
) -> (Vec<Keypair>, u64) {
|
||||
use_move: bool,
|
||||
) -> Result<(Vec<Keypair>, Option<LibraKeys>, u64)> {
|
||||
info!("Creating {} keypairs...", tx_count * 2);
|
||||
let mut keypairs = generate_keypairs(funding_pubkey, tx_count * 2);
|
||||
|
||||
let (mut keypairs, extra) = generate_keypairs(funding_key, tx_count as u64 * 2);
|
||||
info!("Get lamports...");
|
||||
|
||||
// Sample the first keypair, see if it has lamports, if so then resume.
|
||||
@@ -603,24 +844,76 @@ pub fn generate_and_fund_keypairs<T: Client>(
|
||||
.get_balance(&keypairs[tx_count * 2 - 1].pubkey())
|
||||
.unwrap_or(0);
|
||||
|
||||
let mut move_keypairs_ret = None;
|
||||
|
||||
if lamports_per_account > last_keypair_balance {
|
||||
let extra = lamports_per_account - last_keypair_balance;
|
||||
let total = extra * (keypairs.len() as u64);
|
||||
if client.get_balance(&funding_pubkey.pubkey()).unwrap_or(0) < total {
|
||||
airdrop_lamports(client, &drone_addr.unwrap(), funding_pubkey, total);
|
||||
let (_blockhash, fee_calculator) = client.get_recent_blockhash().unwrap();
|
||||
let account_desired_balance =
|
||||
lamports_per_account - last_keypair_balance + fee_calculator.max_lamports_per_signature;
|
||||
let extra_fees = extra * fee_calculator.max_lamports_per_signature;
|
||||
let mut total = account_desired_balance * (1 + keypairs.len() as u64) + extra_fees;
|
||||
if use_move {
|
||||
total *= 2;
|
||||
}
|
||||
info!("adding more lamports {}", extra);
|
||||
fund_keys(client, funding_pubkey, &keypairs, extra);
|
||||
|
||||
println!("Previous key balance: {} max_fee: {} lamports_per_account: {} extra: {} desired_balance: {} total: {}",
|
||||
last_keypair_balance, fee_calculator.max_lamports_per_signature, lamports_per_account, extra,
|
||||
account_desired_balance, total
|
||||
);
|
||||
|
||||
if client.get_balance(&funding_key.pubkey()).unwrap_or(0) < total {
|
||||
airdrop_lamports(client, &drone_addr.unwrap(), funding_key, total)?;
|
||||
}
|
||||
|
||||
if use_move {
|
||||
let libra_genesis_keypair = create_genesis(&funding_key, client, 1_000_000);
|
||||
let libra_mint_program_id = upload_mint_program(&funding_key, client);
|
||||
let libra_pay_program_id = upload_payment_program(&funding_key, client);
|
||||
|
||||
// Generate another set of keypairs for move accounts.
|
||||
// Still fund the solana ones which will be used for fees.
|
||||
let seed = [0u8; 32];
|
||||
let mut rnd = GenKeys::new(seed);
|
||||
let move_keypairs = rnd.gen_n_keypairs(tx_count as u64 * 2);
|
||||
fund_move_keys(
|
||||
client,
|
||||
funding_key,
|
||||
&move_keypairs,
|
||||
total / 2,
|
||||
&libra_pay_program_id,
|
||||
&libra_mint_program_id,
|
||||
&libra_genesis_keypair,
|
||||
);
|
||||
move_keypairs_ret = Some((
|
||||
libra_genesis_keypair,
|
||||
libra_pay_program_id,
|
||||
libra_mint_program_id,
|
||||
move_keypairs,
|
||||
));
|
||||
|
||||
// Give solana keys half and move keys half the lamports.
|
||||
total /= 2;
|
||||
}
|
||||
|
||||
fund_keys(
|
||||
client,
|
||||
funding_key,
|
||||
&keypairs,
|
||||
total,
|
||||
fee_calculator.max_lamports_per_signature,
|
||||
extra,
|
||||
);
|
||||
}
|
||||
|
||||
// 'generate_keypairs' generates extra keys to be able to have size-aligned funding batches for fund_keys.
|
||||
keypairs.truncate(2 * tx_count);
|
||||
|
||||
(keypairs, last_keypair_balance)
|
||||
Ok((keypairs, move_keypairs_ret, last_keypair_balance))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use solana::cluster_info::FULLNODE_PORT_RANGE;
|
||||
use solana::local_cluster::{ClusterConfig, LocalCluster};
|
||||
@@ -630,6 +923,7 @@ mod tests {
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_runtime::bank_client::BankClient;
|
||||
use solana_sdk::client::SyncClient;
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use solana_sdk::genesis_block::create_genesis_block;
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
@@ -648,47 +942,68 @@ mod tests {
|
||||
assert_eq!(should_switch_directions(20, 101), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bench_tps_local_cluster() {
|
||||
fn test_bench_tps_local_cluster(config: Config) {
|
||||
solana_logger::setup();
|
||||
let validator_config = ValidatorConfig::default();
|
||||
const NUM_NODES: usize = 1;
|
||||
let cluster = LocalCluster::new(&ClusterConfig {
|
||||
node_stakes: vec![999_990; NUM_NODES],
|
||||
cluster_lamports: 2_000_000,
|
||||
validator_config,
|
||||
cluster_lamports: 200_000_000,
|
||||
validator_configs: vec![ValidatorConfig::default(); NUM_NODES],
|
||||
native_instruction_processors: vec![solana_move_loader_program!()],
|
||||
..ClusterConfig::default()
|
||||
});
|
||||
|
||||
let drone_keypair = Keypair::new();
|
||||
cluster.transfer(&cluster.funding_keypair, &drone_keypair.pubkey(), 1_000_000);
|
||||
|
||||
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();
|
||||
|
||||
let mut config = Config::default();
|
||||
config.tx_count = 100;
|
||||
config.duration = Duration::from_secs(5);
|
||||
cluster.transfer(
|
||||
&cluster.funding_keypair,
|
||||
&drone_keypair.pubkey(),
|
||||
100_000_000,
|
||||
);
|
||||
|
||||
let client = create_client(
|
||||
(cluster.entry_point_info.rpc, cluster.entry_point_info.tpu),
|
||||
FULLNODE_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();
|
||||
|
||||
let lamports_per_account = 100;
|
||||
let (keypairs, _keypair_balance) = generate_and_fund_keypairs(
|
||||
|
||||
let (keypairs, move_keypairs, _keypair_balance) = generate_and_fund_keypairs(
|
||||
&client,
|
||||
Some(drone_addr),
|
||||
&config.id,
|
||||
config.tx_count,
|
||||
lamports_per_account,
|
||||
);
|
||||
config.use_move,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let total = do_bench_tps(vec![client], config, keypairs, 0);
|
||||
let total = do_bench_tps(vec![client], config, keypairs, 0, move_keypairs);
|
||||
assert!(total > 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bench_tps_local_cluster_solana() {
|
||||
let mut config = Config::default();
|
||||
config.tx_count = 100;
|
||||
config.duration = Duration::from_secs(10);
|
||||
|
||||
test_bench_tps_local_cluster(config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bench_tps_local_cluster_move() {
|
||||
let mut config = Config::default();
|
||||
config.tx_count = 100;
|
||||
config.duration = Duration::from_secs(20);
|
||||
config.use_move = true;
|
||||
|
||||
test_bench_tps_local_cluster(config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bench_tps_bank_client() {
|
||||
let (genesis_block, id) = create_genesis_block(10_000);
|
||||
@@ -700,10 +1015,11 @@ mod tests {
|
||||
config.tx_count = 10;
|
||||
config.duration = Duration::from_secs(5);
|
||||
|
||||
let (keypairs, _keypair_balance) =
|
||||
generate_and_fund_keypairs(&clients[0], None, &config.id, config.tx_count, 20);
|
||||
let (keypairs, _move_keypairs, _keypair_balance) =
|
||||
generate_and_fund_keypairs(&clients[0], None, &config.id, config.tx_count, 20, false)
|
||||
.unwrap();
|
||||
|
||||
do_bench_tps(clients, config, keypairs, 0);
|
||||
do_bench_tps(clients, config, keypairs, 0, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -714,12 +1030,37 @@ mod tests {
|
||||
let tx_count = 10;
|
||||
let lamports = 20;
|
||||
|
||||
let (keypairs, _keypair_balance) =
|
||||
generate_and_fund_keypairs(&client, None, &id, tx_count, lamports);
|
||||
let (keypairs, _move_keypairs, _keypair_balance) =
|
||||
generate_and_fund_keypairs(&client, None, &id, tx_count, lamports, false).unwrap();
|
||||
|
||||
for kp in &keypairs {
|
||||
// TODO: This should be >= lamports, but fails at the moment
|
||||
assert_ne!(client.get_balance(&kp.pubkey()).unwrap(), 0);
|
||||
assert_eq!(client.get_balance(&kp.pubkey()).unwrap(), lamports);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bench_tps_fund_keys_with_fees() {
|
||||
let (mut genesis_block, id) = create_genesis_block(10_000);
|
||||
let fee_calculator = FeeCalculator::new(11);
|
||||
genesis_block.fee_calculator = fee_calculator;
|
||||
let bank = Bank::new(&genesis_block);
|
||||
let client = BankClient::new(bank);
|
||||
let tx_count = 10;
|
||||
let lamports = 20;
|
||||
|
||||
let (keypairs, _move_keypairs, _keypair_balance) =
|
||||
generate_and_fund_keypairs(&client, None, &id, tx_count, lamports, false).unwrap();
|
||||
|
||||
let max_fee = client
|
||||
.get_recent_blockhash()
|
||||
.unwrap()
|
||||
.1
|
||||
.max_lamports_per_signature;
|
||||
for kp in &keypairs {
|
||||
assert_eq!(
|
||||
client.get_balance(&kp.pubkey()).unwrap(),
|
||||
lamports + max_fee
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ use std::time::Duration;
|
||||
|
||||
use clap::{crate_description, crate_name, crate_version, App, Arg, ArgMatches};
|
||||
use solana_drone::drone::DRONE_PORT;
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
|
||||
|
||||
/// Holds the configuration for a single run of the benchmark
|
||||
@@ -17,6 +18,11 @@ pub struct Config {
|
||||
pub tx_count: 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 use_move: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -31,6 +37,11 @@ impl Default for Config {
|
||||
tx_count: 500_000,
|
||||
thread_batch_sleep_ms: 0,
|
||||
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,
|
||||
use_move: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,6 +102,11 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
.long("sustained")
|
||||
.help("Use sustained performance mode vs. peak mode. This overlaps the tx generation with transfers."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("use-move")
|
||||
.long("use-move")
|
||||
.help("Use Move language transactions to perform transfers."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("tx_count")
|
||||
.long("tx_count")
|
||||
@@ -106,6 +122,30 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
|
||||
.takes_value(true)
|
||||
.help("Per-thread-per-iteration sleep in ms"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("write-client-keys")
|
||||
.long("write-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Generate client keys and stakes and write the list to YAML file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("read-client-keys")
|
||||
.long("read-client-keys")
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("Read client keys and stakes from the YAML file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("target_lamports_per_signature")
|
||||
.long("target-lamports-per-signature")
|
||||
.value_name("LAMPORTS")
|
||||
.takes_value(true)
|
||||
.help(
|
||||
"The cost in lamports that the cluster will charge for signature \
|
||||
verification when the cluster is operating at target-signatures-per-slot",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// Parses a clap `ArgMatches` structure into a `Config`
|
||||
@@ -163,5 +203,22 @@ pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
|
||||
|
||||
args.sustained = matches.is_present("sustained");
|
||||
|
||||
if let Some(s) = matches.value_of("write-client-keys") {
|
||||
args.write_to_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
|
||||
if let Some(s) = matches.value_of("read-client-keys") {
|
||||
assert!(!args.write_to_client_file);
|
||||
args.read_from_client_file = true;
|
||||
args.client_ids_and_stake_file = s.to_string();
|
||||
}
|
||||
|
||||
if let Some(v) = matches.value_of("target_lamports_per_signature") {
|
||||
args.target_lamports_per_signature = v.to_string().parse().expect("can't parse lamports");
|
||||
}
|
||||
|
||||
args.use_move = matches.is_present("use-move");
|
||||
|
||||
args
|
||||
}
|
||||
|
@@ -1,10 +1,25 @@
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate solana_move_loader_program;
|
||||
|
||||
mod bench;
|
||||
mod cli;
|
||||
|
||||
use crate::bench::{do_bench_tps, generate_and_fund_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT};
|
||||
use solana::gossip_service::{discover_cluster, get_clients};
|
||||
use crate::bench::{
|
||||
do_bench_tps, generate_and_fund_keypairs, generate_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT,
|
||||
};
|
||||
use solana::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
|
||||
/// 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();
|
||||
solana_metrics::set_panic_hook("bench-tps");
|
||||
@@ -22,15 +37,45 @@ fn main() {
|
||||
tx_count,
|
||||
thread_batch_sleep_ms,
|
||||
sustained,
|
||||
client_ids_and_stake_file,
|
||||
write_to_client_file,
|
||||
read_from_client_file,
|
||||
target_lamports_per_signature,
|
||||
use_move,
|
||||
} = cli_config;
|
||||
|
||||
if write_to_client_file {
|
||||
let (keypairs, _) = generate_keypairs(&id, tx_count as u64 * 2);
|
||||
let num_accounts = keypairs.len() as u64;
|
||||
let max_fee = FeeCalculator::new(target_lamports_per_signature).max_lamports_per_signature;
|
||||
let num_lamports_per_account = (num_accounts - 1 + NUM_SIGNATURES_FOR_TXS * max_fee)
|
||||
/ num_accounts
|
||||
+ NUM_LAMPORTS_PER_ACCOUNT;
|
||||
let mut accounts = HashMap::new();
|
||||
keypairs.iter().for_each(|keypair| {
|
||||
accounts.insert(
|
||||
serde_json::to_string(&keypair.to_bytes().to_vec()).unwrap(),
|
||||
num_lamports_per_account,
|
||||
);
|
||||
});
|
||||
|
||||
let serialized = serde_yaml::to_string(&accounts).unwrap();
|
||||
let path = Path::new(&client_ids_and_stake_file);
|
||||
let mut file = File::create(path).unwrap();
|
||||
file.write_all(&serialized.into_bytes()).unwrap();
|
||||
return;
|
||||
}
|
||||
|
||||
println!("Connecting to the cluster");
|
||||
let (nodes, _replicators) =
|
||||
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|err| {
|
||||
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
|
||||
exit(1);
|
||||
});
|
||||
if nodes.len() < num_nodes {
|
||||
|
||||
let (client, num_clients) = get_multi_client(&nodes);
|
||||
|
||||
if nodes.len() < num_clients {
|
||||
eprintln!(
|
||||
"Error: Insufficient nodes discovered. Expecting {} or more",
|
||||
num_nodes
|
||||
@@ -38,15 +83,38 @@ fn main() {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let clients = get_clients(&nodes);
|
||||
let (keypairs, move_keypairs, keypair_balance) = if read_from_client_file && !use_move {
|
||||
let path = Path::new(&client_ids_and_stake_file);
|
||||
let file = File::open(path).unwrap();
|
||||
|
||||
let (keypairs, keypair_balance) = generate_and_fund_keypairs(
|
||||
&clients[0],
|
||||
let accounts: HashMap<String, u64> = serde_yaml::from_reader(file).unwrap();
|
||||
let mut keypairs = vec![];
|
||||
let mut last_balance = 0;
|
||||
|
||||
accounts.into_iter().for_each(|(keypair, balance)| {
|
||||
let bytes: Vec<u8> = serde_json::from_str(keypair.as_str()).unwrap();
|
||||
keypairs.push(Keypair::from_bytes(&bytes).unwrap());
|
||||
last_balance = balance;
|
||||
});
|
||||
// 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)
|
||||
} else {
|
||||
generate_and_fund_keypairs(
|
||||
&client,
|
||||
Some(drone_addr),
|
||||
&id,
|
||||
tx_count,
|
||||
NUM_LAMPORTS_PER_ACCOUNT,
|
||||
);
|
||||
use_move,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("Error could not fund keys: {:?}", e);
|
||||
exit(1);
|
||||
})
|
||||
};
|
||||
|
||||
let config = Config {
|
||||
id,
|
||||
@@ -55,7 +123,14 @@ fn main() {
|
||||
duration,
|
||||
tx_count,
|
||||
sustained,
|
||||
use_move,
|
||||
};
|
||||
|
||||
do_bench_tps(clients, config, keypairs, keypair_balance);
|
||||
do_bench_tps(
|
||||
vec![client],
|
||||
config,
|
||||
keypairs,
|
||||
keypair_balance,
|
||||
move_keypairs,
|
||||
);
|
||||
}
|
||||
|
18
book/art/spv-bank-merkle.bob
Normal file
18
book/art/spv-bank-merkle.bob
Normal file
@@ -0,0 +1,18 @@
|
||||
+------------+
|
||||
| Bank-Merkle|
|
||||
+------------+
|
||||
^ ^
|
||||
/ \
|
||||
+-----------------+ +-------------+
|
||||
| Bank-Diff-Merkle| | Block-Merkle|
|
||||
+-----------------+ +-------------+
|
||||
^ ^
|
||||
/ \
|
||||
+------+ +--------------------------+
|
||||
| Hash | | Previous Bank-Diff-Merkle|
|
||||
+------+ +--------------------------+
|
||||
^ ^
|
||||
/ \
|
||||
+---------------+ +---------------+
|
||||
| Hash(Account1)| | Hash(Account2)|
|
||||
+---------------+ +---------------+
|
19
book/art/spv-block-merkle.bob
Normal file
19
book/art/spv-block-merkle.bob
Normal file
@@ -0,0 +1,19 @@
|
||||
+---------------+
|
||||
| Block-Merkle |
|
||||
+---------------+
|
||||
^ ^
|
||||
/ \
|
||||
+-------------+ +-------------+
|
||||
| Entry-Merkle| | Entry-Merkle|
|
||||
+-------------+ +-------------+
|
||||
^ ^
|
||||
/ \
|
||||
+-------+ +-------+
|
||||
| Hash | | Hash |
|
||||
+-------+ +-------+
|
||||
^ ^ ^ ^
|
||||
/ | | \
|
||||
+-----------------+ +-----------------+ +-----------------+ +---+
|
||||
| Hash(T1, status)| | Hash(T2, status)| | Hash(T3, status)| | 0 |
|
||||
+-----------------+ +-----------------+ +-----------------+ +---+
|
||||
|
@@ -3,4 +3,4 @@ set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
make -j"$(nproc)"
|
||||
make -j"$(nproc)" test
|
||||
|
@@ -4,11 +4,14 @@ MD_SRCS=$(wildcard src/*.md)
|
||||
|
||||
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/img/%.svg) $(MSC_SRCS:art/%.msc=src/img/%.svg)
|
||||
|
||||
all: html/index.html
|
||||
TARGET=html/index.html
|
||||
TEST_STAMP=src/tests.ok
|
||||
|
||||
test: src/tests.ok
|
||||
all: $(TARGET)
|
||||
|
||||
open: all
|
||||
test: $(TEST_STAMP)
|
||||
|
||||
open: $(TEST_STAMP)
|
||||
mdbook build --open
|
||||
|
||||
watch: $(SVG_IMGS)
|
||||
@@ -26,11 +29,11 @@ src/%.md: %.md
|
||||
@mkdir -p $(@D)
|
||||
@cp $< $@
|
||||
|
||||
src/tests.ok: $(SVG_IMGS) $(MD_SRCS)
|
||||
$(TEST_STAMP): $(TARGET)
|
||||
mdbook test
|
||||
touch $@
|
||||
|
||||
html/index.html: src/tests.ok
|
||||
$(TARGET): $(SVG_IMGS) $(MD_SRCS)
|
||||
mdbook build
|
||||
|
||||
clean:
|
||||
|
@@ -5,6 +5,8 @@
|
||||
- [Terminology](terminology.md)
|
||||
|
||||
- [Getting Started](getting-started.md)
|
||||
- [Testnet Participation](testnet-participation.md)
|
||||
- [Testnet Replicator](testnet-replicator.md)
|
||||
- [Example: Web Wallet](webwallet.md)
|
||||
|
||||
- [Programming Model](programs.md)
|
||||
@@ -16,7 +18,7 @@
|
||||
- [Leader Rotation](leader-rotation.md)
|
||||
- [Fork Generation](fork-generation.md)
|
||||
- [Managing Forks](managing-forks.md)
|
||||
- [Data Plane Fanout](data-plane-fanout.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)
|
||||
@@ -29,7 +31,11 @@
|
||||
- [Gossip Service](gossip.md)
|
||||
- [The Runtime](runtime.md)
|
||||
|
||||
- [Anatomy of a Transaction](transaction.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)
|
||||
@@ -53,17 +59,21 @@
|
||||
- [Economic Design MVP](ed_mvp.md)
|
||||
- [References](ed_references.md)
|
||||
- [Cluster Test Framework](cluster-test-framework.md)
|
||||
- [Credit-only Accounts](credit-only-credit-debit-accounts.md)
|
||||
- [Deterministic Transaction Fees](transaction-fees.md)
|
||||
- [Validator](validator-proposal.md)
|
||||
- [Simple Payment and State Verification](simple-payment-and-state-verification.md)
|
||||
- [Cross-Program Invocation](cross-program-invocation.md)
|
||||
|
||||
- [Implemented Design Proposals](implemented-proposals.md)
|
||||
- [Fork Selection](fork-selection.md)
|
||||
- [Blocktree](blocktree.md)
|
||||
- [Cluster Software Installation and Updates](installer.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)
|
||||
- [Testnet Participation](testnet-participation.md)
|
||||
- [Testing Programs](testing-programs.md)
|
||||
- [Reliable Vote Transmission](reliable-vote-transmission.md)
|
||||
- [Persistent Account Storage](persistent-account-storage.md)
|
||||
- [Cluster Software Installation and Updates](installer.md)
|
||||
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.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)
|
||||
|
@@ -4,7 +4,7 @@ A validator votes on a PoH hash for two purposes. First, the vote indicates it
|
||||
believes the ledger is valid up until that point in time. Second, since many
|
||||
valid forks may exist at a given height, the vote also indicates exclusive
|
||||
support for the fork. This document describes only the former. The latter is
|
||||
described in [fork selection](fork-selection.md).
|
||||
described in [Tower BFT](tower-bft.md).
|
||||
|
||||
## Current Design
|
||||
|
||||
@@ -50,12 +50,11 @@ log the time since the NewBlock transaction was submitted.
|
||||
|
||||
### Finality and Payouts
|
||||
|
||||
Locktower is the proposed [fork selection](fork-selection.md) algorithm. It
|
||||
proposes that payment to miners be postponed until the *stack* of validator
|
||||
votes reaches a certain depth, at which point rollback is not economically
|
||||
feasible. The vote program may therefore implement locktower. Vote instructions
|
||||
would need to reference a global locktower account so that it can track
|
||||
cross-block state.
|
||||
[Tower BFT](tower-bft.md) is the proposed fork selection algorithm. It proposes
|
||||
that payment to miners be postponed until the *stack* of validator votes reaches
|
||||
a certain depth, at which point rollback is not economically feasible. The vote
|
||||
program may therefore implement Tower BFT. Vote instructions would need to
|
||||
reference a global Tower account so that it can track cross-block state.
|
||||
|
||||
## Challenges
|
||||
|
||||
|
111
book/src/cross-program-invocation.md
Normal file
111
book/src/cross-program-invocation.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# 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.
|
66
book/src/embedding-move.md
Normal file
66
book/src/embedding-move.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# 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.
|
@@ -55,7 +55,7 @@ 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
|
||||
[forks selection](fork-selection.md).
|
||||
[Tower BFT](tower-bft.md).
|
||||
|
||||
### Validator's View
|
||||
|
||||
|
@@ -161,7 +161,7 @@ This will dump all the threads stack traces into gdb.txt
|
||||
In this example the client connects to our public testnet. To run validators on the testnet you would need to open udp ports `8000-10000`.
|
||||
|
||||
```bash
|
||||
$ ./multinode-demo/client.sh --entrypoint testnet.solana.com:8001 --duration 60
|
||||
$ ./multinode-demo/client.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)
|
||||
|
@@ -22,7 +22,7 @@ gossip endpoint (a socket address).
|
||||
|
||||
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
|
||||
recieves two records from the same source, it it updates its own copy with the
|
||||
receives two records from the same source, it updates its own copy with the
|
||||
record with the most recent timestamp.
|
||||
|
||||
## Gossip Service Interface
|
||||
@@ -34,8 +34,8 @@ 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 responds with
|
||||
`PushMessagePrune` and drops the message
|
||||
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
|
||||
@@ -51,7 +51,7 @@ Upon receiving a push message, a node examines the message for:
|
||||
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, faster path to that node than direct push.
|
||||
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.
|
||||
|
@@ -12,18 +12,18 @@ updates is managed using an on-chain update manifest program.
|
||||
#### Fetch and run a pre-built installer using a bootstrap curl/shell script
|
||||
The easiest install method for supported platforms:
|
||||
```bash
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.13.0/install/solana-install-init.sh | sh
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.0/install/solana-install-init.sh | sh
|
||||
```
|
||||
|
||||
This script will check github for the latest tagged release and download and run the
|
||||
`solana-install` binary from there.
|
||||
`solana-install-init` binary from there.
|
||||
|
||||
|
||||
If additional arguments need to be specified during the installation, the
|
||||
following shell syntax is used:
|
||||
```bash
|
||||
$ init_args=.... # arguments for `solana-installer init ...`
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.13.0/install/solana-install-init.sh | sh -s - ${init_args}
|
||||
$ init_args=.... # arguments for `solana-install-init ...`
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.0/install/solana-install-init.sh | sh -s - ${init_args}
|
||||
```
|
||||
|
||||
#### Fetch and run a pre-built installer from a Github release
|
||||
@@ -31,9 +31,9 @@ With a well-known release URL, a pre-built binary can be obtained for supported
|
||||
platforms:
|
||||
|
||||
```bash
|
||||
$ curl -o solana-install https://github.com/solana-labs/solana/releases/download/v0.13.0/solana-install-x86_64-apple-darwin
|
||||
$ chmod +x ./solana-install
|
||||
$ ./solana-install --help
|
||||
$ curl -o solana-install-init https://github.com/solana-labs/solana/releases/download/v0.16.0/solana-install-init-x86_64-apple-darwin
|
||||
$ chmod +x ./solana-install-init
|
||||
$ ./solana-install-init --help
|
||||
```
|
||||
|
||||
#### Build and run the installer from source
|
||||
@@ -49,7 +49,7 @@ $ cargo run -- --help
|
||||
Given a solana release tarball (as created by `ci/publish-tarball.sh`) that has already been uploaded to a publicly accessible URL,
|
||||
the following commands will deploy the update:
|
||||
```bash
|
||||
$ solana-keygen -o update-manifest.json # <-- only generated once, the public key is shared with users
|
||||
$ solana-keygen new -o update-manifest.json # <-- only generated once, the public key is shared with users
|
||||
$ solana-install deploy http://example.com/path/to/solana-release.tar.bz2 update-manifest.json
|
||||
```
|
||||
|
||||
@@ -119,7 +119,7 @@ It manages the following files and directories in the user's home directory:
|
||||
|
||||
#### Command-line Interface
|
||||
```manpage
|
||||
solana-install 0.13.0
|
||||
solana-install 0.16.0
|
||||
The solana cluster software installer
|
||||
|
||||
USAGE:
|
||||
@@ -153,7 +153,7 @@ FLAGS:
|
||||
|
||||
OPTIONS:
|
||||
-d, --data_dir <PATH> Directory to store install data [default: /Users/mvines/Library/Application Support/solana]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster [default: https://api.testnet.solana.com/]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster [default: http://testnet.solana.com:8899]
|
||||
-p, --pubkey <PUBKEY> Public key of the update manifest [default: 9XX329sPuskWhH4DQh6k16c87dHKhXLBZTL3Gxmve8Gp]
|
||||
```
|
||||
|
||||
|
25
book/src/instruction-api.md
Normal file
25
book/src/instruction-api.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 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,13 +1,13 @@
|
||||
# What is Solana?
|
||||
|
||||
Solana is the name of an open source project that is implementing a new,
|
||||
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 it's useful, how to use it, how it
|
||||
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
|
||||
|
@@ -25,11 +25,18 @@ Methods
|
||||
* [getAccountInfo](#getaccountinfo)
|
||||
* [getBalance](#getbalance)
|
||||
* [getClusterNodes](#getclusternodes)
|
||||
* [getEpochInfo](#getepochinfo)
|
||||
* [getLeaderSchedule](#getleaderschedule)
|
||||
* [getProgramAccounts](#getprogramaccounts)
|
||||
* [getRecentBlockhash](#getrecentblockhash)
|
||||
* [getSignatureStatus](#getsignaturestatus)
|
||||
* [getSlotLeader](#getslotleader)
|
||||
* [getSlotsPerSegment](#getslotspersegment)
|
||||
* [getStorageTurn](#getstorageturn)
|
||||
* [getStorageTurnRate](#getstorageturnrate)
|
||||
* [getNumBlocksSinceSignatureConfirmation](#getnumblockssincesignatureconfirmation)
|
||||
* [getTransactionCount](#gettransactioncount)
|
||||
* [getTotalSupply](#gettotalsupply)
|
||||
* [getEpochVoteAccounts](#getepochvoteaccounts)
|
||||
* [requestAirdrop](#requestairdrop)
|
||||
* [sendTransaction](#sendtransaction)
|
||||
@@ -95,6 +102,32 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
|
||||
{"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
|
||||
@@ -125,7 +158,7 @@ None
|
||||
|
||||
##### Results:
|
||||
The result field will be an array of JSON objects, each with the following sub fields:
|
||||
* `id` - Node identifier, as base-58 encoded string
|
||||
* `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
|
||||
@@ -136,33 +169,78 @@ The result field will be an array of JSON objects, each with the following sub f
|
||||
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","id":"9QzsJf7LPLj8GkXbYT3LFDKqsj2hHG7TA3xinJHu8epQ","rpc":"10.239.6.48:8899","tpu":"10.239.6.48:8856"}],"id":1}
|
||||
{"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}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getAccountInfo
|
||||
Returns all information associated with the account of provided Pubkey
|
||||
### getEpochInfo
|
||||
Returns information about the current epoch
|
||||
|
||||
##### Parameters:
|
||||
* `string` - Pubkey of account to query, as base-58 encoded string
|
||||
None
|
||||
|
||||
##### Results:
|
||||
The result field will be a JSON object with the following sub fields:
|
||||
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}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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` - a 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)
|
||||
* `loader`, array of 32 bytes representing the loader for this program (if `executable`), otherwise all
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899
|
||||
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":{"executable":false,"loader":[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],"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}
|
||||
{"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}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -233,7 +311,67 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
||||
{"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.
|
||||
@@ -275,6 +413,26 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
||||
|
||||
---
|
||||
|
||||
### 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}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### getEpochVoteAccounts
|
||||
Returns the account info and associated stake for all the voting accounts in the current epoch.
|
||||
|
||||
@@ -282,19 +440,11 @@ Returns the account info and associated stake for all the voting accounts in the
|
||||
None
|
||||
|
||||
##### Results:
|
||||
An array consisting of vote accounts:
|
||||
* `string` - the vote account's Pubkey as base-58 encoded string
|
||||
* `integer` - the stake, in lamports, delegated to this vote account
|
||||
* `VoteState` - the vote account's state
|
||||
|
||||
Each VoteState will be a JSON object with the following sub fields:
|
||||
|
||||
* `votes`, array of most recent vote lockouts
|
||||
* `node_pubkey`, the pubkey of the node that votes using this account
|
||||
* `authorized_voter_pubkey`, the pubkey of the authorized vote signer for this account
|
||||
The result field will be an array of JSON objects, each with the following sub fields:
|
||||
* `votePubkey` - Vote account public key, as base-58 encoded string
|
||||
* `nodePubkey` - Node public key, as base-58 encoded string
|
||||
* `stake` - the stake, in lamports, delegated to this vote account
|
||||
* `commission`, a 32-bit integer used as a fraction (commission/MAX_U32) for rewards payout
|
||||
* `root_slot`, the most recent slot this account has achieved maximum lockout
|
||||
* `credits`, credits accrued by this account for reaching lockouts
|
||||
|
||||
##### Example:
|
||||
```bash
|
||||
@@ -302,7 +452,7 @@ Each VoteState will be a JSON object with the following sub fields:
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochVoteAccounts"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":[[[84,115,89,23,41,83,221,72,58,23,53,245,195,188,140,161,242,189,200,164,139,214,12,180,84,161,28,151,24,243,159,125],10000000,{"authorized_voter_pubkey":[84,115,89,23,41,83,221,72,58,23,53,245,195,188,140,161,242,189,200,164,139,214,12,180,84,161,28,151,24,243,159,125],"commission":0,"credits":0,"node_pubkey":[49,139,227,211,47,39,69,86,131,244,160,144,228,169,84,143,142,253,83,81,212,110,254,12,242,71,219,135,30,60,157,213],"root_slot":null,"votes":[{"confirmation_count":1,"slot":0}]}]],"id":1}
|
||||
{"jsonrpc":"2.0","result":[{"commission":0,"nodePubkey":"Et2RaZJdJRTzTkodUwiHr4H6sLkVmijBFv8tkd7oSSFY","stake":42,"votePubkey":"B4CdWq3NBSoH2wYsVE1CaZSWPo2ZtopE4SJipQhZ3srF"}],"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -389,7 +539,7 @@ for a given account public key changes
|
||||
|
||||
##### Notification Format:
|
||||
```bash
|
||||
{"jsonrpc": "2.0","method": "accountNotification", "params": {"result": {"executable":false,"loader":[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],"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}}
|
||||
{"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}}
|
||||
```
|
||||
|
||||
---
|
||||
|
@@ -45,7 +45,7 @@ The upsides compared to guards:
|
||||
* 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 avalanche performance.
|
||||
heuristic can take into account turbine performance.
|
||||
|
||||
* This design doesn't require a ledger hard fork to update.
|
||||
|
||||
|
@@ -96,7 +96,7 @@ 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
|
||||
[fork selection](fork-selection.md).
|
||||
[Tower BFT](tower-bft.md).
|
||||
|
||||
## Leader Schedule Generation Algorithm
|
||||
|
||||
|
@@ -74,7 +74,7 @@ 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/locktower state.
|
||||
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.
|
||||
|
@@ -1,19 +1,18 @@
|
||||
# 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 full nodes that have
|
||||
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 network.
|
||||
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 its hashed. The simple solution is to force
|
||||
the hash to be done on the reverse of the encryption, or perhaps with a random
|
||||
order. This ensures that all the data is present during the generation of the
|
||||
proof and it also requires the validator 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`
|
||||
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
|
||||
|
||||
@@ -29,13 +28,12 @@ core. The total space required for verification is `1_ledger_segment +
|
||||
## Network
|
||||
|
||||
Validators for PoRep are the same validators that are verifying transactions.
|
||||
They have some stake that they have put up as collateral that ensures that
|
||||
their work is honest. If you can prove that a validator verified a fake PoRep,
|
||||
then the validator will not receive a reward for that storage epoch.
|
||||
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
|
||||
and store it, and provide PoReps of storing the ledger. For each verified PoRep
|
||||
replicators earn a reward of sol from the mining pool.
|
||||
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
|
||||
|
||||
@@ -55,9 +53,8 @@ changes to determine what rate it can validate storage proofs.
|
||||
|
||||
1. SLOTS\_PER\_SEGMENT: Number of slots in a segment of ledger data. The
|
||||
unit of storage for a replicator.
|
||||
2. NUM\_KEY\_ROTATION\_TICKS: Number of ticks to save a PoH value and cause a
|
||||
key generation for the section of ledger just generated and the rotation of
|
||||
another key in the set.
|
||||
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
|
||||
@@ -66,36 +63,40 @@ mining proof claim has to contain to be valid for a reward.
|
||||
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. Validator joins the network and submits a storage validation capacity
|
||||
transaction which tells the network how many proofs it can process in a given
|
||||
period defined by NUM\_KEY\_ROTATION\_TICKS.
|
||||
2. Every NUM\_KEY\_ROTATION\_TICKS the validator stores the PoH value at that
|
||||
height.
|
||||
3. Validator generates a storage proof confirmation transaction.
|
||||
4. The storage proof confirmation transaction is integrated into the ledger.
|
||||
6. Validator responds to RPC interfaces for what the last storage epoch PoH
|
||||
value is and its slot.
|
||||
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 full nodes (validators) 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:
|
||||
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 subscribe to the full transaction stream and generate
|
||||
the information itself
|
||||
- (d) replicator can subscribe to an abbreviated transaction stream to
|
||||
generate the information itself
|
||||
2. A replicator obtains the PoH hash corresponding to the last key rotation
|
||||
along with its slot.
|
||||
- (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
|
||||
@@ -103,38 +104,67 @@ 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 signature from step 2 as
|
||||
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
|
||||
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 transaction stream for storage mining proofs, and
|
||||
keep a mapping of ledger segments by slot to public keys. When it sees
|
||||
a storage mining proof it updates this mapping and provides an RPC interface
|
||||
which takes a slot and hands back a list of public keys. The client
|
||||
then looks up in their cluster\_info table to see which network address that
|
||||
corresponds to and sends a repair request to retrieve the necessary blocks of
|
||||
ledger.
|
||||
2. Validators would need to prune this list which it could do by periodically
|
||||
looking at the oldest entries in its mappings and doing a network query to see
|
||||
if the storage host is still serving the first entry.
|
||||
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. Everyone must use 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.
|
||||
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
|
||||
@@ -155,8 +185,7 @@ the network can reward long lived client identities more than new ones.
|
||||
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 in a stake-weighted fashion to catch bad actors and
|
||||
replicators from being locked out of the network.
|
||||
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
|
||||
|
@@ -76,21 +76,24 @@ this field can only modified by this entity
|
||||
|
||||
### StakeState
|
||||
|
||||
A StakeState takes one of two forms, StakeState::Delegate and StakeState::MiningPool.
|
||||
A StakeState takes one of two forms, StakeState::Stake and StakeState::MiningPool.
|
||||
|
||||
### StakeState::Delegate
|
||||
### StakeState::Stake
|
||||
|
||||
StakeState is the current delegation preference of the **staker**. StakeState
|
||||
Stake is the current delegation preference of the **staker**. Stake
|
||||
contains the following state information:
|
||||
|
||||
* Account::lamports - The staked lamports.
|
||||
|
||||
* `voter_pubkey` - The pubkey of the VoteState instance the lamports are
|
||||
delegated to.
|
||||
|
||||
* `credits_observed` - The total credits claimed over the lifetime of the
|
||||
program.
|
||||
|
||||
* `stake` - The actual activated stake.
|
||||
|
||||
* Account::lamports - Lamports available for staking, including any earned as rewards.
|
||||
|
||||
|
||||
### StakeState::MiningPool
|
||||
|
||||
There are two approaches to the mining pool. The bank could allow the
|
||||
@@ -105,11 +108,12 @@ tokens stored as `Account::lamports`.
|
||||
The stakes and the MiningPool are accounts that are owned by the same `Stake`
|
||||
program.
|
||||
|
||||
### StakeInstruction::Initialize
|
||||
### StakeInstruction::DelegateStake(stake)
|
||||
|
||||
* `account[0]` - RW - The StakeState::Delegate instance.
|
||||
`StakeState::Delegate::credits_observed` is initialized to `VoteState::credits`.
|
||||
`StakeState::Delegate::voter_pubkey` is initialized to `account[1]`
|
||||
* `account[0]` - RW - The StakeState::Stake instance.
|
||||
`StakeState::Stake::credits_observed` is initialized to `VoteState::credits`.
|
||||
`StakeState::Stake::voter_pubkey` is initialized to `account[1]`
|
||||
`StakeState::Stake::stake` is initialized to `stake`, as long as it's less than account[0].lamports
|
||||
|
||||
* `account[1]` - R - The VoteState instance.
|
||||
|
||||
@@ -124,7 +128,7 @@ deposited into the StakeState and as validator commission is proportional to
|
||||
|
||||
* `account[0]` - RW - The StakeState::MiningPool instance that will fulfill the
|
||||
reward.
|
||||
* `account[1]` - RW - The StakeState::Delegate instance that is redeeming votes
|
||||
* `account[1]` - RW - The StakeState::Stake instance that is redeeming votes
|
||||
credits.
|
||||
* `account[2]` - R - The VoteState instance, must be the same as
|
||||
`StakeState::voter_pubkey`
|
||||
@@ -132,7 +136,7 @@ credits.
|
||||
Reward is payed out for the difference between `VoteState::credits` to
|
||||
`StakeState::Delgate.credits_observed`, and `credits_observed` is updated to
|
||||
`VoteState::credits`. The commission is deposited into the `VoteState` token
|
||||
balance, and the reward is deposited to the `StakeState::Delegate` token balance. The
|
||||
balance, and the reward is deposited to the `StakeState::Stake` token balance. The
|
||||
reward and the commission is weighted by the `StakeState::lamports` divided by total lamports staked.
|
||||
|
||||
The Staker or the owner of the Stake program sends a transaction with this
|
||||
@@ -146,7 +150,7 @@ stake_state.credits_observed = vote_state.credits;
|
||||
```
|
||||
|
||||
`credits_to_claim` is used to compute the reward and commission, and
|
||||
`StakeState::Delegate::credits_observed` is updated to the latest
|
||||
`StakeState::Stake::credits_observed` is updated to the latest
|
||||
`VoteState::credits` value.
|
||||
|
||||
### Collecting network fees into the MiningPool
|
||||
@@ -175,13 +179,13 @@ many rewards to be claimed concurrently.
|
||||
|
||||
## Passive Delegation
|
||||
|
||||
Any number of instances of StakeState::Delegate programs can delegate to a single
|
||||
Any number of instances of StakeState::Stake programs can delegate to a single
|
||||
VoteState program without an interactive action from the identity controlling
|
||||
the VoteState program or submitting votes to the program.
|
||||
|
||||
The total stake allocated to a VoteState program can be calculated by the sum of
|
||||
all the StakeState programs that have the VoteState pubkey as the
|
||||
`StakeState::Delegate::voter_pubkey`.
|
||||
`StakeState::Stake::voter_pubkey`.
|
||||
|
||||
## Example Callflow
|
||||
|
||||
|
@@ -60,7 +60,7 @@ The read is satisfied by pointing to a memory-mapped location in the
|
||||
|
||||
## Root Forks
|
||||
|
||||
The [fork selection algorithm](fork-selection.md) eventually selects a fork as a
|
||||
[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
|
||||
|
172
book/src/simple-payment-and-state-verification.md
Normal file
172
book/src/simple-payment-and-state-verification.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Simple Payment and State Verification
|
||||
|
||||
It is often useful to allow low resourced clients to participate in a Solana
|
||||
cluster. Be this participation economic or contract execution, verification
|
||||
that a client's activity has been accepted by the network is typically
|
||||
expensive. This proposal lays out a mechanism for such clients to confirm that
|
||||
their actions have been committed to the ledger state with minimal resource
|
||||
expenditure and third-party trust.
|
||||
|
||||
## A Naive Approach
|
||||
|
||||
Validators store the signatures of recently confirmed transactions for a short
|
||||
period of time to ensure that they are not processed more than once. Validators
|
||||
provide a JSON RPC endpoint, which clients can use to query the cluster if a
|
||||
transaction has been recently processed. Validators also provide a PubSub
|
||||
notification, whereby a client registers to be notified when a given signature
|
||||
is observed by the validator. While these two mechanisms allow a client to
|
||||
verify a payment, they are not a proof and rely on completely trusting a
|
||||
fullnode.
|
||||
|
||||
We will describe a way to minimize this trust using Merkle Proofs to anchor the
|
||||
fullnode's response in the ledger, allowing the client to confirm on their own
|
||||
that a sufficient number of their preferred validators have confirmed a
|
||||
transaction. Requiring multiple validator attestations further reduces trust in
|
||||
the fullnode, as it increases both the technical and economic difficulty of
|
||||
compromising several other network participants.
|
||||
|
||||
## Light Clients
|
||||
|
||||
A 'light client' is a cluster participant that does not itself run a fullnode.
|
||||
This light client would provide a level of security greater than trusting a
|
||||
remote fullnode, without requiring the light client to spend a lot of resources
|
||||
verifying the ledger.
|
||||
|
||||
Rather than providing transaction signatures directly to a light client, the
|
||||
fullnode instead generates a Merkle Proof from the transaction of interest to
|
||||
the root of a Merkle Tree of all transactions in the including block. This Merkle
|
||||
Root is stored in a ledger entry which is voted on by validators, providing it
|
||||
consensus legitimacy. The additional level of security for a light client depends
|
||||
on an initial canonical set of validators the light client considers to be the
|
||||
stakeholders of the cluster. As that set is changed, the client can update its
|
||||
internal set of known validators with [receipts](#receipts). This may become
|
||||
challenging with a large number of delegated stakes.
|
||||
|
||||
Fullnodes themselves may want to use light client APIs for performance reasons.
|
||||
For example, during the initial launch of a fullnode, the fullnode may use a
|
||||
cluster provided checkpoint of the state and verify it with a receipt.
|
||||
|
||||
## Receipts
|
||||
|
||||
A receipt is a minimal proof that; a transaction has been included in a block,
|
||||
that the block has been voted on by the client's preferred set of validators and
|
||||
that the votes have reached the desired confirmation depth.
|
||||
|
||||
The receipts for both state and payments start with a Merkle Path from the
|
||||
value into a Bank-Merkle that has been voted on and included in the ledger. A
|
||||
chain of PoH Entries containing subsequent validator votes, deriving from the
|
||||
Bank-Merkle, is the confirmation proof.
|
||||
|
||||
Clients can examine this ledger data and compute the finality using Solana's fork
|
||||
selection rules.
|
||||
|
||||
### Payment Merkle Path
|
||||
|
||||
A payment receipt is a data structure that contains a Merkle Path from a
|
||||
transaction to the required set of validator votes.
|
||||
|
||||
An Entry-Merkle is a Merkle Root including all transactions in the entry, sorted
|
||||
by signature.
|
||||
|
||||
<img alt="Block Merkle Diagram" src="img/spv-block-merkle.svg" class="center"/>
|
||||
|
||||
A Block-Merkle is a Merkle root of all the Entry-Merkles sequenced in the block.
|
||||
Transaction status is necessary for the receipt because the state receipt is
|
||||
constructed for the block. Two transactions over the same state can appear in
|
||||
the block, and therefore, there is no way to infer from just the state whether a
|
||||
transaction that is committed to the ledger has succeeded or failed in modifying
|
||||
the intended state. It may not be necessary to encode the full status code, but
|
||||
a single status bit to indicate the transaction's success.
|
||||
|
||||
### State Merkle Path
|
||||
|
||||
A state receipt provides a confirmation that a specific state is committed at the
|
||||
end of the block. Inter-block state transitions do not generate a receipt.
|
||||
|
||||
For example:
|
||||
|
||||
* A sends 5 Lamports to B
|
||||
* B spends 5 Lamports
|
||||
* C sends 5 Lamports to A
|
||||
|
||||
At the end of the block, A and B are in the exact same starting state, and any
|
||||
state receipt would point to the same value for A or B.
|
||||
|
||||
The Bank-Merkle is computed from the Merkle Tree of the new state changes, along
|
||||
with the Previous Bank-Merkle, and the Block-Merkle.
|
||||
|
||||
<img alt="Bank Merkle Diagram" src="img/spv-bank-merkle.svg" class="center"/>
|
||||
|
||||
A state receipt contains only the state changes occurring in the block. A direct
|
||||
Merkle Path to the current Bank-Merkle guarantees the state value at that bank
|
||||
hash, but it cannot be used to generate a “current” receipt to the latest state
|
||||
if the state modification occurred in some previous block. There is no guarantee
|
||||
that the path provided by the validator is the latest one available out of all
|
||||
the previous Bank-Merkles.
|
||||
|
||||
Clients that want to query the chain for a receipt of the "latest" state would
|
||||
need to create a transaction that would update the Merkle Path for that account,
|
||||
such as a credit of 0 Lamports.
|
||||
|
||||
### Validator Votes
|
||||
|
||||
Leaders should coalesce the validator votes by stake weight into a single entry.
|
||||
This will reduce the number of entries necessary to create a receipt.
|
||||
|
||||
### Chain of Entries
|
||||
|
||||
A receipt has a PoH link from the payment or state Merkle Path root to a list of
|
||||
consecutive validation votes.
|
||||
|
||||
It contains the following:
|
||||
* State -> Bank-Merkle
|
||||
or
|
||||
* Transaction -> Entry-Merkle -> Block-Merkle -> Bank-Merkle
|
||||
|
||||
And a vector of PoH entries:
|
||||
|
||||
* Validator vote entries
|
||||
* Ticks
|
||||
* Light entries
|
||||
|
||||
|
||||
```rust,ignore
|
||||
/// This Entry definition skips over the transactions and only contains the
|
||||
/// hash of the transactions used to modify PoH.
|
||||
LightEntry {
|
||||
/// The number of hashes since the previous Entry ID.
|
||||
pub num_hashes: u64,
|
||||
/// The SHA-256 hash `num_hashes` after the previous Entry ID.
|
||||
hash: Hash,
|
||||
/// The Merkle Root of the transactions encoded into the Entry.
|
||||
entry_hash: Hash,
|
||||
}
|
||||
```
|
||||
|
||||
The light entries are reconstructed from Entries and simply show the entry Merkle
|
||||
Root that was mixed in to the PoH hash, instead of the full transaction set.
|
||||
|
||||
Clients do not need the starting vote state. The [fork selection](book/src/fork-selection.md) algorithm is
|
||||
defined such that only votes that appear after the transaction provide finality
|
||||
for the transaction, and finality is independent of the starting state.
|
||||
|
||||
### Verification
|
||||
|
||||
A light client that is aware of the supermajority set validators can verify a
|
||||
receipt by following the Merkle Path to the PoH chain. The Bank-Merkle is the
|
||||
Merkle Root and will appear in votes included in an Entry. The light client can
|
||||
simulate [fork selection](book/src/fork-selection.md) for the consecutive votes
|
||||
and verify that the receipt is confirmed at the desired lockout threshold.
|
||||
|
||||
### Synthetic State
|
||||
|
||||
Synthetic state should be computed into the Bank-Merkle along with the bank
|
||||
generated state.
|
||||
|
||||
For example:
|
||||
|
||||
* Epoch validator accounts and their stakes and weights.
|
||||
* Computed fee rates
|
||||
|
||||
These values should have an entry in the Bank-Merkle. They should live under
|
||||
known accounts, and therefore have an exact address in the Merkle Path.
|
@@ -11,7 +11,7 @@ of getting its stake slashed. The economics are covered in [staking
|
||||
rewards](staking-rewards.md). This chapter, on the other hand, describes the
|
||||
underlying mechanics of its implementation.
|
||||
|
||||
## Basic Besign
|
||||
## Basic Design
|
||||
|
||||
The general idea is that the validator owns a Vote account. The Vote account
|
||||
tracks validator votes, counts validator generated credits, and provides any
|
||||
@@ -20,7 +20,7 @@ stakes delegated to it and has no staking weight.
|
||||
|
||||
A separate Stake account (created by a staker) names a Vote account to which the
|
||||
stake is delegated. Rewards generated are proportional to the amount of
|
||||
lamports staked. The Stake account is owned by the staker only. Lamports
|
||||
lamports staked. The Stake account is owned by the staker only. Some portion of the lamports
|
||||
stored in this account are the stake.
|
||||
|
||||
## Passive Delegation
|
||||
@@ -31,7 +31,7 @@ the Vote account or submitting votes to the account.
|
||||
|
||||
The total stake allocated to a Vote account can be calculated by the sum of
|
||||
all the Stake accounts that have the Vote account pubkey as the
|
||||
`StakeState::Delegate::voter_pubkey`.
|
||||
`StakeState::Stake::voter_pubkey`.
|
||||
|
||||
## Vote and Stake accounts
|
||||
|
||||
@@ -46,15 +46,15 @@ program that its delegate has participated in validating the ledger.
|
||||
VoteState is the current state of all the votes the validator has submitted to
|
||||
the network. VoteState contains the following state information:
|
||||
|
||||
* votes - The submitted votes data structure.
|
||||
* `votes` - The submitted votes data structure.
|
||||
|
||||
* credits - The total number of rewards this vote program has generated over its
|
||||
* `credits` - The total number of rewards this vote program has generated over its
|
||||
lifetime.
|
||||
|
||||
* root\_slot - The last slot to reach the full lockout commitment necessary for
|
||||
* `root_slot` - The last slot to reach the full lockout commitment necessary for
|
||||
rewards.
|
||||
|
||||
* commission - The commission taken by this VoteState for any rewards claimed by
|
||||
* `commission` - The commission taken by this VoteState for any rewards claimed by
|
||||
staker's Stake accounts. This is the percentage ceiling of the reward.
|
||||
|
||||
* Account::lamports - The accumulated lamports from the commission. These do not
|
||||
@@ -71,13 +71,17 @@ count as stakes.
|
||||
### VoteInstruction::AuthorizeVoteSigner(Pubkey)
|
||||
|
||||
* `account[0]` - RW - The VoteState
|
||||
`VoteState::authorized_vote_signer` is set to to `Pubkey`, instruction must by
|
||||
signed by Pubkey
|
||||
`VoteState::authorized_vote_signer` is set to to `Pubkey`, the transaction must by
|
||||
signed by the Vote account's current `authorized_vote_signer`. <br>
|
||||
`VoteInstruction::AuthorizeVoter` allows a staker to choose a signing service
|
||||
for its votes. That service is responsible for ensuring the vote won't cause
|
||||
the staker to be slashed.
|
||||
|
||||
|
||||
### VoteInstruction::Vote(Vec<Vote>)
|
||||
|
||||
* `account[0]` - RW - The VoteState
|
||||
`VoteState::lockouts` and `VoteState::credits` are updated according to voting lockout rules see [Fork Selection](fork-selection.md)
|
||||
`VoteState::lockouts` and `VoteState::credits` are updated according to voting lockout rules see [Tower BFT](tower-bft.md)
|
||||
|
||||
|
||||
* `account[1]` - RO - A list of some N most recent slots and their hashes for the vote to be verified against.
|
||||
@@ -85,14 +89,16 @@ count as stakes.
|
||||
|
||||
### StakeState
|
||||
|
||||
A StakeState takes one of two forms, StakeState::Delegate and StakeState::MiningPool.
|
||||
A StakeState takes one of three forms, StakeState::Uninitialized, StakeState::Stake and StakeState::RewardsPool.
|
||||
|
||||
### StakeState::Delegate
|
||||
### StakeState::Stake
|
||||
|
||||
StakeState is the current delegation preference of the **staker**. StakeState
|
||||
StakeState::Stake is the current delegation preference of the **staker** and
|
||||
contains the following state information:
|
||||
|
||||
* Account::lamports - The staked lamports.
|
||||
* Account::lamports - The lamports available for staking.
|
||||
|
||||
* `stake` - the staked amount (subject to warm up and cool down) for generating rewards, always less than or equal to Account::lamports
|
||||
|
||||
* `voter_pubkey` - The pubkey of the VoteState instance the lamports are
|
||||
delegated to.
|
||||
@@ -100,56 +106,53 @@ delegated to.
|
||||
* `credits_observed` - The total credits claimed over the lifetime of the
|
||||
program.
|
||||
|
||||
### StakeState::MiningPool
|
||||
* `activated` - the epoch at which this stake was activated/delegated. The full stake will be counted after warm up.
|
||||
|
||||
There are two approaches to the mining pool. The bank could allow the
|
||||
StakeState program to bypass the token balance check, or a program representing
|
||||
the mining pool could run on the network. To avoid a single network wide lock,
|
||||
the pool can be split into several mining pools. This design focuses on using
|
||||
StakeState::MiningPool instances as the cluster wide mining pools.
|
||||
* `deactivated` - the epoch at which this stake will be completely de-activated, which is `cool down` epochs after StakeInstruction::Deactivate is issued.
|
||||
|
||||
* 256 StakeState::MiningPool are initialized, each with 1/256 number of mining pool
|
||||
tokens stored as `Account::lamports`.
|
||||
### StakeState::RewardsPool
|
||||
|
||||
The stakes and the MiningPool are accounts that are owned by the same `Stake`
|
||||
program.
|
||||
To avoid a single network wide lock or contention in redemption, 256 RewardsPools are part of genesis under pre-determined keys, each with std::u64::MAX credits to be able to satisfy redemptions according to point value.
|
||||
|
||||
### StakeInstruction::Initialize
|
||||
The Stakes and the RewardsPool are accounts that are owned by the same `Stake` program.
|
||||
|
||||
* `account[0]` - RW - The StakeState::Delegate instance.
|
||||
`StakeState::Delegate::credits_observed` is initialized to `VoteState::credits`.
|
||||
`StakeState::Delegate::voter_pubkey` is initialized to `account[1]`
|
||||
### StakeInstruction::DelegateStake(u64)
|
||||
|
||||
The Stake account is moved from Uninitialized to StakeState::Stake form. This is
|
||||
how stakers choose their initial delegate validator node and activate their
|
||||
stake account lamports.
|
||||
|
||||
* `account[0]` - RW - The StakeState::Stake instance. <br>
|
||||
`StakeState::Stake::credits_observed` is initialized to `VoteState::credits`,<br>
|
||||
`StakeState::Stake::voter_pubkey` is initialized to `account[1]`,<br>
|
||||
`StakeState::Stake::stake` is initialized to the u64 passed as an argument above,<br>
|
||||
`StakeState::Stake::activated` is initialized to current Bank epoch, and<br>
|
||||
`StakeState::Stake::deactivated` is initialized to std::u64::MAX
|
||||
|
||||
* `account[1]` - R - The VoteState instance.
|
||||
|
||||
* `account[2]` - R - syscall::current account, carries information about current Bank epoch
|
||||
|
||||
### StakeInstruction::RedeemVoteCredits
|
||||
|
||||
The Staker or the owner of the Stake account sends a transaction with this
|
||||
The staker or the owner of the Stake account sends a transaction with this
|
||||
instruction to claim rewards.
|
||||
|
||||
The Vote account and the Stake account pair maintain a lifetime counter
|
||||
of total rewards generated and claimed. When claiming rewards, the total lamports
|
||||
deposited into the Stake account and as validator commission is proportional to
|
||||
`VoteState::credits - StakeState::credits_observed`.
|
||||
The Vote account and the Stake account pair maintain a lifetime counter of total
|
||||
rewards generated and claimed. Rewards are paid according to a point value
|
||||
supplied by the Bank from inflation. A `point` is one credit * one staked
|
||||
lamport, rewards paid are proportional to the number of lamports staked.
|
||||
|
||||
|
||||
* `account[0]` - RW - The StakeState::MiningPool instance that will fulfill the
|
||||
reward.
|
||||
* `account[1]` - RW - The StakeState::Delegate instance that is redeeming votes
|
||||
credits.
|
||||
* `account[2]` - R - The VoteState instance, must be the same as
|
||||
`StakeState::voter_pubkey`
|
||||
* `account[0]` - RW - The StakeState::Stake instance that is redeeming rewards.
|
||||
* `account[1]` - R - The VoteState instance, must be the same as `StakeState::voter_pubkey`
|
||||
* `account[2]` - RW - The StakeState::RewardsPool instance that will fulfill the request (picked at random).
|
||||
* `account[3]` - R - syscall::rewards account from the Bank that carries point value.
|
||||
|
||||
Reward is paid out for the difference between `VoteState::credits` to
|
||||
`StakeState::Delgate.credits_observed`, and `credits_observed` is updated to
|
||||
`VoteState::credits`. The commission is deposited into the Vote account token
|
||||
`StakeState::Stake::credits_observed`, multiplied by `syscall::rewards::Rewards::validator_point_value`.
|
||||
`StakeState::Stake::credits_observed` is updated to`VoteState::credits`. The commission is deposited into the Vote account token
|
||||
balance, and the reward is deposited to the Stake account token balance.
|
||||
|
||||
The total lamports paid is a percentage-rate of the lamports staked muiltplied by
|
||||
the ratio of rewards being redeemed to rewards that could have been generated
|
||||
during the rate period.
|
||||
|
||||
Any random MiningPool can be used to redeem the credits.
|
||||
|
||||
```rust,ignore
|
||||
let credits_to_claim = vote_state.credits - stake_state.credits_observed;
|
||||
@@ -157,24 +160,26 @@ stake_state.credits_observed = vote_state.credits;
|
||||
```
|
||||
|
||||
`credits_to_claim` is used to compute the reward and commission, and
|
||||
`StakeState::Delegate::credits_observed` is updated to the latest
|
||||
`StakeState::Stake::credits_observed` is updated to the latest
|
||||
`VoteState::credits` value.
|
||||
|
||||
## Collecting network fees into the MiningPool
|
||||
### StakeInstruction::Deactivate
|
||||
A staker may wish to withdraw from the network. To do so he must first deactivate his stake, and wait for cool down.
|
||||
|
||||
At the end of the block, before the bank is frozen, but after it processed all
|
||||
the transactions for the block, a virtual instruction is executed to collect
|
||||
the transaction fees.
|
||||
* `account[0]` - RW - The StakeState::Stake instance that is deactivating, the transaction must be signed by this key.
|
||||
* `account[1]` - R - syscall::current account from the Bank that carries current epoch
|
||||
|
||||
* A portion of the fees are deposited into the leader's account.
|
||||
* A portion of the fees are deposited into the smallest StakeState::MiningPool
|
||||
account.
|
||||
StakeState::Stake::deactivated is set to the current epoch + cool down. The account's stake will ramp down to zero by
|
||||
that epoch, and Account::lamports will be available for withdrawal.
|
||||
|
||||
## Authorizing a Vote Signer
|
||||
|
||||
`VoteInstruction::AuthorizeVoter` allows a staker to choose a signing service
|
||||
for its votes. That service is responsible for ensuring the vote won't cause
|
||||
the staker to be slashed.
|
||||
### StakeInstruction::Withdraw(u64)
|
||||
Lamports build up over time in a Stake account and any excess over activated stake can be withdrawn.
|
||||
|
||||
* `account[0]` - RW - The StakeState::Stake from which to withdraw, the transaction must be signed by this key.
|
||||
* `account[1]` - RW - Account that should be credited with the withdrawn lamports.
|
||||
* `account[2]` - R - syscall::current account from the Bank that carries current epoch, to calculate stake.
|
||||
|
||||
|
||||
## Benefits of the design
|
||||
|
||||
@@ -187,9 +192,6 @@ the staker to be slashed.
|
||||
* Commission for the work is deposited when a reward is claimed by the delegated
|
||||
stake.
|
||||
|
||||
This proposal would benefit from the `read-only` accounts proposal to allow for
|
||||
many rewards to be claimed concurrently.
|
||||
|
||||
## Example Callflow
|
||||
|
||||
<img alt="Passive Staking Callflow" src="img/passive-staking-callflow.svg" class="center"/>
|
||||
|
@@ -48,7 +48,7 @@ specific parameters will be necessary:
|
||||
|
||||
Solana's trustless sense of time and ordering provided by its PoH data
|
||||
structure, along with its
|
||||
[avalanche](https://www.youtube.com/watch?v=qt_gDRXHrHQ&t=1s) data broadcast
|
||||
[turbine](https://www.youtube.com/watch?v=qt_gDRXHrHQ&t=1s) data broadcast
|
||||
and transmission design, should provide sub-second transaction confirmation times that scale
|
||||
with the log of the number of nodes in the cluster. This means we shouldn't
|
||||
have to restrict the number of validating nodes with a prohibitive 'minimum
|
||||
|
@@ -91,6 +91,10 @@ History](#proof-of-history).
|
||||
The time, i.e. number of [slots](#slot), for which a [leader
|
||||
schedule](#leader-schedule) is valid.
|
||||
|
||||
#### finality
|
||||
|
||||
When nodes representing 2/3rd of the stake have a common [root](#root).
|
||||
|
||||
#### fork
|
||||
|
||||
A [ledger](#ledger) derived from common entries but then diverged.
|
||||
@@ -213,6 +217,15 @@ The public key of a [keypair](#keypair).
|
||||
Storage mining client, stores some part of the ledger enumerated in blocks and
|
||||
submits storage proofs to the chain. Not a full-node.
|
||||
|
||||
#### root
|
||||
|
||||
A [block](#block) or [slot](#slot) that has reached maximum [lockout](#lockout)
|
||||
on a validator. The root is the highest block that is an ancestor of all active
|
||||
forks on a validator. All ancestor blocks of a root are also transitively a
|
||||
root. Blocks that are not an ancestor and not a descendant of the root are
|
||||
excluded from consideration for consensus and can be discarded.
|
||||
|
||||
|
||||
#### runtime
|
||||
|
||||
The component of a [fullnode](#fullnode) responsible for [program](#program)
|
||||
|
@@ -32,7 +32,7 @@ traversal issues. A cloud-hosted machine works best. **Ensure that IP ports
|
||||
Prebuilt binaries are available for Linux x86_64 (Ubuntu 18.04 recommended).
|
||||
MacOS or WSL users may build from source.
|
||||
|
||||
For a performance testnet with many transactions we have some preliminary recomended setups:
|
||||
For a performance testnet with many transactions we have some preliminary recommended setups:
|
||||
|
||||
| | Low end | Medium end | High end | Notes |
|
||||
| --- | ---------|------------|----------| -- |
|
||||
@@ -42,6 +42,13 @@ For a performance testnet with many transactions we have some preliminary recome
|
||||
| Accounts Drive(s) | None | Samsung 970 Pro 1TB | 2x Samsung 970 Pro 1TB | |
|
||||
| GPU | 4x Nvidia 1070 or 2x Nvidia 1080 Ti or 2x Nvidia 2070 | 2x Nvidia 2080 Ti | 4x Nvidia 2080 Ti | Any number of cuda-capable GPUs are supported on Linux platforms. |
|
||||
|
||||
#### GPU Requirements
|
||||
CUDA is required to make use of the GPU on your system. The provided Solana
|
||||
release binaries are built on Ubuntu 18.04 with <a
|
||||
href="https://developer.nvidia.com/cuda-toolkit-archive">CUDA Toolkit 10.1
|
||||
update 1"</a>. If your machine is using a different CUDA version then you will
|
||||
need to rebuild from source.
|
||||
|
||||
#### Confirm The Testnet Is Reachable
|
||||
Before attaching a validator node, sanity check that the cluster is accessible
|
||||
to your machine by running some simple commands. If any of the commands fail,
|
||||
@@ -64,11 +71,10 @@ for more detail on cluster activity.
|
||||
##### Bootstrap with `solana-install`
|
||||
|
||||
The `solana-install` tool can be used to easily install and upgrade the cluster
|
||||
software on Linux x86_64 systems.
|
||||
software on Linux x86_64 and mac OS systems.
|
||||
|
||||
```bash
|
||||
$ export SOLANA_RELEASE=v0.14.2 # skip this line to install the latest release
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.14.0/install/solana-install-init.sh | sh -s
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.17.0/install/solana-install-init.sh | sh -s
|
||||
```
|
||||
|
||||
Alternatively build the `solana-install` program from source and run the
|
||||
@@ -78,11 +84,12 @@ $ solana-install init
|
||||
```
|
||||
|
||||
After a successful install, `solana-install update` may be used to easily update the cluster
|
||||
software to a newer version.
|
||||
software to a newer version at any time.
|
||||
|
||||
##### Download Prebuilt Binaries
|
||||
Binaries are available for Linux x86_64 systems.
|
||||
If you would rather not use `solana-install` to manage the install, you can manually download and install the binaries.
|
||||
|
||||
###### Linux
|
||||
Download the binaries by navigating to
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
|
||||
download **solana-release-x86_64-unknown-linux-gnu.tar.bz2**, then extract the
|
||||
@@ -92,6 +99,17 @@ $ tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
|
||||
$ cd solana-release/
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
###### mac OS
|
||||
Download the binaries by navigating to
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
|
||||
download **solana-release-x86_64-apple-darwin.tar.bz2**, then extract the
|
||||
archive:
|
||||
```bash
|
||||
$ tar jxf solana-release-x86_64-apple-darwin.tar.bz2
|
||||
$ cd solana-release/
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
|
||||
##### Build From Source
|
||||
If you are unable to use the prebuilt binaries or prefer to build it yourself
|
||||
from source, navigate to
|
||||
@@ -103,6 +121,15 @@ $ ./scripts/cargo-install-all.sh .
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
|
||||
If building for CUDA (Linux only), fetch the perf-libs first then include the
|
||||
`cuda` feature flag when building:
|
||||
```bash
|
||||
$ ./fetch-perf-libs.sh
|
||||
$ source /home/mvines/ws/solana/target/perf-libs/env.sh
|
||||
$ ./scripts/cargo-install-all.sh . cuda
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
|
||||
### Starting The Validator
|
||||
Sanity check that you are able to interact with the cluster by receiving a small
|
||||
airdrop of lamports from the testnet drone:
|
||||
@@ -119,7 +146,7 @@ $ solana-gossip --entrypoint testnet.solana.com:8001 spy
|
||||
|
||||
Now configure a key pair for your validator by running:
|
||||
```bash
|
||||
$ solana-keygen -o validator-keypair.json
|
||||
$ solana-keygen new -o ~/validator-keypair.json
|
||||
```
|
||||
|
||||
Then use one of the following commands, depending on your installation
|
||||
@@ -127,23 +154,31 @@ choice, to start the node:
|
||||
|
||||
If this is a `solana-install`-installation:
|
||||
```bash
|
||||
$ clear-config.sh
|
||||
$ validator.sh --identity validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
|
||||
$ validator.sh --identity ~/validator-keypair.json --config-dir ~/validator-config --rpc-port 8899 --poll-for-new-genesis-block testnet.solana.com
|
||||
```
|
||||
|
||||
Alternatively, the `solana-install run` command can be used to run the validator
|
||||
node while periodically checking for and applying software updates:
|
||||
```bash
|
||||
$ clear-config.sh
|
||||
$ solana-install run validator.sh -- --identity validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
|
||||
$ solana-install run validator.sh -- --identity ~/validator-keypair.json --config-dir ~/validator-config --rpc-port 8899 --poll-for-new-genesis-block testnet.solana.com
|
||||
```
|
||||
|
||||
If you built from source:
|
||||
```bash
|
||||
$ USE_INSTALL=1 ./multinode-demo/clear-config.sh
|
||||
$ USE_INSTALL=1 ./multinode-demo/validator.sh --identity validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
|
||||
$ NDEBUG=1 USE_INSTALL=1 ./multinode-demo/validator.sh --identity ~/validator-keypair.json --rpc-port 8899 --poll-for-new-genesis-block testnet.solana.com
|
||||
```
|
||||
|
||||
#### Enabling CUDA
|
||||
By default CUDA is disabled. If your machine has a GPU with CUDA installed,
|
||||
define the SOLANA_CUDA flag in your environment *before* running any of the
|
||||
previusly mentioned commands
|
||||
```bash
|
||||
$ export SOLANA_CUDA=1
|
||||
```
|
||||
|
||||
When your validator is started look for the following log message to indicate that CUDA is enabled:
|
||||
`"[<timestamp> solana::validator] CUDA is enabled"`
|
||||
|
||||
#### Controlling local network port allocation
|
||||
By default the validator will dynamically select available network ports in the
|
||||
8000-10000 range, and may be overridden with `--dynamic-port-range`. For
|
||||
@@ -164,7 +199,7 @@ accounts: ...
|
||||
|
||||
The **identity pubkey** for your validator can also be found by running:
|
||||
```bash
|
||||
$ solana-keygen pubkey validator-keypair.json
|
||||
$ solana-keygen pubkey ~/validator-keypair.json
|
||||
```
|
||||
|
||||
From another console, confirm the IP address and **identity pubkey** of your validator is visible in the
|
||||
@@ -176,7 +211,7 @@ $ solana-gossip --entrypoint testnet.solana.com:8001 spy
|
||||
Provide the **vote pubkey** to the `solana-wallet show-vote-account` command to view
|
||||
the recent voting activity from your validator:
|
||||
```bash
|
||||
$ solana-wallet -n testnet.solana.com show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
|
||||
$ solana-wallet show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
|
||||
```
|
||||
|
||||
The vote pubkey for the validator can also be found by running:
|
||||
@@ -187,13 +222,63 @@ $ solana-keygen pubkey ~/.local/share/solana/install/active_release/config-local
|
||||
$ solana-keygen pubkey ./config-local/validator-vote-keypair.json
|
||||
```
|
||||
|
||||
### Sharing Metrics From Your Validator
|
||||
If you have obtained a metrics username/password from the Solana maintainers to
|
||||
help us monitor the health of the testnet, please perform the following steps
|
||||
before starting the validator to activate metrics reporting:
|
||||
|
||||
#### Validator Metrics
|
||||
Metrics are available for local monitoring of your validator.
|
||||
|
||||
Docker must be installed and the current user added to the docker group. Then
|
||||
download `solana-metrics.tar.bz2` from the Github Release and run
|
||||
```bash
|
||||
export u="username obtained from the Solana maintainers"
|
||||
export p="password obtained from the Solana maintainers"
|
||||
export SOLANA_METRICS_CONFIG="db=testnet,u=${u:?},p=${p:?}"
|
||||
source scripts/configure-metrics.sh
|
||||
$ tar jxf solana-metrics.tar.bz2
|
||||
$ cd solana-metrics/
|
||||
$ ./start.sh
|
||||
```
|
||||
|
||||
A local InfluxDB and Grafana instance is now running on your machine. Define
|
||||
`SOLANA_METRICS_CONFIG` in your environment as described at the end of the
|
||||
`start.sh` output and restart your validator.
|
||||
|
||||
Metrics should now be streaming and visible from your local Grafana dashboard.
|
||||
|
||||
#### Timezone For Log Messages
|
||||
Log messages emitted by your validator include a timestamp. When sharing logs
|
||||
with others to help triage issues, that timestamp can cause confusion as it does
|
||||
not contain timezone information.
|
||||
|
||||
To make it easier to compare logs between different sources we request that
|
||||
everybody use Pacific Time on their validator nodes. In Linux this can be
|
||||
accomplished by running:
|
||||
```bash
|
||||
$ sudo ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
|
||||
```
|
||||
|
||||
#### Publishing Validator Info
|
||||
|
||||
You can publish your validator information to the chain to be publicly visible
|
||||
to other users.
|
||||
|
||||
Run the solana-validator-info CLI to populate a validator-info account:
|
||||
```bash
|
||||
$ solana-validator-info publish ~/validator-keypair.json <VALIDATOR_NAME> <VALIDATOR_INFO_ARGS>
|
||||
```
|
||||
Optional fields for VALIDATOR_INFO_ARGS:
|
||||
* Website
|
||||
* Keybase Username
|
||||
* Details
|
||||
|
||||
##### Keybase
|
||||
|
||||
Including a Keybase username allows client applications (like the Solana Network
|
||||
Explorer) to automatically pull in your validator public profile, including
|
||||
cryptographic proofs, brand identity, etc. To connect your validator pubkey with
|
||||
Keybase:
|
||||
|
||||
1. Join https://keybase.io/ and complete the profile for your validator
|
||||
2. Add your validator **identity pubkey** to Keybase:
|
||||
* Create an empty file on your local computer called `validator-<PUBKEY>`
|
||||
* In Keybase, navigate to the Files section, and upload your pubkey file to
|
||||
a `solana` subdirectory in your public folder: `/keybase/public/<KEYBASE_USERNAME>/solana`
|
||||
* To check your pubkey, ensure you can successfully browse to
|
||||
`https://keybase.pub/<KEYBASE_USERNAME>/solana/validator-<PUBKEY>`
|
||||
3. Add or update your `solana-validator-info` with your Keybase username. The
|
||||
CLI will verify the `validator-<PUBKEY>` file
|
||||
|
153
book/src/testnet-replicator.md
Normal file
153
book/src/testnet-replicator.md
Normal file
@@ -0,0 +1,153 @@
|
||||
## Testnet Replicator
|
||||
This document describes how to setup a replicator in the testnet
|
||||
|
||||
Please note some of the information and instructions described here may change
|
||||
in future releases.
|
||||
|
||||
### Overview
|
||||
Replicators are specialized light clients. They download a part of the
|
||||
ledger (a.k.a Segment) and store it. They earn rewards for storing segments.
|
||||
|
||||
The testnet features a validator running at testnet.solana.com, which
|
||||
serves as the entrypoint to the cluster for your replicator node.
|
||||
|
||||
Additionally there is a blockexplorer available at
|
||||
[http://testnet.solana.com/](http://testnet.solana.com/).
|
||||
|
||||
The testnet is configured to reset the ledger daily, or sooner
|
||||
should the hourly automated cluster sanity test fail.
|
||||
|
||||
### Machine Requirements
|
||||
Replicators don't need specialized hardware. Anything with more than
|
||||
128GB of disk space will be able to participate in the cluster as a replicator node.
|
||||
|
||||
Currently the disk space requirements are very low but we expect them to change
|
||||
in the future.
|
||||
|
||||
Prebuilt binaries are available for Linux x86_64 (Ubuntu 18.04 recommended),
|
||||
macOS, and Windows.
|
||||
|
||||
#### Confirm The Testnet Is Reachable
|
||||
Before starting a replicator node, sanity check that the cluster is accessible
|
||||
to your machine by running some simple commands. If any of the commands fail,
|
||||
please retry 5-10 minutes later to confirm the testnet is not just restarting
|
||||
itself before debugging further.
|
||||
|
||||
Fetch the current transaction count over JSON RPC:
|
||||
```bash
|
||||
$ curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://testnet.solana.com:8899
|
||||
```
|
||||
|
||||
Inspect the blockexplorer at [http://testnet.solana.com/](http://testnet.solana.com/) for activity.
|
||||
|
||||
View the [metrics dashboard](
|
||||
https://metrics.solana.com:3000/d/testnet-beta/testnet-monitor-beta?var-testnet=testnet)
|
||||
for more detail on cluster activity.
|
||||
|
||||
### Replicator Setup
|
||||
##### Obtaining The Software
|
||||
##### Bootstrap with `solana-install`
|
||||
|
||||
The `solana-install` tool can be used to easily install and upgrade the cluster
|
||||
software.
|
||||
|
||||
##### Linux and mac OS
|
||||
```bash
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.17.0/install/solana-install-init.sh | sh -s
|
||||
```
|
||||
|
||||
Alternatively build the `solana-install` program from source and run the
|
||||
following command to obtain the same result:
|
||||
```bash
|
||||
$ solana-install init
|
||||
```
|
||||
|
||||
##### Windows
|
||||
Download and install **solana-install-init** from
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest)
|
||||
|
||||
After a successful install, `solana-install update` may be used to
|
||||
easily update the software to a newer version at any time.
|
||||
|
||||
##### Download Prebuilt Binaries
|
||||
If you would rather not use `solana-install` to manage the install, you can manually download and install the binaries.
|
||||
|
||||
##### Linux
|
||||
Download the binaries by navigating to
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
|
||||
download **solana-release-x86_64-unknown-linux-gnu.tar.bz2**, then extract the
|
||||
archive:
|
||||
```bash
|
||||
$ tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
|
||||
$ cd solana-release/
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
##### mac OS
|
||||
Download the binaries by navigating to
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
|
||||
download **solana-release-x86_64-apple-darwin.tar.bz2**, then extract the
|
||||
archive:
|
||||
```bash
|
||||
$ tar jxf solana-release-x86_64-apple-darwin.tar.bz2
|
||||
$ cd solana-release/
|
||||
$ export PATH=$PWD/bin:$PATH
|
||||
```
|
||||
##### Windows
|
||||
Download the binaries by navigating to
|
||||
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
|
||||
download **solana-release-x86_64-pc-windows-msvc.tar.bz2**, then extract it into a folder.
|
||||
It is a good idea to add this extracted folder to your windows PATH.
|
||||
|
||||
### Starting The Replicator
|
||||
Try running following command to join the gossip network and view all the other nodes in the cluster:
|
||||
```bash
|
||||
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
|
||||
# Press ^C to exit
|
||||
```
|
||||
|
||||
Now configure the keypairs for your replicator by running:
|
||||
|
||||
Navigate to the solana install location and open a cmd prompt
|
||||
```bash
|
||||
$ solana-keygen new -o replicator-keypair.json
|
||||
$ solana-keygen new -o storage-keypair.json
|
||||
```
|
||||
|
||||
Use solana-keygen to show the public keys for each of the keypairs,
|
||||
they will be needed in the next step:
|
||||
- Windows
|
||||
```bash
|
||||
# The replicator's identity
|
||||
$ solana-keygen pubkey replicator-keypair.json
|
||||
$ solana-keygen pubkey storage-keypair.json
|
||||
```
|
||||
- Linux and mac OS
|
||||
```bash
|
||||
$ export REPLICATOR_IDENTITY=$(solana-keygen pubkey replicator-keypair.json)
|
||||
$ export STORAGE_IDENTITY=$(solana-keygen pubkey storage-keypair.json)
|
||||
|
||||
```
|
||||
Then set up the storage accounts for your replicator by running:
|
||||
```bash
|
||||
$ solana-wallet --keypair replicator-keypair.json airdrop 100000
|
||||
$ solana-wallet --keypair replicator-keypair.json create-replicator-storage-account $REPLICATOR_IDENTITY $STORAGE_IDENTITY
|
||||
```
|
||||
Note: Every time the testnet restarts, run the wallet steps to setup the replicator accounts again.
|
||||
|
||||
To start the replicator:
|
||||
```bash
|
||||
$ solana-replicator --entrypoint testnet.solana.com:8001 --identity replicator-keypair.json --storage-keypair storage-keypair.json --ledger replicator-ledger
|
||||
```
|
||||
|
||||
### Verify Replicator Setup
|
||||
From another console, confirm the IP address and **identity pubkey** of your replicator is visible in the
|
||||
gossip network by running:
|
||||
```bash
|
||||
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
|
||||
```
|
||||
|
||||
Provide the **storage account pubkey** to the `solana-wallet show-storage-account` command to view
|
||||
the recent mining activity from your replicator:
|
||||
```bash
|
||||
$ solana-wallet --keypair storage-keypair.json show-storage-account $STORAGE_IDENTITY
|
||||
```
|
@@ -1,7 +1,7 @@
|
||||
# Fork Selection
|
||||
# Tower BFT
|
||||
|
||||
This design describes a *Fork Selection* algorithm. It addresses the following
|
||||
problems:
|
||||
This design describes Solana's *Tower BFT* algorithm. It addresses the
|
||||
following problems:
|
||||
|
||||
* Some forks may not end up accepted by the super-majority of the cluster, and
|
||||
voters need to recover from voting on such forks.
|
48
book/src/transaction-api.md
Normal file
48
book/src/transaction-api.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# The Transaction
|
||||
|
||||
### Components of a `Transaction`
|
||||
|
||||
* **Transaction:**
|
||||
* **message:** Defines the transaction
|
||||
* **header:** Details the account types of and signatures required by
|
||||
the transaction
|
||||
* **num_required_signatures:** The total number of signatures
|
||||
required to make the transaction valid.
|
||||
* **num_credit_only_signed_accounts:** The last
|
||||
`num_credit_only_signed_accounts` signatures refer to signing
|
||||
credit only accounts. Credit only accounts can be used concurrently
|
||||
by multiple parallel transactions, but their balance may only be
|
||||
increased, and their account data is read-only.
|
||||
* **num_credit_only_unsigned_accounts:** The last
|
||||
`num_credit_only_unsigned_accounts` pubkeys in `account_keys` refer
|
||||
to non-signing credit only accounts
|
||||
* **account_keys:** List of pubkeys used by the transaction, including
|
||||
by the instructions and for signatures. The first
|
||||
`num_required_signatures` pubkeys must sign the transaction.
|
||||
* **recent_blockhash:** The ID of a recent ledger entry. Validators will
|
||||
reject transactions with a `recent_blockhash` that is too old.
|
||||
* **instructions:** A list of [instructions](instruction.md) that are
|
||||
run sequentially and committed in one atomic transaction if all
|
||||
succeed.
|
||||
* **signatures:** A list of signatures applied to the transaction. The
|
||||
list is always of length `num_required_signatures`, and the signature
|
||||
at index `i` corresponds to the pubkey at index `i` in `account_keys`.
|
||||
The list is initialized with empty signatures (i.e. zeros), and
|
||||
populated as signatures are added.
|
||||
|
||||
### Transaction Signing
|
||||
|
||||
A `Transaction` is signed by using an ed25519 keypair to sign the
|
||||
serialization of the `message`. The resulting signature is placed at the
|
||||
index of `signatures` matching the index of the keypair's pubkey in
|
||||
`account_keys`.
|
||||
|
||||
### Transaction Serialization
|
||||
|
||||
`Transaction`s (and their `message`s) are serialized and deserialized
|
||||
using the [bincode](https://crates.io/crates/bincode) crate with a
|
||||
non-standard vector serialization that uses only one byte for the length
|
||||
if it can be encoded in 7 bits, 2 bytes if it fits in 14 bits, or 3
|
||||
bytes if it requires 15 or 16 bits. The vector serialization is defined
|
||||
by Solana's
|
||||
[short-vec](https://github.com/solana-labs/solana/blob/master/sdk/src/short_vec.rs).
|
@@ -8,17 +8,14 @@ client won't know how much was collected until the transaction is confirmed by
|
||||
the cluster and the remaining balance is checked. It smells of exactly what we
|
||||
dislike about Ethereum's "gas", non-determinism.
|
||||
|
||||
## Implementation Status
|
||||
|
||||
This design is not yet implemented, but is written as though it has been. Once
|
||||
implemented, delete this comment.
|
||||
|
||||
### Congestion-driven fees
|
||||
|
||||
Each validator uses *signatures per slot* (SPS) to estimate network congestion
|
||||
and *SPS target* to estimate the desired processing capacity of the cluster.
|
||||
The validator learns the SPS target from the genesis block, whereas it
|
||||
calculates SPS from the ledger data in the previous epoch.
|
||||
calculates SPS from recently processed transactions. The genesis block also
|
||||
defines a target `lamports_per_signature`, which is the fee to charge per
|
||||
signature when the cluster is operating at *SPS target*.
|
||||
|
||||
### Calculating fees
|
||||
|
||||
@@ -37,8 +34,11 @@ lamports as returned by the fee calculator.
|
||||
In the first implementation of this design, the only fee parameter is
|
||||
`lamports_per_signature`. The more signatures the cluster needs to verify, the
|
||||
higher the fee. The exact number of lamports is determined by the ratio of SPS
|
||||
to the SPS target. The cluster lowers `lamports_per_signature` when SPS is
|
||||
below the target and raises it when at or above the target.
|
||||
to the SPS target. At the end of each slot, the cluster lowers
|
||||
`lamports_per_signature` when SPS is below the target and raises it when above
|
||||
the target. The minimum value for `lamports_per_signature` is 50% of the target
|
||||
`lamports_per_signature` and the maximum value is 10x the target
|
||||
`lamports_per_signature'
|
||||
|
||||
Future parameters might include:
|
||||
|
||||
|
43
book/src/transaction.md
Normal file
43
book/src/transaction.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Anatomy of a Transaction
|
||||
|
||||
Transactions encode lists of instructions that are executed
|
||||
sequentially, and only committed if all the instructions complete
|
||||
successfully. All account states are reverted upon the failure of a
|
||||
transaction. Each Transaction details the accounts used, including which
|
||||
must sign and which are credit only, a recent blockhash, the
|
||||
instructions, and any signatures.
|
||||
|
||||
## Accounts and Signatures
|
||||
|
||||
Each transaction explicitly lists all accounts that it needs access to.
|
||||
This includes accounts that are transferring tokens, accounts whose user
|
||||
data is being modified, and the program accounts that are being called
|
||||
by the instructions. Each account that is not an executable program can
|
||||
be marked as a requiring a signature and/or as credit only. All accounts
|
||||
marked as signers must have a valid signature in the transaction's list
|
||||
of signatures before the transaction is considered valid. Any accounts
|
||||
marked as credit only may only have their token value increased, and
|
||||
their user data is read only. Accounts are locked by the runtime,
|
||||
ensuring that they are not modified by a concurrent program while the
|
||||
transaction is running. Credit only accounts can safely be shared, so
|
||||
the runtime will allow multiple concurrent credit only locks on an
|
||||
account.
|
||||
|
||||
## Recent Blockhash
|
||||
|
||||
A Transaction includes a recent blockhash to prevent duplication and to
|
||||
give transactions lifetimes. Any transaction that is completely
|
||||
identical to a previous one is rejected, so adding a newer blockhash
|
||||
allows multiple transactions to repeat the exact same action.
|
||||
Transactions also have lifetimes that are defined by the blockhash, as
|
||||
any transaction whose blockhash is too old will be rejected.
|
||||
|
||||
## Instructions
|
||||
|
||||
Each instruction specifies a single program account (which must be
|
||||
marked executable), a subset of the transaction's accounts that should
|
||||
be passed to the program, and a data byte array instruction that is
|
||||
passed to the program. The program interprets the data array and
|
||||
operates on the accounts specified by the instructions. The program can
|
||||
return successfully, or with an error code. An error return causes the
|
||||
entire transaction to fail immediately.
|
@@ -1,12 +1,12 @@
|
||||
# Data Plane Fanout
|
||||
# Turbine Block Propagation
|
||||
|
||||
A Solana cluster uses a multi-layer mechanism called *data plane fanout* to
|
||||
broadcast transaction blobs to all nodes in a very quick and efficient manner.
|
||||
In order to establish the fanout, the cluster divides itself into small
|
||||
collections of nodes, called *neighborhoods*. Each node is responsible for
|
||||
sharing any data it receives with the other nodes in its neighborhood, as well
|
||||
as propagating the data on to a small set of nodes in other neighborhoods.
|
||||
This way each node only has to communicate with a small number of nodes.
|
||||
A Solana cluster uses a multi-layer block propagation mechanism called *Turbine*
|
||||
to broadcast transaction blobs to all nodes with minimal amount of duplicate
|
||||
messages. The cluster divides itself into small collections of nodes, called
|
||||
*neighborhoods*. Each node is responsible for sharing any data it receives with
|
||||
the other nodes in its neighborhood, as well as propagating the data on to a
|
||||
small set of nodes in other neighborhoods. This way each node only has to
|
||||
communicate with a small number of nodes.
|
||||
|
||||
During its slot, the leader node distributes blobs between the validator nodes
|
||||
in the first neighborhood (layer 0). Each validator shares its data within its
|
||||
@@ -26,6 +26,14 @@ make up layer 0. These will automatically be the highest stake holders, allowing
|
||||
the heaviest votes to come back to the leader first. Layer-0 and lower-layer
|
||||
nodes use the same logic to find their neighbors and next layer peers.
|
||||
|
||||
To reduce the possibility of attack vectors, each blob is transmitted over a
|
||||
random tree of neighborhoods. Each node uses the same set of nodes representing
|
||||
the cluster. A random tree is generated from the set for each blob using
|
||||
randomness derived from the blob itself. Since the random seed is not known in
|
||||
advance, attacks that try to eclipse neighborhoods from certain leaders or
|
||||
blocks become very difficult, and should require almost complete control of the
|
||||
stake in the cluster.
|
||||
|
||||
## Layer and Neighborhood Structure
|
||||
|
||||
The current leader makes its initial broadcasts to at most `DATA_PLANE_FANOUT`
|
@@ -284,6 +284,18 @@ ARGS:
|
||||
<PATH> /path/to/program.o
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-wallet-fees
|
||||
Display current cluster fees
|
||||
|
||||
USAGE:
|
||||
solana-wallet fees
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
```
|
||||
|
||||
```manpage
|
||||
solana-wallet-get-transaction-count
|
||||
Get current transaction count
|
||||
|
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Builds perf-libs from the upstream source and installs them into the correct
|
||||
# location in the tree
|
||||
#
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
if [[ -d target/perf-libs ]]; then
|
||||
echo "target/perf-libs/ already exists, to continue run:"
|
||||
echo "$ rm -rf target/perf-libs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(
|
||||
set -x
|
||||
git clone git@github.com:solana-labs/solana-perf-libs.git target/perf-libs
|
||||
cd target/perf-libs
|
||||
make -j"$(nproc)"
|
||||
make DESTDIR=. install
|
||||
)
|
||||
|
||||
./fetch-perf-libs.sh
|
@@ -1 +1,2 @@
|
||||
/target/
|
||||
/farf/
|
12
chacha-sys/Cargo.toml
Normal file
12
chacha-sys/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "solana-chacha-sys"
|
||||
version = "0.17.1"
|
||||
description = "Solana chacha-sys"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.38"
|
8
chacha-sys/build.rs
Normal file
8
chacha-sys/build.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
extern crate cc;
|
||||
|
||||
fn main() {
|
||||
cc::Build::new()
|
||||
.file("cpu-crypt/chacha20_core.c")
|
||||
.file("cpu-crypt/chacha_cbc.c")
|
||||
.compile("libcpu-crypt");
|
||||
}
|
1
chacha-sys/cpu-crypt/.gitignore
vendored
Normal file
1
chacha-sys/cpu-crypt/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
release/
|
25
chacha-sys/cpu-crypt/Makefile
Normal file
25
chacha-sys/cpu-crypt/Makefile
Normal file
@@ -0,0 +1,25 @@
|
||||
V:=debug
|
||||
|
||||
LIB:=cpu-crypt
|
||||
|
||||
CFLAGS_common:=-Wall -Werror -pedantic -fPIC
|
||||
CFLAGS_release:=-march=native -O3 $(CFLAGS_common)
|
||||
CFLAGS_debug:=-g $(CFLAGS_common)
|
||||
CFLAGS:=$(CFLAGS_$V)
|
||||
|
||||
all: $V/lib$(LIB).a
|
||||
|
||||
$V/chacha20_core.o: chacha20_core.c chacha.h
|
||||
@mkdir -p $(@D)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$V/chacha_cbc.o: chacha_cbc.c chacha.h
|
||||
@mkdir -p $(@D)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$V/lib$(LIB).a: $V/chacha20_core.o $V/chacha_cbc.o
|
||||
$(AR) rcs $@ $^
|
||||
|
||||
.PHONY:clean
|
||||
clean:
|
||||
rm -rf $V
|
35
chacha-sys/cpu-crypt/chacha.h
Normal file
35
chacha-sys/cpu-crypt/chacha.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef HEADER_CHACHA_H
|
||||
# define HEADER_CHACHA_H
|
||||
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
# include <stddef.h>
|
||||
# ifdef __cplusplus
|
||||
extern "C" {
|
||||
# endif
|
||||
|
||||
typedef unsigned int u32;
|
||||
|
||||
#define CHACHA_KEY_SIZE 32
|
||||
#define CHACHA_NONCE_SIZE 12
|
||||
#define CHACHA_BLOCK_SIZE 64
|
||||
#define CHACHA_ROUNDS 500
|
||||
|
||||
void chacha20_encrypt(const u32 input[16],
|
||||
unsigned char output[64],
|
||||
int num_rounds);
|
||||
|
||||
void chacha20_encrypt_ctr(const uint8_t *in, uint8_t *out, size_t in_len,
|
||||
const uint8_t key[CHACHA_KEY_SIZE], const uint8_t nonce[CHACHA_NONCE_SIZE],
|
||||
uint32_t counter);
|
||||
|
||||
void chacha20_cbc128_encrypt(const unsigned char* in, unsigned char* out,
|
||||
uint32_t len, const uint8_t* key,
|
||||
unsigned char* ivec);
|
||||
|
||||
|
||||
# ifdef __cplusplus
|
||||
}
|
||||
# endif
|
||||
|
||||
#endif
|
102
chacha-sys/cpu-crypt/chacha20_core.c
Normal file
102
chacha-sys/cpu-crypt/chacha20_core.c
Normal file
@@ -0,0 +1,102 @@
|
||||
#include "chacha.h"
|
||||
|
||||
#define ROTL32(v, n) (((v) << (n)) | ((v) >> (32 - (n))))
|
||||
|
||||
#define ROTATE(v, c) ROTL32((v), (c))
|
||||
|
||||
#define XOR(v, w) ((v) ^ (w))
|
||||
|
||||
#define PLUS(x, y) ((x) + (y))
|
||||
|
||||
#define U32TO8_LITTLE(p, v) \
|
||||
{ (p)[0] = ((v) ) & 0xff; (p)[1] = ((v) >> 8) & 0xff; \
|
||||
(p)[2] = ((v) >> 16) & 0xff; (p)[3] = ((v) >> 24) & 0xff; }
|
||||
|
||||
#define U8TO32_LITTLE(p) \
|
||||
(((u32)((p)[0]) ) | ((u32)((p)[1]) << 8) | \
|
||||
((u32)((p)[2]) << 16) | ((u32)((p)[3]) << 24) )
|
||||
|
||||
#define QUARTERROUND(a,b,c,d) \
|
||||
x[a] = PLUS(x[a],x[b]); x[d] = ROTATE(XOR(x[d],x[a]),16); \
|
||||
x[c] = PLUS(x[c],x[d]); x[b] = ROTATE(XOR(x[b],x[c]),12); \
|
||||
x[a] = PLUS(x[a],x[b]); x[d] = ROTATE(XOR(x[d],x[a]), 8); \
|
||||
x[c] = PLUS(x[c],x[d]); x[b] = ROTATE(XOR(x[b],x[c]), 7);
|
||||
|
||||
// sigma contains the ChaCha constants, which happen to be an ASCII string.
|
||||
static const uint8_t sigma[16] = { 'e', 'x', 'p', 'a', 'n', 'd', ' ', '3',
|
||||
'2', '-', 'b', 'y', 't', 'e', ' ', 'k' };
|
||||
|
||||
void chacha20_encrypt(const u32 input[16],
|
||||
unsigned char output[64],
|
||||
int num_rounds)
|
||||
{
|
||||
u32 x[16];
|
||||
int i;
|
||||
memcpy(x, input, sizeof(u32) * 16);
|
||||
for (i = num_rounds; i > 0; i -= 2) {
|
||||
QUARTERROUND( 0, 4, 8,12)
|
||||
QUARTERROUND( 1, 5, 9,13)
|
||||
QUARTERROUND( 2, 6,10,14)
|
||||
QUARTERROUND( 3, 7,11,15)
|
||||
QUARTERROUND( 0, 5,10,15)
|
||||
QUARTERROUND( 1, 6,11,12)
|
||||
QUARTERROUND( 2, 7, 8,13)
|
||||
QUARTERROUND( 3, 4, 9,14)
|
||||
}
|
||||
for (i = 0; i < 16; ++i) {
|
||||
x[i] = PLUS(x[i], input[i]);
|
||||
}
|
||||
for (i = 0; i < 16; ++i) {
|
||||
U32TO8_LITTLE(output + 4 * i, x[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void chacha20_encrypt_ctr(const uint8_t *in, uint8_t *out, size_t in_len,
|
||||
const uint8_t key[CHACHA_KEY_SIZE],
|
||||
const uint8_t nonce[CHACHA_NONCE_SIZE],
|
||||
uint32_t counter)
|
||||
{
|
||||
uint32_t input[16];
|
||||
uint8_t buf[64];
|
||||
size_t todo, i;
|
||||
|
||||
input[0] = U8TO32_LITTLE(sigma + 0);
|
||||
input[1] = U8TO32_LITTLE(sigma + 4);
|
||||
input[2] = U8TO32_LITTLE(sigma + 8);
|
||||
input[3] = U8TO32_LITTLE(sigma + 12);
|
||||
|
||||
input[4] = U8TO32_LITTLE(key + 0);
|
||||
input[5] = U8TO32_LITTLE(key + 4);
|
||||
input[6] = U8TO32_LITTLE(key + 8);
|
||||
input[7] = U8TO32_LITTLE(key + 12);
|
||||
|
||||
input[8] = U8TO32_LITTLE(key + 16);
|
||||
input[9] = U8TO32_LITTLE(key + 20);
|
||||
input[10] = U8TO32_LITTLE(key + 24);
|
||||
input[11] = U8TO32_LITTLE(key + 28);
|
||||
|
||||
input[12] = counter;
|
||||
input[13] = U8TO32_LITTLE(nonce + 0);
|
||||
input[14] = U8TO32_LITTLE(nonce + 4);
|
||||
input[15] = U8TO32_LITTLE(nonce + 8);
|
||||
|
||||
while (in_len > 0) {
|
||||
todo = sizeof(buf);
|
||||
if (in_len < todo) {
|
||||
todo = in_len;
|
||||
}
|
||||
|
||||
chacha20_encrypt(input, buf, 20);
|
||||
for (i = 0; i < todo; i++) {
|
||||
out[i] = in[i] ^ buf[i];
|
||||
}
|
||||
|
||||
out += todo;
|
||||
in += todo;
|
||||
in_len -= todo;
|
||||
|
||||
input[12]++;
|
||||
}
|
||||
}
|
||||
|
||||
|
72
chacha-sys/cpu-crypt/chacha_cbc.c
Normal file
72
chacha-sys/cpu-crypt/chacha_cbc.c
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "chacha.h"
|
||||
|
||||
#if !defined(STRICT_ALIGNMENT) && !defined(PEDANTIC)
|
||||
# define STRICT_ALIGNMENT 0
|
||||
#endif
|
||||
|
||||
void chacha20_cbc128_encrypt(const unsigned char* in, unsigned char* out,
|
||||
uint32_t len, const uint8_t* key,
|
||||
unsigned char* ivec)
|
||||
{
|
||||
size_t n;
|
||||
unsigned char *iv = ivec;
|
||||
(void)key;
|
||||
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if !defined(OPENSSL_SMALL_FOOTPRINT)
|
||||
if (STRICT_ALIGNMENT &&
|
||||
((size_t)in | (size_t)out | (size_t)ivec) % sizeof(size_t) != 0) {
|
||||
while (len >= CHACHA_BLOCK_SIZE) {
|
||||
for (n = 0; n < CHACHA_BLOCK_SIZE; ++n) {
|
||||
out[n] = in[n] ^ iv[n];
|
||||
//printf("%x ", out[n]);
|
||||
}
|
||||
chacha20_encrypt((const u32*)out, out, CHACHA_ROUNDS);
|
||||
iv = out;
|
||||
len -= CHACHA_BLOCK_SIZE;
|
||||
in += CHACHA_BLOCK_SIZE;
|
||||
out += CHACHA_BLOCK_SIZE;
|
||||
}
|
||||
} else {
|
||||
while (len >= CHACHA_BLOCK_SIZE) {
|
||||
for (n = 0; n < CHACHA_BLOCK_SIZE; n += sizeof(size_t)) {
|
||||
*(size_t *)(out + n) =
|
||||
*(size_t *)(in + n) ^ *(size_t *)(iv + n);
|
||||
//printf("%zu ", *(size_t *)(iv + n));
|
||||
}
|
||||
chacha20_encrypt((const u32*)out, out, CHACHA_ROUNDS);
|
||||
iv = out;
|
||||
len -= CHACHA_BLOCK_SIZE;
|
||||
in += CHACHA_BLOCK_SIZE;
|
||||
out += CHACHA_BLOCK_SIZE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
while (len) {
|
||||
for (n = 0; n < CHACHA_BLOCK_SIZE && n < len; ++n) {
|
||||
out[n] = in[n] ^ iv[n];
|
||||
}
|
||||
for (; n < CHACHA_BLOCK_SIZE; ++n) {
|
||||
out[n] = iv[n];
|
||||
}
|
||||
chacha20_encrypt((const u32*)out, out, CHACHA_ROUNDS);
|
||||
iv = out;
|
||||
if (len <= CHACHA_BLOCK_SIZE) {
|
||||
break;
|
||||
}
|
||||
len -= CHACHA_BLOCK_SIZE;
|
||||
in += CHACHA_BLOCK_SIZE;
|
||||
out += CHACHA_BLOCK_SIZE;
|
||||
}
|
||||
memcpy(ivec, iv, CHACHA_BLOCK_SIZE);
|
||||
|
||||
}
|
||||
|
||||
void chacha20_cbc_encrypt(const uint8_t *in, uint8_t *out, size_t in_len,
|
||||
const uint8_t key[CHACHA_KEY_SIZE], uint8_t* ivec)
|
||||
{
|
||||
chacha20_cbc128_encrypt(in, out, in_len, key, ivec);
|
||||
}
|
21
chacha-sys/src/lib.rs
Normal file
21
chacha-sys/src/lib.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
extern "C" {
|
||||
fn chacha20_cbc_encrypt(
|
||||
input: *const u8,
|
||||
output: *mut u8,
|
||||
in_len: usize,
|
||||
key: *const u8,
|
||||
ivec: *mut u8,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn chacha_cbc_encrypt(input: &[u8], output: &mut [u8], key: &[u8], ivec: &mut [u8]) {
|
||||
unsafe {
|
||||
chacha20_cbc_encrypt(
|
||||
input.as_ptr(),
|
||||
output.as_mut_ptr(),
|
||||
input.len(),
|
||||
key.as_ptr(),
|
||||
ivec.as_mut_ptr(),
|
||||
);
|
||||
}
|
||||
}
|
@@ -12,7 +12,7 @@
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
if ci/is-pr.sh; then
|
||||
if [[ -n $CI_PULL_REQUEST ]]; then
|
||||
affectedFiles="$(buildkite-agent meta-data get affected_files)"
|
||||
echo "Affected files in this PR: $affectedFiles"
|
||||
|
||||
|
@@ -1,16 +1,16 @@
|
||||
steps:
|
||||
- command: "sdk/docker-solana/build.sh"
|
||||
timeout_in_minutes: 20
|
||||
timeout_in_minutes: 60
|
||||
name: "publish docker"
|
||||
- command: "ci/publish-crate.sh"
|
||||
timeout_in_minutes: 40
|
||||
timeout_in_minutes: 90
|
||||
name: "publish crate"
|
||||
branches: "!master"
|
||||
- command: "ci/publish-bpf-sdk.sh"
|
||||
timeout_in_minutes: 5
|
||||
name: "publish bpf sdk"
|
||||
- command: "ci/publish-tarball.sh"
|
||||
timeout_in_minutes: 25
|
||||
timeout_in_minutes: 60
|
||||
name: "publish tarball"
|
||||
- command: "ci/publish-book.sh"
|
||||
timeout_in_minutes: 15
|
||||
|
@@ -2,13 +2,13 @@ steps:
|
||||
- command: "ci/shellcheck.sh"
|
||||
name: "shellcheck"
|
||||
timeout_in_minutes: 5
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-checks.sh"
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-checks.sh"
|
||||
name: "checks"
|
||||
timeout_in_minutes: 15
|
||||
timeout_in_minutes: 35
|
||||
- wait
|
||||
- command: "ci/test-stable-perf.sh"
|
||||
name: "stable-perf"
|
||||
timeout_in_minutes: 20
|
||||
timeout_in_minutes: 30
|
||||
artifact_paths: "log-*.txt"
|
||||
agents:
|
||||
- "queue=cuda"
|
||||
@@ -21,7 +21,7 @@ steps:
|
||||
artifact_paths: "log-*.txt"
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
|
||||
name: "coverage"
|
||||
timeout_in_minutes: 20
|
||||
timeout_in_minutes: 40
|
||||
# TODO: Fix and re-enable test-large-network.sh
|
||||
# - command: "ci/test-large-network.sh || true"
|
||||
# name: "large-network [ignored]"
|
||||
|
@@ -89,11 +89,11 @@ BETA_CHANNEL_LATEST_TAG=${beta_tag:+v$beta_tag}
|
||||
STABLE_CHANNEL_LATEST_TAG=${stable_tag:+v$stable_tag}
|
||||
|
||||
|
||||
if [[ $BUILDKITE_BRANCH = "$STABLE_CHANNEL" ]]; then
|
||||
if [[ $CI_BRANCH = "$STABLE_CHANNEL" ]]; then
|
||||
CHANNEL=stable
|
||||
elif [[ $BUILDKITE_BRANCH = "$EDGE_CHANNEL" ]]; then
|
||||
elif [[ $CI_BRANCH = "$EDGE_CHANNEL" ]]; then
|
||||
CHANNEL=edge
|
||||
elif [[ $BUILDKITE_BRANCH = "$BETA_CHANNEL" ]]; then
|
||||
elif [[ $CI_BRANCH = "$BETA_CHANNEL" ]]; then
|
||||
CHANNEL=beta
|
||||
fi
|
||||
|
||||
|
@@ -64,11 +64,14 @@ fi
|
||||
ARGS+=(
|
||||
--env BUILDKITE
|
||||
--env BUILDKITE_AGENT_ACCESS_TOKEN
|
||||
--env BUILDKITE_BRANCH
|
||||
--env BUILDKITE_COMMIT
|
||||
--env BUILDKITE_JOB_ID
|
||||
--env BUILDKITE_TAG
|
||||
--env CI
|
||||
--env CI_BRANCH
|
||||
--env CI_BUILD_ID
|
||||
--env CI_COMMIT
|
||||
--env CI_JOB_ID
|
||||
--env CI_PULL_REQUEST
|
||||
--env CI_REPO_SLUG
|
||||
--env CODECOV_TOKEN
|
||||
--env CRATES_IO_TOKEN
|
||||
)
|
||||
|
@@ -3,6 +3,7 @@ ARG date
|
||||
|
||||
RUN set -x \
|
||||
&& rustup install nightly-$date \
|
||||
&& rustup component add clippy --toolchain=nightly-$date \
|
||||
&& rustup show \
|
||||
&& rustc --version \
|
||||
&& cargo --version \
|
||||
|
@@ -15,12 +15,12 @@ To update the pinned version:
|
||||
1. Run `ci/docker-rust-nightly/build.sh` to rebuild the nightly image locally,
|
||||
or potentially `ci/docker-rust-nightly/build.sh YYYY-MM-DD` if there's a
|
||||
specific YYYY-MM-DD that is desired (default is today's build).
|
||||
1. Update `ci/rust-version.sh` to reflect the new nightly `YYY-MM-DD`
|
||||
1. Run `SOLANA_DOCKER_RUN_NOSETUID=1 ci/docker-run.sh --nopull solanalabs/rust-nightly:YYYY-MM-DD ci/test-coverage.sh`
|
||||
to confirm the new nightly image builds. Fix any issues as needed
|
||||
1. Run `docker login` to enable pushing images to Docker Hub, if you're authorized.
|
||||
1. Run `CI=true ci/docker-rust-nightly/build.sh YYYY-MM-DD` to push the new nightly image to dockerhub.com.
|
||||
1. Modify the `solanalabs/rust-nightly:YYYY-MM-DD` reference in `ci/rust-version.sh` from the previous to
|
||||
new *YYYY-MM-DD* value, send a PR with this change and any codebase adjustments needed.
|
||||
1. Send a PR with the `ci/rust-version.sh` change and any codebase adjustments needed.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
@@ -1,6 +1,10 @@
|
||||
# Note: when the rust version is changed also modify
|
||||
# ci/buildkite.yml to pick up the new image tag
|
||||
FROM rust:1.34.0
|
||||
# ci/rust-version.sh to pick up the new image tag
|
||||
FROM rust:1.36.0
|
||||
|
||||
# Add Google Protocol Buffers for Libra's metrics library.
|
||||
ENV PROTOC_VERSION 3.8.0
|
||||
ENV PROTOC_ZIP protoc-$PROTOC_VERSION-linux-x86_64.zip
|
||||
|
||||
RUN set -x \
|
||||
&& apt update \
|
||||
@@ -20,6 +24,8 @@ RUN set -x \
|
||||
mscgen \
|
||||
rsync \
|
||||
sudo \
|
||||
golang \
|
||||
unzip \
|
||||
\
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rustup component add rustfmt \
|
||||
@@ -28,4 +34,8 @@ RUN set -x \
|
||||
&& cargo install svgbob_cli \
|
||||
&& cargo install mdbook \
|
||||
&& rustc --version \
|
||||
&& cargo --version
|
||||
&& cargo --version \
|
||||
&& curl -OL https://github.com/google/protobuf/releases/download/v$PROTOC_VERSION/$PROTOC_ZIP \
|
||||
&& unzip -o $PROTOC_ZIP -d /usr/local bin/protoc \
|
||||
&& unzip -o $PROTOC_ZIP -d /usr/local include/* \
|
||||
&& rm -f $PROTOC_ZIP
|
||||
|
@@ -1,6 +1,7 @@
|
||||
Docker image containing rust and some preinstalled packages used in CI.
|
||||
|
||||
This image may be manually updated by running `./build.sh` if you are a member
|
||||
of the [Solana Labs](https://hub.docker.com/u/solanalabs/) Docker Hub
|
||||
organization, but it is also automatically updated periodically by
|
||||
[this automation](https://buildkite.com/solana-labs/solana-ci-docker-rust).
|
||||
This image manually maintained:
|
||||
1. Edit `Dockerfile` to match the desired rust version
|
||||
2. Run `./build.sh` to publish the new image, if you are a member of the [Solana
|
||||
Labs](https://hub.docker.com/u/solanalabs/) Docker Hub organization.
|
||||
|
||||
|
89
ci/env.sh
Normal file
89
ci/env.sh
Normal file
@@ -0,0 +1,89 @@
|
||||
#
|
||||
# Normalized CI environment variables
|
||||
#
|
||||
# |source| me
|
||||
#
|
||||
|
||||
if [[ -n $CI ]]; then
|
||||
export CI=1
|
||||
if [[ -n $TRAVIS ]]; then
|
||||
export CI_BRANCH=$TRAVIS_BRANCH
|
||||
export CI_BUILD_ID=$TRAVIS_BUILD_ID
|
||||
export CI_COMMIT=$TRAVIS_COMMIT
|
||||
export CI_JOB_ID=$TRAVIS_JOB_ID
|
||||
if $TRAVIS_PULL_REQUEST; then
|
||||
export CI_PULL_REQUEST=true
|
||||
else
|
||||
export CI_PULL_REQUEST=
|
||||
fi
|
||||
export CI_OS_NAME=$TRAVIS_OS_NAME
|
||||
export CI_REPO_SLUG=$TRAVIS_REPO_SLUG
|
||||
export CI_TAG=$TRAVIS_TAG
|
||||
elif [[ -n $BUILDKITE ]]; then
|
||||
export CI_BRANCH=$BUILDKITE_BRANCH
|
||||
export CI_BUILD_ID=$BUILDKITE_BUILD_ID
|
||||
export CI_COMMIT=$BUILDKITE_COMMIT
|
||||
export CI_JOB_ID=$BUILDKITE_JOB_ID
|
||||
# The standard BUILDKITE_PULL_REQUEST environment variable is always "false" due
|
||||
# to how solana-ci-gate is used to trigger PR builds rather than using the
|
||||
# standard Buildkite PR trigger.
|
||||
if [[ $CI_BRANCH =~ pull/* ]]; then
|
||||
export CI_PULL_REQUEST=true
|
||||
else
|
||||
export CI_PULL_REQUEST=
|
||||
fi
|
||||
export CI_OS_NAME=linux
|
||||
if [[ -n $BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG ]]; then
|
||||
# The solana-secondary pipeline should use the slug of the pipeline that
|
||||
# triggered it
|
||||
export CI_REPO_SLUG=$BUILDKITE_ORGANIZATION_SLUG/$BUILDKITE_TRIGGERED_FROM_BUILD_PIPELINE_SLUG
|
||||
else
|
||||
export CI_REPO_SLUG=$BUILDKITE_ORGANIZATION_SLUG/$BUILDKITE_PIPELINE_SLUG
|
||||
fi
|
||||
# TRIGGERED_BUILDKITE_TAG is a workaround to propagate BUILDKITE_TAG into
|
||||
# the solana-secondary pipeline
|
||||
if [[ -n $TRIGGERED_BUILDKITE_TAG ]]; then
|
||||
export CI_TAG=$TRIGGERED_BUILDKITE_TAG
|
||||
else
|
||||
export CI_TAG=$BUILDKITE_TAG
|
||||
fi
|
||||
elif [[ -n $APPVEYOR ]]; then
|
||||
export CI_BRANCH=$APPVEYOR_REPO_BRANCH
|
||||
export CI_BUILD_ID=$APPVEYOR_BUILD_ID
|
||||
export CI_COMMIT=$APPVEYOR_REPO_COMMIT
|
||||
export CI_JOB_ID=$APPVEYOR_JOB_ID
|
||||
if [[ -n $APPVEYOR_PULL_REQUEST_NUMBER ]]; then
|
||||
export CI_PULL_REQUEST=true
|
||||
else
|
||||
export CI_PULL_REQUEST=
|
||||
fi
|
||||
if [[ $CI_LINUX = True ]]; then
|
||||
export CI_OS_NAME=linux
|
||||
else
|
||||
export CI_OS_NAME=windows
|
||||
fi
|
||||
export CI_REPO_SLUG=$APPVEYOR_REPO_NAME
|
||||
export CI_TAG=$APPVEYOR_REPO_TAG_NAME
|
||||
fi
|
||||
else
|
||||
export CI=
|
||||
export CI_BRANCH=
|
||||
export CI_BUILD_ID=
|
||||
export CI_COMMIT=
|
||||
export CI_JOB_ID=
|
||||
export CI_OS_NAME=
|
||||
export CI_PULL_REQUEST=
|
||||
export CI_REPO_SLUG=
|
||||
export CI_TAG=
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
CI=$CI
|
||||
CI_BRANCH=$CI_BRANCH
|
||||
CI_BUILD_ID=$CI_BUILD_ID
|
||||
CI_COMMIT=$CI_COMMIT
|
||||
CI_JOB_ID=$CI_JOB_ID
|
||||
CI_OS_NAME=$CI_OS_NAME
|
||||
CI_PULL_REQUEST=$CI_PULL_REQUEST
|
||||
CI_TAG=$CI_TAG
|
||||
EOF
|
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
#
|
||||
# The standard BUILDKITE_PULL_REQUEST environment variable is always "false" due
|
||||
# to how solana-ci-gate is used to trigger PR builds rather than using the
|
||||
# standard Buildkite PR trigger.
|
||||
#
|
||||
|
||||
[[ $BUILDKITE_BRANCH =~ pull/* ]]
|
@@ -5,7 +5,6 @@ skipSetup=false
|
||||
iterations=1
|
||||
restartInterval=never
|
||||
rollingRestart=false
|
||||
maybeNoLeaderRotation=
|
||||
extraNodes=0
|
||||
walletRpcPort=:8899
|
||||
|
||||
@@ -54,9 +53,6 @@ while getopts "ch?i:k:brxR" opt; do
|
||||
k)
|
||||
restartInterval=$OPTARG
|
||||
;;
|
||||
b)
|
||||
maybeNoLeaderRotation="--stake 0"
|
||||
;;
|
||||
x)
|
||||
extraNodes=$((extraNodes + 1))
|
||||
;;
|
||||
@@ -82,7 +78,6 @@ nodes=(
|
||||
--no-restart \
|
||||
--init-complete-file init-complete-node1.log"
|
||||
"multinode-demo/validator.sh \
|
||||
$maybeNoLeaderRotation \
|
||||
--enable-rpc-exit \
|
||||
--no-restart \
|
||||
--init-complete-file init-complete-node2.log \
|
||||
@@ -94,8 +89,7 @@ for i in $(seq 1 $extraNodes); do
|
||||
"multinode-demo/validator.sh \
|
||||
--no-restart \
|
||||
--label dyn$i \
|
||||
--init-complete-file init-complete-node$((2 + i)).log \
|
||||
$maybeNoLeaderRotation"
|
||||
--init-complete-file init-complete-node$((2 + i)).log"
|
||||
)
|
||||
done
|
||||
numNodes=$((2 + extraNodes))
|
||||
@@ -125,11 +119,8 @@ startNode() {
|
||||
echo "log: $log"
|
||||
}
|
||||
|
||||
initCompleteFiles=()
|
||||
waitForAllNodesToInit() {
|
||||
echo "--- ${#initCompleteFiles[@]} nodes booting"
|
||||
SECONDS=
|
||||
for initCompleteFile in "${initCompleteFiles[@]}"; do
|
||||
waitForNodeToInit() {
|
||||
declare initCompleteFile=$1
|
||||
while [[ ! -r $initCompleteFile ]]; do
|
||||
if [[ $SECONDS -ge 240 ]]; then
|
||||
echo "^^^ +++"
|
||||
@@ -140,6 +131,14 @@ waitForAllNodesToInit() {
|
||||
sleep 2
|
||||
done
|
||||
echo "Found $initCompleteFile"
|
||||
}
|
||||
|
||||
initCompleteFiles=()
|
||||
waitForAllNodesToInit() {
|
||||
echo "--- ${#initCompleteFiles[@]} nodes booting"
|
||||
SECONDS=
|
||||
for initCompleteFile in "${initCompleteFiles[@]}"; do
|
||||
waitForNodeToInit "$initCompleteFile"
|
||||
done
|
||||
echo "All nodes finished booting in $SECONDS seconds"
|
||||
}
|
||||
@@ -162,6 +161,13 @@ startNodes() {
|
||||
if $addLogs; then
|
||||
logs+=("$(getNodeLogFile "$i" "$cmd")")
|
||||
fi
|
||||
|
||||
# 1 == bootstrap leader, wait until it boots before starting
|
||||
# other validators
|
||||
if [[ "$i" -eq 1 ]]; then
|
||||
SECONDS=
|
||||
waitForNodeToInit "$initCompleteFile"
|
||||
fi
|
||||
done
|
||||
|
||||
waitForAllNodesToInit
|
||||
@@ -294,20 +300,19 @@ flag_error() {
|
||||
}
|
||||
|
||||
if ! $skipSetup; then
|
||||
multinode-demo/setup.sh --hashes-per-tick auto
|
||||
multinode-demo/setup.sh
|
||||
else
|
||||
verifyLedger
|
||||
fi
|
||||
startNodes
|
||||
lastTransactionCount=
|
||||
enforceTransactionCountAdvance=true
|
||||
while [[ $iteration -le $iterations ]]; do
|
||||
echo "--- Node count ($iteration)"
|
||||
(
|
||||
source multinode-demo/common.sh
|
||||
set -x
|
||||
client_keypair=/tmp/client-id.json-$$
|
||||
$solana_keygen -o $client_keypair || exit $?
|
||||
$solana_keygen new -f -o $client_keypair || exit $?
|
||||
$solana_gossip spy --num-nodes-exactly $numNodes || exit $?
|
||||
rm -rf $client_keypair
|
||||
) || flag_error
|
||||
@@ -336,36 +341,20 @@ while [[ $iteration -le $iterations ]]; do
|
||||
transactionCount=$(sed -e 's/{"jsonrpc":"2.0","result":\([0-9]*\),"id":1}/\1/' log-transactionCount.txt)
|
||||
if [[ -n $lastTransactionCount ]]; then
|
||||
echo "--- Transaction count check: $lastTransactionCount < $transactionCount"
|
||||
if $enforceTransactionCountAdvance; then
|
||||
if [[ $lastTransactionCount -ge $transactionCount ]]; then
|
||||
echo "Error: Transaction count is not advancing"
|
||||
echo "* lastTransactionCount: $lastTransactionCount"
|
||||
echo "* transactionCount: $transactionCount"
|
||||
flag_error
|
||||
fi
|
||||
else
|
||||
echo "enforceTransactionCountAdvance=false"
|
||||
fi
|
||||
enforceTransactionCountAdvance=true
|
||||
fi
|
||||
lastTransactionCount=$transactionCount
|
||||
|
||||
echo "--- Wallet sanity ($iteration)"
|
||||
flag_error_if_no_leader_rotation() {
|
||||
# TODO: Stop ignoring wallet sanity failures when leader rotation is enabled
|
||||
# once https://github.com/solana-labs/solana/issues/2474 is fixed
|
||||
if [[ -n $maybeNoLeaderRotation ]]; then
|
||||
flag_error
|
||||
else
|
||||
# Wallet error occurred (and was ignored) so transactionCount may not
|
||||
# advance on the next iteration
|
||||
enforceTransactionCountAdvance=false
|
||||
fi
|
||||
}
|
||||
(
|
||||
set -x
|
||||
timeout 60s scripts/wallet-sanity.sh --url http://127.0.0.1"$walletRpcPort"
|
||||
) || flag_error_if_no_leader_rotation
|
||||
) || flag_error
|
||||
|
||||
iteration=$((iteration + 1))
|
||||
|
||||
|
16
ci/nits.sh
16
ci/nits.sh
@@ -23,11 +23,13 @@ declare print_free_tree=(
|
||||
'metrics/src'
|
||||
'netutil/src'
|
||||
'runtime/src'
|
||||
'sdk/bpf/rust/rust-utils'
|
||||
'sdk/src'
|
||||
'programs/vote_api/src'
|
||||
'programs/vote_program/src'
|
||||
'programs/bpf/rust'
|
||||
'programs/stake_api/src'
|
||||
'programs/stake_program/src'
|
||||
'programs/vote_api/src'
|
||||
'programs/vote_program/src'
|
||||
)
|
||||
|
||||
if _ git --no-pager grep -n --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then
|
||||
@@ -44,16 +46,22 @@ if _ git --no-pager grep -n 'Default::default()' -- '*.rs'; then
|
||||
fi
|
||||
|
||||
# Let's keep a .gitignore for every crate, ensure it's got
|
||||
# /target/ in it
|
||||
# /target/ and /farf/ in it
|
||||
declare gitignores_ok=true
|
||||
for i in $(git --no-pager ls-files \*/Cargo.toml ); do
|
||||
dir=$(dirname "$i")
|
||||
if [[ ! -f $dir/.gitignore ]]; then
|
||||
echo 'error: nits.sh .gitnore missing for crate '"$dir" >&2
|
||||
gitignores_ok=false
|
||||
elif ! grep -q -e '^/target/$' "$dir"/.gitignore; then
|
||||
else
|
||||
if ! grep -q -e '^/target/$' "$dir"/.gitignore; then
|
||||
echo 'error: nits.sh "/target/" apparently missing from '"$dir"'/.gitignore' >&2
|
||||
gitignores_ok=false
|
||||
fi
|
||||
if ! grep -q -e '^/farf/$' "$dir"/.gitignore ; then
|
||||
echo 'error: nits.sh "/farf/" apparently missing from '"$dir"'/.gitignore' >&2
|
||||
gitignores_ok=false
|
||||
fi
|
||||
fi
|
||||
done
|
||||
"$gitignores_ok"
|
||||
|
65
ci/order-crates-for-publishing.py
Executable file
65
ci/order-crates-for-publishing.py
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This script figures the order in which workspace crates must be published to
|
||||
# crates.io. Along the way it also ensures there are no circular dependencies
|
||||
# that would cause a |cargo publish| to fail.
|
||||
#
|
||||
# On success an ordered list of Cargo.toml files is written to stdout
|
||||
#
|
||||
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import sys;
|
||||
|
||||
def load_metadata():
|
||||
return json.loads(subprocess.Popen(
|
||||
'cargo metadata --no-deps --format-version=1',
|
||||
shell=True, stdout=subprocess.PIPE).communicate()[0])
|
||||
|
||||
def get_packages():
|
||||
metadata = load_metadata()
|
||||
|
||||
manifest_path = dict()
|
||||
|
||||
# Build dictionary of packages and their immediate solana-only dependencies
|
||||
dependency_graph = dict()
|
||||
for pkg in metadata['packages']:
|
||||
manifest_path[pkg['name']] = pkg['manifest_path'];
|
||||
dependency_graph[pkg['name']] = [x['name'] for x in pkg['dependencies'] if x['name'].startswith('solana')];
|
||||
|
||||
# Check for direct circular dependencies
|
||||
circular_dependencies = set()
|
||||
for package, dependencies in dependency_graph.items():
|
||||
for dependency in dependencies:
|
||||
if dependency in dependency_graph and package in dependency_graph[dependency]:
|
||||
circular_dependencies.add(' <--> '.join(sorted([package, dependency])))
|
||||
|
||||
for dependency in circular_dependencies:
|
||||
sys.stderr.write('Error: Circular dependency: {}\n'.format(dependency))
|
||||
|
||||
if len(circular_dependencies) != 0:
|
||||
sys.exit(1)
|
||||
|
||||
# Order dependencies
|
||||
sorted_dependency_graph = []
|
||||
max_iterations = pow(len(dependency_graph),2)
|
||||
while dependency_graph:
|
||||
if max_iterations == 0:
|
||||
# TODO: Be more helpful and find the actual cycle for the user
|
||||
sys.exit('Error: Circular dependency suspected between these packages: {}\n'.format(' '.join(dependency_graph.keys())))
|
||||
|
||||
max_iterations -= 1
|
||||
for package, dependencies in dependency_graph.items():
|
||||
for dependency in dependencies:
|
||||
if dependency in dependency_graph:
|
||||
break
|
||||
else:
|
||||
del dependency_graph[package]
|
||||
sorted_dependency_graph.append((package, manifest_path[package]))
|
||||
|
||||
|
||||
return sorted_dependency_graph
|
||||
|
||||
for package, manifest in get_packages():
|
||||
print os.path.relpath(manifest)
|
@@ -2,8 +2,50 @@
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
BOOK="book"
|
||||
|
||||
book/build.sh
|
||||
source ci/rust-version.sh stable
|
||||
eval "$(ci/channel-info.sh)"
|
||||
|
||||
if [[ -n $PUBLISH_BOOK_TAG ]]; then
|
||||
CURRENT_TAG="$(git describe --tags)"
|
||||
COMMIT_TO_PUBLISH="$(git rev-list -n 1 "${PUBLISH_BOOK_TAG}")"
|
||||
|
||||
# book is manually published at a specified release tag
|
||||
if [[ $PUBLISH_BOOK_TAG != "$CURRENT_TAG" ]]; then
|
||||
(
|
||||
cat <<EOF
|
||||
steps:
|
||||
- trigger: "$BUILDKITE_PIPELINE_SLUG"
|
||||
async: true
|
||||
build:
|
||||
message: "$BUILDKITE_MESSAGE"
|
||||
commit: "$COMMIT_TO_PUBLISH"
|
||||
env:
|
||||
PUBLISH_BOOK_TAG: "$PUBLISH_BOOK_TAG"
|
||||
EOF
|
||||
) | buildkite-agent pipeline upload
|
||||
exit 0
|
||||
fi
|
||||
repo=git@github.com:solana-labs/book.git
|
||||
else
|
||||
# book-edge and book-beta are published automatically on the tip of the branch
|
||||
case $CHANNEL in
|
||||
edge)
|
||||
repo=git@github.com:solana-labs/book-edge.git
|
||||
;;
|
||||
beta)
|
||||
repo=git@github.com:solana-labs/book-beta.git
|
||||
;;
|
||||
*)
|
||||
echo "--- publish skipped"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
BOOK=$CHANNEL
|
||||
fi
|
||||
|
||||
ci/docker-run.sh "$rust_stable_docker_image" bash -exc "book/build.sh"
|
||||
|
||||
echo --- create book repo
|
||||
(
|
||||
@@ -13,25 +55,10 @@ echo --- create book repo
|
||||
git config user.email "maintainers@solana.com"
|
||||
git config user.name "$(basename "$0")"
|
||||
git add ./* ./.nojekyll
|
||||
git commit -m "${BUILDKITE_COMMIT:-local}"
|
||||
git commit -m "${CI_COMMIT:-local}"
|
||||
)
|
||||
|
||||
eval "$(ci/channel-info.sh)"
|
||||
# Only publish the book from the edge and beta channels for now.
|
||||
case $CHANNEL in
|
||||
edge)
|
||||
repo=git@github.com:solana-labs/book-edge.git
|
||||
;;
|
||||
beta)
|
||||
repo=git@github.com:solana-labs/book.git
|
||||
;;
|
||||
*)
|
||||
echo "--- publish skipped"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "--- publish $CHANNEL"
|
||||
echo "--- publish $BOOK"
|
||||
cd book/html/
|
||||
git remote add origin $repo
|
||||
git fetch origin master
|
||||
|
@@ -2,44 +2,23 @@
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
source ci/semver_bash/semver.sh
|
||||
source ci/rust-version.sh stable
|
||||
|
||||
# List of internal crates to publish
|
||||
#
|
||||
# IMPORTANT: the order of the CRATES *is* significant. Crates must be published
|
||||
# before the crates that depend on them. Note that this information is already
|
||||
# expressed in the various Cargo.toml files, and ideally would not be duplicated
|
||||
# here. (TODO: figure the crate ordering dynamically)
|
||||
#
|
||||
CRATES=(
|
||||
kvstore
|
||||
logger
|
||||
netutil
|
||||
sdk
|
||||
keygen
|
||||
metrics
|
||||
client
|
||||
drone
|
||||
programs/{budget_api,config_api,stake_api,storage_api,token_api,vote_api,exchange_api}
|
||||
programs/{vote_program,budget_program,bpf_loader,config_program,exchange_program,failure_program}
|
||||
programs/{noop_program,stake_program,storage_program,token_program}
|
||||
runtime
|
||||
vote-signer
|
||||
core
|
||||
validator
|
||||
genesis
|
||||
gossip
|
||||
ledger-tool
|
||||
wallet
|
||||
install
|
||||
)
|
||||
# shellcheck disable=SC2086
|
||||
is_crate_version_uploaded() {
|
||||
name=$1
|
||||
version=$2
|
||||
curl https://crates.io/api/v1/crates/${name}/${version} | \
|
||||
python3 -c "import sys,json; print('version' in json.load(sys.stdin));"
|
||||
}
|
||||
|
||||
# Only package/publish if this is a tagged release
|
||||
[[ -n $TRIGGERED_BUILDKITE_TAG ]] || {
|
||||
echo TRIGGERED_BUILDKITE_TAG unset, skipped
|
||||
[[ -n $CI_TAG ]] || {
|
||||
echo CI_TAG unset, skipped
|
||||
exit 0
|
||||
}
|
||||
|
||||
semverParseInto "$TRIGGERED_BUILDKITE_TAG" MAJOR MINOR PATCH SPECIAL
|
||||
semverParseInto "$CI_TAG" MAJOR MINOR PATCH SPECIAL
|
||||
expectedCrateVersion="$MAJOR.$MINOR.$PATCH$SPECIAL"
|
||||
|
||||
[[ -n "$CRATES_IO_TOKEN" ]] || {
|
||||
@@ -47,27 +26,60 @@ expectedCrateVersion="$MAJOR.$MINOR.$PATCH$SPECIAL"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cargoCommand="cargo publish --token $CRATES_IO_TOKEN"
|
||||
Cargo_tomls=$(ci/order-crates-for-publishing.py)
|
||||
|
||||
for crate in "${CRATES[@]}"; do
|
||||
if [[ ! -r $crate/Cargo.toml ]]; then
|
||||
echo "Error: $crate/Cargo.toml does not exist"
|
||||
exit 1
|
||||
fi
|
||||
echo "-- $crate"
|
||||
grep -q "^version = \"$expectedCrateVersion\"$" "$crate"/Cargo.toml || {
|
||||
echo "Error: $crate/Cargo.toml version is not $expectedCrateVersion"
|
||||
for Cargo_toml in $Cargo_tomls; do
|
||||
echo "--- $Cargo_toml"
|
||||
grep -q "^version = \"$expectedCrateVersion\"$" "$Cargo_toml" || {
|
||||
echo "Error: $Cargo_toml version is not $expectedCrateVersion"
|
||||
exit 1
|
||||
}
|
||||
|
||||
crate_name=$(grep -m 1 '^name = ' "$Cargo_toml" | cut -f 3 -d ' ' | tr -d \")
|
||||
|
||||
if grep -q "^publish = false" "$Cargo_toml"; then
|
||||
echo "$crate_name is is marked as unpublishable"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ $(is_crate_version_uploaded "$crate_name" "$expectedCrateVersion") = True ]] ; then
|
||||
echo "${crate_name} version ${expectedCrateVersion} is already on crates.io"
|
||||
continue
|
||||
fi
|
||||
|
||||
(
|
||||
set -x
|
||||
crate=$(dirname "$Cargo_toml")
|
||||
# TODO: the rocksdb package does not build with the stock rust docker image,
|
||||
# so use the solana rust docker image until this is resolved upstream
|
||||
source ci/rust-version.sh
|
||||
cargoCommand="cargo publish --token $CRATES_IO_TOKEN"
|
||||
ci/docker-run.sh "$rust_stable_docker_image" bash -exc "cd $crate; $cargoCommand"
|
||||
#ci/docker-run.sh rust bash -exc "cd $crate; $cargoCommand"
|
||||
)
|
||||
) || true # <-- Don't fail. We want to be able to retry the job in cases when a publish fails halfway due to network/cloud issues
|
||||
|
||||
numRetries=30
|
||||
for ((i = 1 ; i <= numRetries ; i++)); do
|
||||
echo "Attempt ${i} of ${numRetries}"
|
||||
if [[ $(is_crate_version_uploaded "$crate_name" "$expectedCrateVersion") = True ]] ; then
|
||||
echo "Found ${crate_name} version ${expectedCrateVersion} on crates.io REST API"
|
||||
|
||||
really_uploaded=0
|
||||
(
|
||||
set -x
|
||||
rm -rf crate-test
|
||||
cargo +"$rust_stable" init crate-test
|
||||
cd crate-test/
|
||||
echo "${crate_name} = \"${expectedCrateVersion}\"" >> Cargo.toml
|
||||
echo "[workspace]" >> Cargo.toml
|
||||
cargo +"$rust_stable" check
|
||||
) && really_uploaded=1
|
||||
if ((really_uploaded)); then
|
||||
break;
|
||||
fi
|
||||
echo "${crate_name} not yet available for download from crates.io"
|
||||
fi
|
||||
echo "Did not find ${crate_name} version ${expectedCrateVersion} on crates.io. Sleeping for 2 seconds."
|
||||
sleep 2
|
||||
done
|
||||
done
|
||||
|
||||
exit 0
|
||||
|
@@ -45,7 +45,9 @@ beta)
|
||||
CHANNEL_BRANCH=$BETA_CHANNEL
|
||||
;;
|
||||
stable)
|
||||
CHANNEL_BRANCH=$STABLE_CHANNEL
|
||||
# Set to whatever branch 'testnet' is on.
|
||||
# TODO: Revert to $STABLE_CHANNEL for TdS
|
||||
CHANNEL_BRANCH=$BETA_CHANNEL
|
||||
;;
|
||||
*)
|
||||
echo "Error: Invalid PUBLISH_CHANNEL=$PUBLISH_CHANNEL"
|
||||
@@ -53,7 +55,7 @@ stable)
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ $BUILDKITE_BRANCH != "$CHANNEL_BRANCH" ]]; then
|
||||
if [[ $CI_BRANCH != "$CHANNEL_BRANCH" ]]; then
|
||||
(
|
||||
cat <<EOF
|
||||
steps:
|
||||
|
@@ -3,8 +3,20 @@ set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [[ -n $APPVEYOR ]]; then
|
||||
# Bootstrap rust build environment
|
||||
source ci/env.sh
|
||||
source ci/rust-version.sh
|
||||
|
||||
appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
./rustup-init -yv --default-toolchain $rust_stable --default-host x86_64-pc-windows-msvc
|
||||
export PATH="$PATH:$USERPROFILE/.cargo/bin"
|
||||
rustc -vV
|
||||
cargo -vV
|
||||
fi
|
||||
|
||||
DRYRUN=
|
||||
if [[ -z $BUILDKITE_BRANCH ]]; then
|
||||
if [[ -z $CI_BRANCH ]]; then
|
||||
DRYRUN="echo"
|
||||
CHANNEL=unknown
|
||||
fi
|
||||
@@ -12,12 +24,9 @@ fi
|
||||
eval "$(ci/channel-info.sh)"
|
||||
|
||||
TAG=
|
||||
if [[ -n "$BUILDKITE_TAG" ]]; then
|
||||
CHANNEL_OR_TAG=$BUILDKITE_TAG
|
||||
TAG="$BUILDKITE_TAG"
|
||||
elif [[ -n "$TRIGGERED_BUILDKITE_TAG" ]]; then
|
||||
CHANNEL_OR_TAG=$TRIGGERED_BUILDKITE_TAG
|
||||
TAG="$TRIGGERED_BUILDKITE_TAG"
|
||||
if [[ -n "$CI_TAG" ]]; then
|
||||
CHANNEL_OR_TAG=$CI_TAG
|
||||
TAG="$CI_TAG"
|
||||
else
|
||||
CHANNEL_OR_TAG=$CHANNEL
|
||||
fi
|
||||
@@ -27,15 +36,21 @@ if [[ -z $CHANNEL_OR_TAG ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$(uname)" in
|
||||
Darwin)
|
||||
PERF_LIBS=false
|
||||
case "$CI_OS_NAME" in
|
||||
osx)
|
||||
TARGET=x86_64-apple-darwin
|
||||
;;
|
||||
Linux)
|
||||
linux)
|
||||
TARGET=x86_64-unknown-linux-gnu
|
||||
PERF_LIBS=true
|
||||
;;
|
||||
windows)
|
||||
TARGET=x86_64-pc-windows-msvc
|
||||
;;
|
||||
*)
|
||||
TARGET=unknown-unknown-unknown
|
||||
echo CI_OS_NAME unset
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -56,6 +71,13 @@ echo --- Creating tarball
|
||||
source ci/rust-version.sh stable
|
||||
scripts/cargo-install-all.sh +"$rust_stable" solana-release
|
||||
|
||||
# Reduce the archive size until
|
||||
# https://github.com/appveyor/ci/issues/2997 is fixed
|
||||
if [[ -n $APPVEYOR ]]; then
|
||||
rm -f solana-release/bin/solana-validator.exe solana-release/bin/solana-bench-exchange.exe
|
||||
fi
|
||||
|
||||
if $PERF_LIBS; then
|
||||
rm -rf target/perf-libs
|
||||
./fetch-perf-libs.sh
|
||||
mkdir solana-release/target
|
||||
@@ -68,6 +90,8 @@ echo --- Creating tarball
|
||||
cargo +"$rust_stable" install --path . --features=cuda --root ../solana-release-cuda
|
||||
)
|
||||
cp solana-release-cuda/bin/solana-validator solana-release/bin/solana-validator-cuda
|
||||
fi
|
||||
|
||||
cp -a scripts multinode-demo solana-release/
|
||||
|
||||
# Add a wrapper script for validator.sh
|
||||
@@ -77,36 +101,35 @@ echo --- Creating tarball
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
export USE_INSTALL=1
|
||||
export REQUIRE_CONFIG_DIR=1
|
||||
exec multinode-demo/validator.sh "$@"
|
||||
EOF
|
||||
chmod +x solana-release/bin/validator.sh
|
||||
|
||||
# Add a wrapper script for clear-config.sh
|
||||
# TODO: Remove multinode/... from tarball
|
||||
cat > solana-release/bin/clear-config.sh <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
export USE_INSTALL=1
|
||||
exec multinode-demo/clear-validator-config.sh "$@"
|
||||
EOF
|
||||
chmod +x solana-release/bin/clear-config.sh
|
||||
|
||||
tar jvcf solana-release-$TARGET.tar.bz2 solana-release/
|
||||
cp solana-release/bin/solana-install solana-install-$TARGET
|
||||
tar cvf solana-release-$TARGET.tar solana-release
|
||||
bzip2 solana-release-$TARGET.tar
|
||||
cp solana-release/bin/solana-install-init solana-install-init-$TARGET
|
||||
)
|
||||
|
||||
echo --- Saving build artifacts
|
||||
source ci/upload-ci-artifact.sh
|
||||
upload-ci-artifact solana-release-$TARGET.tar.bz2
|
||||
|
||||
if [[ -n $DO_NOT_PUBLISH_TAR ]]; then
|
||||
echo Skipped due to DO_NOT_PUBLISH_TAR
|
||||
exit 0
|
||||
# Metrics tarball is platform agnostic, only publish it from Linux
|
||||
MAYBE_METRICS_TARBALL=
|
||||
if [[ "$CI_OS_NAME" = linux ]]; then
|
||||
metrics/create-metrics-tarball.sh
|
||||
MAYBE_METRICS_TARBALL=solana-metrics.tar.bz2
|
||||
fi
|
||||
|
||||
for file in solana-release-$TARGET.tar.bz2 solana-install-$TARGET; do
|
||||
echo --- AWS S3 Store: $file
|
||||
source ci/upload-ci-artifact.sh
|
||||
|
||||
for file in solana-release-$TARGET.tar.bz2 solana-install-init-"$TARGET"* $MAYBE_METRICS_TARBALL; do
|
||||
upload-ci-artifact "$file"
|
||||
|
||||
if [[ -n $DO_NOT_PUBLISH_TAR ]]; then
|
||||
echo "Skipped $file due to DO_NOT_PUBLISH_TAR"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ -n $BUILDKITE ]]; then
|
||||
echo --- AWS S3 Store: "$file"
|
||||
(
|
||||
set -x
|
||||
$DRYRUN docker run \
|
||||
@@ -122,7 +145,22 @@ for file in solana-release-$TARGET.tar.bz2 solana-install-$TARGET; do
|
||||
)
|
||||
|
||||
if [[ -n $TAG ]]; then
|
||||
ci/upload-github-release-asset.sh $file
|
||||
ci/upload-github-release-asset.sh "$file"
|
||||
fi
|
||||
elif [[ -n $TRAVIS ]]; then
|
||||
# .travis.yml uploads everything in the travis-s3-upload/ directory to release.solana.com
|
||||
mkdir -p travis-s3-upload/"$CHANNEL_OR_TAG"
|
||||
cp -v "$file" travis-s3-upload/"$CHANNEL_OR_TAG"/
|
||||
|
||||
if [[ -n $TAG ]]; then
|
||||
# .travis.yaml uploads everything in the travis-release-upload/ directory to
|
||||
# the associated Github Release
|
||||
mkdir -p travis-release-upload/
|
||||
cp -v "$file" travis-release-upload/
|
||||
fi
|
||||
elif [[ -n $APPVEYOR ]]; then
|
||||
# Add artifacts for .appveyor.yml to upload
|
||||
appveyor PushArtifact "$file" -FileName "$CHANNEL_OR_TAG"/"$file"
|
||||
fi
|
||||
done
|
||||
|
||||
|
@@ -13,11 +13,14 @@
|
||||
# $ source ci/rust-version.sh
|
||||
#
|
||||
|
||||
export rust_stable=1.34.0
|
||||
export rust_stable_docker_image=solanalabs/rust:1.34.0
|
||||
stable_version=1.36.0
|
||||
nightly_version=2019-07-19
|
||||
|
||||
export rust_nightly=nightly-2019-05-01
|
||||
export rust_nightly_docker_image=solanalabs/rust-nightly:2019-05-01
|
||||
export rust_stable="$stable_version"
|
||||
export rust_stable_docker_image=solanalabs/rust:"$stable_version"
|
||||
|
||||
export rust_nightly=nightly-"$nightly_version"
|
||||
export rust_nightly_docker_image=solanalabs/rust-nightly:"$nightly_version"
|
||||
|
||||
[[ -z $1 ]] || (
|
||||
|
||||
|
@@ -30,8 +30,8 @@ set -o pipefail
|
||||
export RUST_BACKTRACE=1
|
||||
|
||||
UPLOAD_METRICS=""
|
||||
TARGET_BRANCH=$BUILDKITE_BRANCH
|
||||
if [[ -z $BUILDKITE_BRANCH ]] || ./ci/is-pr.sh; then
|
||||
TARGET_BRANCH=$CI_BRANCH
|
||||
if [[ -z $CI_BRANCH ]] || [[ -n $CI_PULL_REQUEST ]]; then
|
||||
TARGET_BRANCH=$EDGE_CHANNEL
|
||||
else
|
||||
UPLOAD_METRICS="upload"
|
||||
@@ -40,6 +40,10 @@ fi
|
||||
BENCH_FILE=bench_output.log
|
||||
BENCH_ARTIFACT=current_bench_results.log
|
||||
|
||||
# Clear the C dependency files, if dependeny moves these files are not regenerated
|
||||
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
|
||||
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
|
||||
|
||||
# Ensure all dependencies are built
|
||||
_ cargo +$rust_nightly build --all --release
|
||||
|
||||
|
@@ -5,15 +5,47 @@ cd "$(dirname "$0")/.."
|
||||
|
||||
source ci/_
|
||||
source ci/rust-version.sh stable
|
||||
source ci/rust-version.sh nightly
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
export RUSTFLAGS="-D warnings"
|
||||
|
||||
do_bpf_check() {
|
||||
_ cargo +"$rust_stable" fmt --all -- --check
|
||||
_ cargo +"$rust_nightly" test --all
|
||||
_ cargo +"$rust_nightly" clippy --version
|
||||
_ cargo +"$rust_nightly" clippy --all -- --deny=warnings
|
||||
_ cargo +"$rust_stable" audit
|
||||
}
|
||||
|
||||
(
|
||||
(
|
||||
cd sdk/bpf/rust/rust-no-std
|
||||
do_bpf_check
|
||||
)
|
||||
(
|
||||
cd sdk/bpf/rust/rust-utils
|
||||
do_bpf_check
|
||||
)
|
||||
(
|
||||
cd sdk/bpf/rust/rust-test
|
||||
do_bpf_check
|
||||
)
|
||||
for project in programs/bpf/rust/*/ ; do
|
||||
(
|
||||
cd "$project"
|
||||
do_bpf_check
|
||||
)
|
||||
done
|
||||
)
|
||||
|
||||
_ cargo +"$rust_stable" fmt --all -- --check
|
||||
_ cargo +"$rust_stable" clippy --all -- --version
|
||||
_ cargo +"$rust_stable" clippy --version
|
||||
_ cargo +"$rust_stable" clippy --all -- --deny=warnings
|
||||
_ cargo +"$rust_stable" audit
|
||||
_ cargo +"$rust_stable" audit --version
|
||||
_ cargo +"$rust_stable" audit --ignore RUSTSEC-2019-0011 # https://github.com/solana-labs/solana/issues/5207
|
||||
_ ci/nits.sh
|
||||
_ ci/order-crates-for-publishing.py
|
||||
_ book/build.sh
|
||||
|
||||
echo --- ok
|
||||
|
@@ -25,7 +25,7 @@ source scripts/ulimit-n.sh
|
||||
|
||||
scripts/coverage.sh
|
||||
|
||||
report=coverage-"${BUILDKITE_COMMIT:0:9}".tar.gz
|
||||
report=coverage-"${CI_COMMIT:0:9}".tar.gz
|
||||
mv target/cov/report.tar.gz "$report"
|
||||
upload-ci-artifact "$report"
|
||||
annotate --style success --context lcov-report \
|
||||
@@ -39,5 +39,5 @@ else
|
||||
bash <(curl -s https://codecov.io/bash) -X gcov -f target/cov/lcov.info
|
||||
|
||||
annotate --style success --context codecov.io \
|
||||
"CodeCov report: https://codecov.io/github/solana-labs/solana/commit/${BUILDKITE_COMMIT:0:9}"
|
||||
"CodeCov report: https://codecov.io/github/solana-labs/solana/commit/${CI_COMMIT:0:9}"
|
||||
fi
|
||||
|
@@ -19,14 +19,21 @@ source scripts/ulimit-n.sh
|
||||
# Clear cached json keypair files
|
||||
rm -rf "$HOME/.config/solana"
|
||||
|
||||
# Run tbe appropriate test based on entrypoint
|
||||
# Clear the C dependency files, if dependeny moves these files are not regenerated
|
||||
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
|
||||
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
|
||||
|
||||
# Clear the BPF sysroot files, they are not automatically rebuilt
|
||||
rm -rf target/xargo # Issue #3105
|
||||
|
||||
# Run the appropriate test based on entrypoint
|
||||
testName=$(basename "$0" .sh)
|
||||
case $testName in
|
||||
test-stable)
|
||||
echo "Executing $testName"
|
||||
|
||||
_ cargo +"$rust_stable" build --all ${V:+--verbose}
|
||||
_ cargo +"$rust_stable" test --all ${V:+--verbose} -- --nocapture --test-threads=1
|
||||
_ cargo +"$rust_stable" test --all ${V:+--verbose} -- --nocapture
|
||||
;;
|
||||
test-stable-perf)
|
||||
echo "Executing $testName"
|
||||
@@ -35,8 +42,10 @@ test-stable-perf)
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
ci/test-stable-perf.sh \
|
||||
ci/test-stable.sh \
|
||||
^ci/test-stable-perf.sh \
|
||||
^ci/test-stable.sh \
|
||||
^core/build.rs \
|
||||
^fetch-perf-libs.sh \
|
||||
^programs/ \
|
||||
^sdk/ \
|
||||
|| {
|
||||
@@ -52,10 +61,8 @@ test-stable-perf)
|
||||
--no-default-features --features=bpf_c,bpf_rust
|
||||
|
||||
# Run root package tests with these features
|
||||
ROOT_FEATURES=erasure,chacha
|
||||
if [[ $(uname) = Darwin ]]; then
|
||||
./build-perf-libs.sh
|
||||
else
|
||||
ROOT_FEATURES=
|
||||
if [[ $(uname) = Linux ]]; then
|
||||
# Enable persistence mode to keep the CUDA kernel driver loaded, avoiding a
|
||||
# lengthy and unexpected delay the first time CUDA is involved when the driver
|
||||
# is not yet loaded.
|
||||
@@ -65,12 +72,12 @@ test-stable-perf)
|
||||
./fetch-perf-libs.sh
|
||||
# shellcheck source=/dev/null
|
||||
source ./target/perf-libs/env.sh
|
||||
ROOT_FEATURES=$ROOT_FEATURES,cuda
|
||||
ROOT_FEATURES=cuda
|
||||
fi
|
||||
|
||||
# Run root package library tests
|
||||
_ cargo +"$rust_stable" build --all ${V:+--verbose} --features="$ROOT_FEATURES"
|
||||
_ cargo +"$rust_stable" test --manifest-path=core/Cargo.toml ${V:+--verbose} --features="$ROOT_FEATURES" -- --nocapture --test-threads=1
|
||||
_ cargo +"$rust_stable" test --manifest-path=core/Cargo.toml ${V:+--verbose} --features="$ROOT_FEATURES" -- --nocapture
|
||||
;;
|
||||
*)
|
||||
echo "Error: Unknown test: $testName"
|
||||
|
@@ -24,6 +24,14 @@ blockstreamer=false
|
||||
deployUpdateManifest=true
|
||||
fetchLogs=true
|
||||
maybeHashesPerTick=
|
||||
maybeDisableAirdrops=
|
||||
maybeInternalNodesStakeLamports=
|
||||
maybeInternalNodesLamports=
|
||||
maybeExternalPrimordialAccountsFile=
|
||||
maybeLamports=
|
||||
maybeLetsEncrypt=
|
||||
maybeFullnodeAdditionalDiskSize=
|
||||
maybeNoSnapshot=
|
||||
|
||||
usage() {
|
||||
exitcode=0
|
||||
@@ -62,11 +70,28 @@ Deploys a CD testnet
|
||||
-s - Skip start. Nodes will still be created or configured, but network software will not be started.
|
||||
-S - Stop network software without tearing down nodes.
|
||||
-f - Discard validator nodes that didn't bootup successfully
|
||||
-w - Skip time-consuming "bells and whistles" that are
|
||||
unnecessary for a high-node count demo testnet
|
||||
|
||||
--no-airdrop
|
||||
- If set, disables airdrops. Nodes must be funded in genesis block when airdrops are disabled.
|
||||
--internal-nodes-stake-lamports NUM_LAMPORTS
|
||||
- Amount to stake internal nodes.
|
||||
--internal-nodes-lamports NUM_LAMPORTS
|
||||
- Amount to fund internal nodes in genesis block
|
||||
--external-accounts-file FILE_PATH
|
||||
- Path to external Primordial Accounts file, if it exists.
|
||||
--hashes-per-tick NUM_HASHES|sleep|auto
|
||||
- Override the default --hashes-per-tick for the cluster
|
||||
--lamports NUM_LAMPORTS
|
||||
- Specify the number of lamports to mint (default 100000000000000)
|
||||
--skip-deploy-update
|
||||
- If set, will skip software update deployment
|
||||
--skip-remote-log-retrieval
|
||||
- If set, will not fetch logs from remote nodes
|
||||
--letsencrypt [dns name]
|
||||
- Attempt to generate a TLS certificate using this DNS name
|
||||
--fullnode-additional-disk-size-gb [number]
|
||||
- Size of additional disk in GB for all fullnodes
|
||||
--no-snapshot
|
||||
- If set, disables booting validators from a snapshot
|
||||
|
||||
Note: the SOLANA_METRICS_CONFIG environment variable is used to configure
|
||||
metrics
|
||||
@@ -82,6 +107,39 @@ while [[ -n $1 ]]; do
|
||||
if [[ $1 = --hashes-per-tick ]]; then
|
||||
maybeHashesPerTick="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 = --lamports ]]; then
|
||||
maybeLamports="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 = --no-airdrop ]]; then
|
||||
maybeDisableAirdrops="$1"
|
||||
shift 1
|
||||
elif [[ $1 = --internal-nodes-stake-lamports ]]; then
|
||||
maybeInternalNodesStakeLamports="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 = --internal-nodes-lamports ]]; then
|
||||
maybeInternalNodesLamports="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 = --external-accounts-file ]]; then
|
||||
maybeExternalPrimordialAccountsFile="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 = --skip-deploy-update ]]; then
|
||||
deployUpdateManifest=false
|
||||
shift 1
|
||||
elif [[ $1 = --skip-remote-log-retrieval ]]; then
|
||||
fetchLogs=false
|
||||
shift 1
|
||||
elif [[ $1 = --letsencrypt ]]; then
|
||||
maybeLetsEncrypt="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 = --fullnode-additional-disk-size-gb ]]; then
|
||||
maybeFullnodeAdditionalDiskSize="$1 $2"
|
||||
shift 2
|
||||
elif [[ $1 == --machine-type* ]]; then # Bypass quoted long args for GPUs
|
||||
shortArgs+=("$1")
|
||||
shift
|
||||
elif [[ $1 = --no-snapshot ]]; then
|
||||
maybeNoSnapshot="$1"
|
||||
shift 1
|
||||
else
|
||||
usage "Unknown long option: $1"
|
||||
fi
|
||||
@@ -228,6 +286,11 @@ if ! $skipCreate; then
|
||||
# shellcheck disable=SC2206
|
||||
create_args+=(${zone_args[@]})
|
||||
|
||||
if [[ -n $maybeLetsEncrypt ]]; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote $maybeLetsEncrypt
|
||||
create_args+=($maybeLetsEncrypt)
|
||||
fi
|
||||
|
||||
if $blockstreamer; then
|
||||
create_args+=(-u)
|
||||
fi
|
||||
@@ -256,6 +319,11 @@ if ! $skipCreate; then
|
||||
create_args+=(-f)
|
||||
fi
|
||||
|
||||
if [[ -n $maybeFullnodeAdditionalDiskSize ]]; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote
|
||||
create_args+=($maybeFullnodeAdditionalDiskSize)
|
||||
fi
|
||||
|
||||
time net/"$cloudProvider".sh create "${create_args[@]}"
|
||||
else
|
||||
echo "--- $cloudProvider.sh config"
|
||||
@@ -311,11 +379,13 @@ if ! $skipStart; then
|
||||
if [[ -n $NO_LEDGER_VERIFY ]]; then
|
||||
args+=(-o noLedgerVerify)
|
||||
fi
|
||||
if [[ -n $NO_INSTALL_CHECK ]]; then
|
||||
args+=(-o noInstallCheck)
|
||||
fi
|
||||
if [[ -n $maybeHashesPerTick ]]; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote $maybeHashesPerTick
|
||||
args+=($maybeHashesPerTick)
|
||||
fi
|
||||
|
||||
if $reuseLedger; then
|
||||
args+=(-r)
|
||||
fi
|
||||
@@ -324,13 +394,39 @@ if ! $skipStart; then
|
||||
args+=(-F)
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2154 # SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu comes from .buildkite/env/
|
||||
if $deployUpdateManifest && [[ -n $SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu ]]; then
|
||||
echo "$SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu" > update_manifest_keypair.json
|
||||
args+=(-i update_manifest_keypair.json)
|
||||
if $deployUpdateManifest; then
|
||||
rm -f update_manifest_keypair.json
|
||||
args+=(--deploy-update linux)
|
||||
args+=(--deploy-update osx)
|
||||
args+=(--deploy-update windows)
|
||||
fi
|
||||
|
||||
if [[ -n $maybeDisableAirdrops ]]; then
|
||||
# shellcheck disable=SC2206
|
||||
args+=($maybeDisableAirdrops)
|
||||
fi
|
||||
if [[ -n $maybeInternalNodesStakeLamports ]]; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote $maybeInternalNodesStakeLamports
|
||||
args+=($maybeInternalNodesStakeLamports)
|
||||
fi
|
||||
if [[ -n $maybeInternalNodesLamports ]]; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote $maybeInternalNodesLamports
|
||||
args+=($maybeInternalNodesLamports)
|
||||
fi
|
||||
if [[ -n $maybeExternalPrimordialAccountsFile ]]; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote $maybeExternalPrimordialAccountsFile
|
||||
args+=($maybeExternalPrimordialAccountsFile)
|
||||
fi
|
||||
if [[ -n $maybeLamports ]]; then
|
||||
# shellcheck disable=SC2206 # Do not want to quote $maybeLamports
|
||||
args+=($maybeLamports)
|
||||
fi
|
||||
|
||||
if [[ -n $maybeNoSnapshot ]]; then
|
||||
# shellcheck disable=SC2206
|
||||
args+=($maybeNoSnapshot)
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086 # Don't want to double quote the $maybeXYZ variables
|
||||
time net/net.sh "${args[@]}"
|
||||
) || ok=false
|
||||
|
||||
|
@@ -44,6 +44,8 @@ steps:
|
||||
value: "testnet-beta-perf"
|
||||
- label: "testnet-demo"
|
||||
value: "testnet-demo"
|
||||
- label: "tds"
|
||||
value: "tds"
|
||||
- select: "Operation"
|
||||
key: "testnet-operation"
|
||||
default: "sanity-or-restart"
|
||||
@@ -153,6 +155,10 @@ testnet-demo)
|
||||
: "${GCE_NODE_COUNT:=150}"
|
||||
: "${GCE_LOW_QUOTA_NODE_COUNT:=70}"
|
||||
;;
|
||||
tds)
|
||||
CHANNEL_OR_TAG=beta
|
||||
CHANNEL_BRANCH=$BETA_CHANNEL
|
||||
;;
|
||||
*)
|
||||
echo "Error: Invalid TESTNET=$TESTNET"
|
||||
exit 1
|
||||
@@ -184,7 +190,7 @@ if [[ -n $TESTNET_TAG ]]; then
|
||||
CHANNEL_OR_TAG=$TESTNET_TAG
|
||||
else
|
||||
|
||||
if [[ $BUILDKITE_BRANCH != "$CHANNEL_BRANCH" ]]; then
|
||||
if [[ $CI_BRANCH != "$CHANNEL_BRANCH" ]]; then
|
||||
(
|
||||
cat <<EOF
|
||||
steps:
|
||||
@@ -212,6 +218,7 @@ sanity() {
|
||||
testnet-edge)
|
||||
(
|
||||
set -x
|
||||
NO_INSTALL_CHECK=1 \
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
ci/testnet-sanity.sh edge-testnet-solana-com ec2 us-west-1a
|
||||
)
|
||||
@@ -228,6 +235,7 @@ sanity() {
|
||||
testnet-beta)
|
||||
(
|
||||
set -x
|
||||
NO_INSTALL_CHECK=1 \
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
ci/testnet-sanity.sh beta-testnet-solana-com ec2 us-west-1a
|
||||
)
|
||||
@@ -285,6 +293,14 @@ sanity() {
|
||||
$ok
|
||||
)
|
||||
;;
|
||||
tds)
|
||||
(
|
||||
set -x
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
NO_VALIDATOR_SANITY=1 \
|
||||
ci/testnet-sanity.sh tds-solana-com gce "${GCE_ZONES[0]}" -f
|
||||
)
|
||||
;;
|
||||
*)
|
||||
echo "Error: Invalid TESTNET=$TESTNET"
|
||||
exit 1
|
||||
@@ -319,12 +335,12 @@ deploy() {
|
||||
(
|
||||
set -x
|
||||
ci/testnet-deploy.sh -p edge-testnet-solana-com -C ec2 -z us-west-1a \
|
||||
-t "$CHANNEL_OR_TAG" -n 3 -c 0 -u -P -a eipalloc-0ccd4f2239886fa94 \
|
||||
-t "$CHANNEL_OR_TAG" -n 3 -c 0 -u -P \
|
||||
-a eipalloc-0ccd4f2239886fa94 --letsencrypt edge.testnet.solana.com \
|
||||
${skipCreate:+-e} \
|
||||
${skipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
${maybeDelete:+-D} \
|
||||
--hashes-per-tick auto
|
||||
${maybeDelete:+-D}
|
||||
)
|
||||
;;
|
||||
testnet-edge-perf)
|
||||
@@ -338,8 +354,7 @@ deploy() {
|
||||
${skipCreate:+-e} \
|
||||
${skipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
${maybeDelete:+-D} \
|
||||
--hashes-per-tick auto
|
||||
${maybeDelete:+-D}
|
||||
)
|
||||
;;
|
||||
testnet-beta)
|
||||
@@ -347,12 +362,12 @@ deploy() {
|
||||
set -x
|
||||
NO_VALIDATOR_SANITY=1 \
|
||||
ci/testnet-deploy.sh -p beta-testnet-solana-com -C ec2 -z us-west-1a \
|
||||
-t "$CHANNEL_OR_TAG" -n 3 -c 0 -u -P -a eipalloc-0f286cf8a0771ce35 \
|
||||
-t "$CHANNEL_OR_TAG" -n 3 -c 0 -u -P \
|
||||
-a eipalloc-0f286cf8a0771ce35 --letsencrypt beta.testnet.solana.com \
|
||||
${skipCreate:+-e} \
|
||||
${skipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
${maybeDelete:+-D} \
|
||||
--hashes-per-tick auto
|
||||
${maybeDelete:+-D}
|
||||
)
|
||||
;;
|
||||
testnet-beta-perf)
|
||||
@@ -366,8 +381,7 @@ deploy() {
|
||||
${skipCreate:+-e} \
|
||||
${skipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
${maybeDelete:+-D} \
|
||||
--hashes-per-tick auto
|
||||
${maybeDelete:+-D}
|
||||
)
|
||||
;;
|
||||
testnet)
|
||||
@@ -380,7 +394,8 @@ deploy() {
|
||||
|
||||
# shellcheck disable=SC2068
|
||||
ci/testnet-deploy.sh -p testnet-solana-com -C ec2 ${EC2_ZONE_ARGS[@]} \
|
||||
-t "$CHANNEL_OR_TAG" -n "$EC2_NODE_COUNT" -c 0 -u -P -f -a eipalloc-0fa502bf95f6f18b2 \
|
||||
-t "$CHANNEL_OR_TAG" -n "$EC2_NODE_COUNT" -c 0 -u -P -f \
|
||||
-a eipalloc-0fa502bf95f6f18b2 --letsencrypt testnet.solana.com \
|
||||
${skipCreate:+-e} \
|
||||
${maybeSkipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
@@ -411,8 +426,7 @@ deploy() {
|
||||
${skipCreate:+-e} \
|
||||
${skipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
${maybeDelete:+-D} \
|
||||
--hashes-per-tick auto
|
||||
${maybeDelete:+-D}
|
||||
)
|
||||
;;
|
||||
testnet-demo)
|
||||
@@ -427,26 +441,147 @@ deploy() {
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
NO_VALIDATOR_SANITY=1 \
|
||||
ci/testnet-deploy.sh -p demo-testnet-solana-com -C gce ${GCE_ZONE_ARGS[@]} \
|
||||
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P -u -f -w \
|
||||
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P -u -f \
|
||||
--skip-deploy-update \
|
||||
--skip-remote-log-retrieval \
|
||||
-a demo-testnet-solana-com \
|
||||
${skipCreate:+-e} \
|
||||
${maybeSkipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
${maybeDelete:+-D} \
|
||||
--hashes-per-tick auto
|
||||
${maybeDelete:+-D}
|
||||
|
||||
if [[ -n $GCE_LOW_QUOTA_NODE_COUNT ]]; then
|
||||
# shellcheck disable=SC2068
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
NO_VALIDATOR_SANITY=1 \
|
||||
ci/testnet-deploy.sh -p demo-testnet-solana-com2 -C gce ${GCE_LOW_QUOTA_ZONE_ARGS[@]} \
|
||||
-t "$CHANNEL_OR_TAG" -n "$GCE_LOW_QUOTA_NODE_COUNT" -c 0 -P -f -x -w \
|
||||
-t "$CHANNEL_OR_TAG" -n "$GCE_LOW_QUOTA_NODE_COUNT" -c 0 -P -f -x \
|
||||
--skip-deploy-update \
|
||||
--skip-remote-log-retrieval \
|
||||
${skipCreate:+-e} \
|
||||
${skipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
${maybeDelete:+-D}
|
||||
fi
|
||||
)
|
||||
;;
|
||||
tds)
|
||||
(
|
||||
set -x
|
||||
|
||||
# Allow cluster configuration to be overridden from env vars
|
||||
|
||||
if [[ -z $TDS_ZONES ]]; then
|
||||
TDS_ZONES="us-west1-a,us-central1-a,europe-west4-a"
|
||||
fi
|
||||
GCE_CLOUD_ZONES=(); while read -r -d, ; do GCE_CLOUD_ZONES+=( "$REPLY" ); done <<< "${TDS_ZONES},"
|
||||
|
||||
if [[ -z $TDS_NODE_COUNT ]]; then
|
||||
TDS_NODE_COUNT="3"
|
||||
fi
|
||||
|
||||
if [[ -z $TDS_CLIENT_COUNT ]]; then
|
||||
TDS_CLIENT_COUNT="1"
|
||||
fi
|
||||
|
||||
if [[ -z $ENABLE_GPU ]]; then
|
||||
maybeGpu=(-G "--machine-type n1-standard-16 --accelerator count=2,type=nvidia-tesla-v100")
|
||||
elif [[ $ENABLE_GPU == skip ]]; then
|
||||
maybeGpu=()
|
||||
else
|
||||
maybeGpu=(-G "${ENABLE_GPU}")
|
||||
fi
|
||||
|
||||
if [[ -z $HASHES_PER_TICK ]]; then
|
||||
maybeHashesPerTick="--hashes-per-tick auto"
|
||||
elif [[ $HASHES_PER_TICK == skip ]]; then
|
||||
maybeHashesPerTick=""
|
||||
else
|
||||
maybeHashesPerTick="--hashes-per-tick ${HASHES_PER_TICK}"
|
||||
fi
|
||||
|
||||
if [[ -z $DISABLE_AIRDROPS ]]; then
|
||||
DISABLE_AIRDROPS="true"
|
||||
fi
|
||||
|
||||
if [[ $DISABLE_AIRDROPS == true ]] ; then
|
||||
maybeDisableAirdrops="--no-airdrop"
|
||||
else
|
||||
maybeDisableAirdrops=""
|
||||
fi
|
||||
|
||||
if [[ -z $INTERNAL_NODES_STAKE_LAMPORTS ]]; then
|
||||
maybeInternalNodesStakeLamports="--internal-nodes-stake-lamports 1000000000000"
|
||||
elif [[ $INTERNAL_NODES_STAKE_LAMPORTS == skip ]]; then
|
||||
maybeInternalNodesStakeLamports=""
|
||||
else
|
||||
maybeInternalNodesStakeLamports="--internal-nodes-stake-lamports ${INTERNAL_NODES_STAKE_LAMPORTS}"
|
||||
fi
|
||||
|
||||
if [[ -z $INTERNAL_NODES_LAMPORTS ]]; then
|
||||
maybeInternalNodesLamports="--internal-nodes-lamports 2000000000000"
|
||||
elif [[ $INTERNAL_NODES_LAMPORTS == skip ]]; then
|
||||
maybeInternalNodesLamports=""
|
||||
else
|
||||
maybeInternalNodesLamports="--internal-nodes-lamports ${INTERNAL_NODES_LAMPORTS}"
|
||||
fi
|
||||
|
||||
EXTERNAL_ACCOUNTS_FILE=/tmp/validator.yml
|
||||
if [[ -z $EXTERNAL_ACCOUNTS_FILE_URL ]]; then
|
||||
EXTERNAL_ACCOUNTS_FILE_URL=https://raw.githubusercontent.com/solana-labs/tour-de-sol/master/validators/all.yml
|
||||
wget ${EXTERNAL_ACCOUNTS_FILE_URL} -O ${EXTERNAL_ACCOUNTS_FILE}
|
||||
maybeExternalAccountsFile="--external-accounts-file ${EXTERNAL_ACCOUNTS_FILE}"
|
||||
elif [[ $EXTERNAL_ACCOUNTS_FILE_URL == skip ]]; then
|
||||
maybeExternalAccountsFile=""
|
||||
else
|
||||
wget ${EXTERNAL_ACCOUNTS_FILE_URL} -O ${EXTERNAL_ACCOUNTS_FILE}
|
||||
maybeExternalAccountsFile="--external-accounts-file ${EXTERNAL_ACCOUNTS_FILE}"
|
||||
fi
|
||||
|
||||
if [[ -z $LAMPORTS ]]; then
|
||||
maybeLamports="--lamports 8589934592000000000"
|
||||
elif [[ $LAMPORTS == skip ]]; then
|
||||
maybeLamports=""
|
||||
else
|
||||
maybeLamports="--lamports ${LAMPORTS}"
|
||||
fi
|
||||
|
||||
if [[ -z $ADDITIONAL_DISK_SIZE_GB ]]; then
|
||||
maybeAdditionalDisk="--fullnode-additional-disk-size-gb 32000"
|
||||
elif [[ $ADDITIONAL_DISK_SIZE_GB == skip ]]; then
|
||||
maybeAdditionalDisk=""
|
||||
else
|
||||
maybeAdditionalDisk="--fullnode-additional-disk-size-gb ${ADDITIONAL_DISK_SIZE_GB}"
|
||||
fi
|
||||
|
||||
|
||||
# Multiple V100 GPUs are available in us-west1, us-central1 and europe-west4
|
||||
# shellcheck disable=SC2068
|
||||
# shellcheck disable=SC2086
|
||||
NO_LEDGER_VERIFY=1 \
|
||||
NO_VALIDATOR_SANITY=1 \
|
||||
ci/testnet-deploy.sh -p tds-solana-com -C gce \
|
||||
"${maybeGpu[@]}" \
|
||||
-d pd-ssd \
|
||||
${GCE_CLOUD_ZONES[@]/#/-z } \
|
||||
-t "$CHANNEL_OR_TAG" \
|
||||
-n ${TDS_NODE_COUNT} \
|
||||
-c ${TDS_CLIENT_COUNT} \
|
||||
-P -u \
|
||||
-a tds-solana-com --letsencrypt tds.solana.com \
|
||||
${maybeHashesPerTick} \
|
||||
${skipCreate:+-e} \
|
||||
${skipStart:+-s} \
|
||||
${maybeStop:+-S} \
|
||||
${maybeDelete:+-D} \
|
||||
--hashes-per-tick auto
|
||||
fi
|
||||
${maybeDisableAirdrops} \
|
||||
${maybeInternalNodesStakeLamports} \
|
||||
${maybeInternalNodesLamports} \
|
||||
${maybeExternalAccountsFile} \
|
||||
${maybeLamports} \
|
||||
${maybeAdditionalDisk} \
|
||||
--skip-deploy-update \
|
||||
--no-snapshot
|
||||
)
|
||||
;;
|
||||
*)
|
||||
|
@@ -64,6 +64,7 @@ for zone in "$@"; do
|
||||
${NO_LEDGER_VERIFY:+-o noLedgerVerify} \
|
||||
${NO_VALIDATOR_SANITY:+-o noValidatorSanity} \
|
||||
${REJECT_EXTRA_NODES:+-o rejectExtraNodes} \
|
||||
${NO_INSTALL_CHECK:+-o noInstallCheck} \
|
||||
$zone || ok=false
|
||||
|
||||
net/net.sh logs
|
||||
|
@@ -8,8 +8,6 @@
|
||||
#
|
||||
set -e
|
||||
|
||||
REPO_SLUG=solana-labs/solana
|
||||
|
||||
if [[ -z $1 ]]; then
|
||||
echo No files specified
|
||||
exit 1
|
||||
@@ -20,31 +18,30 @@ if [[ -z $GITHUB_TOKEN ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n $BUILDKITE_TAG ]]; then
|
||||
TAG=$BUILDKITE_TAG
|
||||
elif [[ -n $TRIGGERED_BUILDKITE_TAG ]]; then
|
||||
TAG=$TRIGGERED_BUILDKITE_TAG
|
||||
if [[ -z $CI_TAG ]]; then
|
||||
echo Error: CI_TAG not defined
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z $TAG ]]; then
|
||||
echo Error: TAG not defined
|
||||
if [[ -z $CI_REPO_SLUG ]]; then
|
||||
echo Error: CI_REPO_SLUG not defined
|
||||
exit 1
|
||||
fi
|
||||
|
||||
releaseId=$( \
|
||||
curl -s "https://api.github.com/repos/$REPO_SLUG/releases/tags/$TAG" \
|
||||
curl -s "https://api.github.com/repos/$CI_REPO_SLUG/releases/tags/$CI_TAG" \
|
||||
| grep -m 1 \"id\": \
|
||||
| sed -ne 's/^[^0-9]*\([0-9]*\),$/\1/p' \
|
||||
)
|
||||
echo "Github release id for $TAG is $releaseId"
|
||||
echo "Github release id for $CI_TAG is $releaseId"
|
||||
|
||||
for file in "$@"; do
|
||||
echo "--- Uploading $file to tag $TAG of $REPO_SLUG"
|
||||
echo "--- Uploading $file to tag $CI_TAG of $CI_REPO_SLUG"
|
||||
curl \
|
||||
--data-binary @"$file" \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
"https://uploads.github.com/repos/$REPO_SLUG/releases/$releaseId/assets?name=$(basename "$file")"
|
||||
"https://uploads.github.com/repos/$CI_REPO_SLUG/releases/$releaseId/assets?name=$(basename "$file")"
|
||||
echo
|
||||
done
|
||||
|
||||
|
1
client/.gitignore
vendored
1
client/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/target/
|
||||
/farf/
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-client"
|
||||
version = "0.15.0"
|
||||
version = "0.17.1"
|
||||
description = "Solana Client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -11,16 +11,18 @@ edition = "2018"
|
||||
[dependencies]
|
||||
bincode = "1.1.4"
|
||||
bs58 = "0.2.0"
|
||||
log = "0.4.2"
|
||||
jsonrpc-core = "10.1.0"
|
||||
reqwest = "0.9.17"
|
||||
serde = "1.0.89"
|
||||
serde_derive = "1.0.91"
|
||||
serde_json = "1.0.39"
|
||||
solana-netutil = { path = "../netutil", version = "0.15.0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.15.0" }
|
||||
jsonrpc-core = "12.1.0"
|
||||
log = "0.4.7"
|
||||
rand = "0.6.5"
|
||||
rayon = "1.1.0"
|
||||
reqwest = "0.9.19"
|
||||
serde = "1.0.97"
|
||||
serde_derive = "1.0.97"
|
||||
serde_json = "1.0.40"
|
||||
solana-netutil = { path = "../netutil", version = "0.17.1" }
|
||||
solana-sdk = { path = "../sdk", version = "0.17.1" }
|
||||
|
||||
[dev-dependencies]
|
||||
jsonrpc-core = "10.1.0"
|
||||
jsonrpc-http-server = "10.1.0"
|
||||
solana-logger = { path = "../logger", version = "0.15.0" }
|
||||
jsonrpc-core = "12.1.0"
|
||||
jsonrpc-http-server = "12.1.0"
|
||||
solana-logger = { path = "../logger", version = "0.17.1" }
|
||||
|
@@ -60,6 +60,7 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
|
||||
serde_json::to_value(response).unwrap()
|
||||
}
|
||||
RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)),
|
||||
RpcRequest::GetSlot => Value::Number(Number::from(0)),
|
||||
RpcRequest::SendTransaction => Value::String(SIGNATURE.to_string()),
|
||||
_ => Value::Null,
|
||||
};
|
||||
|
@@ -36,7 +36,18 @@ pub fn sample_txs<T>(
|
||||
total_elapsed = start_time.elapsed();
|
||||
let elapsed = now.elapsed();
|
||||
now = Instant::now();
|
||||
let mut txs = client.get_transaction_count().expect("transaction count");
|
||||
let mut txs;
|
||||
match client.get_transaction_count() {
|
||||
Err(e) => {
|
||||
// ThinClient with multiple options should pick a better one now.
|
||||
info!("Couldn't get transaction count {:?}", e);
|
||||
sleep(Duration::from_secs(sample_period));
|
||||
continue;
|
||||
}
|
||||
Ok(tx_count) => {
|
||||
txs = tx_count;
|
||||
}
|
||||
}
|
||||
|
||||
if txs < last_txs {
|
||||
info!("Expected txs({}) >= last_txs({})", txs, last_txs);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user