Compare commits
3636 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6e06824c23 | ||
|
43d6da6b52 | ||
|
412d797a3b | ||
|
63bab44e3c | ||
|
f703d01f23 | ||
|
f13fa10b8a | ||
|
657100cebc | ||
|
d2d19ec8c2 | ||
|
0f872aff78 | ||
|
405738c791 | ||
|
3b19a79c37 | ||
|
64a1614769 | ||
|
b981f7beef | ||
|
caa210107e | ||
|
88eb7d3257 | ||
|
73895aae88 | ||
|
c5719983b2 | ||
|
f9b2db206a | ||
|
437ba9b044 | ||
|
98873fc0c0 | ||
|
415a522429 | ||
|
646f49fdd0 | ||
|
25ce4b2ec8 | ||
|
ac30783c82 | ||
|
5e9b4c58ae | ||
|
74e709fd55 | ||
|
3413343caa | ||
|
8bb02dd479 | ||
|
79be8f1ab8 | ||
|
a049baf6b2 | ||
|
d8305c52ea | ||
|
239d790df1 | ||
|
298a1a0ecc | ||
|
36b3f125a4 | ||
|
5d9ff63043 | ||
|
01996c8867 | ||
|
2ae294990a | ||
|
745c4bd00c | ||
|
ecae5f1c47 | ||
|
87ae05d99e | ||
|
a92e5e761b | ||
|
87603926b5 | ||
|
eb42f0c5d9 | ||
|
33908e8361 | ||
|
aa09846200 | ||
|
67ab600bc9 | ||
|
4b437428bd | ||
|
38e40f649c | ||
|
43ee520904 | ||
|
fdaee51ca0 | ||
|
eec38b30e3 | ||
|
287409f9f5 | ||
|
e5ae846de4 | ||
|
e6f3794dd4 | ||
|
5920f232d0 | ||
|
3bce814090 | ||
|
118051696e | ||
|
17057eeedc | ||
|
7aa1e987de | ||
|
193b25a22d | ||
|
99a8ddae41 | ||
|
93b39df02d | ||
|
5dec58ba9f | ||
|
b928380b64 | ||
|
5e7086d54c | ||
|
144b2293a2 | ||
|
f14d3e5a5c | ||
|
f9f492638c | ||
|
fe198ddf7d | ||
|
32d8b5487a | ||
|
6835ec53ad | ||
|
09ab40b956 | ||
|
2df74c26dd | ||
|
ed12fffeef | ||
|
c03cc15468 | ||
|
3b3ecf6676 | ||
|
be9b16ab67 | ||
|
392a909cb7 | ||
|
6f5a00a642 | ||
|
2453f1a803 | ||
|
09967329af | ||
|
459a1a02a4 | ||
|
a716eb3871 | ||
|
0fd7c59724 | ||
|
aa41520dd1 | ||
|
4bffab6715 | ||
|
5709dbc3e0 | ||
|
6ce6666cbb | ||
|
582bca385f | ||
|
9037ad0ea7 | ||
|
c9190a39ed | ||
|
85391f99ac | ||
|
561ed8df3c | ||
|
5eacff59e8 | ||
|
d030870220 | ||
|
ee88247e71 | ||
|
37f5291538 | ||
|
5fdedf0858 | ||
|
504777e879 | ||
|
3317797285 | ||
|
33a3a9deec | ||
|
327c4bcb14 | ||
|
b6b5129058 | ||
|
29dc10c446 | ||
|
48e7d6cee4 | ||
|
a0f406e26b | ||
|
3f8e0cfec4 | ||
|
f143ddb75a | ||
|
32fd2b484c | ||
|
187c81b3f1 | ||
|
d7a958129f | ||
|
eca8fb74ae | ||
|
e2024c4b81 | ||
|
5b904476cd | ||
|
7ea5707904 | ||
|
458d55559e | ||
|
6f50061f0c | ||
|
dbc5f94241 | ||
|
0cf0cdbb86 | ||
|
91e57c803d | ||
|
0fcb102f03 | ||
|
973a5a594b | ||
|
ba011eba15 | ||
|
7c9eed8d65 | ||
|
63fdad8d86 | ||
|
0947261cf2 | ||
|
efb80e1032 | ||
|
f0fd88aa12 | ||
|
142b63a4f9 | ||
|
1d2b640834 | ||
|
f1dc682168 | ||
|
0bb2f8f6b8 | ||
|
d5c2a0fbe2 | ||
|
98563b0a45 | ||
|
d8ce175846 | ||
|
fb9699d8e1 | ||
|
dbf9a1cd98 | ||
|
6b4e56b214 | ||
|
f40e198eb7 | ||
|
bbecb0415e | ||
|
6d81fce451 | ||
|
65c5e6dfd3 | ||
|
2e23ca353f | ||
|
a831379ad8 | ||
|
cfc6439f2e | ||
|
52d966ccaa | ||
|
59d891edf4 | ||
|
f3bdc0da3c | ||
|
a55799d523 | ||
|
1d07c4c06b | ||
|
ea25ffd79d | ||
|
eb876cb2d7 | ||
|
814526a248 | ||
|
d3ba83405c | ||
|
c46fe330dc | ||
|
0e5d6944b7 | ||
|
b0a1e3da03 | ||
|
0706e5468d | ||
|
f286597d10 | ||
|
e2f665e9cf | ||
|
eab41b49cf | ||
|
8d3e0582a8 | ||
|
705bc71593 | ||
|
f723e288c3 | ||
|
a6bd3516e0 | ||
|
08e6cca3e5 | ||
|
8a9d14141a | ||
|
832fc444b6 | ||
|
612a71ecb2 | ||
|
06fc61d7c5 | ||
|
837e8b8725 | ||
|
ea3efd926e | ||
|
3f01b69084 | ||
|
647ff31942 | ||
|
56131b6d92 | ||
|
51d824fbdc | ||
|
1225ff2c5a | ||
|
cb91b7e828 | ||
|
233bab2ee7 | ||
|
fed80cc075 | ||
|
26ab00b6c7 | ||
|
01e72efb80 | ||
|
81ae80b7f1 | ||
|
910bb78f0d | ||
|
6078eeaed7 | ||
|
50a4d5fa57 | ||
|
410853e280 | ||
|
bf5830f766 | ||
|
d811f6e3ce | ||
|
9110b1d9e4 | ||
|
cb0513a8b1 | ||
|
84f675021c | ||
|
03bfb012a1 | ||
|
16542bd355 | ||
|
24cff45334 | ||
|
51817baecd | ||
|
15ebc98877 | ||
|
b6a25ba30a | ||
|
abceaf3832 | ||
|
25c2f7e7fd | ||
|
27a0142af1 | ||
|
698fa6e8f6 | ||
|
ae312bcb01 | ||
|
d17ee979b8 | ||
|
4fb4ef6d24 | ||
|
1c82a0733f | ||
|
b54ddd027d | ||
|
61e56aba41 | ||
|
aecc6fc862 | ||
|
dd38573a28 | ||
|
c84d82580a | ||
|
506cee52e8 | ||
|
c58b52c21c | ||
|
f8326b6e27 | ||
|
ea0c13c0a4 | ||
|
a8668b371c | ||
|
32ea4d69a3 | ||
|
defd24c40e | ||
|
194101ed00 | ||
|
2ab8c72ce3 | ||
|
a75ba3620c | ||
|
54afb33333 | ||
|
3f42b6178f | ||
|
1460f6cc27 | ||
|
751210c963 | ||
|
7dfa57999b | ||
|
11fb967c6a | ||
|
c270599a23 | ||
|
412d0307cb | ||
|
33b5b36f44 | ||
|
ef7a82835a | ||
|
ac8f65dbbf | ||
|
725073a683 | ||
|
c5aed5bab1 | ||
|
cacbf256fe | ||
|
0cd972326c | ||
|
239cf91594 | ||
|
1700873f48 | ||
|
4adb44155d | ||
|
82b37bfa0d | ||
|
544725a018 | ||
|
26e253e554 | ||
|
610d9baba4 | ||
|
253ff3f37b | ||
|
54c2d6167f | ||
|
29ebddc64f | ||
|
fc129b4a26 | ||
|
a071d81fe9 | ||
|
9f94473eaf | ||
|
42437fbc57 | ||
|
f3cdd7bf21 | ||
|
5ee54b7298 | ||
|
06c7096054 | ||
|
b2277f65e4 | ||
|
c52a6c8fb7 | ||
|
7c7b181ca0 | ||
|
24a4fdf405 | ||
|
45f27cec34 | ||
|
6bd7db96fe | ||
|
ff398fe7ff | ||
|
3ebc769757 | ||
|
d60e6384d7 | ||
|
3fd58bdcbd | ||
|
ecbafb2390 | ||
|
adabd8198c | ||
|
c2487cfe07 | ||
|
e0141f8324 | ||
|
b52ac20660 | ||
|
3c85f29f11 | ||
|
d9673b0d6b | ||
|
89f828be1c | ||
|
ec56b1f09d | ||
|
95236d25b2 | ||
|
7b2afdfc8c | ||
|
440e52f410 | ||
|
8840a293dd | ||
|
89d627769e | ||
|
4e2e88a620 | ||
|
ebf51c0be0 | ||
|
04c6867660 | ||
|
0199acbece | ||
|
e4c2fe9e72 | ||
|
407de5e8c4 | ||
|
7d26a82232 | ||
|
3b23817936 | ||
|
aa8487c1d0 | ||
|
9cb8606103 | ||
|
6cf3ba7efd | ||
|
023e511f83 | ||
|
17042e9c32 | ||
|
f2c34f7ca2 | ||
|
375a8daeb4 | ||
|
b700ff3501 | ||
|
9519493e32 | ||
|
037fd1b309 | ||
|
78a534633d | ||
|
effead9ba5 | ||
|
a8ee3c97e6 | ||
|
fb461659c7 | ||
|
a574df3132 | ||
|
d83143d0ba | ||
|
f875175325 | ||
|
c9db8ea21d | ||
|
a16bad4175 | ||
|
595dac6c3f | ||
|
82a148a99b | ||
|
4320c9bc4f | ||
|
23d977ecce | ||
|
ab27848dc4 | ||
|
742a6007fe | ||
|
91933d857d | ||
|
3e1d73126c | ||
|
7014642815 | ||
|
1bd4564216 | ||
|
97cb010df8 | ||
|
ed18c7b54c | ||
|
e71598d876 | ||
|
3d0ce10fa6 | ||
|
cfc8df156b | ||
|
94cb3b6e0e | ||
|
fefec000fb | ||
|
c7ded6a785 | ||
|
2fbb952cdd | ||
|
e2ab3e4f5b | ||
|
1871275ecd | ||
|
afc1b72611 | ||
|
c5c3fb6a75 | ||
|
bceb883d99 | ||
|
fcccbf3b75 | ||
|
9ad71b7baa | ||
|
4311d43497 | ||
|
0815cc3b83 | ||
|
b21844b371 | ||
|
f825048efa | ||
|
2cbffe36e2 | ||
|
6bb106a784 | ||
|
4e0ec4e66b | ||
|
b58a3ed0ad | ||
|
b803f57db6 | ||
|
b457f46c81 | ||
|
b4520c5886 | ||
|
ca67dc251f | ||
|
aea289e79e | ||
|
ef0eda0c39 | ||
|
af7dc3676b | ||
|
fa570f297e | ||
|
9cce6a47d4 | ||
|
bff0bedfa9 | ||
|
d6c80c1672 | ||
|
512343003d | ||
|
3adb640d2b | ||
|
ea589a17a4 | ||
|
a3883ca5d9 | ||
|
5be0163cde | ||
|
55454b2f2d | ||
|
a4dc85543b | ||
|
d8394bded7 | ||
|
d89b8d904f | ||
|
8e866ee551 | ||
|
6dfc1bd474 | ||
|
c84e5745fa | ||
|
751d15e4be | ||
|
8b6c5be6a9 | ||
|
3502b36232 | ||
|
046b8bbc8a | ||
|
6fa4b2dec5 | ||
|
83bcb819da | ||
|
b7e8621846 | ||
|
d5c19f8719 | ||
|
8fa56add47 | ||
|
e45ee6cd72 | ||
|
e344286c32 | ||
|
45d7c60608 | ||
|
a3e39c9858 | ||
|
8ab6d89810 | ||
|
1b3b9b2887 | ||
|
89ae0f0ea0 | ||
|
06cae8a535 | ||
|
12ac992ffb | ||
|
3b23c2e86d | ||
|
0f9b2218da | ||
|
12c42bce9b | ||
|
eb3d33ed6f | ||
|
a139c6d216 | ||
|
50f5ccc4f2 | ||
|
94db961975 | ||
|
38f3747cde | ||
|
4fec2f2fc2 | ||
|
c96d8a742b | ||
|
c2de31e586 | ||
|
4b11d79829 | ||
|
c35abe4196 | ||
|
a9a278a6e1 | ||
|
fb07ffa676 | ||
|
940a88fa4e | ||
|
708e495c28 | ||
|
460681ead9 | ||
|
2a7ed457dc | ||
|
35bbf11ba5 | ||
|
4f1e1e8870 | ||
|
cf505139f1 | ||
|
cdba22a2cb | ||
|
1df6361753 | ||
|
a6c6c7c070 | ||
|
ed6f2877d7 | ||
|
53c408f549 | ||
|
64704c456f | ||
|
0edf8e3f1b | ||
|
53a04e1686 | ||
|
d356c6640d | ||
|
e8e087fc37 | ||
|
181738a736 | ||
|
1ac1224cd3 | ||
|
3687df8da2 | ||
|
67eee6aeb7 | ||
|
83ba9df85b | ||
|
83f706186f | ||
|
eea5f6f232 | ||
|
38af7f35fc | ||
|
eea3de00c1 | ||
|
4d66e8d06d | ||
|
010cfb7d67 | ||
|
f9a8aac036 | ||
|
3d28823be7 | ||
|
492317abd7 | ||
|
ab22d5e278 | ||
|
ce5f704dd5 | ||
|
696dc05dda | ||
|
589083ad7a | ||
|
1fda997370 | ||
|
7c335e8764 | ||
|
5b1d33d5fa | ||
|
e435407080 | ||
|
b180be7526 | ||
|
90fb473d87 | ||
|
3650f2d51c | ||
|
3090324366 | ||
|
130901e820 | ||
|
801b8191ef | ||
|
469f9c26e7 | ||
|
b5f510ead7 | ||
|
b4f8bba843 | ||
|
2bb79614f6 | ||
|
c077dc652d | ||
|
1b6f2a3e92 | ||
|
9136c81f05 | ||
|
912e5599d9 | ||
|
ff0095ac5e | ||
|
bf9fedc4ee | ||
|
78ba54da6b | ||
|
c90e279ab5 | ||
|
215602de08 | ||
|
13b832f959 | ||
|
789bb9c852 | ||
|
aada1f547b | ||
|
60718225ac | ||
|
1a2fc03083 | ||
|
61e8baee0c | ||
|
dbc5f55da9 | ||
|
81b7698428 | ||
|
4ce4bad383 | ||
|
23d25a079b | ||
|
d19bdb642e | ||
|
2e0246a6c2 | ||
|
349098e7b2 | ||
|
dc3b1ecdd0 | ||
|
7fb33796b1 | ||
|
ec886ddefb | ||
|
4ded4181a6 | ||
|
60691d03e0 | ||
|
869fa399b1 | ||
|
7af953fd62 | ||
|
5acbcb0d57 | ||
|
7000c394b2 | ||
|
7c0d894ccf | ||
|
f092c10de5 | ||
|
7f5e6b3a0a | ||
|
5e9dc185a5 | ||
|
0e94ac0111 | ||
|
0b5bbf6048 | ||
|
14c9cbd40e | ||
|
c4466878cf | ||
|
c584221fa2 | ||
|
70ba050c06 | ||
|
f2281dc38a | ||
|
18a2e6265d | ||
|
0d593199d0 | ||
|
832c4a7565 | ||
|
34d22a35dd | ||
|
c880716f16 | ||
|
f20f4c74d2 | ||
|
1fdfa1e6c6 | ||
|
4ee49f03df | ||
|
52d5278a62 | ||
|
35a2b87174 | ||
|
664bb2becd | ||
|
8865b95818 | ||
|
03600dce97 | ||
|
8b607efc40 | ||
|
9f1af6b3e8 | ||
|
c0952ba44b | ||
|
9475a2e474 | ||
|
5baed0c158 | ||
|
97c259858c | ||
|
0f90696528 | ||
|
ac974a180d | ||
|
052380b8de | ||
|
23a29439c0 | ||
|
a8617e2862 | ||
|
6945a6b320 | ||
|
b375c9adbf | ||
|
88a727739b | ||
|
5f3ae4dee3 | ||
|
5cbe834024 | ||
|
eea5b86cc4 | ||
|
09c512abaa | ||
|
d42d816e7f | ||
|
17effd15ab | ||
|
9982eba188 | ||
|
aafe527d4a | ||
|
e4c53a460e | ||
|
ca01596a65 | ||
|
4f2415b483 | ||
|
f680eacdf2 | ||
|
f6dcca3ebb | ||
|
1a642fc624 | ||
|
c4469514db | ||
|
59daf95859 | ||
|
3a6e04ba15 | ||
|
b26f86d6ff | ||
|
570215acae | ||
|
a511264433 | ||
|
06f25d2b27 | ||
|
a3bd355b16 | ||
|
1c1cd8b164 | ||
|
a898109522 | ||
|
3694b10e22 | ||
|
39f25d20a8 | ||
|
61c1646b43 | ||
|
1b9396dcbb | ||
|
05be4b5b0e | ||
|
f8f8bf0fea | ||
|
6643b6a306 | ||
|
7036ab26d7 | ||
|
9b55169251 | ||
|
ff13c9c186 | ||
|
879e7305ca | ||
|
1ff827b2ea | ||
|
e7f1204fa4 | ||
|
9a2c4a34ee | ||
|
f4c421f77a | ||
|
fe84718b55 | ||
|
15d71a01d5 | ||
|
abe30f2578 | ||
|
fdae48547b | ||
|
68ca8df22f | ||
|
b8da38f4e4 | ||
|
1f103ab7f1 | ||
|
da6cf33aac | ||
|
73f08b376f | ||
|
ed34d1fee7 | ||
|
e9f4f1d13c | ||
|
e5bb330be5 | ||
|
e23e22cb81 | ||
|
7434026f5f | ||
|
5319d33bc6 | ||
|
702311b6b2 | ||
|
2511bc20e0 | ||
|
ce5a6eabae | ||
|
581cd97ba1 | ||
|
fa2f99641f | ||
|
c40f7db1ab | ||
|
be1363e943 | ||
|
c313039526 | ||
|
73db5dda8c | ||
|
5a8fb77fb2 | ||
|
4b6ebcbb61 | ||
|
bc056c41bc | ||
|
5b54442a48 | ||
|
dc14cce7a9 | ||
|
4f278ba715 | ||
|
1036fcca36 | ||
|
6b286a5dee | ||
|
c8ae675b95 | ||
|
4186467129 | ||
|
726884afcb | ||
|
5a2f3e700b | ||
|
911fc74346 | ||
|
1388f4d27e | ||
|
5b87327a43 | ||
|
2fc1679886 | ||
|
7ba5652bea | ||
|
1e9aebbc86 | ||
|
61ec361182 | ||
|
7781cbbc57 | ||
|
f3b806b471 | ||
|
6496405f30 | ||
|
85a6dc5e8c | ||
|
856bbfc9c8 | ||
|
ebaa43fa4c | ||
|
2d44b3ebea | ||
|
984493db30 | ||
|
47848769ff | ||
|
a8f6f5b974 | ||
|
1e13f474cb | ||
|
c69c3a9a46 | ||
|
3216b143c2 | ||
|
cc963d42a0 | ||
|
3f95a62e4f | ||
|
7f9a9e2e82 | ||
|
375ecd4ada | ||
|
8875dccd11 | ||
|
4c2301fdf6 | ||
|
346594c406 | ||
|
2609e2db5c | ||
|
692d5b4e08 | ||
|
c4af7464e5 | ||
|
5a1dc3eb8a | ||
|
adcbfcf8d6 | ||
|
b57607e7d3 | ||
|
2f159d4f45 | ||
|
93e1040d07 | ||
|
403c07c305 | ||
|
6253308e2e | ||
|
33a014013a | ||
|
4637215ab2 | ||
|
ceaedbbd7f | ||
|
52f8b1a1d7 | ||
|
e6d1250185 | ||
|
d04e5e49d0 | ||
|
a72436f330 | ||
|
467403f437 | ||
|
bbaac0c6a9 | ||
|
753fd4bda3 | ||
|
57d2c8c94a | ||
|
870ec89e9a | ||
|
0c3b70f2fb | ||
|
7a367698fe | ||
|
c77e99814b | ||
|
4ddd69cc55 | ||
|
ef4a61c769 | ||
|
7dfb5ff5bd | ||
|
6b391312ab | ||
|
6e62d77e4d | ||
|
0281cca9af | ||
|
018e2403b1 | ||
|
61f4534e2a | ||
|
69667317c1 | ||
|
530aac0682 | ||
|
6e7d8f90b5 | ||
|
65bf1086a2 | ||
|
98220442b4 | ||
|
3c3d2ef2b9 | ||
|
67066eb32a | ||
|
631df0fe56 | ||
|
485870296d | ||
|
6e5a1c00dc | ||
|
ba487eaaca | ||
|
eb0986c3f9 | ||
|
baf5be09dc | ||
|
f3aed42dd6 | ||
|
44531e3009 | ||
|
2177a0179e | ||
|
e1f333021f | ||
|
4040d73c60 | ||
|
61bd47ccc1 | ||
|
915c366056 | ||
|
6888a968f9 | ||
|
4e8e5bbb86 | ||
|
5752869824 | ||
|
39a12622ae | ||
|
dca88ff85c | ||
|
ab9843cb00 | ||
|
72fd1fa58d | ||
|
51eac1926f | ||
|
c1aed4af45 | ||
|
c12447c7c5 | ||
|
502bacea82 | ||
|
da5ba4ccc8 | ||
|
1f2426226b | ||
|
c0eb30b604 | ||
|
caca3a8048 | ||
|
f929419676 | ||
|
bc53e78a04 | ||
|
3dbea1a7a1 | ||
|
644d1db5ef | ||
|
07d97d5b26 | ||
|
c658b46fe1 | ||
|
8a5c9a8c70 | ||
|
3e4a525520 | ||
|
74ce0f738e | ||
|
1073d56245 | ||
|
7aa4484a03 | ||
|
6c96b60a63 | ||
|
ac1900a0fc | ||
|
bd3bc5c168 | ||
|
9ed43230ca | ||
|
7abe9ec4cc | ||
|
5ed2527663 | ||
|
b87c7cac54 | ||
|
0b34579b04 | ||
|
31291ebd35 | ||
|
e8e0b08f17 | ||
|
491ce61a76 | ||
|
e12a26dac5 | ||
|
7bf1889af1 | ||
|
139a2b7b0d | ||
|
f230c719d8 | ||
|
8703449dfe | ||
|
108590d924 | ||
|
b2cf5d2958 | ||
|
ee5ed44761 | ||
|
346913b7f6 | ||
|
18a8d2f67f | ||
|
3ae10915e4 | ||
|
949b9c85ca | ||
|
c21c19bd6c | ||
|
29125e830b | ||
|
1ac4676f4b | ||
|
fe5301cebf | ||
|
7262601123 | ||
|
fcb6cc1e76 | ||
|
ff716e7799 | ||
|
9c595aff95 | ||
|
98dbd1fdc7 | ||
|
a6df452841 | ||
|
4eab8672b8 | ||
|
6e2821b4db | ||
|
1564fae011 | ||
|
30e40079ca | ||
|
65a1d88907 | ||
|
e590874a81 | ||
|
b28e742683 | ||
|
62fdfb937a | ||
|
8814195122 | ||
|
e2095d4a5d | ||
|
78a38e9825 | ||
|
712101b63d | ||
|
1f18dbb17c | ||
|
0d8001adea | ||
|
1e44a62494 | ||
|
e8b13cb77e | ||
|
3f2fd610d9 | ||
|
25604dc577 | ||
|
0cfc6bf2a6 | ||
|
90d7823acb | ||
|
b4ae1b6528 | ||
|
f442665c46 | ||
|
c22498066b | ||
|
637883f52b | ||
|
f0c0da8551 | ||
|
10f42a2b39 | ||
|
29baccd857 | ||
|
f9814381a7 | ||
|
771ea47d37 | ||
|
c54beba932 | ||
|
a809621f63 | ||
|
7ad556346e | ||
|
5737c21340 | ||
|
77a5ce6bf3 | ||
|
fb503f523b | ||
|
bf7677ce69 | ||
|
1ce8c1cf82 | ||
|
4ddccfa5e5 | ||
|
5795d332c8 | ||
|
823054dc34 | ||
|
2ce15f429b | ||
|
143411aaf0 | ||
|
7d9548400d | ||
|
1b1941a896 | ||
|
4848c384cd | ||
|
d4f38d3894 | ||
|
e6acbc5a58 | ||
|
8dd4db5d85 | ||
|
d436eddc6a | ||
|
faf8e9ec6a | ||
|
7e6a571cba | ||
|
6bee9cd1e4 | ||
|
fb2b77e991 | ||
|
b914912c06 | ||
|
0ce04845de | ||
|
073365d5d9 | ||
|
3c27587d83 | ||
|
dbccc700f1 | ||
|
9f90ff2e59 | ||
|
4d9c8926b1 | ||
|
070695b348 | ||
|
c6e97d4dc5 | ||
|
a24e78fa92 | ||
|
21e0cd7781 | ||
|
ed45760425 | ||
|
0ca4250bd4 | ||
|
5059619947 | ||
|
edb228839e | ||
|
a9c93c797d | ||
|
f826ac35e3 | ||
|
9dc512349a | ||
|
3ecf16a492 | ||
|
584a76ab70 | ||
|
e339cde790 | ||
|
82a6a0848a | ||
|
441cb7980b | ||
|
5f523f6966 | ||
|
993650f3d6 | ||
|
ab330301eb | ||
|
bab85dd789 | ||
|
acae643a4a | ||
|
01f825b0e1 | ||
|
796637b31a | ||
|
484ecfaf47 | ||
|
c082af6f74 | ||
|
494eb4ab6b | ||
|
fe678dcd2f | ||
|
da5de4a6ff | ||
|
526c61e2c0 | ||
|
c7f608ec74 | ||
|
6816f8b489 | ||
|
cd58b5ff1f | ||
|
bca100cdb0 | ||
|
c63452e25d | ||
|
7e779327eb | ||
|
9f1e08663d | ||
|
91122d9193 | ||
|
c7d21841a4 | ||
|
701692b7d3 | ||
|
787a30cd8e | ||
|
ca6edcaf71 | ||
|
8d171a37f8 | ||
|
e9bd41b3f1 | ||
|
aa67bd5d00 | ||
|
802d684994 | ||
|
434e018584 | ||
|
4839294c86 | ||
|
71bbcd54ff | ||
|
3db353f356 | ||
|
f4c5ea8378 | ||
|
6a97a4a11e | ||
|
79f754e6ac | ||
|
e3e2fcc285 | ||
|
00e61a9100 | ||
|
f3107214f4 | ||
|
78e001284f | ||
|
9caa868603 | ||
|
202c54d423 | ||
|
1051004aee | ||
|
a1a002f4da | ||
|
5ef41ed53e | ||
|
67721f3413 | ||
|
34bf2452c3 | ||
|
8fbb98fb3f | ||
|
1792725651 | ||
|
47ff3a9bee | ||
|
ac3a706f0d | ||
|
9b5483a71b | ||
|
38c31c880f | ||
|
0bed5976e3 | ||
|
19a6725430 | ||
|
48a54efcb2 | ||
|
c0b0dc5219 | ||
|
683a26c830 | ||
|
1a16f335fa | ||
|
4145be863b | ||
|
e9f1b38984 | ||
|
edd90f153c | ||
|
dec390a89f | ||
|
41348dead4 | ||
|
c473ab97c7 | ||
|
0cd1de769b | ||
|
5ae8e8a9ca | ||
|
796d72f48e | ||
|
86a8584252 | ||
|
6509e90c36 | ||
|
09ecd0c583 | ||
|
a6d6adc57f | ||
|
6be5744be4 | ||
|
31b4437b93 | ||
|
458afcd230 | ||
|
cf5ae81ced | ||
|
08e46432c5 | ||
|
6f11621734 | ||
|
6004c394d6 | ||
|
609d83f92c | ||
|
4ef71f8a82 | ||
|
b47218521f | ||
|
c9cee8fd52 | ||
|
75e779029f | ||
|
3016d54f13 | ||
|
05e7c133fb | ||
|
fd57100190 | ||
|
fc86b1799a | ||
|
9546e0c8c2 | ||
|
4fe6c148ef | ||
|
9e872788c7 | ||
|
da95f77996 | ||
|
6f758bc7b1 | ||
|
b37b3cd1fc | ||
|
e81069ab3d | ||
|
bf25f17880 | ||
|
6201532c64 | ||
|
3094ae9df9 | ||
|
59f6931e06 | ||
|
95a601d053 | ||
|
5d7ef54d02 | ||
|
b34d46cbc8 | ||
|
0bb78814a6 | ||
|
4938d5dde5 | ||
|
4817b94d0b | ||
|
2a470deeaf | ||
|
861d829420 | ||
|
09ee6e1477 | ||
|
10fc74eb81 | ||
|
986f485b3e | ||
|
af1088ef61 | ||
|
13bc922e54 | ||
|
1f39a1bd76 | ||
|
13efb6586d | ||
|
a48ed02433 | ||
|
24c43513a6 | ||
|
a6d267abc0 | ||
|
c8877d4098 | ||
|
1318f536c9 | ||
|
bc2f86e806 | ||
|
107f0fa4c6 | ||
|
a5190449da | ||
|
114d4433a9 | ||
|
bd4498cffc | ||
|
6771539a90 | ||
|
123b6ae62e | ||
|
5d6a0d4dae | ||
|
6ecc63002b | ||
|
b3ea766bd5 | ||
|
e2a90ce159 | ||
|
8057e8df43 | ||
|
98b7c07171 | ||
|
3d76417353 | ||
|
2060ea5de3 | ||
|
79eb8f7ace | ||
|
0ebcc200c3 | ||
|
27f3f42ce2 | ||
|
dab967ace8 | ||
|
485d4aa8f3 | ||
|
799ae29ac4 | ||
|
00b209a29e | ||
|
581e510c2d | ||
|
9053c0dfd9 | ||
|
d32ce37484 | ||
|
1020560af6 | ||
|
118588ef6c | ||
|
6b9314eaa9 | ||
|
686bf443e6 | ||
|
93054ef24b | ||
|
1620eabd9d | ||
|
7fcb082cad | ||
|
68d16b723a | ||
|
ec9c6e9783 | ||
|
3b083d545d | ||
|
5400447395 | ||
|
ed8425b8b9 | ||
|
7d7d4822a5 | ||
|
80528c5344 | ||
|
6c24d9897a | ||
|
e346f3058e | ||
|
dc8da3743d | ||
|
6563576ae9 | ||
|
db9397890e | ||
|
bd1e3fc606 | ||
|
fe5f5b28d9 | ||
|
ee41fa6f30 | ||
|
84ecab0eaf | ||
|
3fde07b2e1 | ||
|
3c2f13f88b | ||
|
52f10242e2 | ||
|
9e719f088f | ||
|
6552256981 | ||
|
d1934363e7 | ||
|
cdbcfaa7de | ||
|
6ecc855c34 | ||
|
30ecd045fa | ||
|
f9e64e0965 | ||
|
0ecbb3ec02 | ||
|
3bb5ad7204 | ||
|
8e078b1d83 | ||
|
0c4d2fbc70 | ||
|
e4614e49ae | ||
|
8f56606cac | ||
|
af90fbfb33 | ||
|
938c3707fc | ||
|
1b95af18ff | ||
|
272ebc1ef7 | ||
|
cfd50538bb | ||
|
08abf67a51 | ||
|
981554cf74 | ||
|
979af3d314 | ||
|
57d1f2b4d3 | ||
|
1fa95ac236 | ||
|
25dc1c2155 | ||
|
61ec02248a | ||
|
ecd7caa93d | ||
|
aea26dcc64 | ||
|
1b8f299df2 | ||
|
6e2e08628a | ||
|
fb0e6cf0d0 | ||
|
a57c45bb1c | ||
|
b2cc1c54f8 | ||
|
21dad1d41e | ||
|
1d42b7f0d1 | ||
|
cddc33bb24 | ||
|
cb7ad2366d | ||
|
25536c5ffb | ||
|
842b75c0e6 | ||
|
8b0ba97cf2 | ||
|
b84682168d | ||
|
f20f9f376e | ||
|
24838bbcd3 | ||
|
7a00d97977 | ||
|
ac3de4c5fc | ||
|
28c731881f | ||
|
a7a46f4253 | ||
|
528497b86a | ||
|
32c32ecfda | ||
|
e30839e85f | ||
|
a4c7843a07 | ||
|
f0c6d17ad8 | ||
|
66c0638f3b | ||
|
eec7364760 | ||
|
b10094508f | ||
|
8e8679807d | ||
|
629da8f8bf | ||
|
10a346476a | ||
|
d1f5284fe6 | ||
|
849f5d9a44 | ||
|
7a76916143 | ||
|
2a829b1f1a | ||
|
44c68221a8 | ||
|
e36c4ecc98 | ||
|
01d399ad66 | ||
|
9376796bdb | ||
|
f135c09b36 | ||
|
4e85015836 | ||
|
f56c065f57 | ||
|
adc3457a89 | ||
|
fa261ebd02 | ||
|
7fdb87ad38 | ||
|
baeda93474 | ||
|
7a28f72abc | ||
|
1ab06ce2d2 | ||
|
b0cc44aabb | ||
|
c983efe895 | ||
|
650948feed | ||
|
4e76cce0b7 | ||
|
9e9045ab94 | ||
|
d86333af6b | ||
|
28b5906d9e | ||
|
24f6d8296b | ||
|
9c5e35548d | ||
|
92b5b5644f | ||
|
8fb47b52f5 | ||
|
14361cc7b1 | ||
|
2257bc8e2f | ||
|
461b2d4853 | ||
|
941f2380c4 | ||
|
cb31220a4a | ||
|
9e09d5b6bf | ||
|
a04c5b180a | ||
|
db7a8c4ac7 | ||
|
fac356c701 | ||
|
90eb61091a | ||
|
f6998cb04e | ||
|
897a94641e | ||
|
3f677c6168 | ||
|
1cd93e4ceb | ||
|
03b96a7c0a | ||
|
869fa6fda8 | ||
|
bc2f5586ee | ||
|
16f435b906 | ||
|
0a170efaa5 | ||
|
1356d6d8d5 | ||
|
0a9114203b | ||
|
2b05eb43a9 | ||
|
dd2c27958c | ||
|
6737484eda | ||
|
9fe991db1c | ||
|
6cf441fc9e | ||
|
99e37844fd | ||
|
8348147a4f | ||
|
bd7273061e | ||
|
4f447c50b2 | ||
|
692cd10d4a | ||
|
86446d713a | ||
|
d1487b3177 | ||
|
8e7a08f865 | ||
|
0d75d01c84 | ||
|
1c75e8eb47 | ||
|
431b27d3e1 | ||
|
d97cf34138 | ||
|
e6f75bccfe | ||
|
c039ab79b5 | ||
|
9436e88d27 | ||
|
652f5032a2 | ||
|
d7a7f034db | ||
|
27c32d3629 | ||
|
0d2993e46d | ||
|
e4168c2985 | ||
|
9d3771458d | ||
|
ff0ce70169 | ||
|
811d165458 | ||
|
d57944ffb9 | ||
|
991f0cac6e | ||
|
b2d338bf35 | ||
|
ef80698deb | ||
|
e12a5159a8 | ||
|
0455aa96bf | ||
|
9aaedad64f | ||
|
ea6b0ec164 | ||
|
679d6c6f2b | ||
|
ec96091369 | ||
|
0a535bf485 | ||
|
dcaff6f4c8 | ||
|
5e7d42e4a4 | ||
|
99a13c4e66 | ||
|
060205ab27 | ||
|
9f775a7673 | ||
|
d477670cb9 | ||
|
54c9c382e7 | ||
|
9108a3bb50 | ||
|
c1cced3662 | ||
|
5ea4c22868 | ||
|
f1b7d8ab34 | ||
|
9c9ddaccec | ||
|
68a08df9c3 | ||
|
1e6d889fc7 | ||
|
443115f885 | ||
|
cab073ba00 | ||
|
7f9589d678 | ||
|
899c1a4b0e | ||
|
e3f7b70c38 | ||
|
6e49ff1d98 | ||
|
0c7f998c25 | ||
|
23ea4798e0 | ||
|
2632310b6a | ||
|
c4dd156113 | ||
|
06a7ca221c | ||
|
04931618ed | ||
|
dbc4d85f0a | ||
|
bd45cd4a5e | ||
|
8c02211dc3 | ||
|
eb18e7ade7 | ||
|
8ddd508a44 | ||
|
857809f693 | ||
|
ca0045482c | ||
|
1c2c683ae3 | ||
|
7c8d404cf8 | ||
|
d293f94a6f | ||
|
a60d0e440d | ||
|
9e294d577a | ||
|
6d5d419e14 | ||
|
1b4d9c2d39 | ||
|
a91e562021 | ||
|
d7f690c8ba | ||
|
7f1ff152ca | ||
|
beb6438ef5 | ||
|
e25a660a11 | ||
|
a7887fa9f1 | ||
|
a6915778bb | ||
|
249f81cbc5 | ||
|
373036bb7a | ||
|
ed296312aa | ||
|
d27c36cf75 | ||
|
c737056000 | ||
|
6f5bd845ad | ||
|
fed4864939 | ||
|
acd7192b17 | ||
|
21cb08586b | ||
|
4d1cb01da0 | ||
|
0e548ce7c5 | ||
|
236692cfd5 | ||
|
322dfbcd78 | ||
|
1bad20ae38 | ||
|
113c35af0a | ||
|
5c47116889 | ||
|
23fc5517b5 | ||
|
7e948a088f | ||
|
102bc7809f | ||
|
c2bd1a0e76 | ||
|
de72643898 | ||
|
e0f71b0c17 | ||
|
66f3c50842 | ||
|
373fdb65e9 | ||
|
58a1671076 | ||
|
e0a21e5aae | ||
|
3f33370e7d | ||
|
06fa900504 | ||
|
29d7a0e3c9 | ||
|
e8106016c8 | ||
|
d716bae3d5 | ||
|
f48b09b76e | ||
|
f54944bbfc | ||
|
ff722cac72 | ||
|
eeee90def5 | ||
|
47a02480c4 | ||
|
bbefdec973 | ||
|
f864f72bb5 | ||
|
6623de4e61 | ||
|
33284e988e | ||
|
9d7d6f7108 | ||
|
81b52c7336 | ||
|
fb17ae7751 | ||
|
9bcb589785 | ||
|
2e00f656d0 | ||
|
87f893265d | ||
|
d4205da484 | ||
|
ca0d1f5eb7 | ||
|
e76a545970 | ||
|
ec7282d05c | ||
|
ea3083bd3b | ||
|
6d5b13c80c | ||
|
0da6c7eb45 | ||
|
cdb3afee42 | ||
|
e54c33d9a7 | ||
|
9fec2142ae | ||
|
e32b600530 | ||
|
df63341eb4 | ||
|
6445f12c2d | ||
|
eff5cabb3f | ||
|
f9d5d618dc | ||
|
893979b5da | ||
|
e4f863b4b0 | ||
|
5cf6684461 | ||
|
73756ce262 | ||
|
ee93be80c0 | ||
|
d90ab40a78 | ||
|
f8a2e53f3e | ||
|
e0b4506474 | ||
|
01d9bff3cf | ||
|
db90f5b0a4 | ||
|
bfdc097538 | ||
|
05f47b6359 | ||
|
5b4abec2db | ||
|
d6d6a7cc95 | ||
|
684322cd6f | ||
|
605f3b0381 | ||
|
4bda6efed0 | ||
|
b77771171d | ||
|
e630f647d1 | ||
|
76a098114f | ||
|
e79ade59c9 | ||
|
364a1fa5a8 | ||
|
f905a1676f | ||
|
ff0c44c060 | ||
|
ec34d50459 | ||
|
236a4aac22 | ||
|
899dd0ff4b | ||
|
c6b931766c | ||
|
f8618dd02a | ||
|
37bfcb737b | ||
|
226215eff6 | ||
|
0a34ad50b4 | ||
|
3d803762ff | ||
|
aab63c339d | ||
|
5d6e7e1439 | ||
|
d1815eec55 | ||
|
1cce3cfb75 | ||
|
bf57eb8978 | ||
|
ebb92947a3 | ||
|
5584739b83 | ||
|
8b0004b4cf | ||
|
bf7e8741c7 | ||
|
3cae42bb34 | ||
|
006b2f3564 | ||
|
a385f9a649 | ||
|
8579a56f71 | ||
|
90bd6bf700 | ||
|
1ca1fba8cf | ||
|
a30de1b8d8 | ||
|
fab03398dd | ||
|
78f39e9090 | ||
|
c060d9584d | ||
|
2c60a53fef | ||
|
a59f6d9bd2 | ||
|
a487182e92 | ||
|
f244ebeb4a | ||
|
b4ed51c5f1 | ||
|
516bb685bf | ||
|
66755be8f6 | ||
|
0a7cebe316 | ||
|
fb4582a90e | ||
|
ebda6d3916 | ||
|
8587df17d9 | ||
|
97a3c6e461 | ||
|
89772f40f9 | ||
|
2cfc2b95d0 | ||
|
10a95a5a22 | ||
|
0ea1c41573 | ||
|
d051dd2a60 | ||
|
6dec16bb41 | ||
|
4763887a68 | ||
|
fee88d04d4 | ||
|
f5c68c601e | ||
|
a412f7cca6 | ||
|
dae99cc53e | ||
|
afea11ede6 | ||
|
bc8216a82c | ||
|
f0d9d3c4d2 | ||
|
326de9b535 | ||
|
983c29dab1 | ||
|
73327d011d | ||
|
6d60286bf6 | ||
|
66322c769e | ||
|
9ab0b67bdf | ||
|
8d3843f7dc | ||
|
99dbc68276 | ||
|
d4861f0a29 | ||
|
a39437ac5a | ||
|
3acb9d9f4e | ||
|
51ef70922e | ||
|
d31cfd3c64 | ||
|
18f555464e | ||
|
f43e355254 | ||
|
5859db59c8 | ||
|
b535bf390c | ||
|
2af4bd195f | ||
|
6bebb9e74a | ||
|
d430e7f4e2 | ||
|
b6791977b3 | ||
|
9d972cde98 | ||
|
7513949035 | ||
|
ad44855a1b | ||
|
58645d3908 | ||
|
f8bf7e7d41 | ||
|
2c426defd9 | ||
|
e5c131e0c1 | ||
|
d2e44cf1d0 | ||
|
afae8e8f6e | ||
|
f7012af92e | ||
|
5990c64e31 | ||
|
bfbe471a1a | ||
|
b442b72c9d | ||
|
709fbff067 | ||
|
30737fe580 | ||
|
0c01db4a49 | ||
|
d429ce1849 | ||
|
6773e88211 | ||
|
793624b08c | ||
|
9083bec090 | ||
|
df200d4284 | ||
|
9adee532a0 | ||
|
7d49dd4727 | ||
|
25b19835e3 | ||
|
ee1da09996 | ||
|
36ba004c29 | ||
|
534692cdae | ||
|
c7aed335ce | ||
|
7aeb6c5b5f | ||
|
9fe61dd529 | ||
|
0510cb3aa1 | ||
|
0d7e7f0ab8 | ||
|
c46887e90b | ||
|
12ebd901a5 | ||
|
2ac2fbdf89 | ||
|
a2d1e046a5 | ||
|
62939fd5a0 | ||
|
04b571b742 | ||
|
594716dc1e | ||
|
9578df2abe | ||
|
43358cc6bd | ||
|
d927320719 | ||
|
ad14e656f6 | ||
|
24a40af5bc | ||
|
b7266e7829 | ||
|
6dcf3618d5 | ||
|
da62cfd111 | ||
|
668d910c44 | ||
|
e7f36665bb | ||
|
b6e61f403c | ||
|
f7f718b30f | ||
|
bc942e896e | ||
|
f044b61f42 | ||
|
9a12945304 | ||
|
bf2a925333 | ||
|
cfc2a240c4 | ||
|
436f09d5a5 | ||
|
90b187a837 | ||
|
40551b8ffd | ||
|
73f5cc57be | ||
|
e95b093483 | ||
|
4a4d64be56 | ||
|
d360074323 | ||
|
df5d27d516 | ||
|
b685b7fae3 | ||
|
7e0928b8a2 | ||
|
fff6e037ab | ||
|
0b91bd4b11 | ||
|
cb8c4b4e96 | ||
|
f400a7e171 | ||
|
0bdb5d1ca9 | ||
|
c2136f7976 | ||
|
e4bd4ed060 | ||
|
69d7c4f519 | ||
|
1e8533acbc | ||
|
ab7335d21d | ||
|
bdc3d6fee0 | ||
|
4c22ca2721 | ||
|
825a962323 | ||
|
9c88e755e2 | ||
|
c6b0db61e7 | ||
|
a553485bbd | ||
|
7316cb9d92 | ||
|
33b8f28f62 | ||
|
47b2b151ce | ||
|
a52d601f5c | ||
|
fc6b1cb4b9 | ||
|
ef3cd042fb | ||
|
23192e490e | ||
|
c37a87e490 | ||
|
7e6e024546 | ||
|
36e230ee16 | ||
|
c8ad8aeac4 | ||
|
71da3af1a2 | ||
|
ce69c4e6ea | ||
|
855c1126a4 | ||
|
ac95bfa285 | ||
|
37796bdaa9 | ||
|
36bb5ed795 | ||
|
c417f01a23 | ||
|
1d0af99744 | ||
|
75f292154d | ||
|
1764143db5 | ||
|
7c1cbd33ff | ||
|
5e4febfc2d | ||
|
0292348bd6 | ||
|
27d8befbad | ||
|
51319ebe3f | ||
|
7643e0e17f | ||
|
56311cf0b5 | ||
|
e439eae54f | ||
|
e114b0b28d | ||
|
8405edab41 | ||
|
6a01113610 | ||
|
4ce643ba3f | ||
|
f3e7d94fc4 | ||
|
8cd1f890ca | ||
|
7deeb26e21 | ||
|
457d8baf30 | ||
|
9d33f6f29a | ||
|
823e0a22ca | ||
|
bc91eaf4d0 | ||
|
c6aea0cae6 | ||
|
9c0fceb192 | ||
|
51e6e0b4bd | ||
|
67a74e155d | ||
|
2586eaef92 | ||
|
5fee880fbb | ||
|
483480d7a1 | ||
|
7b40f1cfe9 | ||
|
81d4187d14 | ||
|
f8bd6b9f63 | ||
|
48a15cecf8 | ||
|
09573497b0 | ||
|
45116099c5 | ||
|
26e4fc680c | ||
|
a4a9c53b13 | ||
|
eb1bb8f5bd | ||
|
fc0eb600f3 | ||
|
5b5dd85cf9 | ||
|
74d2896397 | ||
|
a15f62d907 | ||
|
2a73101fab | ||
|
c0e7abcc81 | ||
|
a8001ab453 | ||
|
268e8f5a64 | ||
|
2e12a2db50 | ||
|
31ffb467f5 | ||
|
0d5603eece | ||
|
d6ae6e315e | ||
|
9d35cc1881 | ||
|
82340c058a | ||
|
d80dd81d77 | ||
|
7c24d0665e | ||
|
6f516607a1 | ||
|
83447c201b | ||
|
276496fb4b | ||
|
24378f3c44 | ||
|
d6f160e868 | ||
|
a5a7c1827a | ||
|
7d4e4c7a62 | ||
|
d5b81ead71 | ||
|
9a5d0fed2c | ||
|
516725abe4 | ||
|
8d15338c84 | ||
|
9104d4673c | ||
|
00883e477a | ||
|
ff094e0a03 | ||
|
ee1ce42546 | ||
|
b10d567386 | ||
|
ab2caee0a3 | ||
|
6efca8860a | ||
|
b7a744be59 | ||
|
d7650e2b9c | ||
|
794de9f743 | ||
|
2f9532fe4b | ||
|
2856305977 | ||
|
25a3c001ad | ||
|
3fa7d09b5e | ||
|
6bc88664ce | ||
|
6e5c188eef | ||
|
bdd4f62023 | ||
|
53dce9ff98 | ||
|
ee030cdcac | ||
|
4b016672f3 | ||
|
5f82f0008f | ||
|
5713bb2099 | ||
|
9e7f5671a5 | ||
|
1d9206735b | ||
|
861aa1fa4b | ||
|
4971171e8a | ||
|
5cb53ddd8d | ||
|
f99b977399 | ||
|
0637de7303 | ||
|
3fd94dc40d | ||
|
a638019c17 | ||
|
509038c987 | ||
|
8f6f6a068e | ||
|
4a9a0f6f55 | ||
|
0a808a1ac2 | ||
|
bb1e03e9cb | ||
|
c9865e00a5 | ||
|
6587dfecc0 | ||
|
9821fd92e4 | ||
|
a4959c8509 | ||
|
4d28facf5e | ||
|
053c009cdb | ||
|
82d7fc54b3 | ||
|
f3297dd44a | ||
|
1851453f00 | ||
|
c731b5ef62 | ||
|
0a7f27cb11 | ||
|
140a78fe0b | ||
|
777bce8a13 | ||
|
11b0cb8da8 | ||
|
50ed1f9d1e | ||
|
9202ccccf7 | ||
|
0fe018ff68 | ||
|
b25f93dda5 | ||
|
16d6f98d7c | ||
|
fcddc7751a | ||
|
77ee23bdd0 | ||
|
a05c90abb5 | ||
|
ce390fcd75 | ||
|
92f0db7cd7 | ||
|
f6f7a87dc6 | ||
|
3cb4d81eb1 | ||
|
aff781b8bb | ||
|
5d792324e6 | ||
|
f2b51fe9e0 | ||
|
fb0006beee | ||
|
fa6156ed01 | ||
|
b1517654e1 | ||
|
dd0fba96a4 | ||
|
f61feb33fd | ||
|
2d3073fc02 | ||
|
fb073583fe | ||
|
fcd1f72806 | ||
|
dcf6e1d83d | ||
|
76b143a758 | ||
|
1b898befab | ||
|
7d0780d723 | ||
|
d5c21c9707 | ||
|
3284020b04 | ||
|
7e3872fddc | ||
|
e52a7de3f7 | ||
|
1291a24e4e | ||
|
e08893fcf1 | ||
|
63d86b50e0 | ||
|
e85c98e09a | ||
|
e76cec4afc | ||
|
83ff213f6c | ||
|
dde6baedec | ||
|
c544c3e518 | ||
|
4f12e1dab1 | ||
|
801b6c4bc8 | ||
|
2a7b2c7e32 | ||
|
ad01f99c44 | ||
|
7663451116 | ||
|
d1bf0e0e62 | ||
|
2e96ffc582 | ||
|
3af1e1b3b0 | ||
|
a39e210742 | ||
|
538e540dde | ||
|
519648eb01 | ||
|
903bbc7912 | ||
|
d1d502765b | ||
|
ba89f34751 | ||
|
33a96f8ffe | ||
|
8780fa0d68 | ||
|
e8787c1acf | ||
|
359b4de3ff | ||
|
13f3113826 | ||
|
68db425fe8 | ||
|
c32a5353b9 | ||
|
06ff816afb | ||
|
6836182a36 | ||
|
e802ed04da | ||
|
9b63d6f63f | ||
|
8e89be775c | ||
|
9f10fa3c3d | ||
|
95d54c4160 | ||
|
bef85fb876 | ||
|
9d3ca6ec10 | ||
|
37973f9112 | ||
|
009e350fc7 | ||
|
14686f2652 | ||
|
30816d8155 | ||
|
333e0e1637 | ||
|
989f2a0542 | ||
|
5bd2894146 | ||
|
53e0e3be14 | ||
|
0f0a056225 | ||
|
c296b8ac5d | ||
|
498a708df7 | ||
|
6a7f523cdb | ||
|
12e9c1cebc | ||
|
b036624bac | ||
|
ea9c8a174c | ||
|
d793019607 | ||
|
cebf493dce | ||
|
5d6abfe2f5 | ||
|
a2c12ab13e | ||
|
85e150dbb5 | ||
|
7da66c24f0 | ||
|
df3a8a9234 | ||
|
fc7f3433b7 | ||
|
5a7e065e41 | ||
|
a2c48deab5 | ||
|
309254348b | ||
|
d160feeefd | ||
|
fcee1c0ac8 | ||
|
bc17c61d14 | ||
|
570fc56af7 | ||
|
e7543f656a | ||
|
20f8943eb3 | ||
|
3716b09b2e | ||
|
66b2db0137 | ||
|
7564b05572 | ||
|
17df58b767 | ||
|
2650154cd7 | ||
|
3e4e00fc6a | ||
|
ab7307288b | ||
|
e67b05e632 | ||
|
d105bc272e | ||
|
76e693240d | ||
|
b5fad300ba | ||
|
af409eba07 | ||
|
32edb33608 | ||
|
01d6532875 | ||
|
3945a29ee6 | ||
|
6618827d1a | ||
|
56875a83b3 | ||
|
9798e3c45a | ||
|
73dcde65aa | ||
|
ac722e9e50 | ||
|
d8e86cfe0a | ||
|
8d06e19b70 | ||
|
3d68051a1f | ||
|
5a20a826c4 | ||
|
6fe361b382 | ||
|
3d0616c1ae | ||
|
4f5d73c945 | ||
|
7dc7a673f0 | ||
|
4a6b103f0e | ||
|
6fabb56104 | ||
|
81212c5031 | ||
|
3c82a0d162 | ||
|
e16f6fb9d9 | ||
|
b35cc3f716 | ||
|
2ca57d7cdb | ||
|
605cd5cd9f | ||
|
0230a44b15 | ||
|
fdc045327a | ||
|
a98652bef6 | ||
|
95d3741e32 | ||
|
0bf48e94cc | ||
|
cab5b09591 | ||
|
920e4ae1e2 | ||
|
4dd3097493 | ||
|
a22c48b6b0 | ||
|
e6048e4a34 | ||
|
5a76d35ca8 | ||
|
dd7177dbb2 | ||
|
cc184d5206 | ||
|
4d7c31540e | ||
|
6ddabc0f49 | ||
|
57720311ad | ||
|
7ed2fa8451 | ||
|
abfb9fccd3 | ||
|
f7fa9f6e9d | ||
|
7e512c637a | ||
|
361debd277 | ||
|
2e6cf8e04b | ||
|
dbd64f13f0 | ||
|
8b85f648ca | ||
|
b85369d6e8 | ||
|
0f92856c3f | ||
|
3cf52dac59 | ||
|
b5fc6419e6 | ||
|
a0090dd0d8 | ||
|
af35fa1e33 | ||
|
3833f87256 | ||
|
b9fbe52f32 | ||
|
93e93a50ce | ||
|
073b4dacfb | ||
|
0f2bfda830 | ||
|
399043d37e | ||
|
1749dd8d6c | ||
|
6997ddda89 | ||
|
7fed79295d | ||
|
ce1609726f | ||
|
bcdfc50a0b | ||
|
72907da2ae | ||
|
c4466f450b | ||
|
cffbf3cab1 | ||
|
75cfab8559 | ||
|
ec5519ccd1 | ||
|
5c8f39c3bd | ||
|
5423518e1e | ||
|
2288bcd1b4 | ||
|
be092e7c09 | ||
|
8c6b89df72 | ||
|
15c97336a4 | ||
|
df49b4b065 | ||
|
e13204c5c8 | ||
|
dd87b3014b | ||
|
dd35c9b1f7 | ||
|
f0fc8ed5f8 | ||
|
6ab03412ea | ||
|
261c0d5368 | ||
|
73d195ab79 | ||
|
5977e36687 | ||
|
4c8780f188 | ||
|
ea6e79c9c2 | ||
|
394d6e7259 | ||
|
aaeb5d4cd7 | ||
|
83e2fa3112 | ||
|
b3795ac0b5 | ||
|
d701ed7262 | ||
|
099303a342 | ||
|
acdec22d58 | ||
|
4a07010539 | ||
|
85465fc0b1 | ||
|
bd04517477 | ||
|
6a0e8a557c | ||
|
2557f282a4 | ||
|
0ed1e77996 | ||
|
d7e4dda3e1 | ||
|
8fe40a64d0 | ||
|
851401dded | ||
|
1516fc1c57 | ||
|
bed82e5cc0 | ||
|
ffee6aacff | ||
|
7eacef99b9 | ||
|
7703cd226b | ||
|
713bba00ac | ||
|
60bb2d9c74 | ||
|
f72858ee0a | ||
|
0a69d5ac4c | ||
|
ec44e3d265 | ||
|
b861ad37a4 | ||
|
4b3c26f991 | ||
|
d525d1e8a7 | ||
|
152e56c5d9 | ||
|
38652bd296 | ||
|
39e27076ad | ||
|
4c101c974d | ||
|
719f3788e6 | ||
|
4b6036fa08 | ||
|
4b56f2530d | ||
|
9b4db8b4f0 | ||
|
44511f9254 | ||
|
0abc2b1374 | ||
|
f282823e78 | ||
|
542cee9ace | ||
|
54b14001fa | ||
|
8dfdebc2a2 | ||
|
b50ed887c7 | ||
|
bdf1e6afcc | ||
|
f7ba063b22 | ||
|
dfccc3fb04 | ||
|
b98150526c | ||
|
d68b3f131d | ||
|
f68cb37efb | ||
|
025244e8b2 | ||
|
1253a5c577 | ||
|
dd13526f6c | ||
|
965dff3d32 | ||
|
3e5d9b92c1 | ||
|
dd21e61371 | ||
|
42e23a9633 | ||
|
4ba2587226 | ||
|
5f5476ceb8 | ||
|
56b6adec68 | ||
|
097815cb71 | ||
|
1177c39772 | ||
|
2d222a5920 | ||
|
4ac406da6b | ||
|
f4c9524eb4 | ||
|
bbe9d73e9a | ||
|
9ca294740d | ||
|
9228ce4bae | ||
|
7b14907f7f | ||
|
aa929fe6ae | ||
|
3a01068747 | ||
|
3ea9d80859 | ||
|
41eabfcc1e | ||
|
bfcf4c4281 | ||
|
58db82dbe2 | ||
|
b4c4fddb10 | ||
|
fdbf6bf7d6 | ||
|
490f771faf | ||
|
c1288810c6 | ||
|
195305ce2e | ||
|
da6bd4168b | ||
|
48ac6cbcf8 | ||
|
5281e09828 | ||
|
b1df272f4c | ||
|
51caf97705 | ||
|
2472a2be0c | ||
|
9e04be754c | ||
|
62fa1ed524 | ||
|
304b067417 | ||
|
fc2b58a4eb | ||
|
153b8572dd | ||
|
a2fe7def1f | ||
|
540c93955e | ||
|
0f8fb62581 | ||
|
1d9542fe88 | ||
|
2d10cd0ecc | ||
|
86c2633280 | ||
|
9a62119a82 | ||
|
0e912bca5b | ||
|
607cc6c782 | ||
|
d852e6b063 | ||
|
9c5b96ccbe | ||
|
7fe7b6d9a4 | ||
|
f9e588dd7b | ||
|
c0fc83988f | ||
|
e2b96e1fe0 | ||
|
98d0ef3fff | ||
|
3830f9b464 | ||
|
95f0384510 | ||
|
bb1be15dc4 | ||
|
16d84f8ecf | ||
|
a76f768077 | ||
|
fa019bd03e | ||
|
6f914d1851 | ||
|
f1c50e5648 | ||
|
c83e9d0f1b | ||
|
9e4c1fe6af | ||
|
de6066d6b0 | ||
|
b49d7ee29c | ||
|
7661e93a56 | ||
|
8453dda7ef | ||
|
5b29b6d064 | ||
|
8ecdab68c2 | ||
|
5e61f3493e | ||
|
6dc50d01b5 | ||
|
44db0c55c5 | ||
|
774598974e | ||
|
aa4bc50c6b | ||
|
8365f4d80e | ||
|
9fa639ede9 | ||
|
b63c3a66ca | ||
|
fbad6b5514 | ||
|
2ce0eae406 | ||
|
d1c9acf439 | ||
|
832879e5c8 | ||
|
92a9fd3359 | ||
|
b03117b262 | ||
|
e7690ab658 | ||
|
4df541e47a | ||
|
51b61ccdbf | ||
|
db3ff67182 | ||
|
82c8dccddf | ||
|
fa4e5c98d0 | ||
|
616cafb04a | ||
|
d88ec35a6a | ||
|
96b4467f86 | ||
|
74876fd410 | ||
|
dab40e832c | ||
|
b49baed696 | ||
|
3df67b376b | ||
|
b0c15497e1 | ||
|
190c1dcede | ||
|
1294572907 | ||
|
da8bf76c15 | ||
|
b277c3810f | ||
|
691e93834c | ||
|
fcd81a7b86 | ||
|
d77dabadbb | ||
|
5c220ab64b | ||
|
ba6594e30a | ||
|
f3ea2f354e | ||
|
a2d9b6f9ce | ||
|
8c1e402e02 | ||
|
df39f5e7fc | ||
|
78146e903d | ||
|
7472bc3570 | ||
|
3e7b775961 | ||
|
edbbd42d80 | ||
|
f36b4e0fca | ||
|
e51e54eeeb | ||
|
fee68a5331 | ||
|
761ba164ee | ||
|
c05fd9607c | ||
|
eb526b7769 | ||
|
301190a16f | ||
|
99143c5872 | ||
|
1d2319207a | ||
|
0b666f605f | ||
|
7feb82c242 | ||
|
34c65f60ef | ||
|
df19b8df69 | ||
|
1fec0b1833 | ||
|
bfff19ca9f | ||
|
a101a23ba3 | ||
|
4f86f5b76d | ||
|
7029dda87c | ||
|
0c86357187 | ||
|
0bb845a05c | ||
|
f238c97c6f | ||
|
3a9b3b4729 | ||
|
2bfb510554 | ||
|
604cdde71d | ||
|
690f32c298 | ||
|
504b2de4a8 | ||
|
e3fa460374 | ||
|
3c4fa5583a | ||
|
025c5aebc9 | ||
|
6279ff32f5 | ||
|
cc681c623f | ||
|
007629464a | ||
|
e5a7a674f9 | ||
|
7234a7c630 | ||
|
4e8853c9f7 | ||
|
afb9647f0d | ||
|
c116e064d8 | ||
|
dbd1976fc1 | ||
|
1601030081 | ||
|
8d1964bc3b | ||
|
65ca2f9a07 | ||
|
de0d12d73c | ||
|
439eb294bb | ||
|
ca6d5660c1 | ||
|
b010fb5004 | ||
|
360ecd3728 | ||
|
59365b0133 | ||
|
1b45870af8 | ||
|
07c63c14f3 | ||
|
77a2c77c45 | ||
|
4e9d439f39 | ||
|
6431459bcf | ||
|
6a1eea1b3d | ||
|
d8094e0629 | ||
|
77933aa8a1 | ||
|
7dc281ff2f | ||
|
c4c2c77a40 | ||
|
223467c816 | ||
|
0be532640a | ||
|
bae6a5eeec | ||
|
4f082da02b | ||
|
21c95bbec5 | ||
|
3f520b864b | ||
|
c7ea25227a | ||
|
fb65732076 | ||
|
e8b418ca03 | ||
|
8961d987a9 | ||
|
492da38d67 | ||
|
6956d218b5 | ||
|
b7c2a30d7d | ||
|
290060e6c4 | ||
|
4260910db6 | ||
|
55b0b09d6a | ||
|
1b1548f539 | ||
|
9f00159228 | ||
|
0092417b85 | ||
|
ec1a892f44 | ||
|
5e83ff8835 | ||
|
3091552504 | ||
|
fb38c20c16 | ||
|
f8d256dafa | ||
|
0a5c9b0465 | ||
|
41c3b05a20 | ||
|
931a7a2210 | ||
|
fee056a251 | ||
|
9a086face4 | ||
|
341697630d | ||
|
561e8b42a8 | ||
|
3a60d723d8 | ||
|
39b5e5b98a | ||
|
7cc43893d8 | ||
|
61daa5f3e7 | ||
|
a56c5e6ba8 | ||
|
cc759530fe | ||
|
bc167a211b | ||
|
d8af9f4e7b | ||
|
59d946bf00 | ||
|
6840df4c09 | ||
|
04e36456bd | ||
|
9daa884699 | ||
|
5c08698fa0 | ||
|
0e43ce6e07 | ||
|
70be064aa5 | ||
|
f576926fa2 | ||
|
b74065a471 | ||
|
aab1cf020c | ||
|
5f025cc1b8 | ||
|
81c71ff4ab | ||
|
ab902bfb66 | ||
|
339f63a61a | ||
|
1b7c90e9e1 | ||
|
965e8ae74e | ||
|
61bbc19b0b | ||
|
684b142711 | ||
|
4dc7d3dc45 | ||
|
2b39c43e81 | ||
|
9e0d2c15cd | ||
|
9ed8178dff | ||
|
4fa4dd8fc8 | ||
|
dfe45338c0 | ||
|
32a29c11a0 | ||
|
d02c696030 | ||
|
f9b7be7576 | ||
|
65ca9afce2 | ||
|
abb94c8bf3 | ||
|
99e981abcf | ||
|
206abaebb9 | ||
|
ac3d548079 | ||
|
7e1cb97c7e | ||
|
51674f2cc4 | ||
|
413da805b5 | ||
|
8a21cde188 | ||
|
f38d34919b | ||
|
2eac4f6606 | ||
|
9196c7268a | ||
|
3795a4869e | ||
|
2b48668327 | ||
|
312360ef4e | ||
|
db9a70d008 | ||
|
1b6d840ada | ||
|
afec8358d3 | ||
|
1e1137a6f7 | ||
|
935d1476e9 | ||
|
25b35ebddd | ||
|
b602fb4a5e | ||
|
54bd7d26dc | ||
|
d69dd17d3c | ||
|
dd7a60c7a8 | ||
|
ade37be25b | ||
|
79659bdc76 | ||
|
06ff866e9d | ||
|
e3fc3ccada | ||
|
75b6a31e87 | ||
|
baeab01357 | ||
|
4a184dbbe6 | ||
|
5d774d360e | ||
|
94df97a5ae | ||
|
6e2e122288 | ||
|
e9abcb2f6d | ||
|
8fda8e2071 | ||
|
dc85c66052 | ||
|
03e039b13d | ||
|
2bcdb07b62 | ||
|
ceb1dd8d83 | ||
|
093eb3915a | ||
|
dcea17f8c6 | ||
|
f1a050366f | ||
|
ee14a3fb31 | ||
|
375668bc40 | ||
|
246b5282e5 | ||
|
6d8b511d25 | ||
|
ee4f370bdb | ||
|
afb101e234 | ||
|
8f434cd02c | ||
|
ffced4e17f | ||
|
7d1c7a0474 | ||
|
ad39446e87 | ||
|
7e3c081007 | ||
|
78c264769e | ||
|
b6a1e29d11 | ||
|
4e66d3ca21 | ||
|
8b5ce1a772 | ||
|
118081c45a | ||
|
14d00a7f72 | ||
|
899538ae25 | ||
|
e5bbabb2ba | ||
|
1d8886fa7d | ||
|
be745f711f | ||
|
a62238c19d | ||
|
7462a69583 | ||
|
4d0e05a8c1 | ||
|
2b11448b45 | ||
|
0a71ee95af | ||
|
c49beccadc | ||
|
3497eab26c | ||
|
236b6f1c3e | ||
|
2b39af1f09 | ||
|
eed0e8b03a | ||
|
ccd6ad52cc | ||
|
c623e5f232 | ||
|
dccdedc001 | ||
|
d1930ac708 | ||
|
a688370c9d | ||
|
2df61d0a8c | ||
|
3c60f99def | ||
|
e54784bfeb | ||
|
308f36a532 | ||
|
a5cc14ce8c | ||
|
06e97d107b | ||
|
7ac3dd0889 | ||
|
ffde22e711 | ||
|
e69e8254db | ||
|
372779915f | ||
|
7818ed98c7 | ||
|
e00de28556 | ||
|
b731ccea18 | ||
|
0309462b36 | ||
|
b4cd7ca49a | ||
|
7b8af30590 | ||
|
1d95fe481f | ||
|
2faa28ce9b | ||
|
0b5285c282 | ||
|
ffae847b48 | ||
|
6b8c9cac23 | ||
|
6d98f6fc9a | ||
|
cf366bdb29 | ||
|
fb7b11553a | ||
|
a1bde406de | ||
|
56f46edab8 | ||
|
47f7366a5c | ||
|
d0d8002b66 | ||
|
e9cc4c848b | ||
|
d31b7d8968 | ||
|
d957a10f47 | ||
|
ba3b2712a1 | ||
|
0238295654 | ||
|
94f717a255 | ||
|
7849fff41e | ||
|
f0e8abb07b | ||
|
e04d58f647 | ||
|
abfa8e95a2 | ||
|
e120c75d17 | ||
|
0e088d783d | ||
|
6b5ad69c22 | ||
|
f7e15f2450 | ||
|
8a420d6580 | ||
|
88200a1193 | ||
|
5b8236cdbd | ||
|
b47e76a1a5 | ||
|
be56a06790 | ||
|
4e1fefc8ea | ||
|
d7de40ab4c | ||
|
da91a07906 | ||
|
b1428c2b56 | ||
|
c1c7d7a3e2 | ||
|
36d046f3ca | ||
|
d3c3e0b164 | ||
|
53401d6962 | ||
|
e3666457cf | ||
|
35202e28c2 | ||
|
50495c6898 | ||
|
d520aa2633 | ||
|
a3eceb0c6c | ||
|
2593477151 | ||
|
100d1c7bf6 | ||
|
e390e6b0af | ||
|
96e9a73a1b | ||
|
c2f0778e80 | ||
|
39cf190db7 | ||
|
b8e6799d67 | ||
|
2a0820278b | ||
|
e7cc0477fb | ||
|
17fa7edbbf | ||
|
c6b3faca64 | ||
|
8ead8061ee | ||
|
bf68c87a08 | ||
|
d90ec3d192 | ||
|
322da07010 | ||
|
8019d222a7 | ||
|
f9a08e285c | ||
|
abecd80f54 | ||
|
3226e1ca33 | ||
|
752dad6829 | ||
|
4f69ee0437 | ||
|
e0c2995f78 | ||
|
1b0efa97b2 | ||
|
622632616c | ||
|
4ac95b44e2 | ||
|
9cb4c99e5b | ||
|
301a4720b7 | ||
|
15c3233376 | ||
|
faf0428b60 | ||
|
dd3b6d49ee | ||
|
7246c9c74b | ||
|
5395f13528 | ||
|
9083923f27 | ||
|
5d6c53c9bd | ||
|
7438cfc47d | ||
|
b6024adf85 | ||
|
5b6d47aeef | ||
|
c79ecee094 | ||
|
b953f9b66a | ||
|
10a7750027 | ||
|
5c0f9f1c40 | ||
|
d97f6b3812 | ||
|
3e08829c39 | ||
|
ae66b98aac | ||
|
871a9c063e | ||
|
2ff3dff6ea | ||
|
4bda7bf175 | ||
|
21e8c31aec | ||
|
630118b5a5 | ||
|
7f3e718851 | ||
|
5dac15fa85 | ||
|
6e8f97667a | ||
|
d41dd13918 | ||
|
b4d3f78d67 | ||
|
9ae2ed39ec | ||
|
6c00dc71c1 | ||
|
6f03c2bfd1 | ||
|
98f86d553a | ||
|
10914c8346 | ||
|
73644adf69 | ||
|
f3e7d7c725 | ||
|
2985561012 | ||
|
e8841ae9e2 | ||
|
b68375d462 | ||
|
a247d5b2dc | ||
|
f79159a69c | ||
|
4ccc82be92 | ||
|
9e4c122cf3 | ||
|
407c8c3fb9 | ||
|
6ab0fc4e14 | ||
|
5ed14c1064 | ||
|
2c0a1b652c | ||
|
010be34660 | ||
|
cd0c77dc5c | ||
|
444233bd1b | ||
|
7de4a3a2df | ||
|
2129edaae9 | ||
|
209e8eff35 | ||
|
32d2cd74d4 | ||
|
4cb610d9ae | ||
|
697d17ae9b | ||
|
fefc756870 | ||
|
febf774b4e | ||
|
a02db13278 | ||
|
f19911660d | ||
|
d5252ddf64 | ||
|
67a07adb0b | ||
|
5ff771b125 | ||
|
a34bea1dad | ||
|
1e269c94a6 | ||
|
60f6a3fed3 | ||
|
b9c1d0bd18 | ||
|
4ab607d4de | ||
|
d115006597 | ||
|
10068cee72 | ||
|
b5b6e3dd2a | ||
|
f0e20142af | ||
|
604ea5d684 | ||
|
146feea4a6 | ||
|
65482c5e9d | ||
|
bfd238e000 | ||
|
ecd880c8e7 | ||
|
30be0972b9 | ||
|
b21932687a | ||
|
e128285a77 | ||
|
7d17d77254 | ||
|
d6eb053826 | ||
|
cc95edf4dc | ||
|
a8a11e56db | ||
|
039729a074 | ||
|
4176936d34 | ||
|
45087599ef | ||
|
9b34c77f98 | ||
|
33ba5b63f3 | ||
|
f967bc4ac0 | ||
|
d209100a60 | ||
|
78b3d71745 | ||
|
2d5541f533 | ||
|
9aae4614a1 | ||
|
317addb7e3 | ||
|
a11e9acd12 | ||
|
e93466c897 | ||
|
72fa6a79a2 | ||
|
e7a7bd6695 | ||
|
80db43442e | ||
|
f2929f3b19 | ||
|
afbda7baa8 | ||
|
872e5537bb | ||
|
a20892e5e6 | ||
|
b0228e66a0 | ||
|
a48f321fac | ||
|
67ccfa1da1 | ||
|
bdeea80032 | ||
|
0371dcb7bd | ||
|
455ecf700c | ||
|
82f90085ee | ||
|
66ab102111 | ||
|
2adc25eec7 | ||
|
ee07bf29ce | ||
|
e43b1084c3 | ||
|
28e2383778 | ||
|
dd91121017 | ||
|
b7ebfab575 | ||
|
f157461ee1 | ||
|
35bfbc39f8 | ||
|
49a4499d52 | ||
|
7ddfd2f030 | ||
|
44a7c3d0b6 | ||
|
1bf851ad91 | ||
|
f72196f1bb | ||
|
2df7a5fe51 | ||
|
0892bc90ed | ||
|
63f8cc3503 | ||
|
62153b1ff0 | ||
|
c088a9a660 | ||
|
655ed93ee8 | ||
|
ae9f35668e | ||
|
d30e47a50e | ||
|
6b3f5c977a | ||
|
0abf2abc81 | ||
|
e7641e38d8 | ||
|
b0f9d73f6a | ||
|
30434325a2 | ||
|
34f9e69bff | ||
|
01a02a8985 | ||
|
e99f86a052 | ||
|
f94f69c05c | ||
|
01ea968d4f | ||
|
696089c479 | ||
|
2e840bc89c | ||
|
45f91facf1 | ||
|
0c7c34e609 | ||
|
8de1e92b99 | ||
|
ffd53edf89 | ||
|
47c058a337 | ||
|
0a3a860b27 | ||
|
cbd32035c8 | ||
|
aa95c09a88 | ||
|
191f409741 | ||
|
34a8daa629 | ||
|
1d96ba7dfc | ||
|
b24053f4df | ||
|
7f85c0ce2a | ||
|
3006ab9253 | ||
|
af8c5bb31a | ||
|
67db54d765 | ||
|
dea25b194e | ||
|
9b678a58e8 | ||
|
52828c0444 | ||
|
f8bf4a1522 | ||
|
a2c05123fd | ||
|
5b0eeb75ef | ||
|
d82c2c8cc6 | ||
|
c1108da6e9 | ||
|
017430e803 | ||
|
c509733a30 | ||
|
b7006034b1 | ||
|
6e5a563c06 | ||
|
c4025622de | ||
|
b5f4c40406 | ||
|
317a59d550 | ||
|
a6891e9f9f | ||
|
0e7c115b73 | ||
|
bfbeda4ede | ||
|
3c7ba5045d | ||
|
46126b5fb6 | ||
|
141c2fd34a | ||
|
0b71baac4a | ||
|
2ca4adb62c | ||
|
7f6e2a3a35 | ||
|
81b57ceddb | ||
|
fe6bdc870c | ||
|
fdebeaf95b | ||
|
8826fdb03a | ||
|
e8095826bf | ||
|
9374e31072 | ||
|
1a3f3ffdbf | ||
|
45043c3f52 | ||
|
f2d12aebef | ||
|
3fdb912db6 | ||
|
5086dc3024 | ||
|
204a63a00e | ||
|
06862c7dee | ||
|
003eef982b | ||
|
bb043ba2fd | ||
|
94efa3ac19 | ||
|
58c3948316 | ||
|
ec62d6f7e0 | ||
|
6a51ff22d0 | ||
|
6349da22ac | ||
|
9c5ef1f776 | ||
|
32fafd7a24 | ||
|
88cc4cd17a | ||
|
48f28fe29c | ||
|
2b02651bbf | ||
|
c4989ddc44 | ||
|
eecd823d32 | ||
|
fbb9affadb | ||
|
9f0bfa01ca | ||
|
2bd5c3dba7 | ||
|
f212ae6322 | ||
|
b5f1524e78 | ||
|
ad30a6899b | ||
|
abb62d5c2a | ||
|
e234b7fdbf | ||
|
4000ec681f | ||
|
e84f308264 | ||
|
1f3f91136c | ||
|
e70d5c52e4 | ||
|
a1b8fabd99 | ||
|
5830767273 | ||
|
b1935340c8 | ||
|
671ed1b9db | ||
|
af1fbb39ca | ||
|
53c3b772dd | ||
|
73128039a6 | ||
|
16f39487ba | ||
|
ab6153853b | ||
|
53bde282bb | ||
|
7a810def28 | ||
|
0bdd4a93b8 | ||
|
3e85bebc66 | ||
|
9902714fb4 | ||
|
0209c6e0ff | ||
|
d53028d0a8 | ||
|
f245d66c8a | ||
|
98e0618750 | ||
|
2d9a15324d | ||
|
c345bc3d85 | ||
|
8238fb37f3 | ||
|
70ef33f6fe | ||
|
31a0f5ef28 | ||
|
e317fcf1d8 | ||
|
cd0fbbe948 | ||
|
18fbf3c174 | ||
|
1be3e5ec5e | ||
|
7c05a906d0 | ||
|
b13cd0d484 | ||
|
c466def1e8 | ||
|
5eb8cea6e7 | ||
|
a068f72f08 | ||
|
7d348e2260 | ||
|
dcfb8c1a10 | ||
|
417a037ac5 | ||
|
09e40c2f0d | ||
|
decc5922de | ||
|
715fb688d3 | ||
|
689993a592 | ||
|
4938dfd971 | ||
|
eb895fbb31 | ||
|
2e9df2c39d | ||
|
ee1dfb5605 | ||
|
55bef6ec2f | ||
|
9e30d85fdc | ||
|
aa09cb266d | ||
|
fb68b0924a | ||
|
985b0fbf7f | ||
|
58fec9181f | ||
|
0b46cce95f | ||
|
45f2b82411 | ||
|
8b3ec61151 | ||
|
c522f564a9 | ||
|
de76b8a465 | ||
|
c894f7ac1f | ||
|
78fab4e471 | ||
|
2d6a1160d5 | ||
|
5180919e52 | ||
|
381ed505a0 | ||
|
a562f2c434 | ||
|
13b96ae7c8 | ||
|
5fb32229f9 | ||
|
079b24175c | ||
|
b31b067743 | ||
|
e33f9c1d11 | ||
|
0fa9c93f6f | ||
|
abe7518424 | ||
|
17e6d2d51c | ||
|
6ae93cf14e | ||
|
c72c020182 | ||
|
c369bba03b | ||
|
3605593d37 | ||
|
e3a8780da7 | ||
|
2d0d7150cc | ||
|
ae3dfe9327 | ||
|
49fdd23d58 | ||
|
db0d20e17b | ||
|
0ec917e980 | ||
|
99f4bc76d7 | ||
|
5fb9652af5 | ||
|
023e5b4b90 | ||
|
7f3bb37b96 | ||
|
797e7d98d3 | ||
|
96ff69e7ef | ||
|
e7f82cf62e | ||
|
c94c799347 | ||
|
b9ad093d06 | ||
|
a35db9f454 | ||
|
9a9c4f6ad6 | ||
|
80b9e931f5 | ||
|
02d462e263 | ||
|
10efc7e2d3 | ||
|
b640df8fbb | ||
|
6345b54034 | ||
|
7208b9b525 | ||
|
ad1835e87c | ||
|
4c32177ef3 | ||
|
253bc333c7 | ||
|
9ad0ff960e | ||
|
7664ff5acd | ||
|
7d35f994d2 | ||
|
2cc1c92901 | ||
|
5080cc3c9e | ||
|
e5042d0d38 | ||
|
81de7e1075 | ||
|
d9101b9559 | ||
|
49a55886d3 | ||
|
298ea1d748 | ||
|
d157930f2c | ||
|
88145752ff | ||
|
319a01b34f | ||
|
d51958dbf5 | ||
|
801fcf3271 | ||
|
df144a6fcb | ||
|
410ded5d45 | ||
|
d994d7a10c | ||
|
649552e0f0 | ||
|
36f94fde05 | ||
|
fcdaa0d05b | ||
|
aaff34364d | ||
|
69561da15f | ||
|
04b641ff5f | ||
|
fefeb50cae | ||
|
d93a5d3da1 | ||
|
360f1fa34f | ||
|
c7af702270 | ||
|
fec4ccbbb8 | ||
|
30f2057bdf | ||
|
bb893daf7d | ||
|
4fc16a9eec | ||
|
895350e77c | ||
|
b5cbe34a2a | ||
|
ea14290f52 | ||
|
c647963911 | ||
|
99075ad22a | ||
|
a4fa6a3ac7 | ||
|
3308c40440 | ||
|
69d45f8ce4 | ||
|
5cc40d4525 | ||
|
18582d7b65 | ||
|
b975efa2bb | ||
|
4fea18d945 | ||
|
0f37261dbf | ||
|
3fb8a85f83 | ||
|
2dc92fc245 | ||
|
664dcc063a | ||
|
debbfc117a | ||
|
1aaafa2d11 | ||
|
58307479ad | ||
|
9c3bd83d3b | ||
|
e0b15116a5 | ||
|
e36a7fab2d | ||
|
2007de3241 | ||
|
d8c3c247f8 | ||
|
22261bc2d1 | ||
|
40e29c92bc | ||
|
d365281cce | ||
|
451ef7f09b | ||
|
2d52c7b42f | ||
|
d4175ca6c6 | ||
|
24c8510932 | ||
|
0fca4f95d6 | ||
|
02f2c611d4 | ||
|
0ad0c7882f | ||
|
a30f43b36c | ||
|
ba4d8ceaf3 | ||
|
45d44bedef | ||
|
cd0b62338b | ||
|
b844c8a29a | ||
|
92a9ff3c1a | ||
|
a1a8181913 | ||
|
9c1030878e | ||
|
4936e99f30 | ||
|
7fb46bff06 | ||
|
dc51dde112 | ||
|
c05924a713 | ||
|
f1dcdab75d | ||
|
d970237658 | ||
|
03b0ea6139 | ||
|
56c275b0cb | ||
|
62210fb932 | ||
|
b49c039f41 | ||
|
3d24ae0981 | ||
|
801b6e0ded | ||
|
4dd68f1ef3 | ||
|
15aebacbe7 | ||
|
7599e3998f | ||
|
204fe44b29 | ||
|
f4453f77b8 | ||
|
ed5efebec1 | ||
|
67c1f71b6e | ||
|
f0a6b5d401 | ||
|
a24b6ad983 | ||
|
699aab0b80 | ||
|
84f11278ba | ||
|
cbcda140ec | ||
|
b0f1f8307d | ||
|
ad67ea0eb5 | ||
|
125aa0aeb4 | ||
|
df0ef6618d | ||
|
0409e95d1e | ||
|
104367ce24 | ||
|
5f2cb5e52f | ||
|
8118444442 | ||
|
9e185030e6 | ||
|
f4f8b411f5 | ||
|
6334893561 | ||
|
76f0247f5d | ||
|
44ae70e9ec | ||
|
1c2a4c116a | ||
|
61d8f90530 | ||
|
46183b1cdd | ||
|
a78a89b13a | ||
|
e91025282e | ||
|
a3a4a37ec7 | ||
|
687a542260 | ||
|
e7d3036ace | ||
|
33f1d2af1e | ||
|
1d386f60e9 | ||
|
7dcbcb5c31 | ||
|
981854d5ae | ||
|
349316f70e | ||
|
58a27b3334 | ||
|
2d69c7ecf6 | ||
|
2c4f55a8fb | ||
|
2e5f7f1632 | ||
|
e448b84845 | ||
|
972d30c288 | ||
|
59e87b6a51 | ||
|
9c911c7a28 | ||
|
949086baa5 | ||
|
286526072f | ||
|
a9aaacd88b | ||
|
2e90e02a2c | ||
|
c33ff96bb6 | ||
|
15a9e56b09 | ||
|
cc718bb108 | ||
|
826a4ca0a2 | ||
|
7f77e6004c | ||
|
15d4bc9d6d | ||
|
9f8467aa80 | ||
|
1896c2720b | ||
|
7240eee5a8 | ||
|
9241c6a9b5 | ||
|
17de29e69a | ||
|
f22745eb0a | ||
|
5d9f5e3509 | ||
|
5eba9078fc | ||
|
a3e693d5c3 | ||
|
36c91e4ed5 | ||
|
83690fdb90 | ||
|
38c00fae88 | ||
|
465c7eeaa2 | ||
|
a4195f2780 | ||
|
8a3e82d99a | ||
|
01ce28bc9a | ||
|
3d8dc11442 | ||
|
826bf28196 | ||
|
8798ed4499 | ||
|
d069b98b45 | ||
|
3637c14da5 | ||
|
201f34620a | ||
|
71c68cc000 | ||
|
bcc84a31b7 | ||
|
72094a8bee | ||
|
944f9d7fb5 | ||
|
c7a043b864 | ||
|
57626b60e7 | ||
|
6b16fe3f14 | ||
|
4a7bcfe4f0 | ||
|
59488769cf | ||
|
547031aa0e | ||
|
196c3e7e9b | ||
|
16caf032bd | ||
|
4900e90112 | ||
|
02edc958d7 | ||
|
2c254e3b32 | ||
|
e73569c84d | ||
|
cf772ef555 | ||
|
0f41121556 | ||
|
6dfa097752 | ||
|
d9cb2c7781 | ||
|
72f3576c78 | ||
|
614a80bd23 | ||
|
796482c129 | ||
|
b6fd18a9ca | ||
|
64e6d36944 | ||
|
f9033facd3 | ||
|
53609f703e | ||
|
4d5280e43c | ||
|
dc548315b8 | ||
|
7489a2b36f | ||
|
91f1c84048 | ||
|
e1cd60987b | ||
|
df51cad7e2 | ||
|
a60c5527c7 | ||
|
a6d3d4ea4c | ||
|
97c67bc259 | ||
|
d152fa3e85 | ||
|
a19be3cb51 | ||
|
e871b7a0ac | ||
|
c8e176b2e3 | ||
|
e479260e1f | ||
|
367a5c998d | ||
|
b824bc29b5 | ||
|
d168479a85 | ||
|
bedce59a6f | ||
|
b84be93bb7 | ||
|
a7da8eb593 | ||
|
575c51f5a0 | ||
|
c50dacff17 | ||
|
d4532fac18 | ||
|
4dd26c0d7c | ||
|
acf9096a83 | ||
|
a99d4c3dd3 | ||
|
c1e5c36f7e | ||
|
4eff7abf3b | ||
|
a5531e83fd | ||
|
7f07f7d567 | ||
|
41fed96d96 | ||
|
ed20fa4da1 | ||
|
e06d1dbb0c | ||
|
1efa7e7b10 | ||
|
ce8f8f40d7 | ||
|
4efd673c19 | ||
|
ca27234a59 | ||
|
3111421866 | ||
|
42dcdde4e8 | ||
|
d6a0792bd9 | ||
|
7931d6a02a | ||
|
07e150a147 | ||
|
649de53382 | ||
|
ba6e41cf07 | ||
|
ba35d69831 | ||
|
8d04dffe69 | ||
|
df52394871 | ||
|
58f15f1674 | ||
|
8cb4bc2127 | ||
|
963bcba267 | ||
|
efc46e78f1 | ||
|
00b58d1206 | ||
|
f7fb1bfa1d | ||
|
38b4ba8d33 | ||
|
121b742482 | ||
|
8aa2ed175e | ||
|
fb3668f903 | ||
|
e4c7a7f30a | ||
|
57479dac27 | ||
|
47e1e18e7e | ||
|
172300158d | ||
|
df910277a2 | ||
|
9994133446 | ||
|
8f5a863468 | ||
|
6ea932b1de | ||
|
a2fdf38316 | ||
|
6afe0b0612 | ||
|
7bbd48a2bd | ||
|
ca6e1aeb1b | ||
|
03de333de1 | ||
|
42ad8bbdb0 | ||
|
2501334ed7 | ||
|
15f19fc026 | ||
|
f3874ffaf2 | ||
|
4b74f65ed8 | ||
|
033a75888c | ||
|
6b874cedb8 | ||
|
5f01fb7f1a | ||
|
8eda5fcbbf | ||
|
c768c302da | ||
|
c7c05552f3 | ||
|
788dd04600 | ||
|
f55a69841d | ||
|
41df8413fc | ||
|
b2a42f03eb | ||
|
edea41d35e | ||
|
75ac263961 | ||
|
4c516e1f6f | ||
|
5c3ea4ec29 | ||
|
06eb561af5 | ||
|
51ef847c64 | ||
|
1349b4ba07 | ||
|
4f63377ff2 | ||
|
f86457ffd1 | ||
|
d368006526 | ||
|
cd3a1c1a94 | ||
|
dabd5b2cc8 | ||
|
9418fcafb6 | ||
|
7f4a7abf49 | ||
|
ae8ff941f9 | ||
|
c1599a3d85 | ||
|
2e7f29cc9f | ||
|
c2a8415f78 | ||
|
0e36deec81 | ||
|
317df7e6a6 | ||
|
1fb11c23d8 | ||
|
52f7978d4d | ||
|
b9b5c84417 | ||
|
882f963e6b | ||
|
dec0c8ddd6 | ||
|
9773aa4c76 | ||
|
05aa960c25 | ||
|
19f01194b1 | ||
|
6dd5297f8a | ||
|
139ee17d39 | ||
|
f5d0fa2c21 | ||
|
a5299bdb1a | ||
|
99bfef2801 | ||
|
241de230bb | ||
|
bba1688981 | ||
|
6a480ffb5b | ||
|
ad6ea4fa49 | ||
|
815f427c95 | ||
|
754abdb861 | ||
|
8a364bbfaa | ||
|
e89d49d958 | ||
|
68c8948294 | ||
|
5ec5d2fa0b | ||
|
45d8cc706c | ||
|
d7e531c373 | ||
|
c5c1978584 | ||
|
a328eef8d9 | ||
|
5a20c63b70 | ||
|
480741894b | ||
|
c2c7231cad | ||
|
265618f306 | ||
|
fd3c8bced7 | ||
|
e6a31e7543 | ||
|
d888190637 | ||
|
de4c9507e6 | ||
|
158ebef566 | ||
|
b14145f23b | ||
|
7c2c39cb11 | ||
|
a4aec56d18 | ||
|
fce0167991 | ||
|
3991178b8d | ||
|
a246278727 | ||
|
1f0f27bc65 | ||
|
a4a36422d4 | ||
|
543965411e | ||
|
37c3f51f6e | ||
|
45c6cc6ac0 | ||
|
d33d54dbf9 | ||
|
bbbdd02a00 | ||
|
e1fef5c732 | ||
|
cbcc369a2d | ||
|
7499efecf6 | ||
|
18c3e90dbf | ||
|
00cdc52323 | ||
|
dd004aba9f | ||
|
2ab21acf11 | ||
|
6b6555852c | ||
|
8a54c91431 | ||
|
2092b805b6 | ||
|
53add78a28 | ||
|
386cdb830d | ||
|
aa41b48ba0 | ||
|
a8d99ae465 | ||
|
f38cc8e182 | ||
|
92f5aa7e10 | ||
|
f47cbe0be6 | ||
|
368e3d1f51 | ||
|
7a4eb15b4c | ||
|
532801f9d6 | ||
|
14b715bdc7 | ||
|
0a62117851 | ||
|
86b00a9271 | ||
|
d35da909db | ||
|
73ad575306 | ||
|
292eb1de62 | ||
|
99a1636c62 | ||
|
c944b2e632 | ||
|
ae0841598f | ||
|
637730996a | ||
|
1a1d5e4a73 | ||
|
d10ade1963 | ||
|
84cab18120 | ||
|
3ff72794e5 | ||
|
e7abd3510a | ||
|
f96731c82b | ||
|
5c7c30cc4b | ||
|
a61b249d57 | ||
|
0b4eef48c5 | ||
|
90b8b612cc | ||
|
c0a8eaa3bf | ||
|
a0dd77ca01 | ||
|
f90802498f | ||
|
cf0b8b7681 | ||
|
602115d81e | ||
|
2fd44e6ce4 | ||
|
23a4a22869 | ||
|
aa9894711b | ||
|
6d81b8a49a | ||
|
b1eab698d2 | ||
|
6978e38752 | ||
|
95d9706fe1 | ||
|
8b1f0b7cf4 | ||
|
20bda5f5a0 | ||
|
e0927f099e | ||
|
4b5a1d01ce | ||
|
78461d8a74 | ||
|
2ec51fc0ad | ||
|
379b920d49 | ||
|
240704fb54 | ||
|
ab86f9e696 | ||
|
da8be072fa | ||
|
4c5e4ac8da | ||
|
5d973f8ef5 | ||
|
fa4426c814 | ||
|
c7cf43d1c1 | ||
|
7626ddc9c3 | ||
|
f7ea667140 | ||
|
8642300d74 | ||
|
673cde3c0b | ||
|
95f6f70d8f | ||
|
48ddd8b312 | ||
|
31be9d25f3 | ||
|
665998e797 | ||
|
0f1a857576 | ||
|
945c1a9478 | ||
|
879195397e | ||
|
c27d96a4f1 | ||
|
c5fa9844f2 | ||
|
4df1772078 | ||
|
f8279bb7bb | ||
|
2a6f38c587 | ||
|
909f3d76d8 | ||
|
9bc82543ee | ||
|
2d87f562f6 | ||
|
7cdddd344b | ||
|
b1f6112c5c | ||
|
c38e33ca8b | ||
|
e6ecd05308 | ||
|
83a13ee0e6 | ||
|
662a8317a5 | ||
|
1243728725 | ||
|
5da7c32bdf | ||
|
79e991d8a6 | ||
|
38ed39ebe0 | ||
|
83325b5b72 | ||
|
79f9e9b061 | ||
|
8c86bd1b31 | ||
|
f299190e46 | ||
|
feb7ac7ba8 | ||
|
78b6de55b3 | ||
|
fc3e1a4f18 | ||
|
b1a390983b | ||
|
85cf93e65d | ||
|
28b66d1b58 | ||
|
2b30e0b5e0 | ||
|
7c78dff386 | ||
|
8146cbdae7 | ||
|
566b6a1967 | ||
|
56768ff237 | ||
|
710339d0a8 | ||
|
eff4cde738 | ||
|
3e86b2e666 | ||
|
0a16c350d1 | ||
|
c58c253cbf | ||
|
5baccafb23 | ||
|
076b602343 | ||
|
860d6c01ed | ||
|
35a5c4787b | ||
|
7799883adc | ||
|
c3ee7f04e5 | ||
|
1c262b2172 | ||
|
b9c4e5b36c | ||
|
a2fe46f8b3 | ||
|
bccf58df51 | ||
|
0fd3e36c23 | ||
|
a4b0e0c93c | ||
|
4944f59118 | ||
|
a0929c4c90 | ||
|
66843a349d | ||
|
f014891288 | ||
|
19d97136a5 | ||
|
68cbd87bc2 | ||
|
04eef37cad | ||
|
cd693dcf3e | ||
|
861cbb874b | ||
|
9358f81ac1 | ||
|
4007865bec | ||
|
7623455cb6 | ||
|
9f91c0490c | ||
|
7785fa3938 | ||
|
36b437270b | ||
|
518b6e647b | ||
|
c37c579585 | ||
|
40744d1bd2 | ||
|
9947493977 | ||
|
e5ff36f1f1 | ||
|
d83d938ee3 | ||
|
c5116e5049 | ||
|
1288b4b28f | ||
|
df9096df80 | ||
|
0a36ba54dc | ||
|
af53a8da83 | ||
|
4c28ef40b7 | ||
|
073564b508 | ||
|
0bd4d5bb0c | ||
|
43629d0fd4 | ||
|
1617264b69 | ||
|
91d6f14e3c | ||
|
6dd1fe0312 | ||
|
ed7c366b90 | ||
|
8b9adb4d74 | ||
|
d91fae4e54 | ||
|
0d8920347a | ||
|
4a2ad9fc2a | ||
|
9b025cf8c1 | ||
|
25853b915e | ||
|
d4dc58eafe | ||
|
eb316fcb9c | ||
|
34bdf61cf0 | ||
|
951dc757f8 | ||
|
f5bfb208e7 | ||
|
d5683d2016 | ||
|
5d78488fef | ||
|
de1f7ee39b | ||
|
c1f07c3329 | ||
|
f34a0346bc | ||
|
c7949e2fc3 | ||
|
74044f4821 | ||
|
8fdda2f5b7 | ||
|
15c89a8fff | ||
|
160183b6e8 | ||
|
539164945a | ||
|
70e87677b3 | ||
|
2f9e05830b | ||
|
0658e42dad | ||
|
7fd2051d2c | ||
|
c39952a727 | ||
|
5323fb5e08 | ||
|
1c2580b4a6 | ||
|
a1502738f1 | ||
|
a3d9cce7c5 | ||
|
60bb0de9d6 | ||
|
9cfb50f90f | ||
|
5fd5c9e85c | ||
|
2bb6546ed6 | ||
|
09bd3a9642 | ||
|
b17c6ec054 | ||
|
a56b2f5993 | ||
|
e7484d07aa | ||
|
b57e8f6f0d | ||
|
b0e4c913c0 | ||
|
77c8d588fa | ||
|
02b54ff7e8 | ||
|
da2f684f18 | ||
|
c39da9643e | ||
|
fa243cdf89 | ||
|
2ecdf111b0 | ||
|
4781d2e15a | ||
|
e9ebb2498b | ||
|
a1aaead6a9 | ||
|
e989e2f12c | ||
|
bccb231899 | ||
|
dddefa9a72 | ||
|
0745c019a3 | ||
|
85711ca66d | ||
|
278dfcdcc5 | ||
|
4c04b61f8a | ||
|
37dd488579 | ||
|
81c449fc99 | ||
|
1ed0fedfd5 | ||
|
28dcbc6426 | ||
|
43175f17e4 | ||
|
e439b53652 | ||
|
011f6102a5 | ||
|
2245863f04 | ||
|
d16cfc736d | ||
|
c9d41beb28 | ||
|
d2cf1cbd0e | ||
|
a33dd0108d | ||
|
549367fcd6 | ||
|
6c702c7b6a | ||
|
aabf08fd75 | ||
|
34d68ccb73 | ||
|
02eeefd73b | ||
|
555e5b24f3 | ||
|
bdcf2a9c61 | ||
|
30b3b5e2a7 | ||
|
2badd9778b | ||
|
6797172ad4 | ||
|
95eeca09b7 | ||
|
3d9deb7770 | ||
|
1cd0350f83 | ||
|
012615ae46 | ||
|
17448b370c | ||
|
cf904b6b2f | ||
|
3be3b78c90 | ||
|
8fc453658b | ||
|
3344d96329 | ||
|
2f0ce06cc1 | ||
|
1a262048a6 | ||
|
87ce264926 | ||
|
9d6c50ea2f | ||
|
35958a0965 | ||
|
cdab1ebc04 | ||
|
80a58eea7b | ||
|
851003c07c | ||
|
ac138f6a4c | ||
|
b749718feb | ||
|
234b8dd592 | ||
|
c9b2a7a980 | ||
|
f7d5d6c0cd | ||
|
22497e5133 | ||
|
719023e949 | ||
|
2e1b05940f | ||
|
1d566a85b2 | ||
|
831e52ea07 | ||
|
1725c708fe | ||
|
0c70cee503 | ||
|
6a5702f27c | ||
|
84d1151a1f | ||
|
3ce9071eb9 | ||
|
ec37cf6700 | ||
|
cda12ec995 | ||
|
bc0ea154df | ||
|
a2269a477e | ||
|
262169c8a4 | ||
|
7ab92f0807 | ||
|
c044534d6a | ||
|
fcdd19e105 | ||
|
5fc6a5627e | ||
|
2459501f4e | ||
|
0768ce3600 | ||
|
23469a99e8 | ||
|
b3dfc0b6db | ||
|
2ffb6496f4 | ||
|
7a1db0852b | ||
|
70c82c0f61 | ||
|
f18f81ec58 | ||
|
6c33b781c3 | ||
|
023c45f302 | ||
|
ba03ed4eea | ||
|
ee25249729 | ||
|
167d06d727 | ||
|
468a7a4a77 | ||
|
4b403266ea | ||
|
89e5601ec0 | ||
|
9652c0fabb | ||
|
a33b4cc73b | ||
|
11bcfa05e3 | ||
|
75d9fa3fe9 | ||
|
887b7b5090 | ||
|
bb628bca88 | ||
|
8a46a4b567 | ||
|
8b144bedd1 | ||
|
94c8cceb42 | ||
|
0d9e849162 | ||
|
4568300d9b | ||
|
b3056cd0ab | ||
|
a16fb04280 | ||
|
240c111ebb | ||
|
6652df03df | ||
|
2ec3397b7d | ||
|
97052f3912 | ||
|
0d40df937b | ||
|
7d07c550ba | ||
|
37690cfde2 | ||
|
fc18299869 | ||
|
770c7e5878 | ||
|
4d3f137e1e | ||
|
23e7b53c13 | ||
|
e1e2674cd2 | ||
|
8310877f25 | ||
|
af868a7439 | ||
|
e83de5cde2 | ||
|
a278dd5a0a | ||
|
04e581db61 | ||
|
b50fb71dd1 | ||
|
6da6c755a5 | ||
|
0180b21dd1 | ||
|
f6f9816ef4 | ||
|
cc3adcc2c1 | ||
|
e1fa4ab8ec | ||
|
715d5daafe | ||
|
fd4d7c4b68 | ||
|
a98052fe74 | ||
|
ea09aa584d | ||
|
1df30ee83e | ||
|
314af4cdae | ||
|
1e6674f804 | ||
|
df5f722885 | ||
|
b4f3e30cd6 | ||
|
34a1512ff0 | ||
|
ec922ee5e4 | ||
|
0f80c57dca | ||
|
2f5a774325 | ||
|
c58fd744ee | ||
|
2d8a8bd3e5 | ||
|
13633414a3 | ||
|
d99f1e5f1f | ||
|
15ae24b541 | ||
|
1b36a381e8 | ||
|
e094043b80 | ||
|
a0d83537e5 | ||
|
7723d6281b | ||
|
52eae66c72 | ||
|
daf1495c4e | ||
|
e84d03f31d | ||
|
a254b2098f | ||
|
bb8adcce92 | ||
|
4f8e61dce9 | ||
|
d3b2bcdd79 | ||
|
bbe0eb96f4 | ||
|
95808f51f1 | ||
|
cc6cf8d7ce | ||
|
5b65f10828 | ||
|
a2bec1edb4 | ||
|
b11a84a347 | ||
|
0aaf236ad1 | ||
|
20d4e7139f | ||
|
bca0c6c172 | ||
|
4e498790d4 | ||
|
93a6047912 | ||
|
144d6c2379 | ||
|
645b8e4b0b | ||
|
d6bc60f968 | ||
|
2ce5a656e7 | ||
|
fa88ee148a | ||
|
5e33fe0aa7 | ||
|
9552ca7bb3 | ||
|
3632a98062 | ||
|
23c76caafa | ||
|
8a7ca6f0ba | ||
|
8ea25eeb3c | ||
|
e004e05037 | ||
|
d0e057cabd | ||
|
68ec7ae41e | ||
|
81f48c0001 | ||
|
50886fcb31 | ||
|
5793bb8fac | ||
|
f223ed21a5 | ||
|
d74e044be4 | ||
|
cf75a19e8d | ||
|
528dbf909a | ||
|
a45791d2c0 | ||
|
d19232a848 | ||
|
564a1b0fbb | ||
|
574cfae470 | ||
|
9bcd1245d9 | ||
|
89a4cb08e8 | ||
|
91dbf3c09d | ||
|
3eea77709b | ||
|
08d8709ef6 | ||
|
5ebd38f1d6 | ||
|
03ec27be66 | ||
|
455f994673 | ||
|
4f843ada3c | ||
|
35740456a4 | ||
|
8486e79cad | ||
|
6b7a286158 | ||
|
81db3461fe | ||
|
e2dfea8c12 | ||
|
6165eda356 | ||
|
0d8a2c8c44 | ||
|
8d0fde6f60 | ||
|
237bac4500 | ||
|
5f9dc13207 | ||
|
9b212dc518 | ||
|
94bfe116aa | ||
|
50e0221dd1 | ||
|
abec06f50c | ||
|
ee4f9da385 | ||
|
848779e465 | ||
|
60ce0aee1a | ||
|
0f65779d29 | ||
|
394040e22c | ||
|
0a90f235de | ||
|
2447875b26 | ||
|
df72c9c6cc | ||
|
6aa9005785 | ||
|
c3769b8874 | ||
|
c0c06fdc53 | ||
|
2df4532d50 | ||
|
6a05967bef | ||
|
41700a6996 | ||
|
c9c8f920d2 | ||
|
8c715e025a | ||
|
3eece20967 | ||
|
21e21f1e02 | ||
|
84abf5d84f | ||
|
83fea78d38 | ||
|
1987dad527 | ||
|
cbaa7fdee6 | ||
|
73dc52980e | ||
|
8b0f9759ec | ||
|
d6baadbaf2 | ||
|
569d63d0c9 | ||
|
a529427bc9 | ||
|
2f30b03060 | ||
|
d34aec29ed | ||
|
9271dd0cc7 | ||
|
3d2e9efb1e | ||
|
b840ab8f8b | ||
|
deef600caf | ||
|
d146ae7275 | ||
|
801a2339ee | ||
|
2f757babb9 | ||
|
2398311455 | ||
|
5d6dac495e | ||
|
3915943f57 | ||
|
e0d5eedff7 | ||
|
4e1ef482ad | ||
|
6b0b8078e9 | ||
|
377f20ed49 | ||
|
2cc111ad5d | ||
|
ab2c3468d5 | ||
|
54196a8d4f | ||
|
9bd3f10f41 | ||
|
93ee2a9b64 | ||
|
a4c433c749 | ||
|
858c974440 | ||
|
d2394d3ac3 | ||
|
d4684d6302 | ||
|
5fa088114c | ||
|
8930f510fc | ||
|
7134b44f5b | ||
|
86bc784e41 | ||
|
64c098627e | ||
|
1b0a369889 | ||
|
73fa0cdc31 | ||
|
d8893b959d | ||
|
f409168126 | ||
|
843d95966e | ||
|
a99721004b | ||
|
a6b6c312b8 | ||
|
e28c477075 | ||
|
8e91f7b701 | ||
|
da3dd65726 | ||
|
c7d99c37fb | ||
|
a61b1567d0 | ||
|
7039eaf995 | ||
|
d99aa3192c | ||
|
8a664c524c | ||
|
d6110d7332 | ||
|
1db72ad6e2 | ||
|
6d5bfcfe44 | ||
|
fb88e5d652 | ||
|
f5bce0cd9e | ||
|
62158601fb | ||
|
a12583f762 | ||
|
efe0f8449c | ||
|
c313857485 | ||
|
1ca1a4b1cc | ||
|
90746bd8c9 | ||
|
a4c5375a63 | ||
|
986762a0bc | ||
|
7707f7557a | ||
|
9e02271a68 | ||
|
eb1f76d8bd | ||
|
e8c64b802d | ||
|
23d3b79d9a | ||
|
387a5fb03b | ||
|
34fb39da5b | ||
|
b0a9c1e0fa | ||
|
b52c7bba4b | ||
|
56ba9a30ac | ||
|
4ef89b5ccb | ||
|
109012cae8 | ||
|
7e78480840 | ||
|
3b54b49b0b | ||
|
434ed1b91c | ||
|
ac057ebe93 | ||
|
a78068cbe9 | ||
|
3708b3be63 | ||
|
e0a79699ea | ||
|
b76860fd2b | ||
|
45c0a97142 | ||
|
5700f4ac81 | ||
|
9efab789aa | ||
|
35a43a1e82 | ||
|
974f89d5bf | ||
|
df76f010da | ||
|
10a470a5fa | ||
|
1490ba179c | ||
|
c75b49667e | ||
|
1485dd07ae | ||
|
54eb575000 | ||
|
ac7f1f6719 | ||
|
da837fa9d8 | ||
|
a1df49ef3e | ||
|
f5ea47a7b2 | ||
|
ec52a4a235 | ||
|
bb1bbebfd6 | ||
|
9fdd0e3a0a | ||
|
64cec5ff7d | ||
|
b931a225ba | ||
|
e5c2b25351 | ||
|
77f036ee21 | ||
|
4eb69dc0fe | ||
|
0d110ed47c | ||
|
b96eb45877 | ||
|
a55001ad1d | ||
|
23a6b19985 | ||
|
0abbd7ac97 | ||
|
83911a7290 | ||
|
73b2dd7a59 | ||
|
35d9a9815e | ||
|
e3d6525d83 | ||
|
c718b5618e | ||
|
41f66f33d5 | ||
|
5255b72f67 | ||
|
a9d75e2223 | ||
|
6b4cb35149 | ||
|
5df3f5d136 | ||
|
1879dbca8a | ||
|
2de67538a2 | ||
|
dd1a3fc60a | ||
|
a0619fc101 | ||
|
9d23101c9a | ||
|
044d070667 | ||
|
436ae1333c | ||
|
8a4537fc73 | ||
|
d1293d9fb0 | ||
|
3e7dc57289 | ||
|
219eddf33e | ||
|
b6f9cf4ba7 | ||
|
07ee7a341b | ||
|
67c225f971 | ||
|
a028e445fe | ||
|
b725829bfd | ||
|
e1f2ccd138 | ||
|
7e87e9e8ad | ||
|
d530cc86f3 | ||
|
5e34235a36 | ||
|
63f1ca9243 | ||
|
498d5c0660 | ||
|
384aeda645 | ||
|
7ad36ee36c | ||
|
e99884d0c0 | ||
|
9fcaea2235 | ||
|
dd1f8295c4 | ||
|
bc9c1d4824 | ||
|
21771aa1a6 | ||
|
d013a13be6 | ||
|
797a3e1cd9 | ||
|
3687a7c717 | ||
|
030d01102c | ||
|
c7e6992239 | ||
|
cb881108c3 | ||
|
1a6f23ad2b | ||
|
9b9cd451d1 | ||
|
fe52e969b6 | ||
|
213f61e826 | ||
|
37ece36685 | ||
|
958a8f66a0 | ||
|
badb3729c9 | ||
|
e228de18e2 | ||
|
05cd715c39 | ||
|
a555686bcd | ||
|
16860c3f79 | ||
|
1d87f24715 | ||
|
6fa9393a25 | ||
|
354ec19e3b | ||
|
99c247fe9f | ||
|
2d477946ea | ||
|
c009a289d5 | ||
|
4ebd597354 | ||
|
34d28189ea | ||
|
124ab28c9e | ||
|
c36f4266d2 | ||
|
91ab91cfb9 | ||
|
e756b09e5b | ||
|
701e116dde | ||
|
aa6ee10759 | ||
|
97e0df1363 | ||
|
1dddbf35d6 | ||
|
8be41ad946 | ||
|
7e9936a3cd | ||
|
83f56c3ad9 | ||
|
8c98ddb843 | ||
|
0c051fb78e | ||
|
c13f01c4f9 | ||
|
2ee3a7282b | ||
|
fa42b6acec | ||
|
b5527415d6 | ||
|
04f106aad8 | ||
|
cbb9314531 | ||
|
44052e7d8d | ||
|
579cff478d | ||
|
f84fcfee04 | ||
|
7846544c1b | ||
|
930c8b63db | ||
|
491eeb9878 | ||
|
daca82bdfc | ||
|
131aa14afa | ||
|
57d718fde1 | ||
|
cb79859c0e | ||
|
790bd87bc3 | ||
|
ba07c32300 | ||
|
8d16cdf12f | ||
|
a25d935a1d | ||
|
f8aec7571f | ||
|
1f7fb1591d | ||
|
5f103ee33d | ||
|
240ea8a136 | ||
|
e4c61a5fab | ||
|
b27138e93f | ||
|
1959f0ae12 | ||
|
57770ce84c | ||
|
2dca24cc28 | ||
|
7c45178076 | ||
|
e12bde8e5a | ||
|
5031221369 | ||
|
43d391501e | ||
|
7df4ff2606 | ||
|
b51d7e9f6a | ||
|
43871e393c | ||
|
ef3569741c | ||
|
84fcefba1b | ||
|
7525cb5ee4 | ||
|
e0d554e0ca | ||
|
599f81daa9 | ||
|
8bf5be0cc4 | ||
|
f0a587d310 | ||
|
29bed6ff69 | ||
|
8a37f9599f | ||
|
fddbc9e5cb | ||
|
b7862ac23a | ||
|
90a5c9e489 | ||
|
2924d0a20f | ||
|
3041c95408 | ||
|
8a67a0a80a | ||
|
2ee8f07432 | ||
|
18b29832ac | ||
|
4af49038cd | ||
|
3fe3353696 | ||
|
5d5e45ec0d | ||
|
79afe2d7fb | ||
|
429e1c7a27 | ||
|
e73d867dab | ||
|
1c37ea5860 | ||
|
96f4033a40 | ||
|
5684906d3e | ||
|
b0264a6af2 | ||
|
e2069d2e29 | ||
|
45414e6cfe | ||
|
dd529575f6 | ||
|
bc9464fcbd | ||
|
95745ef4c8 | ||
|
e5defeaada | ||
|
eebb8b87a4 | ||
|
e15f60b819 | ||
|
4e5fd92e67 | ||
|
d98b7aab61 | ||
|
8edee76f8b | ||
|
ca963137e9 | ||
|
6eefecf31e | ||
|
9047b4b487 | ||
|
0ab0cb1173 | ||
|
608c0dc613 | ||
|
aedc473f64 | ||
|
be87151f1c | ||
|
5e54c0fa91 | ||
|
3bb66d03ee | ||
|
3478c16e10 | ||
|
bd00256e0c | ||
|
64342d200c | ||
|
b196635816 | ||
|
5342a482b5 | ||
|
391eb4b66c | ||
|
ea020948fc | ||
|
6062aa5dcd | ||
|
8fb0a2d417 | ||
|
aea9b1d6cc | ||
|
d9087dd2b6 | ||
|
868f83e6ca | ||
|
a1186727af | ||
|
9268a1f59c | ||
|
ca1efc3d77 | ||
|
9692616958 | ||
|
bb39f104f4 | ||
|
be21671c1c | ||
|
ee034185a5 | ||
|
a84a10ff03 | ||
|
731f28a8c0 | ||
|
98be191b25 | ||
|
8a3b5c6332 | ||
|
5bbcf0482b | ||
|
02c51c83cd | ||
|
94a39619b5 | ||
|
f16b53d92a | ||
|
f0f2d00924 | ||
|
81b57a57c7 | ||
|
48e5d82186 | ||
|
7638b2c9e8 | ||
|
dbd05e6c92 | ||
|
a587815ddc | ||
|
c3c83086bc | ||
|
4d08e7b0ae | ||
|
04291fe71e | ||
|
973bb63dca | ||
|
4868f758bf | ||
|
ae3f85bd5b | ||
|
3fb8466c40 | ||
|
b396b56e34 | ||
|
87f3d53607 | ||
|
8e9faa416d | ||
|
41effadb94 | ||
|
bbb50caa89 | ||
|
af235e564e | ||
|
1ff0abc661 | ||
|
645011427a | ||
|
69e82e15a3 | ||
|
7c541117b3 | ||
|
4cf62e816e | ||
|
2b671b8476 | ||
|
32f906fe9f | ||
|
4158693470 | ||
|
92e5982127 | ||
|
ee7779df17 | ||
|
ddbdfafc05 | ||
|
f169c8dbb0 | ||
|
91753c53cd | ||
|
3655601693 |
3
.cargo/config
Normal file
3
.cargo/config
Normal file
@ -0,0 +1,3 @@
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
# Link the C runtime statically ; https://github.com/openethereum/parity-ethereum/issues/6643
|
||||
rustflags = ["-Ctarget-feature=+crt-static"]
|
22
.dockerignore
Normal file
22
.dockerignore
Normal file
@ -0,0 +1,22 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
target
|
||||
|
||||
*.swp
|
||||
*.swo
|
||||
*.swn
|
||||
*.DS_Store
|
||||
|
||||
# Visual Studio Code stuff
|
||||
.vscode
|
||||
|
||||
# GitEye stuff
|
||||
.project
|
||||
|
||||
# idea ide
|
||||
.idea
|
||||
|
||||
# git stuff
|
||||
.git
|
||||
|
||||
ethcore/res/ethereum/tests
|
@ -9,7 +9,7 @@ trim_trailing_whitespace=true
|
||||
max_line_length=120
|
||||
insert_final_newline=true
|
||||
|
||||
[.travis.yml]
|
||||
[*.{yml,sh}]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
tab_width=8
|
||||
|
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@ -0,0 +1,2 @@
|
||||
# Reformat the source code
|
||||
610d9baba4af83b5767c659ca2ccfed337af1056
|
84
.github/CODE_OF_CONDUCT.md
vendored
Normal file
84
.github/CODE_OF_CONDUCT.md
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
# Code of Conduct
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
A primary goal of OpenEthereum is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
|
||||
|
||||
This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.
|
||||
|
||||
We invite all those who participate in OpenEthereum to help us create safe and positive experiences for everyone.
|
||||
|
||||
## 2. Open Source Citizenship
|
||||
|
||||
A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community.
|
||||
|
||||
Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society.
|
||||
|
||||
If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know.
|
||||
|
||||
## 3. Expected Behavior
|
||||
|
||||
The following behaviors are expected and requested of all community members:
|
||||
|
||||
* Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
|
||||
* Exercise consideration and respect in your speech and actions.
|
||||
* Attempt collaboration before conflict.
|
||||
* Refrain from demeaning, discriminatory, or harassing behavior and speech.
|
||||
* Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
|
||||
* Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
|
||||
|
||||
## 4. Unacceptable Behavior
|
||||
|
||||
The following behaviors are considered harassment and are unacceptable within our community:
|
||||
|
||||
* Violence, threats of violence or violent language directed against another person.
|
||||
* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
|
||||
* Posting or displaying sexually explicit or violent material.
|
||||
* Posting or threatening to post other people’s personally identifying information ("doxing").
|
||||
* Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
|
||||
* Inappropriate photography or recording.
|
||||
* Inappropriate physical contact. You should have someone’s consent before touching them.
|
||||
* Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
|
||||
* Deliberate intimidation, stalking or following (online or in person).
|
||||
* Advocating for, or encouraging, any of the above behavior.
|
||||
* Sustained disruption of community events, including talks and presentations.
|
||||
|
||||
## 5. Consequences of Unacceptable Behavior
|
||||
|
||||
Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated.
|
||||
|
||||
Anyone asked to stop unacceptable behavior is expected to comply immediately.
|
||||
|
||||
If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event).
|
||||
|
||||
## 6. Reporting Guidelines
|
||||
|
||||
If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. community@parity.io.
|
||||
|
||||
Link to reporting guidelines: [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
|
||||
Link to security policy: [SECURITY.md](../SECURITY.md)
|
||||
|
||||
Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress.
|
||||
|
||||
## 7. Addressing Grievances
|
||||
|
||||
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify OpenEthereum Technologies with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
|
||||
|
||||
## 8. Scope
|
||||
|
||||
We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business.
|
||||
|
||||
This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members.
|
||||
|
||||
## 9. Contact info
|
||||
|
||||
You can contact OpenEthereum via Email: community@parity.io
|
||||
|
||||
## 10. License and attribution
|
||||
|
||||
This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
|
||||
|
||||
Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).
|
||||
|
||||
Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/)
|
68
.github/CONTRIBUTING.md
vendored
Normal file
68
.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
# Contributing Guidelines
|
||||
|
||||
## Do you have a question?
|
||||
|
||||
Check out our [Beginner Introduction](https://openethereum.github.io/Beginner-Introduction), [Configuration](https://openethereum.github.io//Configuring-OpenEthereum), and [FAQ](https://openethereum.github.io/FAQ) articles on our [wiki](https://openethereum.github.io/)!
|
||||
|
||||
See also frequently asked questions [tagged with `parity`](https://ethereum.stackexchange.com/questions/tagged/parity?sort=votes&pageSize=50) on Stack Exchange.
|
||||
|
||||
## Report bugs!
|
||||
|
||||
Do **not** open an issue on Github if you think your discovered bug could be a **security-relevant vulnerability**. Please, read our [security policy](../SECURITY.md) instead.
|
||||
|
||||
Otherwise, just create a [new issue](https://github.com/openethereum/openethereum/issues/new) in our repository and state:
|
||||
|
||||
- What's your OpenEthereum version?
|
||||
- What's your operating system and version?
|
||||
- How did you install OpenEthereum?
|
||||
- Is your node fully synchronized?
|
||||
- Did you try turning it off and on again?
|
||||
|
||||
Also, try to include **steps to reproduce** the issue and expand on the **actual versus expected behavior**.
|
||||
|
||||
## Contribute!
|
||||
|
||||
If you would like to contribute to OpenEthereum, please **fork it**, fix bugs or implement features, and [propose a pull request](https://github.com/openethereum/openethereum/compare).
|
||||
|
||||
### Labels & Milestones
|
||||
|
||||
We use [labels](https://github.com/openethereum/openethereum/labels) to manage PRs and issues and communicate the state of a PR. Please familiarize yourself with them. Furthermore we are organizing issues in [milestones](https://github.com/openethereum/openethereum/milestones). Best way to get started is to a pick a ticket from the current milestone tagged [`easy`](https://github.com/openethereum/openethereum/labels/Q2-easy%20%F0%9F%92%83) and get going, or [`mentor`](https://github.com/openethereum/openethereum/labels/Q1-mentor%20%F0%9F%95%BA) and get in contact with the mentor offering their support on that larger task.
|
||||
|
||||
### Rules
|
||||
|
||||
There are a few basic ground-rules for contributors (including the maintainer(s) of the project):
|
||||
|
||||
* **No pushing directly to the master branch**.
|
||||
* **All modifications** must be made in a **pull-request** to solicit feedback from other contributors.
|
||||
* Pull-requests cannot be merged before CI runs green and two reviewers have given their approval.
|
||||
* All code changed should be formated by running `cargo fmt -- --config=merge_imports=true`
|
||||
|
||||
### Recommendations
|
||||
|
||||
* **Non-master branch names** *should* be prefixed with a short name moniker, followed by the associated Github Issue ID (if any), and a brief description of the task using the format `<GITHUB_USERNAME>-<ISSUE_ID>-<BRIEF_DESCRIPTION>` (e.g. `gavin-123-readme`). The name moniker helps people to inquiry about their unfinished work, and the GitHub Issue ID helps your future self and other developers (particularly those who are onboarding) find out about and understand the original scope of the task, and where it fits into Parity Ethereum [Projects](https://github.com/openethereum/openethereum/projects).
|
||||
* **Remove stale branches periodically**
|
||||
|
||||
### Preparing Pull Requests
|
||||
|
||||
* If your PR does not alter any logic (e.g. comments, dependencies, docs), then it may be tagged [`insubstantial`](https://github.com/openethereum/openethereum/pulls?q=is%3Aopen+is%3Apr+label%3A%22A2-insubstantial+%F0%9F%91%B6%22).
|
||||
|
||||
* Once a PR is ready for review please add the [`pleasereview`](https://github.com/openethereum/openethereum/pulls?utf8=%E2%9C%93&q=is%3Aopen+is%3Apr+label%3A%22A0-pleasereview+%F0%9F%A4%93%22+) label.
|
||||
|
||||
### Reviewing Pull Requests*:
|
||||
|
||||
* At least two reviewers are required to review PRs (even for PRs tagged [`insubstantial`](https://github.com/openethereum/openethereum/pulls?q=is%3Aopen+is%3Apr+label%3A%22A2-insubstantial+%F0%9F%91%B6%22)).
|
||||
|
||||
When doing a review, make sure to look for any:
|
||||
|
||||
* Buggy behavior.
|
||||
* Undue maintenance burden.
|
||||
* Breaking with house coding style.
|
||||
* Pessimization (i.e. reduction of speed as measured in the projects benchmarks).
|
||||
* Breaking changes should be carefuly reviewed and tagged as such so they end up in the [changelog](../CHANGELOG.md).
|
||||
* Uselessness (i.e. it does not strictly add a feature or fix a known issue).
|
||||
|
||||
## License.
|
||||
|
||||
By contributing to Parity Ethereum, you agree that your contributions will be licensed under the [GPLv3 License](../LICENSE).
|
||||
|
||||
Each contributor has to sign our Contributor License Agreement. The purpose of the CLA is to ensure that the guardian of a project's outputs has the necessary ownership or grants of rights over all contributions to allow them to distribute under the chosen license. You can read and sign our full Contributor License Agreement at [cla.parity.io](https://cla.parity.io) before submitting a pull request.
|
13
.github/ISSUE_TEMPLATE.md
vendored
Normal file
13
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
For questions please use https://discord.io/openethereum, issues are for bugs and feature requests.
|
||||
|
||||
_Before filing a new issue, please **provide the following information**._
|
||||
|
||||
- **OpenEthereum version (>=3.1.0)**: 0.0.0
|
||||
- **Operating system**: Windows / MacOS / Linux
|
||||
- **Installation**: homebrew / one-line installer / built from source
|
||||
- **Fully synchronized**: no / yes
|
||||
- **Network**: ethereum / ropsten / kovan / ...
|
||||
- **Restarted**: no / yes
|
||||
|
||||
_Your issue description goes here below. Try to include **actual** vs. **expected behavior** and **steps to reproduce** the issue._
|
||||
|
33
.github/workflows/build-test-windows.yml
vendored
Normal file
33
.github/workflows/build-test-windows.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
name: Build and Test Suite on Windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
jobs:
|
||||
build-tests:
|
||||
name: Test and Build
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- windows2019 # custom runner
|
||||
toolchain:
|
||||
- 1.52.1
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@main
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: Build tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --locked --all --release --features "json-tests" --verbose --no-run
|
40
.github/workflows/build-test.yml
vendored
Normal file
40
.github/workflows/build-test.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: Build and Test Suite
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
jobs:
|
||||
build-tests:
|
||||
name: Test and Build
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- ubuntu-16.04
|
||||
- macos-latest
|
||||
toolchain:
|
||||
- 1.52.1
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@main
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: Build tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --locked --all --release --features "json-tests" --verbose --no-run
|
||||
- name: Run tests for ${{ matrix.platform }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --locked --all --release --features "json-tests" --verbose
|
285
.github/workflows/build.yml
vendored
Normal file
285
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,285 @@
|
||||
name: Build Release Suite
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
# Global vars
|
||||
env:
|
||||
AWS_REGION: "us-east-1"
|
||||
AWS_S3_ARTIFACTS_BUCKET: "openethereum-releases"
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Release
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- ubuntu-16.04
|
||||
- macos-latest
|
||||
toolchain:
|
||||
- 1.52.1
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@main
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
# ==============================
|
||||
# Windows Build
|
||||
# ==============================
|
||||
|
||||
# - name: Install LLVM for Windows
|
||||
# if: matrix.platform == 'windows2019'
|
||||
# run: choco install llvm
|
||||
|
||||
# - name: Build OpenEthereum for Windows
|
||||
# if: matrix.platform == 'windows2019'
|
||||
# run: sh scripts/actions/build-windows.sh ${{matrix.platform}}
|
||||
|
||||
# - name: Upload Windows build
|
||||
# uses: actions/upload-artifact@v2
|
||||
# if: matrix.platform == 'windows2019'
|
||||
# with:
|
||||
# name: windows-artifacts
|
||||
# path: artifacts
|
||||
|
||||
# ==============================
|
||||
# Linux/Macos Build
|
||||
# ==============================
|
||||
|
||||
- name: Build OpenEthereum for ${{matrix.platform}}
|
||||
if: matrix.platform != 'windows2019'
|
||||
run: sh scripts/actions/build-linux.sh ${{matrix.platform}}
|
||||
|
||||
- name: Upload Linux build
|
||||
uses: actions/upload-artifact@v2
|
||||
if: matrix.platform == 'ubuntu-16.04'
|
||||
with:
|
||||
name: linux-artifacts
|
||||
path: artifacts
|
||||
|
||||
- name: Upload MacOS build
|
||||
uses: actions/upload-artifact@v2
|
||||
if: matrix.platform == 'macos-latest'
|
||||
with:
|
||||
name: macos-artifacts
|
||||
path: artifacts
|
||||
|
||||
zip-artifacts-creator:
|
||||
name: Create zip artifacts
|
||||
needs: build
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
|
||||
# ==============================
|
||||
# Create ZIP files
|
||||
# ==============================
|
||||
|
||||
# - name: Download Windows artifacts
|
||||
# uses: actions/download-artifact@v2
|
||||
# with:
|
||||
# name: windows-artifacts
|
||||
# path: windows-artifacts
|
||||
|
||||
- name: Download Linux artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-artifacts
|
||||
path: linux-artifacts
|
||||
|
||||
- name: Download MacOS artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: macos-artifacts
|
||||
path: macos-artifacts
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls
|
||||
|
||||
- name: Create zip Linux
|
||||
id: create_zip_linux
|
||||
run: |
|
||||
cd linux-artifacts/
|
||||
zip -rT openethereum-linux-${{ env.RELEASE_VERSION }}.zip *
|
||||
ls openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||
cd ..
|
||||
mv linux-artifacts/openethereum-linux-${{ env.RELEASE_VERSION }}.zip .
|
||||
|
||||
echo "Setting outputs..."
|
||||
echo ::set-output name=LINUX_ARTIFACT::openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||
echo ::set-output name=LINUX_SHASUM::$(shasum -a 256 openethereum-linux-${{ env.RELEASE_VERSION }}.zip | awk '{print $1}')
|
||||
|
||||
- name: Create zip MacOS
|
||||
id: create_zip_macos
|
||||
run: |
|
||||
cd macos-artifacts/
|
||||
zip -rT openethereum-macos-${{ env.RELEASE_VERSION }}.zip *
|
||||
ls openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||
cd ..
|
||||
mv macos-artifacts/openethereum-macos-${{ env.RELEASE_VERSION }}.zip .
|
||||
|
||||
echo "Setting outputs..."
|
||||
echo ::set-output name=MACOS_ARTIFACT::openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||
echo ::set-output name=MACOS_SHASUM::$(shasum -a 256 openethereum-macos-${{ env.RELEASE_VERSION }}.zip | awk '{print $1}')
|
||||
|
||||
# - name: Create zip Windows
|
||||
# id: create_zip_windows
|
||||
# run: |
|
||||
# cd windows-artifacts/
|
||||
# zip -rT openethereum-windows-${{ env.RELEASE_VERSION }}.zip *
|
||||
# ls openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||
# cd ..
|
||||
# mv windows-artifacts/openethereum-windows-${{ env.RELEASE_VERSION }}.zip .
|
||||
|
||||
# echo "Setting outputs..."
|
||||
# echo ::set-output name=WINDOWS_ARTIFACT::openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||
# echo ::set-output name=WINDOWS_SHASUM::$(shasum -a 256 openethereum-windows-${{ env.RELEASE_VERSION }}.zip | awk '{print $1}')
|
||||
|
||||
# =======================================================================
|
||||
# Upload artifacts
|
||||
# This is required to share artifacts between different jobs
|
||||
# =======================================================================
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||
path: openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||
path: openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||
|
||||
# - name: Upload artifacts
|
||||
# uses: actions/upload-artifact@v2
|
||||
# with:
|
||||
# name: openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||
# path: openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||
|
||||
# =======================================================================
|
||||
# Upload artifacts to S3
|
||||
# This is required by some software distribution systems which require
|
||||
# artifacts to be downloadable, like Brew on MacOS.
|
||||
# =======================================================================
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ env.AWS_REGION }}
|
||||
|
||||
- name: Copy files to S3 with the AWS CLI
|
||||
run: |
|
||||
# Deploy zip artifacts to S3 bucket to a directory whose name is the tagged release version.
|
||||
# Deploy macos binary artifact (if required, add more `aws s3 cp` commands to deploy specific OS versions)
|
||||
aws s3 cp macos-artifacts/openethereum s3://${{ env.AWS_S3_ARTIFACTS_BUCKET }}/${{ env.RELEASE_VERSION }}/macos/ --region ${{ env.AWS_REGION }}
|
||||
|
||||
outputs:
|
||||
linux-artifact: ${{ steps.create_zip_linux.outputs.LINUX_ARTIFACT }}
|
||||
linux-shasum: ${{ steps.create_zip_linux.outputs.LINUX_SHASUM }}
|
||||
macos-artifact: ${{ steps.create_zip_macos.outputs.MACOS_ARTIFACT }}
|
||||
macos-shasum: ${{ steps.create_zip_macos.outputs.MACOS_SHASUM }}
|
||||
# windows-artifact: ${{ steps.create_zip_windows.outputs.WINDOWS_ARTIFACT }}
|
||||
# windows-shasum: ${{ steps.create_zip_windows.outputs.WINDOWS_SHASUM }}
|
||||
|
||||
draft-release:
|
||||
name: Draft Release
|
||||
needs: zip-artifacts-creator
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
|
||||
# ==============================
|
||||
# Download artifacts
|
||||
# ==============================
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||
|
||||
# - name: Download artifacts
|
||||
# uses: actions/download-artifact@v2
|
||||
# with:
|
||||
# name: openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls
|
||||
|
||||
# ==============================
|
||||
# Create release draft
|
||||
# ==============================
|
||||
|
||||
- name: Create Release Draft
|
||||
id: create_release_draft
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: OpenEthereum ${{ github.ref }}
|
||||
body: |
|
||||
This release contains <ADD_TEXT>
|
||||
|
||||
| System | Architecture | Binary | Sha256 Checksum |
|
||||
|:---:|:---:|:---:|:---|
|
||||
| <img src="https://gist.github.com/5chdn/1fce888fde1d773761f809b607757f76/raw/44c4f0fc63f1ea8e61a9513af5131ef65eaa6c75/apple.png" alt="Apple Icon by Pixel Perfect from https://www.flaticon.com/authors/pixel-perfect" style="width: 32px;"/> | x64 | [${{ needs.zip-artifacts-creator.outputs.macos-artifact }}](https://github.com/openethereum/openethereum/releases/download/${{ env.RELEASE_VERSION }}/${{ needs.zip-artifacts-creator.outputs.macos-artifact }}) | `${{ needs.zip-artifacts-creator.outputs.macos-shasum }}` |
|
||||
| <img src="https://gist.github.com/5chdn/1fce888fde1d773761f809b607757f76/raw/44c4f0fc63f1ea8e61a9513af5131ef65eaa6c75/linux.png" alt="Linux Icon by Pixel Perfect from https://www.flaticon.com/authors/pixel-perfect" style="width: 32px;"/> | x64 | [${{ needs.zip-artifacts-creator.outputs.linux-artifact }}](https://github.com/openethereum/openethereum/releases/download/${{ env.RELEASE_VERSION }}/${{ needs.zip-artifacts-creator.outputs.linux-artifact }}) | `${{ needs.zip-artifacts-creator.outputs.linux-shasum }}` |
|
||||
| <img src="https://gist.github.com/5chdn/1fce888fde1d773761f809b607757f76/raw/44c4f0fc63f1ea8e61a9513af5131ef65eaa6c75/windows.png" alt="Windows Icon by Pixel Perfect from https://www.flaticon.com/authors/pixel-perfect" style="width: 32px;"/> | x64 | [${{ needs.zip-artifacts-creator.outputs.windows-artifact }}](https://github.com/openethereum/openethereum/releases/download/${{ env.RELEASE_VERSION }}/${{ needs.zip-artifacts-creator.outputs.windows-artifact }}) | `${{ needs.zip-artifacts-creator.outputs.windows-shasum }}` |
|
||||
| | | | |
|
||||
| **System** | **Option** | - | **Resource** |
|
||||
| <img src="https://gist.github.com/5chdn/1fce888fde1d773761f809b607757f76/raw/44c4f0fc63f1ea8e61a9513af5131ef65eaa6c75/settings.png" alt="Settings Icon by Pixel Perfect from https://www.flaticon.com/authors/pixel-perfect" style="width: 32px;"/> | Docker | - | [hub.docker.com/r/openethereum/openethereum](https://hub.docker.com/r/openethereum/openethereum) |
|
||||
|
||||
draft: true
|
||||
prerelease: true
|
||||
|
||||
- name: Upload Release Asset - Linux
|
||||
id: upload_release_asset_linux
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release_draft.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
asset_path: ./openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||
asset_name: openethereum-linux-${{ env.RELEASE_VERSION }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload Release Asset - MacOS
|
||||
id: upload_release_asset_macos
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release_draft.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
asset_path: ./openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||
asset_name: openethereum-macos-${{ env.RELEASE_VERSION }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
# - name: Upload Release Asset - Windows
|
||||
# id: upload_release_asset_windows
|
||||
# uses: actions/upload-release-asset@v1
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# with:
|
||||
# upload_url: ${{ steps.create_release_draft.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
# asset_path: ./openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||
# asset_name: openethereum-windows-${{ env.RELEASE_VERSION }}.zip
|
||||
# asset_content_type: application/zip
|
50
.github/workflows/check.yml
vendored
Normal file
50
.github/workflows/check.yml
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
name: Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@main
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install 1.52.1 toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.52.1
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: Run cargo check 1/3
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --locked --no-default-features --verbose
|
||||
- name: Run cargo check 2/3
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --locked --manifest-path crates/runtime/io/Cargo.toml --no-default-features --verbose
|
||||
- name: Run cargo check 3/3
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --locked --manifest-path crates/runtime/io/Cargo.toml --features "mio" --verbose
|
||||
- name: Run cargo check evmbin
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --locked -p evmbin --verbose
|
||||
- name: Run cargo check benches
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --locked --all --benches --verbose
|
||||
- name: Run validate chainspecs
|
||||
run: ./scripts/actions/validate-chainspecs.sh
|
29
.github/workflows/deploy-docker-nightly.yml
vendored
Normal file
29
.github/workflows/deploy-docker-nightly.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
name: Docker Image Nightly Release
|
||||
|
||||
# Run "nightly" build on each commit to "dev" branch.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
deploy-docker:
|
||||
name: Build Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@master
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.52.1
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: Deploy to docker hub
|
||||
uses: elgohr/Publish-Docker-Github-Action@master
|
||||
with:
|
||||
name: openethereum/openethereum
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
dockerfile: scripts/docker/alpine/Dockerfile
|
||||
tags: "nightly"
|
30
.github/workflows/deploy-docker-tag.yml
vendored
Normal file
30
.github/workflows/deploy-docker-tag.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: Docker Image Tag and Latest Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
deploy-docker:
|
||||
name: Build Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@master
|
||||
- name: Set env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.52.1
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: Deploy to docker hub
|
||||
uses: elgohr/Publish-Docker-Github-Action@master
|
||||
with:
|
||||
name: openethereum/openethereum
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
dockerfile: scripts/docker/alpine/Dockerfile
|
||||
tags: "latest,${{ env.RELEASE_VERSION }}"
|
30
.github/workflows/deploy-docker.yml
vendored
Normal file
30
.github/workflows/deploy-docker.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: Docker Image Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
deploy-docker:
|
||||
name: Build Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@master
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.52.1
|
||||
profile: minimal
|
||||
override: true
|
||||
- name: Deploy to docker hub
|
||||
uses: elgohr/Publish-Docker-Github-Action@master
|
||||
with:
|
||||
name: openethereum/openethereum
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
dockerfile: scripts/docker/alpine/Dockerfile
|
||||
tag_names: true
|
20
.github/workflows/fmt.yml
vendored
Normal file
20
.github/workflows/fmt.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
on: [push, pull_request]
|
||||
|
||||
name: rustfmt
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.52.1
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check --config merge_imports=true
|
13
.gitignore
vendored
13
.gitignore
vendored
@ -15,10 +15,18 @@
|
||||
|
||||
# vim stuff
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# mac stuff
|
||||
.DS_Store
|
||||
|
||||
# npm stuff
|
||||
npm-debug.log
|
||||
node_modules
|
||||
|
||||
# js build artifacts
|
||||
.git-release.log
|
||||
|
||||
# gdb files
|
||||
.gdb_history
|
||||
|
||||
@ -32,5 +40,8 @@
|
||||
out/
|
||||
|
||||
.vscode
|
||||
|
||||
rls/
|
||||
/parity.*
|
||||
|
||||
# cargo remote artifacts
|
||||
remote-target
|
||||
|
612
.gitlab-ci.yml
612
.gitlab-ci.yml
@ -1,612 +0,0 @@
|
||||
stages:
|
||||
- test
|
||||
- js-build
|
||||
- build
|
||||
- push-release
|
||||
variables:
|
||||
GIT_DEPTH: "3"
|
||||
SIMPLECOV: "true"
|
||||
RUST_BACKTRACE: "1"
|
||||
RUSTFLAGS: ""
|
||||
CARGOFLAGS: ""
|
||||
cache:
|
||||
key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
|
||||
untracked: true
|
||||
linux-stable:
|
||||
stage: build
|
||||
image: ethcore/rust:stable
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
|
||||
- cargo build -j $(nproc) --release -p evmbin
|
||||
- cargo build -j $(nproc) --release -p ethstore
|
||||
- cargo build -j $(nproc) --release -p ethkey
|
||||
- strip target/release/parity
|
||||
- strip target/release/evm
|
||||
- strip target/release/ethstore
|
||||
- strip target/release/ethkey
|
||||
- export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
- md5sum target/release/parity > parity.md5
|
||||
- sh scripts/deb-build.sh amd64
|
||||
- cp target/release/parity deb/usr/bin/parity
|
||||
- cp target/release/evm deb/usr/bin/evm
|
||||
- cp target/release/ethstore deb/usr/bin/ethstore
|
||||
- cp target/release/ethkey deb/usr/bin/ethkey
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_amd64.deb"
|
||||
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
|
||||
tags:
|
||||
- rust
|
||||
- rust-stable
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
- target/release/parity/evmbin
|
||||
- target/release/parity/ethstore
|
||||
- target/release/parity/ethkey
|
||||
name: "stable-x86_64-unknown-linux-gnu_parity"
|
||||
linux-stable-debian:
|
||||
stage: build
|
||||
image: ethcore/rust-debian:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
|
||||
- cargo build -j $(nproc) --release -p evmbin
|
||||
- cargo build -j $(nproc) --release -p ethstore
|
||||
- cargo build -j $(nproc) --release -p ethkey
|
||||
- strip target/release/parity
|
||||
- strip target/release/evm
|
||||
- strip target/release/ethstore
|
||||
- strip target/release/ethkey
|
||||
- export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
- md5sum target/release/parity > parity.md5
|
||||
- sh scripts/deb-build.sh amd64
|
||||
- cp target/release/parity deb/usr/bin/parity
|
||||
- cp target/release/evm deb/usr/bin/evm
|
||||
- cp target/release/ethstore deb/usr/bin/ethstore
|
||||
- cp target/release/ethkey deb/usr/bin/ethkey
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_amd64.deb"
|
||||
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu
|
||||
tags:
|
||||
- rust
|
||||
- rust-debian
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "stable-x86_64-unknown-debian-gnu_parity"
|
||||
linux-beta:
|
||||
stage: build
|
||||
image: ethcore/rust:beta
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- cargo build -j $(nproc) --release $CARGOFLAGS
|
||||
- strip target/release/parity
|
||||
tags:
|
||||
- rust
|
||||
- rust-beta
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "beta-x86_64-unknown-linux-gnu_parity"
|
||||
allow_failure: true
|
||||
linux-nightly:
|
||||
stage: build
|
||||
image: ethcore/rust:nightly
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- cargo build -j $(nproc) --release $CARGOFLAGS
|
||||
- strip target/release/parity
|
||||
tags:
|
||||
- rust
|
||||
- rust-nightly
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "nigthly-x86_64-unknown-linux-gnu_parity"
|
||||
allow_failure: true
|
||||
linux-centos:
|
||||
stage: build
|
||||
image: ethcore/rust-centos:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- export CXX="g++"
|
||||
- export CC="gcc"
|
||||
- export PLATFORM=x86_64-unknown-centos-gnu
|
||||
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
|
||||
- strip target/release/parity
|
||||
- md5sum target/release/parity > parity.md5
|
||||
- export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity
|
||||
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity.md5 --body parity.md5
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
- rust-centos
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "x86_64-unknown-centos-gnu_parity"
|
||||
linux-i686:
|
||||
stage: build
|
||||
image: ethcore/rust-i686:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- export COMMIT=$(git rev-parse HEAD)
|
||||
- export PLATFORM=i686-unknown-linux-gnu
|
||||
- cargo build -j $(nproc) --target i686-unknown-linux-gnu --features final --release $CARGOFLAGS
|
||||
- strip target/$PLATFORM/release/parity
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- export SHA3=$(target/$PLATFORM/release/parity tools hash target/$PLATFORM/release/parity)
|
||||
- sh scripts/deb-build.sh i386
|
||||
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_i386.deb"
|
||||
- md5sum "parity_"$VER"_i386.deb" > "parity_"$VER"_i386.deb.md5"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5"
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
- rust-i686
|
||||
artifacts:
|
||||
paths:
|
||||
- target/i686-unknown-linux-gnu/release/parity
|
||||
name: "i686-unknown-linux-gnu"
|
||||
allow_failure: true
|
||||
linux-armv7:
|
||||
stage: build
|
||||
image: ethcore/rust-armv7:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- export CC=arm-linux-gnueabihf-gcc
|
||||
- export CXX=arm-linux-gnueabihf-g++
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- export PLATFORM=armv7-unknown-linux-gnueabihf
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.$PLATFORM]" >> .cargo/config
|
||||
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
|
||||
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity
|
||||
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- sh scripts/deb-build.sh armhf
|
||||
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_armhf.deb"
|
||||
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
artifacts:
|
||||
paths:
|
||||
- target/armv7-unknown-linux-gnueabihf/release/parity
|
||||
name: "armv7_unknown_linux_gnueabihf_parity"
|
||||
allow_failure: true
|
||||
linux-arm:
|
||||
stage: build
|
||||
image: ethcore/rust-arm:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- export CC=arm-linux-gnueabihf-gcc
|
||||
- export CXX=arm-linux-gnueabihf-g++
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- export PLATFORM=arm-unknown-linux-gnueabihf
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.$PLATFORM]" >> .cargo/config
|
||||
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
|
||||
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity
|
||||
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- sh scripts/deb-build.sh armhf
|
||||
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_armhf.deb"
|
||||
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
artifacts:
|
||||
paths:
|
||||
- target/arm-unknown-linux-gnueabihf/release/parity
|
||||
name: "arm-unknown-linux-gnueabihf_parity"
|
||||
allow_failure: true
|
||||
linux-armv6:
|
||||
stage: build
|
||||
image: ethcore/rust-armv6:latest
|
||||
only:
|
||||
- beta
|
||||
# - tags
|
||||
# - stable
|
||||
# - triggers
|
||||
script:
|
||||
- export CC=arm-linux-gnueabi-gcc
|
||||
- export CXX=arm-linux-gnueabi-g++
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- export PLATFORM=arm-unknown-linux-gnueabi
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.$PLATFORM]" >> .cargo/config
|
||||
- echo "linker= \"arm-linux-gnueabi-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
|
||||
- arm-linux-gnueabi-strip target/$PLATFORM/release/parity
|
||||
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
artifacts:
|
||||
paths:
|
||||
- target/arm-unknown-linux-gnueabi/release/parity
|
||||
name: "arm-unknown-linux-gnueabi_parity"
|
||||
allow_failure: true
|
||||
linux-aarch64:
|
||||
stage: build
|
||||
image: ethcore/rust-aarch64:latest
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- export CC=aarch64-linux-gnu-gcc
|
||||
- export CXX=aarch64-linux-gnu-g++
|
||||
- export HOST_CC=gcc
|
||||
- export HOST_CXX=g++
|
||||
- export PLATFORM=aarch64-unknown-linux-gnu
|
||||
- rm -rf .cargo
|
||||
- mkdir -p .cargo
|
||||
- echo "[target.$PLATFORM]" >> .cargo/config
|
||||
- echo "linker= \"aarch64-linux-gnu-gcc\"" >> .cargo/config
|
||||
- cat .cargo/config
|
||||
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
|
||||
- aarch64-linux-gnu-strip target/$PLATFORM/release/parity
|
||||
- export SHA3=$(rhash --sha3-256 ~/Core/parity/target/release/parity -p %h)
|
||||
- md5sum target/$PLATFORM/release/parity > parity.md5
|
||||
- sh scripts/deb-build.sh arm64
|
||||
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
- dpkg-deb -b deb "parity_"$VER"_arm64.deb"
|
||||
- md5sum "parity_"$VER"_arm64.deb" > "parity_"$VER"_arm64.deb.md5"
|
||||
- aws configure set aws_access_key_id $s3_key
|
||||
- aws configure set aws_secret_access_key $s3_secret
|
||||
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb"
|
||||
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5"
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- rust
|
||||
- rust-arm
|
||||
artifacts:
|
||||
paths:
|
||||
- target/aarch64-unknown-linux-gnu/release/parity
|
||||
name: "aarch64-unknown-linux-gnu_parity"
|
||||
allow_failure: true
|
||||
darwin:
|
||||
stage: build
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script: |
|
||||
export COMMIT=$(git rev-parse HEAD)
|
||||
export PLATFORM=x86_64-apple-darwin
|
||||
cargo build -j 8 --features final --release #$CARGOFLAGS
|
||||
cargo build -j 8 --features final --release -p ethstore #$CARGOFLAGS
|
||||
rm -rf parity.md5
|
||||
md5sum target/release/parity > parity.md5
|
||||
export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
cd mac
|
||||
xcodebuild -configuration Release
|
||||
cd ..
|
||||
packagesbuild -v mac/Parity.pkgproj
|
||||
productsign --sign 'Developer ID Installer: PARITY TECHNOLOGIES LIMITED (P2PX3JU8FT)' target/release/Parity\ Ethereum.pkg target/release/Parity\ Ethereum-signed.pkg
|
||||
export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
mv target/release/Parity\ Ethereum-signed.pkg "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg"
|
||||
md5sum "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" >> "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
|
||||
aws configure set aws_access_key_id $s3_key
|
||||
aws configure set aws_secret_access_key $s3_secret
|
||||
if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
|
||||
aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/release/parity
|
||||
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
|
||||
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg"
|
||||
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5" --body "parity-"$VER"-osx-installer-EXPERIMENTAL.pkg.md5"
|
||||
curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
|
||||
tags:
|
||||
- osx
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
name: "x86_64-apple-darwin_parity"
|
||||
windows:
|
||||
cache:
|
||||
key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%"
|
||||
untracked: true
|
||||
stage: build
|
||||
only:
|
||||
- beta
|
||||
- tags
|
||||
- stable
|
||||
- triggers
|
||||
script:
|
||||
- set PLATFORM=x86_64-pc-windows-msvc
|
||||
- set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;C:\vs2015\VC\include;C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt
|
||||
- set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64
|
||||
- set RUST_BACKTRACE=1
|
||||
- set RUSTFLAGS=%RUSTFLAGS%
|
||||
- rustup default stable-x86_64-pc-windows-msvc
|
||||
- cargo build --features final --release #%CARGOFLAGS%
|
||||
- signtool sign /f %keyfile% /p %certpass% target\release\parity.exe
|
||||
- target\release\parity.exe tools hash target\release\parity.exe > parity.sha3
|
||||
- set /P SHA3=<parity.sha3
|
||||
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll
|
||||
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe
|
||||
- msbuild windows\ptray\ptray.vcxproj /p:Platform=x64 /p:Configuration=Release
|
||||
- signtool sign /f %keyfile% /p %certpass% windows\ptray\x64\release\ptray.exe
|
||||
- cd nsis
|
||||
- makensis.exe installer.nsi
|
||||
- copy installer.exe InstallParity.exe
|
||||
- signtool sign /f %keyfile% /p %certpass% InstallParity.exe
|
||||
- md5sums InstallParity.exe > InstallParity.exe.md5
|
||||
- zip win-installer.zip InstallParity.exe InstallParity.exe.md5
|
||||
- md5sums win-installer.zip > win-installer.zip.md5
|
||||
- cd ..\target\release\
|
||||
- md5sums parity.exe parity.pdb > parity.md5
|
||||
- md5sums parity.exe > parity.exe.md5
|
||||
- zip parity.zip parity.exe parity.pdb parity.md5
|
||||
- md5sums parity.zip > parity.zip.md5
|
||||
- cd ..\..
|
||||
- aws configure set aws_access_key_id %s3_key%
|
||||
- aws configure set aws_secret_access_key %s3_secret%
|
||||
- echo %CI_BUILD_REF_NAME%
|
||||
- echo %CI_BUILD_REF_NAME% | findstr /R "master" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
|
||||
- echo %CI_BUILD_REF_NAME% | findstr /R "beta" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
|
||||
- echo %CI_BUILD_REF_NAME% | findstr /R "stable" >nul 2>&1 && set S3_BUCKET=builds-parity-published || set S3_BUCKET=builds-parity
|
||||
- echo %S3_BUCKET%
|
||||
- aws s3 rm --recursive s3://%S3_BUCKET%/%CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe --body target\release\parity.exe
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe.md5 --body target\release\parity.exe.md5
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip --body target\release\parity.zip
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip.md5 --body target\release\parity.zip.md5
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe --body nsis\InstallParity.exe
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe.md5 --body nsis\InstallParity.exe.md5
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip --body nsis\win-installer.zip
|
||||
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5
|
||||
- curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1337/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
|
||||
- curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1338/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
|
||||
tags:
|
||||
- rust-windows
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity.exe
|
||||
- target/release/parity.pdb
|
||||
- nsis/InstallParity.exe
|
||||
name: "x86_64-pc-windows-msvc_parity"
|
||||
docker-build:
|
||||
stage: build
|
||||
only:
|
||||
- tags
|
||||
- triggers
|
||||
before_script:
|
||||
- docker info
|
||||
script:
|
||||
- if [ "$CI_BUILD_REF_NAME" == "beta-release" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi
|
||||
- docker login -u $Docker_Hub_User -p $Docker_Hub_Pass
|
||||
- sh scripts/docker-build.sh $DOCKER_TAG
|
||||
tags:
|
||||
- docker
|
||||
test-darwin:
|
||||
stage: test
|
||||
only:
|
||||
- triggers
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||
script:
|
||||
- export RUST_BACKTRACE=1
|
||||
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
|
||||
tags:
|
||||
- osx
|
||||
allow_failure: true
|
||||
test-windows:
|
||||
stage: test
|
||||
only:
|
||||
- triggers
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
script:
|
||||
- set RUST_BACKTRACE=1
|
||||
- echo cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release
|
||||
tags:
|
||||
- rust-windows
|
||||
allow_failure: true
|
||||
test-rust-stable:
|
||||
stage: test
|
||||
image: ethcore/rust:stable
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||
script:
|
||||
- export RUST_BACKTRACE=1
|
||||
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS&&./scripts/cov.sh "$KCOV_CMD"; fi
|
||||
tags:
|
||||
- rust
|
||||
- rust-stable
|
||||
js-test:
|
||||
stage: test
|
||||
image: ethcore/rust:stable
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
|
||||
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi
|
||||
script:
|
||||
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS lint since no JS files modified."; else ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi
|
||||
tags:
|
||||
- rust
|
||||
- rust-stable
|
||||
test-rust-beta:
|
||||
stage: test
|
||||
only:
|
||||
- triggers
|
||||
image: ethcore/rust:beta
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||
script:
|
||||
- export RUST_BACKTRACE=1
|
||||
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
|
||||
tags:
|
||||
- rust
|
||||
- rust-beta
|
||||
allow_failure: true
|
||||
test-rust-nightly:
|
||||
stage: test
|
||||
only:
|
||||
- triggers
|
||||
image: ethcore/rust:nightly
|
||||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
|
||||
script:
|
||||
- export RUST_BACKTRACE=1
|
||||
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
|
||||
tags:
|
||||
- rust
|
||||
- rust-nightly
|
||||
allow_failure: true
|
||||
js-release:
|
||||
stage: js-build
|
||||
only:
|
||||
- master
|
||||
- beta
|
||||
- stable
|
||||
- tags
|
||||
image: ethcore/rust:stable
|
||||
before_script:
|
||||
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
|
||||
- echo $JS_FILES_MODIFIED
|
||||
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi
|
||||
script:
|
||||
- echo $JS_FILES_MODIFIED
|
||||
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS rebuild since no JS files modified."; else ./js/scripts/build.sh && ./js/scripts/release.sh; fi
|
||||
tags:
|
||||
- javascript
|
||||
push-release:
|
||||
stage: push-release
|
||||
only:
|
||||
- tags
|
||||
image: ethcore/rust:stable
|
||||
script:
|
||||
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1337/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
|
||||
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1338/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
|
||||
tags:
|
||||
- curl
|
5
.gitmodules
vendored
5
.gitmodules
vendored
@ -1,4 +1,3 @@
|
||||
[submodule "ethcore/res/ethereum/tests"]
|
||||
path = ethcore/res/ethereum/tests
|
||||
[submodule "crates/ethcore/res/json_tests"]
|
||||
path = crates/ethcore/res/json_tests
|
||||
url = https://github.com/ethereum/tests.git
|
||||
branch = develop
|
||||
|
208
CHANGELOG.md
Normal file
208
CHANGELOG.md
Normal file
@ -0,0 +1,208 @@
|
||||
## OpenEthereum v3.3.3
|
||||
|
||||
Enhancements:
|
||||
* Implement eip-3607 (#593)
|
||||
|
||||
Bug fixes:
|
||||
* Add type field for legacy transactions in RPC calls (#580)
|
||||
* Makes eth_mining to return False if not is not allowed to seal (#581)
|
||||
* Made nodes data concatenate as RLP sequences instead of bytes (#598)
|
||||
|
||||
## OpenEthereum v3.3.2
|
||||
|
||||
Enhancements:
|
||||
* London hardfork block: Sokol (24114400)
|
||||
|
||||
Bug fixes:
|
||||
* Fix for maxPriorityFeePerGas overflow
|
||||
|
||||
## OpenEthereum v3.3.1
|
||||
|
||||
Enhancements:
|
||||
* Add eth_maxPriorityFeePerGas implementation (#570)
|
||||
* Add a bootnode for Kovan
|
||||
|
||||
Bug fixes:
|
||||
* Fix for modexp overflow in debug mode (#578)
|
||||
|
||||
## OpenEthereum v3.3.0
|
||||
|
||||
Enhancements:
|
||||
* Add `validateServiceTransactionsTransition` spec option to be able to enable additional checking of zero gas price transactions by block verifier
|
||||
|
||||
## OpenEthereum v3.3.0-rc.15
|
||||
|
||||
* Revert eip1559BaseFeeMinValue activation on xDai at London hardfork block
|
||||
|
||||
## OpenEthereum v3.3.0-rc.14
|
||||
|
||||
Enhancements:
|
||||
* Add eip1559BaseFeeMinValue and eip1559BaseFeeMinValueTransition spec options
|
||||
* Activate eip1559BaseFeeMinValue on xDai at London hardfork block (19040000), set it to 20 GWei
|
||||
* Activate eip1559BaseFeeMinValue on POA Core at block 24199500 (November 8, 2021), set it to 10 GWei
|
||||
* Delay difficulty bomb to June 2022 for Ethereum Mainnet (EIP-4345)
|
||||
|
||||
## OpenEthereum v3.3.0-rc.13
|
||||
|
||||
Enhancements:
|
||||
* London hardfork block: POA Core (24090200)
|
||||
|
||||
## OpenEthereum v3.3.0-rc.12
|
||||
|
||||
Enhancements:
|
||||
* London hardfork block: xDai (19040000)
|
||||
|
||||
## OpenEthereum v3.3.0-rc.11
|
||||
|
||||
Bug fixes:
|
||||
* Ignore GetNodeData requests only for non-AuRa chains
|
||||
|
||||
## OpenEthereum v3.3.0-rc.10
|
||||
|
||||
Enhancements:
|
||||
* Add eip1559FeeCollector and eip1559FeeCollectorTransition spec options
|
||||
|
||||
## OpenEthereum v3.3.0-rc.9
|
||||
|
||||
Bug fixes:
|
||||
* Add service transactions support for EIP-1559
|
||||
* Fix MinGasPrice config option for POSDAO and EIP-1559
|
||||
|
||||
Enhancements:
|
||||
* min_gas_price becomes min_effective_priority_fee
|
||||
* added version 4 for TxPermission contract
|
||||
|
||||
## OpenEthereum v3.3.0-rc.8
|
||||
|
||||
Bug fixes:
|
||||
* Ignore GetNodeData requests (#519)
|
||||
|
||||
## OpenEthereum v3.3.0-rc.7
|
||||
|
||||
Bug fixes:
|
||||
* GetPooledTransactions is sent in invalid form (wrong packet id)
|
||||
|
||||
## OpenEthereum v3.3.0-rc.6
|
||||
|
||||
Enhancements:
|
||||
* London hardfork block: kovan (26741100) (#502)
|
||||
|
||||
## OpenEthereum v3.3.0-rc.4
|
||||
|
||||
Enhancements:
|
||||
* London hardfork block: mainnet (12,965,000) (#475)
|
||||
* Support for eth/66 protocol version (#465)
|
||||
* Bump ethereum/tests to v9.0.3
|
||||
* Add eth_feeHistory
|
||||
|
||||
Bug fixes:
|
||||
* GetNodeData from eth63 is missing (#466)
|
||||
* Effective gas price not omitting (#477)
|
||||
* London support in openethereum-evm (#479)
|
||||
* gasPrice is required field for Transaction object (#481)
|
||||
|
||||
## OpenEthereum v3.3.0-rc.3
|
||||
|
||||
Bug fixes:
|
||||
* Add effective_gas_price to eth_getTransactionReceipt #445 (#450)
|
||||
* Update eth_gasPrice to support EIP-1559 #449 (#458)
|
||||
* eth_estimateGas returns "Requires higher than upper limit of X" after London Ropsten Hard Fork #459 (#460)
|
||||
|
||||
## OpenEthereum v3.3.0-rc.2
|
||||
|
||||
Enhancements:
|
||||
* EIP-1559: Fee market change for ETH 1.0 chain
|
||||
* EIP-3198: BASEFEE opcode
|
||||
* EIP-3529: Reduction in gas refunds
|
||||
* EIP-3541: Reject new contracts starting with the 0xEF byte
|
||||
* Delay difficulty bomb to December 2021 (EIP-3554)
|
||||
* London hardfork blocks: goerli (5,062,605), rinkeby (8,897,988), ropsten (10,499,401)
|
||||
* Add chainspecs for aleut and baikal
|
||||
* Bump ethereum/tests to v9.0.2
|
||||
|
||||
## OpenEthereum v3.2.6
|
||||
|
||||
Enhancement:
|
||||
* Berlin hardfork blocks: poacore (21,364,900), poasokol (21,050,600)
|
||||
|
||||
## OpenEthereum v3.2.5
|
||||
|
||||
Bug fixes:
|
||||
* Backport: Block sync stopped without any errors. #277 (#286)
|
||||
* Strict memory order (#306)
|
||||
|
||||
Enhancements:
|
||||
* Executable queue for ancient blocks inclusion (#208)
|
||||
* Backport AuRa commits for xdai (#330)
|
||||
* Add Nethermind to clients that accept service transactions (#324)
|
||||
* Implement the filter argument in parity_pendingTransactions (#295)
|
||||
* Ethereum-types and various libs upgraded (#315)
|
||||
* [evmbin] Omit storage output, now for std-json (#311)
|
||||
* Freeze pruning while creating snapshot (#205)
|
||||
* AuRa multi block reward (#290)
|
||||
* Improved metrics. DB read/write. prometheus prefix config (#240)
|
||||
* Send RLPx auth in EIP-8 format (#287)
|
||||
* rpc module reverted for RPC JSON api (#284)
|
||||
* Revert "Remove eth/63 protocol version (#252)"
|
||||
* Support for eth/65 protocol version (#366)
|
||||
* Berlin hardfork blocks: kovan (24,770,900), xdai (16,101,500)
|
||||
* Bump ethereum/tests to v8.0.3
|
||||
|
||||
devops:
|
||||
* Upgrade docker alpine to `v1.13.2`. for rust `v1.47`.
|
||||
* Send SIGTERM instead of SIGHUP to OE daemon (#317)
|
||||
|
||||
## OpenEthereum v3.2.4
|
||||
|
||||
* Fix for Typed transaction broadcast.
|
||||
|
||||
## OpenEthereum v3.2.3
|
||||
|
||||
* Hotfix for berlin consensus error.
|
||||
|
||||
## OpenEthereum v3.2.2-rc.1
|
||||
|
||||
Bug fixes:
|
||||
* Backport: Block sync stopped without any errors. #277 (#286)
|
||||
* Strict memory order (#306)
|
||||
|
||||
Enhancements:
|
||||
* Executable queue for ancient blocks inclusion (#208)
|
||||
* Backport AuRa commits for xdai (#330)
|
||||
* Add Nethermind to clients that accept service transactions (#324)
|
||||
* Implement the filter argument in parity_pendingTransactions (#295)
|
||||
* Ethereum-types and various libs upgraded (#315)
|
||||
* Bump ethereum/tests to v8.0.2
|
||||
* [evmbin] Omit storage output, now for std-json (#311)
|
||||
* Freeze pruning while creating snapshot (#205)
|
||||
* AuRa multi block reward (#290)
|
||||
* Improved metrics. DB read/write. prometheus prefix config (#240)
|
||||
* Send RLPx auth in EIP-8 format (#287)
|
||||
* rpc module reverted for RPC JSON api (#284)
|
||||
* Revert "Remove eth/63 protocol version (#252)"
|
||||
|
||||
devops:
|
||||
* Upgrade docker alpine to `v1.13.2`. for rust `v1.47`.
|
||||
* Send SIGTERM instead of SIGHUP to OE daemon (#317)
|
||||
|
||||
## OpenEthereum v3.2.1
|
||||
|
||||
Hot fix issue, related to initial sync:
|
||||
* Initial sync gets stuck. (#318)
|
||||
|
||||
## OpenEthereum v3.2.0
|
||||
|
||||
Bug fixes:
|
||||
* Update EWF's chains with Istanbul transition block numbers (#11482) (#254)
|
||||
* fix Supplied instant is later than self (#169)
|
||||
* ethcore/snapshot: fix double-lock in Service::feed_chunk (#289)
|
||||
|
||||
Enhancements:
|
||||
* Berlin hardfork blocks: mainnet (12,244,000), goerli (4,460,644), rinkeby (8,290,928) and ropsten (9,812,189)
|
||||
* yolo3x spec (#241)
|
||||
* EIP-2930 RPC support
|
||||
* Remove eth/63 protocol version (#252)
|
||||
* Snapshot manifest block added to prometheus (#232)
|
||||
* EIP-1898: Allow default block parameter to be blockHash
|
||||
* Change ProtocolId to U64
|
||||
* Update ethereum/tests
|
6067
Cargo.lock
generated
6067
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
186
Cargo.toml
186
Cargo.toml
@ -1,97 +1,133 @@
|
||||
[package]
|
||||
description = "Parity Ethereum client"
|
||||
name = "parity"
|
||||
version = "1.6.0"
|
||||
description = "OpenEthereum"
|
||||
name = "openethereum"
|
||||
# NOTE Make sure to update util/version/Cargo.toml as well
|
||||
version = "3.3.3"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
authors = [
|
||||
"OpenEthereum developers",
|
||||
"Parity Technologies <admin@parity.io>"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
log = "0.3"
|
||||
env_logger = "0.3"
|
||||
rustc-serialize = "0.3"
|
||||
docopt = "0.6"
|
||||
time = "0.1"
|
||||
num_cpus = "0.2"
|
||||
blooms-db = { path = "crates/db/blooms-db" }
|
||||
log = "0.4"
|
||||
rustc-hex = "1.0"
|
||||
docopt = "1.0"
|
||||
clap = "2"
|
||||
term_size = "0.3"
|
||||
textwrap = "0.9"
|
||||
num_cpus = "1.2"
|
||||
number_prefix = "0.2"
|
||||
rpassword = "0.2.1"
|
||||
semver = "0.5"
|
||||
ansi_term = "0.7"
|
||||
regex = "0.1"
|
||||
isatty = "0.1"
|
||||
toml = "0.2"
|
||||
serde = "0.9"
|
||||
serde_json = "0.9"
|
||||
app_dirs = "1.1.1"
|
||||
rpassword = "1.0"
|
||||
semver = "0.9"
|
||||
ansi_term = "0.10"
|
||||
parking_lot = "0.11.1"
|
||||
regex = "1.0"
|
||||
atty = "0.2.8"
|
||||
toml = "0.4"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_derive = "1.0"
|
||||
futures = "0.1"
|
||||
hyper = { version = "0.12" }
|
||||
fdlimit = "0.1"
|
||||
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" }
|
||||
ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" }
|
||||
jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" }
|
||||
ethsync = { path = "sync" }
|
||||
ethcore = { path = "ethcore" }
|
||||
ethcore-util = { path = "util" }
|
||||
ethcore-io = { path = "util/io" }
|
||||
ethcore-devtools = { path = "devtools" }
|
||||
ethcore-rpc = { path = "rpc" }
|
||||
ethcore-signer = { path = "signer" }
|
||||
ethcore-ipc = { path = "ipc/rpc" }
|
||||
ethcore-ipc-nano = { path = "ipc/nano" }
|
||||
ethcore-ipc-hypervisor = { path = "ipc/hypervisor" }
|
||||
ethcore-light = { path = "ethcore/light" }
|
||||
ethcore-logger = { path = "logger" }
|
||||
ethcore-stratum = { path = "stratum" }
|
||||
evmbin = { path = "evmbin" }
|
||||
rlp = { path = "util/rlp" }
|
||||
rpc-cli = { path = "rpc_cli" }
|
||||
parity-rpc-client = { path = "rpc_client" }
|
||||
parity-hash-fetch = { path = "hash-fetch" }
|
||||
parity-ipfs-api = { path = "ipfs" }
|
||||
parity-updater = { path = "updater" }
|
||||
parity-reactor = { path = "util/reactor" }
|
||||
parity-local-store = { path = "local-store" }
|
||||
ethcore-dapps = { path = "dapps", optional = true }
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
ethcore-secretstore = { path = "secret_store", optional = true }
|
||||
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
|
||||
jsonrpc-core = "15.0.0"
|
||||
parity-bytes = "0.1"
|
||||
common-types = { path = "crates/ethcore/types" }
|
||||
ethcore = { path = "crates/ethcore", features = ["parity"] }
|
||||
ethcore-accounts = { path = "crates/accounts", optional = true }
|
||||
ethcore-blockchain = { path = "crates/ethcore/blockchain" }
|
||||
ethcore-call-contract = { path = "crates/vm/call-contract"}
|
||||
ethcore-db = { path = "crates/db/db" }
|
||||
ethcore-io = { path = "crates/runtime/io" }
|
||||
ethcore-logger = { path = "bin/oe/logger" }
|
||||
ethcore-miner = { path = "crates/concensus/miner" }
|
||||
ethcore-network = { path = "crates/net/network" }
|
||||
ethcore-service = { path = "crates/ethcore/service" }
|
||||
ethcore-sync = { path = "crates/ethcore/sync" }
|
||||
ethereum-types = "0.9.2"
|
||||
ethkey = { path = "crates/accounts/ethkey" }
|
||||
ethstore = { path = "crates/accounts/ethstore" }
|
||||
fetch = { path = "crates/net/fetch" }
|
||||
node-filter = { path = "crates/net/node-filter" }
|
||||
parity-crypto = { version = "0.6.2", features = [ "publickey" ] }
|
||||
rlp = { version = "0.4.6" }
|
||||
cli-signer= { path = "crates/util/cli-signer" }
|
||||
parity-daemonize = "0.3"
|
||||
parity-local-store = { path = "crates/concensus/miner/local-store" }
|
||||
parity-runtime = { path = "crates/runtime/runtime" }
|
||||
parity-rpc = { path = "crates/rpc" }
|
||||
parity-version = { path = "crates/util/version" }
|
||||
parity-path = "0.1"
|
||||
dir = { path = "crates/util/dir" }
|
||||
panic_hook = { path = "crates/util/panic-hook" }
|
||||
keccak-hash = "0.5.0"
|
||||
migration-rocksdb = { path = "crates/db/migration-rocksdb" }
|
||||
kvdb = "0.1"
|
||||
kvdb-rocksdb = "0.1.3"
|
||||
journaldb = { path = "crates/db/journaldb" }
|
||||
stats = { path = "crates/util/stats" }
|
||||
prometheus = "0.9.0"
|
||||
|
||||
# ethcore-secretstore = { path = "crates/util/secret-store", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
ethcore-ipc-tests = { path = "ipc/tests" }
|
||||
pretty_assertions = "0.1"
|
||||
ipnetwork = "0.12.6"
|
||||
tempdir = "0.3"
|
||||
fake-fetch = { path = "crates/net/fake-fetch" }
|
||||
lazy_static = "1.2.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.2"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
daemonize = "0.2"
|
||||
winapi = { version = "0.3.4", features = ["winsock2", "winuser", "shellapi"] }
|
||||
|
||||
[features]
|
||||
default = ["ui-precompiled"]
|
||||
ui = [
|
||||
"dapps",
|
||||
"ethcore-dapps/ui",
|
||||
"ethcore-signer/ui",
|
||||
]
|
||||
ui-precompiled = [
|
||||
"dapps",
|
||||
"ethcore-signer/ui-precompiled",
|
||||
"ethcore-dapps/ui-precompiled",
|
||||
]
|
||||
dapps = ["ethcore-dapps"]
|
||||
ipc = ["ethcore/ipc", "ethsync/ipc"]
|
||||
jit = ["ethcore/jit"]
|
||||
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethcore-dapps/dev", "ethcore-signer/dev"]
|
||||
default = ["accounts"]
|
||||
accounts = ["ethcore-accounts", "parity-rpc/accounts"]
|
||||
miner-debug = ["ethcore/miner-debug"]
|
||||
json-tests = ["ethcore/json-tests"]
|
||||
ci-skip-tests = ["ethcore/ci-skip-tests"]
|
||||
test-heavy = ["ethcore/test-heavy"]
|
||||
ethkey-cli = ["ethcore/ethkey-cli"]
|
||||
ethstore-cli = ["ethcore/ethstore-cli"]
|
||||
evm-debug = ["ethcore/evm-debug"]
|
||||
evm-debug-tests = ["ethcore/evm-debug-tests"]
|
||||
slow-blocks = ["ethcore/slow-blocks"]
|
||||
final = ["ethcore-util/final"]
|
||||
secretstore = ["ethcore-secretstore"]
|
||||
final = ["parity-version/final"]
|
||||
deadlock_detection = ["parking_lot/deadlock_detection"]
|
||||
# to create a memory profile (requires nightly rust), use e.g.
|
||||
# `heaptrack /path/to/parity <parity params>`,
|
||||
# to visualize a memory profile, use `heaptrack_gui`
|
||||
# or
|
||||
# `valgrind --tool=massif /path/to/parity <parity params>`
|
||||
# and `massif-visualizer` for visualization
|
||||
memory_profiling = []
|
||||
|
||||
[lib]
|
||||
path = "bin/oe/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
path = "parity/main.rs"
|
||||
name = "parity"
|
||||
path = "bin/oe/main.rs"
|
||||
name = "openethereum"
|
||||
|
||||
[profile.test]
|
||||
lto = false
|
||||
opt-level = 3 # makes tests slower to compile, but faster to run
|
||||
|
||||
[profile.release]
|
||||
debug = false
|
||||
lto = false
|
||||
panic = "abort"
|
||||
lto = true
|
||||
|
||||
[workspace]
|
||||
# This should only list projects that are not
|
||||
# in the dependency tree in any other way
|
||||
# (i.e. pretty much only standalone CLI tools)
|
||||
members = [
|
||||
"bin/ethkey",
|
||||
"bin/ethstore",
|
||||
"bin/evmbin",
|
||||
"bin/chainspec"
|
||||
]
|
||||
|
357
README.md
357
README.md
@ -1,133 +1,318 @@
|
||||
# [Parity](https://ethcore.io/parity.html)
|
||||
### Fast, light, and robust Ethereum implementation
|
||||
# OpenEthereum
|
||||
|
||||
[![build status](https://gitlab.ethcore.io/parity/parity/badges/master/build.svg)](https://gitlab.ethcore.io/parity/parity/commits/master) [![Coverage Status][coveralls-image]][coveralls-url] [![GPLv3][license-image]][license-url]
|
||||
Fast and feature-rich multi-network Ethereum client.
|
||||
|
||||
### Join the chat!
|
||||
[» Download the latest release «](https://github.com/openethereum/openethereum/releases/latest)
|
||||
|
||||
Parity [![Join the chat at https://gitter.im/ethcore/parity][gitter-image]][gitter-url] and
|
||||
parity.js [![Join the chat at https://gitter.im/ethcore/parity.js](https://badges.gitter.im/ethcore/parity.js.svg)](https://gitter.im/ethcore/parity.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[![GPL licensed][license-badge]][license-url]
|
||||
[![Build Status][ci-badge]][ci-url]
|
||||
[![Discord chat][chat-badge]][chat-url]
|
||||
|
||||
[Internal Documentation][doc-url]
|
||||
[license-badge]: https://img.shields.io/badge/license-GPL_v3-green.svg
|
||||
[license-url]: LICENSE
|
||||
[ci-badge]: https://github.com/openethereum/openethereum/workflows/Build%20and%20Test%20Suite/badge.svg
|
||||
[ci-url]: https://github.com/openethereum/openethereum/actions
|
||||
[chat-badge]: https://img.shields.io/discord/669192218728202270.svg?logo=discord
|
||||
[chat-url]: https://discord.io/openethereum
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Description](#chapter-001)
|
||||
2. [Technical Overview](#chapter-002)
|
||||
3. [Building](#chapter-003)<br>
|
||||
3.1 [Building Dependencies](#chapter-0031)<br>
|
||||
3.2 [Building from Source Code](#chapter-0032)<br>
|
||||
3.3 [Starting OpenEthereum](#chapter-0034)
|
||||
4. [Testing](#chapter-004)
|
||||
5. [Documentation](#chapter-005)
|
||||
6. [Toolchain](#chapter-006)
|
||||
7. [Contributing](#chapter-008)
|
||||
8. [License](#chapter-009)
|
||||
|
||||
|
||||
Be sure to check out [our wiki][wiki-url] for more information.
|
||||
## 1. Description <a id="chapter-001"></a>
|
||||
|
||||
[coveralls-image]: https://coveralls.io/repos/github/ethcore/parity/badge.svg?branch=master
|
||||
[coveralls-url]: https://coveralls.io/github/ethcore/parity?branch=master
|
||||
[gitter-image]: https://badges.gitter.im/Join%20Chat.svg
|
||||
[gitter-url]: https://gitter.im/ethcore/parity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||
[license-image]: https://img.shields.io/badge/license-GPL%20v3-green.svg
|
||||
[license-url]: https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
[doc-url]: https://ethcore.github.io/parity/ethcore/index.html
|
||||
[wiki-url]: https://github.com/ethcore/parity/wiki
|
||||
**Built for mission-critical use**: Miners, service providers, and exchanges need fast synchronisation and maximum uptime. OpenEthereum provides the core infrastructure essential for speedy and reliable services.
|
||||
|
||||
**Parity requires Rust version 1.15.0 to build**
|
||||
- Clean, modular codebase for easy customisation
|
||||
- Advanced CLI-based client
|
||||
- Minimal memory and storage footprint
|
||||
- Synchronise in hours, not days with Warp Sync
|
||||
- Modular for light integration into your service or product
|
||||
|
||||
----
|
||||
## 2. Technical Overview <a id="chapter-002"></a>
|
||||
|
||||
OpenEthereum's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing OpenEthereum using the **Rust programming language**. OpenEthereum is licensed under the GPLv3 and can be used for all your Ethereum needs.
|
||||
|
||||
## About Parity
|
||||
By default, OpenEthereum runs a JSON-RPC HTTP server on port `:8545` and a Web-Sockets server on port `:8546`. This is fully configurable and supports a number of APIs.
|
||||
|
||||
Parity's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing Parity using the sophisticated and
|
||||
cutting-edge Rust programming language. Parity is licensed under the GPLv3, and can be used for all your Ethereum needs.
|
||||
If you run into problems while using OpenEthereum, check out the [old wiki for documentation](https://openethereum.github.io/), feel free to [file an issue in this repository](https://github.com/openethereum/openethereum/issues/new), or hop on our [Discord](https://discord.io/openethereum) chat room to ask a question. We are glad to help!
|
||||
|
||||
Parity comes with a built-in wallet. To access [Parity Wallet](http://127.0.0.1:8080/) simply go to http://127.0.0.1:8080/. It
|
||||
includes various functionality allowing you to:
|
||||
- create and manage your Ethereum accounts;
|
||||
- manage your Ether and any Ethereum tokens;
|
||||
- create and register your own tokens;
|
||||
- and much more.
|
||||
You can download OpenEthereum's latest release at [the releases page](https://github.com/openethereum/openethereum/releases) or follow the instructions below to build from source. Read the [CHANGELOG.md](CHANGELOG.md) for a list of all changes between different versions.
|
||||
|
||||
By default, Parity will also run a JSONRPC server on `127.0.0.1:8545`. This is fully configurable and supports a number
|
||||
of RPC APIs.
|
||||
## 3. Building <a id="chapter-003"></a>
|
||||
|
||||
If you run into an issue while using parity, feel free to file one in this repository
|
||||
or hop on our [gitter chat room][gitter-url] to ask a question. We are glad to help!
|
||||
### 3.1 Build Dependencies <a id="chapter-0031"></a>
|
||||
|
||||
Parity's current release is 1.5. You can download it at https://ethcore.io/parity.html or follow the instructions
|
||||
below to build from source.
|
||||
OpenEthereum requires **latest stable Rust version** to build.
|
||||
|
||||
----
|
||||
|
||||
## Build dependencies
|
||||
|
||||
Parity is fully compatible with Stable Rust.
|
||||
|
||||
We recommend installing Rust through [rustup](https://www.rustup.rs/). If you don't already have rustup, you can install it like this:
|
||||
We recommend installing Rust through [rustup](https://www.rustup.rs/). If you don't already have `rustup`, you can install it like this:
|
||||
|
||||
- Linux:
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
```
|
||||
|
||||
Parity also requires `gcc`, `g++`, `libssl-dev`/`openssl`, `libudev-dev` and `pkg-config` packages to be installed.
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
```
|
||||
|
||||
OpenEthereum also requires `clang` (>= 9.0), `clang++`, `pkg-config`, `file`, `make`, and `cmake` packages to be installed.
|
||||
|
||||
- OSX:
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
```
|
||||
```bash
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
```
|
||||
|
||||
`clang` is required. It comes with Xcode command line tools or can be installed with homebrew.
|
||||
- Windows
|
||||
`clang` is required. It comes with Xcode command line tools or can be installed with homebrew.
|
||||
|
||||
Make sure you have Visual Studio 2015 with C++ support installed. Next, download and run the rustup installer from
|
||||
https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe, start "VS2015 x64 Native Tools Command Prompt", and use the following command to install and set up the msvc toolchain:
|
||||
```
|
||||
$ rustup default stable-x86_64-pc-windows-msvc
|
||||
```
|
||||
- Windows:
|
||||
Make sure you have Visual Studio 2015 with C++ support installed. Next, download and run the `rustup` installer from
|
||||
https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe, start "VS2015 x64 Native Tools Command Prompt", and use the following command to install and set up the `msvc` toolchain:
|
||||
```bash
|
||||
$ rustup default stable-x86_64-pc-windows-msvc
|
||||
```
|
||||
|
||||
Once you have rustup, install parity or download and build from source
|
||||
Once you have `rustup` installed, then you need to install:
|
||||
* [Perl](https://www.perl.org)
|
||||
* [Yasm](https://yasm.tortall.net)
|
||||
|
||||
----
|
||||
Make sure that these binaries are in your `PATH`. After that, you should be able to build OpenEthereum from source.
|
||||
|
||||
## Quick install
|
||||
### 3.2 Build from Source Code <a id="chapter-0032"></a>
|
||||
|
||||
```bash
|
||||
cargo install --git https://github.com/ethcore/parity.git parity
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
## Build from source
|
||||
|
||||
```bash
|
||||
# download Parity code
|
||||
$ git clone https://github.com/ethcore/parity
|
||||
$ cd parity
|
||||
# download OpenEthereum code
|
||||
$ git clone https://github.com/openethereum/openethereum
|
||||
$ cd openethereum
|
||||
|
||||
# build in release mode
|
||||
$ cargo build --release
|
||||
$ cargo build --release --features final
|
||||
```
|
||||
|
||||
This will produce an executable in the `./target/release` subdirectory.
|
||||
This produces an executable in the `./target/release` subdirectory.
|
||||
|
||||
Note: if cargo fails to parse manifest try:
|
||||
|
||||
```bash
|
||||
$ ~/.cargo/bin/cargo build --release
|
||||
```
|
||||
----
|
||||
|
||||
## Simple one-line installer for Mac and Ubuntu
|
||||
Note, when compiling a crate and you receive errors, it's in most cases your outdated version of Rust, or some of your crates have to be recompiled. Cleaning the repository will most likely solve the issue if you are on the latest stable version of Rust, try:
|
||||
|
||||
```bash
|
||||
bash <(curl https://get.parity.io -Lk)
|
||||
$ cargo clean
|
||||
```
|
||||
|
||||
## Start Parity
|
||||
### Manually
|
||||
To start Parity manually, just run
|
||||
This always compiles the latest nightly builds. If you want to build stable, do a
|
||||
|
||||
```bash
|
||||
$ ./target/release/parity
|
||||
$ git checkout stable
|
||||
```
|
||||
|
||||
and Parity will begin syncing the Ethereum blockchain.
|
||||
### 3.3 Starting OpenEthereum <a id="chapter-0034"></a>
|
||||
|
||||
### Using systemd service file
|
||||
To start Parity as a regular user using systemd init:
|
||||
#### Manually
|
||||
|
||||
1. Copy `parity/scripts/parity.service` to your
|
||||
systemd user directory (usually `~/.config/systemd/user`).
|
||||
2. To pass any argument to Parity, write a `~/.parity/parity.conf` file this way:
|
||||
`ARGS="ARG1 ARG2 ARG3"`.
|
||||
To start OpenEthereum manually, just run
|
||||
|
||||
Example: `ARGS="ui --identity MyMachine"`.
|
||||
```bash
|
||||
$ ./target/release/openethereum
|
||||
```
|
||||
|
||||
so OpenEthereum begins syncing the Ethereum blockchain.
|
||||
|
||||
#### Using `systemd` service file
|
||||
|
||||
To start OpenEthereum as a regular user using `systemd` init:
|
||||
|
||||
1. Copy `./scripts/openethereum.service` to your
|
||||
`systemd` user directory (usually `~/.config/systemd/user`).
|
||||
2. Copy release to bin folder, write `sudo install ./target/release/openethereum /usr/bin/openethereum`
|
||||
3. To configure OpenEthereum, see [our wiki](https://openethereum.github.io/Configuring-OpenEthereum) for details.
|
||||
|
||||
## 4. Testing <a id="chapter-004"></a>
|
||||
|
||||
Download the required test files: `git submodule update --init --recursive`. You can run tests with the following commands:
|
||||
|
||||
* **All** packages
|
||||
```
|
||||
cargo test --all
|
||||
```
|
||||
|
||||
* Specific package
|
||||
```
|
||||
cargo test --package <spec>
|
||||
```
|
||||
|
||||
Replace `<spec>` with one of the packages from the [package list](#package-list) (e.g. `cargo test --package evmbin`).
|
||||
|
||||
You can show your logs in the test output by passing `--nocapture` (i.e. `cargo test --package evmbin -- --nocapture`)
|
||||
|
||||
## 5. Documentation <a id="chapter-005"></a>
|
||||
|
||||
Be sure to [check out our wiki](https://openethereum.github.io/) for more information.
|
||||
|
||||
### Viewing documentation for OpenEthereum packages
|
||||
|
||||
You can generate documentation for OpenEthereum Rust packages that automatically opens in your web browser using [rustdoc with Cargo](https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html#using-rustdoc-with-cargo) (of the The Rustdoc Book), by running the the following commands:
|
||||
|
||||
* **All** packages
|
||||
```
|
||||
cargo doc --document-private-items --open
|
||||
```
|
||||
|
||||
* Specific package
|
||||
```
|
||||
cargo doc --package <spec> -- --document-private-items --open
|
||||
```
|
||||
|
||||
Use`--document-private-items` to also view private documentation and `--no-deps` to exclude building documentation for dependencies.
|
||||
|
||||
Replacing `<spec>` with one of the following from the details section below (i.e. `cargo doc --package openethereum --open`):
|
||||
|
||||
<a id="package-list"></a>
|
||||
**Package List**
|
||||
<details><p>
|
||||
|
||||
* OpenEthereum Client Application
|
||||
```bash
|
||||
openethereum
|
||||
```
|
||||
* OpenEthereum Account Management, Key Management Tool, and Keys Generator
|
||||
```bash
|
||||
ethcore-accounts, ethkey-cli, ethstore, ethstore-cli
|
||||
```
|
||||
* OpenEthereum Chain Specification
|
||||
```bash
|
||||
chainspec
|
||||
```
|
||||
* OpenEthereum CLI Signer Tool & RPC Client
|
||||
```bash
|
||||
cli-signer parity-rpc-client
|
||||
```
|
||||
* OpenEthereum Ethash & ProgPoW Implementations
|
||||
```bash
|
||||
ethash
|
||||
```
|
||||
* EthCore Library
|
||||
```bash
|
||||
ethcore
|
||||
```
|
||||
* OpenEthereum Blockchain Database, Test Generator, Configuration,
|
||||
Caching, Importing Blocks, and Block Information
|
||||
```bash
|
||||
ethcore-blockchain
|
||||
```
|
||||
* OpenEthereum Contract Calls and Blockchain Service & Registry Information
|
||||
```bash
|
||||
ethcore-call-contract
|
||||
```
|
||||
* OpenEthereum Database Access & Utilities, Database Cache Manager
|
||||
```bash
|
||||
ethcore-db
|
||||
```
|
||||
* OpenEthereum Virtual Machine (EVM) Rust Implementation
|
||||
```bash
|
||||
evm
|
||||
```
|
||||
* OpenEthereum Light Client Implementation
|
||||
```bash
|
||||
ethcore-light
|
||||
```
|
||||
* Smart Contract based Node Filter, Manage Permissions of Network Connections
|
||||
```bash
|
||||
node-filter
|
||||
```
|
||||
* OpenEthereum Client & Network Service Creation & Registration with the I/O Subsystem
|
||||
```bash
|
||||
ethcore-service
|
||||
```
|
||||
* OpenEthereum Blockchain Synchronization
|
||||
```bash
|
||||
ethcore-sync
|
||||
```
|
||||
* OpenEthereum Common Types
|
||||
```bash
|
||||
common-types
|
||||
```
|
||||
* OpenEthereum Virtual Machines (VM) Support Library
|
||||
```bash
|
||||
vm
|
||||
```
|
||||
* OpenEthereum WASM Interpreter
|
||||
```bash
|
||||
wasm
|
||||
```
|
||||
* OpenEthereum WASM Test Runner
|
||||
```bash
|
||||
pwasm-run-test
|
||||
```
|
||||
* OpenEthereum EVM Implementation
|
||||
```bash
|
||||
evmbin
|
||||
```
|
||||
* OpenEthereum JSON Deserialization
|
||||
```bash
|
||||
ethjson
|
||||
```
|
||||
* OpenEthereum State Machine Generalization for Consensus Engines
|
||||
```bash
|
||||
parity-machine
|
||||
```
|
||||
* OpenEthereum Miner Interface
|
||||
```bash
|
||||
ethcore-miner parity-local-store price-info ethcore-stratum using_queue
|
||||
```
|
||||
* OpenEthereum Logger Implementation
|
||||
```bash
|
||||
ethcore-logger
|
||||
```
|
||||
* OpenEthereum JSON-RPC Servers
|
||||
```bash
|
||||
parity-rpc
|
||||
```
|
||||
* OpenEthereum Updater Service
|
||||
```bash
|
||||
parity-updater parity-hash-fetch
|
||||
```
|
||||
* OpenEthereum Core Libraries (`util`)
|
||||
```bash
|
||||
accounts-bloom blooms-db dir eip-712 fake-fetch fastmap fetch ethcore-io
|
||||
journaldb keccak-hasher len-caching-lock memory-cache memzero
|
||||
migration-rocksdb ethcore-network ethcore-network-devp2p panic_hook
|
||||
patricia-trie-ethereum registrar rlp_compress stats
|
||||
time-utils triehash-ethereum unexpected parity-version
|
||||
```
|
||||
|
||||
</p></details>
|
||||
|
||||
## 6. Toolchain <a id="chapter-006"></a>
|
||||
|
||||
In addition to the OpenEthereum client, there are additional tools in this repository available:
|
||||
|
||||
- [evmbin](./bin/evmbin) - OpenEthereum EVM Implementation.
|
||||
- [ethstore](./crates/accounts/ethstore) - OpenEthereum Key Management.
|
||||
- [ethkey](./crates/accounts/ethkey) - OpenEthereum Keys Generator.
|
||||
|
||||
The following tools are available in a separate repository:
|
||||
- [ethabi](https://github.com/openethereum/ethabi) - OpenEthereum Encoding of Function Calls. [Docs here](https://crates.io/crates/ethabi)
|
||||
- [whisper](https://github.com/openethereum/whisper) - OpenEthereum Whisper-v2 PoC Implementation.
|
||||
|
||||
## 7. Contributing <a id="chapter-007"></a>
|
||||
|
||||
An introduction has been provided in the ["So You Want to be a Core Developer" presentation slides by Hernando Castano](http://tiny.cc/contrib-to-parity-eth). Additional guidelines are provided in [CONTRIBUTING](./.github/CONTRIBUTING.md).
|
||||
|
||||
### Contributor Code of Conduct
|
||||
|
||||
[CODE_OF_CONDUCT](./.github/CODE_OF_CONDUCT.md)
|
||||
|
||||
## 8. License <a id="chapter-008"></a>
|
||||
|
||||
[LICENSE](./LICENSE)
|
||||
|
9
bin/chainspec/Cargo.toml
Normal file
9
bin/chainspec/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
description = "Parity Ethereum Chain Specification"
|
||||
name = "chainspec"
|
||||
version = "0.1.0"
|
||||
authors = ["Marek Kotewicz <marek@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
ethjson = { path = "../../crates/ethjson" }
|
||||
serde_json = "1.0"
|
51
bin/chainspec/src/main.rs
Normal file
51
bin/chainspec/src/main.rs
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate ethjson;
|
||||
extern crate serde_json;
|
||||
|
||||
use ethjson::spec::Spec;
|
||||
use std::{env, fs, process};
|
||||
|
||||
fn quit(s: &str) -> ! {
|
||||
println!("{}", s);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut args = env::args();
|
||||
if args.len() != 2 {
|
||||
quit(
|
||||
"You need to specify chainspec.json\n\
|
||||
\n\
|
||||
./chainspec <chainspec.json>",
|
||||
);
|
||||
}
|
||||
|
||||
let path = args.nth(1).expect("args.len() == 2; qed");
|
||||
let file = match fs::File::open(&path) {
|
||||
Ok(file) => file,
|
||||
Err(_) => quit(&format!("{} could not be opened", path)),
|
||||
};
|
||||
|
||||
let spec: Result<Spec, _> = serde_json::from_reader(file);
|
||||
|
||||
if let Err(err) = spec {
|
||||
quit(&format!("{} {}", path, err.to_string()));
|
||||
}
|
||||
|
||||
println!("{} is valid", path);
|
||||
}
|
22
bin/ethkey/Cargo.toml
Normal file
22
bin/ethkey/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
description = "Parity Ethereum Keys Generator CLI"
|
||||
name = "ethkey-cli"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
docopt = "1.0"
|
||||
env_logger = "0.5"
|
||||
ethkey = { path = "../../crates/accounts/ethkey" }
|
||||
panic_hook = { path = "../../crates/util/panic-hook" }
|
||||
parity-crypto = { version = "0.6.2", features = [ "publickey" ] }
|
||||
parity-wordlist="1.3"
|
||||
rustc-hex = "1.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
threadpool = "1.7"
|
||||
|
||||
[[bin]]
|
||||
name = "ethkey"
|
||||
path = "src/main.rs"
|
||||
doc = false
|
493
bin/ethkey/src/main.rs
Normal file
493
bin/ethkey/src/main.rs
Normal file
@ -0,0 +1,493 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate docopt;
|
||||
extern crate env_logger;
|
||||
extern crate ethkey;
|
||||
extern crate panic_hook;
|
||||
extern crate parity_crypto as crypto;
|
||||
extern crate parity_wordlist;
|
||||
extern crate rustc_hex;
|
||||
extern crate serde;
|
||||
extern crate threadpool;
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use std::{env, fmt, io, num::ParseIntError, process, sync};
|
||||
|
||||
use crypto::publickey::{
|
||||
sign, verify_address, verify_public, Error as EthkeyError, Generator, KeyPair, Random,
|
||||
};
|
||||
use docopt::Docopt;
|
||||
use ethkey::{brain_recover, Brain, BrainPrefix, Prefix};
|
||||
use rustc_hex::{FromHex, FromHexError};
|
||||
|
||||
const USAGE: &'static str = r#"
|
||||
Parity Ethereum keys generator.
|
||||
Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
|
||||
Usage:
|
||||
ethkey info <secret-or-phrase> [options]
|
||||
ethkey generate random [options]
|
||||
ethkey generate prefix <prefix> [options]
|
||||
ethkey sign <secret> <message>
|
||||
ethkey verify public <public> <signature> <message>
|
||||
ethkey verify address <address> <signature> <message>
|
||||
ethkey recover <address> <known-phrase>
|
||||
ethkey [-h | --help]
|
||||
|
||||
Options:
|
||||
-h, --help Display this message and exit.
|
||||
-s, --secret Display only the secret key.
|
||||
-p, --public Display only the public key.
|
||||
-a, --address Display only the address.
|
||||
-b, --brain Use parity brain wallet algorithm. Not recommended.
|
||||
|
||||
Commands:
|
||||
info Display public key and address of the secret.
|
||||
generate random Generates new random Ethereum key.
|
||||
generate prefix Random generation, but address must start with a prefix ("vanity address").
|
||||
sign Sign message using a secret key.
|
||||
verify Verify signer of the signature by public key or address.
|
||||
recover Try to find brain phrase matching given address from partial phrase.
|
||||
"#;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Args {
|
||||
cmd_info: bool,
|
||||
cmd_generate: bool,
|
||||
cmd_random: bool,
|
||||
cmd_prefix: bool,
|
||||
cmd_sign: bool,
|
||||
cmd_verify: bool,
|
||||
cmd_public: bool,
|
||||
cmd_address: bool,
|
||||
cmd_recover: bool,
|
||||
arg_prefix: String,
|
||||
arg_secret: String,
|
||||
arg_secret_or_phrase: String,
|
||||
arg_known_phrase: String,
|
||||
arg_message: String,
|
||||
arg_public: String,
|
||||
arg_address: String,
|
||||
arg_signature: String,
|
||||
flag_secret: bool,
|
||||
flag_public: bool,
|
||||
flag_address: bool,
|
||||
flag_brain: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
Ethkey(EthkeyError),
|
||||
FromHex(FromHexError),
|
||||
ParseInt(ParseIntError),
|
||||
Docopt(docopt::Error),
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl From<EthkeyError> for Error {
|
||||
fn from(err: EthkeyError) -> Self {
|
||||
Error::Ethkey(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FromHexError> for Error {
|
||||
fn from(err: FromHexError) -> Self {
|
||||
Error::FromHex(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for Error {
|
||||
fn from(err: ParseIntError) -> Self {
|
||||
Error::ParseInt(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<docopt::Error> for Error {
|
||||
fn from(err: docopt::Error) -> Self {
|
||||
Error::Docopt(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
Error::Ethkey(ref e) => write!(f, "{}", e),
|
||||
Error::FromHex(ref e) => write!(f, "{}", e),
|
||||
Error::ParseInt(ref e) => write!(f, "{}", e),
|
||||
Error::Docopt(ref e) => write!(f, "{}", e),
|
||||
Error::Io(ref e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum DisplayMode {
|
||||
KeyPair,
|
||||
Secret,
|
||||
Public,
|
||||
Address,
|
||||
}
|
||||
|
||||
impl DisplayMode {
|
||||
fn new(args: &Args) -> Self {
|
||||
if args.flag_secret {
|
||||
DisplayMode::Secret
|
||||
} else if args.flag_public {
|
||||
DisplayMode::Public
|
||||
} else if args.flag_address {
|
||||
DisplayMode::Address
|
||||
} else {
|
||||
DisplayMode::KeyPair
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
panic_hook::set_abort();
|
||||
env_logger::try_init().expect("Logger initialized only once.");
|
||||
|
||||
match execute(env::args()) {
|
||||
Ok(ok) => println!("{}", ok),
|
||||
Err(Error::Docopt(ref e)) => e.exit(),
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn display(result: (KeyPair, Option<String>), mode: DisplayMode) -> String {
|
||||
let keypair = result.0;
|
||||
match mode {
|
||||
DisplayMode::KeyPair => match result.1 {
|
||||
Some(extra_data) => format!("{}\n{}", extra_data, keypair),
|
||||
None => format!("{}", keypair),
|
||||
},
|
||||
DisplayMode::Secret => format!("{:x}", keypair.secret()),
|
||||
DisplayMode::Public => format!("{:x}", keypair.public()),
|
||||
DisplayMode::Address => format!("{:x}", keypair.address()),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute<S, I>(command: I) -> Result<String, Error>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let args: Args = Docopt::new(USAGE).and_then(|d| d.argv(command).deserialize())?;
|
||||
|
||||
return if args.cmd_info {
|
||||
let display_mode = DisplayMode::new(&args);
|
||||
|
||||
let result = if args.flag_brain {
|
||||
let phrase = args.arg_secret_or_phrase;
|
||||
let phrase_info = validate_phrase(&phrase);
|
||||
let keypair = Brain::new(phrase).generate();
|
||||
(keypair, Some(phrase_info))
|
||||
} else {
|
||||
let secret = args
|
||||
.arg_secret_or_phrase
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidSecretKey)?;
|
||||
(KeyPair::from_secret(secret)?, None)
|
||||
};
|
||||
Ok(display(result, display_mode))
|
||||
} else if args.cmd_generate {
|
||||
let display_mode = DisplayMode::new(&args);
|
||||
let result = if args.cmd_random {
|
||||
if args.flag_brain {
|
||||
let mut brain = BrainPrefix::new(vec![0], usize::max_value(), BRAIN_WORDS);
|
||||
let keypair = brain.generate()?;
|
||||
let phrase = format!("recovery phrase: {}", brain.phrase());
|
||||
(keypair, Some(phrase))
|
||||
} else {
|
||||
(Random.generate(), None)
|
||||
}
|
||||
} else if args.cmd_prefix {
|
||||
let prefix = args.arg_prefix.from_hex()?;
|
||||
let brain = args.flag_brain;
|
||||
in_threads(move || {
|
||||
let iterations = 1024;
|
||||
let prefix = prefix.clone();
|
||||
move || {
|
||||
let prefix = prefix.clone();
|
||||
let res = if brain {
|
||||
let mut brain = BrainPrefix::new(prefix, iterations, BRAIN_WORDS);
|
||||
let result = brain.generate();
|
||||
let phrase = format!("recovery phrase: {}", brain.phrase());
|
||||
result.map(|keypair| (keypair, Some(phrase)))
|
||||
} else {
|
||||
let result = Prefix::new(prefix, iterations).generate();
|
||||
result.map(|res| (res, None))
|
||||
};
|
||||
|
||||
Ok(res.map(Some).unwrap_or(None))
|
||||
}
|
||||
})?
|
||||
} else {
|
||||
return Ok(format!("{}", USAGE));
|
||||
};
|
||||
Ok(display(result, display_mode))
|
||||
} else if args.cmd_sign {
|
||||
let secret = args
|
||||
.arg_secret
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidSecretKey)?;
|
||||
let message = args
|
||||
.arg_message
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidMessage)?;
|
||||
let signature = sign(&secret, &message)?;
|
||||
Ok(format!("{}", signature))
|
||||
} else if args.cmd_verify {
|
||||
let signature = args
|
||||
.arg_signature
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidSignature)?;
|
||||
let message = args
|
||||
.arg_message
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidMessage)?;
|
||||
let ok = if args.cmd_public {
|
||||
let public = args
|
||||
.arg_public
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidPublicKey)?;
|
||||
verify_public(&public, &signature, &message)?
|
||||
} else if args.cmd_address {
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidAddress)?;
|
||||
verify_address(&address, &signature, &message)?
|
||||
} else {
|
||||
return Ok(format!("{}", USAGE));
|
||||
};
|
||||
Ok(format!("{}", ok))
|
||||
} else if args.cmd_recover {
|
||||
let display_mode = DisplayMode::new(&args);
|
||||
let known_phrase = args.arg_known_phrase;
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| EthkeyError::InvalidAddress)?;
|
||||
let (phrase, keypair) = in_threads(move || {
|
||||
let mut it =
|
||||
brain_recover::PhrasesIterator::from_known_phrase(&known_phrase, BRAIN_WORDS);
|
||||
move || {
|
||||
let mut i = 0;
|
||||
while let Some(phrase) = it.next() {
|
||||
i += 1;
|
||||
|
||||
let keypair = Brain::new(phrase.clone()).generate();
|
||||
if keypair.address() == address {
|
||||
return Ok(Some((phrase, keypair)));
|
||||
}
|
||||
|
||||
if i >= 1024 {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
Err(EthkeyError::Custom("Couldn't find any results.".into()))
|
||||
}
|
||||
})?;
|
||||
Ok(display((keypair, Some(phrase)), display_mode))
|
||||
} else {
|
||||
Ok(format!("{}", USAGE))
|
||||
};
|
||||
}
|
||||
|
||||
const BRAIN_WORDS: usize = 12;
|
||||
|
||||
fn validate_phrase(phrase: &str) -> String {
|
||||
match Brain::validate_phrase(phrase, BRAIN_WORDS) {
|
||||
Ok(()) => format!("The recovery phrase looks correct.\n"),
|
||||
Err(err) => format!("The recover phrase was not generated by Parity: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn in_threads<F, X, O>(prepare: F) -> Result<O, EthkeyError>
|
||||
where
|
||||
O: Send + 'static,
|
||||
X: Send + 'static,
|
||||
F: Fn() -> X,
|
||||
X: FnMut() -> Result<Option<O>, EthkeyError>,
|
||||
{
|
||||
let pool = threadpool::Builder::new().build();
|
||||
|
||||
let (tx, rx) = sync::mpsc::sync_channel(1);
|
||||
let is_done = sync::Arc::new(sync::atomic::AtomicBool::default());
|
||||
|
||||
for _ in 0..pool.max_count() {
|
||||
let is_done = is_done.clone();
|
||||
let tx = tx.clone();
|
||||
let mut task = prepare();
|
||||
pool.execute(move || {
|
||||
loop {
|
||||
if is_done.load(sync::atomic::Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
|
||||
let res = match task() {
|
||||
Ok(None) => continue,
|
||||
Ok(Some(v)) => Ok(v),
|
||||
Err(err) => Err(err),
|
||||
};
|
||||
|
||||
// We are interested only in the first response.
|
||||
let _ = tx.send(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Ok(solution) = rx.recv() {
|
||||
is_done.store(true, sync::atomic::Ordering::SeqCst);
|
||||
return solution;
|
||||
}
|
||||
|
||||
Err(EthkeyError::Custom("No results found.".into()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::execute;
|
||||
|
||||
#[test]
|
||||
fn info() {
|
||||
let command = vec![
|
||||
"ethkey",
|
||||
"info",
|
||||
"17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55",
|
||||
]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected =
|
||||
"secret: 17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55
|
||||
public: 689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124
|
||||
address: 26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn brain() {
|
||||
let command = vec!["ethkey", "info", "--brain", "this is sparta"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected =
|
||||
"The recover phrase was not generated by Parity: The word 'this' does not come from the dictionary.
|
||||
|
||||
secret: aa22b54c0cb43ee30a014afe5ef3664b1cde299feabca46cd3167a85a57c39f2
|
||||
public: c4c5398da6843632c123f543d714d2d2277716c11ff612b2a2f23c6bda4d6f0327c31cd58c55a9572c3cc141dade0c32747a13b7ef34c241b26c84adbb28fcf4
|
||||
address: 006e27b6a72e1f34c626762f3c4761547aff1421".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secret() {
|
||||
let command = vec!["ethkey", "info", "--brain", "this is sparta", "--secret"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected =
|
||||
"aa22b54c0cb43ee30a014afe5ef3664b1cde299feabca46cd3167a85a57c39f2".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public() {
|
||||
let command = vec!["ethkey", "info", "--brain", "this is sparta", "--public"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected = "c4c5398da6843632c123f543d714d2d2277716c11ff612b2a2f23c6bda4d6f0327c31cd58c55a9572c3cc141dade0c32747a13b7ef34c241b26c84adbb28fcf4".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address() {
|
||||
let command = vec!["ethkey", "info", "-b", "this is sparta", "--address"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected = "006e27b6a72e1f34c626762f3c4761547aff1421".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign() {
|
||||
let command = vec![
|
||||
"ethkey",
|
||||
"sign",
|
||||
"17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55",
|
||||
"bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987",
|
||||
]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected = "c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_valid_public() {
|
||||
let command = vec!["ethkey", "verify", "public", "689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124", "c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200", "bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected = "true".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_valid_address() {
|
||||
let command = vec!["ethkey", "verify", "address", "26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5", "c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200", "bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected = "true".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_invalid() {
|
||||
let command = vec!["ethkey", "verify", "public", "689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124", "c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200", "bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec986"]
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let expected = "false".to_owned();
|
||||
assert_eq!(execute(command).unwrap(), expected);
|
||||
}
|
||||
}
|
25
bin/ethstore/Cargo.toml
Normal file
25
bin/ethstore/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
description = "Parity Ethereum Key Management CLI"
|
||||
name = "ethstore-cli"
|
||||
version = "0.1.1"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
docopt = "1.0"
|
||||
env_logger = "0.5"
|
||||
num_cpus = "1.6"
|
||||
rustc-hex = "1.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
parking_lot = "0.11.1"
|
||||
ethstore = { path = "../../crates/accounts/ethstore" }
|
||||
dir = { path = '../../crates/util/dir' }
|
||||
panic_hook = { path = "../../crates/util/panic-hook" }
|
||||
|
||||
[[bin]]
|
||||
name = "ethstore"
|
||||
path = "src/main.rs"
|
||||
doc = false
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3.5"
|
66
bin/ethstore/src/crack.rs
Normal file
66
bin/ethstore/src/crack.rs
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use std::{cmp, collections::VecDeque, sync::Arc, thread};
|
||||
|
||||
use ethstore::{ethkey::Password, Error, PresaleWallet};
|
||||
use num_cpus;
|
||||
|
||||
pub fn run(passwords: VecDeque<Password>, wallet_path: &str) -> Result<(), Error> {
|
||||
let passwords = Arc::new(Mutex::new(passwords));
|
||||
|
||||
let mut handles = Vec::new();
|
||||
|
||||
for _ in 0..num_cpus::get() {
|
||||
let passwords = passwords.clone();
|
||||
let wallet = PresaleWallet::open(&wallet_path)?;
|
||||
handles.push(thread::spawn(move || {
|
||||
look_for_password(passwords, wallet);
|
||||
}));
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle
|
||||
.join()
|
||||
.map_err(|err| Error::Custom(format!("Error finishing thread: {:?}", err)))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn look_for_password(passwords: Arc<Mutex<VecDeque<Password>>>, wallet: PresaleWallet) {
|
||||
let mut counter = 0;
|
||||
while !passwords.lock().is_empty() {
|
||||
let package = {
|
||||
let mut passwords = passwords.lock();
|
||||
let len = passwords.len();
|
||||
passwords.split_off(cmp::min(len, 32))
|
||||
};
|
||||
for pass in package {
|
||||
counter += 1;
|
||||
match wallet.decrypt(&pass) {
|
||||
Ok(_) => {
|
||||
println!("Found password: {}", pass.as_str());
|
||||
passwords.lock().clear();
|
||||
return;
|
||||
}
|
||||
_ if counter % 100 == 0 => print!("."),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
363
bin/ethstore/src/main.rs
Normal file
363
bin/ethstore/src/main.rs
Normal file
@ -0,0 +1,363 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate dir;
|
||||
extern crate docopt;
|
||||
extern crate ethstore;
|
||||
extern crate num_cpus;
|
||||
extern crate panic_hook;
|
||||
extern crate parking_lot;
|
||||
extern crate rustc_hex;
|
||||
extern crate serde;
|
||||
|
||||
extern crate env_logger;
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use std::{collections::VecDeque, env, fmt, fs, io::Read, process};
|
||||
|
||||
use docopt::Docopt;
|
||||
use ethstore::{
|
||||
accounts_dir::{KeyDirectory, RootDiskDirectory},
|
||||
ethkey::{Address, Password},
|
||||
import_accounts, EthStore, PresaleWallet, SecretStore, SecretVaultRef, SimpleSecretStore,
|
||||
StoreAccountRef,
|
||||
};
|
||||
|
||||
mod crack;
|
||||
|
||||
pub const USAGE: &'static str = r#"
|
||||
Parity Ethereum key management tool.
|
||||
Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
|
||||
Usage:
|
||||
ethstore insert <secret> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore change-pwd <address> <old-pwd> <new-pwd> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore import [<password>] [--src DIR] [--dir DIR]
|
||||
ethstore import-wallet <path> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore find-wallet-pass <path> <password>
|
||||
ethstore remove <address> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore sign <address> <password> <message> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore public <address> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore list-vaults [--dir DIR]
|
||||
ethstore create-vault <vault> <password> [--dir DIR]
|
||||
ethstore change-vault-pwd <vault> <old-pwd> <new-pwd> [--dir DIR]
|
||||
ethstore move-to-vault <address> <vault> <password> [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD]
|
||||
ethstore move-from-vault <address> <vault> <password> [--dir DIR]
|
||||
ethstore [-h | --help]
|
||||
|
||||
Options:
|
||||
-h, --help Display this message and exit.
|
||||
--dir DIR Specify the secret store directory. It may be either
|
||||
parity, parity-(chain), geth, geth-test
|
||||
or a path [default: parity].
|
||||
--vault VAULT Specify vault to use in this operation.
|
||||
--vault-pwd VAULTPWD Specify vault password to use in this operation. Please note
|
||||
that this option is required when vault option is set.
|
||||
Otherwise it is ignored.
|
||||
--src DIR Specify import source. It may be either
|
||||
parity, parity-(chain), geth, geth-test
|
||||
or a path [default: geth].
|
||||
|
||||
Commands:
|
||||
insert Save account with password.
|
||||
change-pwd Change password.
|
||||
list List accounts.
|
||||
import Import accounts from src.
|
||||
import-wallet Import presale wallet.
|
||||
find-wallet-pass Tries to open a wallet with list of passwords given.
|
||||
remove Remove account.
|
||||
sign Sign message.
|
||||
public Displays public key for an address.
|
||||
list-vaults List vaults.
|
||||
create-vault Create new vault.
|
||||
change-vault-pwd Change vault password.
|
||||
move-to-vault Move account to vault from another vault/root directory.
|
||||
move-from-vault Move account to root directory from given vault.
|
||||
"#;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Args {
|
||||
cmd_insert: bool,
|
||||
cmd_change_pwd: bool,
|
||||
cmd_list: bool,
|
||||
cmd_import: bool,
|
||||
cmd_import_wallet: bool,
|
||||
cmd_find_wallet_pass: bool,
|
||||
cmd_remove: bool,
|
||||
cmd_sign: bool,
|
||||
cmd_public: bool,
|
||||
cmd_list_vaults: bool,
|
||||
cmd_create_vault: bool,
|
||||
cmd_change_vault_pwd: bool,
|
||||
cmd_move_to_vault: bool,
|
||||
cmd_move_from_vault: bool,
|
||||
arg_secret: String,
|
||||
arg_password: String,
|
||||
arg_old_pwd: String,
|
||||
arg_new_pwd: String,
|
||||
arg_address: String,
|
||||
arg_message: String,
|
||||
arg_path: String,
|
||||
arg_vault: String,
|
||||
flag_src: String,
|
||||
flag_dir: String,
|
||||
flag_vault: String,
|
||||
flag_vault_pwd: String,
|
||||
}
|
||||
|
||||
enum Error {
|
||||
Ethstore(ethstore::Error),
|
||||
Docopt(docopt::Error),
|
||||
}
|
||||
|
||||
impl From<ethstore::Error> for Error {
|
||||
fn from(err: ethstore::Error) -> Self {
|
||||
Error::Ethstore(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<docopt::Error> for Error {
|
||||
fn from(err: docopt::Error) -> Self {
|
||||
Error::Docopt(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::Ethstore(ref err) => fmt::Display::fmt(err, f),
|
||||
Error::Docopt(ref err) => fmt::Display::fmt(err, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
panic_hook::set_abort();
|
||||
if env::var("RUST_LOG").is_err() {
|
||||
env::set_var("RUST_LOG", "warn")
|
||||
}
|
||||
env_logger::try_init().expect("Logger initialized only once.");
|
||||
|
||||
match execute(env::args()) {
|
||||
Ok(result) => println!("{}", result),
|
||||
Err(Error::Docopt(ref e)) => e.exit(),
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_dir(location: &str, password: Option<Password>) -> Result<Box<dyn KeyDirectory>, Error> {
|
||||
let dir: RootDiskDirectory = match location {
|
||||
path if path.starts_with("parity") => {
|
||||
let chain = path.split('-').nth(1).unwrap_or("ethereum");
|
||||
let mut path = dir::default_data_pathbuf();
|
||||
path.push("keys");
|
||||
path.push(chain);
|
||||
RootDiskDirectory::create(path)?
|
||||
}
|
||||
path => RootDiskDirectory::create(path)?,
|
||||
};
|
||||
|
||||
Ok(Box::new(dir.with_password(password)))
|
||||
}
|
||||
|
||||
fn open_args_vault(store: &EthStore, args: &Args) -> Result<SecretVaultRef, Error> {
|
||||
if args.flag_vault.is_empty() {
|
||||
return Ok(SecretVaultRef::Root);
|
||||
}
|
||||
|
||||
let vault_pwd = load_password(&args.flag_vault_pwd)?;
|
||||
store.open_vault(&args.flag_vault, &vault_pwd)?;
|
||||
Ok(SecretVaultRef::Vault(args.flag_vault.clone()))
|
||||
}
|
||||
|
||||
fn open_args_vault_account(
|
||||
store: &EthStore,
|
||||
address: Address,
|
||||
args: &Args,
|
||||
) -> Result<StoreAccountRef, Error> {
|
||||
match open_args_vault(store, args)? {
|
||||
SecretVaultRef::Root => Ok(StoreAccountRef::root(address)),
|
||||
SecretVaultRef::Vault(name) => Ok(StoreAccountRef::vault(&name, address)),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_accounts(accounts: &[Address]) -> String {
|
||||
accounts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, a)| format!("{:2}: 0x{:x}", i, a))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn format_vaults(vaults: &[String]) -> String {
|
||||
vaults.join("\n")
|
||||
}
|
||||
|
||||
fn load_password(path: &str) -> Result<Password, Error> {
|
||||
let mut file = fs::File::open(path).map_err(|e| {
|
||||
ethstore::Error::Custom(format!("Error opening password file '{}': {}", path, e))
|
||||
})?;
|
||||
let mut password = String::new();
|
||||
file.read_to_string(&mut password).map_err(|e| {
|
||||
ethstore::Error::Custom(format!("Error reading password file '{}': {}", path, e))
|
||||
})?;
|
||||
// drop EOF
|
||||
let _ = password.pop();
|
||||
Ok(password.into())
|
||||
}
|
||||
|
||||
fn execute<S, I>(command: I) -> Result<String, Error>
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let args: Args = Docopt::new(USAGE).and_then(|d| d.argv(command).deserialize())?;
|
||||
|
||||
let store = EthStore::open(key_dir(&args.flag_dir, None)?)?;
|
||||
|
||||
return if args.cmd_insert {
|
||||
let secret = args
|
||||
.arg_secret
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidSecret)?;
|
||||
let password = load_password(&args.arg_password)?;
|
||||
let vault_ref = open_args_vault(&store, &args)?;
|
||||
let account_ref = store.insert_account(vault_ref, secret, &password)?;
|
||||
Ok(format!("0x{:x}", account_ref.address))
|
||||
} else if args.cmd_change_pwd {
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let old_pwd = load_password(&args.arg_old_pwd)?;
|
||||
let new_pwd = load_password(&args.arg_new_pwd)?;
|
||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||
let ok = store
|
||||
.change_password(&account_ref, &old_pwd, &new_pwd)
|
||||
.is_ok();
|
||||
Ok(format!("{}", ok))
|
||||
} else if args.cmd_list {
|
||||
let vault_ref = open_args_vault(&store, &args)?;
|
||||
let accounts = store.accounts()?;
|
||||
let accounts: Vec<_> = accounts
|
||||
.into_iter()
|
||||
.filter(|a| &a.vault == &vault_ref)
|
||||
.map(|a| a.address)
|
||||
.collect();
|
||||
Ok(format_accounts(&accounts))
|
||||
} else if args.cmd_import {
|
||||
let password = match args.arg_password.as_ref() {
|
||||
"" => None,
|
||||
_ => Some(load_password(&args.arg_password)?),
|
||||
};
|
||||
let src = key_dir(&args.flag_src, password)?;
|
||||
let dst = key_dir(&args.flag_dir, None)?;
|
||||
|
||||
let accounts = import_accounts(&*src, &*dst)?;
|
||||
Ok(format_accounts(&accounts))
|
||||
} else if args.cmd_import_wallet {
|
||||
let wallet = PresaleWallet::open(&args.arg_path)?;
|
||||
let password = load_password(&args.arg_password)?;
|
||||
let kp = wallet.decrypt(&password)?;
|
||||
let vault_ref = open_args_vault(&store, &args)?;
|
||||
let account_ref = store.insert_account(vault_ref, kp.secret().clone(), &password)?;
|
||||
Ok(format!("0x{:x}", account_ref.address))
|
||||
} else if args.cmd_find_wallet_pass {
|
||||
let passwords = load_password(&args.arg_password)?;
|
||||
let passwords = passwords
|
||||
.as_str()
|
||||
.lines()
|
||||
.map(|line| str::to_owned(line).into())
|
||||
.collect::<VecDeque<_>>();
|
||||
crack::run(passwords, &args.arg_path)?;
|
||||
Ok(format!("Password not found."))
|
||||
} else if args.cmd_remove {
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let password = load_password(&args.arg_password)?;
|
||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||
let ok = store.remove_account(&account_ref, &password).is_ok();
|
||||
Ok(format!("{}", ok))
|
||||
} else if args.cmd_sign {
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let message = args
|
||||
.arg_message
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidMessage)?;
|
||||
let password = load_password(&args.arg_password)?;
|
||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||
let signature = store.sign(&account_ref, &password, &message)?;
|
||||
Ok(format!("0x{}", signature))
|
||||
} else if args.cmd_public {
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let password = load_password(&args.arg_password)?;
|
||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||
let public = store.public(&account_ref, &password)?;
|
||||
Ok(format!("0x{:x}", public))
|
||||
} else if args.cmd_list_vaults {
|
||||
let vaults = store.list_vaults()?;
|
||||
Ok(format_vaults(&vaults))
|
||||
} else if args.cmd_create_vault {
|
||||
let password = load_password(&args.arg_password)?;
|
||||
store.create_vault(&args.arg_vault, &password)?;
|
||||
Ok("OK".to_owned())
|
||||
} else if args.cmd_change_vault_pwd {
|
||||
let old_pwd = load_password(&args.arg_old_pwd)?;
|
||||
let new_pwd = load_password(&args.arg_new_pwd)?;
|
||||
store.open_vault(&args.arg_vault, &old_pwd)?;
|
||||
store.change_vault_password(&args.arg_vault, &new_pwd)?;
|
||||
Ok("OK".to_owned())
|
||||
} else if args.cmd_move_to_vault {
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let password = load_password(&args.arg_password)?;
|
||||
let account_ref = open_args_vault_account(&store, address, &args)?;
|
||||
store.open_vault(&args.arg_vault, &password)?;
|
||||
store.change_account_vault(SecretVaultRef::Vault(args.arg_vault), account_ref)?;
|
||||
Ok("OK".to_owned())
|
||||
} else if args.cmd_move_from_vault {
|
||||
let address = args
|
||||
.arg_address
|
||||
.parse()
|
||||
.map_err(|_| ethstore::Error::InvalidAccount)?;
|
||||
let password = load_password(&args.arg_password)?;
|
||||
store.open_vault(&args.arg_vault, &password)?;
|
||||
store.change_account_vault(
|
||||
SecretVaultRef::Root,
|
||||
StoreAccountRef::vault(&args.arg_vault, address),
|
||||
)?;
|
||||
Ok("OK".to_owned())
|
||||
} else {
|
||||
Ok(format!("{}", USAGE))
|
||||
};
|
||||
}
|
33
bin/evmbin/Cargo.toml
Normal file
33
bin/evmbin/Cargo.toml
Normal file
@ -0,0 +1,33 @@
|
||||
[package]
|
||||
description = "Parity EVM Implementation"
|
||||
name = "evmbin"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[[bin]]
|
||||
name = "openethereum-evm"
|
||||
path = "./src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
common-types = { path = "../../crates/ethcore/types", features = ["test-helpers"] }
|
||||
docopt = "1.0"
|
||||
env_logger = "0.5"
|
||||
ethcore = { path = "../../crates/ethcore", features = ["test-helpers", "json-tests", "to-pod-full"] }
|
||||
ethereum-types = "0.9.2"
|
||||
ethjson = { path = "../../crates/ethjson" }
|
||||
evm = { path = "../../crates/vm/evm" }
|
||||
panic_hook = { path = "../../crates/util/panic-hook" }
|
||||
parity-bytes = "0.1"
|
||||
rustc-hex = "1.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
vm = { path = "../../crates/vm/vm" }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3.0"
|
||||
pretty_assertions = "0.1"
|
||||
tempdir = "0.3"
|
||||
|
||||
[features]
|
||||
evm-debug = ["ethcore/evm-debug-tests"]
|
60
bin/evmbin/README.md
Normal file
60
bin/evmbin/README.md
Normal file
@ -0,0 +1,60 @@
|
||||
## evmbin
|
||||
|
||||
EVM implementation for OpenEthereum.
|
||||
|
||||
### Usage
|
||||
|
||||
```
|
||||
EVM implementation for Parity.
|
||||
Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
|
||||
Usage:
|
||||
openethereum-evm state-test <file> [--json --std-json --std-dump-json --only NAME --chain CHAIN --std-out-only --std-err-only --omit-storage-output --omit-memory-output]
|
||||
openethereum-evm stats [options]
|
||||
openethereum-evm stats-jsontests-vm <file>
|
||||
openethereum-evm [options]
|
||||
openethereum-evm [-h | --help]
|
||||
|
||||
Commands:
|
||||
state-test Run a state test from a json file.
|
||||
stats Execute EVM runtime code and return the statistics.
|
||||
stats-jsontests-vm Execute standard json-tests format VMTests and return
|
||||
timing statistics in tsv format.
|
||||
|
||||
Transaction options:
|
||||
--code CODE Contract code as hex (without 0x).
|
||||
--to ADDRESS Recipient address (without 0x).
|
||||
--from ADDRESS Sender address (without 0x).
|
||||
--input DATA Input data as hex (without 0x).
|
||||
--gas GAS Supplied gas as hex (without 0x).
|
||||
--gas-price WEI Supplied gas price as hex (without 0x).
|
||||
|
||||
State test options:
|
||||
--chain CHAIN Run only from specific chain name (i.e. one of EIP150, EIP158,
|
||||
Frontier, Homestead, Byzantium, Constantinople,
|
||||
ConstantinopleFix, Istanbul, EIP158ToByzantiumAt5, FrontierToHomesteadAt5,
|
||||
HomesteadToDaoAt5, HomesteadToEIP150At5, Berlin, Yolo3).
|
||||
--only NAME Runs only a single test matching the name.
|
||||
|
||||
General options:
|
||||
--json Display verbose results in JSON.
|
||||
--std-json Display results in standardized JSON format.
|
||||
--std-err-only With --std-json redirect to err output only.
|
||||
--std-out-only With --std-json redirect to out output only.
|
||||
--omit-storage-output With --std-json omit storage output.
|
||||
--omit-memory-output With --std-json omit memory output.
|
||||
--std-dump-json Display results in standardized JSON format
|
||||
with additional state dump.
|
||||
|
||||
Display result state dump in standardized JSON format.
|
||||
--chain CHAIN Chain spec file path.
|
||||
-h, --help Display this message and exit.
|
||||
```
|
||||
|
||||
## OpenEthereum toolchain
|
||||
_This project is a part of the OpenEthereum toolchain._
|
||||
|
||||
- [evmbin](https://github.com/openethereum/openethereum/blob/master/evmbin/) - EVM implementation for OpenEthereum
|
||||
- [ethabi](https://github.com/openethereum/ethabi) - OpenEthereum function calls encoding.
|
||||
- [ethstore](https://github.com/openethereum/openethereum/blob/master/accounts/ethstore) - OpenEthereum key management.
|
||||
- [ethkey](https://github.com/openethereum/openethereum/blob/master/accounts/ethkey) - OpenEthereum keys generator.
|
98
bin/evmbin/benches/mod.rs
Normal file
98
bin/evmbin/benches/mod.rs
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! benchmarking for EVM
|
||||
//! should be started with:
|
||||
//! ```bash
|
||||
//! cargo bench
|
||||
//! ```
|
||||
|
||||
#[macro_use]
|
||||
extern crate criterion;
|
||||
extern crate ethcore;
|
||||
extern crate ethereum_types;
|
||||
extern crate evm;
|
||||
extern crate rustc_hex;
|
||||
extern crate vm;
|
||||
|
||||
use criterion::{black_box, Criterion};
|
||||
use std::sync::Arc;
|
||||
|
||||
use ethereum_types::U256;
|
||||
use evm::Factory;
|
||||
use rustc_hex::FromHex;
|
||||
use vm::{tests::FakeExt, ActionParams, Ext};
|
||||
|
||||
criterion_group!(
|
||||
evmbin,
|
||||
bench_simple_loop_usize,
|
||||
bench_simple_loop_u256,
|
||||
bench_rng_usize,
|
||||
bench_rng_u256
|
||||
);
|
||||
criterion_main!(evmbin);
|
||||
|
||||
fn bench_simple_loop_usize(c: &mut Criterion) {
|
||||
simple_loop(U256::from(::std::usize::MAX), c, "simple_loop_usize")
|
||||
}
|
||||
|
||||
fn bench_simple_loop_u256(c: &mut Criterion) {
|
||||
simple_loop(!U256::zero(), c, "simple_loop_u256")
|
||||
}
|
||||
|
||||
fn simple_loop(gas: U256, c: &mut Criterion, bench_id: &str) {
|
||||
let code = black_box(
|
||||
"606060405260005b620042408112156019575b6001016007565b600081905550600680602b6000396000f3606060405200".from_hex().unwrap()
|
||||
);
|
||||
|
||||
c.bench_function(bench_id, move |b| {
|
||||
b.iter(|| {
|
||||
let mut params = ActionParams::default();
|
||||
params.gas = gas;
|
||||
params.code = Some(Arc::new(code.clone()));
|
||||
|
||||
let mut ext = FakeExt::new();
|
||||
let evm = Factory::default().create(params, ext.schedule(), ext.depth());
|
||||
let _ = evm.exec(&mut ext);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_rng_usize(c: &mut Criterion) {
|
||||
rng(U256::from(::std::usize::MAX), c, "rng_usize")
|
||||
}
|
||||
|
||||
fn bench_rng_u256(c: &mut Criterion) {
|
||||
rng(!U256::zero(), c, "rng_u256")
|
||||
}
|
||||
|
||||
fn rng(gas: U256, c: &mut Criterion, bench_id: &str) {
|
||||
let code = black_box(
|
||||
"6060604052600360056007600b60005b62004240811215607f5767ffe7649d5eca84179490940267f47ed85c4b9a6379019367f8e5dd9a5c994bba9390930267f91d87e4b8b74e55019267ff97f6f3b29cda529290920267f393ada8dd75c938019167fe8d437c45bb3735830267f47d9a7b5428ffec019150600101600f565b838518831882186000555050505050600680609a6000396000f3606060405200".from_hex().unwrap()
|
||||
);
|
||||
|
||||
c.bench_function(bench_id, move |b| {
|
||||
b.iter(|| {
|
||||
let mut params = ActionParams::default();
|
||||
params.gas = gas;
|
||||
params.code = Some(Arc::new(code.clone()));
|
||||
|
||||
let mut ext = FakeExt::new();
|
||||
let evm = Factory::default().create(params, ext.schedule(), ext.depth());
|
||||
let _ = evm.exec(&mut ext);
|
||||
})
|
||||
});
|
||||
}
|
38
bin/evmbin/res/testchain.json
Normal file
38
bin/evmbin/res/testchain.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "lab",
|
||||
"engine": {
|
||||
"Ethash": {
|
||||
"params": {
|
||||
"minimumDifficulty": "0x1",
|
||||
"difficultyBoundDivisor": "0x800"
|
||||
}
|
||||
}
|
||||
},
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000020": {
|
||||
"nonce": "0x0",
|
||||
"balance": "0x64",
|
||||
"code": "0x62aaaaaa60aa60aa5060aa60aa60aa60aa60aa60aa"
|
||||
}
|
||||
},
|
||||
"params":{
|
||||
"networkID": "0x42",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1",
|
||||
"gasLimitBoundDivisor": "0x400"
|
||||
},
|
||||
"genesis": {
|
||||
"gasLimit": "0x8000000",
|
||||
"seal": {
|
||||
"ethereum": {
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"nonce": "0x0000000000000042"
|
||||
}
|
||||
},
|
||||
"difficulty": "0x400",
|
||||
"extraData": "0x0",
|
||||
"author": "0x3333333333333333333333333333333333333333",
|
||||
"timestamp": "0x0",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
}
|
40
bin/evmbin/src/display/config.rs
Normal file
40
bin/evmbin/src/display/config.rs
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Config used by display informants
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug)]
|
||||
pub struct Config {
|
||||
omit_storage_output: bool,
|
||||
omit_memory_output: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(omit_storage_output: bool, omit_memory_output: bool) -> Config {
|
||||
Config {
|
||||
omit_storage_output,
|
||||
omit_memory_output,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn omit_storage_output(&self) -> bool {
|
||||
self.omit_storage_output
|
||||
}
|
||||
|
||||
pub fn omit_memory_output(&self) -> bool {
|
||||
self.omit_memory_output
|
||||
}
|
||||
}
|
425
bin/evmbin/src/display/json.rs
Normal file
425
bin/evmbin/src/display/json.rs
Normal file
@ -0,0 +1,425 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! JSON VM output.
|
||||
|
||||
use std::{collections::HashMap, mem};
|
||||
|
||||
use super::config::Config;
|
||||
use bytes::ToPretty;
|
||||
use display;
|
||||
use ethcore::trace;
|
||||
use ethereum_types::{BigEndianHash, H256, U256};
|
||||
use info as vm;
|
||||
|
||||
/// JSON formatting informant.
|
||||
#[derive(Default)]
|
||||
pub struct Informant {
|
||||
code: Vec<u8>,
|
||||
depth: usize,
|
||||
pc: usize,
|
||||
instruction: u8,
|
||||
gas_cost: U256,
|
||||
gas_used: U256,
|
||||
mem_written: Option<(usize, usize)>,
|
||||
store_written: Option<(U256, U256)>,
|
||||
stack: Vec<U256>,
|
||||
memory: Vec<u8>,
|
||||
storage: HashMap<H256, H256>,
|
||||
traces: Vec<String>,
|
||||
subtraces: Vec<String>,
|
||||
subinfos: Vec<Informant>,
|
||||
subdepth: usize,
|
||||
unmatched: bool,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl Informant {
|
||||
pub fn new(config: Config) -> Informant {
|
||||
let mut def = Informant::default();
|
||||
def.config = config;
|
||||
def
|
||||
}
|
||||
|
||||
fn with_informant_in_depth<F: Fn(&mut Informant)>(
|
||||
informant: &mut Informant,
|
||||
depth: usize,
|
||||
f: F,
|
||||
) {
|
||||
if depth == 0 {
|
||||
f(informant);
|
||||
} else {
|
||||
Self::with_informant_in_depth(
|
||||
informant
|
||||
.subinfos
|
||||
.last_mut()
|
||||
.expect("prepare/done_trace are not balanced"),
|
||||
depth - 1,
|
||||
f,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn informant_trace(informant: &Informant, gas_used: U256) -> String {
|
||||
let memory = if informant.config.omit_memory_output() {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("0x{}", informant.memory.to_hex())
|
||||
};
|
||||
let storage = if informant.config.omit_storage_output() {
|
||||
None
|
||||
} else {
|
||||
Some(&informant.storage)
|
||||
};
|
||||
let info = ::evm::Instruction::from_u8(informant.instruction).map(|i| i.info());
|
||||
json!({
|
||||
"pc": informant.pc,
|
||||
"op": informant.instruction,
|
||||
"opName": info.map(|i| i.name).unwrap_or(""),
|
||||
"gas": format!("{:#x}", gas_used.saturating_add(informant.gas_cost)),
|
||||
"gasCost": format!("{:#x}", informant.gas_cost),
|
||||
"memory": memory,
|
||||
"stack": informant.stack,
|
||||
"storage": storage,
|
||||
"depth": informant.depth,
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl vm::Informant for Informant {
|
||||
type Sink = Config;
|
||||
|
||||
fn before_test(&mut self, name: &str, action: &str) {
|
||||
println!("{}", json!({"action": action, "test": name}));
|
||||
}
|
||||
|
||||
fn set_gas(&mut self, gas: U256) {
|
||||
self.gas_used = gas;
|
||||
}
|
||||
|
||||
fn clone_sink(&self) -> Self::Sink {
|
||||
self.config
|
||||
}
|
||||
|
||||
fn finish(result: vm::RunResult<Self::Output>, config: &mut Self::Sink) {
|
||||
match result {
|
||||
Ok(success) => {
|
||||
for trace in success.traces.unwrap_or_else(Vec::new) {
|
||||
println!("{}", trace);
|
||||
}
|
||||
|
||||
let success_msg = json!({
|
||||
"output": format!("0x{}", success.output.to_hex()),
|
||||
"gasUsed": format!("{:#x}", success.gas_used),
|
||||
"time": display::as_micros(&success.time),
|
||||
});
|
||||
|
||||
println!("{}", success_msg)
|
||||
}
|
||||
Err(failure) => {
|
||||
if !config.omit_storage_output() {
|
||||
for trace in failure.traces.unwrap_or_else(Vec::new) {
|
||||
println!("{}", trace);
|
||||
}
|
||||
}
|
||||
|
||||
let failure_msg = json!({
|
||||
"error": &failure.error.to_string(),
|
||||
"gasUsed": format!("{:#x}", failure.gas_used),
|
||||
"time": display::as_micros(&failure.time),
|
||||
});
|
||||
|
||||
println!("{}", failure_msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl trace::VMTracer for Informant {
|
||||
type Output = Vec<String>;
|
||||
|
||||
fn trace_next_instruction(&mut self, pc: usize, instruction: u8, _current_gas: U256) -> bool {
|
||||
let subdepth = self.subdepth;
|
||||
Self::with_informant_in_depth(self, subdepth, |informant: &mut Informant| {
|
||||
informant.pc = pc;
|
||||
informant.instruction = instruction;
|
||||
informant.unmatched = true;
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
fn trace_prepare_execute(
|
||||
&mut self,
|
||||
pc: usize,
|
||||
instruction: u8,
|
||||
gas_cost: U256,
|
||||
mem_written: Option<(usize, usize)>,
|
||||
store_written: Option<(U256, U256)>,
|
||||
) {
|
||||
let subdepth = self.subdepth;
|
||||
Self::with_informant_in_depth(self, subdepth, |informant: &mut Informant| {
|
||||
informant.pc = pc;
|
||||
informant.instruction = instruction;
|
||||
informant.gas_cost = gas_cost;
|
||||
informant.mem_written = mem_written;
|
||||
informant.store_written = store_written;
|
||||
});
|
||||
}
|
||||
|
||||
fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem: &[u8]) {
|
||||
let subdepth = self.subdepth;
|
||||
Self::with_informant_in_depth(self, subdepth, |informant: &mut Informant| {
|
||||
let store_diff = informant.store_written.clone();
|
||||
let info = ::evm::Instruction::from_u8(informant.instruction).map(|i| i.info());
|
||||
|
||||
let trace = Self::informant_trace(informant, gas_used);
|
||||
informant.traces.push(trace);
|
||||
|
||||
informant.unmatched = false;
|
||||
informant.gas_used = gas_used;
|
||||
|
||||
let len = informant.stack.len();
|
||||
let info_args = info.map(|i| i.args).unwrap_or(0);
|
||||
informant
|
||||
.stack
|
||||
.truncate(if len > info_args { len - info_args } else { 0 });
|
||||
informant.stack.extend_from_slice(stack_push);
|
||||
|
||||
// TODO [ToDr] Align memory?
|
||||
if let Some((pos, size)) = informant.mem_written.clone() {
|
||||
if informant.memory.len() < (pos + size) {
|
||||
informant.memory.resize(pos + size, 0);
|
||||
}
|
||||
informant.memory[pos..(pos + size)].copy_from_slice(&mem[pos..(pos + size)]);
|
||||
}
|
||||
|
||||
if let Some((pos, val)) = store_diff {
|
||||
informant.storage.insert(
|
||||
BigEndianHash::from_uint(&pos),
|
||||
BigEndianHash::from_uint(&val),
|
||||
);
|
||||
}
|
||||
|
||||
if !informant.subtraces.is_empty() {
|
||||
informant
|
||||
.traces
|
||||
.extend(mem::replace(&mut informant.subtraces, vec![]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn prepare_subtrace(&mut self, code: &[u8]) {
|
||||
let subdepth = self.subdepth;
|
||||
Self::with_informant_in_depth(self, subdepth, |informant: &mut Informant| {
|
||||
let mut vm = Informant::default();
|
||||
vm.config = informant.config;
|
||||
vm.depth = informant.depth + 1;
|
||||
vm.code = code.to_vec();
|
||||
vm.gas_used = informant.gas_used;
|
||||
informant.subinfos.push(vm);
|
||||
});
|
||||
self.subdepth += 1;
|
||||
}
|
||||
|
||||
fn done_subtrace(&mut self) {
|
||||
self.subdepth -= 1;
|
||||
let subdepth = self.subdepth;
|
||||
Self::with_informant_in_depth(self, subdepth, |informant: &mut Informant| {
|
||||
if let Some(subtraces) = informant
|
||||
.subinfos
|
||||
.pop()
|
||||
.expect("prepare/done_subtrace are not balanced")
|
||||
.drain()
|
||||
{
|
||||
informant.subtraces.extend(subtraces);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn drain(mut self) -> Option<Self::Output> {
|
||||
if self.unmatched {
|
||||
// print last line with final state:
|
||||
self.gas_cost = 0.into();
|
||||
let gas_used = self.gas_used;
|
||||
let subdepth = self.subdepth;
|
||||
|
||||
Self::with_informant_in_depth(&mut self, subdepth, |informant: &mut Informant| {
|
||||
let trace = Self::informant_trace(informant, gas_used);
|
||||
informant.traces.push(trace);
|
||||
});
|
||||
} else if !self.subtraces.is_empty() {
|
||||
self.traces
|
||||
.extend(mem::replace(&mut self.subtraces, vec![]));
|
||||
}
|
||||
Some(self.traces)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use info::tests::run_test;
|
||||
use serde_json;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct TestTrace {
|
||||
pc: usize,
|
||||
#[serde(rename = "op")]
|
||||
instruction: u8,
|
||||
op_name: String,
|
||||
#[serde(rename = "gas")]
|
||||
gas_used: U256,
|
||||
gas_cost: U256,
|
||||
memory: String,
|
||||
stack: Vec<U256>,
|
||||
storage: Option<HashMap<H256, H256>>,
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
fn assert_traces_eq(a: &[String], b: &[String]) {
|
||||
let mut ita = a.iter();
|
||||
let mut itb = b.iter();
|
||||
|
||||
loop {
|
||||
match (ita.next(), itb.next()) {
|
||||
(Some(a), Some(b)) => {
|
||||
// Compare both without worrying about the order of the fields
|
||||
let actual: TestTrace = serde_json::from_str(a).unwrap();
|
||||
let expected: TestTrace = serde_json::from_str(b).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
println!("{}", a);
|
||||
}
|
||||
(None, None) => return,
|
||||
e => {
|
||||
panic!("Traces mismatch: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_json(traces: Option<Vec<String>>, expected: &str) {
|
||||
let expected = expected
|
||||
.split("\n")
|
||||
.map(|x| x.trim())
|
||||
.map(|x| x.to_owned())
|
||||
.filter(|x| !x.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
assert_traces_eq(&traces.unwrap(), &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_trace_failure() {
|
||||
run_test(
|
||||
Informant::default(),
|
||||
&compare_json,
|
||||
"60F8d6",
|
||||
0xffff,
|
||||
r#"
|
||||
{"pc":0,"op":96,"opName":"PUSH1","gas":"0xffff","gasCost":"0x3","memory":"0x","stack":[],"storage":{},"depth":1}
|
||||
{"pc":2,"op":214,"opName":"","gas":"0xfffc","gasCost":"0x0","memory":"0x","stack":["0xf8"],"storage":{},"depth":1}
|
||||
"#,
|
||||
);
|
||||
|
||||
run_test(
|
||||
Informant::default(),
|
||||
&compare_json,
|
||||
"F8d6",
|
||||
0xffff,
|
||||
r#"
|
||||
{"pc":0,"op":248,"opName":"","gas":"0xffff","gasCost":"0x0","memory":"0x","stack":[],"storage":{},"depth":1}
|
||||
"#,
|
||||
);
|
||||
|
||||
run_test(
|
||||
Informant::default(),
|
||||
&compare_json,
|
||||
"5A51",
|
||||
0xfffff,
|
||||
r#"
|
||||
{"depth":1,"gas":"0xfffff","gasCost":"0x2","memory":"0x","op":90,"opName":"GAS","pc":0,"stack":[],"storage":{}}
|
||||
{"depth":1,"gas":"0xffffd","gasCost":"0x0","memory":"0x","op":81,"opName":"MLOAD","pc":1,"stack":["0xffffd"],"storage":{}}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_trace_create_correctly() {
|
||||
run_test(
|
||||
Informant::default(),
|
||||
&compare_json,
|
||||
"32343434345830f138343438323439f0",
|
||||
0xffff,
|
||||
r#"
|
||||
{"pc":0,"op":50,"opName":"ORIGIN","gas":"0xffff","gasCost":"0x2","memory":"0x","stack":[],"storage":{},"depth":1}
|
||||
{"pc":1,"op":52,"opName":"CALLVALUE","gas":"0xfffd","gasCost":"0x2","memory":"0x","stack":["0x0"],"storage":{},"depth":1}
|
||||
{"pc":2,"op":52,"opName":"CALLVALUE","gas":"0xfffb","gasCost":"0x2","memory":"0x","stack":["0x0","0x0"],"storage":{},"depth":1}
|
||||
{"pc":3,"op":52,"opName":"CALLVALUE","gas":"0xfff9","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0"],"storage":{},"depth":1}
|
||||
{"pc":4,"op":52,"opName":"CALLVALUE","gas":"0xfff7","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0"],"storage":{},"depth":1}
|
||||
{"pc":5,"op":88,"opName":"PC","gas":"0xfff5","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{},"depth":1}
|
||||
{"pc":6,"op":48,"opName":"ADDRESS","gas":"0xfff3","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{},"depth":1}
|
||||
{"pc":7,"op":241,"opName":"CALL","gas":"0xfff1","gasCost":"0x61d0","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5","0x0"],"storage":{},"depth":1}
|
||||
{"pc":8,"op":56,"opName":"CODESIZE","gas":"0x9e21","gasCost":"0x2","memory":"0x","stack":["0x1"],"storage":{},"depth":1}
|
||||
{"pc":9,"op":52,"opName":"CALLVALUE","gas":"0x9e1f","gasCost":"0x2","memory":"0x","stack":["0x1","0x10"],"storage":{},"depth":1}
|
||||
{"pc":10,"op":52,"opName":"CALLVALUE","gas":"0x9e1d","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0"],"storage":{},"depth":1}
|
||||
{"pc":11,"op":56,"opName":"CODESIZE","gas":"0x9e1b","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0","0x0"],"storage":{},"depth":1}
|
||||
{"pc":12,"op":50,"opName":"ORIGIN","gas":"0x9e19","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0","0x0","0x10"],"storage":{},"depth":1}
|
||||
{"pc":13,"op":52,"opName":"CALLVALUE","gas":"0x9e17","gasCost":"0x2","memory":"0x","stack":["0x1","0x10","0x0","0x0","0x10","0x0"],"storage":{},"depth":1}
|
||||
{"pc":14,"op":57,"opName":"CODECOPY","gas":"0x9e15","gasCost":"0x9","memory":"0x","stack":["0x1","0x10","0x0","0x0","0x10","0x0","0x0"],"storage":{},"depth":1}
|
||||
{"pc":15,"op":240,"opName":"CREATE","gas":"0x9e0c","gasCost":"0x9e0c","memory":"0x32343434345830f138343438323439f0","stack":["0x1","0x10","0x0","0x0"],"storage":{},"depth":1}
|
||||
{"pc":0,"op":50,"opName":"ORIGIN","gas":"0x210c","gasCost":"0x2","memory":"0x","stack":[],"storage":{},"depth":2}
|
||||
{"pc":1,"op":52,"opName":"CALLVALUE","gas":"0x210a","gasCost":"0x2","memory":"0x","stack":["0x0"],"storage":{},"depth":2}
|
||||
{"pc":2,"op":52,"opName":"CALLVALUE","gas":"0x2108","gasCost":"0x2","memory":"0x","stack":["0x0","0x0"],"storage":{},"depth":2}
|
||||
{"pc":3,"op":52,"opName":"CALLVALUE","gas":"0x2106","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0"],"storage":{},"depth":2}
|
||||
{"pc":4,"op":52,"opName":"CALLVALUE","gas":"0x2104","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0"],"storage":{},"depth":2}
|
||||
{"pc":5,"op":88,"opName":"PC","gas":"0x2102","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{},"depth":2}
|
||||
{"pc":6,"op":48,"opName":"ADDRESS","gas":"0x2100","gasCost":"0x2","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{},"depth":2}
|
||||
{"pc":7,"op":241,"opName":"CALL","gas":"0x20fe","gasCost":"0x0","memory":"0x","stack":["0x0","0x0","0x0","0x0","0x0","0x5","0xbd770416a3345f91e4b34576cb804a576fa48eb1"],"storage":{},"depth":2}
|
||||
"#,
|
||||
);
|
||||
|
||||
run_test(
|
||||
Informant::default(),
|
||||
&compare_json,
|
||||
"3260D85554",
|
||||
0xffff,
|
||||
r#"
|
||||
{"pc":0,"op":50,"opName":"ORIGIN","gas":"0xffff","gasCost":"0x2","memory":"0x","stack":[],"storage":{},"depth":1}
|
||||
{"pc":1,"op":96,"opName":"PUSH1","gas":"0xfffd","gasCost":"0x3","memory":"0x","stack":["0x0"],"storage":{},"depth":1}
|
||||
{"pc":3,"op":85,"opName":"SSTORE","gas":"0xfffa","gasCost":"0x1388","memory":"0x","stack":["0x0","0xd8"],"storage":{},"depth":1}
|
||||
{"pc":4,"op":84,"opName":"SLOAD","gas":"0xec72","gasCost":"0x0","memory":"0x","stack":[],"storage":{"0x00000000000000000000000000000000000000000000000000000000000000d8":"0x0000000000000000000000000000000000000000000000000000000000000000"},"depth":1}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_omit_storage_and_memory_flag() {
|
||||
// should omit storage
|
||||
run_test(
|
||||
Informant::new(Config::new(true, true)),
|
||||
&compare_json,
|
||||
"3260D85554",
|
||||
0xffff,
|
||||
r#"
|
||||
{"pc":0,"op":50,"opName":"ORIGIN","gas":"0xffff","gasCost":"0x2","memory":"","stack":[],"storage":null,"depth":1}
|
||||
{"pc":1,"op":96,"opName":"PUSH1","gas":"0xfffd","gasCost":"0x3","memory":"","stack":["0x0"],"storage":null,"depth":1}
|
||||
{"pc":3,"op":85,"opName":"SSTORE","gas":"0xfffa","gasCost":"0x1388","memory":"","stack":["0x0","0xd8"],"storage":null,"depth":1}
|
||||
{"pc":4,"op":84,"opName":"SLOAD","gas":"0xec72","gasCost":"0x0","memory":"","stack":[],"storage":null,"depth":1}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
34
bin/evmbin/src/display/mod.rs
Normal file
34
bin/evmbin/src/display/mod.rs
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! VM Output display utils.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod config;
|
||||
pub mod json;
|
||||
pub mod simple;
|
||||
pub mod std_json;
|
||||
|
||||
/// Formats duration into human readable format.
|
||||
pub fn format_time(time: &Duration) -> String {
|
||||
format!("{}.{:.9}s", time.as_secs(), time.subsec_nanos())
|
||||
}
|
||||
|
||||
/// Formats the time as microseconds.
|
||||
pub fn as_micros(time: &Duration) -> u64 {
|
||||
time.as_secs() * 1_000_000 + time.subsec_nanos() as u64 / 1_000
|
||||
}
|
74
bin/evmbin/src/display/simple.rs
Normal file
74
bin/evmbin/src/display/simple.rs
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Simple VM output.
|
||||
|
||||
use super::config::Config;
|
||||
use bytes::ToPretty;
|
||||
use ethcore::trace;
|
||||
|
||||
use display;
|
||||
use info as vm;
|
||||
|
||||
/// Simple formatting informant.
|
||||
#[derive(Default)]
|
||||
pub struct Informant {
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl Informant {
|
||||
pub fn new(config: Config) -> Informant {
|
||||
Informant { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl vm::Informant for Informant {
|
||||
type Sink = Config;
|
||||
|
||||
fn before_test(&mut self, name: &str, action: &str) {
|
||||
println!("Test: {} ({})", name, action);
|
||||
}
|
||||
|
||||
fn clone_sink(&self) -> Self::Sink {
|
||||
self.config
|
||||
}
|
||||
|
||||
fn finish(result: vm::RunResult<Self::Output>, _sink: &mut Self::Sink) {
|
||||
match result {
|
||||
Ok(success) => {
|
||||
println!("Output: 0x{}", success.output.to_hex());
|
||||
println!("Gas used: {:x}", success.gas_used);
|
||||
println!("Time: {}", display::format_time(&success.time));
|
||||
}
|
||||
Err(failure) => {
|
||||
println!("Error: {}", failure.error);
|
||||
println!("Time: {}", display::format_time(&failure.time));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl trace::VMTracer for Informant {
|
||||
type Output = ();
|
||||
|
||||
fn prepare_subtrace(&mut self, _code: &[u8]) {
|
||||
Default::default()
|
||||
}
|
||||
fn done_subtrace(&mut self) {}
|
||||
fn drain(self) -> Option<()> {
|
||||
None
|
||||
}
|
||||
}
|
413
bin/evmbin/src/display/std_json.rs
Normal file
413
bin/evmbin/src/display/std_json.rs
Normal file
@ -0,0 +1,413 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Standardized JSON VM output.
|
||||
|
||||
use std::{collections::HashMap, io};
|
||||
|
||||
use super::config::Config;
|
||||
use bytes::ToPretty;
|
||||
use display;
|
||||
use ethcore::{pod_state, trace};
|
||||
use ethereum_types::{BigEndianHash, H256, U256};
|
||||
use info as vm;
|
||||
|
||||
pub trait Writer: io::Write + Send + Sized {
|
||||
fn clone(&self) -> Self;
|
||||
fn default() -> Self;
|
||||
}
|
||||
|
||||
impl Writer for io::Stdout {
|
||||
fn clone(&self) -> Self {
|
||||
io::stdout()
|
||||
}
|
||||
|
||||
fn default() -> Self {
|
||||
io::stdout()
|
||||
}
|
||||
}
|
||||
|
||||
impl Writer for io::Stderr {
|
||||
fn clone(&self) -> Self {
|
||||
io::stderr()
|
||||
}
|
||||
|
||||
fn default() -> Self {
|
||||
io::stderr()
|
||||
}
|
||||
}
|
||||
|
||||
/// JSON formatting informant.
|
||||
pub struct Informant<Trace, Out> {
|
||||
code: Vec<u8>,
|
||||
instruction: u8,
|
||||
depth: usize,
|
||||
stack: Vec<U256>,
|
||||
storage: HashMap<H256, H256>,
|
||||
subinfos: Vec<Informant<Trace, Out>>,
|
||||
subdepth: usize,
|
||||
trace_sink: Trace,
|
||||
out_sink: Out,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl Default for Informant<io::Stderr, io::Stdout> {
|
||||
fn default() -> Self {
|
||||
Self::new(io::stderr(), io::stdout(), Config::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Informant<io::Stdout, io::Stdout> {
|
||||
/// std json informant using out only.
|
||||
pub fn out_only(config: Config) -> Self {
|
||||
Self::new(io::stdout(), io::stdout(), config)
|
||||
}
|
||||
}
|
||||
|
||||
impl Informant<io::Stderr, io::Stderr> {
|
||||
/// std json informant using err only.
|
||||
pub fn err_only(config: Config) -> Self {
|
||||
Self::new(io::stderr(), io::stderr(), config)
|
||||
}
|
||||
}
|
||||
|
||||
impl Informant<io::Stderr, io::Stdout> {
|
||||
pub fn new_default(config: Config) -> Self {
|
||||
let mut informant = Self::default();
|
||||
informant.config = config;
|
||||
informant
|
||||
}
|
||||
}
|
||||
|
||||
impl<Trace: Writer, Out: Writer> Informant<Trace, Out> {
|
||||
pub fn new(trace_sink: Trace, out_sink: Out, config: Config) -> Self {
|
||||
Informant {
|
||||
code: Default::default(),
|
||||
instruction: Default::default(),
|
||||
depth: Default::default(),
|
||||
stack: Default::default(),
|
||||
storage: Default::default(),
|
||||
subinfos: Default::default(),
|
||||
subdepth: 0,
|
||||
trace_sink,
|
||||
out_sink,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_informant_in_depth<F: Fn(&mut Informant<Trace, Out>)>(
|
||||
informant: &mut Informant<Trace, Out>,
|
||||
depth: usize,
|
||||
f: F,
|
||||
) {
|
||||
if depth == 0 {
|
||||
f(informant);
|
||||
} else {
|
||||
Self::with_informant_in_depth(
|
||||
informant
|
||||
.subinfos
|
||||
.last_mut()
|
||||
.expect("prepare/done_trace are not balanced"),
|
||||
depth - 1,
|
||||
f,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_state_into(
|
||||
trace_sink: &mut Trace,
|
||||
root: H256,
|
||||
end_state: &Option<pod_state::PodState>,
|
||||
) {
|
||||
if let Some(ref end_state) = end_state {
|
||||
let dump_data = json!({
|
||||
"root": root,
|
||||
"accounts": end_state,
|
||||
});
|
||||
writeln!(trace_sink, "{}", dump_data).expect("The sink must be writeable.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Trace: Writer, Out: Writer> vm::Informant for Informant<Trace, Out> {
|
||||
type Sink = (Trace, Out, Config);
|
||||
|
||||
fn before_test(&mut self, name: &str, action: &str) {
|
||||
let out_data = json!({
|
||||
"action": action,
|
||||
"test": name,
|
||||
});
|
||||
|
||||
writeln!(&mut self.out_sink, "{}", out_data).expect("The sink must be writeable.");
|
||||
}
|
||||
|
||||
fn set_gas(&mut self, _gas: U256) {}
|
||||
|
||||
fn clone_sink(&self) -> Self::Sink {
|
||||
(
|
||||
self.trace_sink.clone(),
|
||||
self.out_sink.clone(),
|
||||
self.config.clone(),
|
||||
)
|
||||
}
|
||||
fn finish(
|
||||
result: vm::RunResult<<Self as trace::VMTracer>::Output>,
|
||||
(ref mut trace_sink, ref mut out_sink, _): &mut Self::Sink,
|
||||
) {
|
||||
match result {
|
||||
Ok(success) => {
|
||||
let trace_data = json!({"stateRoot": success.state_root});
|
||||
writeln!(trace_sink, "{}", trace_data).expect("The sink must be writeable.");
|
||||
|
||||
Self::dump_state_into(trace_sink, success.state_root, &success.end_state);
|
||||
|
||||
let out_data = json!({
|
||||
"output": format!("0x{}", success.output.to_hex()),
|
||||
"gasUsed": format!("{:#x}", success.gas_used),
|
||||
"time": display::as_micros(&success.time),
|
||||
});
|
||||
|
||||
writeln!(out_sink, "{}", out_data).expect("The sink must be writeable.");
|
||||
}
|
||||
Err(failure) => {
|
||||
let out_data = json!({
|
||||
"error": &failure.error.to_string(),
|
||||
"gasUsed": format!("{:#x}", failure.gas_used),
|
||||
"time": display::as_micros(&failure.time),
|
||||
});
|
||||
|
||||
Self::dump_state_into(trace_sink, failure.state_root, &failure.end_state);
|
||||
|
||||
writeln!(out_sink, "{}", out_data).expect("The sink must be writeable.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Trace: Writer, Out: Writer> trace::VMTracer for Informant<Trace, Out> {
|
||||
type Output = ();
|
||||
|
||||
fn trace_next_instruction(&mut self, pc: usize, instruction: u8, current_gas: U256) -> bool {
|
||||
let subdepth = self.subdepth;
|
||||
Self::with_informant_in_depth(self, subdepth, |informant: &mut Informant<Trace, Out>| {
|
||||
let storage = if informant.config.omit_storage_output() {
|
||||
None
|
||||
} else {
|
||||
Some(&informant.storage)
|
||||
};
|
||||
let info = ::evm::Instruction::from_u8(instruction).map(|i| i.info());
|
||||
informant.instruction = instruction;
|
||||
let trace_data = json!({
|
||||
"pc": pc,
|
||||
"op": instruction,
|
||||
"opName": info.map(|i| i.name).unwrap_or(""),
|
||||
"gas": format!("{:#x}", current_gas),
|
||||
"stack": informant.stack,
|
||||
"storage": storage,
|
||||
"depth": informant.depth,
|
||||
});
|
||||
|
||||
writeln!(&mut informant.trace_sink, "{}", trace_data)
|
||||
.expect("The sink must be writeable.");
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
fn trace_prepare_execute(
|
||||
&mut self,
|
||||
_pc: usize,
|
||||
_instruction: u8,
|
||||
_gas_cost: U256,
|
||||
_mem_written: Option<(usize, usize)>,
|
||||
store_written: Option<(U256, U256)>,
|
||||
) {
|
||||
let subdepth = self.subdepth;
|
||||
Self::with_informant_in_depth(self, subdepth, |informant: &mut Informant<Trace, Out>| {
|
||||
if let Some((pos, val)) = store_written {
|
||||
informant.storage.insert(
|
||||
BigEndianHash::from_uint(&pos),
|
||||
BigEndianHash::from_uint(&val),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn trace_executed(&mut self, _gas_used: U256, stack_push: &[U256], _mem: &[u8]) {
|
||||
let subdepth = self.subdepth;
|
||||
Self::with_informant_in_depth(self, subdepth, |informant: &mut Informant<Trace, Out>| {
|
||||
let info = ::evm::Instruction::from_u8(informant.instruction).map(|i| i.info());
|
||||
|
||||
let len = informant.stack.len();
|
||||
let info_args = info.map(|i| i.args).unwrap_or(0);
|
||||
informant
|
||||
.stack
|
||||
.truncate(if len > info_args { len - info_args } else { 0 });
|
||||
informant.stack.extend_from_slice(stack_push);
|
||||
});
|
||||
}
|
||||
|
||||
fn prepare_subtrace(&mut self, code: &[u8]) {
|
||||
let subdepth = self.subdepth;
|
||||
Self::with_informant_in_depth(self, subdepth, |informant: &mut Informant<Trace, Out>| {
|
||||
let mut vm = Informant::new(
|
||||
informant.trace_sink.clone(),
|
||||
informant.out_sink.clone(),
|
||||
informant.config,
|
||||
);
|
||||
vm.depth = informant.depth + 1;
|
||||
vm.code = code.to_vec();
|
||||
informant.subinfos.push(vm);
|
||||
});
|
||||
self.subdepth += 1;
|
||||
}
|
||||
|
||||
fn done_subtrace(&mut self) {
|
||||
self.subdepth -= 1;
|
||||
let subdepth = self.subdepth;
|
||||
Self::with_informant_in_depth(self, subdepth, |informant: &mut Informant<Trace, Out>| {
|
||||
informant.subinfos.pop();
|
||||
});
|
||||
}
|
||||
|
||||
fn drain(self) -> Option<Self::Output> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use info::tests::run_test;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct TestWriter(pub Arc<Mutex<Vec<u8>>>);
|
||||
|
||||
impl Writer for TestWriter {
|
||||
fn clone(&self) -> Self {
|
||||
Clone::clone(self)
|
||||
}
|
||||
fn default() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for TestWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.0.lock().unwrap().write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.0.lock().unwrap().flush()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn informant(config: Config) -> (Informant<TestWriter, TestWriter>, Arc<Mutex<Vec<u8>>>) {
|
||||
let trace_writer: TestWriter = Default::default();
|
||||
let out_writer: TestWriter = Default::default();
|
||||
let res = trace_writer.0.clone();
|
||||
(Informant::new(trace_writer, out_writer, config), res)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_trace_failure() {
|
||||
let (inf, res) = informant(Config::default());
|
||||
run_test(
|
||||
inf,
|
||||
move |_, expected| {
|
||||
let bytes = res.lock().unwrap();
|
||||
assert_eq!(expected, &String::from_utf8_lossy(&**bytes))
|
||||
},
|
||||
"60F8d6",
|
||||
0xffff,
|
||||
r#"{"depth":1,"gas":"0xffff","op":96,"opName":"PUSH1","pc":0,"stack":[],"storage":{}}
|
||||
{"depth":1,"gas":"0xfffc","op":214,"opName":"","pc":2,"stack":["0xf8"],"storage":{}}
|
||||
"#,
|
||||
);
|
||||
|
||||
let (inf, res) = informant(Config::default());
|
||||
run_test(
|
||||
inf,
|
||||
move |_, expected| {
|
||||
let bytes = res.lock().unwrap();
|
||||
assert_eq!(expected, &String::from_utf8_lossy(&**bytes))
|
||||
},
|
||||
"F8d6",
|
||||
0xffff,
|
||||
r#"{"depth":1,"gas":"0xffff","op":248,"opName":"","pc":0,"stack":[],"storage":{}}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_trace_create_correctly() {
|
||||
let (informant, res) = informant(Config::default());
|
||||
run_test(
|
||||
informant,
|
||||
move |_, expected| {
|
||||
let bytes = res.lock().unwrap();
|
||||
assert_eq!(expected, &String::from_utf8_lossy(&**bytes))
|
||||
},
|
||||
"32343434345830f138343438323439f0",
|
||||
0xffff,
|
||||
r#"{"depth":1,"gas":"0xffff","op":50,"opName":"ORIGIN","pc":0,"stack":[],"storage":{}}
|
||||
{"depth":1,"gas":"0xfffd","op":52,"opName":"CALLVALUE","pc":1,"stack":["0x0"],"storage":{}}
|
||||
{"depth":1,"gas":"0xfffb","op":52,"opName":"CALLVALUE","pc":2,"stack":["0x0","0x0"],"storage":{}}
|
||||
{"depth":1,"gas":"0xfff9","op":52,"opName":"CALLVALUE","pc":3,"stack":["0x0","0x0","0x0"],"storage":{}}
|
||||
{"depth":1,"gas":"0xfff7","op":52,"opName":"CALLVALUE","pc":4,"stack":["0x0","0x0","0x0","0x0"],"storage":{}}
|
||||
{"depth":1,"gas":"0xfff5","op":88,"opName":"PC","pc":5,"stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{}}
|
||||
{"depth":1,"gas":"0xfff3","op":48,"opName":"ADDRESS","pc":6,"stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{}}
|
||||
{"depth":1,"gas":"0xfff1","op":241,"opName":"CALL","pc":7,"stack":["0x0","0x0","0x0","0x0","0x0","0x5","0x0"],"storage":{}}
|
||||
{"depth":1,"gas":"0x9e21","op":56,"opName":"CODESIZE","pc":8,"stack":["0x1"],"storage":{}}
|
||||
{"depth":1,"gas":"0x9e1f","op":52,"opName":"CALLVALUE","pc":9,"stack":["0x1","0x10"],"storage":{}}
|
||||
{"depth":1,"gas":"0x9e1d","op":52,"opName":"CALLVALUE","pc":10,"stack":["0x1","0x10","0x0"],"storage":{}}
|
||||
{"depth":1,"gas":"0x9e1b","op":56,"opName":"CODESIZE","pc":11,"stack":["0x1","0x10","0x0","0x0"],"storage":{}}
|
||||
{"depth":1,"gas":"0x9e19","op":50,"opName":"ORIGIN","pc":12,"stack":["0x1","0x10","0x0","0x0","0x10"],"storage":{}}
|
||||
{"depth":1,"gas":"0x9e17","op":52,"opName":"CALLVALUE","pc":13,"stack":["0x1","0x10","0x0","0x0","0x10","0x0"],"storage":{}}
|
||||
{"depth":1,"gas":"0x9e15","op":57,"opName":"CODECOPY","pc":14,"stack":["0x1","0x10","0x0","0x0","0x10","0x0","0x0"],"storage":{}}
|
||||
{"depth":1,"gas":"0x9e0c","op":240,"opName":"CREATE","pc":15,"stack":["0x1","0x10","0x0","0x0"],"storage":{}}
|
||||
{"depth":2,"gas":"0x210c","op":50,"opName":"ORIGIN","pc":0,"stack":[],"storage":{}}
|
||||
{"depth":2,"gas":"0x210a","op":52,"opName":"CALLVALUE","pc":1,"stack":["0x0"],"storage":{}}
|
||||
{"depth":2,"gas":"0x2108","op":52,"opName":"CALLVALUE","pc":2,"stack":["0x0","0x0"],"storage":{}}
|
||||
{"depth":2,"gas":"0x2106","op":52,"opName":"CALLVALUE","pc":3,"stack":["0x0","0x0","0x0"],"storage":{}}
|
||||
{"depth":2,"gas":"0x2104","op":52,"opName":"CALLVALUE","pc":4,"stack":["0x0","0x0","0x0","0x0"],"storage":{}}
|
||||
{"depth":2,"gas":"0x2102","op":88,"opName":"PC","pc":5,"stack":["0x0","0x0","0x0","0x0","0x0"],"storage":{}}
|
||||
{"depth":2,"gas":"0x2100","op":48,"opName":"ADDRESS","pc":6,"stack":["0x0","0x0","0x0","0x0","0x0","0x5"],"storage":{}}
|
||||
{"depth":2,"gas":"0x20fe","op":241,"opName":"CALL","pc":7,"stack":["0x0","0x0","0x0","0x0","0x0","0x5","0xbd770416a3345f91e4b34576cb804a576fa48eb1"],"storage":{}}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_omit_storage_and_memory_flag() {
|
||||
// should omit storage
|
||||
let (informant, res) = informant(Config::new(true, true));
|
||||
run_test(
|
||||
informant,
|
||||
move |_, expected| {
|
||||
let bytes = res.lock().unwrap();
|
||||
assert_eq!(expected, &String::from_utf8_lossy(&**bytes))
|
||||
},
|
||||
"3260D85554",
|
||||
0xffff,
|
||||
r#"{"depth":1,"gas":"0xffff","op":50,"opName":"ORIGIN","pc":0,"stack":[],"storage":null}
|
||||
{"depth":1,"gas":"0xfffd","op":96,"opName":"PUSH1","pc":1,"stack":["0x0"],"storage":null}
|
||||
{"depth":1,"gas":"0xfffa","op":85,"opName":"SSTORE","pc":3,"stack":["0x0","0xd8"],"storage":null}
|
||||
{"depth":1,"gas":"0xec72","op":84,"opName":"SLOAD","pc":4,"stack":[],"storage":null}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
316
bin/evmbin/src/info.rs
Normal file
316
bin/evmbin/src/info.rs
Normal file
@ -0,0 +1,316 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! VM runner.
|
||||
|
||||
use ethcore::{
|
||||
client::{self, EvmTestClient, EvmTestError, TransactErr, TransactSuccess},
|
||||
pod_state, spec, state, state_db, trace, TrieSpec,
|
||||
};
|
||||
use ethereum_types::{H256, U256};
|
||||
use ethjson;
|
||||
use std::time::{Duration, Instant};
|
||||
use types::transaction;
|
||||
use vm::ActionParams;
|
||||
|
||||
/// VM execution informant
|
||||
pub trait Informant: trace::VMTracer {
|
||||
/// Sink to use with finish
|
||||
type Sink;
|
||||
/// Display a single run init message
|
||||
fn before_test(&mut self, test: &str, action: &str);
|
||||
/// Set initial gas.
|
||||
fn set_gas(&mut self, _gas: U256) {}
|
||||
/// Clone sink.
|
||||
fn clone_sink(&self) -> Self::Sink;
|
||||
/// Display final result.
|
||||
fn finish(result: RunResult<Self::Output>, &mut Self::Sink);
|
||||
}
|
||||
|
||||
/// Execution finished correctly
|
||||
#[derive(Debug)]
|
||||
pub struct Success<T> {
|
||||
/// State root
|
||||
pub state_root: H256,
|
||||
/// Used gas
|
||||
pub gas_used: U256,
|
||||
/// Output as bytes
|
||||
pub output: Vec<u8>,
|
||||
/// Time Taken
|
||||
pub time: Duration,
|
||||
/// Traces
|
||||
pub traces: Option<T>,
|
||||
/// Optional end state dump
|
||||
pub end_state: Option<pod_state::PodState>,
|
||||
}
|
||||
|
||||
/// Execution failed
|
||||
#[derive(Debug)]
|
||||
pub struct Failure<T> {
|
||||
/// State root
|
||||
pub state_root: H256,
|
||||
/// Used gas
|
||||
pub gas_used: U256,
|
||||
/// Internal error
|
||||
pub error: EvmTestError,
|
||||
/// Duration
|
||||
pub time: Duration,
|
||||
/// Traces
|
||||
pub traces: Option<T>,
|
||||
/// Optional end state dump
|
||||
pub end_state: Option<pod_state::PodState>,
|
||||
}
|
||||
|
||||
/// EVM Execution result
|
||||
pub type RunResult<T> = Result<Success<T>, Failure<T>>;
|
||||
|
||||
/// Execute given `ActionParams` and return the result.
|
||||
pub fn run_action<T: Informant>(
|
||||
spec: &spec::Spec,
|
||||
mut params: ActionParams,
|
||||
mut informant: T,
|
||||
trie_spec: TrieSpec,
|
||||
) -> RunResult<T::Output> {
|
||||
informant.set_gas(params.gas);
|
||||
|
||||
// if the code is not overwritten from CLI, use code from spec file.
|
||||
if params.code.is_none() {
|
||||
if let Some(acc) = spec.genesis_state().get().get(¶ms.code_address) {
|
||||
params.code = acc.code.clone().map(::std::sync::Arc::new);
|
||||
params.code_hash = None;
|
||||
}
|
||||
}
|
||||
run(
|
||||
spec,
|
||||
trie_spec,
|
||||
params.gas,
|
||||
spec.genesis_state(),
|
||||
|mut client| {
|
||||
let result = match client.call(params, &mut trace::NoopTracer, &mut informant) {
|
||||
Ok(r) => (Ok(r.return_data.to_vec()), Some(r.gas_left)),
|
||||
Err(err) => (Err(err), None),
|
||||
};
|
||||
(result.0, H256::zero(), None, result.1, informant.drain())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Execute given Transaction and verify resulting state root.
|
||||
pub fn run_transaction<T: Informant>(
|
||||
name: &str,
|
||||
idx: usize,
|
||||
spec: ðjson::spec::ForkSpec,
|
||||
pre_state: &pod_state::PodState,
|
||||
post_root: H256,
|
||||
env_info: &client::EnvInfo,
|
||||
transaction: transaction::SignedTransaction,
|
||||
mut informant: T,
|
||||
trie_spec: TrieSpec,
|
||||
) {
|
||||
let spec_name = format!("{:?}", spec).to_lowercase();
|
||||
let spec = match EvmTestClient::spec_from_json(spec) {
|
||||
Some(spec) => {
|
||||
informant.before_test(&format!("{}:{}:{}", name, spec_name, idx), "starting");
|
||||
spec
|
||||
}
|
||||
None => {
|
||||
informant.before_test(
|
||||
&format!("{}:{}:{}", name, spec_name, idx),
|
||||
"skipping because of missing spec",
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
informant.set_gas(env_info.gas_limit);
|
||||
|
||||
let mut sink = informant.clone_sink();
|
||||
let result = run(
|
||||
&spec,
|
||||
trie_spec,
|
||||
transaction.tx().gas,
|
||||
pre_state,
|
||||
|mut client| {
|
||||
let result = client.transact(env_info, transaction, trace::NoopTracer, informant);
|
||||
match result {
|
||||
Ok(TransactSuccess {
|
||||
state_root,
|
||||
gas_left,
|
||||
output,
|
||||
vm_trace,
|
||||
end_state,
|
||||
..
|
||||
}) => {
|
||||
if state_root != post_root {
|
||||
(
|
||||
Err(EvmTestError::PostCondition(format!(
|
||||
"State root mismatch (got: {:#x}, expected: {:#x})",
|
||||
state_root, post_root,
|
||||
))),
|
||||
state_root,
|
||||
end_state,
|
||||
Some(gas_left),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
(Ok(output), state_root, end_state, Some(gas_left), vm_trace)
|
||||
}
|
||||
}
|
||||
Err(TransactErr {
|
||||
state_root,
|
||||
error,
|
||||
end_state,
|
||||
}) => (
|
||||
Err(EvmTestError::PostCondition(format!(
|
||||
"Unexpected execution error: {:?}",
|
||||
error
|
||||
))),
|
||||
state_root,
|
||||
end_state,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
T::finish(result, &mut sink)
|
||||
}
|
||||
|
||||
fn dump_state(state: &state::State<state_db::StateDB>) -> Option<pod_state::PodState> {
|
||||
state.to_pod_full().ok()
|
||||
}
|
||||
|
||||
/// Execute VM with given `ActionParams`
|
||||
pub fn run<'a, F, X>(
|
||||
spec: &'a spec::Spec,
|
||||
trie_spec: TrieSpec,
|
||||
initial_gas: U256,
|
||||
pre_state: &'a pod_state::PodState,
|
||||
run: F,
|
||||
) -> RunResult<X>
|
||||
where
|
||||
F: FnOnce(
|
||||
EvmTestClient,
|
||||
) -> (
|
||||
Result<Vec<u8>, EvmTestError>,
|
||||
H256,
|
||||
Option<pod_state::PodState>,
|
||||
Option<U256>,
|
||||
Option<X>,
|
||||
),
|
||||
{
|
||||
let do_dump = trie_spec == TrieSpec::Fat;
|
||||
|
||||
let mut test_client =
|
||||
EvmTestClient::from_pod_state_with_trie(spec, pre_state.clone(), trie_spec).map_err(
|
||||
|error| Failure {
|
||||
gas_used: 0.into(),
|
||||
error,
|
||||
time: Duration::from_secs(0),
|
||||
traces: None,
|
||||
state_root: H256::default(),
|
||||
end_state: None,
|
||||
},
|
||||
)?;
|
||||
|
||||
if do_dump {
|
||||
test_client.set_dump_state_fn(dump_state);
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
let result = run(test_client);
|
||||
let time = start.elapsed();
|
||||
|
||||
match result {
|
||||
(Ok(output), state_root, end_state, gas_left, traces) => Ok(Success {
|
||||
state_root,
|
||||
gas_used: gas_left
|
||||
.map(|gas_left| initial_gas - gas_left)
|
||||
.unwrap_or(initial_gas),
|
||||
output,
|
||||
time,
|
||||
traces,
|
||||
end_state,
|
||||
}),
|
||||
(Err(error), state_root, end_state, gas_left, traces) => Err(Failure {
|
||||
gas_used: gas_left
|
||||
.map(|gas_left| initial_gas - gas_left)
|
||||
.unwrap_or(initial_gas),
|
||||
error,
|
||||
time,
|
||||
traces,
|
||||
state_root,
|
||||
end_state,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use ethereum_types::Address;
|
||||
use rustc_hex::FromHex;
|
||||
use std::sync::Arc;
|
||||
use tempdir::TempDir;
|
||||
|
||||
pub fn run_test<T, I, F>(informant: I, compare: F, code: &str, gas: T, expected: &str)
|
||||
where
|
||||
T: Into<U256>,
|
||||
I: Informant,
|
||||
F: FnOnce(Option<I::Output>, &str),
|
||||
{
|
||||
let mut params = ActionParams::default();
|
||||
params.code = Some(Arc::new(code.from_hex().unwrap()));
|
||||
params.gas = gas.into();
|
||||
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let spec = ::ethcore::ethereum::new_foundation(&tempdir.path());
|
||||
let result = run_action(&spec, params, informant, TrieSpec::Secure);
|
||||
match result {
|
||||
Ok(Success { traces, .. }) => compare(traces, expected),
|
||||
Err(Failure { traces, .. }) => compare(traces, expected),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_call_account_from_spec() {
|
||||
use display::{config::Config, std_json::tests::informant};
|
||||
|
||||
let (inf, res) = informant(Config::default());
|
||||
let mut params = ActionParams::default();
|
||||
params.code_address = Address::from_low_u64_be(0x20);
|
||||
params.gas = 0xffff.into();
|
||||
|
||||
let spec = ::ethcore::ethereum::load(None, include_bytes!("../res/testchain.json"));
|
||||
let _result = run_action(&spec, params, inf, TrieSpec::Secure);
|
||||
|
||||
assert_eq!(
|
||||
&String::from_utf8_lossy(&**res.lock().unwrap()),
|
||||
r#"{"depth":1,"gas":"0xffff","op":98,"opName":"PUSH3","pc":0,"stack":[],"storage":{}}
|
||||
{"depth":1,"gas":"0xfffc","op":96,"opName":"PUSH1","pc":4,"stack":["0xaaaaaa"],"storage":{}}
|
||||
{"depth":1,"gas":"0xfff9","op":96,"opName":"PUSH1","pc":6,"stack":["0xaaaaaa","0xaa"],"storage":{}}
|
||||
{"depth":1,"gas":"0xfff6","op":80,"opName":"POP","pc":8,"stack":["0xaaaaaa","0xaa","0xaa"],"storage":{}}
|
||||
{"depth":1,"gas":"0xfff4","op":96,"opName":"PUSH1","pc":9,"stack":["0xaaaaaa","0xaa"],"storage":{}}
|
||||
{"depth":1,"gas":"0xfff1","op":96,"opName":"PUSH1","pc":11,"stack":["0xaaaaaa","0xaa","0xaa"],"storage":{}}
|
||||
{"depth":1,"gas":"0xffee","op":96,"opName":"PUSH1","pc":13,"stack":["0xaaaaaa","0xaa","0xaa","0xaa"],"storage":{}}
|
||||
{"depth":1,"gas":"0xffeb","op":96,"opName":"PUSH1","pc":15,"stack":["0xaaaaaa","0xaa","0xaa","0xaa","0xaa"],"storage":{}}
|
||||
{"depth":1,"gas":"0xffe8","op":96,"opName":"PUSH1","pc":17,"stack":["0xaaaaaa","0xaa","0xaa","0xaa","0xaa","0xaa"],"storage":{}}
|
||||
{"depth":1,"gas":"0xffe5","op":96,"opName":"PUSH1","pc":19,"stack":["0xaaaaaa","0xaa","0xaa","0xaa","0xaa","0xaa","0xaa"],"storage":{}}
|
||||
"#
|
||||
);
|
||||
}
|
||||
}
|
511
bin/evmbin/src/main.rs
Normal file
511
bin/evmbin/src/main.rs
Normal file
@ -0,0 +1,511 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! OpenEthereum EVM interpreter binary.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
extern crate common_types as types;
|
||||
extern crate ethcore;
|
||||
extern crate ethjson;
|
||||
extern crate rustc_hex;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
extern crate docopt;
|
||||
extern crate env_logger;
|
||||
extern crate ethereum_types;
|
||||
extern crate evm;
|
||||
extern crate panic_hook;
|
||||
extern crate parity_bytes as bytes;
|
||||
extern crate vm;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate tempdir;
|
||||
|
||||
use bytes::Bytes;
|
||||
use docopt::Docopt;
|
||||
use ethcore::{json_tests, spec, TrieSpec};
|
||||
use ethereum_types::{Address, U256};
|
||||
use ethjson::spec::ForkSpec;
|
||||
use evm::EnvInfo;
|
||||
use rustc_hex::FromHex;
|
||||
use std::{fmt, fs, path::PathBuf, sync::Arc};
|
||||
use vm::{ActionParams, CallType};
|
||||
|
||||
mod display;
|
||||
mod info;
|
||||
|
||||
use info::Informant;
|
||||
|
||||
const USAGE: &'static str = r#"
|
||||
EVM implementation for Parity.
|
||||
Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
|
||||
Usage:
|
||||
openethereum-evm state-test <file> [--json --std-json --std-dump-json --only NAME --chain CHAIN --std-out-only --std-err-only --omit-storage-output --omit-memory-output]
|
||||
openethereum-evm stats [options]
|
||||
openethereum-evm stats-jsontests-vm <file>
|
||||
openethereum-evm [options]
|
||||
openethereum-evm [-h | --help]
|
||||
|
||||
Commands:
|
||||
state-test Run a state test from a json file.
|
||||
stats Execute EVM runtime code and return the statistics.
|
||||
stats-jsontests-vm Execute standard json-tests format VMTests and return
|
||||
timing statistics in tsv format.
|
||||
|
||||
Transaction options:
|
||||
--code CODE Contract code as hex (without 0x).
|
||||
--to ADDRESS Recipient address (without 0x).
|
||||
--from ADDRESS Sender address (without 0x).
|
||||
--input DATA Input data as hex (without 0x).
|
||||
--gas GAS Supplied gas as hex (without 0x).
|
||||
--gas-price WEI Supplied gas price as hex (without 0x).
|
||||
|
||||
State test options:
|
||||
--chain CHAIN Run only from specific chain name (i.e. one of EIP150, EIP158,
|
||||
Frontier, Homestead, Byzantium, Constantinople,
|
||||
ConstantinopleFix, Istanbul, EIP158ToByzantiumAt5, FrontierToHomesteadAt5,
|
||||
HomesteadToDaoAt5, HomesteadToEIP150At5, Berlin, Yolo3).
|
||||
--only NAME Runs only a single test matching the name.
|
||||
|
||||
General options:
|
||||
--json Display verbose results in JSON.
|
||||
--std-json Display results in standardized JSON format.
|
||||
--std-err-only With --std-json redirect to err output only.
|
||||
--std-out-only With --std-json redirect to out output only.
|
||||
--omit-storage-output With --std-json omit storage output.
|
||||
--omit-memory-output With --std-json omit memory output.
|
||||
--std-dump-json Display results in standardized JSON format
|
||||
with additional state dump.
|
||||
|
||||
Display result state dump in standardized JSON format.
|
||||
--chain CHAIN Chain spec file path.
|
||||
-h, --help Display this message and exit.
|
||||
"#;
|
||||
|
||||
fn main() {
|
||||
panic_hook::set_abort();
|
||||
env_logger::init();
|
||||
|
||||
let args: Args = Docopt::new(USAGE)
|
||||
.and_then(|d| d.deserialize())
|
||||
.unwrap_or_else(|e| e.exit());
|
||||
|
||||
let config = args.config();
|
||||
|
||||
if args.cmd_state_test {
|
||||
run_state_test(args)
|
||||
} else if args.cmd_stats_jsontests_vm {
|
||||
run_stats_jsontests_vm(args)
|
||||
} else if args.flag_json {
|
||||
run_call(args, display::json::Informant::new(config))
|
||||
} else if args.flag_std_dump_json || args.flag_std_json {
|
||||
if args.flag_std_err_only {
|
||||
run_call(args, display::std_json::Informant::err_only(config))
|
||||
} else if args.flag_std_out_only {
|
||||
run_call(args, display::std_json::Informant::out_only(config))
|
||||
} else {
|
||||
run_call(args, display::std_json::Informant::new_default(config))
|
||||
};
|
||||
} else {
|
||||
run_call(args, display::simple::Informant::new(config))
|
||||
}
|
||||
}
|
||||
|
||||
fn run_stats_jsontests_vm(args: Args) {
|
||||
use json_tests::HookType;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
let file = args.arg_file.expect("FILE (or PATH) is required");
|
||||
|
||||
let mut timings: HashMap<String, (Instant, Option<Duration>)> = HashMap::new();
|
||||
|
||||
{
|
||||
let mut record_time = |name: &str, typ: HookType| match typ {
|
||||
HookType::OnStart => {
|
||||
timings.insert(name.to_string(), (Instant::now(), None));
|
||||
}
|
||||
HookType::OnStop => {
|
||||
timings.entry(name.to_string()).and_modify(|v| {
|
||||
v.1 = Some(v.0.elapsed());
|
||||
});
|
||||
}
|
||||
};
|
||||
for file_path in json_tests::find_json_files_recursive(&file) {
|
||||
let json_data = std::fs::read(&file_path).unwrap();
|
||||
json_tests::json_executive_test(&file_path, &json_data, &mut record_time);
|
||||
}
|
||||
}
|
||||
|
||||
for (name, v) in timings {
|
||||
println!(
|
||||
"{}\t{}",
|
||||
name,
|
||||
display::as_micros(&v.1.expect("All hooks are called with OnStop; qed"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn run_state_test(args: Args) {
|
||||
use ethjson::state::test::Test;
|
||||
let config = args.config();
|
||||
let file = args.arg_file.expect("FILE is required");
|
||||
let mut file = match fs::File::open(&file) {
|
||||
Err(err) => die(format!("Unable to open: {:?}: {}", file, err)),
|
||||
Ok(file) => file,
|
||||
};
|
||||
let state_test = match Test::load(&mut file) {
|
||||
Err(err) => die(format!("Unable to load the test file: {}", err)),
|
||||
Ok(test) => test,
|
||||
};
|
||||
let only_test = args.flag_only.map(|s| s.to_lowercase());
|
||||
let only_chain = args.flag_chain.map(|s| s.to_lowercase());
|
||||
|
||||
for (name, test) in state_test {
|
||||
if let Some(false) = only_test
|
||||
.as_ref()
|
||||
.map(|only_test| &name.to_lowercase() == only_test)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let multitransaction = test.transaction;
|
||||
let env_info: EnvInfo = test.env.into();
|
||||
let pre = test.pre_state.into();
|
||||
|
||||
for (spec, states) in test.post_states {
|
||||
//hardcode base fee for part of the london tests, that miss base fee field in env
|
||||
let mut test_env = env_info.clone();
|
||||
if spec >= ForkSpec::London {
|
||||
if test_env.base_fee.is_none() {
|
||||
test_env.base_fee = Some(0x0a.into());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(false) = only_chain
|
||||
.as_ref()
|
||||
.map(|only_chain| &format!("{:?}", spec).to_lowercase() == only_chain)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (idx, state) in states.into_iter().enumerate() {
|
||||
let post_root = state.hash.into();
|
||||
let transaction = multitransaction.select(&state.indexes);
|
||||
|
||||
let trie_spec = if args.flag_std_dump_json {
|
||||
TrieSpec::Fat
|
||||
} else {
|
||||
TrieSpec::Secure
|
||||
};
|
||||
if args.flag_json {
|
||||
info::run_transaction(
|
||||
&name,
|
||||
idx,
|
||||
&spec,
|
||||
&pre,
|
||||
post_root,
|
||||
&test_env,
|
||||
transaction,
|
||||
display::json::Informant::new(config),
|
||||
trie_spec,
|
||||
)
|
||||
} else if args.flag_std_dump_json || args.flag_std_json {
|
||||
if args.flag_std_err_only {
|
||||
info::run_transaction(
|
||||
&name,
|
||||
idx,
|
||||
&spec,
|
||||
&pre,
|
||||
post_root,
|
||||
&test_env,
|
||||
transaction,
|
||||
display::std_json::Informant::err_only(config),
|
||||
trie_spec,
|
||||
)
|
||||
} else if args.flag_std_out_only {
|
||||
info::run_transaction(
|
||||
&name,
|
||||
idx,
|
||||
&spec,
|
||||
&pre,
|
||||
post_root,
|
||||
&test_env,
|
||||
transaction,
|
||||
display::std_json::Informant::out_only(config),
|
||||
trie_spec,
|
||||
)
|
||||
} else {
|
||||
info::run_transaction(
|
||||
&name,
|
||||
idx,
|
||||
&spec,
|
||||
&pre,
|
||||
post_root,
|
||||
&test_env,
|
||||
transaction,
|
||||
display::std_json::Informant::new_default(config),
|
||||
trie_spec,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
info::run_transaction(
|
||||
&name,
|
||||
idx,
|
||||
&spec,
|
||||
&pre,
|
||||
post_root,
|
||||
&test_env,
|
||||
transaction,
|
||||
display::simple::Informant::new(config),
|
||||
trie_spec,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_call<T: Informant>(args: Args, informant: T) {
|
||||
let from = arg(args.from(), "--from");
|
||||
let to = arg(args.to(), "--to");
|
||||
let code = arg(args.code(), "--code");
|
||||
let spec = arg(args.spec(), "--chain");
|
||||
let gas = arg(args.gas(), "--gas");
|
||||
let gas_price = arg(args.gas_price(), "--gas-price");
|
||||
let data = arg(args.data(), "--input");
|
||||
|
||||
if code.is_none() && to == Address::default() {
|
||||
die("Either --code or --to is required.");
|
||||
}
|
||||
let mut params = ActionParams::default();
|
||||
if spec.engine.params().eip2929_transition == 0 {
|
||||
params.access_list.enable();
|
||||
params.access_list.insert_address(from);
|
||||
params.access_list.insert_address(to);
|
||||
for (builtin, _) in spec.engine.builtins() {
|
||||
params.access_list.insert_address(*builtin);
|
||||
}
|
||||
}
|
||||
|
||||
params.call_type = if code.is_none() {
|
||||
CallType::Call
|
||||
} else {
|
||||
CallType::None
|
||||
};
|
||||
params.code_address = to;
|
||||
params.address = to;
|
||||
params.sender = from;
|
||||
params.origin = from;
|
||||
params.gas = gas;
|
||||
params.gas_price = gas_price;
|
||||
params.code = code.map(Arc::new);
|
||||
params.data = data;
|
||||
|
||||
let mut sink = informant.clone_sink();
|
||||
let result = if args.flag_std_dump_json {
|
||||
info::run_action(&spec, params, informant, TrieSpec::Fat)
|
||||
} else {
|
||||
info::run_action(&spec, params, informant, TrieSpec::Secure)
|
||||
};
|
||||
T::finish(result, &mut sink);
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Args {
|
||||
cmd_stats: bool,
|
||||
cmd_state_test: bool,
|
||||
cmd_stats_jsontests_vm: bool,
|
||||
arg_file: Option<PathBuf>,
|
||||
flag_only: Option<String>,
|
||||
flag_from: Option<String>,
|
||||
flag_to: Option<String>,
|
||||
flag_code: Option<String>,
|
||||
flag_gas: Option<String>,
|
||||
flag_gas_price: Option<String>,
|
||||
flag_input: Option<String>,
|
||||
flag_chain: Option<String>,
|
||||
flag_json: bool,
|
||||
flag_std_json: bool,
|
||||
flag_std_dump_json: bool,
|
||||
flag_std_err_only: bool,
|
||||
flag_std_out_only: bool,
|
||||
flag_omit_storage_output: bool,
|
||||
flag_omit_memory_output: bool,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub fn gas(&self) -> Result<U256, String> {
|
||||
match self.flag_gas {
|
||||
Some(ref gas) => gas.parse().map_err(to_string),
|
||||
None => Ok(U256::from(u64::max_value())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gas_price(&self) -> Result<U256, String> {
|
||||
match self.flag_gas_price {
|
||||
Some(ref gas_price) => gas_price.parse().map_err(to_string),
|
||||
None => Ok(U256::zero()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from(&self) -> Result<Address, String> {
|
||||
match self.flag_from {
|
||||
Some(ref from) => from.parse().map_err(to_string),
|
||||
None => Ok(Address::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to(&self) -> Result<Address, String> {
|
||||
match self.flag_to {
|
||||
Some(ref to) => to.parse().map_err(to_string),
|
||||
None => Ok(Address::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn code(&self) -> Result<Option<Bytes>, String> {
|
||||
match self.flag_code {
|
||||
Some(ref code) => code.from_hex().map(Some).map_err(to_string),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data(&self) -> Result<Option<Bytes>, String> {
|
||||
match self.flag_input {
|
||||
Some(ref input) => input.from_hex().map_err(to_string).map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spec(&self) -> Result<spec::Spec, String> {
|
||||
Ok(match self.flag_chain {
|
||||
Some(ref spec_name) => {
|
||||
let fork_spec: Result<ethjson::spec::ForkSpec, _> =
|
||||
serde_json::from_str(&format!("{:?}", spec_name));
|
||||
if let Ok(fork_spec) = fork_spec {
|
||||
ethcore::client::EvmTestClient::spec_from_json(&fork_spec)
|
||||
.expect("this forkspec is not defined")
|
||||
} else {
|
||||
let file = fs::File::open(spec_name).map_err(|e| format!("{}", e))?;
|
||||
spec::Spec::load(&::std::env::temp_dir(), file)?
|
||||
}
|
||||
}
|
||||
None => ethcore::ethereum::new_foundation(&::std::env::temp_dir()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn config(&self) -> display::config::Config {
|
||||
display::config::Config::new(self.flag_omit_storage_output, self.flag_omit_memory_output)
|
||||
}
|
||||
}
|
||||
|
||||
fn arg<T>(v: Result<T, String>, param: &str) -> T {
|
||||
v.unwrap_or_else(|e| die(format!("Invalid {}: {}", param, e)))
|
||||
}
|
||||
|
||||
fn to_string<T: fmt::Display>(msg: T) -> String {
|
||||
format!("{}", msg)
|
||||
}
|
||||
|
||||
fn die<T: fmt::Display>(msg: T) -> ! {
|
||||
println!("{}", msg);
|
||||
::std::process::exit(-1)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Args, USAGE};
|
||||
use docopt::Docopt;
|
||||
use ethereum_types::Address;
|
||||
|
||||
fn run<T: AsRef<str>>(args: &[T]) -> Args {
|
||||
Docopt::new(USAGE)
|
||||
.and_then(|d| d.argv(args.into_iter()).deserialize())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_parse_all_the_options() {
|
||||
let args = run(&[
|
||||
"openethereum-evm",
|
||||
"--json",
|
||||
"--std-json",
|
||||
"--std-dump-json",
|
||||
"--gas",
|
||||
"1",
|
||||
"--gas-price",
|
||||
"2",
|
||||
"--from",
|
||||
"0000000000000000000000000000000000000003",
|
||||
"--to",
|
||||
"0000000000000000000000000000000000000004",
|
||||
"--code",
|
||||
"05",
|
||||
"--input",
|
||||
"06",
|
||||
"--chain",
|
||||
"./testfile",
|
||||
"--std-err-only",
|
||||
"--std-out-only",
|
||||
]);
|
||||
|
||||
assert_eq!(args.flag_json, true);
|
||||
assert_eq!(args.flag_std_json, true);
|
||||
assert_eq!(args.flag_std_dump_json, true);
|
||||
assert_eq!(args.flag_std_err_only, true);
|
||||
assert_eq!(args.flag_std_out_only, true);
|
||||
assert_eq!(args.gas(), Ok(1.into()));
|
||||
assert_eq!(args.gas_price(), Ok(2.into()));
|
||||
assert_eq!(args.from(), Ok(Address::from_low_u64_be(3)));
|
||||
assert_eq!(args.to(), Ok(Address::from_low_u64_be(4)));
|
||||
assert_eq!(args.code(), Ok(Some(vec![05])));
|
||||
assert_eq!(args.data(), Ok(Some(vec![06])));
|
||||
assert_eq!(args.flag_chain, Some("./testfile".to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_parse_state_test_command() {
|
||||
let args = run(&[
|
||||
"openethereum-evm",
|
||||
"state-test",
|
||||
"./file.json",
|
||||
"--chain",
|
||||
"homestead",
|
||||
"--only=add11",
|
||||
"--json",
|
||||
"--std-json",
|
||||
"--std-dump-json",
|
||||
]);
|
||||
|
||||
assert_eq!(args.cmd_state_test, true);
|
||||
assert!(args.arg_file.is_some());
|
||||
assert_eq!(args.flag_json, true);
|
||||
assert_eq!(args.flag_std_json, true);
|
||||
assert_eq!(args.flag_std_dump_json, true);
|
||||
assert_eq!(args.flag_chain, Some("homestead".to_owned()));
|
||||
assert_eq!(args.flag_only, Some("add11".to_owned()));
|
||||
}
|
||||
}
|
141
bin/oe/account.rs
Normal file
141
bin/oe/account.rs
Normal file
@ -0,0 +1,141 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::params::SpecType;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum AccountCmd {
|
||||
New(NewAccount),
|
||||
List(ListAccounts),
|
||||
Import(ImportAccounts),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ListAccounts {
|
||||
pub path: String,
|
||||
pub spec: SpecType,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct NewAccount {
|
||||
pub iterations: NonZeroU32,
|
||||
pub path: String,
|
||||
pub spec: SpecType,
|
||||
pub password_file: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ImportAccounts {
|
||||
pub from: Vec<String>,
|
||||
pub to: String,
|
||||
pub spec: SpecType,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "accounts"))]
|
||||
pub fn execute(_cmd: AccountCmd) -> Result<String, String> {
|
||||
Err("Account management is deprecated. Please see #9997 for alternatives:\nhttps://github.com/openethereum/openethereum/issues/9997".into())
|
||||
}
|
||||
|
||||
#[cfg(feature = "accounts")]
|
||||
mod command {
|
||||
use super::*;
|
||||
use crate::{
|
||||
accounts::{AccountProvider, AccountProviderSettings},
|
||||
helpers::{password_from_file, password_prompt},
|
||||
};
|
||||
use ethstore::{accounts_dir::RootDiskDirectory, import_account, import_accounts, EthStore};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn execute(cmd: AccountCmd) -> Result<String, String> {
|
||||
match cmd {
|
||||
AccountCmd::New(new_cmd) => new(new_cmd),
|
||||
AccountCmd::List(list_cmd) => list(list_cmd),
|
||||
AccountCmd::Import(import_cmd) => import(import_cmd),
|
||||
}
|
||||
}
|
||||
|
||||
fn keys_dir(path: String, spec: SpecType) -> Result<RootDiskDirectory, String> {
|
||||
let spec = spec.spec(&::std::env::temp_dir())?;
|
||||
let mut path = PathBuf::from(&path);
|
||||
path.push(spec.data_dir);
|
||||
RootDiskDirectory::create(path).map_err(|e| format!("Could not open keys directory: {}", e))
|
||||
}
|
||||
|
||||
fn secret_store(
|
||||
dir: Box<RootDiskDirectory>,
|
||||
iterations: Option<NonZeroU32>,
|
||||
) -> Result<EthStore, String> {
|
||||
match iterations {
|
||||
Some(i) => EthStore::open_with_iterations(dir, i),
|
||||
_ => EthStore::open(dir),
|
||||
}
|
||||
.map_err(|e| format!("Could not open keys store: {}", e))
|
||||
}
|
||||
|
||||
fn new(n: NewAccount) -> Result<String, String> {
|
||||
let password = match n.password_file {
|
||||
Some(file) => password_from_file(file)?,
|
||||
None => password_prompt()?,
|
||||
};
|
||||
|
||||
let dir = Box::new(keys_dir(n.path, n.spec)?);
|
||||
let secret_store = Box::new(secret_store(dir, Some(n.iterations))?);
|
||||
let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default());
|
||||
let new_account = acc_provider
|
||||
.new_account(&password)
|
||||
.map_err(|e| format!("Could not create new account: {}", e))?;
|
||||
Ok(format!("0x{:x}", new_account))
|
||||
}
|
||||
|
||||
fn list(list_cmd: ListAccounts) -> Result<String, String> {
|
||||
let dir = Box::new(keys_dir(list_cmd.path, list_cmd.spec)?);
|
||||
let secret_store = Box::new(secret_store(dir, None)?);
|
||||
let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default());
|
||||
let accounts = acc_provider.accounts().map_err(|e| format!("{}", e))?;
|
||||
let result = accounts
|
||||
.into_iter()
|
||||
.map(|a| format!("0x{:x}", a))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn import(i: ImportAccounts) -> Result<String, String> {
|
||||
let to = keys_dir(i.to, i.spec)?;
|
||||
let mut imported = 0;
|
||||
|
||||
for path in &i.from {
|
||||
let path = PathBuf::from(path);
|
||||
if path.is_dir() {
|
||||
let from = RootDiskDirectory::at(&path);
|
||||
imported += import_accounts(&from, &to)
|
||||
.map_err(|e| format!("Importing accounts from {:?} failed: {}", path, e))?
|
||||
.len();
|
||||
} else if path.is_file() {
|
||||
import_account(&path, &to)
|
||||
.map_err(|e| format!("Importing account from {:?} failed: {}", path, e))?;
|
||||
imported += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(format!("{} account(s) imported", imported))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "accounts")]
|
||||
pub use self::command::execute;
|
263
bin/oe/account_utils.rs
Normal file
263
bin/oe/account_utils.rs
Normal file
@ -0,0 +1,263 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crypto::publickey;
|
||||
use dir::Directories;
|
||||
use ethereum_types::{Address, H160};
|
||||
use ethkey::Password;
|
||||
|
||||
use crate::params::{AccountsConfig, SpecType};
|
||||
|
||||
#[cfg(not(feature = "accounts"))]
|
||||
mod accounts {
|
||||
use super::*;
|
||||
|
||||
/// Dummy AccountProvider
|
||||
pub struct AccountProvider;
|
||||
|
||||
impl ::ethcore::miner::LocalAccounts for AccountProvider {
|
||||
fn is_local(&self, _address: &Address) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_account_provider(
|
||||
_spec: &SpecType,
|
||||
_dirs: &Directories,
|
||||
_data_dir: &str,
|
||||
_cfg: AccountsConfig,
|
||||
_passwords: &[Password],
|
||||
) -> Result<AccountProvider, String> {
|
||||
warn!("Note: Your instance of OpenEthereum is running without account support. Some CLI options are ignored.");
|
||||
Ok(AccountProvider)
|
||||
}
|
||||
|
||||
pub fn miner_local_accounts(_: Arc<AccountProvider>) -> AccountProvider {
|
||||
AccountProvider
|
||||
}
|
||||
|
||||
pub fn miner_author(
|
||||
_spec: &SpecType,
|
||||
_dirs: &Directories,
|
||||
_account_provider: &Arc<AccountProvider>,
|
||||
_engine_signer: Address,
|
||||
_passwords: &[Password],
|
||||
) -> Result<Option<::ethcore::miner::Author>, String> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn accounts_list(
|
||||
_account_provider: Arc<AccountProvider>,
|
||||
) -> Arc<dyn Fn() -> Vec<Address> + Send + Sync> {
|
||||
Arc::new(|| vec![])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "accounts")]
|
||||
mod accounts {
|
||||
use super::*;
|
||||
use crate::{ethereum_types::H256, upgrade::upgrade_key_location};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub use crate::accounts::AccountProvider;
|
||||
|
||||
/// Pops along with error messages when a password is missing or invalid.
|
||||
const VERIFY_PASSWORD_HINT: &str = "Make sure valid password is present in files passed using `--password` or in the configuration file.";
|
||||
|
||||
/// Initialize account provider
|
||||
pub fn prepare_account_provider(
|
||||
spec: &SpecType,
|
||||
dirs: &Directories,
|
||||
data_dir: &str,
|
||||
cfg: AccountsConfig,
|
||||
passwords: &[Password],
|
||||
) -> Result<AccountProvider, String> {
|
||||
use crate::accounts::AccountProviderSettings;
|
||||
use ethstore::{accounts_dir::RootDiskDirectory, EthStore};
|
||||
|
||||
let path = dirs.keys_path(data_dir);
|
||||
upgrade_key_location(&dirs.legacy_keys_path(cfg.testnet), &path);
|
||||
let dir = Box::new(
|
||||
RootDiskDirectory::create(&path)
|
||||
.map_err(|e| format!("Could not open keys directory: {}", e))?,
|
||||
);
|
||||
let account_settings = AccountProviderSettings {
|
||||
unlock_keep_secret: cfg.enable_fast_unlock,
|
||||
blacklisted_accounts: match *spec {
|
||||
SpecType::Morden
|
||||
| SpecType::Ropsten
|
||||
| SpecType::Kovan
|
||||
| SpecType::Goerli
|
||||
| SpecType::Sokol
|
||||
| SpecType::Dev => vec![],
|
||||
_ => vec![H160::from_str("00a329c0648769a73afac7f9381e08fb43dbea72")
|
||||
.expect("the string is valid hex; qed")],
|
||||
},
|
||||
};
|
||||
|
||||
let ethstore = EthStore::open_with_iterations(dir, cfg.iterations)
|
||||
.map_err(|e| format!("Could not open keys directory: {}", e))?;
|
||||
if cfg.refresh_time > 0 {
|
||||
ethstore.set_refresh_time(::std::time::Duration::from_secs(cfg.refresh_time));
|
||||
}
|
||||
let account_provider = AccountProvider::new(Box::new(ethstore), account_settings);
|
||||
|
||||
// Add development account if running dev chain:
|
||||
if let SpecType::Dev = *spec {
|
||||
insert_dev_account(&account_provider);
|
||||
}
|
||||
|
||||
for a in cfg.unlocked_accounts {
|
||||
// Check if the account exists
|
||||
if !account_provider.has_account(a) {
|
||||
return Err(format!(
|
||||
"Account {} not found for the current chain. {}",
|
||||
a,
|
||||
build_create_account_hint(spec, &dirs.keys)
|
||||
));
|
||||
}
|
||||
|
||||
// Check if any passwords have been read from the password file(s)
|
||||
if passwords.is_empty() {
|
||||
return Err(format!(
|
||||
"No password found to unlock account {}. {}",
|
||||
a, VERIFY_PASSWORD_HINT
|
||||
));
|
||||
}
|
||||
|
||||
if !passwords.iter().any(|p| {
|
||||
account_provider
|
||||
.unlock_account_permanently(a, (*p).clone())
|
||||
.is_ok()
|
||||
}) {
|
||||
return Err(format!(
|
||||
"No valid password to unlock account {}. {}",
|
||||
a, VERIFY_PASSWORD_HINT
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(account_provider)
|
||||
}
|
||||
|
||||
pub struct LocalAccounts(Arc<AccountProvider>);
|
||||
impl ::ethcore::miner::LocalAccounts for LocalAccounts {
|
||||
fn is_local(&self, address: &Address) -> bool {
|
||||
self.0.has_account(*address)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn miner_local_accounts(account_provider: Arc<AccountProvider>) -> LocalAccounts {
|
||||
LocalAccounts(account_provider)
|
||||
}
|
||||
|
||||
pub fn miner_author(
|
||||
spec: &SpecType,
|
||||
dirs: &Directories,
|
||||
account_provider: &Arc<AccountProvider>,
|
||||
engine_signer: Address,
|
||||
passwords: &[Password],
|
||||
) -> Result<Option<::ethcore::miner::Author>, String> {
|
||||
use ethcore::engines::EngineSigner;
|
||||
|
||||
// Check if engine signer exists
|
||||
if !account_provider.has_account(engine_signer) {
|
||||
return Err(format!(
|
||||
"Consensus signer account not found for the current chain. {}",
|
||||
build_create_account_hint(spec, &dirs.keys)
|
||||
));
|
||||
}
|
||||
|
||||
// Check if any passwords have been read from the password file(s)
|
||||
if passwords.is_empty() {
|
||||
return Err(format!(
|
||||
"No password found for the consensus signer {}. {}",
|
||||
engine_signer, VERIFY_PASSWORD_HINT
|
||||
));
|
||||
}
|
||||
|
||||
let mut author = None;
|
||||
for password in passwords {
|
||||
let signer = parity_rpc::signer::EngineSigner::new(
|
||||
account_provider.clone(),
|
||||
engine_signer,
|
||||
password.clone(),
|
||||
);
|
||||
// sign dummy msg to check if password and account can be used.
|
||||
if signer.sign(H256::from_low_u64_be(1)).is_ok() {
|
||||
author = Some(::ethcore::miner::Author::Sealer(Box::new(signer)));
|
||||
}
|
||||
}
|
||||
if author.is_none() {
|
||||
return Err(format!(
|
||||
"No valid password for the consensus signer {}. {}",
|
||||
engine_signer, VERIFY_PASSWORD_HINT
|
||||
));
|
||||
}
|
||||
|
||||
Ok(author)
|
||||
}
|
||||
|
||||
pub fn accounts_list(
|
||||
account_provider: Arc<AccountProvider>,
|
||||
) -> Arc<dyn Fn() -> Vec<Address> + Send + Sync> {
|
||||
Arc::new(move || account_provider.accounts().unwrap_or_default())
|
||||
}
|
||||
|
||||
fn insert_dev_account(account_provider: &AccountProvider) {
|
||||
let secret = publickey::Secret::from_str(
|
||||
"4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7",
|
||||
)
|
||||
.expect("Valid account;qed");
|
||||
let dev_account = publickey::KeyPair::from_secret(secret.clone())
|
||||
.expect("Valid secret produces valid key;qed");
|
||||
if !account_provider.has_account(dev_account.address()) {
|
||||
match account_provider.insert_account(secret, &Password::from(String::new())) {
|
||||
Err(e) => warn!("Unable to add development account: {}", e),
|
||||
Ok(address) => {
|
||||
let _ = account_provider
|
||||
.set_account_name(address.clone(), "Development Account".into());
|
||||
let _ = account_provider.set_account_meta(
|
||||
address,
|
||||
::serde_json::to_string(
|
||||
&(vec![
|
||||
(
|
||||
"description",
|
||||
"Never use this account outside of development chain!",
|
||||
),
|
||||
("passwordHint", "Password is empty string"),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<::std::collections::HashMap<_, _>>()),
|
||||
)
|
||||
.expect("Serialization of hashmap does not fail."),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct an error `String` with an adaptive hint on how to create an account.
|
||||
fn build_create_account_hint(spec: &SpecType, keys: &str) -> String {
|
||||
format!("You can create an account via RPC, UI or `openethereum account new --chain {} --keys-path {}`.", spec, keys)
|
||||
}
|
||||
}
|
||||
|
||||
pub use self::accounts::{
|
||||
accounts_list, miner_author, miner_local_accounts, prepare_account_provider, AccountProvider,
|
||||
};
|
551
bin/oe/blockchain.rs
Normal file
551
bin/oe/blockchain.rs
Normal file
@ -0,0 +1,551 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{fs, io, sync::Arc, time::Instant};
|
||||
|
||||
use crate::{
|
||||
bytes::ToPretty,
|
||||
cache::CacheConfig,
|
||||
db,
|
||||
hash::{keccak, KECCAK_NULL_RLP},
|
||||
helpers::{execute_upgrades, to_client_config},
|
||||
informant::{FullNodeInformantData, Informant, MillisecondDuration},
|
||||
params::{fatdb_switch_to_bool, tracing_switch_to_bool, Pruning, SpecType, Switch},
|
||||
types::data_format::DataFormat,
|
||||
user_defaults::UserDefaults,
|
||||
};
|
||||
use ansi_term::Colour;
|
||||
use dir::Directories;
|
||||
use ethcore::{
|
||||
client::{
|
||||
Balance, BlockChainClient, BlockChainReset, BlockId, DatabaseCompactionProfile,
|
||||
ImportExportBlocks, Mode, Nonce, VMType,
|
||||
},
|
||||
miner::Miner,
|
||||
verification::queue::VerifierSettings,
|
||||
};
|
||||
use ethcore_service::ClientService;
|
||||
use ethereum_types::{Address, H256, U256};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BlockchainCmd {
|
||||
Kill(KillBlockchain),
|
||||
Import(ImportBlockchain),
|
||||
Export(ExportBlockchain),
|
||||
ExportState(ExportState),
|
||||
Reset(ResetBlockchain),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ResetBlockchain {
|
||||
pub dirs: Directories,
|
||||
pub spec: SpecType,
|
||||
pub pruning: Pruning,
|
||||
pub pruning_history: u64,
|
||||
pub pruning_memory: usize,
|
||||
pub tracing: Switch,
|
||||
pub fat_db: Switch,
|
||||
pub compaction: DatabaseCompactionProfile,
|
||||
pub cache_config: CacheConfig,
|
||||
pub num: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct KillBlockchain {
|
||||
pub spec: SpecType,
|
||||
pub dirs: Directories,
|
||||
pub pruning: Pruning,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ImportBlockchain {
|
||||
pub spec: SpecType,
|
||||
pub cache_config: CacheConfig,
|
||||
pub dirs: Directories,
|
||||
pub file_path: Option<String>,
|
||||
pub format: Option<DataFormat>,
|
||||
pub pruning: Pruning,
|
||||
pub pruning_history: u64,
|
||||
pub pruning_memory: usize,
|
||||
pub compaction: DatabaseCompactionProfile,
|
||||
pub tracing: Switch,
|
||||
pub fat_db: Switch,
|
||||
pub vm_type: VMType,
|
||||
pub check_seal: bool,
|
||||
pub with_color: bool,
|
||||
pub verifier_settings: VerifierSettings,
|
||||
pub max_round_blocks_to_import: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ExportBlockchain {
|
||||
pub spec: SpecType,
|
||||
pub cache_config: CacheConfig,
|
||||
pub dirs: Directories,
|
||||
pub file_path: Option<String>,
|
||||
pub format: Option<DataFormat>,
|
||||
pub pruning: Pruning,
|
||||
pub pruning_history: u64,
|
||||
pub pruning_memory: usize,
|
||||
pub compaction: DatabaseCompactionProfile,
|
||||
pub fat_db: Switch,
|
||||
pub tracing: Switch,
|
||||
pub from_block: BlockId,
|
||||
pub to_block: BlockId,
|
||||
pub check_seal: bool,
|
||||
pub max_round_blocks_to_import: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ExportState {
|
||||
pub spec: SpecType,
|
||||
pub cache_config: CacheConfig,
|
||||
pub dirs: Directories,
|
||||
pub file_path: Option<String>,
|
||||
pub format: Option<DataFormat>,
|
||||
pub pruning: Pruning,
|
||||
pub pruning_history: u64,
|
||||
pub pruning_memory: usize,
|
||||
pub compaction: DatabaseCompactionProfile,
|
||||
pub fat_db: Switch,
|
||||
pub tracing: Switch,
|
||||
pub at: BlockId,
|
||||
pub storage: bool,
|
||||
pub code: bool,
|
||||
pub min_balance: Option<U256>,
|
||||
pub max_balance: Option<U256>,
|
||||
pub max_round_blocks_to_import: usize,
|
||||
}
|
||||
|
||||
pub fn execute(cmd: BlockchainCmd) -> Result<(), String> {
|
||||
match cmd {
|
||||
BlockchainCmd::Kill(kill_cmd) => kill_db(kill_cmd),
|
||||
BlockchainCmd::Import(import_cmd) => execute_import(import_cmd),
|
||||
BlockchainCmd::Export(export_cmd) => execute_export(export_cmd),
|
||||
BlockchainCmd::ExportState(export_cmd) => execute_export_state(export_cmd),
|
||||
BlockchainCmd::Reset(reset_cmd) => execute_reset(reset_cmd),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_import(cmd: ImportBlockchain) -> Result<(), String> {
|
||||
let timer = Instant::now();
|
||||
|
||||
// load spec file
|
||||
let spec = cmd.spec.spec(&cmd.dirs.cache)?;
|
||||
|
||||
// load genesis hash
|
||||
let genesis_hash = spec.genesis_header().hash();
|
||||
|
||||
// database paths
|
||||
let db_dirs = cmd.dirs.database(genesis_hash, None, spec.data_dir.clone());
|
||||
|
||||
// user defaults path
|
||||
let user_defaults_path = db_dirs.user_defaults_path();
|
||||
|
||||
// load user defaults
|
||||
let mut user_defaults = UserDefaults::load(&user_defaults_path)?;
|
||||
|
||||
// select pruning algorithm
|
||||
let algorithm = cmd.pruning.to_algorithm(&user_defaults);
|
||||
|
||||
// check if tracing is on
|
||||
let tracing = tracing_switch_to_bool(cmd.tracing, &user_defaults)?;
|
||||
|
||||
// check if fatdb is on
|
||||
let fat_db = fatdb_switch_to_bool(cmd.fat_db, &user_defaults, algorithm)?;
|
||||
|
||||
// prepare client and snapshot paths.
|
||||
let client_path = db_dirs.client_path(algorithm);
|
||||
let snapshot_path = db_dirs.snapshot_path();
|
||||
|
||||
// execute upgrades
|
||||
execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, &cmd.compaction)?;
|
||||
|
||||
// create dirs used by parity
|
||||
cmd.dirs.create_dirs(false, false)?;
|
||||
|
||||
// prepare client config
|
||||
let mut client_config = to_client_config(
|
||||
&cmd.cache_config,
|
||||
spec.name.to_lowercase(),
|
||||
Mode::Active,
|
||||
tracing,
|
||||
fat_db,
|
||||
cmd.compaction,
|
||||
cmd.vm_type,
|
||||
"".into(),
|
||||
algorithm,
|
||||
cmd.pruning_history,
|
||||
cmd.pruning_memory,
|
||||
cmd.check_seal,
|
||||
12,
|
||||
);
|
||||
|
||||
client_config.queue.verifier_settings = cmd.verifier_settings;
|
||||
|
||||
let restoration_db_handler = db::restoration_db_handler(&client_path, &client_config);
|
||||
let client_db = restoration_db_handler
|
||||
.open(&client_path)
|
||||
.map_err(|e| format!("Failed to open database {:?}", e))?;
|
||||
|
||||
// build client
|
||||
let service = ClientService::start(
|
||||
client_config,
|
||||
&spec,
|
||||
client_db,
|
||||
&snapshot_path,
|
||||
restoration_db_handler,
|
||||
&cmd.dirs.ipc_path(),
|
||||
// TODO [ToDr] don't use test miner here
|
||||
// (actually don't require miner at all)
|
||||
Arc::new(Miner::new_for_tests(&spec, None)),
|
||||
)
|
||||
.map_err(|e| format!("Client service error: {:?}", e))?;
|
||||
|
||||
// free up the spec in memory.
|
||||
drop(spec);
|
||||
|
||||
let client = service.client();
|
||||
|
||||
let instream: Box<dyn io::Read> = match cmd.file_path {
|
||||
Some(f) => {
|
||||
Box::new(fs::File::open(&f).map_err(|_| format!("Cannot open given file: {}", f))?)
|
||||
}
|
||||
None => Box::new(io::stdin()),
|
||||
};
|
||||
|
||||
let informant = Arc::new(Informant::new(
|
||||
FullNodeInformantData {
|
||||
client: client.clone(),
|
||||
sync: None,
|
||||
net: None,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
cmd.with_color,
|
||||
));
|
||||
|
||||
service
|
||||
.register_io_handler(informant)
|
||||
.map_err(|_| "Unable to register informant handler".to_owned())?;
|
||||
|
||||
client.import_blocks(instream, cmd.format)?;
|
||||
|
||||
// save user defaults
|
||||
user_defaults.pruning = algorithm;
|
||||
user_defaults.tracing = tracing;
|
||||
user_defaults.fat_db = fat_db;
|
||||
user_defaults.save(&user_defaults_path)?;
|
||||
|
||||
let report = client.report();
|
||||
|
||||
let ms = timer.elapsed().as_milliseconds();
|
||||
info!("Import completed in {} seconds, {} blocks, {} blk/s, {} transactions, {} tx/s, {} Mgas, {} Mgas/s",
|
||||
ms / 1000,
|
||||
report.blocks_imported,
|
||||
(report.blocks_imported * 1000) as u64 / ms,
|
||||
report.transactions_applied,
|
||||
(report.transactions_applied * 1000) as u64 / ms,
|
||||
report.gas_processed / 1_000_000,
|
||||
(report.gas_processed / (ms * 1000)).low_u64(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_client(
|
||||
dirs: Directories,
|
||||
spec: SpecType,
|
||||
pruning: Pruning,
|
||||
pruning_history: u64,
|
||||
pruning_memory: usize,
|
||||
tracing: Switch,
|
||||
fat_db: Switch,
|
||||
compaction: DatabaseCompactionProfile,
|
||||
cache_config: CacheConfig,
|
||||
require_fat_db: bool,
|
||||
max_round_blocks_to_import: usize,
|
||||
) -> Result<ClientService, String> {
|
||||
// load spec file
|
||||
let spec = spec.spec(&dirs.cache)?;
|
||||
|
||||
// load genesis hash
|
||||
let genesis_hash = spec.genesis_header().hash();
|
||||
|
||||
// database paths
|
||||
let db_dirs = dirs.database(genesis_hash, None, spec.data_dir.clone());
|
||||
|
||||
// user defaults path
|
||||
let user_defaults_path = db_dirs.user_defaults_path();
|
||||
|
||||
// load user defaults
|
||||
let user_defaults = UserDefaults::load(&user_defaults_path)?;
|
||||
|
||||
// select pruning algorithm
|
||||
let algorithm = pruning.to_algorithm(&user_defaults);
|
||||
|
||||
// check if tracing is on
|
||||
let tracing = tracing_switch_to_bool(tracing, &user_defaults)?;
|
||||
|
||||
// check if fatdb is on
|
||||
let fat_db = fatdb_switch_to_bool(fat_db, &user_defaults, algorithm)?;
|
||||
if !fat_db && require_fat_db {
|
||||
return Err("This command requires OpenEthereum to be synced with --fat-db on.".to_owned());
|
||||
}
|
||||
|
||||
// prepare client and snapshot paths.
|
||||
let client_path = db_dirs.client_path(algorithm);
|
||||
let snapshot_path = db_dirs.snapshot_path();
|
||||
|
||||
// execute upgrades
|
||||
execute_upgrades(&dirs.base, &db_dirs, algorithm, &compaction)?;
|
||||
|
||||
// create dirs used by OpenEthereum.
|
||||
dirs.create_dirs(false, false)?;
|
||||
|
||||
// prepare client config
|
||||
let client_config = to_client_config(
|
||||
&cache_config,
|
||||
spec.name.to_lowercase(),
|
||||
Mode::Active,
|
||||
tracing,
|
||||
fat_db,
|
||||
compaction,
|
||||
VMType::default(),
|
||||
"".into(),
|
||||
algorithm,
|
||||
pruning_history,
|
||||
pruning_memory,
|
||||
true,
|
||||
max_round_blocks_to_import,
|
||||
);
|
||||
|
||||
let restoration_db_handler = db::restoration_db_handler(&client_path, &client_config);
|
||||
let client_db = restoration_db_handler
|
||||
.open(&client_path)
|
||||
.map_err(|e| format!("Failed to open database {:?}", e))?;
|
||||
|
||||
let service = ClientService::start(
|
||||
client_config,
|
||||
&spec,
|
||||
client_db,
|
||||
&snapshot_path,
|
||||
restoration_db_handler,
|
||||
&dirs.ipc_path(),
|
||||
// It's fine to use test version here,
|
||||
// since we don't care about miner parameters at all
|
||||
Arc::new(Miner::new_for_tests(&spec, None)),
|
||||
)
|
||||
.map_err(|e| format!("Client service error: {:?}", e))?;
|
||||
|
||||
drop(spec);
|
||||
Ok(service)
|
||||
}
|
||||
|
||||
fn execute_export(cmd: ExportBlockchain) -> Result<(), String> {
|
||||
let service = start_client(
|
||||
cmd.dirs,
|
||||
cmd.spec,
|
||||
cmd.pruning,
|
||||
cmd.pruning_history,
|
||||
cmd.pruning_memory,
|
||||
cmd.tracing,
|
||||
cmd.fat_db,
|
||||
cmd.compaction,
|
||||
cmd.cache_config,
|
||||
false,
|
||||
cmd.max_round_blocks_to_import,
|
||||
)?;
|
||||
let client = service.client();
|
||||
|
||||
let out: Box<dyn io::Write> = match cmd.file_path {
|
||||
Some(f) => Box::new(
|
||||
fs::File::create(&f).map_err(|_| format!("Cannot write to file given: {}", f))?,
|
||||
),
|
||||
None => Box::new(io::stdout()),
|
||||
};
|
||||
|
||||
client.export_blocks(out, cmd.from_block, cmd.to_block, cmd.format)?;
|
||||
|
||||
info!("Export completed.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_export_state(cmd: ExportState) -> Result<(), String> {
|
||||
let service = start_client(
|
||||
cmd.dirs,
|
||||
cmd.spec,
|
||||
cmd.pruning,
|
||||
cmd.pruning_history,
|
||||
cmd.pruning_memory,
|
||||
cmd.tracing,
|
||||
cmd.fat_db,
|
||||
cmd.compaction,
|
||||
cmd.cache_config,
|
||||
true,
|
||||
cmd.max_round_blocks_to_import,
|
||||
)?;
|
||||
|
||||
let client = service.client();
|
||||
|
||||
let mut out: Box<dyn io::Write> = match cmd.file_path {
|
||||
Some(f) => Box::new(
|
||||
fs::File::create(&f).map_err(|_| format!("Cannot write to file given: {}", f))?,
|
||||
),
|
||||
None => Box::new(io::stdout()),
|
||||
};
|
||||
|
||||
let mut last: Option<Address> = None;
|
||||
let at = cmd.at;
|
||||
let mut i = 0usize;
|
||||
|
||||
out.write_fmt(format_args!("{{ \"state\": {{",))
|
||||
.expect("Couldn't write to stream.");
|
||||
loop {
|
||||
let accounts = client
|
||||
.list_accounts(at, last.as_ref(), 1000)
|
||||
.ok_or("Specified block not found")?;
|
||||
if accounts.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
for account in accounts.into_iter() {
|
||||
let balance = client
|
||||
.balance(&account, at.into())
|
||||
.unwrap_or_else(U256::zero);
|
||||
if cmd.min_balance.map_or(false, |m| balance < m)
|
||||
|| cmd.max_balance.map_or(false, |m| balance > m)
|
||||
{
|
||||
last = Some(account);
|
||||
continue; //filtered out
|
||||
}
|
||||
|
||||
if i != 0 {
|
||||
out.write(b",").expect("Write error");
|
||||
}
|
||||
out.write_fmt(format_args!(
|
||||
"\n\"0x{:x}\": {{\"balance\": \"{:x}\", \"nonce\": \"{:x}\"",
|
||||
account,
|
||||
balance,
|
||||
client.nonce(&account, at).unwrap_or_else(U256::zero)
|
||||
))
|
||||
.expect("Write error");
|
||||
let code = client
|
||||
.code(&account, at.into())
|
||||
.unwrap_or(None)
|
||||
.unwrap_or_else(Vec::new);
|
||||
if !code.is_empty() {
|
||||
out.write_fmt(format_args!(", \"code_hash\": \"0x{:x}\"", keccak(&code)))
|
||||
.expect("Write error");
|
||||
if cmd.code {
|
||||
out.write_fmt(format_args!(", \"code\": \"{}\"", code.to_hex()))
|
||||
.expect("Write error");
|
||||
}
|
||||
}
|
||||
let storage_root = client.storage_root(&account, at).unwrap_or(KECCAK_NULL_RLP);
|
||||
if storage_root != KECCAK_NULL_RLP {
|
||||
out.write_fmt(format_args!(", \"storage_root\": \"0x{:x}\"", storage_root))
|
||||
.expect("Write error");
|
||||
if cmd.storage {
|
||||
out.write_fmt(format_args!(", \"storage\": {{"))
|
||||
.expect("Write error");
|
||||
let mut last_storage: Option<H256> = None;
|
||||
loop {
|
||||
let keys = client
|
||||
.list_storage(at, &account, last_storage.as_ref(), 1000)
|
||||
.ok_or("Specified block not found")?;
|
||||
if keys.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
for key in keys.into_iter() {
|
||||
if last_storage.is_some() {
|
||||
out.write(b",").expect("Write error");
|
||||
}
|
||||
out.write_fmt(format_args!(
|
||||
"\n\t\"0x{:x}\": \"0x{:x}\"",
|
||||
key,
|
||||
client
|
||||
.storage_at(&account, &key, at.into())
|
||||
.unwrap_or_else(Default::default)
|
||||
))
|
||||
.expect("Write error");
|
||||
last_storage = Some(key);
|
||||
}
|
||||
}
|
||||
out.write(b"\n}").expect("Write error");
|
||||
}
|
||||
}
|
||||
out.write(b"}").expect("Write error");
|
||||
i += 1;
|
||||
if i % 10000 == 0 {
|
||||
info!("Account #{}", i);
|
||||
}
|
||||
last = Some(account);
|
||||
}
|
||||
}
|
||||
out.write_fmt(format_args!("\n}}}}")).expect("Write error");
|
||||
info!("Export completed.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_reset(cmd: ResetBlockchain) -> Result<(), String> {
|
||||
let service = start_client(
|
||||
cmd.dirs,
|
||||
cmd.spec,
|
||||
cmd.pruning,
|
||||
cmd.pruning_history,
|
||||
cmd.pruning_memory,
|
||||
cmd.tracing,
|
||||
cmd.fat_db,
|
||||
cmd.compaction,
|
||||
cmd.cache_config,
|
||||
false,
|
||||
0,
|
||||
)?;
|
||||
|
||||
let client = service.client();
|
||||
client.reset(cmd.num)?;
|
||||
info!("{}", Colour::Green.bold().paint("Successfully reset db!"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn kill_db(cmd: KillBlockchain) -> Result<(), String> {
|
||||
let spec = cmd.spec.spec(&cmd.dirs.cache)?;
|
||||
let genesis_hash = spec.genesis_header().hash();
|
||||
let db_dirs = cmd.dirs.database(genesis_hash, None, spec.data_dir);
|
||||
let user_defaults_path = db_dirs.user_defaults_path();
|
||||
let mut user_defaults = UserDefaults::load(&user_defaults_path)?;
|
||||
let algorithm = cmd.pruning.to_algorithm(&user_defaults);
|
||||
let dir = db_dirs.db_path(algorithm);
|
||||
fs::remove_dir_all(&dir).map_err(|e| format!("Error removing database: {:?}", e))?;
|
||||
user_defaults.is_first_launch = true;
|
||||
user_defaults.save(&user_defaults_path)?;
|
||||
info!("Database deleted.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::DataFormat;
|
||||
|
||||
#[test]
|
||||
fn test_data_format_parsing() {
|
||||
assert_eq!(DataFormat::Binary, "binary".parse().unwrap());
|
||||
assert_eq!(DataFormat::Binary, "bin".parse().unwrap());
|
||||
assert_eq!(DataFormat::Hex, "hex".parse().unwrap());
|
||||
}
|
||||
}
|
142
bin/oe/cache.rs
Normal file
142
bin/oe/cache.rs
Normal file
@ -0,0 +1,142 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::cmp::max;
|
||||
|
||||
const MIN_BC_CACHE_MB: u32 = 4;
|
||||
const MIN_DB_CACHE_MB: u32 = 8;
|
||||
const MIN_BLOCK_QUEUE_SIZE_LIMIT_MB: u32 = 16;
|
||||
const DEFAULT_DB_CACHE_SIZE: u32 = 128;
|
||||
const DEFAULT_BC_CACHE_SIZE: u32 = 8;
|
||||
const DEFAULT_BLOCK_QUEUE_SIZE_LIMIT_MB: u32 = 40;
|
||||
const DEFAULT_TRACE_CACHE_SIZE: u32 = 20;
|
||||
const DEFAULT_STATE_CACHE_SIZE: u32 = 25;
|
||||
|
||||
/// Configuration for application cache sizes.
|
||||
/// All values are represented in MB.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct CacheConfig {
|
||||
/// Size of rocksDB cache. Almost all goes to the state column.
|
||||
db: u32,
|
||||
/// Size of blockchain cache.
|
||||
blockchain: u32,
|
||||
/// Size of transaction queue cache.
|
||||
queue: u32,
|
||||
/// Size of traces cache.
|
||||
traces: u32,
|
||||
/// Size of the state cache.
|
||||
state: u32,
|
||||
}
|
||||
|
||||
impl Default for CacheConfig {
|
||||
fn default() -> Self {
|
||||
CacheConfig::new(
|
||||
DEFAULT_DB_CACHE_SIZE,
|
||||
DEFAULT_BC_CACHE_SIZE,
|
||||
DEFAULT_BLOCK_QUEUE_SIZE_LIMIT_MB,
|
||||
DEFAULT_STATE_CACHE_SIZE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheConfig {
|
||||
/// Creates new cache config with cumulative size equal `total`.
|
||||
pub fn new_with_total_cache_size(total: u32) -> Self {
|
||||
CacheConfig {
|
||||
db: total * 7 / 10,
|
||||
blockchain: total / 10,
|
||||
queue: DEFAULT_BLOCK_QUEUE_SIZE_LIMIT_MB,
|
||||
traces: DEFAULT_TRACE_CACHE_SIZE,
|
||||
state: total * 2 / 10,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new cache config with gitven details.
|
||||
pub fn new(db: u32, blockchain: u32, queue: u32, state: u32) -> Self {
|
||||
CacheConfig {
|
||||
db: db,
|
||||
blockchain: blockchain,
|
||||
queue: queue,
|
||||
traces: DEFAULT_TRACE_CACHE_SIZE,
|
||||
state: state,
|
||||
}
|
||||
}
|
||||
|
||||
/// Size of db cache.
|
||||
pub fn db_cache_size(&self) -> u32 {
|
||||
max(MIN_DB_CACHE_MB, self.db)
|
||||
}
|
||||
|
||||
/// Size of block queue size limit
|
||||
pub fn queue(&self) -> u32 {
|
||||
max(self.queue, MIN_BLOCK_QUEUE_SIZE_LIMIT_MB)
|
||||
}
|
||||
|
||||
/// Size of the blockchain cache.
|
||||
pub fn blockchain(&self) -> u32 {
|
||||
max(self.blockchain, MIN_BC_CACHE_MB)
|
||||
}
|
||||
|
||||
/// Size of the traces cache.
|
||||
pub fn traces(&self) -> u32 {
|
||||
self.traces
|
||||
}
|
||||
|
||||
/// Size of the state cache.
|
||||
pub fn state(&self) -> u32 {
|
||||
self.state * 3 / 4
|
||||
}
|
||||
|
||||
/// Size of the jump-tables cache.
|
||||
pub fn jump_tables(&self) -> u32 {
|
||||
self.state / 4
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::CacheConfig;
|
||||
|
||||
#[test]
|
||||
fn test_cache_config_constructor() {
|
||||
let config = CacheConfig::new_with_total_cache_size(200);
|
||||
assert_eq!(config.db, 140);
|
||||
assert_eq!(config.blockchain(), 20);
|
||||
assert_eq!(config.queue(), 40);
|
||||
assert_eq!(config.state(), 30);
|
||||
assert_eq!(config.jump_tables(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_config_db_cache_sizes() {
|
||||
let config = CacheConfig::new_with_total_cache_size(400);
|
||||
assert_eq!(config.db, 280);
|
||||
assert_eq!(config.db_cache_size(), 280);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cache_config_default() {
|
||||
assert_eq!(
|
||||
CacheConfig::default(),
|
||||
CacheConfig::new(
|
||||
super::DEFAULT_DB_CACHE_SIZE,
|
||||
super::DEFAULT_BC_CACHE_SIZE,
|
||||
super::DEFAULT_BLOCK_QUEUE_SIZE_LIMIT_MB,
|
||||
super::DEFAULT_STATE_CACHE_SIZE
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
1655
bin/oe/cli/mod.rs
Normal file
1655
bin/oe/cli/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
11
bin/oe/cli/presets/config.dev-insecure.toml
Normal file
11
bin/oe/cli/presets/config.dev-insecure.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[parity]
|
||||
chain = "dev"
|
||||
|
||||
[mining]
|
||||
reseal_min_period = 0
|
||||
min_gas_price = 0
|
||||
|
||||
[rpc]
|
||||
interface = "all"
|
||||
apis = ["all"]
|
||||
hosts = ["all"]
|
6
bin/oe/cli/presets/config.dev.toml
Normal file
6
bin/oe/cli/presets/config.dev.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[parity]
|
||||
chain = "dev"
|
||||
|
||||
[mining]
|
||||
reseal_min_period = 0
|
||||
min_gas_price = 0
|
4
bin/oe/cli/presets/config.insecure.toml
Normal file
4
bin/oe/cli/presets/config.insecure.toml
Normal file
@ -0,0 +1,4 @@
|
||||
[rpc]
|
||||
interface = "all"
|
||||
apis = ["all"]
|
||||
hosts = ["all"]
|
28
bin/oe/cli/presets/config.mining.toml
Normal file
28
bin/oe/cli/presets/config.mining.toml
Normal file
@ -0,0 +1,28 @@
|
||||
[network]
|
||||
# OpenEthereum will try to maintain connection to at least 50 peers.
|
||||
min_peers = 50
|
||||
# OpenEthereum will maintain at most 100 peers.
|
||||
max_peers = 100
|
||||
|
||||
[ipc]
|
||||
# You won't be able to use IPC to interact with OpenEthereum.
|
||||
disable = true
|
||||
|
||||
[mining]
|
||||
# Prepare a block to seal even when there are no miners connected.
|
||||
force_sealing = true
|
||||
# New pending block will be created for all transactions (both local and external).
|
||||
reseal_on_txs = "all"
|
||||
# New pending block will be created only once per 4000 milliseconds.
|
||||
reseal_min_period = 4000
|
||||
# OpenEthereum will keep/relay at most 8192 transactions in queue.
|
||||
tx_queue_size = 8192
|
||||
tx_queue_per_sender = 128
|
||||
|
||||
[footprint]
|
||||
# If defined will never use more then 1024MB for all caches. (Overrides other cache settings).
|
||||
cache_size = 1024
|
||||
|
||||
[misc]
|
||||
# Logging pattern (`<module>=<level>`, e.g. `own_tx=trace`).
|
||||
logging = "miner=trace,own_tx=trace"
|
7
bin/oe/cli/presets/config.non-standard-ports.toml
Normal file
7
bin/oe/cli/presets/config.non-standard-ports.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[network]
|
||||
# OpenEthereum will listen for connections on port 30305.
|
||||
port = 30305
|
||||
|
||||
[rpc]
|
||||
# JSON-RPC over HTTP will be accessible on port 8645.
|
||||
port = 8645
|
28
bin/oe/cli/presets/mod.rs
Normal file
28
bin/oe/cli/presets/mod.rs
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::io::{Error, ErrorKind};
|
||||
|
||||
pub fn preset_config_string(arg: &str) -> Result<&'static str, Error> {
|
||||
match arg.to_lowercase().as_ref() {
|
||||
"dev" => Ok(include_str!("./config.dev.toml")),
|
||||
"mining" => Ok(include_str!("./config.mining.toml")),
|
||||
"non-standard-ports" => Ok(include_str!("./config.non-standard-ports.toml")),
|
||||
"insecure" => Ok(include_str!("./config.insecure.toml")),
|
||||
"dev-insecure" => Ok(include_str!("./config.dev-insecure.toml")),
|
||||
_ => Err(Error::new(ErrorKind::InvalidInput, "Config doesn't match any presets [dev, mining, non-standard-ports, insecure, dev-insecure]"))
|
||||
}
|
||||
}
|
126
bin/oe/cli/tests/config.full.toml
Normal file
126
bin/oe/cli/tests/config.full.toml
Normal file
@ -0,0 +1,126 @@
|
||||
[parity]
|
||||
mode = "last"
|
||||
mode_timeout = 300
|
||||
mode_alarm = 3600
|
||||
no_persistent_txqueue = false
|
||||
|
||||
chain = "homestead"
|
||||
base_path = "$HOME/.parity"
|
||||
db_path = "$HOME/.parity/chains"
|
||||
keys_path = "$HOME/.parity/keys"
|
||||
identity = ""
|
||||
|
||||
[account]
|
||||
unlock = ["0xdeadbeefcafe0000000000000000000000000000"]
|
||||
password = ["~/.safe/password.file"]
|
||||
keys_iterations = 10240
|
||||
|
||||
[ui]
|
||||
path = "$HOME/.parity/signer"
|
||||
|
||||
[network]
|
||||
port = 30303
|
||||
min_peers = 25
|
||||
max_peers = 50
|
||||
nat = "any"
|
||||
id = 1
|
||||
bootnodes = []
|
||||
discovery = true
|
||||
warp = true
|
||||
allow_ips = "all"
|
||||
snapshot_peers = 0
|
||||
max_pending_peers = 64
|
||||
|
||||
reserved_only = false
|
||||
reserved_peers = "./path_to_file"
|
||||
|
||||
[rpc]
|
||||
disable = false
|
||||
port = 8545
|
||||
interface = "local"
|
||||
cors = ["null"]
|
||||
apis = ["web3", "eth", "net", "parity", "traces", "rpc", "secretstore"]
|
||||
hosts = ["none"]
|
||||
allow_missing_blocks = false
|
||||
|
||||
[websockets]
|
||||
disable = false
|
||||
port = 8546
|
||||
interface = "local"
|
||||
origins = ["none"]
|
||||
apis = ["web3", "eth", "net", "parity", "traces", "rpc", "secretstore"]
|
||||
hosts = ["none"]
|
||||
|
||||
[ipc]
|
||||
disable = false
|
||||
path = "$HOME/.parity/jsonrpc.ipc"
|
||||
apis = ["web3", "eth", "net", "parity", "parity_accounts", "personal", "traces", "rpc", "secretstore"]
|
||||
|
||||
[secretstore]
|
||||
disable = false
|
||||
disable_http = false
|
||||
acl_contract = "registry"
|
||||
service_contract = "none"
|
||||
service_contract_srv_gen = "none"
|
||||
service_contract_srv_retr = "none"
|
||||
service_contract_doc_store = "none"
|
||||
service_contract_doc_sretr = "none"
|
||||
server_set_contract = "registry"
|
||||
nodes = []
|
||||
http_interface = "local"
|
||||
http_port = 8082
|
||||
interface = "local"
|
||||
port = 8083
|
||||
path = "$HOME/.parity/secretstore"
|
||||
|
||||
[mining]
|
||||
author = "0xdeadbeefcafe0000000000000000000000000001"
|
||||
engine_signer = "0xdeadbeefcafe0000000000000000000000000001"
|
||||
force_sealing = true
|
||||
reseal_on_txs = "all"
|
||||
reseal_min_period = 4000
|
||||
reseal_max_period = 60000
|
||||
work_queue_size = 20
|
||||
relay_set = "cheap"
|
||||
min_gas_price = 0
|
||||
usd_per_tx = "0.0001"
|
||||
usd_per_eth = "auto"
|
||||
price_update_period = "hourly"
|
||||
gas_floor_target = "8000000"
|
||||
gas_cap = "10000000"
|
||||
tx_queue_size = 8192
|
||||
tx_queue_locals = ["0xdeadbeefcafe0000000000000000000000000000"]
|
||||
tx_queue_strategy = "gas_factor"
|
||||
tx_queue_ban_count = 1
|
||||
tx_queue_ban_time = 180 #s
|
||||
tx_gas_limit = "10000000"
|
||||
tx_time_limit = 100 #ms
|
||||
tx_queue_no_unfamiliar_locals = false
|
||||
tx_queue_no_early_reject = false
|
||||
extra_data = "Parity"
|
||||
remove_solved = false
|
||||
notify_work = ["http://localhost:3001"]
|
||||
refuse_service_transactions = false
|
||||
|
||||
[footprint]
|
||||
tracing = "auto"
|
||||
pruning = "auto"
|
||||
pruning_history = 64
|
||||
pruning_memory = 500
|
||||
cache_size_db = 64
|
||||
cache_size_blocks = 8
|
||||
cache_size_queue = 50
|
||||
cache_size_state = 25
|
||||
cache_size = 128 # Overrides above caches with total size
|
||||
db_compaction = "ssd"
|
||||
fat_db = "auto"
|
||||
scale_verifiers = true
|
||||
num_verifiers = 6
|
||||
|
||||
[snapshots]
|
||||
enable = false
|
||||
|
||||
[misc]
|
||||
logging = "own_tx=trace"
|
||||
log_file = "/var/log/openethereum.log"
|
||||
color = true
|
2
bin/oe/cli/tests/config.invalid4.toml
Normal file
2
bin/oe/cli/tests/config.invalid4.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[account]
|
||||
invalid = 5
|
@ -8,9 +8,6 @@ chain = "./chain.json"
|
||||
unlock = ["0x1", "0x2", "0x3"]
|
||||
password = ["passwdfile path"]
|
||||
|
||||
[ui]
|
||||
disable = true
|
||||
|
||||
[network]
|
||||
warp = false
|
||||
discovery = true
|
||||
@ -24,6 +21,9 @@ allow_ips = "public"
|
||||
reserved_only = true
|
||||
reserved_peers = "./path/to/reserved_peers"
|
||||
|
||||
[websockets]
|
||||
disable = true
|
||||
origins = ["none"]
|
||||
|
||||
[rpc]
|
||||
disable = true
|
||||
@ -32,17 +32,15 @@ port = 8180
|
||||
[ipc]
|
||||
apis = ["rpc", "eth"]
|
||||
|
||||
[dapps]
|
||||
port = 8080
|
||||
user = "username"
|
||||
pass = "password"
|
||||
[metrics]
|
||||
enable = true
|
||||
interface = "local"
|
||||
port = 4000
|
||||
prefix = "oe"
|
||||
|
||||
[secretstore]
|
||||
port = 8082
|
||||
|
||||
[ipfs]
|
||||
enable = false
|
||||
port = 5001
|
||||
http_port = 8082
|
||||
port = 8083
|
||||
|
||||
[mining]
|
||||
author = "0xdeadbeefcafe0000000000000000000000000001"
|
||||
@ -50,15 +48,15 @@ engine_signer = "0xdeadbeefcafe0000000000000000000000000001"
|
||||
force_sealing = true
|
||||
reseal_on_txs = "all"
|
||||
reseal_min_period = 4000
|
||||
reseal_max_period = 60000
|
||||
price_update_period = "hourly"
|
||||
tx_queue_size = 1024
|
||||
tx_queue_gas = "auto"
|
||||
tx_queue_size = 8192
|
||||
|
||||
[footprint]
|
||||
tracing = "on"
|
||||
pruning = "fast"
|
||||
pruning_history = 64
|
||||
cache_size_db = 128
|
||||
cache_size_db = 256
|
||||
cache_size_blocks = 16
|
||||
cache_size_queue = 100
|
||||
cache_size_state = 25
|
||||
@ -67,12 +65,11 @@ fat_db = "off"
|
||||
scale_verifiers = false
|
||||
|
||||
[snapshots]
|
||||
disable_periodic = true
|
||||
|
||||
[vm]
|
||||
jit = false
|
||||
enable = false
|
||||
|
||||
[misc]
|
||||
logging = "own_tx=trace"
|
||||
log_file = "/var/log/parity.log"
|
||||
log_file = "/var/log/openethereum.log"
|
||||
color = true
|
||||
ports_shift = 0
|
||||
unsafe_expose = false
|
781
bin/oe/cli/usage.rs
Normal file
781
bin/oe/cli/usage.rs
Normal file
@ -0,0 +1,781 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
macro_rules! return_if_parse_error {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
Err(
|
||||
clap_error
|
||||
@
|
||||
ClapError {
|
||||
kind: ClapErrorKind::ValueValidation,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
return Err(clap_error);
|
||||
}
|
||||
|
||||
// Otherwise, if $e is ClapErrorKind::ArgumentNotFound or Ok(),
|
||||
// then convert to Option
|
||||
_ => $e.ok(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! if_option {
|
||||
(Option<$type:ty>, THEN {$($then:tt)*} ELSE {$($otherwise:tt)*}) => (
|
||||
$($then)*
|
||||
);
|
||||
($type:ty, THEN {$($then:tt)*} ELSE {$($otherwise:tt)*}) => (
|
||||
$($otherwise)*
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! if_vec {
|
||||
(Vec<$type:ty>, THEN {$($then:tt)*} ELSE {$($otherwise:tt)*}) => (
|
||||
$($then)*
|
||||
);
|
||||
($type:ty, THEN {$($then:tt)*} ELSE {$($otherwise:tt)*}) => (
|
||||
$($otherwise)*
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! if_option_vec {
|
||||
(Option<Vec<String>>, THEN {$then:expr} ELSE {$otherwise:expr}) => {
|
||||
$then
|
||||
};
|
||||
(Option<$type:ty>, THEN {$then:expr} ELSE {$otherwise:expr}) => {
|
||||
$otherwise
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! inner_option_type {
|
||||
(Option<$type:ty>) => {
|
||||
$type
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! inner_vec_type {
|
||||
(Vec<$type:ty>) => {
|
||||
$type
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! inner_option_vec_type {
|
||||
(Option<Vec<String>>) => {
|
||||
String
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! usage_with_ident {
|
||||
($name:expr, $usage:expr, $help:expr) => {
|
||||
if $usage.contains("<") {
|
||||
format!("<{}> {} '{}'", $name, $usage, $help)
|
||||
} else {
|
||||
format!("[{}] {} '{}'", $name, $usage, $help)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! underscore_to_hyphen {
|
||||
($e:expr) => {
|
||||
str::replace($e, "_", "-")
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! usage {
|
||||
(
|
||||
{
|
||||
$(
|
||||
CMD $subc:ident
|
||||
{
|
||||
$subc_help:expr,
|
||||
|
||||
$(
|
||||
CMD $subc_subc:ident
|
||||
{
|
||||
$subc_subc_help:expr,
|
||||
$(
|
||||
FLAG $subc_subc_flag:ident : (bool) = false, $subc_subc_flag_usage:expr, $subc_subc_flag_help:expr,
|
||||
)*
|
||||
$(
|
||||
ARG $subc_subc_arg:ident : ($($subc_subc_arg_type_tt:tt)+) = $subc_subc_arg_default:expr, $subc_subc_arg_usage:expr, $subc_subc_arg_help:expr,
|
||||
)*
|
||||
}
|
||||
)*
|
||||
|
||||
$(
|
||||
FLAG $subc_flag:ident : (bool) = false, $subc_flag_usage:expr, $subc_flag_help:expr,
|
||||
)*
|
||||
$(
|
||||
ARG $subc_arg:ident : ($($subc_arg_type_tt:tt)+) = $subc_arg_default:expr, $subc_arg_usage:expr, $subc_arg_help:expr,
|
||||
)*
|
||||
}
|
||||
)*
|
||||
}
|
||||
{
|
||||
$(
|
||||
[$group_name:expr]
|
||||
$(
|
||||
FLAG $flag:ident : (bool) = false, or $flag_from_config:expr, $flag_usage:expr, $flag_help:expr,
|
||||
)*
|
||||
$(
|
||||
ARG $arg:ident : ($($arg_type_tt:tt)+) = $arg_default:expr, or $arg_from_config:expr, $arg_usage:expr, $arg_help:expr,
|
||||
)*
|
||||
$(
|
||||
CHECK $check:expr,
|
||||
)*
|
||||
)*
|
||||
}
|
||||
) => {
|
||||
use toml;
|
||||
use std::{fs, io, process, cmp};
|
||||
use std::io::Read;
|
||||
use parity_version::version;
|
||||
use clap::{Arg, App, SubCommand, AppSettings, ArgSettings, Error as ClapError, ErrorKind as ClapErrorKind};
|
||||
use dir::helpers::replace_home;
|
||||
use std::ffi::OsStr;
|
||||
use std::collections::HashMap;
|
||||
|
||||
extern crate textwrap;
|
||||
extern crate term_size;
|
||||
use self::textwrap::{Wrapper};
|
||||
|
||||
const MAX_TERM_WIDTH: usize = 120;
|
||||
|
||||
#[cfg(test)]
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ArgsError {
|
||||
Clap(ClapError),
|
||||
Decode(toml::de::Error),
|
||||
Config(String, io::Error),
|
||||
PeerConfiguration,
|
||||
}
|
||||
|
||||
impl ArgsError {
|
||||
pub fn exit(self) -> ! {
|
||||
match self {
|
||||
ArgsError::Clap(e) => e.exit(),
|
||||
ArgsError::Decode(e) => {
|
||||
eprintln!("You might have supplied invalid parameters in config file.");
|
||||
eprintln!("{}", e);
|
||||
process::exit(2)
|
||||
},
|
||||
ArgsError::Config(path, e) => {
|
||||
eprintln!("There was an error reading your config file at: {}", path);
|
||||
eprintln!("{}", e);
|
||||
process::exit(2)
|
||||
},
|
||||
ArgsError::PeerConfiguration => {
|
||||
eprintln!("You have supplied `min_peers` > `max_peers`");
|
||||
process::exit(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ClapError> for ArgsError {
|
||||
fn from(e: ClapError) -> Self {
|
||||
ArgsError::Clap(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml::de::Error> for ArgsError {
|
||||
fn from(e: toml::de::Error) -> Self {
|
||||
ArgsError::Decode(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parsed command line arguments.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Args {
|
||||
$(
|
||||
pub $subc: bool,
|
||||
|
||||
$(
|
||||
pub $subc_subc: bool,
|
||||
$(
|
||||
pub $subc_subc_flag: bool,
|
||||
)*
|
||||
$(
|
||||
pub $subc_subc_arg: $($subc_subc_arg_type_tt)+,
|
||||
)*
|
||||
)*
|
||||
|
||||
$(
|
||||
pub $subc_flag: bool,
|
||||
)*
|
||||
$(
|
||||
pub $subc_arg: $($subc_arg_type_tt)+,
|
||||
)*
|
||||
)*
|
||||
|
||||
$(
|
||||
$(
|
||||
pub $flag: bool,
|
||||
)*
|
||||
$(
|
||||
pub $arg: $($arg_type_tt)+,
|
||||
)*
|
||||
)*
|
||||
}
|
||||
|
||||
impl Default for Args {
|
||||
fn default() -> Self {
|
||||
Args {
|
||||
$(
|
||||
$subc: Default::default(),
|
||||
$(
|
||||
$subc_subc: Default::default(),
|
||||
$(
|
||||
$subc_subc_flag: Default::default(),
|
||||
)*
|
||||
$(
|
||||
$subc_subc_arg: Default::default(),
|
||||
)*
|
||||
)*
|
||||
|
||||
$(
|
||||
$subc_flag: Default::default(),
|
||||
)*
|
||||
$(
|
||||
$subc_arg: Default::default(),
|
||||
)*
|
||||
)*
|
||||
|
||||
$(
|
||||
$(
|
||||
$flag: Default::default(),
|
||||
)*
|
||||
$(
|
||||
$arg: Default::default(),
|
||||
)*
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Clone, Deserialize)]
|
||||
struct RawArgs {
|
||||
$(
|
||||
$subc: bool,
|
||||
|
||||
$(
|
||||
$subc_subc: bool,
|
||||
$(
|
||||
$subc_subc_flag: bool,
|
||||
)*
|
||||
$(
|
||||
$subc_subc_arg: if_option!(
|
||||
$($subc_subc_arg_type_tt)+,
|
||||
THEN { $($subc_subc_arg_type_tt)+ }
|
||||
ELSE { Option<$($subc_subc_arg_type_tt)+> }
|
||||
),
|
||||
)*
|
||||
)*
|
||||
|
||||
$(
|
||||
$subc_flag: bool,
|
||||
)*
|
||||
$(
|
||||
$subc_arg: if_option!(
|
||||
$($subc_arg_type_tt)+,
|
||||
THEN { $($subc_arg_type_tt)+ }
|
||||
ELSE { Option<$($subc_arg_type_tt)+> }
|
||||
),
|
||||
)*
|
||||
|
||||
)*
|
||||
$(
|
||||
$(
|
||||
$flag: bool,
|
||||
)*
|
||||
|
||||
$(
|
||||
$arg: if_option!(
|
||||
$($arg_type_tt)+,
|
||||
THEN { $($arg_type_tt)+ }
|
||||
ELSE { Option<$($arg_type_tt)+> }
|
||||
),
|
||||
)*
|
||||
)*
|
||||
}
|
||||
|
||||
impl Args {
|
||||
|
||||
pub fn parse<S: AsRef<str>>(command: &[S]) -> Result<Self, ArgsError> {
|
||||
let raw_args = RawArgs::parse(command)?;
|
||||
|
||||
// Skip loading config file if no_config flag is specified
|
||||
if raw_args.flag_no_config {
|
||||
return Ok(raw_args.into_args(Config::default()));
|
||||
}
|
||||
|
||||
let config_file = raw_args.arg_config.clone().unwrap_or_else(|| raw_args.clone().into_args(Config::default()).arg_config);
|
||||
let config_file = replace_home(&::dir::default_data_path(), &config_file);
|
||||
|
||||
let args = match (fs::File::open(&config_file), raw_args.arg_config.clone()) {
|
||||
// Load config file
|
||||
(Ok(mut file), _) => {
|
||||
eprintln!("Loading config file from {}", &config_file);
|
||||
let mut config = String::new();
|
||||
file.read_to_string(&mut config).map_err(|e| ArgsError::Config(config_file, e))?;
|
||||
Ok(raw_args.into_args(Self::parse_config(&config)?))
|
||||
},
|
||||
// Don't display error in case default config cannot be loaded.
|
||||
(Err(_), None) => Ok(raw_args.into_args(Config::default())),
|
||||
// Config set from CLI (fail with error)
|
||||
(Err(_), Some(ref config_arg)) => {
|
||||
match presets::preset_config_string(config_arg) {
|
||||
Ok(s) => Ok(raw_args.into_args(Self::parse_config(&s)?)),
|
||||
Err(e) => Err(ArgsError::Config(config_file, e))
|
||||
}
|
||||
},
|
||||
}?;
|
||||
|
||||
$(
|
||||
$(
|
||||
$check(&args)?;
|
||||
)*
|
||||
)*
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn parse_without_config<S: AsRef<str>>(command: &[S]) -> Result<Self, ArgsError> {
|
||||
Self::parse_with_config(command, Config::default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn parse_with_config<S: AsRef<str>>(command: &[S], config: Config) -> Result<Self, ArgsError> {
|
||||
RawArgs::parse(command).map(|raw| raw.into_args(config)).map_err(ArgsError::Clap)
|
||||
}
|
||||
|
||||
fn parse_config(config: &str) -> Result<Config, ArgsError> {
|
||||
Ok(toml::from_str(config)?)
|
||||
}
|
||||
|
||||
pub fn print_version() -> String {
|
||||
format!(include_str!("./version.txt"), version())
|
||||
}
|
||||
|
||||
#[allow(unused_mut)] // subc_subc_exist may be assigned true by the macro
|
||||
#[allow(unused_assignments)] // Rust issue #22630
|
||||
pub fn print_help() -> String {
|
||||
|
||||
const TAB: &str = " ";
|
||||
const TAB_TAB: &str = " ";
|
||||
|
||||
let term_width = match term_size::dimensions() {
|
||||
None => MAX_TERM_WIDTH,
|
||||
Some((w, _)) => {
|
||||
cmp::min(w, MAX_TERM_WIDTH)
|
||||
}
|
||||
};
|
||||
|
||||
let mut help : String = include_str!("./usage_header.txt").to_owned();
|
||||
|
||||
help.push_str("\n");
|
||||
|
||||
// Subcommands
|
||||
let mut subcommands_wrapper = Wrapper::new(term_width).subsequent_indent(TAB);
|
||||
help.push_str("openethereum [options]\n");
|
||||
$(
|
||||
{
|
||||
let mut subc_subc_exist = false;
|
||||
|
||||
$(
|
||||
subc_subc_exist = true;
|
||||
let subc_subc_usages : Vec<&str> = vec![
|
||||
$(
|
||||
concat!("[",$subc_subc_flag_usage,"]"),
|
||||
)*
|
||||
$(
|
||||
$subc_subc_arg_usage,
|
||||
)*
|
||||
];
|
||||
|
||||
help.push_str(&subcommands_wrapper.fill(
|
||||
format!(
|
||||
"openethereum [options] {} {} {}\n",
|
||||
underscore_to_hyphen!(&stringify!($subc)[4..]),
|
||||
underscore_to_hyphen!(&stringify!($subc_subc)[stringify!($subc).len()+1..]),
|
||||
subc_subc_usages.join(" ")
|
||||
).as_ref())
|
||||
);
|
||||
)*
|
||||
|
||||
// Print the subcommand on its own only if it has no subsubcommands
|
||||
if !subc_subc_exist {
|
||||
let subc_usages : Vec<&str> = vec![
|
||||
$(
|
||||
concat!("[",$subc_flag_usage,"]"),
|
||||
)*
|
||||
$(
|
||||
$subc_arg_usage,
|
||||
)*
|
||||
];
|
||||
|
||||
help.push_str(&subcommands_wrapper.fill(
|
||||
format!(
|
||||
"openethereum [options] {} {}\n",
|
||||
underscore_to_hyphen!(&stringify!($subc)[4..]),
|
||||
subc_usages.join(" ")
|
||||
).as_ref())
|
||||
);
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
help.push_str("\n");
|
||||
|
||||
// Arguments and flags
|
||||
let args_wrapper = Wrapper::new(term_width).initial_indent(TAB_TAB).subsequent_indent(TAB_TAB);
|
||||
$(
|
||||
if $group_name != "Legacy Options" {
|
||||
help.push_str($group_name); help.push_str(":\n");
|
||||
$(
|
||||
help.push_str(&format!("{}{}\n{}\n",
|
||||
TAB, $flag_usage,
|
||||
args_wrapper.fill($flag_help)
|
||||
));
|
||||
help.push_str("\n");
|
||||
)*
|
||||
$(
|
||||
if_option!(
|
||||
$($arg_type_tt)+,
|
||||
THEN {
|
||||
if_option_vec!(
|
||||
$($arg_type_tt)+,
|
||||
THEN {
|
||||
help.push_str(&format!("{}{}\n{}\n",
|
||||
TAB, $arg_usage,
|
||||
args_wrapper.fill(format!(
|
||||
"{} (default: {:?})",
|
||||
$arg_help,
|
||||
{let x : inner_option_type!($($arg_type_tt)+)> = $arg_default; x}
|
||||
).as_ref())
|
||||
))
|
||||
}
|
||||
ELSE {
|
||||
help.push_str(&format!("{}{}\n{}\n",
|
||||
TAB, $arg_usage,
|
||||
args_wrapper.fill(format!(
|
||||
"{}{}",
|
||||
$arg_help,
|
||||
$arg_default.map(|x: inner_option_type!($($arg_type_tt)+)| format!(" (default: {})",x)).unwrap_or("".to_owned())
|
||||
).as_ref())
|
||||
))
|
||||
}
|
||||
)
|
||||
}
|
||||
ELSE {
|
||||
if_vec!(
|
||||
$($arg_type_tt)+,
|
||||
THEN {
|
||||
help.push_str(&format!("{}{}\n{}\n", TAB, $arg_usage,
|
||||
args_wrapper.fill(format!(
|
||||
"{} (default: {:?})",
|
||||
$arg_help,
|
||||
{let x : $($arg_type_tt)+ = $arg_default; x}
|
||||
).as_ref())
|
||||
))
|
||||
}
|
||||
ELSE {
|
||||
help.push_str(&format!("{}{}\n{}\n", TAB, $arg_usage,
|
||||
args_wrapper.fill(format!(
|
||||
"{} (default: {})",
|
||||
$arg_help,
|
||||
$arg_default
|
||||
).as_ref())
|
||||
))
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
help.push_str("\n");
|
||||
)*
|
||||
}
|
||||
)*
|
||||
help
|
||||
}
|
||||
}
|
||||
|
||||
impl RawArgs {
|
||||
fn into_args(self, config: Config) -> Args {
|
||||
let mut args = Args::default();
|
||||
$(
|
||||
args.$subc = self.$subc;
|
||||
|
||||
$(
|
||||
args.$subc_subc = self.$subc_subc;
|
||||
$(
|
||||
args.$subc_subc_flag = self.$subc_subc_flag;
|
||||
)*
|
||||
$(
|
||||
args.$subc_subc_arg = if_option!(
|
||||
$($subc_subc_arg_type_tt)+,
|
||||
THEN { self.$subc_subc_arg.or($subc_subc_arg_default) }
|
||||
ELSE { self.$subc_subc_arg.unwrap_or($subc_subc_arg_default.into()) }
|
||||
);
|
||||
)*
|
||||
)*
|
||||
|
||||
$(
|
||||
args.$subc_flag = self.$subc_flag;
|
||||
)*
|
||||
$(
|
||||
args.$subc_arg = if_option!(
|
||||
$($subc_arg_type_tt)+,
|
||||
THEN { self.$subc_arg.or($subc_arg_default) }
|
||||
ELSE { self.$subc_arg.unwrap_or($subc_arg_default.into()) }
|
||||
);
|
||||
)*
|
||||
)*
|
||||
|
||||
$(
|
||||
$(
|
||||
args.$flag = self.$flag || $flag_from_config(&config).unwrap_or(false);
|
||||
)*
|
||||
$(
|
||||
args.$arg = if_option!(
|
||||
$($arg_type_tt)+,
|
||||
THEN { self.$arg.or_else(|| $arg_from_config(&config)).or_else(|| $arg_default.into()) }
|
||||
ELSE { self.$arg.or_else(|| $arg_from_config(&config)).unwrap_or_else(|| $arg_default.into()) }
|
||||
);
|
||||
)*
|
||||
)*
|
||||
args
|
||||
}
|
||||
|
||||
#[allow(unused_variables)] // the submatches of arg-less subcommands aren't used
|
||||
pub fn parse<S: AsRef<str>>(command: &[S]) -> Result<Self, ClapError> {
|
||||
|
||||
let usages = vec![
|
||||
$(
|
||||
$(
|
||||
usage_with_ident!(stringify!($arg), $arg_usage, $arg_help),
|
||||
)*
|
||||
$(
|
||||
usage_with_ident!(stringify!($flag), $flag_usage, $flag_help),
|
||||
)*
|
||||
)*
|
||||
];
|
||||
|
||||
// Hash of subc|subc_subc => Vec<String>
|
||||
let mut subc_usages = HashMap::new();
|
||||
$(
|
||||
{
|
||||
let this_subc_usages = vec![
|
||||
$(
|
||||
usage_with_ident!(stringify!($subc_flag), $subc_flag_usage, $subc_flag_help),
|
||||
)*
|
||||
$(
|
||||
usage_with_ident!(stringify!($subc_arg), $subc_arg_usage, $subc_arg_help),
|
||||
)*
|
||||
];
|
||||
|
||||
subc_usages.insert(stringify!($subc),this_subc_usages);
|
||||
|
||||
$(
|
||||
{
|
||||
let this_subc_subc_usages = vec![
|
||||
$(
|
||||
usage_with_ident!(stringify!($subc_subc_flag), $subc_subc_flag_usage, $subc_subc_flag_help),
|
||||
)*
|
||||
$(
|
||||
usage_with_ident!(stringify!($subc_subc_arg), $subc_subc_arg_usage, $subc_subc_arg_help),
|
||||
)*
|
||||
];
|
||||
|
||||
subc_usages.insert(stringify!($subc_subc), this_subc_subc_usages);
|
||||
}
|
||||
)*
|
||||
}
|
||||
)*
|
||||
|
||||
let matches = App::new("OpenEthereum")
|
||||
.global_setting(AppSettings::VersionlessSubcommands)
|
||||
.global_setting(AppSettings::DisableHelpSubcommand)
|
||||
.max_term_width(MAX_TERM_WIDTH)
|
||||
.help(Args::print_help().as_ref())
|
||||
.args(&usages.iter().map(|u| {
|
||||
let mut arg = Arg::from_usage(u)
|
||||
.allow_hyphen_values(true) // Allow for example --allow-ips -10.0.0.0/8
|
||||
.global(true) // Argument doesn't have to come before the first subcommand
|
||||
.hidden(true); // Hide global arguments from the (subcommand) help messages generated by Clap
|
||||
|
||||
if arg.is_set(ArgSettings::Multiple) {
|
||||
arg = arg.require_delimiter(true); // Multiple values can only be separated by commas, not spaces (#7428)
|
||||
}
|
||||
|
||||
arg
|
||||
}).collect::<Vec<Arg>>())
|
||||
$(
|
||||
.subcommand(
|
||||
SubCommand::with_name(&underscore_to_hyphen!(&stringify!($subc)[4..]))
|
||||
.about($subc_help)
|
||||
.args(&subc_usages.get(stringify!($subc)).unwrap().iter().map(|u| Arg::from_usage(u).use_delimiter(false).allow_hyphen_values(true)).collect::<Vec<Arg>>())
|
||||
$(
|
||||
.setting(AppSettings::SubcommandRequired) // prevent from running `openethereum account`
|
||||
.subcommand(
|
||||
SubCommand::with_name(&underscore_to_hyphen!(&stringify!($subc_subc)[stringify!($subc).len()+1..]))
|
||||
.about($subc_subc_help)
|
||||
.args(&subc_usages.get(stringify!($subc_subc)).unwrap().iter().map(|u| Arg::from_usage(u).use_delimiter(false).allow_hyphen_values(true)).collect::<Vec<Arg>>())
|
||||
)
|
||||
)*
|
||||
)
|
||||
)*
|
||||
.get_matches_from_safe(command.iter().map(|x| OsStr::new(x.as_ref())))?;
|
||||
|
||||
let mut raw_args : RawArgs = Default::default();
|
||||
|
||||
// Globals
|
||||
$(
|
||||
$(
|
||||
raw_args.$flag = raw_args.$flag || matches.is_present(stringify!($flag));
|
||||
)*
|
||||
$(
|
||||
if let some @ Some(_) = return_if_parse_error!(if_option!(
|
||||
$($arg_type_tt)+,
|
||||
THEN {
|
||||
if_option_vec!(
|
||||
$($arg_type_tt)+,
|
||||
THEN { values_t!(matches, stringify!($arg), inner_option_vec_type!($($arg_type_tt)+)) }
|
||||
ELSE { value_t!(matches, stringify!($arg), inner_option_type!($($arg_type_tt)+)) }
|
||||
)
|
||||
}
|
||||
ELSE {
|
||||
if_vec!(
|
||||
$($arg_type_tt)+,
|
||||
THEN { values_t!(matches, stringify!($arg), inner_vec_type!($($arg_type_tt)+)) }
|
||||
ELSE { value_t!(matches, stringify!($arg), $($arg_type_tt)+) }
|
||||
)
|
||||
}
|
||||
)) {
|
||||
raw_args.$arg = some;
|
||||
}
|
||||
)*
|
||||
)*
|
||||
|
||||
// Subcommands
|
||||
$(
|
||||
if let Some(submatches) = matches.subcommand_matches(&underscore_to_hyphen!(&stringify!($subc)[4..])) {
|
||||
raw_args.$subc = true;
|
||||
|
||||
// Subcommand flags
|
||||
$(
|
||||
raw_args.$subc_flag = submatches.is_present(&stringify!($subc_flag));
|
||||
)*
|
||||
// Subcommand arguments
|
||||
$(
|
||||
raw_args.$subc_arg = return_if_parse_error!(if_option!(
|
||||
$($subc_arg_type_tt)+,
|
||||
THEN {
|
||||
if_option_vec!(
|
||||
$($subc_arg_type_tt)+,
|
||||
THEN { values_t!(submatches, stringify!($subc_arg), inner_option_vec_type!($($subc_arg_type_tt)+)) }
|
||||
ELSE { value_t!(submatches, stringify!($subc_arg), inner_option_type!($($subc_arg_type_tt)+)) }
|
||||
)
|
||||
}
|
||||
ELSE {
|
||||
if_vec!(
|
||||
$($subc_arg_type_tt)+,
|
||||
THEN { values_t!(submatches, stringify!($subc_arg), inner_vec_type!($($subc_arg_type_tt)+)) }
|
||||
ELSE { value_t!(submatches, stringify!($subc_arg), $($subc_arg_type_tt)+) }
|
||||
)
|
||||
}
|
||||
));
|
||||
)*
|
||||
// Sub-subcommands
|
||||
$(
|
||||
if let Some(subsubmatches) = submatches.subcommand_matches(&underscore_to_hyphen!(&stringify!($subc_subc)[stringify!($subc).len()+1..])) {
|
||||
raw_args.$subc_subc = true;
|
||||
|
||||
// Sub-subcommand flags
|
||||
$(
|
||||
raw_args.$subc_subc_flag = subsubmatches.is_present(&stringify!($subc_subc_flag));
|
||||
)*
|
||||
// Sub-subcommand arguments
|
||||
$(
|
||||
raw_args.$subc_subc_arg = return_if_parse_error!(if_option!(
|
||||
$($subc_subc_arg_type_tt)+,
|
||||
THEN {
|
||||
if_option_vec!(
|
||||
$($subc_subc_arg_type_tt)+,
|
||||
THEN { values_t!(subsubmatches, stringify!($subc_subc_arg), inner_option_vec_type!($($subc_subc_arg_type_tt)+)) }
|
||||
ELSE { value_t!(subsubmatches, stringify!($subc_subc_arg), inner_option_type!($($subc_subc_arg_type_tt)+)) }
|
||||
)
|
||||
}
|
||||
ELSE {
|
||||
if_vec!(
|
||||
$($subc_subc_arg_type_tt)+,
|
||||
THEN { values_t!(subsubmatches, stringify!($subc_subc_arg), inner_vec_type!($($subc_subc_arg_type_tt)+)) }
|
||||
ELSE { value_t!(subsubmatches, stringify!($subc_subc_arg), $($subc_subc_arg_type_tt)+) }
|
||||
)
|
||||
}
|
||||
));
|
||||
)*
|
||||
}
|
||||
else {
|
||||
raw_args.$subc_subc = false;
|
||||
}
|
||||
)*
|
||||
}
|
||||
else {
|
||||
raw_args.$subc = false;
|
||||
}
|
||||
)*
|
||||
|
||||
Ok(raw_args)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usages_valid() {
|
||||
let re = Regex::new(r"^(?:(-[a-zA-Z-]+, )?--[a-z-]+(=\[[a-zA-Z]+\](\.\.\.)?|=<[a-zA-Z]+>(\.\.\.)?)?)|(?:\[[a-zA-Z-]+\])(\.\.\.)?|(?:<[a-zA-Z-]+>)(\.\.\.)?$").unwrap();
|
||||
|
||||
let usages = vec![
|
||||
$(
|
||||
$(
|
||||
$(
|
||||
$subc_subc_arg_usage,
|
||||
)*
|
||||
)*
|
||||
$(
|
||||
$subc_arg_usage,
|
||||
)*
|
||||
)*
|
||||
$(
|
||||
$(
|
||||
$flag_usage,
|
||||
)*
|
||||
$(
|
||||
$arg_usage,
|
||||
)*
|
||||
)*
|
||||
];
|
||||
|
||||
for usage in &usages {
|
||||
assert!(re.is_match(usage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
6
bin/oe/cli/usage_header.txt
Normal file
6
bin/oe/cli/usage_header.txt
Normal file
@ -0,0 +1,6 @@
|
||||
OpenEthereum Client.
|
||||
By Wood/Paronyan/Kotewicz/Drwięga/Volf/Greeff
|
||||
Habermeier/Czaban/Gotchac/Redman/Nikolsky
|
||||
Schoedon/Tang/Adolfsson/Silva/Palm/Hirsz et al.
|
||||
Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
|
10
bin/oe/cli/version.txt
Normal file
10
bin/oe/cli/version.txt
Normal file
@ -0,0 +1,10 @@
|
||||
OpenEthereum Client.
|
||||
version {}
|
||||
Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
|
||||
This is free software: you are free to change and redistribute it.
|
||||
There is NO WARRANTY, to the extent permitted by law.
|
||||
|
||||
By Wood/Paronyan/Kotewicz/Drwięga/Volf/Greeff
|
||||
Habermeier/Czaban/Gotchac/Redman/Nikolsky
|
||||
Schoedon/Tang/Adolfsson/Silva/Palm/Hirsz et al.
|
2045
bin/oe/configuration.rs
Normal file
2045
bin/oe/configuration.rs
Normal file
File diff suppressed because it is too large
Load Diff
25
bin/oe/db/mod.rs
Normal file
25
bin/oe/db/mod.rs
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Database-related operations.
|
||||
|
||||
#[path = "rocksdb/mod.rs"]
|
||||
mod impls;
|
||||
|
||||
pub use self::impls::{migrate, restoration_db_handler};
|
||||
|
||||
#[cfg(feature = "secretstore")]
|
||||
pub use self::impls::open_secretstore_db;
|
83
bin/oe/db/rocksdb/blooms.rs
Normal file
83
bin/oe/db/rocksdb/blooms.rs
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Blooms migration from rocksdb to blooms-db
|
||||
|
||||
use super::{kvdb_rocksdb::DatabaseConfig, open_database};
|
||||
use ethcore::error::Error;
|
||||
use ethereum_types::Bloom;
|
||||
use rlp;
|
||||
use std::path::Path;
|
||||
|
||||
const LOG_BLOOMS_ELEMENTS_PER_INDEX: u64 = 16;
|
||||
|
||||
pub fn migrate_blooms<P: AsRef<Path>>(path: P, config: &DatabaseConfig) -> Result<(), Error> {
|
||||
// init
|
||||
let db = open_database(&path.as_ref().to_string_lossy(), config)?;
|
||||
|
||||
// possible optimization:
|
||||
// pre-allocate space on disk for faster migration
|
||||
|
||||
// iterate over header blooms and insert them in blooms-db
|
||||
// Some(3) -> COL_EXTRA
|
||||
// 3u8 -> ExtrasIndex::BlocksBlooms
|
||||
// 0u8 -> level 0
|
||||
let blooms_iterator = db
|
||||
.key_value()
|
||||
.iter_from_prefix(Some(3), &[3u8, 0u8])
|
||||
.filter(|(key, _)| key.len() == 6)
|
||||
.take_while(|(key, _)| key[0] == 3u8 && key[1] == 0u8)
|
||||
.map(|(key, group)| {
|
||||
let index = (key[2] as u64) << 24
|
||||
| (key[3] as u64) << 16
|
||||
| (key[4] as u64) << 8
|
||||
| (key[5] as u64);
|
||||
let number = index * LOG_BLOOMS_ELEMENTS_PER_INDEX;
|
||||
|
||||
let blooms = rlp::decode_list::<Bloom>(&group);
|
||||
(number, blooms)
|
||||
});
|
||||
|
||||
for (number, blooms) in blooms_iterator {
|
||||
db.blooms().insert_blooms(number, blooms.iter())?;
|
||||
}
|
||||
|
||||
// iterate over trace blooms and insert them in blooms-db
|
||||
// Some(4) -> COL_TRACE
|
||||
// 1u8 -> TraceDBIndex::BloomGroups
|
||||
// 0u8 -> level 0
|
||||
let trace_blooms_iterator = db
|
||||
.key_value()
|
||||
.iter_from_prefix(Some(4), &[1u8, 0u8])
|
||||
.filter(|(key, _)| key.len() == 6)
|
||||
.take_while(|(key, _)| key[0] == 1u8 && key[1] == 0u8)
|
||||
.map(|(key, group)| {
|
||||
let index = (key[2] as u64)
|
||||
| (key[3] as u64) << 8
|
||||
| (key[4] as u64) << 16
|
||||
| (key[5] as u64) << 24;
|
||||
let number = index * LOG_BLOOMS_ELEMENTS_PER_INDEX;
|
||||
|
||||
let blooms = rlp::decode_list::<Bloom>(&group);
|
||||
(number, blooms)
|
||||
});
|
||||
|
||||
for (number, blooms) in trace_blooms_iterator {
|
||||
db.trace_blooms().insert_blooms(number, blooms.iter())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
40
bin/oe/db/rocksdb/helpers.rs
Normal file
40
bin/oe/db/rocksdb/helpers.rs
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::kvdb_rocksdb::{CompactionProfile, DatabaseConfig};
|
||||
use ethcore::client::{ClientConfig, DatabaseCompactionProfile};
|
||||
use ethcore_db::NUM_COLUMNS;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn compaction_profile(
|
||||
profile: &DatabaseCompactionProfile,
|
||||
db_path: &Path,
|
||||
) -> CompactionProfile {
|
||||
match profile {
|
||||
&DatabaseCompactionProfile::Auto => CompactionProfile::auto(db_path),
|
||||
&DatabaseCompactionProfile::SSD => CompactionProfile::ssd(),
|
||||
&DatabaseCompactionProfile::HDD => CompactionProfile::hdd(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client_db_config(client_path: &Path, client_config: &ClientConfig) -> DatabaseConfig {
|
||||
let mut client_db_config = DatabaseConfig::with_columns(NUM_COLUMNS);
|
||||
|
||||
client_db_config.memory_budget = client_config.db_cache_size;
|
||||
client_db_config.compaction = compaction_profile(&client_config.db_compaction, &client_path);
|
||||
|
||||
client_db_config
|
||||
}
|
262
bin/oe/db/rocksdb/migration.rs
Normal file
262
bin/oe/db/rocksdb/migration.rs
Normal file
@ -0,0 +1,262 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::{
|
||||
kvdb_rocksdb::{CompactionProfile, DatabaseConfig},
|
||||
migration_rocksdb::{ChangeColumns, Config as MigrationConfig, Manager as MigrationManager},
|
||||
};
|
||||
use ethcore::{self, client::DatabaseCompactionProfile};
|
||||
use std::{
|
||||
fmt::{Display, Error as FmtError, Formatter},
|
||||
fs,
|
||||
io::{Error as IoError, ErrorKind, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use super::{blooms::migrate_blooms, helpers};
|
||||
|
||||
/// The migration from v10 to v11.
|
||||
/// Adds a column for node info.
|
||||
pub const TO_V11: ChangeColumns = ChangeColumns {
|
||||
pre_columns: Some(6),
|
||||
post_columns: Some(7),
|
||||
version: 11,
|
||||
};
|
||||
|
||||
/// The migration from v11 to v12.
|
||||
/// Adds a column for light chain storage.
|
||||
pub const TO_V12: ChangeColumns = ChangeColumns {
|
||||
pre_columns: Some(7),
|
||||
post_columns: Some(8),
|
||||
version: 12,
|
||||
};
|
||||
|
||||
/// Database is assumed to be at default version, when no version file is found.
|
||||
const DEFAULT_VERSION: u32 = 5;
|
||||
/// Current version of database models.
|
||||
const CURRENT_VERSION: u32 = 16;
|
||||
/// Until this version please use upgrade tool.
|
||||
const USE_MIGRATION_TOOL: u32 = 15;
|
||||
/// A version of database at which blooms-db was introduced
|
||||
const BLOOMS_DB_VERSION: u32 = 13;
|
||||
/// Defines how many items are migrated to the new version of database at once.
|
||||
const BATCH_SIZE: usize = 1024;
|
||||
/// Version file name.
|
||||
const VERSION_FILE_NAME: &'static str = "db_version";
|
||||
|
||||
/// Migration related erorrs.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Returned when current version cannot be read or guessed.
|
||||
UnknownDatabaseVersion,
|
||||
/// Existing DB is newer than the known one.
|
||||
FutureDBVersion,
|
||||
/// Migration is not possible.
|
||||
MigrationImpossible,
|
||||
/// For old versions use external migration tool
|
||||
UseMigrationTool,
|
||||
/// Blooms-db migration error.
|
||||
BloomsDB(ethcore::error::Error),
|
||||
/// Migration was completed succesfully,
|
||||
/// but there was a problem with io.
|
||||
Io(IoError),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
||||
let out = match *self {
|
||||
Error::UnknownDatabaseVersion => "Current database version cannot be read".into(),
|
||||
Error::FutureDBVersion => "Database was created with newer client version. Upgrade your client or delete DB and resync.".into(),
|
||||
Error::MigrationImpossible => format!("Database migration to version {} is not possible.", CURRENT_VERSION),
|
||||
Error::BloomsDB(ref err) => format!("blooms-db migration error: {}", err),
|
||||
Error::UseMigrationTool => "For db versions 15 and lower (v2.5.13=>13, 2.7.2=>14, v3.0.1=>15) please use upgrade db tool to manually upgrade db: https://github.com/openethereum/3.1-db-upgrade-tool".into(),
|
||||
Error::Io(ref err) => format!("Unexpected io error on DB migration: {}.", err),
|
||||
};
|
||||
|
||||
write!(f, "{}", out)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IoError> for Error {
|
||||
fn from(err: IoError) -> Self {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the version file path.
|
||||
fn version_file_path(path: &Path) -> PathBuf {
|
||||
let mut file_path = path.to_owned();
|
||||
file_path.push(VERSION_FILE_NAME);
|
||||
file_path
|
||||
}
|
||||
|
||||
/// Reads current database version from the file at given path.
|
||||
/// If the file does not exist returns `DEFAULT_VERSION`.
|
||||
fn current_version(path: &Path) -> Result<u32, Error> {
|
||||
match fs::File::open(version_file_path(path)) {
|
||||
Err(ref err) if err.kind() == ErrorKind::NotFound => Ok(DEFAULT_VERSION),
|
||||
Err(_) => Err(Error::UnknownDatabaseVersion),
|
||||
Ok(mut file) => {
|
||||
let mut s = String::new();
|
||||
file.read_to_string(&mut s)
|
||||
.map_err(|_| Error::UnknownDatabaseVersion)?;
|
||||
u32::from_str_radix(&s, 10).map_err(|_| Error::UnknownDatabaseVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes current database version to the file.
|
||||
/// Creates a new file if the version file does not exist yet.
|
||||
fn update_version(path: &Path) -> Result<(), Error> {
|
||||
fs::create_dir_all(path)?;
|
||||
let mut file = fs::File::create(version_file_path(path))?;
|
||||
file.write_all(format!("{}", CURRENT_VERSION).as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Consolidated database path
|
||||
fn consolidated_database_path(path: &Path) -> PathBuf {
|
||||
let mut state_path = path.to_owned();
|
||||
state_path.push("db");
|
||||
state_path
|
||||
}
|
||||
|
||||
/// Database backup
|
||||
fn backup_database_path(path: &Path) -> PathBuf {
|
||||
let mut backup_path = path.to_owned();
|
||||
backup_path.pop();
|
||||
backup_path.push("temp_backup");
|
||||
backup_path
|
||||
}
|
||||
|
||||
/// Default migration settings.
|
||||
pub fn default_migration_settings(compaction_profile: &CompactionProfile) -> MigrationConfig {
|
||||
MigrationConfig {
|
||||
batch_size: BATCH_SIZE,
|
||||
compaction_profile: *compaction_profile,
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrations on the consolidated database.
|
||||
fn consolidated_database_migrations(
|
||||
compaction_profile: &CompactionProfile,
|
||||
) -> Result<MigrationManager, Error> {
|
||||
let mut manager = MigrationManager::new(default_migration_settings(compaction_profile));
|
||||
manager
|
||||
.add_migration(TO_V11)
|
||||
.map_err(|_| Error::MigrationImpossible)?;
|
||||
manager
|
||||
.add_migration(TO_V12)
|
||||
.map_err(|_| Error::MigrationImpossible)?;
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
/// Migrates database at given position with given migration rules.
|
||||
fn migrate_database(
|
||||
version: u32,
|
||||
db_path: &Path,
|
||||
mut migrations: MigrationManager,
|
||||
) -> Result<(), Error> {
|
||||
// check if migration is needed
|
||||
if !migrations.is_needed(version) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let backup_path = backup_database_path(&db_path);
|
||||
// remove the backup dir if it exists
|
||||
let _ = fs::remove_dir_all(&backup_path);
|
||||
|
||||
// migrate old database to the new one
|
||||
let temp_path = migrations.execute(&db_path, version)?;
|
||||
|
||||
// completely in-place migration leads to the paths being equal.
|
||||
// in that case, no need to shuffle directories.
|
||||
if temp_path == db_path {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// create backup
|
||||
fs::rename(&db_path, &backup_path)?;
|
||||
|
||||
// replace the old database with the new one
|
||||
if let Err(err) = fs::rename(&temp_path, &db_path) {
|
||||
// if something went wrong, bring back backup
|
||||
fs::rename(&backup_path, &db_path)?;
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
// remove backup
|
||||
fs::remove_dir_all(&backup_path).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn exists(path: &Path) -> bool {
|
||||
fs::metadata(path).is_ok()
|
||||
}
|
||||
|
||||
/// Migrates the database.
|
||||
pub fn migrate(path: &Path, compaction_profile: &DatabaseCompactionProfile) -> Result<(), Error> {
|
||||
let compaction_profile = helpers::compaction_profile(&compaction_profile, path);
|
||||
|
||||
// read version file.
|
||||
let version = current_version(path)?;
|
||||
|
||||
// migrate the databases.
|
||||
// main db directory may already exists, so let's check if we have blocks dir
|
||||
if version > CURRENT_VERSION {
|
||||
return Err(Error::FutureDBVersion);
|
||||
}
|
||||
|
||||
// We are in the latest version, yay!
|
||||
if version == CURRENT_VERSION {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if version != DEFAULT_VERSION && version <= USE_MIGRATION_TOOL {
|
||||
return Err(Error::UseMigrationTool);
|
||||
}
|
||||
|
||||
let db_path = consolidated_database_path(path);
|
||||
|
||||
// Further migrations
|
||||
if version < CURRENT_VERSION && exists(&db_path) {
|
||||
println!(
|
||||
"Migrating database from version {} to {}",
|
||||
version, CURRENT_VERSION
|
||||
);
|
||||
migrate_database(
|
||||
version,
|
||||
&db_path,
|
||||
consolidated_database_migrations(&compaction_profile)?,
|
||||
)?;
|
||||
|
||||
if version < BLOOMS_DB_VERSION {
|
||||
println!("Migrating blooms to blooms-db...");
|
||||
let db_config = DatabaseConfig {
|
||||
max_open_files: 64,
|
||||
memory_budget: None,
|
||||
compaction: compaction_profile,
|
||||
columns: ethcore_db::NUM_COLUMNS,
|
||||
};
|
||||
|
||||
migrate_blooms(&db_path, &db_config).map_err(Error::BloomsDB)?;
|
||||
}
|
||||
|
||||
println!("Migration finished");
|
||||
}
|
||||
|
||||
// update version file.
|
||||
update_version(path)
|
||||
}
|
119
bin/oe/db/rocksdb/mod.rs
Normal file
119
bin/oe/db/rocksdb/mod.rs
Normal file
@ -0,0 +1,119 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate ethcore_blockchain;
|
||||
extern crate kvdb_rocksdb;
|
||||
extern crate migration_rocksdb;
|
||||
|
||||
use self::{
|
||||
ethcore_blockchain::{BlockChainDB, BlockChainDBHandler},
|
||||
kvdb_rocksdb::{Database, DatabaseConfig},
|
||||
};
|
||||
use blooms_db;
|
||||
use ethcore::client::ClientConfig;
|
||||
use ethcore_db::KeyValueDB;
|
||||
use stats::PrometheusMetrics;
|
||||
use std::{fs, io, path::Path, sync::Arc};
|
||||
|
||||
mod blooms;
|
||||
mod helpers;
|
||||
mod migration;
|
||||
|
||||
pub use self::migration::migrate;
|
||||
|
||||
struct AppDB {
|
||||
key_value: Arc<dyn KeyValueDB>,
|
||||
blooms: blooms_db::Database,
|
||||
trace_blooms: blooms_db::Database,
|
||||
}
|
||||
|
||||
impl BlockChainDB for AppDB {
|
||||
fn key_value(&self) -> &Arc<dyn KeyValueDB> {
|
||||
&self.key_value
|
||||
}
|
||||
|
||||
fn blooms(&self) -> &blooms_db::Database {
|
||||
&self.blooms
|
||||
}
|
||||
|
||||
fn trace_blooms(&self) -> &blooms_db::Database {
|
||||
&self.trace_blooms
|
||||
}
|
||||
}
|
||||
|
||||
impl PrometheusMetrics for AppDB {
|
||||
fn prometheus_metrics(&self, _: &mut stats::PrometheusRegistry) {}
|
||||
}
|
||||
|
||||
/// Open a secret store DB using the given secret store data path. The DB path is one level beneath the data path.
|
||||
#[cfg(feature = "secretstore")]
|
||||
pub fn open_secretstore_db(data_path: &str) -> Result<Arc<dyn KeyValueDB>, String> {
|
||||
use std::path::PathBuf;
|
||||
|
||||
let mut db_path = PathBuf::from(data_path);
|
||||
db_path.push("db");
|
||||
let db_path = db_path
|
||||
.to_str()
|
||||
.ok_or_else(|| "Invalid secretstore path".to_string())?;
|
||||
Ok(Arc::new(
|
||||
Database::open_default(&db_path).map_err(|e| format!("Error opening database: {:?}", e))?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a restoration db handler using the config generated by `client_path` and `client_config`.
|
||||
pub fn restoration_db_handler(
|
||||
client_path: &Path,
|
||||
client_config: &ClientConfig,
|
||||
) -> Box<dyn BlockChainDBHandler> {
|
||||
let client_db_config = helpers::client_db_config(client_path, client_config);
|
||||
|
||||
struct RestorationDBHandler {
|
||||
config: DatabaseConfig,
|
||||
}
|
||||
|
||||
impl BlockChainDBHandler for RestorationDBHandler {
|
||||
fn open(&self, db_path: &Path) -> io::Result<Arc<dyn BlockChainDB>> {
|
||||
open_database(&db_path.to_string_lossy(), &self.config)
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(RestorationDBHandler {
|
||||
config: client_db_config,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_database(
|
||||
client_path: &str,
|
||||
config: &DatabaseConfig,
|
||||
) -> io::Result<Arc<dyn BlockChainDB>> {
|
||||
let path = Path::new(client_path);
|
||||
|
||||
let blooms_path = path.join("blooms");
|
||||
let trace_blooms_path = path.join("trace_blooms");
|
||||
fs::create_dir_all(&blooms_path)?;
|
||||
fs::create_dir_all(&trace_blooms_path)?;
|
||||
|
||||
let db = Database::open(&config, client_path)?;
|
||||
let db_with_metrics = ethcore_db::DatabaseWithMetrics::new(db);
|
||||
|
||||
let db = AppDB {
|
||||
key_value: Arc::new(db_with_metrics),
|
||||
blooms: blooms_db::Database::open(blooms_path)?,
|
||||
trace_blooms: blooms_db::Database::open(trace_blooms_path)?,
|
||||
};
|
||||
|
||||
Ok(Arc::new(db))
|
||||
}
|
600
bin/oe/helpers.rs
Normal file
600
bin/oe/helpers.rs
Normal file
@ -0,0 +1,600 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
cache::CacheConfig,
|
||||
db::migrate,
|
||||
miner::pool::PrioritizationStrategy,
|
||||
sync::{self, validate_node_url},
|
||||
upgrade::{upgrade, upgrade_data_paths},
|
||||
};
|
||||
use dir::{helpers::replace_home, DatabaseDirectories};
|
||||
use ethcore::{
|
||||
client::{BlockId, ClientConfig, DatabaseCompactionProfile, Mode, VMType, VerifierType},
|
||||
miner::{Penalization, PendingSet},
|
||||
};
|
||||
use ethereum_types::{Address, U256};
|
||||
use ethkey::Password;
|
||||
use journaldb::Algorithm;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::File,
|
||||
io,
|
||||
io::{BufRead, BufReader, Write},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub fn to_duration(s: &str) -> Result<Duration, String> {
|
||||
to_seconds(s).map(Duration::from_secs)
|
||||
}
|
||||
|
||||
fn clean_0x(s: &str) -> &str {
|
||||
if s.starts_with("0x") {
|
||||
&s[2..]
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
fn to_seconds(s: &str) -> Result<u64, String> {
|
||||
let bad = |_| {
|
||||
format!(
|
||||
"{}: Invalid duration given. See openethereum --help for more information.",
|
||||
s
|
||||
)
|
||||
};
|
||||
|
||||
match s {
|
||||
"twice-daily" => Ok(12 * 60 * 60),
|
||||
"half-hourly" => Ok(30 * 60),
|
||||
"1second" | "1 second" | "second" => Ok(1),
|
||||
"1minute" | "1 minute" | "minute" => Ok(60),
|
||||
"hourly" | "1hour" | "1 hour" | "hour" => Ok(60 * 60),
|
||||
"daily" | "1day" | "1 day" | "day" => Ok(24 * 60 * 60),
|
||||
x if x.ends_with("seconds") => x[0..x.len() - 7].trim().parse().map_err(bad),
|
||||
x if x.ends_with("minutes") => x[0..x.len() - 7]
|
||||
.trim()
|
||||
.parse::<u64>()
|
||||
.map_err(bad)
|
||||
.map(|x| x * 60),
|
||||
x if x.ends_with("hours") => x[0..x.len() - 5]
|
||||
.trim()
|
||||
.parse::<u64>()
|
||||
.map_err(bad)
|
||||
.map(|x| x * 60 * 60),
|
||||
x if x.ends_with("days") => x[0..x.len() - 4]
|
||||
.trim()
|
||||
.parse::<u64>()
|
||||
.map_err(bad)
|
||||
.map(|x| x * 24 * 60 * 60),
|
||||
x => x.trim().parse().map_err(bad),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_mode(s: &str, timeout: u64, alarm: u64) -> Result<Mode, String> {
|
||||
match s {
|
||||
"active" => Ok(Mode::Active),
|
||||
"passive" => Ok(Mode::Passive(
|
||||
Duration::from_secs(timeout),
|
||||
Duration::from_secs(alarm),
|
||||
)),
|
||||
"dark" => Ok(Mode::Dark(Duration::from_secs(timeout))),
|
||||
"offline" => Ok(Mode::Off),
|
||||
_ => Err(format!(
|
||||
"{}: Invalid value for --mode. Must be one of active, passive, dark or offline.",
|
||||
s
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_block_id(s: &str) -> Result<BlockId, String> {
|
||||
if s == "latest" {
|
||||
Ok(BlockId::Latest)
|
||||
} else if let Ok(num) = s.parse() {
|
||||
Ok(BlockId::Number(num))
|
||||
} else if let Ok(hash) = s.parse() {
|
||||
Ok(BlockId::Hash(hash))
|
||||
} else {
|
||||
Err("Invalid block.".into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_u256(s: &str) -> Result<U256, String> {
|
||||
if let Ok(decimal) = U256::from_dec_str(s) {
|
||||
Ok(decimal)
|
||||
} else {
|
||||
clean_0x(s)
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid numeric value: {}", s))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_pending_set(s: &str) -> Result<PendingSet, String> {
|
||||
match s {
|
||||
"cheap" => Ok(PendingSet::AlwaysQueue),
|
||||
"strict" => Ok(PendingSet::AlwaysSealing),
|
||||
"lenient" => Ok(PendingSet::SealingOrElseQueue),
|
||||
other => Err(format!("Invalid pending set value: {:?}", other)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_queue_strategy(s: &str) -> Result<PrioritizationStrategy, String> {
|
||||
match s {
|
||||
"gas_price" => Ok(PrioritizationStrategy::GasPriceOnly),
|
||||
other => Err(format!("Invalid queue strategy: {}", other)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_queue_penalization(time: Option<u64>) -> Result<Penalization, String> {
|
||||
Ok(match time {
|
||||
Some(threshold_ms) => Penalization::Enabled {
|
||||
offend_threshold: Duration::from_millis(threshold_ms),
|
||||
},
|
||||
None => Penalization::Disabled,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_address(s: Option<String>) -> Result<Address, String> {
|
||||
match s {
|
||||
Some(ref a) => clean_0x(a)
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid address: {:?}", a)),
|
||||
None => Ok(Address::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_addresses(s: &Option<String>) -> Result<Vec<Address>, String> {
|
||||
match *s {
|
||||
Some(ref adds) if !adds.is_empty() => adds
|
||||
.split(',')
|
||||
.map(|a| {
|
||||
clean_0x(a)
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid address: {:?}", a))
|
||||
})
|
||||
.collect(),
|
||||
_ => Ok(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to parse string as a price.
|
||||
pub fn to_price(s: &str) -> Result<f32, String> {
|
||||
s.parse::<f32>().map_err(|_| {
|
||||
format!(
|
||||
"Invalid transaction price {:?} given. Must be a decimal number.",
|
||||
s
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn join_set(set: Option<&HashSet<String>>) -> Option<String> {
|
||||
set.map(|s| {
|
||||
s.iter()
|
||||
.map(|s| s.as_str())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(",")
|
||||
})
|
||||
}
|
||||
|
||||
/// Flush output buffer.
|
||||
pub fn flush_stdout() {
|
||||
io::stdout().flush().expect("stdout is flushable; qed");
|
||||
}
|
||||
|
||||
/// Formats and returns parity ipc path.
|
||||
pub fn parity_ipc_path(base: &str, path: &str, shift: u16) -> String {
|
||||
let mut path = path.to_owned();
|
||||
if shift != 0 {
|
||||
path = path.replace("jsonrpc.ipc", &format!("jsonrpc-{}.ipc", shift));
|
||||
}
|
||||
replace_home(base, &path)
|
||||
}
|
||||
|
||||
/// Validates and formats bootnodes option.
|
||||
pub fn to_bootnodes(bootnodes: &Option<String>) -> Result<Vec<String>, String> {
|
||||
match *bootnodes {
|
||||
Some(ref x) if !x.is_empty() => x
|
||||
.split(',')
|
||||
.map(|s| match validate_node_url(s).map(Into::into) {
|
||||
None => Ok(s.to_owned()),
|
||||
Some(sync::ErrorKind::AddressResolve(_)) => {
|
||||
Err(format!("Failed to resolve hostname of a boot node: {}", s))
|
||||
}
|
||||
Some(_) => Err(format!(
|
||||
"Invalid node address format given for a boot node: {}",
|
||||
s
|
||||
)),
|
||||
})
|
||||
.collect(),
|
||||
Some(_) => Ok(vec![]),
|
||||
None => Ok(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn default_network_config() -> crate::sync::NetworkConfiguration {
|
||||
use super::network::IpFilter;
|
||||
use crate::sync::NetworkConfiguration;
|
||||
NetworkConfiguration {
|
||||
config_path: Some(replace_home(&::dir::default_data_path(), "$BASE/network")),
|
||||
net_config_path: None,
|
||||
listen_address: Some("0.0.0.0:30303".into()),
|
||||
public_address: None,
|
||||
udp_port: None,
|
||||
nat_enabled: true,
|
||||
discovery_enabled: true,
|
||||
boot_nodes: Vec::new(),
|
||||
use_secret: None,
|
||||
max_peers: 50,
|
||||
min_peers: 25,
|
||||
snapshot_peers: 0,
|
||||
max_pending_peers: 64,
|
||||
ip_filter: IpFilter::default(),
|
||||
reserved_nodes: Vec::new(),
|
||||
allow_non_reserved: true,
|
||||
client_version: ::parity_version::version(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_client_config(
|
||||
cache_config: &CacheConfig,
|
||||
spec_name: String,
|
||||
mode: Mode,
|
||||
tracing: bool,
|
||||
fat_db: bool,
|
||||
compaction: DatabaseCompactionProfile,
|
||||
vm_type: VMType,
|
||||
name: String,
|
||||
pruning: Algorithm,
|
||||
pruning_history: u64,
|
||||
pruning_memory: usize,
|
||||
check_seal: bool,
|
||||
max_round_blocks_to_import: usize,
|
||||
) -> ClientConfig {
|
||||
let mut client_config = ClientConfig::default();
|
||||
|
||||
let mb = 1024 * 1024;
|
||||
// in bytes
|
||||
client_config.blockchain.max_cache_size = cache_config.blockchain() as usize * mb;
|
||||
// in bytes
|
||||
client_config.blockchain.pref_cache_size = cache_config.blockchain() as usize * 3 / 4 * mb;
|
||||
// db cache size, in megabytes
|
||||
client_config.db_cache_size = Some(cache_config.db_cache_size() as usize);
|
||||
// db queue cache size, in bytes
|
||||
client_config.queue.max_mem_use = cache_config.queue() as usize * mb;
|
||||
// in bytes
|
||||
client_config.tracing.max_cache_size = cache_config.traces() as usize * mb;
|
||||
// in bytes
|
||||
client_config.tracing.pref_cache_size = cache_config.traces() as usize * 3 / 4 * mb;
|
||||
// in bytes
|
||||
client_config.state_cache_size = cache_config.state() as usize * mb;
|
||||
// in bytes
|
||||
client_config.jump_table_size = cache_config.jump_tables() as usize * mb;
|
||||
// in bytes
|
||||
client_config.history_mem = pruning_memory * mb;
|
||||
|
||||
client_config.mode = mode;
|
||||
client_config.tracing.enabled = tracing;
|
||||
client_config.fat_db = fat_db;
|
||||
client_config.pruning = pruning;
|
||||
client_config.history = pruning_history;
|
||||
client_config.db_compaction = compaction;
|
||||
client_config.vm_type = vm_type;
|
||||
client_config.name = name;
|
||||
client_config.verifier_type = if check_seal {
|
||||
VerifierType::Canon
|
||||
} else {
|
||||
VerifierType::CanonNoSeal
|
||||
};
|
||||
client_config.spec_name = spec_name;
|
||||
client_config.max_round_blocks_to_import = max_round_blocks_to_import;
|
||||
client_config
|
||||
}
|
||||
|
||||
pub fn execute_upgrades(
|
||||
base_path: &str,
|
||||
dirs: &DatabaseDirectories,
|
||||
pruning: Algorithm,
|
||||
compaction_profile: &DatabaseCompactionProfile,
|
||||
) -> Result<(), String> {
|
||||
upgrade_data_paths(base_path, dirs, pruning);
|
||||
|
||||
match upgrade(&dirs.path) {
|
||||
Ok(upgrades_applied) if upgrades_applied > 0 => {
|
||||
debug!("Executed {} upgrade scripts - ok", upgrades_applied);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(format!("Error upgrading OpenEthereum data: {:?}", e));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let client_path = dirs.db_path(pruning);
|
||||
migrate(&client_path, compaction_profile).map_err(|e| format!("{}", e))
|
||||
}
|
||||
|
||||
/// Prompts user asking for password.
|
||||
pub fn password_prompt() -> Result<Password, String> {
|
||||
use rpassword::read_password;
|
||||
const STDIN_ERROR: &'static str = "Unable to ask for password on non-interactive terminal.";
|
||||
|
||||
println!("Please note that password is NOT RECOVERABLE.");
|
||||
print!("Type password: ");
|
||||
flush_stdout();
|
||||
|
||||
let password = read_password().map_err(|_| STDIN_ERROR.to_owned())?.into();
|
||||
|
||||
print!("Repeat password: ");
|
||||
flush_stdout();
|
||||
|
||||
let password_repeat = read_password().map_err(|_| STDIN_ERROR.to_owned())?.into();
|
||||
|
||||
if password != password_repeat {
|
||||
return Err("Passwords do not match!".into());
|
||||
}
|
||||
|
||||
Ok(password)
|
||||
}
|
||||
|
||||
/// Read a password from password file.
|
||||
pub fn password_from_file(path: String) -> Result<Password, String> {
|
||||
let passwords = passwords_from_files(&[path])?;
|
||||
// use only first password from the file
|
||||
passwords
|
||||
.get(0)
|
||||
.map(Password::clone)
|
||||
.ok_or_else(|| "Password file seems to be empty.".to_owned())
|
||||
}
|
||||
|
||||
/// Reads passwords from files. Treats each line as a separate password.
|
||||
pub fn passwords_from_files(files: &[String]) -> Result<Vec<Password>, String> {
|
||||
let passwords = files.iter().map(|filename| {
|
||||
let file = File::open(filename).map_err(|_| format!("{} Unable to read password file. Ensure it exists and permissions are correct.", filename))?;
|
||||
let reader = BufReader::new(&file);
|
||||
let lines = reader.lines()
|
||||
.filter_map(|l| l.ok())
|
||||
.map(|pwd| pwd.trim().to_owned().into())
|
||||
.collect::<Vec<Password>>();
|
||||
Ok(lines)
|
||||
}).collect::<Result<Vec<Vec<Password>>, String>>();
|
||||
Ok(passwords?.into_iter().flat_map(|x| x).collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
join_set, password_from_file, to_address, to_addresses, to_block_id, to_bootnodes,
|
||||
to_duration, to_mode, to_pending_set, to_price, to_u256,
|
||||
};
|
||||
use ethcore::{
|
||||
client::{BlockId, Mode},
|
||||
miner::PendingSet,
|
||||
};
|
||||
use ethereum_types::U256;
|
||||
use ethkey::Password;
|
||||
use std::{collections::HashSet, fs::File, io::Write, time::Duration};
|
||||
use tempdir::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_to_duration() {
|
||||
assert_eq!(
|
||||
to_duration("twice-daily").unwrap(),
|
||||
Duration::from_secs(12 * 60 * 60)
|
||||
);
|
||||
assert_eq!(
|
||||
to_duration("half-hourly").unwrap(),
|
||||
Duration::from_secs(30 * 60)
|
||||
);
|
||||
assert_eq!(to_duration("1second").unwrap(), Duration::from_secs(1));
|
||||
assert_eq!(to_duration("2seconds").unwrap(), Duration::from_secs(2));
|
||||
assert_eq!(to_duration("15seconds").unwrap(), Duration::from_secs(15));
|
||||
assert_eq!(to_duration("1minute").unwrap(), Duration::from_secs(1 * 60));
|
||||
assert_eq!(
|
||||
to_duration("2minutes").unwrap(),
|
||||
Duration::from_secs(2 * 60)
|
||||
);
|
||||
assert_eq!(
|
||||
to_duration("15minutes").unwrap(),
|
||||
Duration::from_secs(15 * 60)
|
||||
);
|
||||
assert_eq!(to_duration("hourly").unwrap(), Duration::from_secs(60 * 60));
|
||||
assert_eq!(
|
||||
to_duration("daily").unwrap(),
|
||||
Duration::from_secs(24 * 60 * 60)
|
||||
);
|
||||
assert_eq!(
|
||||
to_duration("1hour").unwrap(),
|
||||
Duration::from_secs(1 * 60 * 60)
|
||||
);
|
||||
assert_eq!(
|
||||
to_duration("2hours").unwrap(),
|
||||
Duration::from_secs(2 * 60 * 60)
|
||||
);
|
||||
assert_eq!(
|
||||
to_duration("15hours").unwrap(),
|
||||
Duration::from_secs(15 * 60 * 60)
|
||||
);
|
||||
assert_eq!(
|
||||
to_duration("1day").unwrap(),
|
||||
Duration::from_secs(1 * 24 * 60 * 60)
|
||||
);
|
||||
assert_eq!(
|
||||
to_duration("2days").unwrap(),
|
||||
Duration::from_secs(2 * 24 * 60 * 60)
|
||||
);
|
||||
assert_eq!(
|
||||
to_duration("15days").unwrap(),
|
||||
Duration::from_secs(15 * 24 * 60 * 60)
|
||||
);
|
||||
assert_eq!(
|
||||
to_duration("15 days").unwrap(),
|
||||
Duration::from_secs(15 * 24 * 60 * 60)
|
||||
);
|
||||
assert_eq!(to_duration("2 seconds").unwrap(), Duration::from_secs(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_mode() {
|
||||
assert_eq!(to_mode("active", 0, 0).unwrap(), Mode::Active);
|
||||
assert_eq!(
|
||||
to_mode("passive", 10, 20).unwrap(),
|
||||
Mode::Passive(Duration::from_secs(10), Duration::from_secs(20))
|
||||
);
|
||||
assert_eq!(
|
||||
to_mode("dark", 20, 30).unwrap(),
|
||||
Mode::Dark(Duration::from_secs(20))
|
||||
);
|
||||
assert!(to_mode("other", 20, 30).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_block_id() {
|
||||
assert_eq!(to_block_id("latest").unwrap(), BlockId::Latest);
|
||||
assert_eq!(to_block_id("0").unwrap(), BlockId::Number(0));
|
||||
assert_eq!(to_block_id("2").unwrap(), BlockId::Number(2));
|
||||
assert_eq!(to_block_id("15").unwrap(), BlockId::Number(15));
|
||||
assert_eq!(
|
||||
to_block_id("9fc84d84f6a785dc1bd5abacfcf9cbdd3b6afb80c0f799bfb2fd42c44a0c224e")
|
||||
.unwrap(),
|
||||
BlockId::Hash(
|
||||
"9fc84d84f6a785dc1bd5abacfcf9cbdd3b6afb80c0f799bfb2fd42c44a0c224e"
|
||||
.parse()
|
||||
.unwrap()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_u256() {
|
||||
assert_eq!(to_u256("0").unwrap(), U256::from(0));
|
||||
assert_eq!(to_u256("11").unwrap(), U256::from(11));
|
||||
assert_eq!(to_u256("0x11").unwrap(), U256::from(17));
|
||||
assert!(to_u256("u").is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pending_set() {
|
||||
assert_eq!(to_pending_set("cheap").unwrap(), PendingSet::AlwaysQueue);
|
||||
assert_eq!(to_pending_set("strict").unwrap(), PendingSet::AlwaysSealing);
|
||||
assert_eq!(
|
||||
to_pending_set("lenient").unwrap(),
|
||||
PendingSet::SealingOrElseQueue
|
||||
);
|
||||
assert!(to_pending_set("othe").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_address() {
|
||||
assert_eq!(
|
||||
to_address(Some("0xD9A111feda3f362f55Ef1744347CDC8Dd9964a41".into())).unwrap(),
|
||||
"D9A111feda3f362f55Ef1744347CDC8Dd9964a41".parse().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
to_address(Some("D9A111feda3f362f55Ef1744347CDC8Dd9964a41".into())).unwrap(),
|
||||
"D9A111feda3f362f55Ef1744347CDC8Dd9964a41".parse().unwrap()
|
||||
);
|
||||
assert_eq!(to_address(None).unwrap(), Default::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_addresses() {
|
||||
let addresses = to_addresses(&Some(
|
||||
"0xD9A111feda3f362f55Ef1744347CDC8Dd9964a41,D9A111feda3f362f55Ef1744347CDC8Dd9964a42"
|
||||
.into(),
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
addresses,
|
||||
vec![
|
||||
"D9A111feda3f362f55Ef1744347CDC8Dd9964a41".parse().unwrap(),
|
||||
"D9A111feda3f362f55Ef1744347CDC8Dd9964a42".parse().unwrap(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_password() {
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let path = tempdir.path().join("file");
|
||||
let mut file = File::create(&path).unwrap();
|
||||
file.write_all(b"a bc ").unwrap();
|
||||
assert_eq!(
|
||||
password_from_file(path.to_str().unwrap().into())
|
||||
.unwrap()
|
||||
.as_bytes(),
|
||||
b"a bc"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_password_multiline() {
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let path = tempdir.path().join("file");
|
||||
let mut file = File::create(path.as_path()).unwrap();
|
||||
file.write_all(
|
||||
br#" password with trailing whitespace
|
||||
those passwords should be
|
||||
ignored
|
||||
but the first password is trimmed
|
||||
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
password_from_file(path.to_str().unwrap().into()).unwrap(),
|
||||
Password::from("password with trailing whitespace")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_price() {
|
||||
assert_eq!(to_price("1").unwrap(), 1.0);
|
||||
assert_eq!(to_price("2.3").unwrap(), 2.3);
|
||||
assert_eq!(to_price("2.33").unwrap(), 2.33);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_bootnodes() {
|
||||
let one_bootnode = "enode://e731347db0521f3476e6bbbb83375dcd7133a1601425ebd15fd10f3835fd4c304fba6282087ca5a0deeafadf0aa0d4fd56c3323331901c1f38bd181c283e3e35@128.199.55.137:30303";
|
||||
let two_bootnodes = "enode://e731347db0521f3476e6bbbb83375dcd7133a1601425ebd15fd10f3835fd4c304fba6282087ca5a0deeafadf0aa0d4fd56c3323331901c1f38bd181c283e3e35@128.199.55.137:30303,enode://e731347db0521f3476e6bbbb83375dcd7133a1601425ebd15fd10f3835fd4c304fba6282087ca5a0deeafadf0aa0d4fd56c3323331901c1f38bd181c283e3e35@128.199.55.137:30303";
|
||||
|
||||
assert_eq!(to_bootnodes(&Some("".into())), Ok(vec![]));
|
||||
assert_eq!(to_bootnodes(&None), Ok(vec![]));
|
||||
assert_eq!(
|
||||
to_bootnodes(&Some(one_bootnode.into())),
|
||||
Ok(vec![one_bootnode.into()])
|
||||
);
|
||||
assert_eq!(
|
||||
to_bootnodes(&Some(two_bootnodes.into())),
|
||||
Ok(vec![one_bootnode.into(), one_bootnode.into()])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_join_set() {
|
||||
let mut test_set = HashSet::new();
|
||||
test_set.insert("0x1111111111111111111111111111111111111111".to_string());
|
||||
test_set.insert("0x0000000000000000000000000000000000000000".to_string());
|
||||
|
||||
let res = join_set(Some(&test_set)).unwrap();
|
||||
|
||||
assert!(
|
||||
res == "0x1111111111111111111111111111111111111111,0x0000000000000000000000000000000000000000"
|
||||
||
|
||||
res == "0x0000000000000000000000000000000000000000,0x1111111111111111111111111111111111111111"
|
||||
);
|
||||
}
|
||||
}
|
429
bin/oe/informant.rs
Normal file
429
bin/oe/informant.rs
Normal file
@ -0,0 +1,429 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate ansi_term;
|
||||
use self::ansi_term::{
|
||||
Colour,
|
||||
Colour::{Blue, Cyan, Green, White, Yellow},
|
||||
Style,
|
||||
};
|
||||
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering as AtomicOrdering},
|
||||
Arc,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
io::{IoContext, IoHandler, TimerToken},
|
||||
sync::{ManageNetwork, SyncProvider},
|
||||
types::BlockNumber,
|
||||
};
|
||||
use atty;
|
||||
use ethcore::{
|
||||
client::{
|
||||
BlockChainClient, BlockChainInfo, BlockId, BlockInfo, BlockQueueInfo, ChainInfo,
|
||||
ChainNotify, Client, ClientIoMessage, ClientReport, NewBlocks,
|
||||
},
|
||||
snapshot::{service::Service as SnapshotService, RestorationStatus, SnapshotService as SS},
|
||||
};
|
||||
use number_prefix::{binary_prefix, Prefixed, Standalone};
|
||||
use parity_rpc::{informant::RpcStats, is_major_importing_or_waiting};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
|
||||
/// Format byte counts to standard denominations.
|
||||
pub fn format_bytes(b: usize) -> String {
|
||||
match binary_prefix(b as f64) {
|
||||
Standalone(bytes) => format!("{} bytes", bytes),
|
||||
Prefixed(prefix, n) => format!("{:.0} {}B", n, prefix),
|
||||
}
|
||||
}
|
||||
|
||||
/// Something that can be converted to milliseconds.
|
||||
pub trait MillisecondDuration {
|
||||
/// Get the value in milliseconds.
|
||||
fn as_milliseconds(&self) -> u64;
|
||||
}
|
||||
|
||||
impl MillisecondDuration for Duration {
|
||||
fn as_milliseconds(&self) -> u64 {
|
||||
self.as_secs() * 1000 + self.subsec_nanos() as u64 / 1_000_000
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CacheSizes {
|
||||
sizes: ::std::collections::BTreeMap<&'static str, usize>,
|
||||
}
|
||||
|
||||
impl CacheSizes {
|
||||
fn insert(&mut self, key: &'static str, bytes: usize) {
|
||||
self.sizes.insert(key, bytes);
|
||||
}
|
||||
|
||||
fn display<F>(&self, style: Style, paint: F) -> String
|
||||
where
|
||||
F: Fn(Style, String) -> String,
|
||||
{
|
||||
use std::fmt::Write;
|
||||
|
||||
let mut buf = String::new();
|
||||
for (name, &size) in &self.sizes {
|
||||
write!(buf, " {:>8} {}", paint(style, format_bytes(size)), name)
|
||||
.expect("writing to string won't fail unless OOM; qed")
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SyncInfo {
|
||||
last_imported_block_number: BlockNumber,
|
||||
last_imported_ancient_number: Option<BlockNumber>,
|
||||
num_peers: usize,
|
||||
max_peers: u32,
|
||||
snapshot_sync: bool,
|
||||
}
|
||||
|
||||
pub struct Report {
|
||||
importing: bool,
|
||||
chain_info: BlockChainInfo,
|
||||
client_report: ClientReport,
|
||||
queue_info: BlockQueueInfo,
|
||||
cache_sizes: CacheSizes,
|
||||
sync_info: Option<SyncInfo>,
|
||||
}
|
||||
|
||||
/// Something which can provide data to the informant.
|
||||
pub trait InformantData: Send + Sync {
|
||||
/// Whether it executes transactions
|
||||
fn executes_transactions(&self) -> bool;
|
||||
|
||||
/// Whether it is currently importing (also included in `Report`)
|
||||
fn is_major_importing(&self) -> bool;
|
||||
|
||||
/// Generate a report of blockchain status, memory usage, and sync info.
|
||||
fn report(&self) -> Report;
|
||||
}
|
||||
|
||||
/// Informant data for a full node.
|
||||
pub struct FullNodeInformantData {
|
||||
pub client: Arc<Client>,
|
||||
pub sync: Option<Arc<dyn SyncProvider>>,
|
||||
pub net: Option<Arc<dyn ManageNetwork>>,
|
||||
}
|
||||
|
||||
impl InformantData for FullNodeInformantData {
|
||||
fn executes_transactions(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn is_major_importing(&self) -> bool {
|
||||
let state = self.sync.as_ref().map(|sync| sync.status().state);
|
||||
is_major_importing_or_waiting(state, self.client.queue_info(), false)
|
||||
}
|
||||
|
||||
fn report(&self) -> Report {
|
||||
let (client_report, queue_info, blockchain_cache_info) = (
|
||||
self.client.report(),
|
||||
self.client.queue_info(),
|
||||
self.client.blockchain_cache_info(),
|
||||
);
|
||||
|
||||
let chain_info = self.client.chain_info();
|
||||
|
||||
let mut cache_sizes = CacheSizes::default();
|
||||
cache_sizes.insert("queue", queue_info.mem_used);
|
||||
cache_sizes.insert("chain", blockchain_cache_info.total());
|
||||
|
||||
let importing = self.is_major_importing();
|
||||
let sync_info = match (self.sync.as_ref(), self.net.as_ref()) {
|
||||
(Some(sync), Some(net)) => {
|
||||
let status = sync.status();
|
||||
let num_peers_range = net.num_peers_range();
|
||||
debug_assert!(num_peers_range.end() >= num_peers_range.start());
|
||||
|
||||
Some(SyncInfo {
|
||||
last_imported_block_number: status
|
||||
.last_imported_block_number
|
||||
.unwrap_or(chain_info.best_block_number),
|
||||
last_imported_ancient_number: status.last_imported_old_block_number,
|
||||
num_peers: status.num_peers,
|
||||
max_peers: status
|
||||
.current_max_peers(*num_peers_range.start(), *num_peers_range.end()),
|
||||
snapshot_sync: status.is_snapshot_syncing(),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Report {
|
||||
importing,
|
||||
chain_info,
|
||||
client_report,
|
||||
queue_info,
|
||||
cache_sizes,
|
||||
sync_info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Informant<T> {
|
||||
last_tick: RwLock<Instant>,
|
||||
with_color: bool,
|
||||
target: T,
|
||||
snapshot: Option<Arc<SnapshotService>>,
|
||||
rpc_stats: Option<Arc<RpcStats>>,
|
||||
last_import: Mutex<Instant>,
|
||||
skipped: AtomicUsize,
|
||||
skipped_txs: AtomicUsize,
|
||||
in_shutdown: AtomicBool,
|
||||
last_report: Mutex<ClientReport>,
|
||||
}
|
||||
|
||||
impl<T: InformantData> Informant<T> {
|
||||
/// Make a new instance potentially `with_color` output.
|
||||
pub fn new(
|
||||
target: T,
|
||||
snapshot: Option<Arc<SnapshotService>>,
|
||||
rpc_stats: Option<Arc<RpcStats>>,
|
||||
with_color: bool,
|
||||
) -> Self {
|
||||
Informant {
|
||||
last_tick: RwLock::new(Instant::now()),
|
||||
with_color: with_color,
|
||||
target: target,
|
||||
snapshot: snapshot,
|
||||
rpc_stats: rpc_stats,
|
||||
last_import: Mutex::new(Instant::now()),
|
||||
skipped: AtomicUsize::new(0),
|
||||
skipped_txs: AtomicUsize::new(0),
|
||||
in_shutdown: AtomicBool::new(false),
|
||||
last_report: Mutex::new(Default::default()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Signal that we're shutting down; no more output necessary.
|
||||
pub fn shutdown(&self) {
|
||||
self.in_shutdown
|
||||
.store(true, ::std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn tick(&self) {
|
||||
let now = Instant::now();
|
||||
let elapsed;
|
||||
{
|
||||
let last_tick = self.last_tick.read();
|
||||
if now < *last_tick + Duration::from_millis(1500) {
|
||||
return;
|
||||
}
|
||||
elapsed = now - *last_tick;
|
||||
}
|
||||
|
||||
let (client_report, full_report) = {
|
||||
let last_report = self.last_report.lock();
|
||||
let full_report = self.target.report();
|
||||
let diffed = full_report.client_report.clone() - &*last_report;
|
||||
(diffed, full_report)
|
||||
};
|
||||
|
||||
let Report {
|
||||
importing,
|
||||
chain_info,
|
||||
queue_info,
|
||||
cache_sizes,
|
||||
sync_info,
|
||||
..
|
||||
} = full_report;
|
||||
|
||||
let rpc_stats = self.rpc_stats.as_ref();
|
||||
let snapshot_sync = sync_info.as_ref().map_or(false, |s| s.snapshot_sync)
|
||||
&& self
|
||||
.snapshot
|
||||
.as_ref()
|
||||
.map_or(false, |s| match s.restoration_status() {
|
||||
RestorationStatus::Ongoing { .. } | RestorationStatus::Initializing { .. } => {
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
if !importing && !snapshot_sync && elapsed < Duration::from_secs(30) {
|
||||
return;
|
||||
}
|
||||
|
||||
*self.last_tick.write() = now;
|
||||
*self.last_report.lock() = full_report.client_report.clone();
|
||||
|
||||
let paint = |c: Style, t: String| match self.with_color && atty::is(atty::Stream::Stdout) {
|
||||
true => format!("{}", c.paint(t)),
|
||||
false => t,
|
||||
};
|
||||
|
||||
info!(target: "import", "{}{} {} {} {}",
|
||||
match importing {
|
||||
true => match snapshot_sync {
|
||||
false => format!("Syncing {} {} {} {}+{} Qed",
|
||||
paint(White.bold(), format!("{:>8}", format!("#{}", chain_info.best_block_number))),
|
||||
paint(White.bold(), format!("{}", chain_info.best_block_hash)),
|
||||
if self.target.executes_transactions() {
|
||||
format!("{} blk/s {} tx/s {} Mgas/s",
|
||||
paint(Yellow.bold(), format!("{:7.2}", (client_report.blocks_imported * 1000) as f64 / elapsed.as_milliseconds() as f64)),
|
||||
paint(Yellow.bold(), format!("{:6.1}", (client_report.transactions_applied * 1000) as f64 / elapsed.as_milliseconds() as f64)),
|
||||
paint(Yellow.bold(), format!("{:6.1}", (client_report.gas_processed / 1000).low_u64() as f64 / elapsed.as_milliseconds() as f64))
|
||||
)
|
||||
} else {
|
||||
format!("{} hdr/s",
|
||||
paint(Yellow.bold(), format!("{:6.1}", (client_report.blocks_imported * 1000) as f64 / elapsed.as_milliseconds() as f64))
|
||||
)
|
||||
},
|
||||
paint(Green.bold(), format!("{:5}", queue_info.unverified_queue_size)),
|
||||
paint(Green.bold(), format!("{:5}", queue_info.verified_queue_size))
|
||||
),
|
||||
true => {
|
||||
self.snapshot.as_ref().map_or(String::new(), |s|
|
||||
match s.restoration_status() {
|
||||
RestorationStatus::Ongoing { state_chunks, block_chunks, state_chunks_done, block_chunks_done, .. } => {
|
||||
format!("Syncing snapshot {}/{}", state_chunks_done + block_chunks_done, state_chunks + block_chunks)
|
||||
},
|
||||
RestorationStatus::Initializing { chunks_done } => {
|
||||
format!("Snapshot initializing ({} chunks restored)", chunks_done)
|
||||
},
|
||||
_ => String::new(),
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
false => String::new(),
|
||||
},
|
||||
match chain_info.ancient_block_number {
|
||||
Some(ancient_number) => format!(" Ancient:#{}", ancient_number),
|
||||
None => String::new(),
|
||||
},
|
||||
match sync_info.as_ref() {
|
||||
Some(ref sync_info) => format!("{}{}/{} peers",
|
||||
match importing {
|
||||
true => format!("{}",
|
||||
if self.target.executes_transactions() {
|
||||
paint(Green.bold(), format!("{:>8} ", format!("LI:#{}", sync_info.last_imported_block_number)))
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
false => match sync_info.last_imported_ancient_number {
|
||||
Some(number) => format!("{} ", paint(Yellow.bold(), format!("{:>8}", format!("AB:#{}", number)))),
|
||||
None => String::new(),
|
||||
}
|
||||
},
|
||||
paint(Cyan.bold(), format!("{:2}", sync_info.num_peers)),
|
||||
paint(Cyan.bold(), format!("{:2}", sync_info.max_peers)),
|
||||
),
|
||||
_ => String::new(),
|
||||
},
|
||||
cache_sizes.display(Blue.bold(), &paint),
|
||||
match rpc_stats {
|
||||
Some(ref rpc_stats) => format!(
|
||||
"RPC: {} conn, {} req/s, {} µs",
|
||||
paint(Blue.bold(), format!("{:2}", rpc_stats.sessions())),
|
||||
paint(Blue.bold(), format!("{:4}", rpc_stats.requests_rate())),
|
||||
paint(Blue.bold(), format!("{:4}", rpc_stats.approximated_roundtrip())),
|
||||
),
|
||||
_ => String::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainNotify for Informant<FullNodeInformantData> {
|
||||
// t_nb 11.2 Informant. Prints new block inclusiong to console/log.
|
||||
fn new_blocks(&self, new_blocks: NewBlocks) {
|
||||
if new_blocks.has_more_blocks_to_import {
|
||||
return;
|
||||
}
|
||||
let mut last_import = self.last_import.lock();
|
||||
let client = &self.target.client;
|
||||
|
||||
let importing = self.target.is_major_importing();
|
||||
let ripe = Instant::now() > *last_import + Duration::from_secs(1) && !importing;
|
||||
let txs_imported = new_blocks
|
||||
.imported
|
||||
.iter()
|
||||
.take(
|
||||
new_blocks
|
||||
.imported
|
||||
.len()
|
||||
.saturating_sub(if ripe { 1 } else { 0 }),
|
||||
)
|
||||
.filter_map(|h| client.block(BlockId::Hash(*h)))
|
||||
.map(|b| b.transactions_count())
|
||||
.sum();
|
||||
|
||||
if ripe {
|
||||
if let Some(block) = new_blocks
|
||||
.imported
|
||||
.last()
|
||||
.and_then(|h| client.block(BlockId::Hash(*h)))
|
||||
{
|
||||
let header_view = block.header_view();
|
||||
let size = block.rlp().as_raw().len();
|
||||
let (skipped, skipped_txs) = (
|
||||
self.skipped.load(AtomicOrdering::Relaxed) + new_blocks.imported.len() - 1,
|
||||
self.skipped_txs.load(AtomicOrdering::Relaxed) + txs_imported,
|
||||
);
|
||||
info!(target: "import", "Imported {} {} ({} txs, {} Mgas, {} ms, {} KiB){}",
|
||||
Colour::White.bold().paint(format!("#{}", header_view.number())),
|
||||
Colour::White.bold().paint(format!("{}", header_view.hash())),
|
||||
Colour::Yellow.bold().paint(format!("{}", block.transactions_count())),
|
||||
Colour::Yellow.bold().paint(format!("{:.2}", header_view.gas_used().low_u64() as f32 / 1000000f32)),
|
||||
Colour::Purple.bold().paint(format!("{}", new_blocks.duration.as_milliseconds())),
|
||||
Colour::Blue.bold().paint(format!("{:.2}", size as f32 / 1024f32)),
|
||||
if skipped > 0 {
|
||||
format!(" + another {} block(s) containing {} tx(s)",
|
||||
Colour::Red.bold().paint(format!("{}", skipped)),
|
||||
Colour::Red.bold().paint(format!("{}", skipped_txs))
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
);
|
||||
self.skipped.store(0, AtomicOrdering::Relaxed);
|
||||
self.skipped_txs.store(0, AtomicOrdering::Relaxed);
|
||||
*last_import = Instant::now();
|
||||
}
|
||||
} else {
|
||||
self.skipped
|
||||
.fetch_add(new_blocks.imported.len(), AtomicOrdering::Relaxed);
|
||||
self.skipped_txs
|
||||
.fetch_add(txs_imported, AtomicOrdering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const INFO_TIMER: TimerToken = 0;
|
||||
|
||||
impl<T: InformantData> IoHandler<ClientIoMessage> for Informant<T> {
|
||||
fn initialize(&self, io: &IoContext<ClientIoMessage>) {
|
||||
io.register_timer(INFO_TIMER, Duration::from_secs(5))
|
||||
.expect("Error registering timer");
|
||||
}
|
||||
|
||||
fn timeout(&self, _io: &IoContext<ClientIoMessage>, timer: TimerToken) {
|
||||
if timer == INFO_TIMER && !self.in_shutdown.load(AtomicOrdering::SeqCst) {
|
||||
self.tick();
|
||||
}
|
||||
}
|
||||
}
|
240
bin/oe/lib.rs
Normal file
240
bin/oe/lib.rs
Normal file
@ -0,0 +1,240 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethcore client application.
|
||||
#![warn(missing_docs)]
|
||||
|
||||
extern crate ansi_term;
|
||||
extern crate docopt;
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
extern crate atty;
|
||||
extern crate dir;
|
||||
extern crate futures;
|
||||
extern crate jsonrpc_core;
|
||||
extern crate num_cpus;
|
||||
extern crate number_prefix;
|
||||
extern crate parking_lot;
|
||||
extern crate regex;
|
||||
extern crate rlp;
|
||||
extern crate rpassword;
|
||||
extern crate rustc_hex;
|
||||
extern crate semver;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate toml;
|
||||
|
||||
extern crate blooms_db;
|
||||
extern crate cli_signer;
|
||||
extern crate common_types as types;
|
||||
extern crate ethcore;
|
||||
extern crate ethcore_call_contract as call_contract;
|
||||
extern crate ethcore_db;
|
||||
extern crate ethcore_io as io;
|
||||
extern crate ethcore_logger;
|
||||
extern crate ethcore_miner as miner;
|
||||
extern crate ethcore_network as network;
|
||||
extern crate ethcore_service;
|
||||
extern crate ethcore_sync as sync;
|
||||
extern crate ethereum_types;
|
||||
extern crate ethkey;
|
||||
extern crate ethstore;
|
||||
extern crate fetch;
|
||||
extern crate hyper;
|
||||
extern crate journaldb;
|
||||
extern crate keccak_hash as hash;
|
||||
extern crate kvdb;
|
||||
extern crate node_filter;
|
||||
extern crate parity_bytes as bytes;
|
||||
extern crate parity_crypto as crypto;
|
||||
extern crate parity_local_store as local_store;
|
||||
extern crate parity_path as path;
|
||||
extern crate parity_rpc;
|
||||
extern crate parity_runtime;
|
||||
extern crate parity_version;
|
||||
extern crate prometheus;
|
||||
extern crate stats;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log as rlog;
|
||||
|
||||
#[cfg(feature = "ethcore-accounts")]
|
||||
extern crate ethcore_accounts as accounts;
|
||||
|
||||
#[cfg(feature = "secretstore")]
|
||||
extern crate ethcore_secretstore;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate tempdir;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
mod account;
|
||||
mod account_utils;
|
||||
mod blockchain;
|
||||
mod cache;
|
||||
mod cli;
|
||||
mod configuration;
|
||||
mod db;
|
||||
mod helpers;
|
||||
mod informant;
|
||||
mod metrics;
|
||||
mod modules;
|
||||
mod params;
|
||||
mod presale;
|
||||
mod rpc;
|
||||
mod rpc_apis;
|
||||
mod run;
|
||||
mod secretstore;
|
||||
mod signer;
|
||||
mod snapshot;
|
||||
mod upgrade;
|
||||
mod user_defaults;
|
||||
|
||||
use std::{fs::File, io::BufReader, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
cli::Args,
|
||||
configuration::{Cmd, Execute},
|
||||
hash::keccak_buffer,
|
||||
};
|
||||
|
||||
#[cfg(feature = "memory_profiling")]
|
||||
use std::alloc::System;
|
||||
|
||||
pub use self::{configuration::Configuration, run::RunningClient};
|
||||
pub use ethcore_logger::{setup_log, Config as LoggerConfig, RotatingLogger};
|
||||
pub use parity_rpc::PubSubSession;
|
||||
|
||||
#[cfg(feature = "memory_profiling")]
|
||||
#[global_allocator]
|
||||
static A: System = System;
|
||||
|
||||
fn print_hash_of(maybe_file: Option<String>) -> Result<String, String> {
|
||||
if let Some(file) = maybe_file {
|
||||
let mut f =
|
||||
BufReader::new(File::open(&file).map_err(|_| "Unable to open file".to_owned())?);
|
||||
let hash = keccak_buffer(&mut f).map_err(|_| "Unable to read from file".to_owned())?;
|
||||
Ok(format!("{:x}", hash))
|
||||
} else {
|
||||
Err("Streaming from standard input not yet supported. Specify a file.".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
fn run_deadlock_detection_thread() {
|
||||
use ansi_term::Style;
|
||||
use parking_lot::deadlock;
|
||||
use std::{thread, time::Duration};
|
||||
|
||||
info!("Starting deadlock detection thread.");
|
||||
// Create a background thread which checks for deadlocks every 10s
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_secs(10));
|
||||
let deadlocks = deadlock::check_deadlock();
|
||||
if deadlocks.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
warn!(
|
||||
"{} {} detected",
|
||||
deadlocks.len(),
|
||||
Style::new().bold().paint("deadlock(s)")
|
||||
);
|
||||
for (i, threads) in deadlocks.iter().enumerate() {
|
||||
warn!("{} #{}", Style::new().bold().paint("Deadlock"), i);
|
||||
for t in threads {
|
||||
warn!("Thread Id {:#?}", t.thread_id());
|
||||
warn!("{:#?}", t.backtrace());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Action that OpenEthereum performed when running `start`.
|
||||
pub enum ExecutionAction {
|
||||
/// The execution didn't require starting a node, and thus has finished.
|
||||
/// Contains the string to print on stdout, if any.
|
||||
Instant(Option<String>),
|
||||
|
||||
/// The client has started running and must be shut down manually by calling `shutdown`.
|
||||
///
|
||||
/// If you don't call `shutdown()`, execution will continue in the background.
|
||||
Running(RunningClient),
|
||||
}
|
||||
|
||||
fn execute(command: Execute, logger: Arc<RotatingLogger>) -> Result<ExecutionAction, String> {
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
run_deadlock_detection_thread();
|
||||
|
||||
match command.cmd {
|
||||
Cmd::Run(run_cmd) => {
|
||||
let outcome = run::execute(run_cmd, logger)?;
|
||||
Ok(ExecutionAction::Running(outcome))
|
||||
}
|
||||
Cmd::Version => Ok(ExecutionAction::Instant(Some(Args::print_version()))),
|
||||
Cmd::Hash(maybe_file) => {
|
||||
print_hash_of(maybe_file).map(|s| ExecutionAction::Instant(Some(s)))
|
||||
}
|
||||
Cmd::Account(account_cmd) => {
|
||||
account::execute(account_cmd).map(|s| ExecutionAction::Instant(Some(s)))
|
||||
}
|
||||
Cmd::ImportPresaleWallet(presale_cmd) => {
|
||||
presale::execute(presale_cmd).map(|s| ExecutionAction::Instant(Some(s)))
|
||||
}
|
||||
Cmd::Blockchain(blockchain_cmd) => {
|
||||
blockchain::execute(blockchain_cmd).map(|_| ExecutionAction::Instant(None))
|
||||
}
|
||||
Cmd::SignerToken(ws_conf, logger_config) => {
|
||||
signer::execute(ws_conf, logger_config).map(|s| ExecutionAction::Instant(Some(s)))
|
||||
}
|
||||
Cmd::SignerSign {
|
||||
id,
|
||||
pwfile,
|
||||
port,
|
||||
authfile,
|
||||
} => cli_signer::signer_sign(id, pwfile, port, authfile)
|
||||
.map(|s| ExecutionAction::Instant(Some(s))),
|
||||
Cmd::SignerList { port, authfile } => {
|
||||
cli_signer::signer_list(port, authfile).map(|s| ExecutionAction::Instant(Some(s)))
|
||||
}
|
||||
Cmd::SignerReject { id, port, authfile } => {
|
||||
cli_signer::signer_reject(id, port, authfile).map(|s| ExecutionAction::Instant(Some(s)))
|
||||
}
|
||||
Cmd::Snapshot(snapshot_cmd) => {
|
||||
snapshot::execute(snapshot_cmd).map(|s| ExecutionAction::Instant(Some(s)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts the OpenEthereum client.
|
||||
///
|
||||
/// The first parameter is the command line arguments that you would pass when running the openethereum
|
||||
/// binary.
|
||||
///
|
||||
/// On error, returns what to print on stderr.
|
||||
// FIXME: totally independent logging capability, see https://github.com/openethereum/openethereum/issues/10252
|
||||
pub fn start(conf: Configuration, logger: Arc<RotatingLogger>) -> Result<ExecutionAction, String> {
|
||||
execute(conf.into_command()?, logger)
|
||||
}
|
17
bin/oe/logger/Cargo.toml
Normal file
17
bin/oe/logger/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
description = "Parity Ethereum Logger Implementation"
|
||||
name = "ethcore-logger"
|
||||
version = "1.12.0"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
env_logger = "0.5"
|
||||
atty = "0.2"
|
||||
lazy_static = "1.0"
|
||||
regex = "1.0"
|
||||
time = "0.1"
|
||||
parking_lot = "0.11.1"
|
||||
arrayvec = "0.4"
|
||||
ansi_term = "0.10"
|
191
bin/oe/logger/src/lib.rs
Normal file
191
bin/oe/logger/src/lib.rs
Normal file
@ -0,0 +1,191 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Logger for OpenEthereum executables
|
||||
|
||||
extern crate ansi_term;
|
||||
extern crate arrayvec;
|
||||
extern crate atty;
|
||||
extern crate env_logger;
|
||||
extern crate log as rlog;
|
||||
extern crate parking_lot;
|
||||
extern crate regex;
|
||||
extern crate time;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
mod rotating;
|
||||
|
||||
use ansi_term::Colour;
|
||||
use env_logger::{Builder as LogBuilder, Formatter};
|
||||
use parking_lot::Mutex;
|
||||
use regex::Regex;
|
||||
use std::{
|
||||
env, fs,
|
||||
io::Write,
|
||||
sync::{Arc, Weak},
|
||||
thread,
|
||||
};
|
||||
|
||||
pub use rotating::{init_log, RotatingLogger};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Config {
|
||||
pub mode: Option<String>,
|
||||
pub color: bool,
|
||||
pub file: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
mode: None,
|
||||
color: !cfg!(windows),
|
||||
file: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref ROTATING_LOGGER: Mutex<Weak<RotatingLogger>> = Mutex::new(Default::default());
|
||||
}
|
||||
|
||||
/// Sets up the logger
|
||||
pub fn setup_log(config: &Config) -> Result<Arc<RotatingLogger>, String> {
|
||||
use rlog::*;
|
||||
|
||||
let mut levels = String::new();
|
||||
let mut builder = LogBuilder::new();
|
||||
// Disable info logging by default for some modules:
|
||||
builder.filter(Some("ws"), LevelFilter::Warn);
|
||||
builder.filter(Some("hyper"), LevelFilter::Warn);
|
||||
builder.filter(Some("rustls"), LevelFilter::Error);
|
||||
// Enable info for others.
|
||||
builder.filter(None, LevelFilter::Info);
|
||||
|
||||
if let Ok(lvl) = env::var("RUST_LOG") {
|
||||
levels.push_str(&lvl);
|
||||
levels.push_str(",");
|
||||
builder.parse(&lvl);
|
||||
}
|
||||
|
||||
if let Some(ref s) = config.mode {
|
||||
levels.push_str(s);
|
||||
builder.parse(s);
|
||||
}
|
||||
|
||||
let isatty = atty::is(atty::Stream::Stderr);
|
||||
let enable_color = config.color && isatty;
|
||||
let logs = Arc::new(RotatingLogger::new(levels));
|
||||
let logger = logs.clone();
|
||||
let mut open_options = fs::OpenOptions::new();
|
||||
|
||||
let maybe_file = match config.file.as_ref() {
|
||||
Some(f) => Some(
|
||||
open_options
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(f)
|
||||
.map_err(|e| format!("Cannot write to log file given: {}, {}", f, e))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let format = move |buf: &mut Formatter, record: &Record| {
|
||||
let timestamp = time::strftime("%Y-%m-%d %H:%M:%S %Z", &time::now()).unwrap();
|
||||
|
||||
let with_color = if max_level() <= LevelFilter::Info {
|
||||
format!(
|
||||
"{} {}",
|
||||
Colour::Black.bold().paint(timestamp),
|
||||
record.args()
|
||||
)
|
||||
} else {
|
||||
let name = thread::current().name().map_or_else(Default::default, |x| {
|
||||
format!("{}", Colour::Blue.bold().paint(x))
|
||||
});
|
||||
format!(
|
||||
"{} {} {} {} {}",
|
||||
Colour::Black.bold().paint(timestamp),
|
||||
name,
|
||||
record.level(),
|
||||
record.target(),
|
||||
record.args()
|
||||
)
|
||||
};
|
||||
|
||||
let removed_color = kill_color(with_color.as_ref());
|
||||
|
||||
let ret = match enable_color {
|
||||
true => with_color,
|
||||
false => removed_color.clone(),
|
||||
};
|
||||
|
||||
if let Some(mut file) = maybe_file.as_ref() {
|
||||
// ignore errors - there's nothing we can do
|
||||
let _ = file.write_all(removed_color.as_bytes());
|
||||
let _ = file.write_all(b"\n");
|
||||
}
|
||||
logger.append(removed_color);
|
||||
if !isatty && record.level() <= Level::Info && atty::is(atty::Stream::Stdout) {
|
||||
// duplicate INFO/WARN output to console
|
||||
println!("{}", ret);
|
||||
}
|
||||
|
||||
writeln!(buf, "{}", ret)
|
||||
};
|
||||
|
||||
builder.format(format);
|
||||
builder
|
||||
.try_init()
|
||||
.and_then(|_| {
|
||||
*ROTATING_LOGGER.lock() = Arc::downgrade(&logs);
|
||||
Ok(logs)
|
||||
})
|
||||
// couldn't create new logger - try to fall back on previous logger.
|
||||
.or_else(|err| {
|
||||
ROTATING_LOGGER
|
||||
.lock()
|
||||
.upgrade()
|
||||
.ok_or_else(|| format!("{:?}", err))
|
||||
})
|
||||
}
|
||||
|
||||
fn kill_color(s: &str) -> String {
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new("\x1b\\[[^m]+m").unwrap();
|
||||
}
|
||||
RE.replace_all(s, "").to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_remove_colour() {
|
||||
let before = "test";
|
||||
let after = kill_color(&Colour::Red.bold().paint(before));
|
||||
assert_eq!(after, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_remove_multiple_colour() {
|
||||
let t = format!(
|
||||
"{} {}",
|
||||
Colour::Red.bold().paint("test"),
|
||||
Colour::White.normal().paint("again")
|
||||
);
|
||||
let after = kill_color(&t);
|
||||
assert_eq!(after, "test again");
|
||||
}
|
121
bin/oe/logger/src/rotating.rs
Normal file
121
bin/oe/logger/src/rotating.rs
Normal file
@ -0,0 +1,121 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Common log helper functions
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use env_logger::Builder as LogBuilder;
|
||||
use rlog::LevelFilter;
|
||||
use std::env;
|
||||
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
|
||||
lazy_static! {
|
||||
static ref LOG_DUMMY: () = {
|
||||
let mut builder = LogBuilder::new();
|
||||
builder.filter(None, LevelFilter::Info);
|
||||
|
||||
if let Ok(log) = env::var("RUST_LOG") {
|
||||
builder.parse(&log);
|
||||
}
|
||||
|
||||
if !builder.try_init().is_ok() {
|
||||
println!("logger initialization failed!");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Intialize log with default settings
|
||||
pub fn init_log() {
|
||||
*LOG_DUMMY
|
||||
}
|
||||
|
||||
const LOG_SIZE: usize = 128;
|
||||
|
||||
/// Logger implementation that keeps up to `LOG_SIZE` log elements.
|
||||
pub struct RotatingLogger {
|
||||
/// Defined logger levels
|
||||
levels: String,
|
||||
/// Logs array. Latest log is always at index 0
|
||||
logs: RwLock<ArrayVec<[String; LOG_SIZE]>>,
|
||||
}
|
||||
|
||||
impl RotatingLogger {
|
||||
/// Creates new `RotatingLogger` with given levels.
|
||||
/// It does not enforce levels - it's just read only.
|
||||
pub fn new(levels: String) -> Self {
|
||||
RotatingLogger {
|
||||
levels: levels,
|
||||
logs: RwLock::new(ArrayVec::<[_; LOG_SIZE]>::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Append new log entry
|
||||
pub fn append(&self, log: String) {
|
||||
let mut logs = self.logs.write();
|
||||
if logs.is_full() {
|
||||
logs.pop();
|
||||
}
|
||||
logs.insert(0, log);
|
||||
}
|
||||
|
||||
/// Return levels
|
||||
pub fn levels(&self) -> &str {
|
||||
&self.levels
|
||||
}
|
||||
|
||||
/// Return logs
|
||||
pub fn logs(&self) -> RwLockReadGuard<ArrayVec<[String; LOG_SIZE]>> {
|
||||
self.logs.read()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::RotatingLogger;
|
||||
|
||||
fn logger() -> RotatingLogger {
|
||||
RotatingLogger::new("test".to_owned())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_log_levels() {
|
||||
// given
|
||||
let logger = logger();
|
||||
|
||||
// when
|
||||
let levels = logger.levels();
|
||||
|
||||
// then
|
||||
assert_eq!(levels, "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_latest_logs() {
|
||||
// given
|
||||
let logger = logger();
|
||||
|
||||
// when
|
||||
logger.append("a".to_owned());
|
||||
logger.append("b".to_owned());
|
||||
|
||||
// then
|
||||
let logs = logger.logs();
|
||||
assert_eq!(logs[0], "b".to_owned());
|
||||
assert_eq!(logs[1], "a".to_owned());
|
||||
assert_eq!(logs.len(), 2);
|
||||
}
|
||||
}
|
180
bin/oe/main.rs
Normal file
180
bin/oe/main.rs
Normal file
@ -0,0 +1,180 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethcore client application.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
extern crate ctrlc;
|
||||
extern crate dir;
|
||||
extern crate fdlimit;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate ansi_term;
|
||||
extern crate openethereum;
|
||||
extern crate panic_hook;
|
||||
extern crate parity_daemonize;
|
||||
extern crate parking_lot;
|
||||
|
||||
extern crate ethcore_logger;
|
||||
#[cfg(windows)]
|
||||
extern crate winapi;
|
||||
|
||||
use std::{
|
||||
io::Write,
|
||||
process,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use ansi_term::Colour;
|
||||
use ctrlc::CtrlC;
|
||||
use ethcore_logger::setup_log;
|
||||
use fdlimit::raise_fd_limit;
|
||||
use openethereum::{start, ExecutionAction};
|
||||
use parity_daemonize::AsHandle;
|
||||
use parking_lot::{Condvar, Mutex};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Status used to exit or restart the program.
|
||||
struct ExitStatus {
|
||||
/// Whether the program panicked.
|
||||
panicking: bool,
|
||||
/// Whether the program should exit.
|
||||
should_exit: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), i32> {
|
||||
let conf = {
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
openethereum::Configuration::parse_cli(&args).unwrap_or_else(|e| e.exit())
|
||||
};
|
||||
|
||||
let logger = setup_log(&conf.logger_config()).unwrap_or_else(|e| {
|
||||
eprintln!("{}", e);
|
||||
process::exit(2)
|
||||
});
|
||||
|
||||
// FIXME: `pid_file` shouldn't need to cloned here
|
||||
// see: `https://github.com/paritytech/parity-daemonize/pull/13` for more info
|
||||
let handle = if let Some(pid) = conf.args.arg_daemon_pid_file.clone() {
|
||||
info!(
|
||||
"{}",
|
||||
Colour::Blue.paint("starting in daemon mode").to_string()
|
||||
);
|
||||
let _ = std::io::stdout().flush();
|
||||
|
||||
match parity_daemonize::daemonize(pid) {
|
||||
Ok(h) => Some(h),
|
||||
Err(e) => {
|
||||
error!("{}", Colour::Red.paint(format!("{}", e)));
|
||||
return Err(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// increase max number of open files
|
||||
raise_fd_limit();
|
||||
|
||||
let exit = Arc::new((
|
||||
Mutex::new(ExitStatus {
|
||||
panicking: false,
|
||||
should_exit: false,
|
||||
}),
|
||||
Condvar::new(),
|
||||
));
|
||||
|
||||
// Double panic can happen. So when we lock `ExitStatus` after the main thread is notified, it cannot be locked
|
||||
// again.
|
||||
let exiting = Arc::new(AtomicBool::new(false));
|
||||
|
||||
trace!(target: "mode", "Not hypervised: not setting exit handlers.");
|
||||
let exec = start(conf, logger);
|
||||
|
||||
match exec {
|
||||
Ok(result) => match result {
|
||||
ExecutionAction::Instant(output) => {
|
||||
if let Some(s) = output {
|
||||
println!("{}", s);
|
||||
}
|
||||
}
|
||||
ExecutionAction::Running(client) => {
|
||||
panic_hook::set_with({
|
||||
let e = exit.clone();
|
||||
let exiting = exiting.clone();
|
||||
move |panic_msg| {
|
||||
warn!("Panic occured, see stderr for details");
|
||||
eprintln!("{}", panic_msg);
|
||||
if !exiting.swap(true, Ordering::SeqCst) {
|
||||
*e.0.lock() = ExitStatus {
|
||||
panicking: true,
|
||||
should_exit: true,
|
||||
};
|
||||
e.1.notify_all();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CtrlC::set_handler({
|
||||
let e = exit.clone();
|
||||
let exiting = exiting.clone();
|
||||
move || {
|
||||
if !exiting.swap(true, Ordering::SeqCst) {
|
||||
*e.0.lock() = ExitStatus {
|
||||
panicking: false,
|
||||
should_exit: true,
|
||||
};
|
||||
e.1.notify_all();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// so the client has started successfully
|
||||
// if this is a daemon, detach from the parent process
|
||||
if let Some(mut handle) = handle {
|
||||
handle.detach()
|
||||
}
|
||||
|
||||
// Wait for signal
|
||||
let mut lock = exit.0.lock();
|
||||
if !lock.should_exit {
|
||||
let _ = exit.1.wait(&mut lock);
|
||||
}
|
||||
|
||||
client.shutdown();
|
||||
|
||||
if lock.panicking {
|
||||
return Err(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
// error occured during start up
|
||||
// if this is a daemon, detach from the parent process
|
||||
if let Some(mut handle) = handle {
|
||||
handle.detach_with_msg(format!("{}", Colour::Red.paint(&err)))
|
||||
}
|
||||
eprintln!("{}", err);
|
||||
return Err(1);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
117
bin/oe/metrics.rs
Normal file
117
bin/oe/metrics.rs
Normal file
@ -0,0 +1,117 @@
|
||||
use std::{sync::Arc, time::Instant};
|
||||
|
||||
use crate::{futures::Future, rpc, rpc_apis};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use hyper::{service::service_fn_ok, Body, Method, Request, Response, Server, StatusCode};
|
||||
|
||||
use stats::{
|
||||
prometheus::{self, Encoder},
|
||||
PrometheusMetrics, PrometheusRegistry,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MetricsConfiguration {
|
||||
/// Are metrics enabled (default is false)?
|
||||
pub enabled: bool,
|
||||
/// Prefix
|
||||
pub prefix: String,
|
||||
/// The IP of the network interface used (default is 127.0.0.1).
|
||||
pub interface: String,
|
||||
/// The network port (default is 3000).
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl Default for MetricsConfiguration {
|
||||
fn default() -> Self {
|
||||
MetricsConfiguration {
|
||||
enabled: false,
|
||||
prefix: "".into(),
|
||||
interface: "127.0.0.1".into(),
|
||||
port: 3000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
rpc_apis: Arc<rpc_apis::FullDependencies>,
|
||||
}
|
||||
|
||||
fn handle_request(
|
||||
req: Request<Body>,
|
||||
conf: Arc<MetricsConfiguration>,
|
||||
state: Arc<Mutex<State>>,
|
||||
) -> Response<Body> {
|
||||
let (parts, _body) = req.into_parts();
|
||||
match (parts.method, parts.uri.path()) {
|
||||
(Method::GET, "/metrics") => {
|
||||
let start = Instant::now();
|
||||
|
||||
let mut reg = PrometheusRegistry::new(conf.prefix.clone());
|
||||
let state = state.lock();
|
||||
state.rpc_apis.client.prometheus_metrics(&mut reg);
|
||||
state.rpc_apis.sync.prometheus_metrics(&mut reg);
|
||||
let elapsed = start.elapsed();
|
||||
reg.register_gauge(
|
||||
"metrics_time",
|
||||
"Time to perform rpc metrics",
|
||||
elapsed.as_millis() as i64,
|
||||
);
|
||||
|
||||
let mut buffer = vec![];
|
||||
let encoder = prometheus::TextEncoder::new();
|
||||
let metric_families = reg.registry().gather();
|
||||
|
||||
encoder
|
||||
.encode(&metric_families, &mut buffer)
|
||||
.expect("all source of metrics are static; qed");
|
||||
let text = String::from_utf8(buffer).expect("metrics encoding is ASCII; qed");
|
||||
|
||||
Response::new(Body::from(text))
|
||||
}
|
||||
(_, _) => {
|
||||
let mut res = Response::new(Body::from("not found"));
|
||||
*res.status_mut() = StatusCode::NOT_FOUND;
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the prometheus metrics server accessible via GET <host>:<port>/metrics
|
||||
pub fn start_prometheus_metrics(
|
||||
conf: &MetricsConfiguration,
|
||||
deps: &rpc::Dependencies<rpc_apis::FullDependencies>,
|
||||
) -> Result<(), String> {
|
||||
if !conf.enabled {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let addr = format!("{}:{}", conf.interface, conf.port);
|
||||
let addr = addr
|
||||
.parse()
|
||||
.map_err(|err| format!("Failed to parse address '{}': {}", addr, err))?;
|
||||
|
||||
let state = State {
|
||||
rpc_apis: deps.apis.clone(),
|
||||
};
|
||||
let state = Arc::new(Mutex::new(state));
|
||||
let conf = Arc::new(conf.to_owned());
|
||||
let server = Server::bind(&addr)
|
||||
.serve(move || {
|
||||
// This is the `Service` that will handle the connection.
|
||||
// `service_fn_ok` is a helper to convert a function that
|
||||
// returns a Response into a `Service`.
|
||||
let state = state.clone();
|
||||
let conf = conf.clone();
|
||||
service_fn_ok(move |req: Request<Body>| {
|
||||
handle_request(req, conf.clone(), state.clone())
|
||||
})
|
||||
})
|
||||
.map_err(|e| eprintln!("server error: {}", e));
|
||||
info!("Started prometeus metrics at http://{}/metrics", addr);
|
||||
|
||||
deps.executor.spawn(server);
|
||||
|
||||
Ok(())
|
||||
}
|
63
bin/oe/modules.rs
Normal file
63
bin/oe/modules.rs
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::{mpsc, Arc};
|
||||
|
||||
use crate::{
|
||||
sync::{self, ConnectionFilter, NetworkConfiguration, Params, SyncConfig},
|
||||
types::BlockNumber,
|
||||
};
|
||||
use ethcore::{client::BlockChainClient, snapshot::SnapshotService};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
pub use crate::sync::{EthSync, ManageNetwork, SyncProvider};
|
||||
pub use ethcore::client::ChainNotify;
|
||||
use ethcore_logger::Config as LogConfig;
|
||||
|
||||
pub type SyncModules = (
|
||||
Arc<dyn SyncProvider>,
|
||||
Arc<dyn ManageNetwork>,
|
||||
Arc<dyn ChainNotify>,
|
||||
mpsc::Sender<sync::PriorityTask>,
|
||||
);
|
||||
|
||||
pub fn sync(
|
||||
config: SyncConfig,
|
||||
network_config: NetworkConfiguration,
|
||||
chain: Arc<dyn BlockChainClient>,
|
||||
forks: BTreeSet<BlockNumber>,
|
||||
snapshot_service: Arc<dyn SnapshotService>,
|
||||
_log_settings: &LogConfig,
|
||||
connection_filter: Option<Arc<dyn ConnectionFilter>>,
|
||||
) -> Result<SyncModules, sync::Error> {
|
||||
let eth_sync = EthSync::new(
|
||||
Params {
|
||||
config,
|
||||
chain,
|
||||
forks,
|
||||
snapshot_service,
|
||||
network_config,
|
||||
},
|
||||
connection_filter,
|
||||
)?;
|
||||
|
||||
Ok((
|
||||
eth_sync.clone() as Arc<dyn SyncProvider>,
|
||||
eth_sync.clone() as Arc<dyn ManageNetwork>,
|
||||
eth_sync.clone() as Arc<dyn ChainNotify>,
|
||||
eth_sync.priority_tasks(),
|
||||
))
|
||||
}
|
542
bin/oe/params.rs
Normal file
542
bin/oe/params.rs
Normal file
@ -0,0 +1,542 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{collections::HashSet, fmt, fs, num::NonZeroU32, str, time::Duration};
|
||||
|
||||
use crate::{
|
||||
miner::{
|
||||
gas_price_calibrator::{GasPriceCalibrator, GasPriceCalibratorOptions},
|
||||
gas_pricer::GasPricer,
|
||||
},
|
||||
user_defaults::UserDefaults,
|
||||
};
|
||||
use ethcore::{
|
||||
client::Mode,
|
||||
ethereum,
|
||||
spec::{Spec, SpecParams},
|
||||
};
|
||||
use ethereum_types::{Address, U256};
|
||||
use fetch::Client as FetchClient;
|
||||
use journaldb::Algorithm;
|
||||
use parity_runtime::Executor;
|
||||
use parity_version::version_data;
|
||||
|
||||
use crate::configuration;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum SpecType {
|
||||
Foundation,
|
||||
Poanet,
|
||||
Xdai,
|
||||
Volta,
|
||||
Ewc,
|
||||
Musicoin,
|
||||
Ellaism,
|
||||
Mix,
|
||||
Callisto,
|
||||
Morden,
|
||||
Ropsten,
|
||||
Kovan,
|
||||
Rinkeby,
|
||||
Goerli,
|
||||
Sokol,
|
||||
Yolo3,
|
||||
Dev,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl Default for SpecType {
|
||||
fn default() -> Self {
|
||||
SpecType::Foundation
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for SpecType {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let spec = match s {
|
||||
"eth" | "ethereum" | "foundation" | "mainnet" => SpecType::Foundation,
|
||||
"poanet" | "poacore" => SpecType::Poanet,
|
||||
"xdai" => SpecType::Xdai,
|
||||
"volta" => SpecType::Volta,
|
||||
"ewc" | "energyweb" => SpecType::Ewc,
|
||||
"musicoin" => SpecType::Musicoin,
|
||||
"ellaism" => SpecType::Ellaism,
|
||||
"mix" => SpecType::Mix,
|
||||
"callisto" => SpecType::Callisto,
|
||||
"morden" => SpecType::Morden,
|
||||
"ropsten" => SpecType::Ropsten,
|
||||
"kovan" => SpecType::Kovan,
|
||||
"rinkeby" => SpecType::Rinkeby,
|
||||
"goerli" | "görli" | "testnet" => SpecType::Goerli,
|
||||
"sokol" | "poasokol" => SpecType::Sokol,
|
||||
"yolo3" => SpecType::Yolo3,
|
||||
"dev" => SpecType::Dev,
|
||||
other => SpecType::Custom(other.into()),
|
||||
};
|
||||
Ok(spec)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SpecType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(match *self {
|
||||
SpecType::Foundation => "foundation",
|
||||
SpecType::Poanet => "poanet",
|
||||
SpecType::Xdai => "xdai",
|
||||
SpecType::Volta => "volta",
|
||||
SpecType::Ewc => "energyweb",
|
||||
SpecType::Musicoin => "musicoin",
|
||||
SpecType::Ellaism => "ellaism",
|
||||
SpecType::Mix => "mix",
|
||||
SpecType::Callisto => "callisto",
|
||||
SpecType::Morden => "morden",
|
||||
SpecType::Ropsten => "ropsten",
|
||||
SpecType::Kovan => "kovan",
|
||||
SpecType::Rinkeby => "rinkeby",
|
||||
SpecType::Goerli => "goerli",
|
||||
SpecType::Sokol => "sokol",
|
||||
SpecType::Yolo3 => "yolo3",
|
||||
SpecType::Dev => "dev",
|
||||
SpecType::Custom(ref custom) => custom,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecType {
|
||||
pub fn spec<'a, T: Into<SpecParams<'a>>>(&self, params: T) -> Result<Spec, String> {
|
||||
let params = params.into();
|
||||
match *self {
|
||||
SpecType::Foundation => Ok(ethereum::new_foundation(params)),
|
||||
SpecType::Poanet => Ok(ethereum::new_poanet(params)),
|
||||
SpecType::Xdai => Ok(ethereum::new_xdai(params)),
|
||||
SpecType::Volta => Ok(ethereum::new_volta(params)),
|
||||
SpecType::Ewc => Ok(ethereum::new_ewc(params)),
|
||||
SpecType::Musicoin => Ok(ethereum::new_musicoin(params)),
|
||||
SpecType::Ellaism => Ok(ethereum::new_ellaism(params)),
|
||||
SpecType::Mix => Ok(ethereum::new_mix(params)),
|
||||
SpecType::Callisto => Ok(ethereum::new_callisto(params)),
|
||||
SpecType::Morden => Ok(ethereum::new_morden(params)),
|
||||
SpecType::Ropsten => Ok(ethereum::new_ropsten(params)),
|
||||
SpecType::Kovan => Ok(ethereum::new_kovan(params)),
|
||||
SpecType::Rinkeby => Ok(ethereum::new_rinkeby(params)),
|
||||
SpecType::Goerli => Ok(ethereum::new_goerli(params)),
|
||||
SpecType::Sokol => Ok(ethereum::new_sokol(params)),
|
||||
SpecType::Yolo3 => Ok(ethereum::new_yolo3(params)),
|
||||
SpecType::Dev => Ok(Spec::new_instant()),
|
||||
SpecType::Custom(ref filename) => {
|
||||
let file = fs::File::open(filename).map_err(|e| {
|
||||
format!("Could not load specification file at {}: {}", filename, e)
|
||||
})?;
|
||||
Spec::load(params, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn legacy_fork_name(&self) -> Option<String> {
|
||||
match *self {
|
||||
SpecType::Musicoin => Some("musicoin".to_owned()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Pruning {
|
||||
Specific(Algorithm),
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl Default for Pruning {
|
||||
fn default() -> Self {
|
||||
Pruning::Auto
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for Pruning {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"auto" => Ok(Pruning::Auto),
|
||||
other => other.parse().map(Pruning::Specific),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pruning {
|
||||
pub fn to_algorithm(&self, user_defaults: &UserDefaults) -> Algorithm {
|
||||
match *self {
|
||||
Pruning::Specific(algo) => algo,
|
||||
Pruning::Auto => user_defaults.pruning,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ResealPolicy {
|
||||
pub own: bool,
|
||||
pub external: bool,
|
||||
}
|
||||
|
||||
impl Default for ResealPolicy {
|
||||
fn default() -> Self {
|
||||
ResealPolicy {
|
||||
own: true,
|
||||
external: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for ResealPolicy {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (own, external) = match s {
|
||||
"none" => (false, false),
|
||||
"own" => (true, false),
|
||||
"ext" => (false, true),
|
||||
"all" => (true, true),
|
||||
x => return Err(format!("Invalid reseal value: {}", x)),
|
||||
};
|
||||
|
||||
let reseal = ResealPolicy {
|
||||
own: own,
|
||||
external: external,
|
||||
};
|
||||
|
||||
Ok(reseal)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct AccountsConfig {
|
||||
pub iterations: NonZeroU32,
|
||||
pub refresh_time: u64,
|
||||
pub testnet: bool,
|
||||
pub password_files: Vec<String>,
|
||||
pub unlocked_accounts: Vec<Address>,
|
||||
pub enable_fast_unlock: bool,
|
||||
}
|
||||
|
||||
impl Default for AccountsConfig {
|
||||
fn default() -> Self {
|
||||
AccountsConfig {
|
||||
iterations: NonZeroU32::new(10240).expect("10240 > 0; qed"),
|
||||
refresh_time: 5,
|
||||
testnet: false,
|
||||
password_files: Vec::new(),
|
||||
unlocked_accounts: Vec::new(),
|
||||
enable_fast_unlock: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum GasPricerConfig {
|
||||
Fixed(U256),
|
||||
Calibrated {
|
||||
usd_per_tx: f32,
|
||||
recalibration_period: Duration,
|
||||
api_endpoint: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for GasPricerConfig {
|
||||
fn default() -> Self {
|
||||
GasPricerConfig::Calibrated {
|
||||
usd_per_tx: 0.0001f32,
|
||||
recalibration_period: Duration::from_secs(3600),
|
||||
api_endpoint: configuration::ETHERSCAN_ETH_PRICE_ENDPOINT.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GasPricerConfig {
|
||||
pub fn to_gas_pricer(&self, fetch: FetchClient, p: Executor) -> GasPricer {
|
||||
match *self {
|
||||
GasPricerConfig::Fixed(u) => GasPricer::Fixed(u),
|
||||
GasPricerConfig::Calibrated {
|
||||
usd_per_tx,
|
||||
recalibration_period,
|
||||
ref api_endpoint,
|
||||
} => GasPricer::new_calibrated(GasPriceCalibrator::new(
|
||||
GasPriceCalibratorOptions {
|
||||
usd_per_tx: usd_per_tx,
|
||||
recalibration_period: recalibration_period,
|
||||
},
|
||||
fetch,
|
||||
p,
|
||||
api_endpoint.clone(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct MinerExtras {
|
||||
pub author: Address,
|
||||
pub engine_signer: Address,
|
||||
pub extra_data: Vec<u8>,
|
||||
pub gas_range_target: (U256, U256),
|
||||
pub work_notify: Vec<String>,
|
||||
pub local_accounts: HashSet<Address>,
|
||||
}
|
||||
|
||||
impl Default for MinerExtras {
|
||||
fn default() -> Self {
|
||||
MinerExtras {
|
||||
author: Default::default(),
|
||||
engine_signer: Default::default(),
|
||||
extra_data: version_data(),
|
||||
gas_range_target: (8_000_000.into(), 10_000_000.into()),
|
||||
work_notify: Default::default(),
|
||||
local_accounts: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 3-value enum.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Switch {
|
||||
/// True.
|
||||
On,
|
||||
/// False.
|
||||
Off,
|
||||
/// Auto.
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl Default for Switch {
|
||||
fn default() -> Self {
|
||||
Switch::Auto
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for Switch {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"on" => Ok(Switch::On),
|
||||
"off" => Ok(Switch::Off),
|
||||
"auto" => Ok(Switch::Auto),
|
||||
other => Err(format!("Invalid switch value: {}", other)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tracing_switch_to_bool(
|
||||
switch: Switch,
|
||||
user_defaults: &UserDefaults,
|
||||
) -> Result<bool, String> {
|
||||
match (user_defaults.is_first_launch, switch, user_defaults.tracing) {
|
||||
(false, Switch::On, false) => Err("TraceDB resync required".into()),
|
||||
(_, Switch::On, _) => Ok(true),
|
||||
(_, Switch::Off, _) => Ok(false),
|
||||
(_, Switch::Auto, def) => Ok(def),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fatdb_switch_to_bool(
|
||||
switch: Switch,
|
||||
user_defaults: &UserDefaults,
|
||||
_algorithm: Algorithm,
|
||||
) -> Result<bool, String> {
|
||||
let result = match (user_defaults.is_first_launch, switch, user_defaults.fat_db) {
|
||||
(false, Switch::On, false) => Err("FatDB resync required".into()),
|
||||
(_, Switch::On, _) => Ok(true),
|
||||
(_, Switch::Off, _) => Ok(false),
|
||||
(_, Switch::Auto, def) => Ok(def),
|
||||
};
|
||||
result
|
||||
}
|
||||
|
||||
pub fn mode_switch_to_bool(
|
||||
switch: Option<Mode>,
|
||||
user_defaults: &UserDefaults,
|
||||
) -> Result<Mode, String> {
|
||||
Ok(switch.unwrap_or(user_defaults.mode().clone()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{tracing_switch_to_bool, Pruning, ResealPolicy, SpecType, Switch};
|
||||
use crate::user_defaults::UserDefaults;
|
||||
use journaldb::Algorithm;
|
||||
|
||||
#[test]
|
||||
fn test_spec_type_parsing() {
|
||||
assert_eq!(SpecType::Foundation, "eth".parse().unwrap());
|
||||
assert_eq!(SpecType::Foundation, "ethereum".parse().unwrap());
|
||||
assert_eq!(SpecType::Foundation, "foundation".parse().unwrap());
|
||||
assert_eq!(SpecType::Foundation, "mainnet".parse().unwrap());
|
||||
assert_eq!(SpecType::Poanet, "poanet".parse().unwrap());
|
||||
assert_eq!(SpecType::Poanet, "poacore".parse().unwrap());
|
||||
assert_eq!(SpecType::Xdai, "xdai".parse().unwrap());
|
||||
assert_eq!(SpecType::Volta, "volta".parse().unwrap());
|
||||
assert_eq!(SpecType::Ewc, "ewc".parse().unwrap());
|
||||
assert_eq!(SpecType::Ewc, "energyweb".parse().unwrap());
|
||||
assert_eq!(SpecType::Musicoin, "musicoin".parse().unwrap());
|
||||
assert_eq!(SpecType::Ellaism, "ellaism".parse().unwrap());
|
||||
assert_eq!(SpecType::Mix, "mix".parse().unwrap());
|
||||
assert_eq!(SpecType::Callisto, "callisto".parse().unwrap());
|
||||
assert_eq!(SpecType::Morden, "morden".parse().unwrap());
|
||||
assert_eq!(SpecType::Ropsten, "ropsten".parse().unwrap());
|
||||
assert_eq!(SpecType::Kovan, "kovan".parse().unwrap());
|
||||
assert_eq!(SpecType::Rinkeby, "rinkeby".parse().unwrap());
|
||||
assert_eq!(SpecType::Goerli, "goerli".parse().unwrap());
|
||||
assert_eq!(SpecType::Goerli, "görli".parse().unwrap());
|
||||
assert_eq!(SpecType::Goerli, "testnet".parse().unwrap());
|
||||
assert_eq!(SpecType::Sokol, "sokol".parse().unwrap());
|
||||
assert_eq!(SpecType::Sokol, "poasokol".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spec_type_default() {
|
||||
assert_eq!(SpecType::Foundation, SpecType::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spec_type_display() {
|
||||
assert_eq!(format!("{}", SpecType::Foundation), "foundation");
|
||||
assert_eq!(format!("{}", SpecType::Poanet), "poanet");
|
||||
assert_eq!(format!("{}", SpecType::Xdai), "xdai");
|
||||
assert_eq!(format!("{}", SpecType::Volta), "volta");
|
||||
assert_eq!(format!("{}", SpecType::Ewc), "energyweb");
|
||||
assert_eq!(format!("{}", SpecType::Musicoin), "musicoin");
|
||||
assert_eq!(format!("{}", SpecType::Ellaism), "ellaism");
|
||||
assert_eq!(format!("{}", SpecType::Mix), "mix");
|
||||
assert_eq!(format!("{}", SpecType::Callisto), "callisto");
|
||||
assert_eq!(format!("{}", SpecType::Morden), "morden");
|
||||
assert_eq!(format!("{}", SpecType::Ropsten), "ropsten");
|
||||
assert_eq!(format!("{}", SpecType::Kovan), "kovan");
|
||||
assert_eq!(format!("{}", SpecType::Rinkeby), "rinkeby");
|
||||
assert_eq!(format!("{}", SpecType::Goerli), "goerli");
|
||||
assert_eq!(format!("{}", SpecType::Sokol), "sokol");
|
||||
assert_eq!(format!("{}", SpecType::Dev), "dev");
|
||||
assert_eq!(format!("{}", SpecType::Custom("foo/bar".into())), "foo/bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pruning_parsing() {
|
||||
assert_eq!(Pruning::Auto, "auto".parse().unwrap());
|
||||
assert_eq!(
|
||||
Pruning::Specific(Algorithm::Archive),
|
||||
"archive".parse().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Pruning::Specific(Algorithm::EarlyMerge),
|
||||
"light".parse().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Pruning::Specific(Algorithm::OverlayRecent),
|
||||
"fast".parse().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Pruning::Specific(Algorithm::RefCounted),
|
||||
"basic".parse().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pruning_default() {
|
||||
assert_eq!(Pruning::Auto, Pruning::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reseal_policy_parsing() {
|
||||
let none = ResealPolicy {
|
||||
own: false,
|
||||
external: false,
|
||||
};
|
||||
let own = ResealPolicy {
|
||||
own: true,
|
||||
external: false,
|
||||
};
|
||||
let ext = ResealPolicy {
|
||||
own: false,
|
||||
external: true,
|
||||
};
|
||||
let all = ResealPolicy {
|
||||
own: true,
|
||||
external: true,
|
||||
};
|
||||
assert_eq!(none, "none".parse().unwrap());
|
||||
assert_eq!(own, "own".parse().unwrap());
|
||||
assert_eq!(ext, "ext".parse().unwrap());
|
||||
assert_eq!(all, "all".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reseal_policy_default() {
|
||||
let all = ResealPolicy {
|
||||
own: true,
|
||||
external: true,
|
||||
};
|
||||
assert_eq!(all, ResealPolicy::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_switch_parsing() {
|
||||
assert_eq!(Switch::On, "on".parse().unwrap());
|
||||
assert_eq!(Switch::Off, "off".parse().unwrap());
|
||||
assert_eq!(Switch::Auto, "auto".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_switch_default() {
|
||||
assert_eq!(Switch::default(), Switch::Auto);
|
||||
}
|
||||
|
||||
fn user_defaults_with_tracing(first_launch: bool, tracing: bool) -> UserDefaults {
|
||||
let mut ud = UserDefaults::default();
|
||||
ud.is_first_launch = first_launch;
|
||||
ud.tracing = tracing;
|
||||
ud
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_switch_to_bool() {
|
||||
assert!(
|
||||
!tracing_switch_to_bool(Switch::Off, &user_defaults_with_tracing(true, true)).unwrap()
|
||||
);
|
||||
assert!(
|
||||
!tracing_switch_to_bool(Switch::Off, &user_defaults_with_tracing(true, false)).unwrap()
|
||||
);
|
||||
assert!(
|
||||
!tracing_switch_to_bool(Switch::Off, &user_defaults_with_tracing(false, true)).unwrap()
|
||||
);
|
||||
assert!(
|
||||
!tracing_switch_to_bool(Switch::Off, &user_defaults_with_tracing(false, false))
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
assert!(
|
||||
tracing_switch_to_bool(Switch::On, &user_defaults_with_tracing(true, true)).unwrap()
|
||||
);
|
||||
assert!(
|
||||
tracing_switch_to_bool(Switch::On, &user_defaults_with_tracing(true, false)).unwrap()
|
||||
);
|
||||
assert!(
|
||||
tracing_switch_to_bool(Switch::On, &user_defaults_with_tracing(false, true)).unwrap()
|
||||
);
|
||||
assert!(
|
||||
tracing_switch_to_bool(Switch::On, &user_defaults_with_tracing(false, false)).is_err()
|
||||
);
|
||||
}
|
||||
}
|
65
bin/oe/presale.rs
Normal file
65
bin/oe/presale.rs
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
helpers::{password_from_file, password_prompt},
|
||||
params::SpecType,
|
||||
};
|
||||
use crypto::publickey;
|
||||
|
||||
use ethkey::Password;
|
||||
use ethstore::PresaleWallet;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ImportWallet {
|
||||
pub iterations: NonZeroU32,
|
||||
pub path: String,
|
||||
pub spec: SpecType,
|
||||
pub wallet_path: String,
|
||||
pub password_file: Option<String>,
|
||||
}
|
||||
|
||||
pub fn execute(cmd: ImportWallet) -> Result<String, String> {
|
||||
let password = match cmd.password_file.clone() {
|
||||
Some(file) => password_from_file(file)?,
|
||||
None => password_prompt()?,
|
||||
};
|
||||
|
||||
let wallet = PresaleWallet::open(cmd.wallet_path.clone())
|
||||
.map_err(|_| "Unable to open presale wallet.")?;
|
||||
let kp = wallet.decrypt(&password).map_err(|_| "Invalid password.")?;
|
||||
let address = kp.address();
|
||||
import_account(&cmd, kp, password);
|
||||
Ok(format!("{:?}", address))
|
||||
}
|
||||
|
||||
#[cfg(feature = "accounts")]
|
||||
|
||||
pub fn import_account(cmd: &ImportWallet, kp: publickey::KeyPair, password: Password) {
|
||||
use accounts::{AccountProvider, AccountProviderSettings};
|
||||
use ethstore::{accounts_dir::RootDiskDirectory, EthStore};
|
||||
|
||||
let dir = Box::new(RootDiskDirectory::create(cmd.path.clone()).unwrap());
|
||||
let secret_store = Box::new(EthStore::open_with_iterations(dir, cmd.iterations).unwrap());
|
||||
let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default());
|
||||
acc_provider
|
||||
.insert_account(kp.secret().clone(), &password)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "accounts"))]
|
||||
pub fn import_account(_cmd: &ImportWallet, _kp: publickey::KeyPair, _password: Password) {}
|
364
bin/oe/rpc.rs
Normal file
364
bin/oe/rpc.rs
Normal file
@ -0,0 +1,364 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{collections::HashSet, io, path::PathBuf, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
helpers::parity_ipc_path,
|
||||
rpc_apis::{self, ApiSet},
|
||||
};
|
||||
use dir::{default_data_path, helpers::replace_home};
|
||||
use jsonrpc_core::MetaIoHandler;
|
||||
use parity_rpc::{
|
||||
self as rpc,
|
||||
informant::{Middleware, RpcStats},
|
||||
DomainsValidation, Metadata,
|
||||
};
|
||||
use parity_runtime::Executor;
|
||||
|
||||
pub use parity_rpc::{HttpServer, IpcServer, RequestMiddleware};
|
||||
//pub use parity_rpc::ws::Server as WsServer;
|
||||
pub use parity_rpc::ws::{ws, Server as WsServer};
|
||||
|
||||
pub const DAPPS_DOMAIN: &'static str = "web3.site";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct HttpConfiguration {
|
||||
pub enabled: bool,
|
||||
pub interface: String,
|
||||
pub port: u16,
|
||||
pub apis: ApiSet,
|
||||
pub cors: Option<Vec<String>>,
|
||||
pub hosts: Option<Vec<String>>,
|
||||
pub server_threads: usize,
|
||||
pub processing_threads: usize,
|
||||
pub max_payload: usize,
|
||||
pub keep_alive: bool,
|
||||
}
|
||||
|
||||
impl Default for HttpConfiguration {
|
||||
fn default() -> Self {
|
||||
HttpConfiguration {
|
||||
enabled: true,
|
||||
interface: "127.0.0.1".into(),
|
||||
port: 8545,
|
||||
apis: ApiSet::UnsafeContext,
|
||||
cors: Some(vec![]),
|
||||
hosts: Some(vec![]),
|
||||
server_threads: 1,
|
||||
processing_threads: 4,
|
||||
max_payload: 5,
|
||||
keep_alive: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct IpcConfiguration {
|
||||
pub enabled: bool,
|
||||
pub socket_addr: String,
|
||||
pub apis: ApiSet,
|
||||
}
|
||||
|
||||
impl Default for IpcConfiguration {
|
||||
fn default() -> Self {
|
||||
IpcConfiguration {
|
||||
enabled: true,
|
||||
socket_addr: if cfg!(windows) {
|
||||
r"\\.\pipe\jsonrpc.ipc".into()
|
||||
} else {
|
||||
let data_dir = ::dir::default_data_path();
|
||||
parity_ipc_path(&data_dir, "$BASE/jsonrpc.ipc", 0)
|
||||
},
|
||||
apis: ApiSet::IpcContext,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct WsConfiguration {
|
||||
pub enabled: bool,
|
||||
pub interface: String,
|
||||
pub port: u16,
|
||||
pub apis: ApiSet,
|
||||
pub max_connections: usize,
|
||||
pub origins: Option<Vec<String>>,
|
||||
pub hosts: Option<Vec<String>>,
|
||||
pub signer_path: PathBuf,
|
||||
pub support_token_api: bool,
|
||||
pub max_payload: usize,
|
||||
}
|
||||
|
||||
impl Default for WsConfiguration {
|
||||
fn default() -> Self {
|
||||
let data_dir = default_data_path();
|
||||
WsConfiguration {
|
||||
enabled: true,
|
||||
interface: "127.0.0.1".into(),
|
||||
port: 8546,
|
||||
apis: ApiSet::UnsafeContext,
|
||||
max_connections: 100,
|
||||
origins: Some(vec![
|
||||
"parity://*".into(),
|
||||
"chrome-extension://*".into(),
|
||||
"moz-extension://*".into(),
|
||||
]),
|
||||
hosts: Some(Vec::new()),
|
||||
signer_path: replace_home(&data_dir, "$BASE/signer").into(),
|
||||
support_token_api: true,
|
||||
max_payload: 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WsConfiguration {
|
||||
pub fn address(&self) -> Option<rpc::Host> {
|
||||
address(self.enabled, &self.interface, self.port, &self.hosts)
|
||||
}
|
||||
}
|
||||
|
||||
fn address(
|
||||
enabled: bool,
|
||||
bind_iface: &str,
|
||||
bind_port: u16,
|
||||
hosts: &Option<Vec<String>>,
|
||||
) -> Option<rpc::Host> {
|
||||
if !enabled {
|
||||
return None;
|
||||
}
|
||||
|
||||
match *hosts {
|
||||
Some(ref hosts) if !hosts.is_empty() => Some(hosts[0].clone().into()),
|
||||
_ => Some(format!("{}:{}", bind_iface, bind_port).into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Dependencies<D: rpc_apis::Dependencies> {
|
||||
pub apis: Arc<D>,
|
||||
pub executor: Executor,
|
||||
pub stats: Arc<RpcStats>,
|
||||
}
|
||||
|
||||
pub fn new_ws<D: rpc_apis::Dependencies>(
|
||||
conf: WsConfiguration,
|
||||
deps: &Dependencies<D>,
|
||||
) -> Result<Option<WsServer>, String> {
|
||||
if !conf.enabled {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let domain = DAPPS_DOMAIN;
|
||||
let url = format!("{}:{}", conf.interface, conf.port);
|
||||
let addr = url
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid WebSockets listen host/port given: {}", url))?;
|
||||
|
||||
let full_handler = setup_apis(rpc_apis::ApiSet::All, deps);
|
||||
let handler = {
|
||||
let mut handler = MetaIoHandler::with_middleware((
|
||||
rpc::WsDispatcher::new(full_handler),
|
||||
Middleware::new(deps.stats.clone(), deps.apis.activity_notifier()),
|
||||
));
|
||||
let apis = conf.apis.list_apis();
|
||||
deps.apis.extend_with_set(&mut handler, &apis);
|
||||
|
||||
handler
|
||||
};
|
||||
|
||||
let allowed_origins = into_domains(with_domain(conf.origins, domain, &None));
|
||||
let allowed_hosts = into_domains(with_domain(conf.hosts, domain, &Some(url.clone().into())));
|
||||
|
||||
let signer_path;
|
||||
let path = match conf.support_token_api {
|
||||
true => {
|
||||
signer_path = crate::signer::codes_path(&conf.signer_path);
|
||||
Some(signer_path.as_path())
|
||||
}
|
||||
false => None,
|
||||
};
|
||||
let start_result = rpc::start_ws(
|
||||
&addr,
|
||||
handler,
|
||||
allowed_origins,
|
||||
allowed_hosts,
|
||||
conf.max_connections,
|
||||
rpc::WsExtractor::new(path.clone()),
|
||||
rpc::WsExtractor::new(path.clone()),
|
||||
rpc::WsStats::new(deps.stats.clone()),
|
||||
conf.max_payload,
|
||||
);
|
||||
|
||||
// match start_result {
|
||||
// Ok(server) => Ok(Some(server)),
|
||||
// Err(rpc::ws::Error::Io(rpc::ws::ErrorKind::Io(ref err), _)) if err.kind() == io::ErrorKind::AddrInUse => Err(
|
||||
// format!("WebSockets address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --ws-port and --ws-interface options.", url)
|
||||
// ),
|
||||
// Err(e) => Err(format!("WebSockets error: {:?}", e)),
|
||||
// }
|
||||
match start_result {
|
||||
Ok(server) => Ok(Some(server)),
|
||||
Err(rpc::ws::Error::WsError(ws::Error {
|
||||
kind: ws::ErrorKind::Io(ref err), ..
|
||||
})) if err.kind() == io::ErrorKind::AddrInUse => Err(
|
||||
format!("WebSockets address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --ws-port and --ws-interface options.", url)
|
||||
),
|
||||
Err(e) => Err(format!("WebSockets error: {:?}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_http<D: rpc_apis::Dependencies>(
|
||||
id: &str,
|
||||
options: &str,
|
||||
conf: HttpConfiguration,
|
||||
deps: &Dependencies<D>,
|
||||
) -> Result<Option<HttpServer>, String> {
|
||||
if !conf.enabled {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let domain = DAPPS_DOMAIN;
|
||||
let url = format!("{}:{}", conf.interface, conf.port);
|
||||
let addr = url
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid {} listen host/port given: {}", id, url))?;
|
||||
let handler = setup_apis(conf.apis, deps);
|
||||
|
||||
let cors_domains = into_domains(conf.cors);
|
||||
let allowed_hosts = into_domains(with_domain(conf.hosts, domain, &Some(url.clone().into())));
|
||||
|
||||
let start_result = rpc::start_http(
|
||||
&addr,
|
||||
cors_domains,
|
||||
allowed_hosts,
|
||||
handler,
|
||||
rpc::RpcExtractor,
|
||||
conf.server_threads,
|
||||
conf.max_payload,
|
||||
conf.keep_alive,
|
||||
);
|
||||
|
||||
match start_result {
|
||||
Ok(server) => Ok(Some(server)),
|
||||
Err(ref err) if err.kind() == io::ErrorKind::AddrInUse => Err(
|
||||
format!("{} address {} is already in use, make sure that another instance of an Ethereum client is not running or change the address using the --{}-port and --{}-interface options.", id, url, options, options)
|
||||
),
|
||||
Err(e) => Err(format!("{} error: {:?}", id, e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_ipc<D: rpc_apis::Dependencies>(
|
||||
conf: IpcConfiguration,
|
||||
dependencies: &Dependencies<D>,
|
||||
) -> Result<Option<IpcServer>, String> {
|
||||
if !conf.enabled {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let handler = setup_apis(conf.apis, dependencies);
|
||||
let path = PathBuf::from(&conf.socket_addr);
|
||||
// Make sure socket file can be created on unix-like OS.
|
||||
// Windows pipe paths are not on the FS.
|
||||
if !cfg!(windows) {
|
||||
if let Some(dir) = path.parent() {
|
||||
::std::fs::create_dir_all(&dir).map_err(|err| {
|
||||
format!(
|
||||
"Unable to create IPC directory at {}: {}",
|
||||
dir.display(),
|
||||
err
|
||||
)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
match rpc::start_ipc(&conf.socket_addr, handler, rpc::RpcExtractor) {
|
||||
Ok(server) => Ok(Some(server)),
|
||||
Err(io_error) => Err(format!("IPC error: {}", io_error)),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_domains<T: From<String>>(items: Option<Vec<String>>) -> DomainsValidation<T> {
|
||||
items
|
||||
.map(|vals| vals.into_iter().map(T::from).collect())
|
||||
.into()
|
||||
}
|
||||
|
||||
fn with_domain(
|
||||
items: Option<Vec<String>>,
|
||||
domain: &str,
|
||||
dapps_address: &Option<rpc::Host>,
|
||||
) -> Option<Vec<String>> {
|
||||
fn extract_port(s: &str) -> Option<u16> {
|
||||
s.split(':').nth(1).and_then(|s| s.parse().ok())
|
||||
}
|
||||
|
||||
items.map(move |items| {
|
||||
let mut items = items.into_iter().collect::<HashSet<_>>();
|
||||
{
|
||||
let mut add_hosts = |address: &Option<rpc::Host>| {
|
||||
if let Some(host) = address.clone() {
|
||||
items.insert(host.to_string());
|
||||
items.insert(host.replace("127.0.0.1", "localhost"));
|
||||
items.insert(format!("http://*.{}", domain)); //proxypac
|
||||
if let Some(port) = extract_port(&*host) {
|
||||
items.insert(format!("http://*.{}:{}", domain, port));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
add_hosts(dapps_address);
|
||||
}
|
||||
items.into_iter().collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn setup_apis<D>(
|
||||
apis: ApiSet,
|
||||
deps: &Dependencies<D>,
|
||||
) -> MetaIoHandler<Metadata, Middleware<D::Notifier>>
|
||||
where
|
||||
D: rpc_apis::Dependencies,
|
||||
{
|
||||
let mut handler = MetaIoHandler::with_middleware(Middleware::new(
|
||||
deps.stats.clone(),
|
||||
deps.apis.activity_notifier(),
|
||||
));
|
||||
let apis = apis.list_apis();
|
||||
deps.apis.extend_with_set(&mut handler, &apis);
|
||||
|
||||
handler
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::address;
|
||||
|
||||
#[test]
|
||||
fn should_return_proper_address() {
|
||||
assert_eq!(address(false, "localhost", 8180, &None), None);
|
||||
assert_eq!(
|
||||
address(true, "localhost", 8180, &None),
|
||||
Some("localhost:8180".into())
|
||||
);
|
||||
assert_eq!(
|
||||
address(true, "localhost", 8180, &Some(vec!["host:443".into()])),
|
||||
Some("host:443".into())
|
||||
);
|
||||
assert_eq!(
|
||||
address(true, "localhost", 8180, &Some(vec!["host".into()])),
|
||||
Some("host".into())
|
||||
);
|
||||
}
|
||||
}
|
646
bin/oe/rpc_apis.rs
Normal file
646
bin/oe/rpc_apis.rs
Normal file
@ -0,0 +1,646 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{
|
||||
cmp::PartialEq,
|
||||
collections::{BTreeMap, HashSet},
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub use parity_rpc::signer::SignerService;
|
||||
|
||||
use crate::{
|
||||
account_utils::{self, AccountProvider},
|
||||
miner::external::ExternalMiner,
|
||||
sync::{ManageNetwork, SyncProvider},
|
||||
};
|
||||
use ethcore::{client::Client, miner::Miner, snapshot::SnapshotService};
|
||||
use ethcore_logger::RotatingLogger;
|
||||
use fetch::Client as FetchClient;
|
||||
use jsonrpc_core::{self as core, MetaIoHandler};
|
||||
use parity_rpc::{
|
||||
dispatch::FullDispatcher,
|
||||
informant::{ActivityNotifier, ClientNotifier},
|
||||
Host, Metadata, NetworkSettings,
|
||||
};
|
||||
use parity_runtime::Executor;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
pub enum Api {
|
||||
/// Web3 (Safe)
|
||||
Web3,
|
||||
/// Net (Safe)
|
||||
Net,
|
||||
/// Eth (Safe)
|
||||
Eth,
|
||||
/// Eth Pub-Sub (Safe)
|
||||
EthPubSub,
|
||||
/// Geth-compatible "personal" API (DEPRECATED; only used in `--geth` mode.)
|
||||
Personal,
|
||||
/// Signer - Confirm transactions in Signer (UNSAFE: Passwords, List of transactions)
|
||||
Signer,
|
||||
/// Parity - Custom extensions (Safe)
|
||||
Parity,
|
||||
/// Traces (Safe)
|
||||
Traces,
|
||||
/// Rpc (Safe)
|
||||
Rpc,
|
||||
/// Parity PubSub - Generic Publish-Subscriber (Safety depends on other APIs exposed).
|
||||
ParityPubSub,
|
||||
/// Parity Accounts extensions (UNSAFE: Passwords, Side Effects (new account))
|
||||
ParityAccounts,
|
||||
/// Parity - Set methods (UNSAFE: Side Effects affecting node operation)
|
||||
ParitySet,
|
||||
/// SecretStore (UNSAFE: arbitrary hash signing)
|
||||
SecretStore,
|
||||
/// Geth-compatible (best-effort) debug API (Potentially UNSAFE)
|
||||
/// NOTE We don't aim to support all methods, only the ones that are useful.
|
||||
Debug,
|
||||
}
|
||||
|
||||
impl FromStr for Api {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use self::Api::*;
|
||||
|
||||
match s {
|
||||
"debug" => Ok(Debug),
|
||||
"eth" => Ok(Eth),
|
||||
"net" => Ok(Net),
|
||||
"parity" => Ok(Parity),
|
||||
"parity_accounts" => Ok(ParityAccounts),
|
||||
"parity_pubsub" => Ok(ParityPubSub),
|
||||
"parity_set" => Ok(ParitySet),
|
||||
"personal" => Ok(Personal),
|
||||
"pubsub" => Ok(EthPubSub),
|
||||
"rpc" => Ok(Rpc),
|
||||
"secretstore" => Ok(SecretStore),
|
||||
"signer" => Ok(Signer),
|
||||
"traces" => Ok(Traces),
|
||||
"web3" => Ok(Web3),
|
||||
api => Err(format!("Unknown api: {}", api)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ApiSet {
|
||||
// Unsafe context (like jsonrpc over http)
|
||||
UnsafeContext,
|
||||
// All possible APIs (safe context like token-protected WS interface)
|
||||
All,
|
||||
// Local "unsafe" context and accounts access
|
||||
IpcContext,
|
||||
// APIs for Parity Generic Pub-Sub
|
||||
PubSub,
|
||||
// Fixed list of APis
|
||||
List(HashSet<Api>),
|
||||
}
|
||||
|
||||
impl Default for ApiSet {
|
||||
fn default() -> Self {
|
||||
ApiSet::UnsafeContext
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ApiSet {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.list_apis() == other.list_apis()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ApiSet {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut apis = HashSet::new();
|
||||
|
||||
for api in s.split(',') {
|
||||
match api {
|
||||
"all" => {
|
||||
apis.extend(ApiSet::All.list_apis());
|
||||
}
|
||||
"safe" => {
|
||||
// Safe APIs are those that are safe even in UnsafeContext.
|
||||
apis.extend(ApiSet::UnsafeContext.list_apis());
|
||||
}
|
||||
// Remove the API
|
||||
api if api.starts_with("-") => {
|
||||
let api = api[1..].parse()?;
|
||||
apis.remove(&api);
|
||||
}
|
||||
api => {
|
||||
let api = api.parse()?;
|
||||
apis.insert(api);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ApiSet::List(apis))
|
||||
}
|
||||
}
|
||||
|
||||
fn to_modules(apis: &HashSet<Api>) -> BTreeMap<String, String> {
|
||||
let mut modules = BTreeMap::new();
|
||||
for api in apis {
|
||||
let (name, version) = match *api {
|
||||
Api::Debug => ("debug", "1.0"),
|
||||
Api::Eth => ("eth", "1.0"),
|
||||
Api::EthPubSub => ("pubsub", "1.0"),
|
||||
Api::Net => ("net", "1.0"),
|
||||
Api::Parity => ("parity", "1.0"),
|
||||
Api::ParityAccounts => ("parity_accounts", "1.0"),
|
||||
Api::ParityPubSub => ("parity_pubsub", "1.0"),
|
||||
Api::ParitySet => ("parity_set", "1.0"),
|
||||
Api::Personal => ("personal", "1.0"),
|
||||
Api::Rpc => ("rpc", "1.0"),
|
||||
Api::SecretStore => ("secretstore", "1.0"),
|
||||
Api::Signer => ("signer", "1.0"),
|
||||
Api::Traces => ("traces", "1.0"),
|
||||
Api::Web3 => ("web3", "1.0"),
|
||||
};
|
||||
modules.insert(name.into(), version.into());
|
||||
}
|
||||
modules
|
||||
}
|
||||
|
||||
macro_rules! add_signing_methods {
|
||||
($namespace:ident, $handler:expr, $deps:expr, $dispatch:expr) => {{
|
||||
let deps = &$deps;
|
||||
let (dispatcher, accounts) = $dispatch;
|
||||
if deps.signer_service.is_enabled() {
|
||||
$handler.extend_with($namespace::to_delegate(SigningQueueClient::new(
|
||||
&deps.signer_service,
|
||||
dispatcher.clone(),
|
||||
deps.executor.clone(),
|
||||
accounts,
|
||||
)))
|
||||
} else {
|
||||
$handler.extend_with($namespace::to_delegate(SigningUnsafeClient::new(
|
||||
accounts,
|
||||
dispatcher.clone(),
|
||||
)))
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// RPC dependencies can be used to initialize RPC endpoints from APIs.
|
||||
pub trait Dependencies {
|
||||
type Notifier: ActivityNotifier;
|
||||
|
||||
/// Create the activity notifier.
|
||||
fn activity_notifier(&self) -> Self::Notifier;
|
||||
|
||||
/// Extend the given I/O handler with endpoints for each API.
|
||||
fn extend_with_set<S>(&self, handler: &mut MetaIoHandler<Metadata, S>, apis: &HashSet<Api>)
|
||||
where
|
||||
S: core::Middleware<Metadata>;
|
||||
}
|
||||
|
||||
/// RPC dependencies for a full node.
|
||||
pub struct FullDependencies {
|
||||
pub signer_service: Arc<SignerService>,
|
||||
pub client: Arc<Client>,
|
||||
pub snapshot: Arc<dyn SnapshotService>,
|
||||
pub sync: Arc<dyn SyncProvider>,
|
||||
pub net: Arc<dyn ManageNetwork>,
|
||||
pub accounts: Arc<AccountProvider>,
|
||||
pub miner: Arc<Miner>,
|
||||
pub external_miner: Arc<ExternalMiner>,
|
||||
pub logger: Arc<RotatingLogger>,
|
||||
pub settings: Arc<NetworkSettings>,
|
||||
pub net_service: Arc<dyn ManageNetwork>,
|
||||
pub experimental_rpcs: bool,
|
||||
pub ws_address: Option<Host>,
|
||||
pub fetch: FetchClient,
|
||||
pub executor: Executor,
|
||||
pub gas_price_percentile: usize,
|
||||
pub poll_lifetime: u32,
|
||||
pub allow_missing_blocks: bool,
|
||||
pub no_ancient_blocks: bool,
|
||||
}
|
||||
|
||||
impl FullDependencies {
|
||||
fn extend_api<S>(
|
||||
&self,
|
||||
handler: &mut MetaIoHandler<Metadata, S>,
|
||||
apis: &HashSet<Api>,
|
||||
for_generic_pubsub: bool,
|
||||
) where
|
||||
S: core::Middleware<Metadata>,
|
||||
{
|
||||
use parity_rpc::v1::*;
|
||||
|
||||
let nonces = Arc::new(Mutex::new(dispatch::Reservations::new(
|
||||
self.executor.clone(),
|
||||
)));
|
||||
let dispatcher = FullDispatcher::new(
|
||||
self.client.clone(),
|
||||
self.miner.clone(),
|
||||
nonces.clone(),
|
||||
self.gas_price_percentile,
|
||||
);
|
||||
let account_signer = Arc::new(dispatch::Signer::new(self.accounts.clone())) as _;
|
||||
let accounts = account_utils::accounts_list(self.accounts.clone());
|
||||
|
||||
for api in apis {
|
||||
match *api {
|
||||
Api::Debug => {
|
||||
handler.extend_with(DebugClient::new(self.client.clone()).to_delegate());
|
||||
}
|
||||
Api::Web3 => {
|
||||
handler.extend_with(Web3Client::default().to_delegate());
|
||||
}
|
||||
Api::Net => {
|
||||
handler.extend_with(NetClient::new(&self.sync).to_delegate());
|
||||
}
|
||||
Api::Eth => {
|
||||
let client = EthClient::new(
|
||||
&self.client,
|
||||
&self.snapshot,
|
||||
&self.sync,
|
||||
&accounts,
|
||||
&self.miner,
|
||||
&self.external_miner,
|
||||
EthClientOptions {
|
||||
gas_price_percentile: self.gas_price_percentile,
|
||||
allow_missing_blocks: self.allow_missing_blocks,
|
||||
allow_experimental_rpcs: self.experimental_rpcs,
|
||||
no_ancient_blocks: self.no_ancient_blocks,
|
||||
},
|
||||
);
|
||||
handler.extend_with(client.to_delegate());
|
||||
|
||||
if !for_generic_pubsub {
|
||||
let filter_client = EthFilterClient::new(
|
||||
self.client.clone(),
|
||||
self.miner.clone(),
|
||||
self.poll_lifetime,
|
||||
);
|
||||
handler.extend_with(filter_client.to_delegate());
|
||||
|
||||
add_signing_methods!(
|
||||
EthSigning,
|
||||
handler,
|
||||
self,
|
||||
(&dispatcher, &account_signer)
|
||||
);
|
||||
}
|
||||
}
|
||||
Api::EthPubSub => {
|
||||
if !for_generic_pubsub {
|
||||
let client =
|
||||
EthPubSubClient::new(self.client.clone(), self.executor.clone());
|
||||
let h = client.handler();
|
||||
self.miner
|
||||
.add_transactions_listener(Box::new(move |hashes| {
|
||||
if let Some(h) = h.upgrade() {
|
||||
h.notify_new_transactions(hashes);
|
||||
}
|
||||
}));
|
||||
|
||||
if let Some(h) = client.handler().upgrade() {
|
||||
self.client.add_notify(h);
|
||||
}
|
||||
handler.extend_with(client.to_delegate());
|
||||
}
|
||||
}
|
||||
Api::Personal => {
|
||||
#[cfg(feature = "accounts")]
|
||||
handler.extend_with(
|
||||
PersonalClient::new(
|
||||
&self.accounts,
|
||||
dispatcher.clone(),
|
||||
self.experimental_rpcs,
|
||||
)
|
||||
.to_delegate(),
|
||||
);
|
||||
}
|
||||
Api::Signer => {
|
||||
handler.extend_with(
|
||||
SignerClient::new(
|
||||
account_signer.clone(),
|
||||
dispatcher.clone(),
|
||||
&self.signer_service,
|
||||
self.executor.clone(),
|
||||
)
|
||||
.to_delegate(),
|
||||
);
|
||||
}
|
||||
Api::Parity => {
|
||||
let signer = match self.signer_service.is_enabled() {
|
||||
true => Some(self.signer_service.clone()),
|
||||
false => None,
|
||||
};
|
||||
handler.extend_with(
|
||||
ParityClient::new(
|
||||
self.client.clone(),
|
||||
self.miner.clone(),
|
||||
self.sync.clone(),
|
||||
self.net_service.clone(),
|
||||
self.logger.clone(),
|
||||
self.settings.clone(),
|
||||
signer,
|
||||
self.ws_address.clone(),
|
||||
self.snapshot.clone().into(),
|
||||
)
|
||||
.to_delegate(),
|
||||
);
|
||||
#[cfg(feature = "accounts")]
|
||||
handler.extend_with(ParityAccountsInfo::to_delegate(
|
||||
ParityAccountsClient::new(&self.accounts),
|
||||
));
|
||||
|
||||
if !for_generic_pubsub {
|
||||
add_signing_methods!(
|
||||
ParitySigning,
|
||||
handler,
|
||||
self,
|
||||
(&dispatcher, &account_signer)
|
||||
);
|
||||
}
|
||||
}
|
||||
Api::ParityPubSub => {
|
||||
if !for_generic_pubsub {
|
||||
let mut rpc = MetaIoHandler::default();
|
||||
let apis = ApiSet::List(apis.clone())
|
||||
.retain(ApiSet::PubSub)
|
||||
.list_apis();
|
||||
self.extend_api(&mut rpc, &apis, true);
|
||||
handler.extend_with(
|
||||
PubSubClient::new(rpc, self.executor.clone()).to_delegate(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Api::ParityAccounts => {
|
||||
#[cfg(feature = "accounts")]
|
||||
handler.extend_with(ParityAccounts::to_delegate(ParityAccountsClient::new(
|
||||
&self.accounts,
|
||||
)));
|
||||
}
|
||||
Api::ParitySet => {
|
||||
handler.extend_with(
|
||||
ParitySetClient::new(
|
||||
&self.client,
|
||||
&self.miner,
|
||||
&self.net_service,
|
||||
self.fetch.clone(),
|
||||
)
|
||||
.to_delegate(),
|
||||
);
|
||||
#[cfg(feature = "accounts")]
|
||||
handler.extend_with(
|
||||
ParitySetAccountsClient::new(&self.accounts, &self.miner).to_delegate(),
|
||||
);
|
||||
}
|
||||
Api::Traces => handler.extend_with(TracesClient::new(&self.client).to_delegate()),
|
||||
Api::Rpc => {
|
||||
let modules = to_modules(&apis);
|
||||
handler.extend_with(RpcClient::new(modules).to_delegate());
|
||||
}
|
||||
Api::SecretStore => {
|
||||
#[cfg(feature = "accounts")]
|
||||
handler.extend_with(SecretStoreClient::new(&self.accounts).to_delegate());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dependencies for FullDependencies {
|
||||
type Notifier = ClientNotifier;
|
||||
|
||||
fn activity_notifier(&self) -> ClientNotifier {
|
||||
ClientNotifier {
|
||||
client: self.client.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn extend_with_set<S>(&self, handler: &mut MetaIoHandler<Metadata, S>, apis: &HashSet<Api>)
|
||||
where
|
||||
S: core::Middleware<Metadata>,
|
||||
{
|
||||
self.extend_api(handler, apis, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiSet {
|
||||
/// Retains only APIs in given set.
|
||||
pub fn retain(self, set: Self) -> Self {
|
||||
ApiSet::List(&self.list_apis() & &set.list_apis())
|
||||
}
|
||||
|
||||
pub fn list_apis(&self) -> HashSet<Api> {
|
||||
let mut public_list: HashSet<Api> = [
|
||||
Api::Web3,
|
||||
Api::Net,
|
||||
Api::Eth,
|
||||
Api::EthPubSub,
|
||||
Api::Parity,
|
||||
Api::Rpc,
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
match *self {
|
||||
ApiSet::List(ref apis) => apis.clone(),
|
||||
ApiSet::UnsafeContext => {
|
||||
public_list.insert(Api::Traces);
|
||||
public_list.insert(Api::ParityPubSub);
|
||||
public_list
|
||||
}
|
||||
ApiSet::IpcContext => {
|
||||
public_list.insert(Api::Traces);
|
||||
public_list.insert(Api::ParityPubSub);
|
||||
public_list.insert(Api::ParityAccounts);
|
||||
public_list
|
||||
}
|
||||
ApiSet::All => {
|
||||
public_list.insert(Api::Debug);
|
||||
public_list.insert(Api::Traces);
|
||||
public_list.insert(Api::ParityPubSub);
|
||||
public_list.insert(Api::ParityAccounts);
|
||||
public_list.insert(Api::ParitySet);
|
||||
public_list.insert(Api::Signer);
|
||||
public_list.insert(Api::Personal);
|
||||
public_list.insert(Api::SecretStore);
|
||||
public_list
|
||||
}
|
||||
ApiSet::PubSub => [
|
||||
Api::Eth,
|
||||
Api::Parity,
|
||||
Api::ParityAccounts,
|
||||
Api::ParitySet,
|
||||
Api::Traces,
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Api, ApiSet};
|
||||
|
||||
#[test]
|
||||
fn test_api_parsing() {
|
||||
assert_eq!(Api::Debug, "debug".parse().unwrap());
|
||||
assert_eq!(Api::Web3, "web3".parse().unwrap());
|
||||
assert_eq!(Api::Net, "net".parse().unwrap());
|
||||
assert_eq!(Api::Eth, "eth".parse().unwrap());
|
||||
assert_eq!(Api::EthPubSub, "pubsub".parse().unwrap());
|
||||
assert_eq!(Api::Personal, "personal".parse().unwrap());
|
||||
assert_eq!(Api::Signer, "signer".parse().unwrap());
|
||||
assert_eq!(Api::Parity, "parity".parse().unwrap());
|
||||
assert_eq!(Api::ParityAccounts, "parity_accounts".parse().unwrap());
|
||||
assert_eq!(Api::ParitySet, "parity_set".parse().unwrap());
|
||||
assert_eq!(Api::Traces, "traces".parse().unwrap());
|
||||
assert_eq!(Api::Rpc, "rpc".parse().unwrap());
|
||||
assert_eq!(Api::SecretStore, "secretstore".parse().unwrap());
|
||||
assert!("rp".parse::<Api>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_set_default() {
|
||||
assert_eq!(ApiSet::UnsafeContext, ApiSet::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_set_parsing() {
|
||||
assert_eq!(
|
||||
ApiSet::List(vec![Api::Web3, Api::Eth].into_iter().collect()),
|
||||
"web3,eth".parse().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_set_unsafe_context() {
|
||||
let expected = vec![
|
||||
// make sure this list contains only SAFE methods
|
||||
Api::Web3,
|
||||
Api::Net,
|
||||
Api::Eth,
|
||||
Api::EthPubSub,
|
||||
Api::Parity,
|
||||
Api::ParityPubSub,
|
||||
Api::Traces,
|
||||
Api::Rpc,
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
assert_eq!(ApiSet::UnsafeContext.list_apis(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_set_ipc_context() {
|
||||
let expected = vec![
|
||||
// safe
|
||||
Api::Web3,
|
||||
Api::Net,
|
||||
Api::Eth,
|
||||
Api::EthPubSub,
|
||||
Api::Parity,
|
||||
Api::ParityPubSub,
|
||||
Api::Traces,
|
||||
Api::Rpc,
|
||||
// semi-safe
|
||||
Api::ParityAccounts,
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
assert_eq!(ApiSet::IpcContext.list_apis(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_apis() {
|
||||
assert_eq!(
|
||||
"all".parse::<ApiSet>().unwrap(),
|
||||
ApiSet::List(
|
||||
vec![
|
||||
Api::Web3,
|
||||
Api::Net,
|
||||
Api::Eth,
|
||||
Api::EthPubSub,
|
||||
Api::Parity,
|
||||
Api::ParityPubSub,
|
||||
Api::Traces,
|
||||
Api::Rpc,
|
||||
Api::SecretStore,
|
||||
Api::ParityAccounts,
|
||||
Api::ParitySet,
|
||||
Api::Signer,
|
||||
Api::Personal,
|
||||
Api::Debug,
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_without_personal_apis() {
|
||||
assert_eq!(
|
||||
"personal,all,-personal".parse::<ApiSet>().unwrap(),
|
||||
ApiSet::List(
|
||||
vec![
|
||||
Api::Web3,
|
||||
Api::Net,
|
||||
Api::Eth,
|
||||
Api::EthPubSub,
|
||||
Api::Parity,
|
||||
Api::ParityPubSub,
|
||||
Api::Traces,
|
||||
Api::Rpc,
|
||||
Api::SecretStore,
|
||||
Api::ParityAccounts,
|
||||
Api::ParitySet,
|
||||
Api::Signer,
|
||||
Api::Debug,
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_safe_parsing() {
|
||||
assert_eq!(
|
||||
"safe".parse::<ApiSet>().unwrap(),
|
||||
ApiSet::List(
|
||||
vec![
|
||||
Api::Web3,
|
||||
Api::Net,
|
||||
Api::Eth,
|
||||
Api::EthPubSub,
|
||||
Api::Parity,
|
||||
Api::ParityPubSub,
|
||||
Api::Traces,
|
||||
Api::Rpc,
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
751
bin/oe/run.rs
Normal file
751
bin/oe/run.rs
Normal file
@ -0,0 +1,751 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{
|
||||
any::Any,
|
||||
str::FromStr,
|
||||
sync::{atomic, Arc, Weak},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
account_utils,
|
||||
cache::CacheConfig,
|
||||
db,
|
||||
helpers::{execute_upgrades, passwords_from_files, to_client_config},
|
||||
informant::{FullNodeInformantData, Informant},
|
||||
metrics::{start_prometheus_metrics, MetricsConfiguration},
|
||||
miner::{external::ExternalMiner, work_notify::WorkPoster},
|
||||
modules,
|
||||
params::{
|
||||
fatdb_switch_to_bool, mode_switch_to_bool, tracing_switch_to_bool, AccountsConfig,
|
||||
GasPricerConfig, MinerExtras, Pruning, SpecType, Switch,
|
||||
},
|
||||
rpc, rpc_apis, secretstore, signer,
|
||||
sync::{self, SyncConfig},
|
||||
user_defaults::UserDefaults,
|
||||
};
|
||||
use ansi_term::Colour;
|
||||
use dir::{DatabaseDirectories, Directories};
|
||||
use ethcore::{
|
||||
client::{BlockChainClient, BlockInfo, Client, DatabaseCompactionProfile, Mode, VMType},
|
||||
miner::{self, stratum, Miner, MinerOptions, MinerService},
|
||||
snapshot::{self, SnapshotConfiguration},
|
||||
verification::queue::VerifierSettings,
|
||||
};
|
||||
use ethcore_logger::{Config as LogConfig, RotatingLogger};
|
||||
use ethcore_service::ClientService;
|
||||
use ethereum_types::{H256, U64};
|
||||
use journaldb::Algorithm;
|
||||
use jsonrpc_core;
|
||||
use node_filter::NodeFilter;
|
||||
use parity_rpc::{
|
||||
informant, is_major_importing, FutureOutput, FutureResponse, FutureResult, Metadata,
|
||||
NetworkSettings, Origin, PubSubSession,
|
||||
};
|
||||
use parity_runtime::Runtime;
|
||||
use parity_version::version;
|
||||
|
||||
// How often we attempt to take a snapshot: only snapshot on blocknumbers that are multiples of this.
|
||||
const SNAPSHOT_PERIOD: u64 = 20000;
|
||||
|
||||
// Start snapshoting from `tip`-`history, with this we want to bypass reorgs. Should be smaller than prunning history.
|
||||
const SNAPSHOT_HISTORY: u64 = 50;
|
||||
|
||||
// Full client number of DNS threads
|
||||
const FETCH_FULL_NUM_DNS_THREADS: usize = 4;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct RunCmd {
|
||||
pub cache_config: CacheConfig,
|
||||
pub dirs: Directories,
|
||||
pub spec: SpecType,
|
||||
pub pruning: Pruning,
|
||||
pub pruning_history: u64,
|
||||
pub pruning_memory: usize,
|
||||
/// Some if execution should be daemonized. Contains pid_file path.
|
||||
pub daemon: Option<String>,
|
||||
pub logger_config: LogConfig,
|
||||
pub miner_options: MinerOptions,
|
||||
pub gas_price_percentile: usize,
|
||||
pub poll_lifetime: u32,
|
||||
pub ws_conf: rpc::WsConfiguration,
|
||||
pub http_conf: rpc::HttpConfiguration,
|
||||
pub ipc_conf: rpc::IpcConfiguration,
|
||||
pub net_conf: sync::NetworkConfiguration,
|
||||
pub network_id: Option<u64>,
|
||||
pub warp_sync: bool,
|
||||
pub warp_barrier: Option<u64>,
|
||||
pub acc_conf: AccountsConfig,
|
||||
pub gas_pricer_conf: GasPricerConfig,
|
||||
pub miner_extras: MinerExtras,
|
||||
pub mode: Option<Mode>,
|
||||
pub tracing: Switch,
|
||||
pub fat_db: Switch,
|
||||
pub compaction: DatabaseCompactionProfile,
|
||||
pub vm_type: VMType,
|
||||
pub experimental_rpcs: bool,
|
||||
pub net_settings: NetworkSettings,
|
||||
pub secretstore_conf: secretstore::Configuration,
|
||||
pub name: String,
|
||||
pub custom_bootnodes: bool,
|
||||
pub stratum: Option<stratum::Options>,
|
||||
pub snapshot_conf: SnapshotConfiguration,
|
||||
pub check_seal: bool,
|
||||
pub allow_missing_blocks: bool,
|
||||
pub download_old_blocks: bool,
|
||||
pub verifier_settings: VerifierSettings,
|
||||
pub no_persistent_txqueue: bool,
|
||||
pub max_round_blocks_to_import: usize,
|
||||
pub metrics_conf: MetricsConfiguration,
|
||||
}
|
||||
|
||||
// node info fetcher for the local store.
|
||||
struct FullNodeInfo {
|
||||
miner: Option<Arc<Miner>>, // TODO: only TXQ needed, just use that after decoupling.
|
||||
}
|
||||
|
||||
impl crate::local_store::NodeInfo for FullNodeInfo {
|
||||
fn pending_transactions(&self) -> Vec<crate::types::transaction::PendingTransaction> {
|
||||
let miner = match self.miner.as_ref() {
|
||||
Some(m) => m,
|
||||
None => return Vec::new(),
|
||||
};
|
||||
|
||||
miner
|
||||
.local_transactions()
|
||||
.values()
|
||||
.filter_map(|status| match *status {
|
||||
crate::miner::pool::local_transactions::Status::Pending(ref tx) => {
|
||||
Some(tx.pending().clone())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the given run command.
|
||||
///
|
||||
/// On error, returns what to print on stderr.
|
||||
pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<RunningClient, String> {
|
||||
// load spec
|
||||
let spec = cmd.spec.spec(&cmd.dirs.cache)?;
|
||||
|
||||
// load genesis hash
|
||||
let genesis_hash = spec.genesis_header().hash();
|
||||
|
||||
// database paths
|
||||
let db_dirs = cmd.dirs.database(
|
||||
genesis_hash,
|
||||
cmd.spec.legacy_fork_name(),
|
||||
spec.data_dir.clone(),
|
||||
);
|
||||
|
||||
// user defaults path
|
||||
let user_defaults_path = db_dirs.user_defaults_path();
|
||||
|
||||
// load user defaults
|
||||
let mut user_defaults = UserDefaults::load(&user_defaults_path)?;
|
||||
|
||||
// select pruning algorithm
|
||||
let algorithm = cmd.pruning.to_algorithm(&user_defaults);
|
||||
|
||||
// check if tracing is on
|
||||
let tracing = tracing_switch_to_bool(cmd.tracing, &user_defaults)?;
|
||||
|
||||
// check if fatdb is on
|
||||
let fat_db = fatdb_switch_to_bool(cmd.fat_db, &user_defaults, algorithm)?;
|
||||
|
||||
// get the mode
|
||||
let mode = mode_switch_to_bool(cmd.mode, &user_defaults)?;
|
||||
trace!(target: "mode", "mode is {:?}", mode);
|
||||
let network_enabled = match mode {
|
||||
Mode::Dark(_) | Mode::Off => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// prepare client and snapshot paths.
|
||||
let client_path = db_dirs.client_path(algorithm);
|
||||
let snapshot_path = db_dirs.snapshot_path();
|
||||
|
||||
// execute upgrades
|
||||
execute_upgrades(&cmd.dirs.base, &db_dirs, algorithm, &cmd.compaction)?;
|
||||
|
||||
// create dirs used by parity
|
||||
cmd.dirs.create_dirs(
|
||||
cmd.acc_conf.unlocked_accounts.len() == 0,
|
||||
cmd.secretstore_conf.enabled,
|
||||
)?;
|
||||
|
||||
//print out running parity environment
|
||||
print_running_environment(&spec.data_dir, &cmd.dirs, &db_dirs);
|
||||
|
||||
// display info about used pruning algorithm
|
||||
info!(
|
||||
"State DB configuration: {}{}{}",
|
||||
Colour::White.bold().paint(algorithm.as_str()),
|
||||
match fat_db {
|
||||
true => Colour::White.bold().paint(" +Fat").to_string(),
|
||||
false => "".to_owned(),
|
||||
},
|
||||
match tracing {
|
||||
true => Colour::White.bold().paint(" +Trace").to_string(),
|
||||
false => "".to_owned(),
|
||||
}
|
||||
);
|
||||
info!(
|
||||
"Operating mode: {}",
|
||||
Colour::White.bold().paint(format!("{}", mode))
|
||||
);
|
||||
|
||||
// display warning about using experimental journaldb algorithm
|
||||
if !algorithm.is_stable() {
|
||||
warn!(
|
||||
"Your chosen strategy is {}! You can re-run with --pruning to change.",
|
||||
Colour::Red.bold().paint("unstable")
|
||||
);
|
||||
}
|
||||
|
||||
// create sync config
|
||||
let mut sync_config = SyncConfig::default();
|
||||
sync_config.network_id = match cmd.network_id {
|
||||
Some(id) => id,
|
||||
None => spec.network_id(),
|
||||
};
|
||||
if spec.subprotocol_name().len() > 8 {
|
||||
warn!("Your chain specification's subprotocol length is more then 8. Ignoring.");
|
||||
} else {
|
||||
sync_config.subprotocol_name = U64::from(spec.subprotocol_name().as_bytes())
|
||||
}
|
||||
|
||||
sync_config.fork_block = spec.fork_block();
|
||||
let mut warp_sync = spec.engine.supports_warp() && cmd.warp_sync;
|
||||
if warp_sync {
|
||||
// Logging is not initialized yet, so we print directly to stderr
|
||||
if fat_db {
|
||||
warn!("Warning: Warp Sync is disabled because Fat DB is turned on.");
|
||||
warp_sync = false;
|
||||
} else if tracing {
|
||||
warn!("Warning: Warp Sync is disabled because tracing is turned on.");
|
||||
warp_sync = false;
|
||||
} else if algorithm != Algorithm::OverlayRecent {
|
||||
warn!("Warning: Warp Sync is disabled because of non-default pruning mode.");
|
||||
warp_sync = false;
|
||||
}
|
||||
}
|
||||
sync_config.warp_sync = match (warp_sync, cmd.warp_barrier) {
|
||||
(true, Some(block)) => sync::WarpSync::OnlyAndAfter(block),
|
||||
(true, _) => sync::WarpSync::Enabled,
|
||||
_ => sync::WarpSync::Disabled,
|
||||
};
|
||||
sync_config.download_old_blocks = cmd.download_old_blocks;
|
||||
sync_config.eip1559_transition = spec.params().eip1559_transition;
|
||||
|
||||
let passwords = passwords_from_files(&cmd.acc_conf.password_files)?;
|
||||
|
||||
// prepare account provider
|
||||
let account_provider = Arc::new(account_utils::prepare_account_provider(
|
||||
&cmd.spec,
|
||||
&cmd.dirs,
|
||||
&spec.data_dir,
|
||||
cmd.acc_conf,
|
||||
&passwords,
|
||||
)?);
|
||||
|
||||
// spin up event loop
|
||||
let runtime = Runtime::with_default_thread_count();
|
||||
|
||||
// fetch service
|
||||
let fetch = fetch::Client::new(FETCH_FULL_NUM_DNS_THREADS)
|
||||
.map_err(|e| format!("Error starting fetch client: {:?}", e))?;
|
||||
|
||||
let txpool_size = cmd.miner_options.pool_limits.max_count;
|
||||
// create miner
|
||||
let miner = Arc::new(Miner::new(
|
||||
cmd.miner_options,
|
||||
cmd.gas_pricer_conf
|
||||
.to_gas_pricer(fetch.clone(), runtime.executor()),
|
||||
&spec,
|
||||
(
|
||||
cmd.miner_extras.local_accounts,
|
||||
account_utils::miner_local_accounts(account_provider.clone()),
|
||||
),
|
||||
));
|
||||
miner.set_author(miner::Author::External(cmd.miner_extras.author));
|
||||
miner.set_gas_range_target(cmd.miner_extras.gas_range_target);
|
||||
miner.set_extra_data(cmd.miner_extras.extra_data);
|
||||
|
||||
if !cmd.miner_extras.work_notify.is_empty() {
|
||||
miner.add_work_listener(Box::new(WorkPoster::new(
|
||||
&cmd.miner_extras.work_notify,
|
||||
fetch.clone(),
|
||||
runtime.executor(),
|
||||
)));
|
||||
}
|
||||
|
||||
let engine_signer = cmd.miner_extras.engine_signer;
|
||||
if engine_signer != Default::default() {
|
||||
if let Some(author) = account_utils::miner_author(
|
||||
&cmd.spec,
|
||||
&cmd.dirs,
|
||||
&account_provider,
|
||||
engine_signer,
|
||||
&passwords,
|
||||
)? {
|
||||
miner.set_author(author);
|
||||
}
|
||||
}
|
||||
|
||||
// create client config
|
||||
let mut client_config = to_client_config(
|
||||
&cmd.cache_config,
|
||||
spec.name.to_lowercase(),
|
||||
mode.clone(),
|
||||
tracing,
|
||||
fat_db,
|
||||
cmd.compaction,
|
||||
cmd.vm_type,
|
||||
cmd.name,
|
||||
algorithm,
|
||||
cmd.pruning_history,
|
||||
cmd.pruning_memory,
|
||||
cmd.check_seal,
|
||||
cmd.max_round_blocks_to_import,
|
||||
);
|
||||
|
||||
client_config.queue.verifier_settings = cmd.verifier_settings;
|
||||
client_config.queue.verifier_settings.bad_hashes = verification_bad_blocks(&cmd.spec);
|
||||
client_config.transaction_verification_queue_size = ::std::cmp::max(2048, txpool_size / 4);
|
||||
client_config.snapshot = cmd.snapshot_conf.clone();
|
||||
|
||||
// set up bootnodes
|
||||
let mut net_conf = cmd.net_conf;
|
||||
if !cmd.custom_bootnodes {
|
||||
net_conf.boot_nodes = spec.nodes.clone();
|
||||
}
|
||||
|
||||
// set network path.
|
||||
net_conf.net_config_path = Some(db_dirs.network_path().to_string_lossy().into_owned());
|
||||
|
||||
let restoration_db_handler = db::restoration_db_handler(&client_path, &client_config);
|
||||
let client_db = restoration_db_handler
|
||||
.open(&client_path)
|
||||
.map_err(|e| format!("Failed to open database {:?}", e))?;
|
||||
|
||||
// create client service.
|
||||
let service = ClientService::start(
|
||||
client_config,
|
||||
&spec,
|
||||
client_db,
|
||||
&snapshot_path,
|
||||
restoration_db_handler,
|
||||
&cmd.dirs.ipc_path(),
|
||||
miner.clone(),
|
||||
)
|
||||
.map_err(|e| format!("Client service error: {:?}", e))?;
|
||||
|
||||
let connection_filter_address = spec.params().node_permission_contract;
|
||||
// drop the spec to free up genesis state.
|
||||
let forks = spec.hard_forks.clone();
|
||||
drop(spec);
|
||||
|
||||
// take handle to client
|
||||
let client = service.client();
|
||||
// Update miners block gas limit and base_fee
|
||||
let base_fee = client
|
||||
.engine()
|
||||
.calculate_base_fee(&client.best_block_header());
|
||||
let allow_non_eoa_sender = client
|
||||
.engine()
|
||||
.allow_non_eoa_sender(client.best_block_header().number() + 1);
|
||||
miner.update_transaction_queue_limits(
|
||||
*client.best_block_header().gas_limit(),
|
||||
base_fee,
|
||||
allow_non_eoa_sender,
|
||||
);
|
||||
|
||||
let connection_filter = connection_filter_address.map(|a| {
|
||||
Arc::new(NodeFilter::new(
|
||||
Arc::downgrade(&client) as Weak<dyn BlockChainClient>,
|
||||
a,
|
||||
))
|
||||
});
|
||||
let snapshot_service = service.snapshot_service();
|
||||
|
||||
// initialize the local node information store.
|
||||
let store = {
|
||||
let db = service.db();
|
||||
let node_info = FullNodeInfo {
|
||||
miner: match cmd.no_persistent_txqueue {
|
||||
true => None,
|
||||
false => Some(miner.clone()),
|
||||
},
|
||||
};
|
||||
|
||||
let store = crate::local_store::create(
|
||||
db.key_value().clone(),
|
||||
::ethcore_db::COL_NODE_INFO,
|
||||
node_info,
|
||||
);
|
||||
|
||||
if cmd.no_persistent_txqueue {
|
||||
info!("Running without a persistent transaction queue.");
|
||||
|
||||
if let Err(e) = store.clear() {
|
||||
warn!("Error clearing persistent transaction queue: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// re-queue pending transactions.
|
||||
match store.pending_transactions() {
|
||||
Ok(pending) => {
|
||||
for pending_tx in pending {
|
||||
if let Err(e) = miner.import_own_transaction(&*client, pending_tx) {
|
||||
warn!("Error importing saved transaction: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => warn!("Error loading cached pending transactions from disk: {}", e),
|
||||
}
|
||||
|
||||
Arc::new(store)
|
||||
};
|
||||
|
||||
// register it as an IO service to update periodically.
|
||||
service
|
||||
.register_io_handler(store)
|
||||
.map_err(|_| "Unable to register local store handler".to_owned())?;
|
||||
|
||||
// create external miner
|
||||
let external_miner = Arc::new(ExternalMiner::default());
|
||||
|
||||
// start stratum
|
||||
if let Some(ref stratum_config) = cmd.stratum {
|
||||
stratum::Stratum::register(stratum_config, miner.clone(), Arc::downgrade(&client))
|
||||
.map_err(|e| format!("Stratum start error: {:?}", e))?;
|
||||
}
|
||||
|
||||
// create sync object
|
||||
let (sync_provider, manage_network, chain_notify, priority_tasks) = modules::sync(
|
||||
sync_config,
|
||||
net_conf.clone().into(),
|
||||
client.clone(),
|
||||
forks,
|
||||
snapshot_service.clone(),
|
||||
&cmd.logger_config,
|
||||
connection_filter
|
||||
.clone()
|
||||
.map(|f| f as Arc<dyn crate::sync::ConnectionFilter + 'static>),
|
||||
)
|
||||
.map_err(|e| format!("Sync error: {}", e))?;
|
||||
|
||||
service.add_notify(chain_notify.clone());
|
||||
|
||||
// Propagate transactions as soon as they are imported.
|
||||
let tx = ::parking_lot::Mutex::new(priority_tasks);
|
||||
let is_ready = Arc::new(atomic::AtomicBool::new(true));
|
||||
miner.add_transactions_listener(Box::new(move |_hashes| {
|
||||
// we want to have only one PendingTransactions task in the queue.
|
||||
if is_ready
|
||||
.compare_exchange(
|
||||
true,
|
||||
false,
|
||||
atomic::Ordering::SeqCst,
|
||||
atomic::Ordering::SeqCst,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
let task =
|
||||
crate::sync::PriorityTask::PropagateTransactions(Instant::now(), is_ready.clone());
|
||||
// we ignore error cause it means that we are closing
|
||||
let _ = tx.lock().send(task);
|
||||
}
|
||||
}));
|
||||
|
||||
// start network
|
||||
if network_enabled {
|
||||
chain_notify.start();
|
||||
}
|
||||
|
||||
// set up dependencies for rpc servers
|
||||
let rpc_stats = Arc::new(informant::RpcStats::default());
|
||||
let secret_store = account_provider.clone();
|
||||
let signer_service = Arc::new(signer::new_service(&cmd.ws_conf, &cmd.logger_config));
|
||||
|
||||
let deps_for_rpc_apis = Arc::new(rpc_apis::FullDependencies {
|
||||
signer_service: signer_service,
|
||||
snapshot: snapshot_service.clone(),
|
||||
client: client.clone(),
|
||||
sync: sync_provider.clone(),
|
||||
net: manage_network.clone(),
|
||||
accounts: secret_store,
|
||||
miner: miner.clone(),
|
||||
external_miner: external_miner.clone(),
|
||||
logger: logger.clone(),
|
||||
settings: Arc::new(cmd.net_settings.clone()),
|
||||
net_service: manage_network.clone(),
|
||||
experimental_rpcs: cmd.experimental_rpcs,
|
||||
ws_address: cmd.ws_conf.address(),
|
||||
fetch: fetch.clone(),
|
||||
executor: runtime.executor(),
|
||||
gas_price_percentile: cmd.gas_price_percentile,
|
||||
poll_lifetime: cmd.poll_lifetime,
|
||||
allow_missing_blocks: cmd.allow_missing_blocks,
|
||||
no_ancient_blocks: !cmd.download_old_blocks,
|
||||
});
|
||||
|
||||
let dependencies = rpc::Dependencies {
|
||||
apis: deps_for_rpc_apis.clone(),
|
||||
executor: runtime.executor(),
|
||||
stats: rpc_stats.clone(),
|
||||
};
|
||||
|
||||
// start rpc servers
|
||||
let rpc_direct = rpc::setup_apis(rpc_apis::ApiSet::All, &dependencies);
|
||||
let ws_server = rpc::new_ws(cmd.ws_conf.clone(), &dependencies)?;
|
||||
let ipc_server = rpc::new_ipc(cmd.ipc_conf, &dependencies)?;
|
||||
|
||||
// start the prometheus metrics server
|
||||
start_prometheus_metrics(&cmd.metrics_conf, &dependencies)?;
|
||||
|
||||
let http_server = rpc::new_http(
|
||||
"HTTP JSON-RPC",
|
||||
"jsonrpc",
|
||||
cmd.http_conf.clone(),
|
||||
&dependencies,
|
||||
)?;
|
||||
|
||||
// secret store key server
|
||||
let secretstore_deps = secretstore::Dependencies {
|
||||
client: client.clone(),
|
||||
sync: sync_provider.clone(),
|
||||
miner: miner.clone(),
|
||||
account_provider,
|
||||
accounts_passwords: &passwords,
|
||||
};
|
||||
let secretstore_key_server = secretstore::start(
|
||||
cmd.secretstore_conf.clone(),
|
||||
secretstore_deps,
|
||||
runtime.executor(),
|
||||
)?;
|
||||
|
||||
// the informant
|
||||
let informant = Arc::new(Informant::new(
|
||||
FullNodeInformantData {
|
||||
client: service.client(),
|
||||
sync: Some(sync_provider.clone()),
|
||||
net: Some(manage_network.clone()),
|
||||
},
|
||||
Some(snapshot_service.clone()),
|
||||
Some(rpc_stats.clone()),
|
||||
cmd.logger_config.color,
|
||||
));
|
||||
service.add_notify(informant.clone());
|
||||
service
|
||||
.register_io_handler(informant.clone())
|
||||
.map_err(|_| "Unable to register informant handler".to_owned())?;
|
||||
|
||||
// save user defaults
|
||||
user_defaults.is_first_launch = false;
|
||||
user_defaults.pruning = algorithm;
|
||||
user_defaults.tracing = tracing;
|
||||
user_defaults.fat_db = fat_db;
|
||||
user_defaults.set_mode(mode);
|
||||
user_defaults.save(&user_defaults_path)?;
|
||||
|
||||
// tell client how to save the default mode if it gets changed.
|
||||
client.on_user_defaults_change(move |mode: Option<Mode>| {
|
||||
if let Some(mode) = mode {
|
||||
user_defaults.set_mode(mode);
|
||||
}
|
||||
let _ = user_defaults.save(&user_defaults_path); // discard failures - there's nothing we can do
|
||||
});
|
||||
|
||||
// the watcher must be kept alive.
|
||||
let watcher = match cmd.snapshot_conf.enable {
|
||||
false => None,
|
||||
true => {
|
||||
let sync = sync_provider.clone();
|
||||
let client = client.clone();
|
||||
let watcher = Arc::new(snapshot::Watcher::new(
|
||||
service.client(),
|
||||
move || is_major_importing(Some(sync.status().state), client.queue_info()),
|
||||
service.io().channel(),
|
||||
SNAPSHOT_PERIOD,
|
||||
SNAPSHOT_HISTORY,
|
||||
));
|
||||
|
||||
service.add_notify(watcher.clone());
|
||||
Some(watcher)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(RunningClient {
|
||||
inner: RunningClientInner::Full {
|
||||
rpc: rpc_direct,
|
||||
informant,
|
||||
client,
|
||||
client_service: Arc::new(service),
|
||||
keep_alive: Box::new((
|
||||
watcher,
|
||||
ws_server,
|
||||
http_server,
|
||||
ipc_server,
|
||||
secretstore_key_server,
|
||||
runtime,
|
||||
)),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Set bad blocks in VerificationQeueu. By omiting header we can omit particular fork of chain.
|
||||
fn verification_bad_blocks(spec: &SpecType) -> Vec<H256> {
|
||||
match *spec {
|
||||
SpecType::Ropsten => {
|
||||
vec![
|
||||
H256::from_str("1eac3d16c642411f13c287e29144c6f58fda859407c8f24c38deb168e1040714")
|
||||
.expect("Valid hex string"),
|
||||
]
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Parity client currently executing in background threads.
|
||||
///
|
||||
/// Should be destroyed by calling `shutdown()`, otherwise execution will continue in the
|
||||
/// background.
|
||||
pub struct RunningClient {
|
||||
inner: RunningClientInner,
|
||||
}
|
||||
|
||||
enum RunningClientInner {
|
||||
Full {
|
||||
rpc:
|
||||
jsonrpc_core::MetaIoHandler<Metadata, informant::Middleware<informant::ClientNotifier>>,
|
||||
informant: Arc<Informant<FullNodeInformantData>>,
|
||||
client: Arc<Client>,
|
||||
client_service: Arc<ClientService>,
|
||||
keep_alive: Box<dyn Any>,
|
||||
},
|
||||
}
|
||||
|
||||
impl RunningClient {
|
||||
/// Performs an asynchronous RPC query.
|
||||
// FIXME: [tomaka] This API should be better, with for example a Future
|
||||
pub fn rpc_query(
|
||||
&self,
|
||||
request: &str,
|
||||
session: Option<Arc<PubSubSession>>,
|
||||
) -> FutureResult<FutureResponse, FutureOutput> {
|
||||
let metadata = Metadata {
|
||||
origin: Origin::CApi,
|
||||
session,
|
||||
};
|
||||
|
||||
match self.inner {
|
||||
RunningClientInner::Full { ref rpc, .. } => rpc.handle_request(request, metadata),
|
||||
}
|
||||
}
|
||||
|
||||
/// Shuts down the client.
|
||||
pub fn shutdown(self) {
|
||||
match self.inner {
|
||||
RunningClientInner::Full {
|
||||
rpc,
|
||||
informant,
|
||||
client,
|
||||
client_service,
|
||||
keep_alive,
|
||||
} => {
|
||||
info!("Finishing work, please wait...");
|
||||
// Create a weak reference to the client so that we can wait on shutdown
|
||||
// until it is dropped
|
||||
let weak_client = Arc::downgrade(&client);
|
||||
// Shutdown and drop the ClientService
|
||||
client_service.shutdown();
|
||||
trace!(target: "shutdown", "ClientService shut down");
|
||||
drop(client_service);
|
||||
trace!(target: "shutdown", "ClientService dropped");
|
||||
// drop this stuff as soon as exit detected.
|
||||
drop(rpc);
|
||||
trace!(target: "shutdown", "RPC dropped");
|
||||
drop(keep_alive);
|
||||
trace!(target: "shutdown", "KeepAlive dropped");
|
||||
// to make sure timer does not spawn requests while shutdown is in progress
|
||||
informant.shutdown();
|
||||
trace!(target: "shutdown", "Informant shut down");
|
||||
// just Arc is dropping here, to allow other reference release in its default time
|
||||
drop(informant);
|
||||
trace!(target: "shutdown", "Informant dropped");
|
||||
drop(client);
|
||||
trace!(target: "shutdown", "Client dropped");
|
||||
// This may help when debugging ref cycles. Requires nightly-only `#![feature(weak_counts)]`
|
||||
// trace!(target: "shutdown", "Waiting for refs to Client to shutdown, strong_count={:?}, weak_count={:?}", weak_client.strong_count(), weak_client.weak_count());
|
||||
trace!(target: "shutdown", "Waiting for refs to Client to shutdown");
|
||||
wait_for_drop(weak_client);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_running_environment(data_dir: &str, dirs: &Directories, db_dirs: &DatabaseDirectories) {
|
||||
info!("Starting {}", Colour::White.bold().paint(version()));
|
||||
info!(
|
||||
"Keys path {}",
|
||||
Colour::White
|
||||
.bold()
|
||||
.paint(dirs.keys_path(data_dir).to_string_lossy().into_owned())
|
||||
);
|
||||
info!(
|
||||
"DB path {}",
|
||||
Colour::White
|
||||
.bold()
|
||||
.paint(db_dirs.db_root_path().to_string_lossy().into_owned())
|
||||
);
|
||||
}
|
||||
|
||||
fn wait_for_drop<T>(w: Weak<T>) {
|
||||
const SLEEP_DURATION: Duration = Duration::from_secs(1);
|
||||
const WARN_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
const MAX_TIMEOUT: Duration = Duration::from_secs(300);
|
||||
|
||||
let instant = Instant::now();
|
||||
let mut warned = false;
|
||||
|
||||
while instant.elapsed() < MAX_TIMEOUT {
|
||||
if w.upgrade().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
if !warned && instant.elapsed() > WARN_TIMEOUT {
|
||||
warned = true;
|
||||
warn!("Shutdown is taking longer than expected.");
|
||||
}
|
||||
|
||||
thread::sleep(SLEEP_DURATION);
|
||||
|
||||
// When debugging shutdown issues on a nightly build it can help to enable this with the
|
||||
// `#![feature(weak_counts)]` added to lib.rs (TODO: enable when
|
||||
// https://github.com/rust-lang/rust/issues/57977 is stable)
|
||||
// trace!(target: "shutdown", "Waiting for client to drop, strong_count={:?}, weak_count={:?}", w.strong_count(), w.weak_count());
|
||||
trace!(target: "shutdown", "Waiting for client to drop");
|
||||
}
|
||||
|
||||
warn!("Shutdown timeout reached, exiting uncleanly.");
|
||||
}
|
333
bin/oe/secretstore.rs
Normal file
333
bin/oe/secretstore.rs
Normal file
@ -0,0 +1,333 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{account_utils::AccountProvider, sync::SyncProvider};
|
||||
use crypto::publickey::{Public, Secret};
|
||||
use dir::{default_data_path, helpers::replace_home};
|
||||
use ethcore::{client::Client, miner::Miner};
|
||||
use ethereum_types::Address;
|
||||
use ethkey::Password;
|
||||
use parity_runtime::Executor;
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
/// This node secret key.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum NodeSecretKey {
|
||||
/// Stored as plain text in configuration file.
|
||||
Plain(Secret),
|
||||
/// Stored as account in key store.
|
||||
#[cfg(feature = "accounts")]
|
||||
KeyStore(Address),
|
||||
}
|
||||
|
||||
/// Secret store service contract address.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ContractAddress {
|
||||
/// Contract address is read from registry.
|
||||
Registry,
|
||||
/// Contract address is specified.
|
||||
Address(Address),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
/// Secret store configuration
|
||||
pub struct Configuration {
|
||||
/// Is secret store functionality enabled?
|
||||
pub enabled: bool,
|
||||
/// Is HTTP API enabled?
|
||||
pub http_enabled: bool,
|
||||
/// Is auto migrate enabled.
|
||||
pub auto_migrate_enabled: bool,
|
||||
/// ACL check contract address.
|
||||
pub acl_check_contract_address: Option<ContractAddress>,
|
||||
/// Service contract address.
|
||||
pub service_contract_address: Option<ContractAddress>,
|
||||
/// Server key generation service contract address.
|
||||
pub service_contract_srv_gen_address: Option<ContractAddress>,
|
||||
/// Server key retrieval service contract address.
|
||||
pub service_contract_srv_retr_address: Option<ContractAddress>,
|
||||
/// Document key store service contract address.
|
||||
pub service_contract_doc_store_address: Option<ContractAddress>,
|
||||
/// Document key shadow retrieval service contract address.
|
||||
pub service_contract_doc_sretr_address: Option<ContractAddress>,
|
||||
/// This node secret.
|
||||
pub self_secret: Option<NodeSecretKey>,
|
||||
/// Other nodes IDs + addresses.
|
||||
pub nodes: BTreeMap<Public, (String, u16)>,
|
||||
/// Key Server Set contract address. If None, 'nodes' map is used.
|
||||
pub key_server_set_contract_address: Option<ContractAddress>,
|
||||
/// Interface to listen to
|
||||
pub interface: String,
|
||||
/// Port to listen to
|
||||
pub port: u16,
|
||||
/// Interface to listen to
|
||||
pub http_interface: String,
|
||||
/// Port to listen to
|
||||
pub http_port: u16,
|
||||
/// Data directory path for secret store
|
||||
pub data_path: String,
|
||||
/// Administrator public key.
|
||||
pub admin_public: Option<Public>,
|
||||
}
|
||||
|
||||
/// Secret store dependencies
|
||||
pub struct Dependencies<'a> {
|
||||
/// Blockchain client.
|
||||
pub client: Arc<Client>,
|
||||
/// Sync provider.
|
||||
pub sync: Arc<dyn SyncProvider>,
|
||||
/// Miner service.
|
||||
pub miner: Arc<Miner>,
|
||||
/// Account provider.
|
||||
pub account_provider: Arc<AccountProvider>,
|
||||
/// Passed accounts passwords.
|
||||
pub accounts_passwords: &'a [Password],
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "secretstore"))]
|
||||
mod server {
|
||||
use super::{Configuration, Dependencies, Executor};
|
||||
|
||||
/// Noop key server implementation
|
||||
pub struct KeyServer;
|
||||
|
||||
impl KeyServer {
|
||||
/// Create new noop key server
|
||||
pub fn new(
|
||||
_conf: Configuration,
|
||||
_deps: Dependencies,
|
||||
_executor: Executor,
|
||||
) -> Result<Self, String> {
|
||||
Ok(KeyServer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "secretstore")]
|
||||
mod server {
|
||||
use super::{Configuration, ContractAddress, Dependencies, Executor, NodeSecretKey};
|
||||
use ansi_term::Colour::{Red, White};
|
||||
use db;
|
||||
use ethcore_secretstore;
|
||||
use ethkey::KeyPair;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn into_service_contract_address(
|
||||
address: ContractAddress,
|
||||
) -> ethcore_secretstore::ContractAddress {
|
||||
match address {
|
||||
ContractAddress::Registry => ethcore_secretstore::ContractAddress::Registry,
|
||||
ContractAddress::Address(address) => {
|
||||
ethcore_secretstore::ContractAddress::Address(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Key server
|
||||
pub struct KeyServer {
|
||||
_key_server: Box<dyn ethcore_secretstore::KeyServer>,
|
||||
}
|
||||
|
||||
impl KeyServer {
|
||||
/// Create new key server
|
||||
pub fn new(
|
||||
mut conf: Configuration,
|
||||
deps: Dependencies,
|
||||
executor: Executor,
|
||||
) -> Result<Self, String> {
|
||||
let self_secret: Arc<dyn ethcore_secretstore::NodeKeyPair> =
|
||||
match conf.self_secret.take() {
|
||||
Some(NodeSecretKey::Plain(secret)) => {
|
||||
Arc::new(ethcore_secretstore::PlainNodeKeyPair::new(
|
||||
KeyPair::from_secret(secret)
|
||||
.map_err(|e| format!("invalid secret: {}", e))?,
|
||||
))
|
||||
}
|
||||
#[cfg(feature = "accounts")]
|
||||
Some(NodeSecretKey::KeyStore(account)) => {
|
||||
// Check if account exists
|
||||
if !deps.account_provider.has_account(account.clone()) {
|
||||
return Err(format!(
|
||||
"Account {} passed as secret store node key is not found",
|
||||
account
|
||||
));
|
||||
}
|
||||
|
||||
// Check if any passwords have been read from the password file(s)
|
||||
if deps.accounts_passwords.is_empty() {
|
||||
return Err(format!(
|
||||
"No password found for the secret store node account {}",
|
||||
account
|
||||
));
|
||||
}
|
||||
|
||||
// Attempt to sign in the engine signer.
|
||||
let password = deps
|
||||
.accounts_passwords
|
||||
.iter()
|
||||
.find(|p| {
|
||||
deps.account_provider
|
||||
.sign(account.clone(), Some((*p).clone()), Default::default())
|
||||
.is_ok()
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"No valid password for the secret store node account {}",
|
||||
account
|
||||
)
|
||||
})?;
|
||||
Arc::new(
|
||||
ethcore_secretstore::KeyStoreNodeKeyPair::new(
|
||||
deps.account_provider,
|
||||
account,
|
||||
password.clone(),
|
||||
)
|
||||
.map_err(|e| format!("{}", e))?,
|
||||
)
|
||||
}
|
||||
None => return Err("self secret is required when using secretstore".into()),
|
||||
};
|
||||
|
||||
info!(
|
||||
"Starting SecretStore node: {}",
|
||||
White.bold().paint(format!("{:?}", self_secret.public()))
|
||||
);
|
||||
if conf.acl_check_contract_address.is_none() {
|
||||
warn!(
|
||||
"Running SecretStore with disabled ACL check: {}",
|
||||
Red.bold().paint("everyone has access to stored keys")
|
||||
);
|
||||
}
|
||||
|
||||
let key_server_name = format!("{}:{}", conf.interface, conf.port);
|
||||
let mut cconf = ethcore_secretstore::ServiceConfiguration {
|
||||
listener_address: if conf.http_enabled {
|
||||
Some(ethcore_secretstore::NodeAddress {
|
||||
address: conf.http_interface.clone(),
|
||||
port: conf.http_port,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
},
|
||||
service_contract_address: conf
|
||||
.service_contract_address
|
||||
.map(into_service_contract_address),
|
||||
service_contract_srv_gen_address: conf
|
||||
.service_contract_srv_gen_address
|
||||
.map(into_service_contract_address),
|
||||
service_contract_srv_retr_address: conf
|
||||
.service_contract_srv_retr_address
|
||||
.map(into_service_contract_address),
|
||||
service_contract_doc_store_address: conf
|
||||
.service_contract_doc_store_address
|
||||
.map(into_service_contract_address),
|
||||
service_contract_doc_sretr_address: conf
|
||||
.service_contract_doc_sretr_address
|
||||
.map(into_service_contract_address),
|
||||
acl_check_contract_address: conf
|
||||
.acl_check_contract_address
|
||||
.map(into_service_contract_address),
|
||||
cluster_config: ethcore_secretstore::ClusterConfiguration {
|
||||
listener_address: ethcore_secretstore::NodeAddress {
|
||||
address: conf.interface.clone(),
|
||||
port: conf.port,
|
||||
},
|
||||
nodes: conf
|
||||
.nodes
|
||||
.into_iter()
|
||||
.map(|(p, (ip, port))| {
|
||||
(
|
||||
p,
|
||||
ethcore_secretstore::NodeAddress {
|
||||
address: ip,
|
||||
port: port,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
key_server_set_contract_address: conf
|
||||
.key_server_set_contract_address
|
||||
.map(into_service_contract_address),
|
||||
allow_connecting_to_higher_nodes: true,
|
||||
admin_public: conf.admin_public,
|
||||
auto_migrate_enabled: conf.auto_migrate_enabled,
|
||||
},
|
||||
};
|
||||
|
||||
cconf.cluster_config.nodes.insert(
|
||||
self_secret.public().clone(),
|
||||
cconf.cluster_config.listener_address.clone(),
|
||||
);
|
||||
|
||||
let db = db::open_secretstore_db(&conf.data_path)?;
|
||||
let key_server = ethcore_secretstore::start(
|
||||
deps.client,
|
||||
deps.sync,
|
||||
deps.miner,
|
||||
self_secret,
|
||||
cconf,
|
||||
db,
|
||||
executor,
|
||||
)
|
||||
.map_err(|e| format!("Error starting KeyServer {}: {}", key_server_name, e))?;
|
||||
|
||||
Ok(KeyServer {
|
||||
_key_server: key_server,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use self::server::KeyServer;
|
||||
|
||||
impl Default for Configuration {
|
||||
fn default() -> Self {
|
||||
let data_dir = default_data_path();
|
||||
Configuration {
|
||||
enabled: true,
|
||||
http_enabled: true,
|
||||
auto_migrate_enabled: true,
|
||||
acl_check_contract_address: Some(ContractAddress::Registry),
|
||||
service_contract_address: None,
|
||||
service_contract_srv_gen_address: None,
|
||||
service_contract_srv_retr_address: None,
|
||||
service_contract_doc_store_address: None,
|
||||
service_contract_doc_sretr_address: None,
|
||||
self_secret: None,
|
||||
admin_public: None,
|
||||
nodes: BTreeMap::new(),
|
||||
key_server_set_contract_address: Some(ContractAddress::Registry),
|
||||
interface: "127.0.0.1".to_owned(),
|
||||
port: 8083,
|
||||
http_interface: "127.0.0.1".to_owned(),
|
||||
http_port: 8082,
|
||||
data_path: replace_home(&data_dir, "$BASE/secretstore"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start secret store-related functionality
|
||||
pub fn start(
|
||||
conf: Configuration,
|
||||
deps: Dependencies,
|
||||
executor: Executor,
|
||||
) -> Result<Option<KeyServer>, String> {
|
||||
if !conf.enabled {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
KeyServer::new(conf, deps, executor).map(|s| Some(s))
|
||||
}
|
98
bin/oe/signer.rs
Normal file
98
bin/oe/signer.rs
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::{path::restrict_permissions_owner, rpc, rpc_apis};
|
||||
use ansi_term::Colour::White;
|
||||
use ethcore_logger::Config as LogConfig;
|
||||
use parity_rpc;
|
||||
|
||||
pub const CODES_FILENAME: &'static str = "authcodes";
|
||||
|
||||
pub struct NewToken {
|
||||
pub token: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
pub fn new_service(
|
||||
ws_conf: &rpc::WsConfiguration,
|
||||
logger_config: &LogConfig,
|
||||
) -> rpc_apis::SignerService {
|
||||
let logger_config_color = logger_config.color;
|
||||
let signer_path = ws_conf.signer_path.clone();
|
||||
let signer_enabled = ws_conf.support_token_api;
|
||||
|
||||
rpc_apis::SignerService::new(
|
||||
move || {
|
||||
generate_new_token(&signer_path, logger_config_color).map_err(|e| format!("{:?}", e))
|
||||
},
|
||||
signer_enabled,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn codes_path(path: &Path) -> PathBuf {
|
||||
let mut p = path.to_owned();
|
||||
p.push(CODES_FILENAME);
|
||||
let _ = restrict_permissions_owner(&p, true, false);
|
||||
p
|
||||
}
|
||||
|
||||
pub fn execute(ws_conf: rpc::WsConfiguration, logger_config: LogConfig) -> Result<String, String> {
|
||||
Ok(generate_token_and_url(&ws_conf, &logger_config)?.message)
|
||||
}
|
||||
|
||||
pub fn generate_token_and_url(
|
||||
ws_conf: &rpc::WsConfiguration,
|
||||
logger_config: &LogConfig,
|
||||
) -> Result<NewToken, String> {
|
||||
let code = generate_new_token(&ws_conf.signer_path, logger_config.color)
|
||||
.map_err(|err| format!("Error generating token: {:?}", err))?;
|
||||
let colored = |s: String| match logger_config.color {
|
||||
true => format!("{}", White.bold().paint(s)),
|
||||
false => s,
|
||||
};
|
||||
|
||||
Ok(NewToken {
|
||||
token: code.clone(),
|
||||
message: format!(
|
||||
r#"
|
||||
Generated token:
|
||||
{}
|
||||
"#,
|
||||
colored(code)
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_new_token(path: &Path, logger_config_color: bool) -> io::Result<String> {
|
||||
let path = codes_path(path);
|
||||
let mut codes = parity_rpc::AuthCodes::from_file(&path)?;
|
||||
codes.clear_garbage();
|
||||
let code = codes.generate_new()?;
|
||||
codes.to_file(&path)?;
|
||||
trace!(
|
||||
"New key code created: {}",
|
||||
match logger_config_color {
|
||||
true => format!("{}", White.bold().paint(&code[..])),
|
||||
false => format!("{}", &code[..]),
|
||||
}
|
||||
);
|
||||
Ok(code)
|
||||
}
|
347
bin/oe/snapshot.rs
Normal file
347
bin/oe/snapshot.rs
Normal file
@ -0,0 +1,347 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Snapshot and restoration commands.
|
||||
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{hash::keccak, types::ids::BlockId};
|
||||
use ethcore::{
|
||||
client::{DatabaseCompactionProfile, Mode, VMType},
|
||||
miner::Miner,
|
||||
snapshot::{
|
||||
io::{PackedReader, PackedWriter, SnapshotReader},
|
||||
service::Service as SnapshotService,
|
||||
Progress, RestorationStatus, SnapshotConfiguration, SnapshotService as SS,
|
||||
},
|
||||
};
|
||||
use ethcore_service::ClientService;
|
||||
|
||||
use crate::{
|
||||
cache::CacheConfig,
|
||||
db,
|
||||
helpers::{execute_upgrades, to_client_config},
|
||||
params::{fatdb_switch_to_bool, tracing_switch_to_bool, Pruning, SpecType, Switch},
|
||||
user_defaults::UserDefaults,
|
||||
};
|
||||
use dir::Directories;
|
||||
|
||||
/// Kinds of snapshot commands.
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum Kind {
|
||||
/// Take a snapshot.
|
||||
Take,
|
||||
/// Restore a snapshot.
|
||||
Restore,
|
||||
}
|
||||
|
||||
/// Command for snapshot creation or restoration.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct SnapshotCommand {
|
||||
pub cache_config: CacheConfig,
|
||||
pub dirs: Directories,
|
||||
pub spec: SpecType,
|
||||
pub pruning: Pruning,
|
||||
pub pruning_history: u64,
|
||||
pub pruning_memory: usize,
|
||||
pub tracing: Switch,
|
||||
pub fat_db: Switch,
|
||||
pub compaction: DatabaseCompactionProfile,
|
||||
pub file_path: Option<String>,
|
||||
pub kind: Kind,
|
||||
pub block_at: BlockId,
|
||||
pub max_round_blocks_to_import: usize,
|
||||
pub snapshot_conf: SnapshotConfiguration,
|
||||
}
|
||||
|
||||
// helper for reading chunks from arbitrary reader and feeding them into the
|
||||
// service.
|
||||
fn restore_using<R: SnapshotReader>(
|
||||
snapshot: Arc<SnapshotService>,
|
||||
reader: &R,
|
||||
recover: bool,
|
||||
) -> Result<(), String> {
|
||||
let manifest = reader.manifest();
|
||||
|
||||
info!(
|
||||
"Restoring to block #{} (0x{:?})",
|
||||
manifest.block_number, manifest.block_hash
|
||||
);
|
||||
|
||||
snapshot
|
||||
.init_restore(manifest.clone(), recover)
|
||||
.map_err(|e| format!("Failed to begin restoration: {}", e))?;
|
||||
|
||||
let (num_state, num_blocks) = (manifest.state_hashes.len(), manifest.block_hashes.len());
|
||||
|
||||
let informant_handle = snapshot.clone();
|
||||
::std::thread::spawn(move || {
|
||||
while let RestorationStatus::Ongoing {
|
||||
state_chunks_done,
|
||||
block_chunks_done,
|
||||
..
|
||||
} = informant_handle.restoration_status()
|
||||
{
|
||||
info!(
|
||||
"Processed {}/{} state chunks and {}/{} block chunks.",
|
||||
state_chunks_done, num_state, block_chunks_done, num_blocks
|
||||
);
|
||||
::std::thread::sleep(Duration::from_secs(5));
|
||||
}
|
||||
});
|
||||
|
||||
info!("Restoring state");
|
||||
for &state_hash in &manifest.state_hashes {
|
||||
if snapshot.restoration_status() == RestorationStatus::Failed {
|
||||
return Err("Restoration failed".into());
|
||||
}
|
||||
|
||||
let chunk = reader.chunk(state_hash).map_err(|e| {
|
||||
format!(
|
||||
"Encountered error while reading chunk {:?}: {}",
|
||||
state_hash, e
|
||||
)
|
||||
})?;
|
||||
|
||||
let hash = keccak(&chunk);
|
||||
if hash != state_hash {
|
||||
return Err(format!(
|
||||
"Mismatched chunk hash. Expected {:?}, got {:?}",
|
||||
state_hash, hash
|
||||
));
|
||||
}
|
||||
|
||||
snapshot.feed_state_chunk(state_hash, &chunk);
|
||||
}
|
||||
|
||||
info!("Restoring blocks");
|
||||
for &block_hash in &manifest.block_hashes {
|
||||
if snapshot.restoration_status() == RestorationStatus::Failed {
|
||||
return Err("Restoration failed".into());
|
||||
}
|
||||
|
||||
let chunk = reader.chunk(block_hash).map_err(|e| {
|
||||
format!(
|
||||
"Encountered error while reading chunk {:?}: {}",
|
||||
block_hash, e
|
||||
)
|
||||
})?;
|
||||
|
||||
let hash = keccak(&chunk);
|
||||
if hash != block_hash {
|
||||
return Err(format!(
|
||||
"Mismatched chunk hash. Expected {:?}, got {:?}",
|
||||
block_hash, hash
|
||||
));
|
||||
}
|
||||
snapshot.feed_block_chunk(block_hash, &chunk);
|
||||
}
|
||||
|
||||
match snapshot.restoration_status() {
|
||||
RestorationStatus::Ongoing { .. } => {
|
||||
Err("Snapshot file is incomplete and missing chunks.".into())
|
||||
}
|
||||
RestorationStatus::Initializing { .. } => {
|
||||
Err("Snapshot restoration is still initializing.".into())
|
||||
}
|
||||
RestorationStatus::Failed => Err("Snapshot restoration failed.".into()),
|
||||
RestorationStatus::Inactive => {
|
||||
info!("Restoration complete.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SnapshotCommand {
|
||||
// shared portion of snapshot commands: start the client service
|
||||
fn start_service(self) -> Result<ClientService, String> {
|
||||
// load spec file
|
||||
let spec = self.spec.spec(&self.dirs.cache)?;
|
||||
|
||||
// load genesis hash
|
||||
let genesis_hash = spec.genesis_header().hash();
|
||||
|
||||
// database paths
|
||||
let db_dirs = self
|
||||
.dirs
|
||||
.database(genesis_hash, None, spec.data_dir.clone());
|
||||
|
||||
// user defaults path
|
||||
let user_defaults_path = db_dirs.user_defaults_path();
|
||||
|
||||
// load user defaults
|
||||
let user_defaults = UserDefaults::load(&user_defaults_path)?;
|
||||
|
||||
// select pruning algorithm
|
||||
let algorithm = self.pruning.to_algorithm(&user_defaults);
|
||||
|
||||
// check if tracing is on
|
||||
let tracing = tracing_switch_to_bool(self.tracing, &user_defaults)?;
|
||||
|
||||
// check if fatdb is on
|
||||
let fat_db = fatdb_switch_to_bool(self.fat_db, &user_defaults, algorithm)?;
|
||||
|
||||
// prepare client and snapshot paths.
|
||||
let client_path = db_dirs.client_path(algorithm);
|
||||
let snapshot_path = db_dirs.snapshot_path();
|
||||
|
||||
// execute upgrades
|
||||
execute_upgrades(&self.dirs.base, &db_dirs, algorithm, &self.compaction)?;
|
||||
|
||||
// prepare client config
|
||||
let mut client_config = to_client_config(
|
||||
&self.cache_config,
|
||||
spec.name.to_lowercase(),
|
||||
Mode::Active,
|
||||
tracing,
|
||||
fat_db,
|
||||
self.compaction,
|
||||
VMType::default(),
|
||||
"".into(),
|
||||
algorithm,
|
||||
self.pruning_history,
|
||||
self.pruning_memory,
|
||||
true,
|
||||
self.max_round_blocks_to_import,
|
||||
);
|
||||
|
||||
client_config.snapshot = self.snapshot_conf;
|
||||
|
||||
let restoration_db_handler = db::restoration_db_handler(&client_path, &client_config);
|
||||
let client_db = restoration_db_handler
|
||||
.open(&client_path)
|
||||
.map_err(|e| format!("Failed to open database {:?}", e))?;
|
||||
|
||||
let service = ClientService::start(
|
||||
client_config,
|
||||
&spec,
|
||||
client_db,
|
||||
&snapshot_path,
|
||||
restoration_db_handler,
|
||||
&self.dirs.ipc_path(),
|
||||
// TODO [ToDr] don't use test miner here
|
||||
// (actually don't require miner at all)
|
||||
Arc::new(Miner::new_for_tests(&spec, None)),
|
||||
)
|
||||
.map_err(|e| format!("Client service error: {:?}", e))?;
|
||||
|
||||
Ok(service)
|
||||
}
|
||||
/// restore from a snapshot
|
||||
pub fn restore(self) -> Result<(), String> {
|
||||
let file = self.file_path.clone();
|
||||
let service = self.start_service()?;
|
||||
|
||||
warn!("Snapshot restoration is experimental and the format may be subject to change.");
|
||||
warn!(
|
||||
"On encountering an unexpected error, please ensure that you have a recent snapshot."
|
||||
);
|
||||
|
||||
let snapshot = service.snapshot_service();
|
||||
|
||||
if let Some(file) = file {
|
||||
info!("Attempting to restore from snapshot at '{}'", file);
|
||||
|
||||
let reader = PackedReader::new(Path::new(&file))
|
||||
.map_err(|e| format!("Couldn't open snapshot file: {}", e))
|
||||
.and_then(|x| x.ok_or("Snapshot file has invalid format.".into()));
|
||||
|
||||
let reader = reader?;
|
||||
restore_using(snapshot, &reader, true)?;
|
||||
} else {
|
||||
info!("Attempting to restore from local snapshot.");
|
||||
|
||||
// attempting restoration with recovery will lead to deadlock
|
||||
// as we currently hold a read lock on the service's reader.
|
||||
match *snapshot.reader() {
|
||||
Some(ref reader) => restore_using(snapshot.clone(), reader, false)?,
|
||||
None => return Err("No local snapshot found.".into()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Take a snapshot from the head of the chain.
|
||||
pub fn take_snapshot(self) -> Result<(), String> {
|
||||
let file_path = self
|
||||
.file_path
|
||||
.clone()
|
||||
.ok_or("No file path provided.".to_owned())?;
|
||||
let file_path: PathBuf = file_path.into();
|
||||
let block_at = self.block_at;
|
||||
let service = self.start_service()?;
|
||||
|
||||
warn!("Snapshots are currently experimental. File formats may be subject to change.");
|
||||
|
||||
let writer = PackedWriter::new(&file_path)
|
||||
.map_err(|e| format!("Failed to open snapshot writer: {}", e))?;
|
||||
|
||||
let progress = Arc::new(Progress::default());
|
||||
let p = progress.clone();
|
||||
let informant_handle = ::std::thread::spawn(move || {
|
||||
::std::thread::sleep(Duration::from_secs(5));
|
||||
|
||||
let mut last_size = 0;
|
||||
while !p.done() {
|
||||
let cur_size = p.size();
|
||||
if cur_size != last_size {
|
||||
last_size = cur_size;
|
||||
let bytes = crate::informant::format_bytes(cur_size as usize);
|
||||
info!(
|
||||
"Snapshot: {} accounts {} blocks {}",
|
||||
p.accounts(),
|
||||
p.blocks(),
|
||||
bytes
|
||||
);
|
||||
}
|
||||
|
||||
::std::thread::sleep(Duration::from_secs(5));
|
||||
}
|
||||
});
|
||||
|
||||
if let Err(e) = service.client().take_snapshot(writer, block_at, &*progress) {
|
||||
let _ = ::std::fs::remove_file(&file_path);
|
||||
return Err(format!(
|
||||
"Encountered fatal error while creating snapshot: {}",
|
||||
e
|
||||
));
|
||||
}
|
||||
|
||||
info!("snapshot creation complete");
|
||||
|
||||
assert!(progress.done());
|
||||
informant_handle
|
||||
.join()
|
||||
.map_err(|_| "failed to join logger thread")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute this snapshot command.
|
||||
pub fn execute(cmd: SnapshotCommand) -> Result<String, String> {
|
||||
match cmd.kind {
|
||||
Kind::Take => cmd.take_snapshot()?,
|
||||
Kind::Restore => cmd.restore()?,
|
||||
}
|
||||
|
||||
Ok(String::new())
|
||||
}
|
119
bin/oe/stratum.rs
Normal file
119
bin/oe/stratum.rs
Normal file
@ -0,0 +1,119 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! OpenEthereum sync service
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use ethcore_stratum::{Stratum as StratumServer, PushWorkHandler, RemoteJobDispatcher, ServiceConfiguration};
|
||||
use modules::service_urls;
|
||||
use boot;
|
||||
use hypervisor::service::IpcModuleId;
|
||||
use hypervisor::{HYPERVISOR_IPC_URL, ControlService};
|
||||
use std::net::{SocketAddr, IpAddr};
|
||||
use std::str::FromStr;
|
||||
use nanoipc;
|
||||
use std::thread;
|
||||
use ethcore::miner::stratum::{STRATUM_SOCKET_NAME, JOB_DISPATCHER_SOCKET_NAME};
|
||||
|
||||
pub const MODULE_ID: IpcModuleId = 8000;
|
||||
|
||||
#[derive(Default)]
|
||||
struct StratumControlService {
|
||||
pub stop: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl ControlService for StratumControlService {
|
||||
fn shutdown(&self) -> bool {
|
||||
trace!(target: "hypervisor", "Received shutdown from control service");
|
||||
self.stop.store(true, ::std::sync::atomic::Ordering::SeqCst);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
boot::setup_cli_logger("stratum");
|
||||
|
||||
let service_config: ServiceConfiguration = boot::payload()
|
||||
.unwrap_or_else(|e| {
|
||||
println!("Fatal: error reading boot arguments ({:?})", e);
|
||||
std::process::exit(1)
|
||||
});
|
||||
|
||||
let job_dispatcher = dependency!(
|
||||
RemoteJobDispatcher,
|
||||
&service_urls::with_base(&service_config.io_path, JOB_DISPATCHER_SOCKET_NAME)
|
||||
);
|
||||
|
||||
let _ = boot::main_thread();
|
||||
let service_stop = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let server =
|
||||
StratumServer::start(
|
||||
&SocketAddr::new(
|
||||
IpAddr::from_str(&service_config.listen_addr)
|
||||
.unwrap_or_else(|e| {
|
||||
println!("Fatal: invalid listen address: '{}' ({:?})", &service_config.listen_addr, e);
|
||||
std::process::exit(1)
|
||||
}),
|
||||
service_config.port,
|
||||
),
|
||||
job_dispatcher.service().clone(),
|
||||
service_config.secret
|
||||
).unwrap_or_else(
|
||||
|e| {
|
||||
println!("Fatal: cannot start stratum server({:?})", e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
);
|
||||
|
||||
boot::host_service(
|
||||
&service_urls::with_base(&service_config.io_path, STRATUM_SOCKET_NAME),
|
||||
service_stop.clone(),
|
||||
server.clone() as Arc<PushWorkHandler>
|
||||
);
|
||||
|
||||
let hypervisor = boot::register(
|
||||
&service_urls::with_base(&service_config.io_path, HYPERVISOR_IPC_URL),
|
||||
&service_urls::with_base(&service_config.io_path, service_urls::STRATUM_CONTROL),
|
||||
MODULE_ID
|
||||
);
|
||||
|
||||
let timer_svc = server.clone();
|
||||
let timer_stop = service_stop.clone();
|
||||
thread::spawn(move || {
|
||||
while !timer_stop.load(Ordering::SeqCst) {
|
||||
thread::park_timeout(::std::time::Duration::from_millis(2000));
|
||||
// It almost always not doing anything, only greets new peers with a job
|
||||
timer_svc.maintain();
|
||||
}
|
||||
});
|
||||
|
||||
let control_service = Arc::new(StratumControlService::default());
|
||||
let as_control = control_service.clone() as Arc<ControlService>;
|
||||
let mut worker = nanoipc::Worker::<ControlService>::new(&as_control);
|
||||
worker.add_reqrep(
|
||||
&service_urls::with_base(&service_config.io_path, service_urls::STRATUM_CONTROL)
|
||||
).unwrap();
|
||||
|
||||
while !control_service.stop.load(Ordering::SeqCst) {
|
||||
worker.poll();
|
||||
}
|
||||
service_stop.store(true, Ordering::SeqCst);
|
||||
|
||||
hypervisor.module_shutdown(MODULE_ID);
|
||||
trace!(target: "hypervisor", "Stratum process terminated gracefully");
|
||||
}
|
246
bin/oe/upgrade.rs
Normal file
246
bin/oe/upgrade.rs
Normal file
@ -0,0 +1,246 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Parity upgrade logic
|
||||
|
||||
use dir::{default_data_path, helpers::replace_home, home_dir, DatabaseDirectories};
|
||||
use journaldb::Algorithm;
|
||||
use semver::{SemVerError, Version};
|
||||
use std::{
|
||||
collections::*,
|
||||
fs::{self, create_dir_all, File},
|
||||
io,
|
||||
io::{Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
CannotCreateConfigPath(io::Error),
|
||||
CannotWriteVersionFile(io::Error),
|
||||
CannotUpdateVersionFile(io::Error),
|
||||
SemVer(SemVerError),
|
||||
}
|
||||
|
||||
impl From<SemVerError> for Error {
|
||||
fn from(err: SemVerError) -> Self {
|
||||
Error::SemVer(err)
|
||||
}
|
||||
}
|
||||
|
||||
const CURRENT_VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(Hash, PartialEq, Eq)]
|
||||
struct UpgradeKey {
|
||||
pub old_version: Version,
|
||||
pub new_version: Version,
|
||||
}
|
||||
|
||||
type UpgradeList = HashMap<UpgradeKey, fn() -> Result<(), Error>>;
|
||||
|
||||
impl UpgradeKey {
|
||||
// given the following config exist
|
||||
// ver.lock 1.1 (`previous_version`)
|
||||
//
|
||||
// current_version 1.4 (`current_version`)
|
||||
//
|
||||
//
|
||||
//upgrades (set of `UpgradeKey`)
|
||||
// 1.0 -> 1.1 (u1)
|
||||
// 1.1 -> 1.2 (u2)
|
||||
// 1.2 -> 1.3 (u3)
|
||||
// 1.3 -> 1.4 (u4)
|
||||
// 1.4 -> 1.5 (u5)
|
||||
//
|
||||
// then the following upgrades should be applied:
|
||||
// u2, u3, u4
|
||||
fn is_applicable(&self, previous_version: &Version, current_version: &Version) -> bool {
|
||||
self.old_version >= *previous_version && self.new_version <= *current_version
|
||||
}
|
||||
}
|
||||
|
||||
// dummy upgrade (remove when the first one is in)
|
||||
fn dummy_upgrade() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_upgrades(upgrades: &mut UpgradeList) {
|
||||
// dummy upgrade (remove when the first one is in)
|
||||
upgrades.insert(
|
||||
UpgradeKey {
|
||||
old_version: Version::new(0, 9, 0),
|
||||
new_version: Version::new(1, 0, 0),
|
||||
},
|
||||
dummy_upgrade,
|
||||
);
|
||||
}
|
||||
|
||||
fn upgrade_from_version(previous_version: &Version) -> Result<usize, Error> {
|
||||
let mut upgrades = HashMap::new();
|
||||
push_upgrades(&mut upgrades);
|
||||
|
||||
let current_version = Version::parse(CURRENT_VERSION)?;
|
||||
|
||||
let mut count = 0;
|
||||
for upgrade_key in upgrades.keys() {
|
||||
if upgrade_key.is_applicable(previous_version, ¤t_version) {
|
||||
let upgrade_script = upgrades[upgrade_key];
|
||||
upgrade_script()?;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn with_locked_version<F>(db_path: &str, script: F) -> Result<usize, Error>
|
||||
where
|
||||
F: Fn(&Version) -> Result<usize, Error>,
|
||||
{
|
||||
let mut path = PathBuf::from(db_path);
|
||||
create_dir_all(&path).map_err(Error::CannotCreateConfigPath)?;
|
||||
path.push("ver.lock");
|
||||
|
||||
let version = File::open(&path)
|
||||
.ok()
|
||||
.and_then(|ref mut file| {
|
||||
let mut version_string = String::new();
|
||||
file.read_to_string(&mut version_string)
|
||||
.ok()
|
||||
.and_then(|_| Version::parse(&version_string).ok())
|
||||
})
|
||||
.unwrap_or(Version::new(0, 9, 0));
|
||||
|
||||
let mut lock = File::create(&path).map_err(Error::CannotWriteVersionFile)?;
|
||||
let result = script(&version);
|
||||
|
||||
let written_version = Version::parse(CURRENT_VERSION)?;
|
||||
lock.write_all(written_version.to_string().as_bytes())
|
||||
.map_err(Error::CannotUpdateVersionFile)?;
|
||||
result
|
||||
}
|
||||
|
||||
pub fn upgrade(db_path: &str) -> Result<usize, Error> {
|
||||
with_locked_version(db_path, |ver| upgrade_from_version(ver))
|
||||
}
|
||||
|
||||
fn file_exists(path: &Path) -> bool {
|
||||
match fs::metadata(&path) {
|
||||
Err(ref e) if e.kind() == io::ErrorKind::NotFound => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "accounts"))]
|
||||
pub fn upgrade_key_location(from: &PathBuf, to: &PathBuf) {
|
||||
match fs::create_dir_all(&to).and_then(|()| fs::read_dir(from)) {
|
||||
Ok(entries) => {
|
||||
let files: Vec<_> = entries
|
||||
.filter_map(|f| {
|
||||
f.ok().and_then(|f| {
|
||||
if f.file_type().ok().map_or(false, |f| f.is_file()) {
|
||||
f.file_name().to_str().map(|s| s.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let mut num: usize = 0;
|
||||
for name in files {
|
||||
let mut from = from.clone();
|
||||
from.push(&name);
|
||||
let mut to = to.clone();
|
||||
to.push(&name);
|
||||
if !file_exists(&to) {
|
||||
if let Err(e) = fs::rename(&from, &to) {
|
||||
debug!("Error upgrading key {:?}: {:?}", from, e);
|
||||
} else {
|
||||
num += 1;
|
||||
}
|
||||
} else {
|
||||
debug!("Skipped upgrading key {:?}", from);
|
||||
}
|
||||
}
|
||||
if num > 0 {
|
||||
info!(
|
||||
"Moved {} keys from {} to {}",
|
||||
num,
|
||||
from.to_string_lossy(),
|
||||
to.to_string_lossy()
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Error moving keys from {:?} to {:?}: {:?}", from, to, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn upgrade_dir_location(source: &PathBuf, dest: &PathBuf) {
|
||||
if file_exists(&source) {
|
||||
if !file_exists(&dest) {
|
||||
let mut parent = dest.clone();
|
||||
parent.pop();
|
||||
if let Err(e) = fs::create_dir_all(&parent).and_then(|()| fs::rename(&source, &dest)) {
|
||||
debug!("Skipped path {:?} -> {:?} :{:?}", source, dest, e);
|
||||
} else {
|
||||
info!(
|
||||
"Moved {} to {}",
|
||||
source.to_string_lossy(),
|
||||
dest.to_string_lossy()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
"Skipped upgrading directory {:?}, Destination already exists at {:?}",
|
||||
source, dest
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn upgrade_user_defaults(dirs: &DatabaseDirectories) {
|
||||
let source = dirs.legacy_user_defaults_path();
|
||||
let dest = dirs.user_defaults_path();
|
||||
if file_exists(&source) {
|
||||
if !file_exists(&dest) {
|
||||
if let Err(e) = fs::rename(&source, &dest) {
|
||||
debug!("Skipped upgrading user defaults {:?}:{:?}", dest, e);
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
"Skipped upgrading user defaults {:?}, File exists at {:?}",
|
||||
source, dest
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upgrade_data_paths(base_path: &str, dirs: &DatabaseDirectories, pruning: Algorithm) {
|
||||
if home_dir().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let legacy_root_path = replace_home("", "$HOME/.parity");
|
||||
let default_path = default_data_path();
|
||||
if legacy_root_path != base_path && base_path == default_path {
|
||||
upgrade_dir_location(&PathBuf::from(legacy_root_path), &PathBuf::from(&base_path));
|
||||
}
|
||||
upgrade_dir_location(&dirs.legacy_version_path(pruning), &dirs.db_path(pruning));
|
||||
upgrade_dir_location(&dirs.legacy_snapshot_path(), &dirs.snapshot_path());
|
||||
upgrade_dir_location(&dirs.legacy_network_path(), &dirs.network_path());
|
||||
upgrade_user_defaults(&dirs);
|
||||
}
|
188
bin/oe/user_defaults.rs
Normal file
188
bin/oe/user_defaults.rs
Normal file
@ -0,0 +1,188 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use ethcore::client::Mode as ClientMode;
|
||||
use journaldb::Algorithm;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json::{de::from_reader, ser::to_string};
|
||||
use std::{fs::File, io::Write, path::Path, time::Duration};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Seconds(Duration);
|
||||
|
||||
impl Seconds {
|
||||
pub fn value(&self) -> u64 {
|
||||
self.0.as_secs()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Seconds {
|
||||
fn from(s: u64) -> Seconds {
|
||||
Seconds(Duration::from_secs(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Duration> for Seconds {
|
||||
fn from(d: Duration) -> Seconds {
|
||||
Seconds(d)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Duration> for Seconds {
|
||||
fn into(self) -> Duration {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Seconds {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_u64(self.value())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Seconds {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let secs = u64::deserialize(deserializer)?;
|
||||
Ok(Seconds::from(secs))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase", tag = "mode")]
|
||||
pub enum Mode {
|
||||
Active,
|
||||
Passive {
|
||||
#[serde(rename = "mode.timeout")]
|
||||
timeout: Seconds,
|
||||
#[serde(rename = "mode.alarm")]
|
||||
alarm: Seconds,
|
||||
},
|
||||
Dark {
|
||||
#[serde(rename = "mode.timeout")]
|
||||
timeout: Seconds,
|
||||
},
|
||||
Offline,
|
||||
}
|
||||
|
||||
impl Into<ClientMode> for Mode {
|
||||
fn into(self) -> ClientMode {
|
||||
match self {
|
||||
Mode::Active => ClientMode::Active,
|
||||
Mode::Passive { timeout, alarm } => ClientMode::Passive(timeout.into(), alarm.into()),
|
||||
Mode::Dark { timeout } => ClientMode::Dark(timeout.into()),
|
||||
Mode::Offline => ClientMode::Off,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ClientMode> for Mode {
|
||||
fn from(mode: ClientMode) -> Mode {
|
||||
match mode {
|
||||
ClientMode::Active => Mode::Active,
|
||||
ClientMode::Passive(timeout, alarm) => Mode::Passive {
|
||||
timeout: timeout.into(),
|
||||
alarm: alarm.into(),
|
||||
},
|
||||
ClientMode::Dark(timeout) => Mode::Dark {
|
||||
timeout: timeout.into(),
|
||||
},
|
||||
ClientMode::Off => Mode::Offline,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct UserDefaults {
|
||||
pub is_first_launch: bool,
|
||||
#[serde(with = "algorithm_serde")]
|
||||
pub pruning: Algorithm,
|
||||
pub tracing: bool,
|
||||
pub fat_db: bool,
|
||||
#[serde(flatten)]
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
impl UserDefaults {
|
||||
pub fn mode(&self) -> ClientMode {
|
||||
self.mode.clone().into()
|
||||
}
|
||||
|
||||
pub fn set_mode(&mut self, mode: ClientMode) {
|
||||
self.mode = mode.into();
|
||||
}
|
||||
}
|
||||
|
||||
mod algorithm_serde {
|
||||
use journaldb::Algorithm;
|
||||
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
pub fn serialize<S>(algorithm: &Algorithm, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
algorithm.as_str().serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Algorithm, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let pruning = String::deserialize(deserializer)?;
|
||||
pruning
|
||||
.parse()
|
||||
.map_err(|_| Error::custom("invalid pruning method"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UserDefaults {
|
||||
fn default() -> Self {
|
||||
UserDefaults {
|
||||
is_first_launch: true,
|
||||
pruning: Algorithm::OverlayRecent,
|
||||
tracing: false,
|
||||
fat_db: false,
|
||||
mode: Mode::Active,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserDefaults {
|
||||
pub fn load<P>(path: P) -> Result<Self, String>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
match File::open(path) {
|
||||
Ok(file) => match from_reader(file) {
|
||||
Ok(defaults) => Ok(defaults),
|
||||
Err(e) => {
|
||||
warn!("Error loading user defaults file: {:?}", e);
|
||||
Ok(UserDefaults::default())
|
||||
}
|
||||
},
|
||||
_ => Ok(UserDefaults::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save<P>(&self, path: P) -> Result<(), String>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut file: File =
|
||||
File::create(path).map_err(|_| "Cannot create user defaults file".to_owned())?;
|
||||
file.write_all(to_string(&self).unwrap().as_bytes())
|
||||
.map_err(|_| "Failed to save user defaults".to_owned())
|
||||
}
|
||||
}
|
23
crates/accounts/Cargo.toml
Normal file
23
crates/accounts/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
description = "OpenEthereum Account Management"
|
||||
homepage = "https://github.com/openethereum/openethereum"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore-accounts"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
common-types = { path = "../ethcore/types" }
|
||||
ethkey = { path = "ethkey" }
|
||||
ethstore = { path = "ethstore" }
|
||||
log = "0.4"
|
||||
parity-crypto = { version = "0.6.2", features = [ "publickey" ] }
|
||||
parking_lot = "0.11.1"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
ethereum-types = "0.9.2"
|
||||
tempdir = "0.3"
|
21
crates/accounts/ethkey/Cargo.toml
Normal file
21
crates/accounts/ethkey/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
description = "Parity Ethereum Keys Generator"
|
||||
name = "ethkey"
|
||||
version = "0.3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
edit-distance = "2.0"
|
||||
parity-crypto = { version = "0.6.2", features = ["publickey"] }
|
||||
eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1", rev = "9791e79f21a5309dcb6e0bd254b1ef88fca2f1f4" }
|
||||
ethereum-types = "0.9.2"
|
||||
lazy_static = "1.0"
|
||||
log = "0.4"
|
||||
memzero = { path = "../../../crates/util/memzero" }
|
||||
parity-wordlist = "1.3"
|
||||
quick-error = "1.2.2"
|
||||
rand = "0.7.3"
|
||||
rustc-hex = "1.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
tiny-keccak = "1.4"
|
220
crates/accounts/ethkey/README.md
Normal file
220
crates/accounts/ethkey/README.md
Normal file
@ -0,0 +1,220 @@
|
||||
## ethkey-cli
|
||||
|
||||
Parity Ethereum keys generator.
|
||||
|
||||
### Usage
|
||||
|
||||
```
|
||||
Parity Ethereum Keys Generator.
|
||||
Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
|
||||
Usage:
|
||||
ethkey info <secret-or-phrase> [options]
|
||||
ethkey generate random [options]
|
||||
ethkey generate prefix <prefix> [options]
|
||||
ethkey sign <secret> <message>
|
||||
ethkey verify public <public> <signature> <message>
|
||||
ethkey verify address <address> <signature> <message>
|
||||
ethkey recover <address> <known-phrase>
|
||||
ethkey [-h | --help]
|
||||
|
||||
Options:
|
||||
-h, --help Display this message and exit.
|
||||
-s, --secret Display only the secret key.
|
||||
-p, --public Display only the public key.
|
||||
-a, --address Display only the address.
|
||||
-b, --brain Use parity brain wallet algorithm. Not recommended.
|
||||
|
||||
Commands:
|
||||
info Display public key and address of the secret.
|
||||
generate random Generates new random Ethereum key.
|
||||
generate prefix Random generation, but address must start with a prefix ("vanity address").
|
||||
sign Sign message using a secret key.
|
||||
verify Verify signer of the signature by public key or address.
|
||||
recover Try to find brain phrase matching given address from partial phrase.
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
#### `info <secret>`
|
||||
*Display info about private key.*
|
||||
|
||||
- `<secret>` - ethereum secret, 32 bytes long
|
||||
|
||||
```
|
||||
ethkey info 17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55
|
||||
```
|
||||
|
||||
```
|
||||
secret: 17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55
|
||||
public: 689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124
|
||||
address: 26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
|
||||
#### `info --brain <phrase>`
|
||||
*Display info about private key generate from brain wallet recovery phrase.*
|
||||
|
||||
- `<phrase>` - Parity recovery phrase, 12 words
|
||||
|
||||
```
|
||||
ethkey info --brain "this is sparta"
|
||||
```
|
||||
|
||||
```
|
||||
The recover phrase was not generated by Parity: The word 'this' does not come from the dictionary.
|
||||
|
||||
secret: aa22b54c0cb43ee30a014afe5ef3664b1cde299feabca46cd3167a85a57c39f2
|
||||
public: c4c5398da6843632c123f543d714d2d2277716c11ff612b2a2f23c6bda4d6f0327c31cd58c55a9572c3cc141dade0c32747a13b7ef34c241b26c84adbb28fcf4
|
||||
address: 006e27b6a72e1f34c626762f3c4761547aff1421
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `generate random`
|
||||
*Generate new keypair randomly.*
|
||||
|
||||
```
|
||||
ethkey generate random
|
||||
```
|
||||
|
||||
```
|
||||
secret: 7d29fab185a33e2cd955812397354c472d2b84615b645aa135ff539f6b0d70d5
|
||||
public: 35f222d88b80151857a2877826d940104887376a94c1cbd2c8c7c192eb701df88a18a4ecb8b05b1466c5b3706042027b5e079fe3a3683e66d822b0e047aa3418
|
||||
address: a8fa5dd30a87bb9e3288d604eb74949c515ab66e
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `generate random --brain`
|
||||
*Generate new keypair with recovery phrase randomly.*
|
||||
|
||||
```
|
||||
ethkey generate random --brain
|
||||
```
|
||||
|
||||
```
|
||||
recovery phrase: thwarting scandal creamer nuzzle asparagus blast crouch trusting anytime elixir frenzied octagon
|
||||
secret: 001ce488d50d2f7579dc190c4655f32918d505cee3de63bddc7101bc91c0c2f0
|
||||
public: 4e19a5fdae82596e1485c69b687c9cc52b5078e5b0668ef3ce8543cd90e712cb00df822489bc1f1dcb3623538a54476c7b3def44e1a51dc174e86448b63f42d0
|
||||
address: 00cf3711cbd3a1512570639280758118ba0b2bcb
|
||||
```
|
||||
|
||||
|
||||
--
|
||||
|
||||
#### `generate prefix <prefix>`
|
||||
*Generate new keypair randomly with address starting with prefix.*
|
||||
|
||||
- `<prefix>` - desired address prefix, 0 - 32 bytes long.
|
||||
|
||||
```
|
||||
ethkey generate prefix ff
|
||||
```
|
||||
|
||||
```
|
||||
secret: 2075b1d9c124ea673de7273758ed6de14802a9da8a73ceb74533d7c312ff6acd
|
||||
public: 48dbce4508566a05509980a5dd1335599fcdac6f9858ba67018cecb9f09b8c4066dc4c18ae2722112fd4d9ac36d626793fffffb26071dfeb0c2300df994bd173
|
||||
address: fff7e25dff2aa60f61f9d98130c8646a01f31649
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `generate prefix --brain <prefix>`
|
||||
*Generate new keypair with recovery phrase randomly with address starting with prefix.*
|
||||
|
||||
- `<prefix>` - desired address prefix, 0 - 32 bytes long.
|
||||
|
||||
```
|
||||
ethkey generate prefix --brain 00cf
|
||||
```
|
||||
|
||||
```
|
||||
recovery phrase: thwarting scandal creamer nuzzle asparagus blast crouch trusting anytime elixir frenzied octagon
|
||||
secret: 001ce488d50d2f7579dc190c4655f32918d505cee3de63bddc7101bc91c0c2f0
|
||||
public: 4e19a5fdae82596e1485c69b687c9cc52b5078e5b0668ef3ce8543cd90e712cb00df822489bc1f1dcb3623538a54476c7b3def44e1a51dc174e86448b63f42d0
|
||||
address: 00cf3711cbd3a1512570639280758118ba0b2bcb
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `sign <secret> <message>`
|
||||
*Sign a message with a secret.*
|
||||
|
||||
- `<secret>` - ethereum secret, 32 bytes long
|
||||
- `<message>` - message to sign, 32 bytes long
|
||||
|
||||
```
|
||||
ethkey sign 17d08f5fe8c77af811caa0c9a187e668ce3b74a99acc3f6d976f075fa8e0be55 bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987
|
||||
```
|
||||
|
||||
```
|
||||
c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `verify public <public> <signature> <message>`
|
||||
*Verify the signature.*
|
||||
|
||||
- `<public>` - ethereum public, 64 bytes long
|
||||
- `<signature>` - message signature, 65 bytes long
|
||||
- `<message>` - message, 32 bytes long
|
||||
|
||||
```
|
||||
ethkey verify public 689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124 c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200 bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987
|
||||
```
|
||||
|
||||
```
|
||||
true
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `verify address <address> <signature> <message>`
|
||||
*Verify the signature.*
|
||||
|
||||
- `<address>` - ethereum address, 20 bytes long
|
||||
- `<signature>` - message signature, 65 bytes long
|
||||
- `<message>` - message, 32 bytes long
|
||||
|
||||
```
|
||||
ethkey verify address 689268c0ff57a20cd299fa60d3fb374862aff565b20b5f1767906a99e6e09f3ff04ca2b2a5cd22f62941db103c0356df1a8ed20ce322cab2483db67685afd124 c1878cf60417151c766a712653d26ef350c8c75393458b7a9be715f053215af63dfd3b02c2ae65a8677917a8efa3172acb71cb90196e42106953ea0363c5aaf200 bd50b7370c3f96733b31744c6c45079e7ae6c8d299613246d28ebcef507ec987
|
||||
```
|
||||
|
||||
```
|
||||
true
|
||||
```
|
||||
|
||||
--
|
||||
|
||||
#### `recover <address> <known-phrase>`
|
||||
*Try to recover an account given expected address and partial (too short or with invalid words) recovery phrase.*
|
||||
|
||||
- `<address>` - ethereum address, 20 bytes long
|
||||
- `<known-phrase>` - known phrase, can be in a form of `thwarting * creamer`
|
||||
|
||||
```
|
||||
RUST_LOG="info" ethkey recover "00cf3711cbd3a1512570639280758118ba0b2bcb" "thwarting scandal creamer nuzzle asparagus blast crouch trusting anytime elixir frenzied octag"
|
||||
```
|
||||
|
||||
```
|
||||
INFO:ethkey::brain_recover: Invalid word 'octag', looking for potential substitutions.
|
||||
INFO:ethkey::brain_recover: Closest words: ["ocean", "octagon", "octane", "outage", "tag", "acting", "acts", "aorta", "cage", "chug"]
|
||||
INFO:ethkey::brain_recover: Starting to test 7776 possible combinations.
|
||||
|
||||
thwarting scandal creamer nuzzle asparagus blast crouch trusting anytime elixir frenzied octagon
|
||||
secret: 001ce488d50d2f7579dc190c4655f32918d505cee3de63bddc7101bc91c0c2f0
|
||||
public: 4e19a5fdae82596e1485c69b687c9cc52b5078e5b0668ef3ce8543cd90e712cb00df822489bc1f1dcb3623538a54476c7b3def44e1a51dc174e86448b63f42d0
|
||||
address: 00cf3711cbd3a1512570639280758118ba0b2bcb
|
||||
```
|
||||
|
||||
## Parity Ethereum toolchain
|
||||
_This project is a part of the Parity Ethereum toolchain._
|
||||
|
||||
- [evmbin](https://github.com/paritytech/parity-ethereum/blob/master/evmbin/) - EVM implementation for Parity Ethereum.
|
||||
- [ethabi](https://github.com/paritytech/ethabi) - Parity Ethereum function calls encoding.
|
||||
- [ethstore](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethstore) - Parity Ethereum key management.
|
||||
- [ethkey](https://github.com/paritytech/parity-ethereum/blob/master/accounts/ethkey) - Parity Ethereum keys generator.
|
69
crates/accounts/ethkey/src/brain.rs
Normal file
69
crates/accounts/ethkey/src/brain.rs
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use parity_crypto::{
|
||||
publickey::{KeyPair, Secret},
|
||||
Keccak256,
|
||||
};
|
||||
use parity_wordlist;
|
||||
|
||||
/// Simple brainwallet.
|
||||
pub struct Brain(String);
|
||||
|
||||
impl Brain {
|
||||
pub fn new(s: String) -> Self {
|
||||
Brain(s)
|
||||
}
|
||||
|
||||
pub fn validate_phrase(phrase: &str, expected_words: usize) -> Result<(), ::WordlistError> {
|
||||
parity_wordlist::validate_phrase(phrase, expected_words)
|
||||
}
|
||||
|
||||
pub fn generate(&mut self) -> KeyPair {
|
||||
let seed = self.0.clone();
|
||||
let mut secret = seed.into_bytes().keccak256();
|
||||
|
||||
let mut i = 0;
|
||||
loop {
|
||||
secret = secret.keccak256();
|
||||
|
||||
match i > 16384 {
|
||||
false => i += 1,
|
||||
true => {
|
||||
if let Ok(pair) = Secret::import_key(&secret).and_then(KeyPair::from_secret) {
|
||||
if pair.address()[0] == 0 {
|
||||
trace!("Testing: {}, got: {:?}", self.0, pair.address());
|
||||
return pair;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use Brain;
|
||||
|
||||
#[test]
|
||||
fn test_brain() {
|
||||
let words = "this is sparta!".to_owned();
|
||||
let first_keypair = Brain::new(words.clone()).generate();
|
||||
let second_keypair = Brain::new(words.clone()).generate();
|
||||
assert_eq!(first_keypair.secret(), second_keypair.secret());
|
||||
}
|
||||
}
|
69
crates/accounts/ethkey/src/brain_prefix.rs
Normal file
69
crates/accounts/ethkey/src/brain_prefix.rs
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::Brain;
|
||||
use parity_crypto::publickey::{Error, KeyPair};
|
||||
use parity_wordlist as wordlist;
|
||||
|
||||
/// Tries to find brain-seed keypair with address starting with given prefix.
|
||||
pub struct BrainPrefix {
|
||||
prefix: Vec<u8>,
|
||||
iterations: usize,
|
||||
no_of_words: usize,
|
||||
last_phrase: String,
|
||||
}
|
||||
|
||||
impl BrainPrefix {
|
||||
pub fn new(prefix: Vec<u8>, iterations: usize, no_of_words: usize) -> Self {
|
||||
BrainPrefix {
|
||||
prefix,
|
||||
iterations,
|
||||
no_of_words,
|
||||
last_phrase: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn phrase(&self) -> &str {
|
||||
&self.last_phrase
|
||||
}
|
||||
|
||||
pub fn generate(&mut self) -> Result<KeyPair, Error> {
|
||||
for _ in 0..self.iterations {
|
||||
let phrase = wordlist::random_phrase(self.no_of_words);
|
||||
let keypair = Brain::new(phrase.clone()).generate();
|
||||
if keypair.address().as_ref().starts_with(&self.prefix) {
|
||||
self.last_phrase = phrase;
|
||||
return Ok(keypair);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::Custom("Could not find keypair".into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use BrainPrefix;
|
||||
|
||||
#[test]
|
||||
fn prefix_generator() {
|
||||
let prefix = vec![0x00u8];
|
||||
let keypair = BrainPrefix::new(prefix.clone(), usize::max_value(), 12)
|
||||
.generate()
|
||||
.unwrap();
|
||||
assert!(keypair.address().as_bytes().starts_with(&prefix));
|
||||
}
|
||||
}
|
177
crates/accounts/ethkey/src/brain_recover.rs
Normal file
177
crates/accounts/ethkey/src/brain_recover.rs
Normal file
@ -0,0 +1,177 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use edit_distance::edit_distance;
|
||||
use parity_crypto::publickey::Address;
|
||||
use parity_wordlist;
|
||||
|
||||
use super::Brain;
|
||||
|
||||
/// Tries to find a phrase for address, given the number
|
||||
/// of expected words and a partial phrase.
|
||||
///
|
||||
/// Returns `None` if phrase couldn't be found.
|
||||
pub fn brain_recover(
|
||||
address: &Address,
|
||||
known_phrase: &str,
|
||||
expected_words: usize,
|
||||
) -> Option<String> {
|
||||
let it = PhrasesIterator::from_known_phrase(known_phrase, expected_words);
|
||||
for phrase in it {
|
||||
let keypair = Brain::new(phrase.clone()).generate();
|
||||
trace!("Testing: {}, got: {:?}", phrase, keypair.address());
|
||||
if &keypair.address() == address {
|
||||
return Some(phrase);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn generate_substitutions(word: &str) -> Vec<&'static str> {
|
||||
let mut words = parity_wordlist::WORDS
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|w| (edit_distance(w, word), w))
|
||||
.collect::<Vec<_>>();
|
||||
words.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
words.into_iter().map(|pair| pair.1).collect()
|
||||
}
|
||||
|
||||
/// Iterator over possible
|
||||
pub struct PhrasesIterator {
|
||||
words: Vec<Vec<&'static str>>,
|
||||
combinations: u64,
|
||||
indexes: Vec<usize>,
|
||||
has_next: bool,
|
||||
}
|
||||
|
||||
impl PhrasesIterator {
|
||||
pub fn from_known_phrase(known_phrase: &str, expected_words: usize) -> Self {
|
||||
let known_words = parity_wordlist::WORDS
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<HashSet<_>>();
|
||||
let mut words = known_phrase
|
||||
.split(' ')
|
||||
.map(|word| match known_words.get(word) {
|
||||
None => {
|
||||
info!(
|
||||
"Invalid word '{}', looking for potential substitutions.",
|
||||
word
|
||||
);
|
||||
let substitutions = generate_substitutions(word);
|
||||
info!("Closest words: {:?}", &substitutions[..10]);
|
||||
substitutions
|
||||
}
|
||||
Some(word) => vec![*word],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// add missing words
|
||||
if words.len() < expected_words {
|
||||
let to_add = expected_words - words.len();
|
||||
info!("Number of words is insuficcient adding {} more.", to_add);
|
||||
for _ in 0..to_add {
|
||||
words.push(parity_wordlist::WORDS.iter().cloned().collect());
|
||||
}
|
||||
}
|
||||
|
||||
// start searching
|
||||
PhrasesIterator::new(words)
|
||||
}
|
||||
|
||||
pub fn new(words: Vec<Vec<&'static str>>) -> Self {
|
||||
let combinations = words.iter().fold(1u64, |acc, x| acc * x.len() as u64);
|
||||
let indexes = words.iter().map(|_| 0).collect();
|
||||
info!("Starting to test {} possible combinations.", combinations);
|
||||
|
||||
PhrasesIterator {
|
||||
words,
|
||||
combinations,
|
||||
indexes,
|
||||
has_next: combinations > 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn combinations(&self) -> u64 {
|
||||
self.combinations
|
||||
}
|
||||
|
||||
fn current(&self) -> String {
|
||||
let mut s = self.words[0][self.indexes[0]].to_owned();
|
||||
for i in 1..self.indexes.len() {
|
||||
s.push(' ');
|
||||
s.push_str(self.words[i][self.indexes[i]]);
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
fn next_index(&mut self) -> bool {
|
||||
let mut pos = self.indexes.len();
|
||||
while pos > 0 {
|
||||
pos -= 1;
|
||||
self.indexes[pos] += 1;
|
||||
if self.indexes[pos] >= self.words[pos].len() {
|
||||
self.indexes[pos] = 0;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for PhrasesIterator {
|
||||
type Item = String;
|
||||
|
||||
fn next(&mut self) -> Option<String> {
|
||||
if !self.has_next {
|
||||
return None;
|
||||
}
|
||||
|
||||
let phrase = self.current();
|
||||
self.has_next = self.next_index();
|
||||
Some(phrase)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PhrasesIterator;
|
||||
|
||||
#[test]
|
||||
fn should_generate_possible_combinations() {
|
||||
let mut it =
|
||||
PhrasesIterator::new(vec![vec!["1", "2", "3"], vec!["test"], vec!["a", "b", "c"]]);
|
||||
|
||||
assert_eq!(it.combinations(), 9);
|
||||
assert_eq!(it.next(), Some("1 test a".to_owned()));
|
||||
assert_eq!(it.next(), Some("1 test b".to_owned()));
|
||||
assert_eq!(it.next(), Some("1 test c".to_owned()));
|
||||
assert_eq!(it.next(), Some("2 test a".to_owned()));
|
||||
assert_eq!(it.next(), Some("2 test b".to_owned()));
|
||||
assert_eq!(it.next(), Some("2 test c".to_owned()));
|
||||
assert_eq!(it.next(), Some("3 test a".to_owned()));
|
||||
assert_eq!(it.next(), Some("3 test b".to_owned()));
|
||||
assert_eq!(it.next(), Some("3 test c".to_owned()));
|
||||
assert_eq!(it.next(), None);
|
||||
}
|
||||
}
|
88
crates/accounts/ethkey/src/error.rs
Normal file
88
crates/accounts/ethkey/src/error.rs
Normal file
@ -0,0 +1,88 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crypto::Error as CryptoError;
|
||||
use std::{error, fmt};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Crypto error
|
||||
pub enum Error {
|
||||
/// Invalid secret key
|
||||
InvalidSecret,
|
||||
/// Invalid public key
|
||||
InvalidPublic,
|
||||
/// Invalid address
|
||||
InvalidAddress,
|
||||
/// Invalid EC signature
|
||||
InvalidSignature,
|
||||
/// Invalid AES message
|
||||
InvalidMessage,
|
||||
/// IO Error
|
||||
Io(::std::io::Error),
|
||||
/// Custom
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let msg = match *self {
|
||||
Error::InvalidSecret => "Invalid secret".into(),
|
||||
Error::InvalidPublic => "Invalid public".into(),
|
||||
Error::InvalidAddress => "Invalid address".into(),
|
||||
Error::InvalidSignature => "Invalid EC signature".into(),
|
||||
Error::InvalidMessage => "Invalid AES message".into(),
|
||||
Error::Io(ref err) => format!("I/O error: {}", err),
|
||||
Error::Custom(ref s) => s.clone(),
|
||||
};
|
||||
|
||||
f.write_fmt(format_args!("Crypto error ({})", msg))
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
format!("{:?}", &self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for Error {
|
||||
fn into(self) -> String {
|
||||
format!("{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CryptoError> for Error {
|
||||
fn from(e: CryptoError) -> Error {
|
||||
Error::Custom(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::secp256k1::Error> for Error {
|
||||
fn from(e: ::secp256k1::Error) -> Error {
|
||||
match e {
|
||||
::secp256k1::Error::InvalidMessage => Error::InvalidMessage,
|
||||
::secp256k1::Error::InvalidPublicKey => Error::InvalidPublic,
|
||||
::secp256k1::Error::InvalidSecretKey => Error::InvalidSecret,
|
||||
_ => Error::InvalidSignature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::std::io::Error> for Error {
|
||||
fn from(err: ::std::io::Error) -> Error {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
39
crates/accounts/ethkey/src/lib.rs
Normal file
39
crates/accounts/ethkey/src/lib.rs
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// #![warn(missing_docs)]
|
||||
|
||||
extern crate edit_distance;
|
||||
extern crate parity_crypto;
|
||||
extern crate parity_wordlist;
|
||||
extern crate serde;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
mod brain;
|
||||
mod brain_prefix;
|
||||
mod password;
|
||||
mod prefix;
|
||||
|
||||
pub mod brain_recover;
|
||||
|
||||
pub use self::{
|
||||
brain::Brain, brain_prefix::BrainPrefix, parity_wordlist::Error as WordlistError,
|
||||
password::Password, prefix::Prefix,
|
||||
};
|
59
crates/accounts/ethkey/src/password.rs
Normal file
59
crates/accounts/ethkey/src/password.rs
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{fmt, ptr};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Password(String);
|
||||
|
||||
impl fmt::Debug for Password {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Password(******)")
|
||||
}
|
||||
}
|
||||
|
||||
impl Password {
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
// Custom drop impl to zero out memory.
|
||||
impl Drop for Password {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
for byte_ref in self.0.as_mut_vec() {
|
||||
ptr::write_volatile(byte_ref, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Password {
|
||||
fn from(s: String) -> Password {
|
||||
Password(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Password {
|
||||
fn from(s: &'a str) -> Password {
|
||||
Password::from(String::from(s))
|
||||
}
|
||||
}
|
54
crates/accounts/ethkey/src/prefix.rs
Normal file
54
crates/accounts/ethkey/src/prefix.rs
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// OpenEthereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use parity_crypto::publickey::{Error, Generator, KeyPair, Random};
|
||||
|
||||
/// Tries to find keypair with address starting with given prefix.
|
||||
pub struct Prefix {
|
||||
prefix: Vec<u8>,
|
||||
iterations: usize,
|
||||
}
|
||||
|
||||
impl Prefix {
|
||||
pub fn new(prefix: Vec<u8>, iterations: usize) -> Self {
|
||||
Prefix { prefix, iterations }
|
||||
}
|
||||
|
||||
pub fn generate(&mut self) -> Result<KeyPair, Error> {
|
||||
for _ in 0..self.iterations {
|
||||
let keypair = Random.generate();
|
||||
if keypair.address().as_ref().starts_with(&self.prefix) {
|
||||
return Ok(keypair);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::Custom("Could not find keypair".into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use Prefix;
|
||||
|
||||
#[test]
|
||||
fn prefix_generator() {
|
||||
let prefix = vec![0xffu8];
|
||||
let keypair = Prefix::new(prefix.clone(), usize::max_value())
|
||||
.generate()
|
||||
.unwrap();
|
||||
assert!(keypair.address().as_bytes().starts_with(&prefix));
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user