Compare commits
1065 Commits
v1.12.0
...
unbork-mas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5c36a8e2c | ||
|
|
afddce396d | ||
|
|
2b5d289b66 | ||
|
|
09056601d8 | ||
|
|
41abab9e39 | ||
|
|
a4e338f05e | ||
|
|
7cfff30ba3 | ||
|
|
06f1d077d3 | ||
|
|
4939c25341 | ||
|
|
36d67be41b | ||
|
|
19c3c1e205 | ||
|
|
045b9718d5 | ||
|
|
269e80b07e | ||
|
|
9298d2db88 | ||
|
|
98b5930d2d | ||
|
|
ed8fd0ac09 | ||
|
|
73f7e7c087 | ||
|
|
fe0c0b04fe | ||
|
|
0a651f8972 | ||
|
|
d8ea7ac2b0 | ||
|
|
a71f6f91fd | ||
|
|
c10ac4f48f | ||
|
|
e0e45dbc32 | ||
|
|
27654d3022 | ||
|
|
00675c5876 | ||
|
|
27008408a5 | ||
|
|
c11aac249d | ||
|
|
0e3a0a693c | ||
|
|
67a862db9d | ||
|
|
7cf6a63687 | ||
|
|
d8664490da | ||
|
|
c736b04d9b | ||
|
|
115d154392 | ||
|
|
b78d2352ef | ||
|
|
a58e4f0674 | ||
|
|
34b46a2f75 | ||
|
|
fd5078c779 | ||
|
|
86150af2e5 | ||
|
|
69351e8b0f | ||
|
|
3687c34cfc | ||
|
|
1e97148249 | ||
|
|
b6f2bbd417 | ||
|
|
c732039a34 | ||
|
|
caa066dcb0 | ||
|
|
ffb29be7d4 | ||
|
|
3aa874bed2 | ||
|
|
85587d5ef2 | ||
|
|
2eb185c92b | ||
|
|
db273c8733 | ||
|
|
8bda642963 | ||
|
|
349fcdd22d | ||
|
|
1098d148a5 | ||
|
|
deaf10982c | ||
|
|
6a49d13c13 | ||
|
|
4405f18519 | ||
|
|
4461c1fc17 | ||
|
|
0dd173a727 | ||
|
|
85459e1439 | ||
|
|
0750cb0c8f | ||
|
|
cbbfa3eac0 | ||
|
|
6c518fe606 | ||
|
|
bc6569462d | ||
|
|
d09ddac399 | ||
|
|
e85e21c932 | ||
|
|
fc40d68e5b | ||
|
|
5550d8399f | ||
|
|
125fb1ff58 | ||
|
|
682ae838b2 | ||
|
|
68c0ec0815 | ||
|
|
adbbd8cd7b | ||
|
|
a6751d6fc8 | ||
|
|
7270cba25c | ||
|
|
b36c73813c | ||
|
|
50405e29b7 | ||
|
|
d38b88a5a1 | ||
|
|
c9e0b3105b | ||
|
|
d4b81f0e08 | ||
|
|
2613523cb5 | ||
|
|
bdc62f9beb | ||
|
|
5d7d48fc3e | ||
|
|
2262bf3415 | ||
|
|
e015c1116f | ||
|
|
6bb13e8e2b | ||
|
|
2f06c1e854 | ||
|
|
3fef53447f | ||
|
|
94a8b296e4 | ||
|
|
e26fa9e40e | ||
|
|
2f0e63e5ac | ||
|
|
06263b1b35 | ||
|
|
b8cf1636d4 | ||
|
|
153f8da887 | ||
|
|
daf4f72077 | ||
|
|
5534c849b6 | ||
|
|
cc22e0cdf0 | ||
|
|
171430c3f5 | ||
|
|
e517183719 | ||
|
|
af0a3274be | ||
|
|
b88051ec83 | ||
|
|
61932e4710 | ||
|
|
871e55d93e | ||
|
|
42471d7a3e | ||
|
|
caafa93598 | ||
|
|
ea6c16007c | ||
|
|
513276864b | ||
|
|
1a4e4a4fe1 | ||
|
|
7224576fba | ||
|
|
7f5cc02a99 | ||
|
|
d1d9f34e51 | ||
|
|
b6474e9f90 | ||
|
|
64b1cd8aaf | ||
|
|
08fe6a8614 | ||
|
|
61b3d93bb0 | ||
|
|
cc9e2bd9dd | ||
|
|
6a9158bb1b | ||
|
|
70bee977d6 | ||
|
|
b779e469da | ||
|
|
fa581766f5 | ||
|
|
0d4cdb3dbe | ||
|
|
7fd7c1f7dd | ||
|
|
be5df74ed5 | ||
|
|
473ee8fc07 | ||
|
|
7ed52c949e | ||
|
|
d2f00cb54e | ||
|
|
8919c5c0fc | ||
|
|
be3284373f | ||
|
|
5b3e3cd2be | ||
|
|
2ac83e197b | ||
|
|
44a50c9f96 | ||
|
|
47af69c2bc | ||
|
|
603fd898d4 | ||
|
|
e5f5eaebc4 | ||
|
|
74edc93864 | ||
|
|
0e456d9eeb | ||
|
|
6d51c1f5f4 | ||
|
|
ab48ba42f4 | ||
|
|
804afb8faa | ||
|
|
faff03c403 | ||
|
|
1a79f8fe58 | ||
|
|
35b2d07f4b | ||
|
|
eeb22089fd | ||
|
|
14f4228472 | ||
|
|
dd09f7e3fa | ||
|
|
6154f87c33 | ||
|
|
dd4afb9fec | ||
|
|
9ec50080eb | ||
|
|
e96de6489c | ||
|
|
71aa15c98f | ||
|
|
d6e91e2e05 | ||
|
|
e4b8058d5a | ||
|
|
3e896c875a | ||
|
|
43cbcd78ea | ||
|
|
a09a610384 | ||
|
|
905e325cd8 | ||
|
|
86a1f0c394 | ||
|
|
2c67fab0d7 | ||
|
|
fbf6238ae9 | ||
|
|
bc609e852a | ||
|
|
682ee820fa | ||
|
|
9f96e07c1c | ||
|
|
f8820f170c | ||
|
|
45baf21111 | ||
|
|
2e8e35f2ad | ||
|
|
5e07054589 | ||
|
|
bd6bc37eec | ||
|
|
7c7e3a77fc | ||
|
|
ea89f9adf0 | ||
|
|
242b24af9f | ||
|
|
f46c878441 | ||
|
|
c04b8e6d74 | ||
|
|
69f815f6f5 | ||
|
|
fecc8a0f4a | ||
|
|
8c3fc56d7f | ||
|
|
4bdbaab471 | ||
|
|
4253030ef6 | ||
|
|
8d42e115b1 | ||
|
|
ad4fb2c729 | ||
|
|
634d037937 | ||
|
|
a0282fc94f | ||
|
|
1f628d842c | ||
|
|
243cde0f54 | ||
|
|
a13b92524d | ||
|
|
2f6ff492ae | ||
|
|
4f4f9d88d3 | ||
|
|
7362691479 | ||
|
|
ac21f9bfb5 | ||
|
|
0d4c38865e | ||
|
|
938734be3c | ||
|
|
87246f3cba | ||
|
|
5f3c58f1de | ||
|
|
ade7515c81 | ||
|
|
fb08fd334a | ||
|
|
882d1e22f6 | ||
|
|
94579932b1 | ||
|
|
256d4b099c | ||
|
|
b2b0e1da8c | ||
|
|
709e0b3997 | ||
|
|
0e380ddaf7 | ||
|
|
853e0c23f3 | ||
|
|
acd1eaae2c | ||
|
|
e6689fe090 | ||
|
|
1ec7af2612 | ||
|
|
c2dfe7a0c7 | ||
|
|
82b0dec713 | ||
|
|
ad3d8cb12a | ||
|
|
28ccb2bbf8 | ||
|
|
98f504f69f | ||
|
|
2e06fbd409 | ||
|
|
cce879b71b | ||
|
|
81349ff6e5 | ||
|
|
823719b9e1 | ||
|
|
b5902cf595 | ||
|
|
5f95145308 | ||
|
|
0da69e84c0 | ||
|
|
1e9bf2a09e | ||
|
|
74e8d2da97 | ||
|
|
27de7dec65 | ||
|
|
92da96b7d5 | ||
|
|
0a51028819 | ||
|
|
5ffd940b7e | ||
|
|
65e32d47ea | ||
|
|
72f69366de | ||
|
|
fadd9d8b81 | ||
|
|
f437307877 | ||
|
|
71c78bf56d | ||
|
|
e4ecaf89cf | ||
|
|
d3c4466edd | ||
|
|
ef5ac3fb7a | ||
|
|
67422e2a56 | ||
|
|
84b12df09e | ||
|
|
3705acd1a9 | ||
|
|
b179b7b8e7 | ||
|
|
bd91810462 | ||
|
|
b9010f3e87 | ||
|
|
9dcf8aae47 | ||
|
|
34aac1d756 | ||
|
|
f202dfdd47 | ||
|
|
0bbd88bda0 | ||
|
|
1126c6d8a5 | ||
|
|
3caf617dcd | ||
|
|
f447de936c | ||
|
|
70bf94c34e | ||
|
|
c170cc0ab0 | ||
|
|
3c75c64e6b | ||
|
|
c3465cb5ba | ||
|
|
ed4bc7f27b | ||
|
|
cfc7d06cc9 | ||
|
|
0dc09da7db | ||
|
|
7aafad2233 | ||
|
|
8876868bb8 | ||
|
|
ccb76c01d7 | ||
|
|
74995bf8a1 | ||
|
|
cc348a601e | ||
|
|
4458905f26 | ||
|
|
7ee9a6e89f | ||
|
|
35fcf9c52b | ||
|
|
15ff066a24 | ||
|
|
e3bdd84e98 | ||
|
|
a851e39cbe | ||
|
|
9cb8de8703 | ||
|
|
9dfe728909 | ||
|
|
8bd0334168 | ||
|
|
2e0c5e05ba | ||
|
|
eea0acc549 | ||
|
|
6b39e9236c | ||
|
|
1f8f1377e6 | ||
|
|
7bb3fb1481 | ||
|
|
dfb3d46098 | ||
|
|
a83e57666d | ||
|
|
12dcc162d0 | ||
|
|
ab6419ccd8 | ||
|
|
fe0bf325a6 | ||
|
|
0bd03dbc55 | ||
|
|
e63f992fed | ||
|
|
31e63fcf66 | ||
|
|
fde90443a4 | ||
|
|
8c5576b1ac | ||
|
|
6c9f702982 | ||
|
|
c39d00e316 | ||
|
|
a3829178af | ||
|
|
0183c7ad82 | ||
|
|
7481398a24 | ||
|
|
3754a6cc92 | ||
|
|
3b77e0ff4b | ||
|
|
7aba6511b0 | ||
|
|
767b00b0b5 | ||
|
|
fa5019de19 | ||
|
|
8bb8f23bb2 | ||
|
|
304879da20 | ||
|
|
da7469e5c4 | ||
|
|
723b1e36ad | ||
|
|
58a3e2f180 | ||
|
|
1dd898c24e | ||
|
|
f2a6ac17b2 | ||
|
|
738b5a586e | ||
|
|
100c0f47de | ||
|
|
eda9cb7b36 | ||
|
|
5cea7a6230 | ||
|
|
14cc967d19 | ||
|
|
ae47004487 | ||
|
|
6f1fb0c29f | ||
|
|
064f37d6f6 | ||
|
|
38eb8b3e20 | ||
|
|
d9bde37ac3 | ||
|
|
6490d9897a | ||
|
|
f46fe62c5d | ||
|
|
14eb8967be | ||
|
|
bca6c40709 | ||
|
|
04bf1c802f | ||
|
|
8f7fbdfedc | ||
|
|
0444388c74 | ||
|
|
78c102dec5 | ||
|
|
22ac46cbdb | ||
|
|
9a7e6ce6f5 | ||
|
|
de08f3d625 | ||
|
|
0ceac8d00e | ||
|
|
45b88abbde | ||
|
|
6f929a0762 | ||
|
|
4c1b57856f | ||
|
|
eda9c7e36f | ||
|
|
6b3d4d068a | ||
|
|
ac6060a4c6 | ||
|
|
15eb9773f9 | ||
|
|
ab49f228ad | ||
|
|
c611924727 | ||
|
|
ba2dd9385c | ||
|
|
40cac1d0e2 | ||
|
|
95715fdb03 | ||
|
|
cffb7c8604 | ||
|
|
d28adb61bf | ||
|
|
20d3e0ac06 | ||
|
|
3c26ffeb29 | ||
|
|
57308beecf | ||
|
|
f3d18d64bf | ||
|
|
c170fa277c | ||
|
|
b80643b737 | ||
|
|
d5bacfa4de | ||
|
|
eff424cc30 | ||
|
|
758fce71fa | ||
|
|
6c76b813df | ||
|
|
4bd55a064c | ||
|
|
99bbbc0277 | ||
|
|
89cefe240f | ||
|
|
4e1116f9c5 | ||
|
|
ebf9e11af2 | ||
|
|
fa4ade8ecb | ||
|
|
00c21128ef | ||
|
|
b393ad8d29 | ||
|
|
3dc549b3d7 | ||
|
|
e31709db65 | ||
|
|
d35c8f0c25 | ||
|
|
c41105ce80 | ||
|
|
cd490608e3 | ||
|
|
3bebabbd03 | ||
|
|
aadcb88675 | ||
|
|
d8e0807da2 | ||
|
|
6e379b6fc7 | ||
|
|
a90fe84971 | ||
|
|
e73f55365c | ||
|
|
a000acb611 | ||
|
|
899bb88a4b | ||
|
|
588c5480fd | ||
|
|
66e1a6ef49 | ||
|
|
f4d53133f6 | ||
|
|
9a0fa8093c | ||
|
|
9e129efd7b | ||
|
|
a970295956 | ||
|
|
a6d6e8ac41 | ||
|
|
dfa6c5e9c8 | ||
|
|
96bf23f1ea | ||
|
|
7b81cf6362 | ||
|
|
e199319fd6 | ||
|
|
d89d7ebdec | ||
|
|
9b3ceb2137 | ||
|
|
5d5b384efd | ||
|
|
19607d1a10 | ||
|
|
ca473b81cb | ||
|
|
a97d622588 | ||
|
|
35cebc1687 | ||
|
|
679a27a2b3 | ||
|
|
5a1e8a6547 | ||
|
|
b408b3e5fe | ||
|
|
a732ad0364 | ||
|
|
00905f7dc4 | ||
|
|
0b1438c3df | ||
|
|
0a2f33946b | ||
|
|
865e1e9f57 | ||
|
|
db4cf69166 | ||
|
|
28d55218f7 | ||
|
|
dbc27a199f | ||
|
|
1883438964 | ||
|
|
9986a69c25 | ||
|
|
5bae14f9df | ||
|
|
49623bd469 | ||
|
|
170fcd80c6 | ||
|
|
02d77c98f9 | ||
|
|
57d2b552c7 | ||
|
|
9038ba6942 | ||
|
|
51b479e564 | ||
|
|
5a0f468f8c | ||
|
|
45a272c7b9 | ||
|
|
63aaac8100 | ||
|
|
c1f59b98f6 | ||
|
|
821d70240d | ||
|
|
8bca93e82c | ||
|
|
edffacca8f | ||
|
|
26724fc2aa | ||
|
|
32d4d6e616 | ||
|
|
93c541ad56 | ||
|
|
b87b9b4533 | ||
|
|
e47a7c22c4 | ||
|
|
b590cae892 | ||
|
|
3b4ede7444 | ||
|
|
b47cf8fe1d | ||
|
|
b9ca38b735 | ||
|
|
79e340fb12 | ||
|
|
bba3fa9af9 | ||
|
|
7f5e96dc6c | ||
|
|
f4852b8ddc | ||
|
|
ac0ff04460 | ||
|
|
6fb0d0992b | ||
|
|
5d984796af | ||
|
|
034bc4669f | ||
|
|
593e303485 | ||
|
|
95741b1844 | ||
|
|
3c30de219f | ||
|
|
a193bb0c73 | ||
|
|
1bdf8b9b2d | ||
|
|
0c412dcd1f | ||
|
|
286090689a | ||
|
|
886f0e72e5 | ||
|
|
9e3e46671e | ||
|
|
2a1d94bd1d | ||
|
|
efddedc16c | ||
|
|
9d537f5439 | ||
|
|
8321fe2fda | ||
|
|
55a46c3b10 | ||
|
|
fe91d476ba | ||
|
|
4c15d58007 | ||
|
|
beb2954fa4 | ||
|
|
f1c27c286e | ||
|
|
1a79089193 | ||
|
|
f0c5b6765d | ||
|
|
89575aeb4b | ||
|
|
8facf44109 | ||
|
|
85938dda09 | ||
|
|
ac5aa672d3 | ||
|
|
2732fb10d2 | ||
|
|
8a76a814a2 | ||
|
|
ae3b7a0b65 | ||
|
|
2dc33d46b8 | ||
|
|
2ab365f6d8 | ||
|
|
69f5d5ba1f | ||
|
|
449d3f0d87 | ||
|
|
1f50aa7631 | ||
|
|
199e0c9ff5 | ||
|
|
16ce7bf50f | ||
|
|
0b5d8d2b58 | ||
|
|
99e9c0702b | ||
|
|
8fd43c8013 | ||
|
|
8ec638dc5e | ||
|
|
19af9008f1 | ||
|
|
253447a4f5 | ||
|
|
47d76c5f95 | ||
|
|
62affdc9c5 | ||
|
|
06a871136e | ||
|
|
5c67066a05 | ||
|
|
3adf1cecf2 | ||
|
|
eaac53ec38 | ||
|
|
fc380f52ef | ||
|
|
e2778cd59f | ||
|
|
db98cc485e | ||
|
|
2e947b7a00 | ||
|
|
bc0b87ca19 | ||
|
|
cd0770ea68 | ||
|
|
99dc3fe118 | ||
|
|
765f2904d8 | ||
|
|
a8a87586c1 | ||
|
|
6b0de79935 | ||
|
|
542c861b4f | ||
|
|
98eaa57e6f | ||
|
|
2dc74770a7 | ||
|
|
c89a3da7d9 | ||
|
|
4c8d92d303 | ||
|
|
a5a4fa7032 | ||
|
|
819a4977e8 | ||
|
|
19d9977641 | ||
|
|
6a724b94db | ||
|
|
78a3c32ef4 | ||
|
|
f55a10b64d | ||
|
|
1c488298c8 | ||
|
|
0e93da3197 | ||
|
|
830f3c764c | ||
|
|
e5d5e09faa | ||
|
|
2e2e89c2fb | ||
|
|
c66ca8bf7a | ||
|
|
d4f25b4dcf | ||
|
|
566754c74a | ||
|
|
9ee6809ff4 | ||
|
|
18e154eaa2 | ||
|
|
7596db5f48 | ||
|
|
89ccc680da | ||
|
|
1485814f89 | ||
|
|
29b73555ae | ||
|
|
407f779c8e | ||
|
|
1335ba5f28 | ||
|
|
a608c0ac84 | ||
|
|
43ba7d65a8 | ||
|
|
065f82a8cc | ||
|
|
7280a5b31a | ||
|
|
ae4ea047e3 | ||
|
|
6e235c0833 | ||
|
|
5c2de7fcbe | ||
|
|
2e8b1187aa | ||
|
|
a162091e8f | ||
|
|
daa2e5d6a6 | ||
|
|
4f825318ea | ||
|
|
2d08c99009 | ||
|
|
9e018ce3a5 | ||
|
|
d0edc5af4a | ||
|
|
1010a79c7c | ||
|
|
cfff3cbbf1 | ||
|
|
f29520ffdf | ||
|
|
e7fa158086 | ||
|
|
07b17f991b | ||
|
|
877d09443d | ||
|
|
e3eeb64c94 | ||
|
|
99eb49e601 | ||
|
|
0b471c312a | ||
|
|
2365d77968 | ||
|
|
33c94ef083 | ||
|
|
c053eb71b6 | ||
|
|
76a5474b32 | ||
|
|
09e0208029 | ||
|
|
b5b70033e2 | ||
|
|
d2e3cb894b | ||
|
|
8d0391806f | ||
|
|
904a278054 | ||
|
|
f469470aff | ||
|
|
cca94792a4 | ||
|
|
577be37e0e | ||
|
|
8c2d455ccd | ||
|
|
d3452a22cc | ||
|
|
7124057bad | ||
|
|
9258a44b8f | ||
|
|
3fd568855f | ||
|
|
0cc192bd3a | ||
|
|
435bed5da0 | ||
|
|
5a9dda64ce | ||
|
|
952b343cb3 | ||
|
|
cd58897f18 | ||
|
|
54a400ee71 | ||
|
|
4410c1416a | ||
|
|
a18b845ecd | ||
|
|
c18c5c3d92 | ||
|
|
553bafc127 | ||
|
|
05bbc56677 | ||
|
|
02766d349a | ||
|
|
5b22a472d6 | ||
|
|
edc864f9ba | ||
|
|
f1794ba278 | ||
|
|
0f74aad641 | ||
|
|
b2ced97ac4 | ||
|
|
81fd1b3cf9 | ||
|
|
17c2b3c194 | ||
|
|
a3ca1b2818 | ||
|
|
e206d3f897 | ||
|
|
d98d70f670 | ||
|
|
fff843cfaf | ||
|
|
1048e2d6a3 | ||
|
|
5a45e7a631 | ||
|
|
77c4bbcaa5 | ||
|
|
a113497dd7 | ||
|
|
55b483d82a | ||
|
|
69576df254 | ||
|
|
b8d44ed98b | ||
|
|
3dc071e036 | ||
|
|
2e13b01046 | ||
|
|
70fd0b635e | ||
|
|
f04e5bde74 | ||
|
|
6e488c2449 | ||
|
|
5fb8ebc9ec | ||
|
|
dd0d0a2522 | ||
|
|
f2b509d8a1 | ||
|
|
fa0df76f3c | ||
|
|
ab0eb46a84 | ||
|
|
28e7371701 | ||
|
|
61b844f2b2 | ||
|
|
e0c7ad01ab | ||
|
|
34dcd74935 | ||
|
|
bbc5db8405 | ||
|
|
248dc50ee8 | ||
|
|
63979bc9cc | ||
|
|
58297e339b | ||
|
|
71817f318e | ||
|
|
5b57727d6d | ||
|
|
1e28e0bb03 | ||
|
|
333dd956bf | ||
|
|
2a2013014c | ||
|
|
bdf5e388ca | ||
|
|
d76efbb9be | ||
|
|
eec37e3b71 | ||
|
|
63127f5443 | ||
|
|
d468c333a7 | ||
|
|
5ff929c22f | ||
|
|
3cfcd252db | ||
|
|
104dbf7821 | ||
|
|
d6cea4832a | ||
|
|
347fecd881 | ||
|
|
e9f59b5d5e | ||
|
|
6489a0dd1f | ||
|
|
146e8d999c | ||
|
|
525db7b2c5 | ||
|
|
ad16f11f84 | ||
|
|
661bd45188 | ||
|
|
460cc1673e | ||
|
|
14a1e96b68 | ||
|
|
a73748258f | ||
|
|
77cb21da2c | ||
|
|
c8a2202028 | ||
|
|
8b78d6a7a0 | ||
|
|
5bf8769fb0 | ||
|
|
a75a2d6db6 | ||
|
|
db7895d3b6 | ||
|
|
fcc7ae162d | ||
|
|
b9504e4966 | ||
|
|
2814ee0547 | ||
|
|
984f82629c | ||
|
|
c5b7cfa9c3 | ||
|
|
2391fbc676 | ||
|
|
24d46224c1 | ||
|
|
e803ef09ad | ||
|
|
fa8d39807d | ||
|
|
f265cc24b4 | ||
|
|
49b2c5f43c | ||
|
|
ce5a4809fd | ||
|
|
2f4833b828 | ||
|
|
326fa00759 | ||
|
|
e38b9f1830 | ||
|
|
f7dde2a96c | ||
|
|
b77a9b127c | ||
|
|
7ea860d665 | ||
|
|
470dba8fc1 | ||
|
|
4d9f3cd5d7 | ||
|
|
f20b334f21 | ||
|
|
97ae32441e | ||
|
|
51b5ad3da3 | ||
|
|
e91cdb49be | ||
|
|
b1cec853be | ||
|
|
a3be38127c | ||
|
|
f4ac548619 | ||
|
|
285202aae2 | ||
|
|
bc42e88415 | ||
|
|
447945e438 | ||
|
|
ea2e66a58e | ||
|
|
233db64cc1 | ||
|
|
2d7dba024d | ||
|
|
4cbca5178a | ||
|
|
abe3fca1de | ||
|
|
58ae1df684 | ||
|
|
d8c6ae054c | ||
|
|
f7b62e5506 | ||
|
|
96b75033c0 | ||
|
|
300df874d7 | ||
|
|
a8617c6d4d | ||
|
|
ab04aeb855 | ||
|
|
43e6a3c196 | ||
|
|
3853f50082 | ||
|
|
062598bb40 | ||
|
|
54f35c68be | ||
|
|
6c6982163b | ||
|
|
ffc6a0f36e | ||
|
|
a6a0ae45b6 | ||
|
|
c1d5a012ea | ||
|
|
cd29535672 | ||
|
|
4d3c0d41f4 | ||
|
|
ec1a0502bf | ||
|
|
d10a2f6ab7 | ||
|
|
da55b23d21 | ||
|
|
d782dc2341 | ||
|
|
20d5256e40 | ||
|
|
b85c86022e | ||
|
|
f62502e123 | ||
|
|
1f11d2d340 | ||
|
|
1b1611b8d0 | ||
|
|
aeb0abf80a | ||
|
|
3f907d6a6f | ||
|
|
667966c5c1 | ||
|
|
00c63830e4 | ||
|
|
8b99ad4602 | ||
|
|
a5544d35f6 | ||
|
|
2e478aab98 | ||
|
|
4632b7b31e | ||
|
|
509a64ffb9 | ||
|
|
425cb6f65d | ||
|
|
f62c58f8de | ||
|
|
31b566f7a8 | ||
|
|
ed5da55149 | ||
|
|
660cbe4117 | ||
|
|
78c8e1060c | ||
|
|
1f30cae4ad | ||
|
|
1cb3b6aee4 | ||
|
|
2f66d7c47c | ||
|
|
0d45d72d70 | ||
|
|
d2c0bed9d5 | ||
|
|
0004c6b229 | ||
|
|
eeb5dc3ccf | ||
|
|
13d1d425ac | ||
|
|
a6deb2d994 | ||
|
|
7776a3214a | ||
|
|
8976a0c97a | ||
|
|
2c007cfed7 | ||
|
|
5e43ed0d72 | ||
|
|
8afbcf4713 | ||
|
|
6505297456 | ||
|
|
6b1e4f4211 | ||
|
|
db9afae2ea | ||
|
|
fa6107c85e | ||
|
|
08326794e8 | ||
|
|
4e1e37323d | ||
|
|
052355f5e2 | ||
|
|
95b0555c84 | ||
|
|
a8a9c8e4b0 | ||
|
|
bc6d184872 | ||
|
|
7963c4e808 | ||
|
|
2091ebdf5e | ||
|
|
339a4cf056 | ||
|
|
07dec7a11c | ||
|
|
705a51e566 | ||
|
|
c39cbc1a78 | ||
|
|
7b6ff527d5 | ||
|
|
a408e37fa1 | ||
|
|
966e50bddb | ||
|
|
22dcb7a77b | ||
|
|
1f9d672df1 | ||
|
|
c5ff839fb2 | ||
|
|
0ded110b80 | ||
|
|
1f6e63900d | ||
|
|
f988b2332e | ||
|
|
b9450bfcca | ||
|
|
46c850a941 | ||
|
|
37a2d919b0 | ||
|
|
3dc45a3e1d | ||
|
|
dc34fe8291 | ||
|
|
73f5bcb75b | ||
|
|
a081130081 | ||
|
|
614804b33c | ||
|
|
b85c183ea7 | ||
|
|
adb9b319c9 | ||
|
|
2b7bc2c36b | ||
|
|
40219109b0 | ||
|
|
4de89e92e4 | ||
|
|
4985d83b8f | ||
|
|
f6f64cc43d | ||
|
|
3d297fc2d7 | ||
|
|
c3742a9ae0 | ||
|
|
1fa3362ea7 | ||
|
|
c2cfe35f12 | ||
|
|
d051ea5e89 | ||
|
|
323542af50 | ||
|
|
82ec555d70 | ||
|
|
f1b2ec0833 | ||
|
|
d135bafdcb | ||
|
|
83f3fc2e80 | ||
|
|
03c2176a1d | ||
|
|
4773dcbc81 | ||
|
|
545f4c5547 | ||
|
|
5b9cbe30f8 | ||
|
|
5c6f4b9f0d | ||
|
|
7ed5bc021a | ||
|
|
30d5d7c1b3 | ||
|
|
41a0ad9f03 | ||
|
|
4b748b7a27 | ||
|
|
ef76afad35 | ||
|
|
e9f78db79d | ||
|
|
90d5bd85bc | ||
|
|
3f40e65c48 | ||
|
|
c53b0fef2a | ||
|
|
d8a351b58f | ||
|
|
52234eb172 | ||
|
|
217719347d | ||
|
|
9a9db3d265 | ||
|
|
16cd1a7561 | ||
|
|
4fa3db49a1 | ||
|
|
48fdb79de5 | ||
|
|
65a17c00c7 | ||
|
|
909dd4a109 | ||
|
|
ee654626ad | ||
|
|
8514d665ee | ||
|
|
86bc2cdf33 | ||
|
|
636c64caa9 | ||
|
|
d9fbb71d63 | ||
|
|
b9b99a12e5 | ||
|
|
eb7438997b | ||
|
|
8b6cf128af | ||
|
|
8d38b1fe62 | ||
|
|
43df612268 | ||
|
|
766272ff8c | ||
|
|
7371b38171 | ||
|
|
12ef276a7d | ||
|
|
1efd12f695 | ||
|
|
5cf53f51ac | ||
|
|
83886e40b6 | ||
|
|
a7842c9cae | ||
|
|
a8d7201ec5 | ||
|
|
c60f7dd08d | ||
|
|
2e02c1ffd9 | ||
|
|
2f77299136 | ||
|
|
25733a4aad | ||
|
|
eff7c3bda0 | ||
|
|
f260a9edb9 | ||
|
|
28857080d7 | ||
|
|
0acc0a1f86 | ||
|
|
53f3c2ae65 | ||
|
|
5b159498bb | ||
|
|
41ee96fdfe | ||
|
|
b8adb4cb0c | ||
|
|
fe24d22a62 | ||
|
|
f174ddba7a | ||
|
|
d4e345c7d4 | ||
|
|
3a662d4735 | ||
|
|
3ff6b3c31e | ||
|
|
5ca7fb82d6 | ||
|
|
6aa88ccdd2 | ||
|
|
cde462c6bf | ||
|
|
9bbb9df185 | ||
|
|
6b98d18789 | ||
|
|
5e0eb62a8e | ||
|
|
6dc9cdf15b | ||
|
|
56d2366699 | ||
|
|
0ba2d3cfa4 | ||
|
|
1a2135044c | ||
|
|
45b198dd3a | ||
|
|
9b46986edc | ||
|
|
60ec41ce73 | ||
|
|
feb8f416ac | ||
|
|
d1f6735171 | ||
|
|
eb6cbe37e1 | ||
|
|
2f4dbb4f90 | ||
|
|
4abc412348 | ||
|
|
e3f3e01504 | ||
|
|
f0f8703bf2 | ||
|
|
5c7136adb4 | ||
|
|
52219ced8b | ||
|
|
76d4ac1acb | ||
|
|
4af98d4ee6 | ||
|
|
00fead91c4 | ||
|
|
bce5c46739 | ||
|
|
0c6bbeb423 | ||
|
|
ab3762b2d9 | ||
|
|
c31f9cf23a | ||
|
|
16946d218a | ||
|
|
4c7053baf1 | ||
|
|
8d2492982b | ||
|
|
b8d38e76ef | ||
|
|
0b4b299099 | ||
|
|
55c5f5964d | ||
|
|
bbee0e7e50 | ||
|
|
7c0d90c8c9 | ||
|
|
9f4a528793 | ||
|
|
f56ee7d9c5 | ||
|
|
b3024e8fe6 | ||
|
|
5976e58415 | ||
|
|
7dea9c10cd | ||
|
|
950ccddfc8 | ||
|
|
649deb69f3 | ||
|
|
1aa5520d75 | ||
|
|
32fde3f838 | ||
|
|
a3e35414b7 | ||
|
|
386cba15b5 | ||
|
|
a16d757cd4 | ||
|
|
e0b119884c | ||
|
|
ab28680e66 | ||
|
|
05a8b887a9 | ||
|
|
f1801a9fed | ||
|
|
509cd428e9 | ||
|
|
68855216c9 | ||
|
|
2a6beb6a39 | ||
|
|
68860063fb | ||
|
|
e91b21ce2b | ||
|
|
be65b47645 | ||
|
|
0ce331f56a | ||
|
|
80b76a9527 | ||
|
|
8f8ef2bc0c | ||
|
|
35f7f3d015 | ||
|
|
6ddb92cac3 | ||
|
|
e2507a17e8 | ||
|
|
503f1f7ada | ||
|
|
5e89ff4d6b | ||
|
|
86d7f5aeee | ||
|
|
8d1db1601d | ||
|
|
d9a8b0ff71 | ||
|
|
9c216bd6cb | ||
|
|
67979022aa | ||
|
|
10d9f9377b | ||
|
|
7ec60d5f0c | ||
|
|
e13fa32cea | ||
|
|
0d772b9f09 | ||
|
|
6d2bcb911a | ||
|
|
eeebb07c73 | ||
|
|
d14c07d91e | ||
|
|
857476753d | ||
|
|
60070fe5c6 | ||
|
|
5c30541c2a | ||
|
|
bb148dd342 | ||
|
|
57cdbaef30 | ||
|
|
df544350bc | ||
|
|
6e934f40f9 | ||
|
|
8224bb9218 | ||
|
|
d04bde0a20 | ||
|
|
ff97b4cc6a | ||
|
|
7de748d3f6 | ||
|
|
9d744f0ca8 | ||
|
|
f404a2d0f1 | ||
|
|
7c95ebd63d | ||
|
|
2fd77a6a7e | ||
|
|
852be575e1 | ||
|
|
3ca92f70e5 | ||
|
|
4e9775668e | ||
|
|
817553cc28 | ||
|
|
43a1a48ee2 | ||
|
|
5a4eba6886 | ||
|
|
95cc7bf4f8 | ||
|
|
8f2ae29b8f | ||
|
|
d9556533c3 | ||
|
|
57268f7e6c | ||
|
|
0f4b21feac | ||
|
|
393d4db18c | ||
|
|
1662228ac6 | ||
|
|
37b952a4a2 | ||
|
|
2274a03e33 | ||
|
|
a196f3e8a2 | ||
|
|
7a1fba1a02 | ||
|
|
88f3d61468 | ||
|
|
a46f4173cd | ||
|
|
7f756dc118 | ||
|
|
e86ad52640 | ||
|
|
d4d88f9bce | ||
|
|
988d84aa7c | ||
|
|
b058cf454b | ||
|
|
99e000cb13 | ||
|
|
d233b6b23a | ||
|
|
00408f7479 | ||
|
|
34d5072159 | ||
|
|
47b9f1b4ae | ||
|
|
13c0305106 | ||
|
|
60ecf48dd4 | ||
|
|
c40ab6af72 | ||
|
|
e1fe6bc846 | ||
|
|
517ac886d4 | ||
|
|
1e069cf802 | ||
|
|
5d035043ea | ||
|
|
10a136a4f9 | ||
|
|
4f0d8f0d15 | ||
|
|
714f75943b | ||
|
|
040a4a543b | ||
|
|
80b7bfe70d | ||
|
|
a426999fc9 | ||
|
|
0b1f97e151 | ||
|
|
cecd22143b | ||
|
|
7a565fa4fe | ||
|
|
645b0db98e | ||
|
|
4b06e4f25e | ||
|
|
aecf3f9579 | ||
|
|
e1fd3d67e5 | ||
|
|
5c9cbc218a | ||
|
|
af8b138c1a | ||
|
|
c2db667c8f | ||
|
|
c866dfdc78 | ||
|
|
cbf2579691 | ||
|
|
ea782809f7 | ||
|
|
ab0e0f3517 | ||
|
|
83d7f426d1 | ||
|
|
863f6dac19 | ||
|
|
59f7b289c3 | ||
|
|
6ca3ef9a7b | ||
|
|
8bbb16b70e | ||
|
|
85b8d1c06c | ||
|
|
f5d3d486e4 | ||
|
|
eed7983c7c | ||
|
|
c7b099b2ea | ||
|
|
d73eb87979 | ||
|
|
900591299f | ||
|
|
d7ea278fe3 | ||
|
|
4b90c4488d | ||
|
|
b4bc9b0db6 | ||
|
|
80441779d4 | ||
|
|
2754b197c9 | ||
|
|
942ba4ddaa | ||
|
|
699243f8ae | ||
|
|
5520cd97a1 | ||
|
|
fd5d2ef0a6 | ||
|
|
713fc8bbe6 | ||
|
|
6d2aeb43d5 | ||
|
|
8c288b528d | ||
|
|
1affc1c08d | ||
|
|
154b016b6c | ||
|
|
84b05d4f34 | ||
|
|
b1ef0bfe03 | ||
|
|
9a167c45d1 | ||
|
|
ceca4578ca | ||
|
|
091c25d983 | ||
|
|
50ecb16de0 | ||
|
|
87e510d963 | ||
|
|
a848212709 | ||
|
|
4367ab499f | ||
|
|
760fd0c79b | ||
|
|
cd3b8c3d78 | ||
|
|
4544dc5f32 | ||
|
|
311b742c84 | ||
|
|
f0b5af74a3 | ||
|
|
e4660a1181 | ||
|
|
a71b9b9ffa | ||
|
|
168d0cc3b3 | ||
|
|
5d75123cb7 | ||
|
|
289c6c3b15 | ||
|
|
46ec972c9c | ||
|
|
36ca59f1ec | ||
|
|
d40a255e97 | ||
|
|
c375936e81 | ||
|
|
604da5c84b | ||
|
|
9cf9fae668 | ||
|
|
93ecd77d77 | ||
|
|
7823ff6d06 | ||
|
|
acc2a2ac61 | ||
|
|
6f08c2f3f1 | ||
|
|
8bbaf882a6 | ||
|
|
f3314bb6df | ||
|
|
5ac4da3653 | ||
|
|
174d267f48 | ||
|
|
281e8cd5ab | ||
|
|
5c51ef8527 | ||
|
|
99eb0b52aa | ||
|
|
fbe432fa15 | ||
|
|
0783cb7d91 | ||
|
|
950d5643b1 | ||
|
|
4cf708d30b | ||
|
|
b8ee2877c5 | ||
|
|
8eb0c2de76 | ||
|
|
0e5d2c7c53 | ||
|
|
c537ace249 | ||
|
|
380fb4e249 | ||
|
|
78f7a6b7f2 | ||
|
|
a7b2106edf | ||
|
|
c7c84ca16c | ||
|
|
15bd21f3c8 | ||
|
|
2372fb2781 | ||
|
|
45a3ab42aa | ||
|
|
ac86547b01 | ||
|
|
008086f935 | ||
|
|
495692c9db | ||
|
|
1f9b69b36d | ||
|
|
cc2ab421e4 | ||
|
|
d4961881d7 | ||
|
|
61dcf76230 | ||
|
|
8013a494fe | ||
|
|
560dceb58e | ||
|
|
d789c68b66 | ||
|
|
188817468e | ||
|
|
c57b3436f4 | ||
|
|
13166210c8 | ||
|
|
1816cdc9fd | ||
|
|
db9a178ad2 | ||
|
|
9358b62fcb | ||
|
|
6c732766c8 | ||
|
|
690249de7b |
7
.github/CODEOWNERS
vendored
7
.github/CODEOWNERS
vendored
@@ -4,13 +4,18 @@
|
|||||||
accounts/usbwallet @karalabe
|
accounts/usbwallet @karalabe
|
||||||
accounts/scwallet @gballet
|
accounts/scwallet @gballet
|
||||||
accounts/abi @gballet @MariusVanDerWijden
|
accounts/abi @gballet @MariusVanDerWijden
|
||||||
|
beacon/engine @lightclient
|
||||||
cmd/clef @holiman
|
cmd/clef @holiman
|
||||||
|
cmd/evm @holiman @MariusVanDerWijden @lightclient
|
||||||
consensus @karalabe
|
consensus @karalabe
|
||||||
core/ @karalabe @holiman @rjl493456442
|
core/ @karalabe @holiman @rjl493456442
|
||||||
eth/ @karalabe @holiman @rjl493456442
|
eth/ @karalabe @holiman @rjl493456442
|
||||||
eth/catalyst/ @gballet
|
eth/catalyst/ @gballet @lightclient
|
||||||
eth/tracers/ @s1na
|
eth/tracers/ @s1na
|
||||||
|
core/tracing/ @s1na
|
||||||
graphql/ @s1na
|
graphql/ @s1na
|
||||||
|
internal/ethapi @lightclient
|
||||||
|
internal/era @lightclient
|
||||||
les/ @zsfelfoldi @rjl493456442
|
les/ @zsfelfoldi @rjl493456442
|
||||||
light/ @zsfelfoldi @rjl493456442
|
light/ @zsfelfoldi @rjl493456442
|
||||||
node/ @fjl
|
node/ @fjl
|
||||||
|
|||||||
24
.github/workflows/go.yml
vendored
Normal file
24
.github/workflows/go.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: i386 linux tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: 1.21.4
|
||||||
|
cache: false
|
||||||
|
- name: Run tests
|
||||||
|
run: go test -short ./...
|
||||||
|
env:
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: 386
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -47,4 +47,6 @@ profile.cov
|
|||||||
/dashboard/assets/package-lock.json
|
/dashboard/assets/package-lock.json
|
||||||
|
|
||||||
**/yarn-error.log
|
**/yarn-error.log
|
||||||
logs/
|
logs/
|
||||||
|
|
||||||
|
tests/spec-tests/
|
||||||
|
|||||||
@@ -6,13 +6,10 @@ run:
|
|||||||
# default is true. Enables skipping of directories:
|
# default is true. Enables skipping of directories:
|
||||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||||
skip-dirs-use-default: true
|
skip-dirs-use-default: true
|
||||||
skip-files:
|
|
||||||
- core/genesis_alloc.go
|
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- goconst
|
|
||||||
- goimports
|
- goimports
|
||||||
- gosimple
|
- gosimple
|
||||||
- govet
|
- govet
|
||||||
@@ -26,7 +23,10 @@ linters:
|
|||||||
- durationcheck
|
- durationcheck
|
||||||
- exportloopref
|
- exportloopref
|
||||||
- whitespace
|
- whitespace
|
||||||
|
- revive # only certain checks enabled
|
||||||
|
|
||||||
|
### linters we tried and will not be using:
|
||||||
|
###
|
||||||
# - structcheck # lots of false positives
|
# - structcheck # lots of false positives
|
||||||
# - errcheck #lot of false positives
|
# - errcheck #lot of false positives
|
||||||
# - contextcheck
|
# - contextcheck
|
||||||
@@ -39,16 +39,27 @@ linters:
|
|||||||
linters-settings:
|
linters-settings:
|
||||||
gofmt:
|
gofmt:
|
||||||
simplify: true
|
simplify: true
|
||||||
goconst:
|
revive:
|
||||||
min-len: 3 # minimum length of string constant
|
enable-all-rules: false
|
||||||
min-occurrences: 6 # minimum number of occurrences
|
# here we enable specific useful rules
|
||||||
|
# see https://golangci-lint.run/usage/linters/#revive for supported rules
|
||||||
|
rules:
|
||||||
|
- name: receiver-naming
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
exclude: [""]
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
|
exclude-files:
|
||||||
|
- core/genesis_alloc.go
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
- path: crypto/bn256/cloudflare/optate.go
|
- path: crypto/bn256/cloudflare/optate.go
|
||||||
linters:
|
linters:
|
||||||
- deadcode
|
- deadcode
|
||||||
- staticcheck
|
- staticcheck
|
||||||
|
- path: crypto/bn256/
|
||||||
|
linters:
|
||||||
|
- revive
|
||||||
- path: internal/build/pgp.go
|
- path: internal/build/pgp.go
|
||||||
text: 'SA1019: "golang.org/x/crypto/openpgp" is deprecated: this package is unmaintained except for security fixes.'
|
text: 'SA1019: "golang.org/x/crypto/openpgp" is deprecated: this package is unmaintained except for security fixes.'
|
||||||
- path: core/vm/contracts.go
|
- path: core/vm/contracts.go
|
||||||
|
|||||||
1
.mailmap
1
.mailmap
@@ -56,7 +56,6 @@ Diederik Loerakker <proto@protolambda.com>
|
|||||||
Dimitry Khokhlov <winsvega@mail.ru>
|
Dimitry Khokhlov <winsvega@mail.ru>
|
||||||
|
|
||||||
Domino Valdano <dominoplural@gmail.com>
|
Domino Valdano <dominoplural@gmail.com>
|
||||||
Domino Valdano <dominoplural@gmail.com> <jeff@okcupid.com>
|
|
||||||
|
|
||||||
Edgar Aroutiounian <edgar.factorial@gmail.com>
|
Edgar Aroutiounian <edgar.factorial@gmail.com>
|
||||||
|
|
||||||
|
|||||||
99
.travis.yml
99
.travis.yml
@@ -9,26 +9,14 @@ jobs:
|
|||||||
- azure-osx
|
- azure-osx
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# This builder only tests code linters on latest version of Go
|
|
||||||
- stage: lint
|
|
||||||
os: linux
|
|
||||||
dist: bionic
|
|
||||||
go: 1.20.x
|
|
||||||
env:
|
|
||||||
- lint
|
|
||||||
git:
|
|
||||||
submodules: false # avoid cloning ethereum/tests
|
|
||||||
script:
|
|
||||||
- go run build/ci.go lint
|
|
||||||
|
|
||||||
# These builders create the Docker sub-images for multi-arch push and each
|
# These builders create the Docker sub-images for multi-arch push and each
|
||||||
# will attempt to push the multi-arch image if they are the last builder
|
# will attempt to push the multi-arch image if they are the last builder
|
||||||
- stage: build
|
- stage: build
|
||||||
if: type = push
|
if: type = push
|
||||||
os: linux
|
os: linux
|
||||||
arch: amd64
|
arch: amd64
|
||||||
dist: bionic
|
dist: noble
|
||||||
go: 1.20.x
|
go: 1.22.x
|
||||||
env:
|
env:
|
||||||
- docker
|
- docker
|
||||||
services:
|
services:
|
||||||
@@ -44,8 +32,8 @@ jobs:
|
|||||||
if: type = push
|
if: type = push
|
||||||
os: linux
|
os: linux
|
||||||
arch: arm64
|
arch: arm64
|
||||||
dist: bionic
|
dist: noble
|
||||||
go: 1.20.x
|
go: 1.22.x
|
||||||
env:
|
env:
|
||||||
- docker
|
- docker
|
||||||
services:
|
services:
|
||||||
@@ -61,22 +49,20 @@ jobs:
|
|||||||
- stage: build
|
- stage: build
|
||||||
if: type = push
|
if: type = push
|
||||||
os: linux
|
os: linux
|
||||||
dist: bionic
|
dist: noble
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.20.x
|
go: 1.22.x
|
||||||
env:
|
env:
|
||||||
- azure-linux
|
- azure-linux
|
||||||
- GO111MODULE=on
|
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- gcc-multilib
|
|
||||||
script:
|
script:
|
||||||
# Build for the primary platforms that Trusty can manage
|
# build amd64
|
||||||
- go run build/ci.go install -dlgo
|
- go run build/ci.go install -dlgo
|
||||||
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
||||||
|
|
||||||
|
# build 386
|
||||||
|
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends install gcc-multilib
|
||||||
- go run build/ci.go install -dlgo -arch 386
|
- go run build/ci.go install -dlgo -arch 386
|
||||||
- go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
- go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
||||||
|
|
||||||
@@ -97,67 +83,48 @@ jobs:
|
|||||||
- stage: build
|
- stage: build
|
||||||
if: type = push
|
if: type = push
|
||||||
os: osx
|
os: osx
|
||||||
go: 1.20.x
|
osx_image: xcode14.2
|
||||||
|
go: 1.22.x
|
||||||
env:
|
env:
|
||||||
- azure-osx
|
- azure-osx
|
||||||
- GO111MODULE=on
|
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go install -dlgo
|
- go run build/ci.go install -dlgo
|
||||||
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
||||||
|
- go run build/ci.go install -dlgo -arch arm64
|
||||||
|
- go run build/ci.go archive -arch arm64 -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
|
||||||
|
|
||||||
# These builders run the tests
|
# These builders run the tests
|
||||||
- stage: build
|
- stage: build
|
||||||
|
if: type = push
|
||||||
os: linux
|
os: linux
|
||||||
arch: amd64
|
arch: amd64
|
||||||
dist: bionic
|
dist: noble
|
||||||
go: 1.20.x
|
go: 1.22.x
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go test $TEST_PACKAGES
|
- travis_wait 45 go run build/ci.go test $TEST_PACKAGES
|
||||||
|
|
||||||
- stage: build
|
- stage: build
|
||||||
if: type = pull_request
|
if: type = push
|
||||||
os: linux
|
os: linux
|
||||||
arch: arm64
|
dist: noble
|
||||||
dist: bionic
|
go: 1.21.x
|
||||||
go: 1.19.x
|
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go test $TEST_PACKAGES
|
- travis_wait 45 go run build/ci.go test $TEST_PACKAGES
|
||||||
|
|
||||||
- stage: build
|
|
||||||
os: linux
|
|
||||||
dist: bionic
|
|
||||||
go: 1.19.x
|
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
script:
|
|
||||||
- go run build/ci.go test $TEST_PACKAGES
|
|
||||||
|
|
||||||
# This builder does the Ubuntu PPA nightly uploads
|
# This builder does the Ubuntu PPA nightly uploads
|
||||||
- stage: build
|
- stage: build
|
||||||
if: type = cron || (type = push && tag ~= /^v[0-9]/)
|
if: type = cron || (type = push && tag ~= /^v[0-9]/)
|
||||||
os: linux
|
os: linux
|
||||||
dist: bionic
|
dist: noble
|
||||||
go: 1.20.x
|
go: 1.22.x
|
||||||
env:
|
env:
|
||||||
- ubuntu-ppa
|
- ubuntu-ppa
|
||||||
- GO111MODULE=on
|
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
addons:
|
before_install:
|
||||||
apt:
|
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends install devscripts debhelper dput fakeroot
|
||||||
packages:
|
|
||||||
- devscripts
|
|
||||||
- debhelper
|
|
||||||
- dput
|
|
||||||
- fakeroot
|
|
||||||
- python-bzrlib
|
|
||||||
- python-paramiko
|
|
||||||
script:
|
script:
|
||||||
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
|
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
|
||||||
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
||||||
@@ -166,11 +133,10 @@ jobs:
|
|||||||
- stage: build
|
- stage: build
|
||||||
if: type = cron
|
if: type = cron
|
||||||
os: linux
|
os: linux
|
||||||
dist: bionic
|
dist: noble
|
||||||
go: 1.20.x
|
go: 1.22.x
|
||||||
env:
|
env:
|
||||||
- azure-purge
|
- azure-purge
|
||||||
- GO111MODULE=on
|
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
script:
|
script:
|
||||||
@@ -180,10 +146,9 @@ jobs:
|
|||||||
- stage: build
|
- stage: build
|
||||||
if: type = cron
|
if: type = cron
|
||||||
os: linux
|
os: linux
|
||||||
dist: bionic
|
dist: noble
|
||||||
go: 1.20.x
|
go: 1.22.x
|
||||||
env:
|
env:
|
||||||
- GO111MODULE=on
|
- racetests
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go test -race $TEST_PACKAGES
|
- travis_wait 60 go run build/ci.go test -race $TEST_PACKAGES
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ ARG VERSION=""
|
|||||||
ARG BUILDNUM=""
|
ARG BUILDNUM=""
|
||||||
|
|
||||||
# Build Geth in a stock Go builder container
|
# Build Geth in a stock Go builder container
|
||||||
FROM golang:1.20-alpine as builder
|
FROM golang:1.22-alpine as builder
|
||||||
|
|
||||||
RUN apk add --no-cache gcc musl-dev linux-headers git
|
RUN apk add --no-cache gcc musl-dev linux-headers git
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
|
|||||||
EXPOSE 8545 8546 30303 30303/udp
|
EXPOSE 8545 8546 30303 30303/udp
|
||||||
ENTRYPOINT ["geth"]
|
ENTRYPOINT ["geth"]
|
||||||
|
|
||||||
# Add some metadata labels to help programatic image consumption
|
# Add some metadata labels to help programmatic image consumption
|
||||||
ARG COMMIT=""
|
ARG COMMIT=""
|
||||||
ARG VERSION=""
|
ARG VERSION=""
|
||||||
ARG BUILDNUM=""
|
ARG BUILDNUM=""
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ ARG VERSION=""
|
|||||||
ARG BUILDNUM=""
|
ARG BUILDNUM=""
|
||||||
|
|
||||||
# Build Geth in a stock Go builder container
|
# Build Geth in a stock Go builder container
|
||||||
FROM golang:1.20-alpine as builder
|
FROM golang:1.22-alpine as builder
|
||||||
|
|
||||||
RUN apk add --no-cache gcc musl-dev linux-headers git
|
RUN apk add --no-cache gcc musl-dev linux-headers git
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/
|
|||||||
|
|
||||||
EXPOSE 8545 8546 30303 30303/udp
|
EXPOSE 8545 8546 30303 30303/udp
|
||||||
|
|
||||||
# Add some metadata labels to help programatic image consumption
|
# Add some metadata labels to help programmatic image consumption
|
||||||
ARG COMMIT=""
|
ARG COMMIT=""
|
||||||
ARG VERSION=""
|
ARG VERSION=""
|
||||||
ARG BUILDNUM=""
|
ARG BUILDNUM=""
|
||||||
|
|||||||
25
Makefile
25
Makefile
@@ -2,33 +2,43 @@
|
|||||||
# with Go source code. If you know what GOPATH is then you probably
|
# with Go source code. If you know what GOPATH is then you probably
|
||||||
# don't need to bother with make.
|
# don't need to bother with make.
|
||||||
|
|
||||||
.PHONY: geth android ios evm all test clean
|
.PHONY: geth all test lint fmt clean devtools help
|
||||||
|
|
||||||
GOBIN = ./build/bin
|
GOBIN = ./build/bin
|
||||||
GO ?= latest
|
GO ?= latest
|
||||||
GORUN = env GO111MODULE=on go run
|
GORUN = go run
|
||||||
|
|
||||||
|
#? geth: Build geth.
|
||||||
geth:
|
geth:
|
||||||
$(GORUN) build/ci.go install ./cmd/geth
|
$(GORUN) build/ci.go install ./cmd/geth
|
||||||
@echo "Done building."
|
@echo "Done building."
|
||||||
@echo "Run \"$(GOBIN)/geth\" to launch geth."
|
@echo "Run \"$(GOBIN)/geth\" to launch geth."
|
||||||
|
|
||||||
|
#? all: Build all packages and executables.
|
||||||
all:
|
all:
|
||||||
$(GORUN) build/ci.go install
|
$(GORUN) build/ci.go install
|
||||||
|
|
||||||
|
#? test: Run the tests.
|
||||||
test: all
|
test: all
|
||||||
$(GORUN) build/ci.go test
|
$(GORUN) build/ci.go test
|
||||||
|
|
||||||
|
#? lint: Run certain pre-selected linters.
|
||||||
lint: ## Run linters.
|
lint: ## Run linters.
|
||||||
$(GORUN) build/ci.go lint
|
$(GORUN) build/ci.go lint
|
||||||
|
|
||||||
|
#? fmt: Ensure consistent code formatting.
|
||||||
|
fmt:
|
||||||
|
gofmt -s -w $(shell find . -name "*.go")
|
||||||
|
|
||||||
|
#? clean: Clean go cache, built executables, and the auto generated folder.
|
||||||
clean:
|
clean:
|
||||||
env GO111MODULE=on go clean -cache
|
go clean -cache
|
||||||
rm -fr build/_workspace/pkg/ $(GOBIN)/*
|
rm -fr build/_workspace/pkg/ $(GOBIN)/*
|
||||||
|
|
||||||
# The devtools target installs tools required for 'go generate'.
|
# The devtools target installs tools required for 'go generate'.
|
||||||
# You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'.
|
# You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'.
|
||||||
|
|
||||||
|
#? devtools: Install recommended developer tools.
|
||||||
devtools:
|
devtools:
|
||||||
env GOBIN= go install golang.org/x/tools/cmd/stringer@latest
|
env GOBIN= go install golang.org/x/tools/cmd/stringer@latest
|
||||||
env GOBIN= go install github.com/fjl/gencodec@latest
|
env GOBIN= go install github.com/fjl/gencodec@latest
|
||||||
@@ -36,3 +46,12 @@ devtools:
|
|||||||
env GOBIN= go install ./cmd/abigen
|
env GOBIN= go install ./cmd/abigen
|
||||||
@type "solc" 2> /dev/null || echo 'Please install solc'
|
@type "solc" 2> /dev/null || echo 'Please install solc'
|
||||||
@type "protoc" 2> /dev/null || echo 'Please install protoc'
|
@type "protoc" 2> /dev/null || echo 'Please install protoc'
|
||||||
|
|
||||||
|
#? help: Get more info on make commands.
|
||||||
|
help: Makefile
|
||||||
|
@echo ''
|
||||||
|
@echo 'Usage:'
|
||||||
|
@echo ' make [target]'
|
||||||
|
@echo ''
|
||||||
|
@echo 'Targets:'
|
||||||
|
@sed -n 's/^#?//p' $< | column -t -s ':' | sort | sed -e 's/^/ /'
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -1,12 +1,12 @@
|
|||||||
## Go Ethereum
|
## Go Ethereum
|
||||||
|
|
||||||
Official Golang execution layer implementation of the Ethereum protocol.
|
Golang execution layer implementation of the Ethereum protocol.
|
||||||
|
|
||||||
[](https://pkg.go.dev/github.com/ethereum/go-ethereum?tab=doc)
|
)](https://pkg.go.dev/github.com/ethereum/go-ethereum?tab=doc)
|
||||||
[](https://goreportcard.com/report/github.com/ethereum/go-ethereum)
|
[](https://goreportcard.com/report/github.com/ethereum/go-ethereum)
|
||||||
[](https://travis-ci.com/ethereum/go-ethereum)
|
[](https://app.travis-ci.com/github/ethereum/go-ethereum)
|
||||||
[](https://discord.gg/nthXNEv)
|
[](https://discord.gg/nthXNEv)
|
||||||
|
|
||||||
Automated builds are available for stable releases and the unstable master branch. Binary
|
Automated builds are available for stable releases and the unstable master branch. Binary
|
||||||
@@ -16,7 +16,7 @@ archives are published at https://geth.ethereum.org/downloads/.
|
|||||||
|
|
||||||
For prerequisites and detailed build instructions please read the [Installation Instructions](https://geth.ethereum.org/docs/getting-started/installing-geth).
|
For prerequisites and detailed build instructions please read the [Installation Instructions](https://geth.ethereum.org/docs/getting-started/installing-geth).
|
||||||
|
|
||||||
Building `geth` requires both a Go (version 1.19 or later) and a C compiler. You can install
|
Building `geth` requires both a Go (version 1.21 or later) and a C compiler. You can install
|
||||||
them using your favourite package manager. Once the dependencies are installed, run
|
them using your favourite package manager. Once the dependencies are installed, run
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -123,15 +123,6 @@ use separate accounts for play and real money. Unless you manually move
|
|||||||
accounts, `geth` will by default correctly separate the two networks and will not make any
|
accounts, `geth` will by default correctly separate the two networks and will not make any
|
||||||
accounts available between them.*
|
accounts available between them.*
|
||||||
|
|
||||||
### Full node on the Rinkeby test network
|
|
||||||
|
|
||||||
Go Ethereum also supports connecting to the older proof-of-authority based test network
|
|
||||||
called [*Rinkeby*](https://www.rinkeby.io) which is operated by members of the community.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ geth --rinkeby console
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
As an alternative to passing the numerous flags to the `geth` binary, you can also pass a
|
As an alternative to passing the numerous flags to the `geth` binary, you can also pass a
|
||||||
|
|||||||
@@ -22,13 +22,14 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The ABI holds information about a contract's context and available
|
// The ABI holds information about a contract's context and available
|
||||||
// invokable methods. It will allow you to type check function calls and
|
// invocable methods. It will allow you to type check function calls and
|
||||||
// packs data accordingly.
|
// packs data accordingly.
|
||||||
type ABI struct {
|
type ABI struct {
|
||||||
Constructor Method
|
Constructor Method
|
||||||
@@ -246,24 +247,65 @@ func (abi *ABI) HasReceive() bool {
|
|||||||
// revertSelector is a special function selector for revert reason unpacking.
|
// revertSelector is a special function selector for revert reason unpacking.
|
||||||
var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
|
var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
|
||||||
|
|
||||||
|
// panicSelector is a special function selector for panic reason unpacking.
|
||||||
|
var panicSelector = crypto.Keccak256([]byte("Panic(uint256)"))[:4]
|
||||||
|
|
||||||
|
// panicReasons map is for readable panic codes
|
||||||
|
// see this linkage for the details
|
||||||
|
// https://docs.soliditylang.org/en/v0.8.21/control-structures.html#panic-via-assert-and-error-via-require
|
||||||
|
// the reason string list is copied from ether.js
|
||||||
|
// https://github.com/ethers-io/ethers.js/blob/fa3a883ff7c88611ce766f58bdd4b8ac90814470/src.ts/abi/interface.ts#L207-L218
|
||||||
|
var panicReasons = map[uint64]string{
|
||||||
|
0x00: "generic panic",
|
||||||
|
0x01: "assert(false)",
|
||||||
|
0x11: "arithmetic underflow or overflow",
|
||||||
|
0x12: "division or modulo by zero",
|
||||||
|
0x21: "enum overflow",
|
||||||
|
0x22: "invalid encoded storage byte array accessed",
|
||||||
|
0x31: "out-of-bounds array access; popping on an empty array",
|
||||||
|
0x32: "out-of-bounds access of an array or bytesN",
|
||||||
|
0x41: "out of memory",
|
||||||
|
0x51: "uninitialized function",
|
||||||
|
}
|
||||||
|
|
||||||
// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
|
// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
|
||||||
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
|
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
|
||||||
// the provided revert reason is abi-encoded as if it were a call to a function
|
// the provided revert reason is abi-encoded as if it were a call to function
|
||||||
// `Error(string)`. So it's a special tool for it.
|
// `Error(string)` or `Panic(uint256)`. So it's a special tool for it.
|
||||||
func UnpackRevert(data []byte) (string, error) {
|
func UnpackRevert(data []byte) (string, error) {
|
||||||
if len(data) < 4 {
|
if len(data) < 4 {
|
||||||
return "", errors.New("invalid data for unpacking")
|
return "", errors.New("invalid data for unpacking")
|
||||||
}
|
}
|
||||||
if !bytes.Equal(data[:4], revertSelector) {
|
switch {
|
||||||
|
case bytes.Equal(data[:4], revertSelector):
|
||||||
|
typ, err := NewType("string", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return unpacked[0].(string), nil
|
||||||
|
case bytes.Equal(data[:4], panicSelector):
|
||||||
|
typ, err := NewType("uint256", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pCode := unpacked[0].(*big.Int)
|
||||||
|
// uint64 safety check for future
|
||||||
|
// but the code is not bigger than MAX(uint64) now
|
||||||
|
if pCode.IsUint64() {
|
||||||
|
if reason, ok := panicReasons[pCode.Uint64()]; ok {
|
||||||
|
return reason, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("unknown panic code: %#x", pCode), nil
|
||||||
|
default:
|
||||||
return "", errors.New("invalid data for unpacking")
|
return "", errors.New("invalid data for unpacking")
|
||||||
}
|
}
|
||||||
typ, err := NewType("string", "", nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return unpacked[0].(string), nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ var methods = map[string]Method{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReader(t *testing.T) {
|
func TestReader(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi := ABI{
|
abi := ABI{
|
||||||
Methods: methods,
|
Methods: methods,
|
||||||
}
|
}
|
||||||
@@ -151,6 +152,7 @@ func TestReader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidABI(t *testing.T) {
|
func TestInvalidABI(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
json := `[{ "type" : "function", "name" : "", "constant" : fals }]`
|
json := `[{ "type" : "function", "name" : "", "constant" : fals }]`
|
||||||
_, err := JSON(strings.NewReader(json))
|
_, err := JSON(strings.NewReader(json))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -170,6 +172,7 @@ func TestInvalidABI(t *testing.T) {
|
|||||||
// constructor(uint256 a, uint256 b) public{}
|
// constructor(uint256 a, uint256 b) public{}
|
||||||
// }
|
// }
|
||||||
func TestConstructor(t *testing.T) {
|
func TestConstructor(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
json := `[{ "inputs": [{"internalType": "uint256","name": "a","type": "uint256" },{ "internalType": "uint256","name": "b","type": "uint256"}],"stateMutability": "nonpayable","type": "constructor"}]`
|
json := `[{ "inputs": [{"internalType": "uint256","name": "a","type": "uint256" },{ "internalType": "uint256","name": "b","type": "uint256"}],"stateMutability": "nonpayable","type": "constructor"}]`
|
||||||
method := NewMethod("", "", Constructor, "nonpayable", false, false, []Argument{{"a", Uint256, false}, {"b", Uint256, false}}, nil)
|
method := NewMethod("", "", Constructor, "nonpayable", false, false, []Argument{{"a", Uint256, false}, {"b", Uint256, false}}, nil)
|
||||||
// Test from JSON
|
// Test from JSON
|
||||||
@@ -199,6 +202,7 @@ func TestConstructor(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTestNumbers(t *testing.T) {
|
func TestTestNumbers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi, err := JSON(strings.NewReader(jsondata))
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -236,6 +240,7 @@ func TestTestNumbers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMethodSignature(t *testing.T) {
|
func TestMethodSignature(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
m := NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil)
|
m := NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil)
|
||||||
exp := "foo(string,string)"
|
exp := "foo(string,string)"
|
||||||
if m.Sig != exp {
|
if m.Sig != exp {
|
||||||
@@ -274,6 +279,7 @@ func TestMethodSignature(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOverloadedMethodSignature(t *testing.T) {
|
func TestOverloadedMethodSignature(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
json := `[{"constant":true,"inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"i","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"}],"name":"bar","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"},{"indexed":false,"name":"j","type":"uint256"}],"name":"bar","type":"event"}]`
|
json := `[{"constant":true,"inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"i","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"}],"name":"bar","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"},{"indexed":false,"name":"j","type":"uint256"}],"name":"bar","type":"event"}]`
|
||||||
abi, err := JSON(strings.NewReader(json))
|
abi, err := JSON(strings.NewReader(json))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -297,6 +303,7 @@ func TestOverloadedMethodSignature(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCustomErrors(t *testing.T) {
|
func TestCustomErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
json := `[{ "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ],"name": "MyError", "type": "error"} ]`
|
json := `[{ "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ],"name": "MyError", "type": "error"} ]`
|
||||||
abi, err := JSON(strings.NewReader(json))
|
abi, err := JSON(strings.NewReader(json))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -311,6 +318,7 @@ func TestCustomErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiPack(t *testing.T) {
|
func TestMultiPack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi, err := JSON(strings.NewReader(jsondata))
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -348,6 +356,7 @@ func ExampleJSON() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInputVariableInputLength(t *testing.T) {
|
func TestInputVariableInputLength(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[
|
const definition = `[
|
||||||
{ "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] },
|
{ "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] },
|
||||||
{ "type" : "function", "name" : "bytesOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] },
|
{ "type" : "function", "name" : "bytesOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] },
|
||||||
@@ -476,6 +485,7 @@ func TestInputVariableInputLength(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
|
func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi, err := JSON(strings.NewReader(jsondata))
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -650,6 +660,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultFunctionParsing(t *testing.T) {
|
func TestDefaultFunctionParsing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[{ "name" : "balance", "type" : "function" }]`
|
const definition = `[{ "name" : "balance", "type" : "function" }]`
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
@@ -663,6 +674,7 @@ func TestDefaultFunctionParsing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBareEvents(t *testing.T) {
|
func TestBareEvents(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[
|
const definition = `[
|
||||||
{ "type" : "event", "name" : "balance" },
|
{ "type" : "event", "name" : "balance" },
|
||||||
{ "type" : "event", "name" : "anon", "anonymous" : true},
|
{ "type" : "event", "name" : "anon", "anonymous" : true},
|
||||||
@@ -739,6 +751,7 @@ func TestBareEvents(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
||||||
func TestUnpackEvent(t *testing.T) {
|
func TestUnpackEvent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
||||||
abi, err := JSON(strings.NewReader(abiJSON))
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -777,6 +790,7 @@ func TestUnpackEvent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackEventIntoMap(t *testing.T) {
|
func TestUnpackEventIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
||||||
abi, err := JSON(strings.NewReader(abiJSON))
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -827,6 +841,7 @@ func TestUnpackEventIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackMethodIntoMap(t *testing.T) {
|
func TestUnpackMethodIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
|
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
|
||||||
abi, err := JSON(strings.NewReader(abiJSON))
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -877,6 +892,7 @@ func TestUnpackMethodIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackIntoMapNamingConflict(t *testing.T) {
|
func TestUnpackIntoMapNamingConflict(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// Two methods have the same name
|
// Two methods have the same name
|
||||||
var abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"get","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
|
var abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"get","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
|
||||||
abi, err := JSON(strings.NewReader(abiJSON))
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
@@ -960,6 +976,7 @@ func TestUnpackIntoMapNamingConflict(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestABI_MethodById(t *testing.T) {
|
func TestABI_MethodById(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi, err := JSON(strings.NewReader(jsondata))
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -992,6 +1009,7 @@ func TestABI_MethodById(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestABI_EventById(t *testing.T) {
|
func TestABI_EventById(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
json string
|
json string
|
||||||
@@ -1058,6 +1076,7 @@ func TestABI_EventById(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestABI_ErrorByID(t *testing.T) {
|
func TestABI_ErrorByID(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi, err := JSON(strings.NewReader(`[
|
abi, err := JSON(strings.NewReader(`[
|
||||||
{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"MyError1","type":"error"},
|
{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"MyError1","type":"error"},
|
||||||
{"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"string","name":"b","type":"string"},{"internalType":"address","name":"c","type":"address"}],"internalType":"struct MyError.MyStruct","name":"x","type":"tuple"},{"internalType":"address","name":"y","type":"address"},{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"string","name":"b","type":"string"},{"internalType":"address","name":"c","type":"address"}],"internalType":"struct MyError.MyStruct","name":"z","type":"tuple"}],"name":"MyError2","type":"error"},
|
{"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"string","name":"b","type":"string"},{"internalType":"address","name":"c","type":"address"}],"internalType":"struct MyError.MyStruct","name":"x","type":"tuple"},{"internalType":"address","name":"y","type":"address"},{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"string","name":"b","type":"string"},{"internalType":"address","name":"c","type":"address"}],"internalType":"struct MyError.MyStruct","name":"z","type":"tuple"}],"name":"MyError2","type":"error"},
|
||||||
@@ -1088,6 +1107,7 @@ func TestABI_ErrorByID(t *testing.T) {
|
|||||||
// TestDoubleDuplicateMethodNames checks that if transfer0 already exists, there won't be a name
|
// TestDoubleDuplicateMethodNames checks that if transfer0 already exists, there won't be a name
|
||||||
// conflict and that the second transfer method will be renamed transfer1.
|
// conflict and that the second transfer method will be renamed transfer1.
|
||||||
func TestDoubleDuplicateMethodNames(t *testing.T) {
|
func TestDoubleDuplicateMethodNames(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abiJSON := `[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"}],"name":"transfer0","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"customFallback","type":"string"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]`
|
abiJSON := `[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"}],"name":"transfer0","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"customFallback","type":"string"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]`
|
||||||
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1117,6 +1137,7 @@ func TestDoubleDuplicateMethodNames(t *testing.T) {
|
|||||||
// event send();
|
// event send();
|
||||||
// }
|
// }
|
||||||
func TestDoubleDuplicateEventNames(t *testing.T) {
|
func TestDoubleDuplicateEventNames(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abiJSON := `[{"anonymous": false,"inputs": [{"indexed": false,"internalType": "uint256","name": "a","type": "uint256"}],"name": "send","type": "event"},{"anonymous": false,"inputs": [],"name": "send0","type": "event"},{ "anonymous": false, "inputs": [],"name": "send","type": "event"}]`
|
abiJSON := `[{"anonymous": false,"inputs": [{"indexed": false,"internalType": "uint256","name": "a","type": "uint256"}],"name": "send","type": "event"},{"anonymous": false,"inputs": [],"name": "send0","type": "event"},{ "anonymous": false, "inputs": [],"name": "send","type": "event"}]`
|
||||||
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1144,6 +1165,7 @@ func TestDoubleDuplicateEventNames(t *testing.T) {
|
|||||||
// event send(uint256, uint256);
|
// event send(uint256, uint256);
|
||||||
// }
|
// }
|
||||||
func TestUnnamedEventParam(t *testing.T) {
|
func TestUnnamedEventParam(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abiJSON := `[{ "anonymous": false, "inputs": [{ "indexed": false,"internalType": "uint256", "name": "","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "","type": "uint256"}],"name": "send","type": "event"}]`
|
abiJSON := `[{ "anonymous": false, "inputs": [{ "indexed": false,"internalType": "uint256", "name": "","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "","type": "uint256"}],"name": "send","type": "event"}]`
|
||||||
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
contractAbi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1173,9 +1195,13 @@ func TestUnpackRevert(t *testing.T) {
|
|||||||
{"", "", errors.New("invalid data for unpacking")},
|
{"", "", errors.New("invalid data for unpacking")},
|
||||||
{"08c379a1", "", errors.New("invalid data for unpacking")},
|
{"08c379a1", "", errors.New("invalid data for unpacking")},
|
||||||
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
|
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
|
||||||
|
{"4e487b710000000000000000000000000000000000000000000000000000000000000000", "generic panic", nil},
|
||||||
|
{"4e487b7100000000000000000000000000000000000000000000000000000000000000ff", "unknown panic code: 0xff", nil},
|
||||||
}
|
}
|
||||||
for index, c := range cases {
|
for index, c := range cases {
|
||||||
|
index, c := index, c
|
||||||
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
|
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
got, err := UnpackRevert(common.Hex2Bytes(c.input))
|
got, err := UnpackRevert(common.Hex2Bytes(c.input))
|
||||||
if c.expectErr != nil {
|
if c.expectErr != nil {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -20,20 +20,34 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
|
||||||
fuzz "github.com/google/gofuzz"
|
fuzz "github.com/google/gofuzz"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TestReplicate can be used to replicate crashers from the fuzzing tests.
|
||||||
|
// Just replace testString with the data in .quoted
|
||||||
|
func TestReplicate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
//t.Skip("Test only useful for reproducing issues")
|
||||||
|
fuzzAbi([]byte("\x20\x20\x20\x20\x20\x20\x20\x20\x80\x00\x00\x00\x20\x20\x20\x20\x00"))
|
||||||
|
//fuzzAbi([]byte("asdfasdfkadsf;lasdf;lasd;lfk"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuzzABI is the main entrypoint for fuzzing
|
||||||
|
func FuzzABI(f *testing.F) {
|
||||||
|
f.Fuzz(func(t *testing.T, data []byte) {
|
||||||
|
fuzzAbi(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"}
|
names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"}
|
||||||
stateMut = []string{"", "pure", "view", "payable"}
|
stateMut = []string{"pure", "view", "payable"}
|
||||||
stateMutabilites = []*string{&stateMut[0], &stateMut[1], &stateMut[2], &stateMut[3]}
|
pays = []string{"true", "false"}
|
||||||
pays = []string{"", "true", "false"}
|
vNames = []string{"a", "b", "c", "d", "e", "f", "g"}
|
||||||
payables = []*string{&pays[0], &pays[1]}
|
varNames = append(vNames, names...)
|
||||||
vNames = []string{"a", "b", "c", "d", "e", "f", "g"}
|
varTypes = []string{"bool", "address", "bytes", "string",
|
||||||
varNames = append(vNames, names...)
|
|
||||||
varTypes = []string{"bool", "address", "bytes", "string",
|
|
||||||
"uint8", "int8", "uint8", "int8", "uint16", "int16",
|
"uint8", "int8", "uint8", "int8", "uint16", "int16",
|
||||||
"uint24", "int24", "uint32", "int32", "uint40", "int40", "uint48", "int48", "uint56", "int56",
|
"uint24", "int24", "uint32", "int32", "uint40", "int40", "uint48", "int48", "uint56", "int56",
|
||||||
"uint64", "int64", "uint72", "int72", "uint80", "int80", "uint88", "int88", "uint96", "int96",
|
"uint64", "int64", "uint72", "int72", "uint80", "int80", "uint88", "int88", "uint96", "int96",
|
||||||
@@ -47,7 +61,7 @@ var (
|
|||||||
"bytes32", "bytes"}
|
"bytes32", "bytes"}
|
||||||
)
|
)
|
||||||
|
|
||||||
func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool) {
|
func unpackPack(abi ABI, method string, input []byte) ([]interface{}, bool) {
|
||||||
if out, err := abi.Unpack(method, input); err == nil {
|
if out, err := abi.Unpack(method, input); err == nil {
|
||||||
_, err := abi.Pack(method, out...)
|
_, err := abi.Pack(method, out...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -63,7 +77,7 @@ func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool)
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool {
|
func packUnpack(abi ABI, method string, input *[]interface{}) bool {
|
||||||
if packed, err := abi.Pack(method, input); err == nil {
|
if packed, err := abi.Pack(method, input); err == nil {
|
||||||
outptr := reflect.New(reflect.TypeOf(input))
|
outptr := reflect.New(reflect.TypeOf(input))
|
||||||
err := abi.UnpackIntoInterface(outptr.Interface(), method, packed)
|
err := abi.UnpackIntoInterface(outptr.Interface(), method, packed)
|
||||||
@@ -79,12 +93,12 @@ func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type args struct {
|
type arg struct {
|
||||||
name string
|
name string
|
||||||
typ string
|
typ string
|
||||||
}
|
}
|
||||||
|
|
||||||
func createABI(name string, stateMutability, payable *string, inputs []args) (abi.ABI, error) {
|
func createABI(name string, stateMutability, payable *string, inputs []arg) (ABI, error) {
|
||||||
sig := fmt.Sprintf(`[{ "type" : "function", "name" : "%v" `, name)
|
sig := fmt.Sprintf(`[{ "type" : "function", "name" : "%v" `, name)
|
||||||
if stateMutability != nil {
|
if stateMutability != nil {
|
||||||
sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability)
|
sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability)
|
||||||
@@ -111,60 +125,55 @@ func createABI(name string, stateMutability, payable *string, inputs []args) (ab
|
|||||||
sig += "} ]"
|
sig += "} ]"
|
||||||
}
|
}
|
||||||
sig += `}]`
|
sig += `}]`
|
||||||
|
//fmt.Printf("sig: %s\n", sig)
|
||||||
return abi.JSON(strings.NewReader(sig))
|
return JSON(strings.NewReader(sig))
|
||||||
}
|
}
|
||||||
|
|
||||||
func runFuzzer(input []byte) int {
|
func fuzzAbi(input []byte) {
|
||||||
good := false
|
var (
|
||||||
fuzzer := fuzz.NewFromGoFuzz(input)
|
fuzzer = fuzz.NewFromGoFuzz(input)
|
||||||
|
name = oneOf(fuzzer, names)
|
||||||
name := names[getUInt(fuzzer)%len(names)]
|
stateM = oneOfOrNil(fuzzer, stateMut)
|
||||||
stateM := stateMutabilites[getUInt(fuzzer)%len(stateMutabilites)]
|
payable = oneOfOrNil(fuzzer, pays)
|
||||||
payable := payables[getUInt(fuzzer)%len(payables)]
|
arguments []arg
|
||||||
maxLen := 5
|
)
|
||||||
for k := 1; k < maxLen; k++ {
|
for i := 0; i < upTo(fuzzer, 10); i++ {
|
||||||
var arg []args
|
argName := oneOf(fuzzer, varNames)
|
||||||
for i := k; i > 0; i-- {
|
argTyp := oneOf(fuzzer, varTypes)
|
||||||
argName := varNames[i]
|
switch upTo(fuzzer, 10) {
|
||||||
argTyp := varTypes[getUInt(fuzzer)%len(varTypes)]
|
case 0: // 10% chance to make it a slice
|
||||||
if getUInt(fuzzer)%10 == 0 {
|
argTyp += "[]"
|
||||||
argTyp += "[]"
|
case 1: // 10% chance to make it an array
|
||||||
} else if getUInt(fuzzer)%10 == 0 {
|
argTyp += fmt.Sprintf("[%d]", 1+upTo(fuzzer, 30))
|
||||||
arrayArgs := getUInt(fuzzer)%30 + 1
|
default:
|
||||||
argTyp += fmt.Sprintf("[%d]", arrayArgs)
|
|
||||||
}
|
|
||||||
arg = append(arg, args{
|
|
||||||
name: argName,
|
|
||||||
typ: argTyp,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
abi, err := createABI(name, stateM, payable, arg)
|
arguments = append(arguments, arg{name: argName, typ: argTyp})
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
structs, b := unpackPack(abi, name, input)
|
|
||||||
c := packUnpack(abi, name, &structs)
|
|
||||||
good = good || b || c
|
|
||||||
}
|
}
|
||||||
if good {
|
abi, err := createABI(name, stateM, payable, arguments)
|
||||||
return 1
|
if err != nil {
|
||||||
|
//fmt.Printf("err: %v\n", err)
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
return 0
|
structs, _ := unpackPack(abi, name, input)
|
||||||
|
_ = packUnpack(abi, name, &structs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Fuzz(input []byte) int {
|
func upTo(fuzzer *fuzz.Fuzzer, max int) int {
|
||||||
return runFuzzer(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUInt(fuzzer *fuzz.Fuzzer) int {
|
|
||||||
var i int
|
var i int
|
||||||
fuzzer.Fuzz(&i)
|
fuzzer.Fuzz(&i)
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
i = -i
|
return (-1 - i) % max
|
||||||
if i < 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return i
|
return i % max
|
||||||
|
}
|
||||||
|
|
||||||
|
func oneOf(fuzzer *fuzz.Fuzzer, options []string) string {
|
||||||
|
return options[upTo(fuzzer, len(options))]
|
||||||
|
}
|
||||||
|
|
||||||
|
func oneOfOrNil(fuzzer *fuzz.Fuzzer, options []string) *string {
|
||||||
|
if i := upTo(fuzzer, len(options)+1); i < len(options) {
|
||||||
|
return &options[i]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ func (arguments Arguments) isTuple() bool {
|
|||||||
func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) {
|
func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) {
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
if len(arguments.NonIndexed()) != 0 {
|
if len(arguments.NonIndexed()) != 0 {
|
||||||
return nil, errors.New("abi: attempting to unmarshall an empty string while arguments are expected")
|
return nil, errors.New("abi: attempting to unmarshal an empty string while arguments are expected")
|
||||||
}
|
}
|
||||||
return make([]interface{}, 0), nil
|
return make([]interface{}, 0), nil
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte)
|
|||||||
}
|
}
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
if len(arguments.NonIndexed()) != 0 {
|
if len(arguments.NonIndexed()) != 0 {
|
||||||
return errors.New("abi: attempting to unmarshall an empty string while arguments are expected")
|
return errors.New("abi: attempting to unmarshal an empty string while arguments are expected")
|
||||||
}
|
}
|
||||||
return nil // Nothing to unmarshal, return
|
return nil // Nothing to unmarshal, return
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ func (arguments Arguments) Copy(v interface{}, values []interface{}) error {
|
|||||||
return arguments.copyAtomic(v, values[0])
|
return arguments.copyAtomic(v, values[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// unpackAtomic unpacks ( hexdata -> go ) a single value
|
// copyAtomic copies ( hexdata -> go ) a single value
|
||||||
func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{}) error {
|
func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{}) error {
|
||||||
dst := reflect.ValueOf(v).Elem()
|
dst := reflect.ValueOf(v).Elem()
|
||||||
src := reflect.ValueOf(marshalledValues)
|
src := reflect.ValueOf(marshalledValues)
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewKeyStoreTransactor is a utility method to easily create a transaction signer from
|
// NewKeyStoreTransactor is a utility method to easily create a transaction signer from
|
||||||
// an decrypted key from a keystore.
|
// a decrypted key from a keystore.
|
||||||
//
|
//
|
||||||
// Deprecated: Use NewKeyStoreTransactorWithChainID instead.
|
// Deprecated: Use NewKeyStoreTransactorWithChainID instead.
|
||||||
func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
|
func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
|
||||||
@@ -117,7 +117,7 @@ func NewTransactorWithChainID(keyin io.Reader, passphrase string, chainID *big.I
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from
|
// NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from
|
||||||
// an decrypted key from a keystore.
|
// a decrypted key from a keystore.
|
||||||
func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) {
|
func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) {
|
||||||
if chainID == nil {
|
if chainID == nil {
|
||||||
return nil, ErrNoChainID
|
return nil, ErrNoChainID
|
||||||
@@ -142,10 +142,10 @@ func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accou
|
|||||||
// NewKeyedTransactorWithChainID is a utility method to easily create a transaction signer
|
// NewKeyedTransactorWithChainID is a utility method to easily create a transaction signer
|
||||||
// from a single private key.
|
// from a single private key.
|
||||||
func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*TransactOpts, error) {
|
func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*TransactOpts, error) {
|
||||||
keyAddr := crypto.PubkeyToAddress(key.PublicKey)
|
|
||||||
if chainID == nil {
|
if chainID == nil {
|
||||||
return nil, ErrNoChainID
|
return nil, ErrNoChainID
|
||||||
}
|
}
|
||||||
|
keyAddr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
signer := types.LatestSignerForChainID(chainID)
|
signer := types.LatestSignerForChainID(chainID)
|
||||||
return &TransactOpts{
|
return &TransactOpts{
|
||||||
From: keyAddr,
|
From: keyAddr,
|
||||||
|
|||||||
@@ -29,13 +29,17 @@ import (
|
|||||||
var (
|
var (
|
||||||
// ErrNoCode is returned by call and transact operations for which the requested
|
// ErrNoCode is returned by call and transact operations for which the requested
|
||||||
// recipient contract to operate on does not exist in the state db or does not
|
// recipient contract to operate on does not exist in the state db or does not
|
||||||
// have any code associated with it (i.e. suicided).
|
// have any code associated with it (i.e. self-destructed).
|
||||||
ErrNoCode = errors.New("no contract code at given address")
|
ErrNoCode = errors.New("no contract code at given address")
|
||||||
|
|
||||||
// ErrNoPendingState is raised when attempting to perform a pending state action
|
// ErrNoPendingState is raised when attempting to perform a pending state action
|
||||||
// on a backend that doesn't implement PendingContractCaller.
|
// on a backend that doesn't implement PendingContractCaller.
|
||||||
ErrNoPendingState = errors.New("backend does not support pending state")
|
ErrNoPendingState = errors.New("backend does not support pending state")
|
||||||
|
|
||||||
|
// ErrNoBlockHashState is raised when attempting to perform a block hash action
|
||||||
|
// on a backend that doesn't implement BlockHashContractCaller.
|
||||||
|
ErrNoBlockHashState = errors.New("backend does not support block hash state")
|
||||||
|
|
||||||
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
|
// ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves
|
||||||
// an empty contract behind.
|
// an empty contract behind.
|
||||||
ErrNoCodeAfterDeploy = errors.New("no contract code after deployment")
|
ErrNoCodeAfterDeploy = errors.New("no contract code after deployment")
|
||||||
@@ -64,11 +68,27 @@ type PendingContractCaller interface {
|
|||||||
PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error)
|
PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockHashContractCaller defines methods to perform contract calls on a specific block hash.
|
||||||
|
// Call will try to discover this interface when access to a block by hash is requested.
|
||||||
|
// If the backend does not support the block hash state, Call returns ErrNoBlockHashState.
|
||||||
|
type BlockHashContractCaller interface {
|
||||||
|
// CodeAtHash returns the code of the given account in the state at the specified block hash.
|
||||||
|
CodeAtHash(ctx context.Context, contract common.Address, blockHash common.Hash) ([]byte, error)
|
||||||
|
|
||||||
|
// CallContractAtHash executes an Ethereum contract call against the state at the specified block hash.
|
||||||
|
CallContractAtHash(ctx context.Context, call ethereum.CallMsg, blockHash common.Hash) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
// ContractTransactor defines the methods needed to allow operating with a contract
|
// ContractTransactor defines the methods needed to allow operating with a contract
|
||||||
// on a write only basis. Besides the transacting method, the remainder are helpers
|
// on a write only basis. Besides the transacting method, the remainder are helpers
|
||||||
// used when the user does not provide some needed values, but rather leaves it up
|
// used when the user does not provide some needed values, but rather leaves it up
|
||||||
// to the transactor to decide.
|
// to the transactor to decide.
|
||||||
type ContractTransactor interface {
|
type ContractTransactor interface {
|
||||||
|
ethereum.GasEstimator
|
||||||
|
ethereum.GasPricer
|
||||||
|
ethereum.GasPricer1559
|
||||||
|
ethereum.TransactionSender
|
||||||
|
|
||||||
// HeaderByNumber returns a block header from the current canonical chain. If
|
// HeaderByNumber returns a block header from the current canonical chain. If
|
||||||
// number is nil, the latest known header is returned.
|
// number is nil, the latest known header is returned.
|
||||||
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
|
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
|
||||||
@@ -78,38 +98,6 @@ type ContractTransactor interface {
|
|||||||
|
|
||||||
// PendingNonceAt retrieves the current pending nonce associated with an account.
|
// PendingNonceAt retrieves the current pending nonce associated with an account.
|
||||||
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
|
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
|
||||||
|
|
||||||
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
|
|
||||||
// execution of a transaction.
|
|
||||||
SuggestGasPrice(ctx context.Context) (*big.Int, error)
|
|
||||||
|
|
||||||
// SuggestGasTipCap retrieves the currently suggested 1559 priority fee to allow
|
|
||||||
// a timely execution of a transaction.
|
|
||||||
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
|
|
||||||
|
|
||||||
// EstimateGas tries to estimate the gas needed to execute a specific
|
|
||||||
// transaction based on the current pending state of the backend blockchain.
|
|
||||||
// There is no guarantee that this is the true gas limit requirement as other
|
|
||||||
// transactions may be added or removed by miners, but it should provide a basis
|
|
||||||
// for setting a reasonable default.
|
|
||||||
EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
|
|
||||||
|
|
||||||
// SendTransaction injects the transaction into the pending pool for execution.
|
|
||||||
SendTransaction(ctx context.Context, tx *types.Transaction) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContractFilterer defines the methods needed to access log events using one-off
|
|
||||||
// queries or continuous event subscriptions.
|
|
||||||
type ContractFilterer interface {
|
|
||||||
// FilterLogs executes a log filter operation, blocking during execution and
|
|
||||||
// returning all the results in one batch.
|
|
||||||
//
|
|
||||||
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
|
||||||
FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error)
|
|
||||||
|
|
||||||
// SubscribeFilterLogs creates a background log filtering operation, returning
|
|
||||||
// a subscription immediately, which can be used to stream the found events.
|
|
||||||
SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
|
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
|
||||||
@@ -118,6 +106,12 @@ type DeployBackend interface {
|
|||||||
CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
|
CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContractFilterer defines the methods needed to access log events using one-off
|
||||||
|
// queries or continuous event subscriptions.
|
||||||
|
type ContractFilterer interface {
|
||||||
|
ethereum.LogFilterer
|
||||||
|
}
|
||||||
|
|
||||||
// ContractBackend defines the methods needed to work with contracts on a read-write basis.
|
// ContractBackend defines the methods needed to work with contracts on a read-write basis.
|
||||||
type ContractBackend interface {
|
type ContractBackend interface {
|
||||||
ContractCaller
|
ContractCaller
|
||||||
|
|||||||
@@ -18,921 +18,35 @@ package backends
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum"
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/ethclient/simulated"
|
||||||
"github.com/ethereum/go-ethereum/eth/filters"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// This nil assignment ensures at compile time that SimulatedBackend implements bind.ContractBackend.
|
// SimulatedBackend is a simulated blockchain.
|
||||||
var _ bind.ContractBackend = (*SimulatedBackend)(nil)
|
// Deprecated: use package github.com/ethereum/go-ethereum/ethclient/simulated instead.
|
||||||
|
|
||||||
var (
|
|
||||||
errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block")
|
|
||||||
errBlockDoesNotExist = errors.New("block does not exist in blockchain")
|
|
||||||
errTransactionDoesNotExist = errors.New("transaction does not exist")
|
|
||||||
)
|
|
||||||
|
|
||||||
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
|
|
||||||
// the background. Its main purpose is to allow for easy testing of contract bindings.
|
|
||||||
// Simulated backend implements the following interfaces:
|
|
||||||
// ChainReader, ChainStateReader, ContractBackend, ContractCaller, ContractFilterer, ContractTransactor,
|
|
||||||
// DeployBackend, GasEstimator, GasPricer, LogFilterer, PendingContractCaller, TransactionReader, and TransactionSender
|
|
||||||
type SimulatedBackend struct {
|
type SimulatedBackend struct {
|
||||||
database ethdb.Database // In memory database to store our testing data
|
*simulated.Backend
|
||||||
blockchain *core.BlockChain // Ethereum blockchain to handle the consensus
|
simulated.Client
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
pendingBlock *types.Block // Currently pending block that will be imported on request
|
|
||||||
pendingState *state.StateDB // Currently pending state that will be the active on request
|
|
||||||
pendingReceipts types.Receipts // Currently receipts for the pending block
|
|
||||||
|
|
||||||
events *filters.EventSystem // for filtering log events live
|
|
||||||
filterSystem *filters.FilterSystem // for filtering database logs
|
|
||||||
|
|
||||||
config *params.ChainConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSimulatedBackendWithDatabase creates a new binding backend based on the given database
|
// Fork sets the head to a new block, which is based on the provided parentHash.
|
||||||
// and uses a simulated blockchain for testing purposes.
|
func (b *SimulatedBackend) Fork(ctx context.Context, parentHash common.Hash) error {
|
||||||
// A simulated backend always uses chainID 1337.
|
return b.Backend.Fork(parentHash)
|
||||||
func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
|
|
||||||
genesis := core.Genesis{
|
|
||||||
Config: params.AllEthashProtocolChanges,
|
|
||||||
GasLimit: gasLimit,
|
|
||||||
Alloc: alloc,
|
|
||||||
}
|
|
||||||
blockchain, _ := core.NewBlockChain(database, nil, &genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil)
|
|
||||||
|
|
||||||
backend := &SimulatedBackend{
|
|
||||||
database: database,
|
|
||||||
blockchain: blockchain,
|
|
||||||
config: genesis.Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
filterBackend := &filterBackend{database, blockchain, backend}
|
|
||||||
backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{})
|
|
||||||
backend.events = filters.NewEventSystem(backend.filterSystem, false)
|
|
||||||
|
|
||||||
header := backend.blockchain.CurrentBlock()
|
|
||||||
block := backend.blockchain.GetBlock(header.Hash(), header.Number.Uint64())
|
|
||||||
|
|
||||||
backend.rollback(block)
|
|
||||||
return backend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
|
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
|
||||||
// for testing purposes.
|
// for testing purposes.
|
||||||
|
//
|
||||||
// A simulated backend always uses chainID 1337.
|
// A simulated backend always uses chainID 1337.
|
||||||
func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
|
|
||||||
return NewSimulatedBackendWithDatabase(rawdb.NewMemoryDatabase(), alloc, gasLimit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close terminates the underlying blockchain's update loop.
|
|
||||||
func (b *SimulatedBackend) Close() error {
|
|
||||||
b.blockchain.Stop()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit imports all the pending transactions as a single block and starts a
|
|
||||||
// fresh new state.
|
|
||||||
func (b *SimulatedBackend) Commit() common.Hash {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil {
|
|
||||||
panic(err) // This cannot happen unless the simulator is wrong, fail in that case
|
|
||||||
}
|
|
||||||
blockHash := b.pendingBlock.Hash()
|
|
||||||
|
|
||||||
// Using the last inserted block here makes it possible to build on a side
|
|
||||||
// chain after a fork.
|
|
||||||
b.rollback(b.pendingBlock)
|
|
||||||
|
|
||||||
return blockHash
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rollback aborts all pending transactions, reverting to the last committed state.
|
|
||||||
func (b *SimulatedBackend) Rollback() {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
header := b.blockchain.CurrentBlock()
|
|
||||||
block := b.blockchain.GetBlock(header.Hash(), header.Number.Uint64())
|
|
||||||
|
|
||||||
b.rollback(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *SimulatedBackend) rollback(parent *types.Block) {
|
|
||||||
blocks, _ := core.GenerateChain(b.config, parent, ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {})
|
|
||||||
|
|
||||||
b.pendingBlock = blocks[0]
|
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.blockchain.StateCache(), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fork creates a side-chain that can be used to simulate reorgs.
|
|
||||||
//
|
//
|
||||||
// This function should be called with the ancestor block where the new side
|
// Deprecated: please use simulated.Backend from package
|
||||||
// chain should be started. Transactions (old and new) can then be applied on
|
// github.com/ethereum/go-ethereum/ethclient/simulated instead.
|
||||||
// top and Commit-ed.
|
func NewSimulatedBackend(alloc types.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
|
||||||
//
|
b := simulated.NewBackend(alloc, simulated.WithBlockGasLimit(gasLimit))
|
||||||
// Note, the side-chain will only become canonical (and trigger the events) when
|
return &SimulatedBackend{
|
||||||
// it becomes longer. Until then CallContract will still operate on the current
|
Backend: b,
|
||||||
// canonical chain.
|
Client: b.Client(),
|
||||||
//
|
|
||||||
// There is a % chance that the side chain becomes canonical at the same length
|
|
||||||
// to simulate live network behavior.
|
|
||||||
func (b *SimulatedBackend) Fork(ctx context.Context, parent common.Hash) error {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if len(b.pendingBlock.Transactions()) != 0 {
|
|
||||||
return errors.New("pending block dirty")
|
|
||||||
}
|
|
||||||
block, err := b.blockByHash(ctx, parent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.rollback(block)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateByBlockNumber retrieves a state by a given blocknumber.
|
|
||||||
func (b *SimulatedBackend) stateByBlockNumber(ctx context.Context, blockNumber *big.Int) (*state.StateDB, error) {
|
|
||||||
if blockNumber == nil || blockNumber.Cmp(b.blockchain.CurrentBlock().Number) == 0 {
|
|
||||||
return b.blockchain.State()
|
|
||||||
}
|
|
||||||
block, err := b.blockByNumber(ctx, blockNumber)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b.blockchain.StateAt(block.Root())
|
|
||||||
}
|
|
||||||
|
|
||||||
// CodeAt returns the code associated with a certain account in the blockchain.
|
|
||||||
func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return stateDB.GetCode(contract), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BalanceAt returns the wei balance of a certain account in the blockchain.
|
|
||||||
func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (*big.Int, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return stateDB.GetBalance(contract), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NonceAt returns the nonce of a certain account in the blockchain.
|
|
||||||
func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (uint64, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return stateDB.GetNonce(contract), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StorageAt returns the value of key in the storage of an account in the blockchain.
|
|
||||||
func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
stateDB, err := b.stateByBlockNumber(ctx, blockNumber)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
val := stateDB.GetState(contract, key)
|
|
||||||
return val[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransactionReceipt returns the receipt of a transaction.
|
|
||||||
func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash, b.config)
|
|
||||||
if receipt == nil {
|
|
||||||
return nil, ethereum.NotFound
|
|
||||||
}
|
|
||||||
return receipt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransactionByHash checks the pool of pending transactions in addition to the
|
|
||||||
// blockchain. The isPending return value indicates whether the transaction has been
|
|
||||||
// mined yet. Note that the transaction may not be part of the canonical chain even if
|
|
||||||
// it's not pending.
|
|
||||||
func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
tx := b.pendingBlock.Transaction(txHash)
|
|
||||||
if tx != nil {
|
|
||||||
return tx, true, nil
|
|
||||||
}
|
|
||||||
tx, _, _, _ = rawdb.ReadTransaction(b.database, txHash)
|
|
||||||
if tx != nil {
|
|
||||||
return tx, false, nil
|
|
||||||
}
|
|
||||||
return nil, false, ethereum.NotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockByHash retrieves a block based on the block hash.
|
|
||||||
func (b *SimulatedBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
return b.blockByHash(ctx, hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// blockByHash retrieves a block based on the block hash without Locking.
|
|
||||||
func (b *SimulatedBackend) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
|
||||||
if hash == b.pendingBlock.Hash() {
|
|
||||||
return b.pendingBlock, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
block := b.blockchain.GetBlockByHash(hash)
|
|
||||||
if block != nil {
|
|
||||||
return block, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errBlockDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockByNumber retrieves a block from the database by number, caching it
|
|
||||||
// (associated with its hash) if found.
|
|
||||||
func (b *SimulatedBackend) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
return b.blockByNumber(ctx, number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// blockByNumber retrieves a block from the database by number, caching it
|
|
||||||
// (associated with its hash) if found without Lock.
|
|
||||||
func (b *SimulatedBackend) blockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
|
|
||||||
if number == nil || number.Cmp(b.pendingBlock.Number()) == 0 {
|
|
||||||
return b.blockByHash(ctx, b.blockchain.CurrentBlock().Hash())
|
|
||||||
}
|
|
||||||
|
|
||||||
block := b.blockchain.GetBlockByNumber(uint64(number.Int64()))
|
|
||||||
if block == nil {
|
|
||||||
return nil, errBlockDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
return block, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderByHash returns a block header from the current canonical chain.
|
|
||||||
func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if hash == b.pendingBlock.Hash() {
|
|
||||||
return b.pendingBlock.Header(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
header := b.blockchain.GetHeaderByHash(hash)
|
|
||||||
if header == nil {
|
|
||||||
return nil, errBlockDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
return header, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderByNumber returns a block header from the current canonical chain. If number is
|
|
||||||
// nil, the latest known header is returned.
|
|
||||||
func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (*types.Header, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if block == nil || block.Cmp(b.pendingBlock.Number()) == 0 {
|
|
||||||
return b.blockchain.CurrentHeader(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.blockchain.GetHeaderByNumber(uint64(block.Int64())), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransactionCount returns the number of transactions in a given block.
|
|
||||||
func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if blockHash == b.pendingBlock.Hash() {
|
|
||||||
return uint(b.pendingBlock.Transactions().Len()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
block := b.blockchain.GetBlockByHash(blockHash)
|
|
||||||
if block == nil {
|
|
||||||
return uint(0), errBlockDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint(block.Transactions().Len()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransactionInBlock returns the transaction for a specific block at a specific index.
|
|
||||||
func (b *SimulatedBackend) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if blockHash == b.pendingBlock.Hash() {
|
|
||||||
transactions := b.pendingBlock.Transactions()
|
|
||||||
if uint(len(transactions)) < index+1 {
|
|
||||||
return nil, errTransactionDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
return transactions[index], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
block := b.blockchain.GetBlockByHash(blockHash)
|
|
||||||
if block == nil {
|
|
||||||
return nil, errBlockDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
transactions := block.Transactions()
|
|
||||||
if uint(len(transactions)) < index+1 {
|
|
||||||
return nil, errTransactionDoesNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
return transactions[index], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PendingCodeAt returns the code associated with an account in the pending state.
|
|
||||||
func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
return b.pendingState.GetCode(contract), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRevertError(result *core.ExecutionResult) *revertError {
|
|
||||||
reason, errUnpack := abi.UnpackRevert(result.Revert())
|
|
||||||
err := errors.New("execution reverted")
|
|
||||||
if errUnpack == nil {
|
|
||||||
err = fmt.Errorf("execution reverted: %v", reason)
|
|
||||||
}
|
|
||||||
return &revertError{
|
|
||||||
error: err,
|
|
||||||
reason: hexutil.Encode(result.Revert()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// revertError is an API error that encompasses an EVM revert with JSON error
|
|
||||||
// code and a binary data blob.
|
|
||||||
type revertError struct {
|
|
||||||
error
|
|
||||||
reason string // revert reason hex encoded
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorCode returns the JSON error code for a revert.
|
|
||||||
// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal
|
|
||||||
func (e *revertError) ErrorCode() int {
|
|
||||||
return 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorData returns the hex encoded revert reason.
|
|
||||||
func (e *revertError) ErrorData() interface{} {
|
|
||||||
return e.reason
|
|
||||||
}
|
|
||||||
|
|
||||||
// CallContract executes a contract call.
|
|
||||||
func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number) != 0 {
|
|
||||||
return nil, errBlockNumberUnsupported
|
|
||||||
}
|
|
||||||
stateDB, err := b.blockchain.State()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), stateDB)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// If the result contains a revert reason, try to unpack and return it.
|
|
||||||
if len(res.Revert()) > 0 {
|
|
||||||
return nil, newRevertError(res)
|
|
||||||
}
|
|
||||||
return res.Return(), res.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PendingCallContract executes a contract call on the pending state.
|
|
||||||
func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot())
|
|
||||||
|
|
||||||
res, err := b.callContract(ctx, call, b.pendingBlock.Header(), b.pendingState)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// If the result contains a revert reason, try to unpack and return it.
|
|
||||||
if len(res.Revert()) > 0 {
|
|
||||||
return nil, newRevertError(res)
|
|
||||||
}
|
|
||||||
return res.Return(), res.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
|
|
||||||
// the nonce currently pending for the account.
|
|
||||||
func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
return b.pendingState.GetOrNewStateObject(account).Nonce(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
|
|
||||||
// chain doesn't have miners, we just return a gas price of 1 for any call.
|
|
||||||
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if b.pendingBlock.Header().BaseFee != nil {
|
|
||||||
return b.pendingBlock.Header().BaseFee, nil
|
|
||||||
}
|
|
||||||
return big.NewInt(1), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuggestGasTipCap implements ContractTransactor.SuggestGasTipCap. Since the simulated
|
|
||||||
// chain doesn't have miners, we just return a gas tip of 1 for any call.
|
|
||||||
func (b *SimulatedBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
|
|
||||||
return big.NewInt(1), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EstimateGas executes the requested code against the currently pending block/state and
|
|
||||||
// returns the used amount of gas.
|
|
||||||
func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
// Determine the lowest and highest possible gas limits to binary search in between
|
|
||||||
var (
|
|
||||||
lo uint64 = params.TxGas - 1
|
|
||||||
hi uint64
|
|
||||||
cap uint64
|
|
||||||
)
|
|
||||||
if call.Gas >= params.TxGas {
|
|
||||||
hi = call.Gas
|
|
||||||
} else {
|
|
||||||
hi = b.pendingBlock.GasLimit()
|
|
||||||
}
|
|
||||||
// Normalize the max fee per gas the call is willing to spend.
|
|
||||||
var feeCap *big.Int
|
|
||||||
if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) {
|
|
||||||
return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
|
||||||
} else if call.GasPrice != nil {
|
|
||||||
feeCap = call.GasPrice
|
|
||||||
} else if call.GasFeeCap != nil {
|
|
||||||
feeCap = call.GasFeeCap
|
|
||||||
} else {
|
|
||||||
feeCap = common.Big0
|
|
||||||
}
|
|
||||||
// Recap the highest gas allowance with account's balance.
|
|
||||||
if feeCap.BitLen() != 0 {
|
|
||||||
balance := b.pendingState.GetBalance(call.From) // from can't be nil
|
|
||||||
available := new(big.Int).Set(balance)
|
|
||||||
if call.Value != nil {
|
|
||||||
if call.Value.Cmp(available) >= 0 {
|
|
||||||
return 0, core.ErrInsufficientFundsForTransfer
|
|
||||||
}
|
|
||||||
available.Sub(available, call.Value)
|
|
||||||
}
|
|
||||||
allowance := new(big.Int).Div(available, feeCap)
|
|
||||||
if allowance.IsUint64() && hi > allowance.Uint64() {
|
|
||||||
transfer := call.Value
|
|
||||||
if transfer == nil {
|
|
||||||
transfer = new(big.Int)
|
|
||||||
}
|
|
||||||
log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance,
|
|
||||||
"sent", transfer, "feecap", feeCap, "fundable", allowance)
|
|
||||||
hi = allowance.Uint64()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cap = hi
|
|
||||||
|
|
||||||
// Create a helper to check if a gas allowance results in an executable transaction
|
|
||||||
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
|
|
||||||
call.Gas = gas
|
|
||||||
|
|
||||||
snapshot := b.pendingState.Snapshot()
|
|
||||||
res, err := b.callContract(ctx, call, b.pendingBlock.Header(), b.pendingState)
|
|
||||||
b.pendingState.RevertToSnapshot(snapshot)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, core.ErrIntrinsicGas) {
|
|
||||||
return true, nil, nil // Special case, raise gas limit
|
|
||||||
}
|
|
||||||
return true, nil, err // Bail out
|
|
||||||
}
|
|
||||||
return res.Failed(), res, nil
|
|
||||||
}
|
|
||||||
// Execute the binary search and hone in on an executable gas limit
|
|
||||||
for lo+1 < hi {
|
|
||||||
mid := (hi + lo) / 2
|
|
||||||
failed, _, err := executable(mid)
|
|
||||||
|
|
||||||
// If the error is not nil(consensus error), it means the provided message
|
|
||||||
// call or transaction will never be accepted no matter how much gas it is
|
|
||||||
// assigned. Return the error directly, don't struggle any more
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if failed {
|
|
||||||
lo = mid
|
|
||||||
} else {
|
|
||||||
hi = mid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Reject the transaction as invalid if it still fails at the highest allowance
|
|
||||||
if hi == cap {
|
|
||||||
failed, result, err := executable(hi)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if failed {
|
|
||||||
if result != nil && result.Err != vm.ErrOutOfGas {
|
|
||||||
if len(result.Revert()) > 0 {
|
|
||||||
return 0, newRevertError(result)
|
|
||||||
}
|
|
||||||
return 0, result.Err
|
|
||||||
}
|
|
||||||
// Otherwise, the specified gas cap is too low
|
|
||||||
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hi, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// callContract implements common code between normal and pending contract calls.
|
|
||||||
// state is modified during execution, make sure to copy it if necessary.
|
|
||||||
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, header *types.Header, stateDB *state.StateDB) (*core.ExecutionResult, error) {
|
|
||||||
// Gas prices post 1559 need to be initialized
|
|
||||||
if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) {
|
|
||||||
return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
|
||||||
}
|
|
||||||
head := b.blockchain.CurrentHeader()
|
|
||||||
if !b.blockchain.Config().IsLondon(head.Number) {
|
|
||||||
// If there's no basefee, then it must be a non-1559 execution
|
|
||||||
if call.GasPrice == nil {
|
|
||||||
call.GasPrice = new(big.Int)
|
|
||||||
}
|
|
||||||
call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice
|
|
||||||
} else {
|
|
||||||
// A basefee is provided, necessitating 1559-type execution
|
|
||||||
if call.GasPrice != nil {
|
|
||||||
// User specified the legacy gas field, convert to 1559 gas typing
|
|
||||||
call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice
|
|
||||||
} else {
|
|
||||||
// User specified 1559 gas fields (or none), use those
|
|
||||||
if call.GasFeeCap == nil {
|
|
||||||
call.GasFeeCap = new(big.Int)
|
|
||||||
}
|
|
||||||
if call.GasTipCap == nil {
|
|
||||||
call.GasTipCap = new(big.Int)
|
|
||||||
}
|
|
||||||
// Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
|
|
||||||
call.GasPrice = new(big.Int)
|
|
||||||
if call.GasFeeCap.BitLen() > 0 || call.GasTipCap.BitLen() > 0 {
|
|
||||||
call.GasPrice = math.BigMin(new(big.Int).Add(call.GasTipCap, head.BaseFee), call.GasFeeCap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Ensure message is initialized properly.
|
|
||||||
if call.Gas == 0 {
|
|
||||||
call.Gas = 50000000
|
|
||||||
}
|
|
||||||
if call.Value == nil {
|
|
||||||
call.Value = new(big.Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set infinite balance to the fake caller account.
|
|
||||||
from := stateDB.GetOrNewStateObject(call.From)
|
|
||||||
from.SetBalance(math.MaxBig256)
|
|
||||||
|
|
||||||
// Execute the call.
|
|
||||||
msg := &core.Message{
|
|
||||||
From: call.From,
|
|
||||||
To: call.To,
|
|
||||||
Value: call.Value,
|
|
||||||
GasLimit: call.Gas,
|
|
||||||
GasPrice: call.GasPrice,
|
|
||||||
GasFeeCap: call.GasFeeCap,
|
|
||||||
GasTipCap: call.GasTipCap,
|
|
||||||
Data: call.Data,
|
|
||||||
AccessList: call.AccessList,
|
|
||||||
SkipAccountChecks: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new environment which holds all relevant information
|
|
||||||
// about the transaction and calling mechanisms.
|
|
||||||
txContext := core.NewEVMTxContext(msg)
|
|
||||||
evmContext := core.NewEVMBlockContext(header, b.blockchain, nil)
|
|
||||||
vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true})
|
|
||||||
gasPool := new(core.GasPool).AddGas(math.MaxUint64)
|
|
||||||
|
|
||||||
return core.ApplyMessage(vmEnv, msg, gasPool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendTransaction updates the pending block to include the given transaction.
|
|
||||||
func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
// Get the last block
|
|
||||||
block, err := b.blockByHash(ctx, b.pendingBlock.ParentHash())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not fetch parent")
|
|
||||||
}
|
|
||||||
// Check transaction validity
|
|
||||||
signer := types.MakeSigner(b.blockchain.Config(), block.Number(), block.Time())
|
|
||||||
sender, err := types.Sender(signer, tx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid transaction: %v", err)
|
|
||||||
}
|
|
||||||
nonce := b.pendingState.GetNonce(sender)
|
|
||||||
if tx.Nonce() != nonce {
|
|
||||||
return fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce)
|
|
||||||
}
|
|
||||||
// Include tx in chain
|
|
||||||
blocks, receipts := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
|
|
||||||
for _, tx := range b.pendingBlock.Transactions() {
|
|
||||||
block.AddTxWithChain(b.blockchain, tx)
|
|
||||||
}
|
|
||||||
block.AddTxWithChain(b.blockchain, tx)
|
|
||||||
})
|
|
||||||
stateDB, _ := b.blockchain.State()
|
|
||||||
|
|
||||||
b.pendingBlock = blocks[0]
|
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil)
|
|
||||||
b.pendingReceipts = receipts[0]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterLogs executes a log filter operation, blocking during execution and
|
|
||||||
// returning all the results in one batch.
|
|
||||||
//
|
|
||||||
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
|
||||||
func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
|
|
||||||
var filter *filters.Filter
|
|
||||||
if query.BlockHash != nil {
|
|
||||||
// Block filter requested, construct a single-shot filter
|
|
||||||
filter = b.filterSystem.NewBlockFilter(*query.BlockHash, query.Addresses, query.Topics)
|
|
||||||
} else {
|
|
||||||
// Initialize unset filter boundaries to run from genesis to chain head
|
|
||||||
from := int64(0)
|
|
||||||
if query.FromBlock != nil {
|
|
||||||
from = query.FromBlock.Int64()
|
|
||||||
}
|
|
||||||
to := int64(-1)
|
|
||||||
if query.ToBlock != nil {
|
|
||||||
to = query.ToBlock.Int64()
|
|
||||||
}
|
|
||||||
// Construct the range filter
|
|
||||||
filter = b.filterSystem.NewRangeFilter(from, to, query.Addresses, query.Topics)
|
|
||||||
}
|
|
||||||
// Run the filter and return all the logs
|
|
||||||
logs, err := filter.Logs(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res := make([]types.Log, len(logs))
|
|
||||||
for i, nLog := range logs {
|
|
||||||
res[i] = *nLog
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscribeFilterLogs creates a background log filtering operation, returning a
|
|
||||||
// subscription immediately, which can be used to stream the found events.
|
|
||||||
func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
|
|
||||||
// Subscribe to contract events
|
|
||||||
sink := make(chan []*types.Log)
|
|
||||||
|
|
||||||
sub, err := b.events.SubscribeLogs(query, sink)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Since we're getting logs in batches, we need to flatten them into a plain stream
|
|
||||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case logs := <-sink:
|
|
||||||
for _, nlog := range logs {
|
|
||||||
select {
|
|
||||||
case ch <- *nlog:
|
|
||||||
case err := <-sub.Err():
|
|
||||||
return err
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case err := <-sub.Err():
|
|
||||||
return err
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscribeNewHead returns an event subscription for a new header.
|
|
||||||
func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
|
|
||||||
// subscribe to a new head
|
|
||||||
sink := make(chan *types.Header)
|
|
||||||
sub := b.events.SubscribeNewHeads(sink)
|
|
||||||
|
|
||||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case head := <-sink:
|
|
||||||
select {
|
|
||||||
case ch <- head:
|
|
||||||
case err := <-sub.Err():
|
|
||||||
return err
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
case err := <-sub.Err():
|
|
||||||
return err
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdjustTime adds a time shift to the simulated clock.
|
|
||||||
// It can only be called on empty blocks.
|
|
||||||
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
|
||||||
b.mu.Lock()
|
|
||||||
defer b.mu.Unlock()
|
|
||||||
|
|
||||||
if len(b.pendingBlock.Transactions()) != 0 {
|
|
||||||
return errors.New("Could not adjust time on non-empty block")
|
|
||||||
}
|
|
||||||
// Get the last block
|
|
||||||
block := b.blockchain.GetBlockByHash(b.pendingBlock.ParentHash())
|
|
||||||
if block == nil {
|
|
||||||
return fmt.Errorf("could not find parent")
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks, _ := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
|
|
||||||
block.OffsetTime(int64(adjustment.Seconds()))
|
|
||||||
})
|
|
||||||
stateDB, _ := b.blockchain.State()
|
|
||||||
|
|
||||||
b.pendingBlock = blocks[0]
|
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blockchain returns the underlying blockchain.
|
|
||||||
func (b *SimulatedBackend) Blockchain() *core.BlockChain {
|
|
||||||
return b.blockchain
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterBackend implements filters.Backend to support filtering for logs without
|
|
||||||
// taking bloom-bits acceleration structures into account.
|
|
||||||
type filterBackend struct {
|
|
||||||
db ethdb.Database
|
|
||||||
bc *core.BlockChain
|
|
||||||
backend *SimulatedBackend
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db }
|
|
||||||
|
|
||||||
func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") }
|
|
||||||
|
|
||||||
func (fb *filterBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
|
|
||||||
switch number {
|
|
||||||
case rpc.PendingBlockNumber:
|
|
||||||
if block := fb.backend.pendingBlock; block != nil {
|
|
||||||
return block.Header(), nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
case rpc.LatestBlockNumber:
|
|
||||||
return fb.bc.CurrentHeader(), nil
|
|
||||||
case rpc.FinalizedBlockNumber:
|
|
||||||
return fb.bc.CurrentFinalBlock(), nil
|
|
||||||
case rpc.SafeBlockNumber:
|
|
||||||
return fb.bc.CurrentSafeBlock(), nil
|
|
||||||
default:
|
|
||||||
return fb.bc.GetHeaderByNumber(uint64(number.Int64())), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
|
||||||
return fb.bc.GetHeaderByHash(hash), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {
|
|
||||||
if body := fb.bc.GetBody(hash); body != nil {
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("block body not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
|
|
||||||
return fb.backend.pendingBlock, fb.backend.pendingReceipts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
|
||||||
number := rawdb.ReadHeaderNumber(fb.db, hash)
|
|
||||||
if number == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
header := rawdb.ReadHeader(fb.db, hash, *number)
|
|
||||||
if header == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return rawdb.ReadReceipts(fb.db, hash, *number, header.Time, fb.bc.Config()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
|
|
||||||
logs := rawdb.ReadLogs(fb.db, hash, number, fb.bc.Config())
|
|
||||||
return logs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
|
||||||
return nullSubscription()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
|
||||||
return fb.bc.SubscribeChainEvent(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
|
|
||||||
return fb.bc.SubscribeRemovedLogsEvent(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
|
||||||
return fb.bc.SubscribeLogsEvent(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
|
||||||
return nullSubscription()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 }
|
|
||||||
|
|
||||||
func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) {
|
|
||||||
panic("not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) ChainConfig() *params.ChainConfig {
|
|
||||||
panic("not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fb *filterBackend) CurrentHeader() *types.Header {
|
|
||||||
panic("not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func nullSubscription() event.Subscription {
|
|
||||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
|
||||||
<-quit
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -48,6 +48,7 @@ type CallOpts struct {
|
|||||||
Pending bool // Whether to operate on the pending state or the last known one
|
Pending bool // Whether to operate on the pending state or the last known one
|
||||||
From common.Address // Optional the sender address, otherwise the first account is used
|
From common.Address // Optional the sender address, otherwise the first account is used
|
||||||
BlockNumber *big.Int // Optional the block number on which the call should be performed
|
BlockNumber *big.Int // Optional the block number on which the call should be performed
|
||||||
|
BlockHash common.Hash // Optional the block hash on which the call should be performed
|
||||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +190,23 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
|
|||||||
return ErrNoCode
|
return ErrNoCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if opts.BlockHash != (common.Hash{}) {
|
||||||
|
bh, ok := c.caller.(BlockHashContractCaller)
|
||||||
|
if !ok {
|
||||||
|
return ErrNoBlockHashState
|
||||||
|
}
|
||||||
|
output, err = bh.CallContractAtHash(ctx, msg, opts.BlockHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(output) == 0 {
|
||||||
|
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||||
|
if code, err = bh.CodeAtHash(ctx, c.address, opts.BlockHash); err != nil {
|
||||||
|
return err
|
||||||
|
} else if len(code) == 0 {
|
||||||
|
return ErrNoCode
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
|
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -220,7 +238,7 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// todo(rjl493456442) check the method is payable or not,
|
// todo(rjl493456442) check whether the method is payable or not,
|
||||||
// reject invalid transaction at the first place
|
// reject invalid transaction at the first place
|
||||||
return c.transact(opts, &c.address, input)
|
return c.transact(opts, &c.address, input)
|
||||||
}
|
}
|
||||||
@@ -228,7 +246,7 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in
|
|||||||
// RawTransact initiates a transaction with the given raw calldata as the input.
|
// RawTransact initiates a transaction with the given raw calldata as the input.
|
||||||
// It's usually used to initiate transactions for invoking **Fallback** function.
|
// It's usually used to initiate transactions for invoking **Fallback** function.
|
||||||
func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) {
|
func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) {
|
||||||
// todo(rjl493456442) check the method is payable or not,
|
// todo(rjl493456442) check whether the method is payable or not,
|
||||||
// reject invalid transaction at the first place
|
// reject invalid transaction at the first place
|
||||||
return c.transact(opts, &c.address, calldata)
|
return c.transact(opts, &c.address, calldata)
|
||||||
}
|
}
|
||||||
@@ -443,7 +461,7 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
sub, err := event.NewSubscription(func(quit <-chan struct{}) error {
|
sub := event.NewSubscription(func(quit <-chan struct{}) error {
|
||||||
for _, log := range buff {
|
for _, log := range buff {
|
||||||
select {
|
select {
|
||||||
case logs <- log:
|
case logs <- log:
|
||||||
@@ -452,11 +470,8 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}), nil
|
})
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return logs, sub, nil
|
return logs, sub, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,28 @@ func (mc *mockPendingCaller) PendingCallContract(ctx context.Context, call ether
|
|||||||
return mc.pendingCallContractBytes, mc.pendingCallContractErr
|
return mc.pendingCallContractBytes, mc.pendingCallContractErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockBlockHashCaller struct {
|
||||||
|
*mockCaller
|
||||||
|
codeAtHashBytes []byte
|
||||||
|
codeAtHashErr error
|
||||||
|
codeAtHashCalled bool
|
||||||
|
callContractAtHashCalled bool
|
||||||
|
callContractAtHashBytes []byte
|
||||||
|
callContractAtHashErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockBlockHashCaller) CodeAtHash(ctx context.Context, contract common.Address, hash common.Hash) ([]byte, error) {
|
||||||
|
mc.codeAtHashCalled = true
|
||||||
|
return mc.codeAtHashBytes, mc.codeAtHashErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockBlockHashCaller) CallContractAtHash(ctx context.Context, call ethereum.CallMsg, hash common.Hash) ([]byte, error) {
|
||||||
|
mc.callContractAtHashCalled = true
|
||||||
|
return mc.callContractAtHashBytes, mc.callContractAtHashErr
|
||||||
|
}
|
||||||
|
|
||||||
func TestPassingBlockNumber(t *testing.T) {
|
func TestPassingBlockNumber(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mc := &mockPendingCaller{
|
mc := &mockPendingCaller{
|
||||||
mockCaller: &mockCaller{
|
mockCaller: &mockCaller{
|
||||||
codeAtBytes: []byte{1, 2, 3},
|
codeAtBytes: []byte{1, 2, 3},
|
||||||
@@ -166,6 +187,7 @@ func TestPassingBlockNumber(t *testing.T) {
|
|||||||
const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158"
|
const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158"
|
||||||
|
|
||||||
func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
|
func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
hash := crypto.Keccak256Hash([]byte("testName"))
|
hash := crypto.Keccak256Hash([]byte("testName"))
|
||||||
topics := []common.Hash{
|
topics := []common.Hash{
|
||||||
crypto.Keccak256Hash([]byte("received(string,address,uint256,bytes)")),
|
crypto.Keccak256Hash([]byte("received(string,address,uint256,bytes)")),
|
||||||
@@ -187,6 +209,7 @@ func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackAnonymousLogIntoMap(t *testing.T) {
|
func TestUnpackAnonymousLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mockLog := newMockLog(nil, common.HexToHash("0x0"))
|
mockLog := newMockLog(nil, common.HexToHash("0x0"))
|
||||||
|
|
||||||
abiString := `[{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"received","type":"event"}]`
|
abiString := `[{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"received","type":"event"}]`
|
||||||
@@ -204,6 +227,7 @@ func TestUnpackAnonymousLogIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
|
func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"})
|
sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -229,6 +253,7 @@ func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
|
func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")})
|
arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -254,6 +279,7 @@ func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
|
func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")
|
mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")
|
||||||
addrBytes := mockAddress.Bytes()
|
addrBytes := mockAddress.Bytes()
|
||||||
hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)"))
|
hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)"))
|
||||||
@@ -280,6 +306,7 @@ func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
|
func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
bytes := []byte{1, 2, 3, 4, 5}
|
bytes := []byte{1, 2, 3, 4, 5}
|
||||||
hash := crypto.Keccak256Hash(bytes)
|
hash := crypto.Keccak256Hash(bytes)
|
||||||
topics := []common.Hash{
|
topics := []common.Hash{
|
||||||
@@ -302,6 +329,7 @@ func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTransactGasFee(t *testing.T) {
|
func TestTransactGasFee(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
// GasTipCap and GasFeeCap
|
// GasTipCap and GasFeeCap
|
||||||
@@ -377,6 +405,7 @@ func newMockLog(topics []common.Hash, txHash common.Hash) types.Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCall(t *testing.T) {
|
func TestCall(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var method, methodWithArg = "something", "somethingArrrrg"
|
var method, methodWithArg = "something", "somethingArrrrg"
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name, method string
|
name, method string
|
||||||
@@ -400,6 +429,15 @@ func TestCall(t *testing.T) {
|
|||||||
Pending: true,
|
Pending: true,
|
||||||
},
|
},
|
||||||
method: method,
|
method: method,
|
||||||
|
}, {
|
||||||
|
name: "ok hash",
|
||||||
|
mc: &mockBlockHashCaller{
|
||||||
|
codeAtHashBytes: []byte{0},
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
}, {
|
}, {
|
||||||
name: "pack error, no method",
|
name: "pack error, no method",
|
||||||
mc: new(mockCaller),
|
mc: new(mockCaller),
|
||||||
@@ -413,6 +451,14 @@ func TestCall(t *testing.T) {
|
|||||||
},
|
},
|
||||||
method: method,
|
method: method,
|
||||||
wantErrExact: bind.ErrNoPendingState,
|
wantErrExact: bind.ErrNoPendingState,
|
||||||
|
}, {
|
||||||
|
name: "interface error, blockHash but not a BlockHashContractCaller",
|
||||||
|
mc: new(mockCaller),
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoBlockHashState,
|
||||||
}, {
|
}, {
|
||||||
name: "pending call canceled",
|
name: "pending call canceled",
|
||||||
mc: &mockPendingCaller{
|
mc: &mockPendingCaller{
|
||||||
@@ -460,6 +506,34 @@ func TestCall(t *testing.T) {
|
|||||||
mc: new(mockCaller),
|
mc: new(mockCaller),
|
||||||
method: method,
|
method: method,
|
||||||
wantErrExact: bind.ErrNoCode,
|
wantErrExact: bind.ErrNoCode,
|
||||||
|
}, {
|
||||||
|
name: "call contract at hash error",
|
||||||
|
mc: &mockBlockHashCaller{
|
||||||
|
callContractAtHashErr: context.DeadlineExceeded,
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: context.DeadlineExceeded,
|
||||||
|
}, {
|
||||||
|
name: "code at error",
|
||||||
|
mc: &mockBlockHashCaller{
|
||||||
|
codeAtHashErr: errors.New(""),
|
||||||
|
},
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
name: "no code at hash",
|
||||||
|
mc: new(mockBlockHashCaller),
|
||||||
|
opts: &bind.CallOpts{
|
||||||
|
BlockHash: common.Hash{0xaa},
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
wantErrExact: bind.ErrNoCode,
|
||||||
}, {
|
}, {
|
||||||
name: "unpack error missing arg",
|
name: "unpack error missing arg",
|
||||||
mc: &mockCaller{
|
mc: &mockCaller{
|
||||||
@@ -507,6 +581,7 @@ func TestCall(t *testing.T) {
|
|||||||
|
|
||||||
// TestCrashers contains some strings which previously caused the abi codec to crash.
|
// TestCrashers contains some strings which previously caused the abi codec to crash.
|
||||||
func TestCrashers(t *testing.T) {
|
func TestCrashers(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"_1"}]}]}]`))
|
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"_1"}]}]}]`))
|
||||||
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"&"}]}]}]`))
|
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"&"}]}]}]`))
|
||||||
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`))
|
abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`))
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func isKeyWord(arg string) bool {
|
|||||||
|
|
||||||
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
|
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
|
||||||
// to be used as is in client code, but rather as an intermediate struct which
|
// to be used as is in client code, but rather as an intermediate struct which
|
||||||
// enforces compile time type safety and naming convention opposed to having to
|
// enforces compile time type safety and naming convention as opposed to having to
|
||||||
// manually maintain hard coded strings that break on runtime.
|
// manually maintain hard coded strings that break on runtime.
|
||||||
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) {
|
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) {
|
||||||
var (
|
var (
|
||||||
@@ -363,7 +363,7 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
|||||||
// parameters that are not value types i.e. arrays and structs are not
|
// parameters that are not value types i.e. arrays and structs are not
|
||||||
// stored directly but instead a keccak256-hash of an encoding is stored.
|
// stored directly but instead a keccak256-hash of an encoding is stored.
|
||||||
//
|
//
|
||||||
// We only convert stringS and bytes to hash, still need to deal with
|
// We only convert strings and bytes to hash, still need to deal with
|
||||||
// array(both fixed-size and dynamic-size) and struct.
|
// array(both fixed-size and dynamic-size) and struct.
|
||||||
if bound == "string" || bound == "[]byte" {
|
if bound == "string" || bound == "[]byte" {
|
||||||
bound = "common.Hash"
|
bound = "common.Hash"
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@@ -297,7 +297,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy an interaction tester contract and call a transaction on it
|
// Deploy an interaction tester contract and call a transaction on it
|
||||||
@@ -305,6 +305,7 @@ var bindTests = []struct {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to deploy interactor contract: %v", err)
|
t.Fatalf("Failed to deploy interactor contract: %v", err)
|
||||||
}
|
}
|
||||||
|
sim.Commit()
|
||||||
if _, err := interactor.Transact(auth, "Transact string"); err != nil {
|
if _, err := interactor.Transact(auth, "Transact string"); err != nil {
|
||||||
t.Fatalf("Failed to transact with interactor contract: %v", err)
|
t.Fatalf("Failed to transact with interactor contract: %v", err)
|
||||||
}
|
}
|
||||||
@@ -344,7 +345,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@@ -352,7 +353,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a tuple tester contract and execute a structured call on it
|
// Deploy a tuple tester contract and execute a structured call on it
|
||||||
@@ -390,7 +391,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@@ -398,7 +399,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a tuple tester contract and execute a structured call on it
|
// Deploy a tuple tester contract and execute a structured call on it
|
||||||
@@ -448,7 +449,7 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@@ -456,7 +457,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a slice tester contract and execute a n array call on it
|
// Deploy a slice tester contract and execute a n array call on it
|
||||||
@@ -496,7 +497,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@@ -504,7 +505,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a default method invoker contract and execute its default method
|
// Deploy a default method invoker contract and execute its default method
|
||||||
@@ -512,6 +513,7 @@ var bindTests = []struct {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to deploy defaulter contract: %v", err)
|
t.Fatalf("Failed to deploy defaulter contract: %v", err)
|
||||||
}
|
}
|
||||||
|
sim.Commit()
|
||||||
if _, err := (&DefaulterRaw{defaulter}).Transfer(auth); err != nil {
|
if _, err := (&DefaulterRaw{defaulter}).Transfer(auth); err != nil {
|
||||||
t.Fatalf("Failed to invoke default method: %v", err)
|
t.Fatalf("Failed to invoke default method: %v", err)
|
||||||
}
|
}
|
||||||
@@ -562,7 +564,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@@ -570,7 +572,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a structs method invoker contract and execute its default method
|
// Deploy a structs method invoker contract and execute its default method
|
||||||
@@ -608,12 +610,12 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
// Create a simulator and wrap a non-deployed contract
|
// Create a simulator and wrap a non-deployed contract
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{}, uint64(10000000000))
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{}, uint64(10000000000))
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
nonexistent, err := NewNonExistent(common.Address{}, sim)
|
nonexistent, err := NewNonExistent(common.Address{}, sim)
|
||||||
@@ -647,12 +649,12 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
// Create a simulator and wrap a non-deployed contract
|
// Create a simulator and wrap a non-deployed contract
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{}, uint64(10000000000))
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{}, uint64(10000000000))
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
nonexistent, err := NewNonExistentStruct(common.Address{}, sim)
|
nonexistent, err := NewNonExistentStruct(common.Address{}, sim)
|
||||||
@@ -694,7 +696,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@@ -702,7 +704,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a funky gas pattern contract
|
// Deploy a funky gas pattern contract
|
||||||
@@ -744,7 +746,7 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@@ -752,7 +754,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a sender tester contract and execute a structured call on it
|
// Deploy a sender tester contract and execute a structured call on it
|
||||||
@@ -819,7 +821,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@@ -827,7 +829,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a underscorer tester contract and execute a structured call on it
|
// Deploy a underscorer tester contract and execute a structured call on it
|
||||||
@@ -913,7 +915,7 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@@ -921,7 +923,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy an eventer contract
|
// Deploy an eventer contract
|
||||||
@@ -1103,7 +1105,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@@ -1111,7 +1113,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
//deploy the test contract
|
//deploy the test contract
|
||||||
@@ -1238,7 +1240,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
|
|
||||||
@@ -1246,7 +1248,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
_, _, contract, err := DeployTuple(auth, sim)
|
_, _, contract, err := DeployTuple(auth, sim)
|
||||||
@@ -1380,7 +1382,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@@ -1388,7 +1390,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
//deploy the test contract
|
//deploy the test contract
|
||||||
@@ -1446,14 +1448,14 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
// Initialize test accounts
|
// Initialize test accounts
|
||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// deploy the test contract
|
// deploy the test contract
|
||||||
@@ -1535,7 +1537,7 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
// Initialize test accounts
|
// Initialize test accounts
|
||||||
@@ -1543,7 +1545,7 @@ var bindTests = []struct {
|
|||||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
|
||||||
// Deploy registrar contract
|
// Deploy registrar contract
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
@@ -1598,14 +1600,14 @@ var bindTests = []struct {
|
|||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
|
||||||
// Deploy registrar contract
|
// Deploy registrar contract
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
@@ -1659,7 +1661,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
@@ -1667,7 +1669,7 @@ var bindTests = []struct {
|
|||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000000000)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
// Deploy a tester contract and execute a structured call on it
|
// Deploy a tester contract and execute a structured call on it
|
||||||
@@ -1677,7 +1679,7 @@ var bindTests = []struct {
|
|||||||
}
|
}
|
||||||
sim.Commit()
|
sim.Commit()
|
||||||
|
|
||||||
// This test the existence of the free retreiver call for view and pure functions
|
// This test the existence of the free retriever call for view and pure functions
|
||||||
if num, err := pav.PureFunc(nil); err != nil {
|
if num, err := pav.PureFunc(nil); err != nil {
|
||||||
t.Fatalf("Failed to call anonymous field retriever: %v", err)
|
t.Fatalf("Failed to call anonymous field retriever: %v", err)
|
||||||
} else if num.Cmp(big.NewInt(42)) != 0 {
|
} else if num.Cmp(big.NewInt(42)) != 0 {
|
||||||
@@ -1720,14 +1722,14 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
`,
|
`,
|
||||||
`
|
`
|
||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
|
||||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 1000000)
|
sim := backends.NewSimulatedBackend(types.GenesisAlloc{addr: {Balance: big.NewInt(10000000000000000)}}, 1000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
@@ -1808,7 +1810,7 @@ var bindTests = []struct {
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
`,
|
`,
|
||||||
@@ -1816,7 +1818,7 @@ var bindTests = []struct {
|
|||||||
var (
|
var (
|
||||||
key, _ = crypto.GenerateKey()
|
key, _ = crypto.GenerateKey()
|
||||||
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
||||||
)
|
)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
@@ -1874,11 +1876,12 @@ var bindTests = []struct {
|
|||||||
[]string{"0x6080604052348015600f57600080fd5b5060998061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063726c638214602d575b600080fd5b60336035565b005b60405163024876cd60e61b815260016004820152600260248201526003604482015260640160405180910390fdfea264697066735822122093f786a1bc60216540cd999fbb4a6109e0fef20abcff6e9107fb2817ca968f3c64736f6c63430008070033"},
|
[]string{"0x6080604052348015600f57600080fd5b5060998061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063726c638214602d575b600080fd5b60336035565b005b60405163024876cd60e61b815260016004820152600260248201526003604482015260640160405180910390fdfea264697066735822122093f786a1bc60216540cd999fbb4a6109e0fef20abcff6e9107fb2817ca968f3c64736f6c63430008070033"},
|
||||||
[]string{`[{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError1","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError2","type":"error"},{"inputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"},{"internalType":"uint256","name":"c","type":"uint256"}],"name":"MyError3","type":"error"},{"inputs":[],"name":"Error","outputs":[],"stateMutability":"pure","type":"function"}]`},
|
[]string{`[{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError1","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError2","type":"error"},{"inputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"},{"internalType":"uint256","name":"c","type":"uint256"}],"name":"MyError3","type":"error"},{"inputs":[],"name":"Error","outputs":[],"stateMutability":"pure","type":"function"}]`},
|
||||||
`
|
`
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
`,
|
`,
|
||||||
@@ -1886,7 +1889,7 @@ var bindTests = []struct {
|
|||||||
var (
|
var (
|
||||||
key, _ = crypto.GenerateKey()
|
key, _ = crypto.GenerateKey()
|
||||||
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
||||||
)
|
)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
@@ -1895,7 +1898,7 @@ var bindTests = []struct {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
sim.Commit()
|
sim.Commit()
|
||||||
_, err = bind.WaitDeployed(nil, sim, tx)
|
_, err = bind.WaitDeployed(context.Background(), sim, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -1926,11 +1929,12 @@ var bindTests = []struct {
|
|||||||
bytecode: []string{`0x608060405234801561001057600080fd5b506040516101c43803806101c48339818101604052810190610032919061014a565b50610177565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100958261004c565b810181811067ffffffffffffffff821117156100b4576100b361005d565b5b80604052505050565b60006100c7610038565b90506100d3828261008c565b919050565b6000819050919050565b6100eb816100d8565b81146100f657600080fd5b50565b600081519050610108816100e2565b92915050565b60006020828403121561012457610123610047565b5b61012e60206100bd565b9050600061013e848285016100f9565b60008301525092915050565b6000602082840312156101605761015f610042565b5b600061016e8482850161010e565b91505092915050565b603f806101856000396000f3fe6080604052600080fdfea2646970667358221220cdffa667affecefac5561f65f4a4ba914204a8d4eb859d8cd426fb306e5c12a364736f6c634300080a0033`},
|
bytecode: []string{`0x608060405234801561001057600080fd5b506040516101c43803806101c48339818101604052810190610032919061014a565b50610177565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100958261004c565b810181811067ffffffffffffffff821117156100b4576100b361005d565b5b80604052505050565b60006100c7610038565b90506100d3828261008c565b919050565b6000819050919050565b6100eb816100d8565b81146100f657600080fd5b50565b600081519050610108816100e2565b92915050565b60006020828403121561012457610123610047565b5b61012e60206100bd565b9050600061013e848285016100f9565b60008301525092915050565b6000602082840312156101605761015f610042565b5b600061016e8482850161010e565b91505092915050565b603f806101856000396000f3fe6080604052600080fdfea2646970667358221220cdffa667affecefac5561f65f4a4ba914204a8d4eb859d8cd426fb306e5c12a364736f6c634300080a0033`},
|
||||||
abi: []string{`[{"inputs":[{"components":[{"internalType":"uint256","name":"field","type":"uint256"}],"internalType":"struct ConstructorWithStructParam.StructType","name":"st","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"}]`},
|
abi: []string{`[{"inputs":[{"components":[{"internalType":"uint256","name":"field","type":"uint256"}],"internalType":"struct ConstructorWithStructParam.StructType","name":"st","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"}]`},
|
||||||
imports: `
|
imports: `
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
`,
|
`,
|
||||||
@@ -1938,7 +1942,7 @@ var bindTests = []struct {
|
|||||||
var (
|
var (
|
||||||
key, _ = crypto.GenerateKey()
|
key, _ = crypto.GenerateKey()
|
||||||
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
||||||
)
|
)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
@@ -1948,7 +1952,7 @@ var bindTests = []struct {
|
|||||||
}
|
}
|
||||||
sim.Commit()
|
sim.Commit()
|
||||||
|
|
||||||
if _, err = bind.WaitDeployed(nil, sim, tx); err != nil {
|
if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil {
|
||||||
t.Logf("Deployment tx: %+v", tx)
|
t.Logf("Deployment tx: %+v", tx)
|
||||||
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)
|
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)
|
||||||
}
|
}
|
||||||
@@ -1974,11 +1978,12 @@ var bindTests = []struct {
|
|||||||
bytecode: []string{"0x608060405234801561001057600080fd5b5061042b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063c2bb515f1461003b578063cce7b04814610059575b600080fd5b610043610075565b60405161005091906101af565b60405180910390f35b610073600480360381019061006e91906103ac565b6100b5565b005b61007d6100b8565b604051806040016040528060405180602001604052806000815250815260200160405180602001604052806000815250815250905090565b50565b604051806040016040528060608152602001606081525090565b600081519050919050565b600082825260208201905092915050565b60005b8381101561010c5780820151818401526020810190506100f1565b8381111561011b576000848401525b50505050565b6000601f19601f8301169050919050565b600061013d826100d2565b61014781856100dd565b93506101578185602086016100ee565b61016081610121565b840191505092915050565b600060408301600083015184820360008601526101888282610132565b915050602083015184820360208601526101a28282610132565b9150508091505092915050565b600060208201905081810360008301526101c9818461016b565b905092915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61022282610121565b810181811067ffffffffffffffff82111715610241576102406101ea565b5b80604052505050565b60006102546101d1565b90506102608282610219565b919050565b600080fd5b600080fd5b600080fd5b600067ffffffffffffffff82111561028f5761028e6101ea565b5b61029882610121565b9050602081019050919050565b82818337600083830152505050565b60006102c76102c284610274565b61024a565b9050828152602081018484840111156102e3576102e261026f565b5b6102ee8482856102a5565b509392505050565b600082601f83011261030b5761030a61026a565b5b813561031b8482602086016102b4565b91505092915050565b60006040828403121561033a576103396101e5565b5b610344604061024a565b9050600082013567ffffffffffffffff81111561036457610363610265565b5b610370848285016102f6565b600083015250602082013567ffffffffffffffff81111561039457610393610265565b5b6103a0848285016102f6565b60208301525092915050565b6000602082840312156103c2576103c16101db565b5b600082013567ffffffffffffffff8111156103e0576103df6101e0565b5b6103ec84828501610324565b9150509291505056fea264697066735822122033bca1606af9b6aeba1673f98c52003cec19338539fb44b86690ce82c51483b564736f6c634300080e0033"},
|
bytecode: []string{"0x608060405234801561001057600080fd5b5061042b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063c2bb515f1461003b578063cce7b04814610059575b600080fd5b610043610075565b60405161005091906101af565b60405180910390f35b610073600480360381019061006e91906103ac565b6100b5565b005b61007d6100b8565b604051806040016040528060405180602001604052806000815250815260200160405180602001604052806000815250815250905090565b50565b604051806040016040528060608152602001606081525090565b600081519050919050565b600082825260208201905092915050565b60005b8381101561010c5780820151818401526020810190506100f1565b8381111561011b576000848401525b50505050565b6000601f19601f8301169050919050565b600061013d826100d2565b61014781856100dd565b93506101578185602086016100ee565b61016081610121565b840191505092915050565b600060408301600083015184820360008601526101888282610132565b915050602083015184820360208601526101a28282610132565b9150508091505092915050565b600060208201905081810360008301526101c9818461016b565b905092915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61022282610121565b810181811067ffffffffffffffff82111715610241576102406101ea565b5b80604052505050565b60006102546101d1565b90506102608282610219565b919050565b600080fd5b600080fd5b600080fd5b600067ffffffffffffffff82111561028f5761028e6101ea565b5b61029882610121565b9050602081019050919050565b82818337600083830152505050565b60006102c76102c284610274565b61024a565b9050828152602081018484840111156102e3576102e261026f565b5b6102ee8482856102a5565b509392505050565b600082601f83011261030b5761030a61026a565b5b813561031b8482602086016102b4565b91505092915050565b60006040828403121561033a576103396101e5565b5b610344604061024a565b9050600082013567ffffffffffffffff81111561036457610363610265565b5b610370848285016102f6565b600083015250602082013567ffffffffffffffff81111561039457610393610265565b5b6103a0848285016102f6565b60208301525092915050565b6000602082840312156103c2576103c16101db565b5b600082013567ffffffffffffffff8111156103e0576103df6101e0565b5b6103ec84828501610324565b9150509291505056fea264697066735822122033bca1606af9b6aeba1673f98c52003cec19338539fb44b86690ce82c51483b564736f6c634300080e0033"},
|
||||||
abi: []string{`[ { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "int256", "name": "msg", "type": "int256" }, { "indexed": false, "internalType": "int256", "name": "_msg", "type": "int256" } ], "name": "log", "type": "event" }, { "inputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "req", "type": "tuple" } ], "name": "addRequest", "outputs": [], "stateMutability": "pure", "type": "function" }, { "inputs": [], "name": "getRequest", "outputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "", "type": "tuple" } ], "stateMutability": "pure", "type": "function" } ]`},
|
abi: []string{`[ { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "int256", "name": "msg", "type": "int256" }, { "indexed": false, "internalType": "int256", "name": "_msg", "type": "int256" } ], "name": "log", "type": "event" }, { "inputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "req", "type": "tuple" } ], "name": "addRequest", "outputs": [], "stateMutability": "pure", "type": "function" }, { "inputs": [], "name": "getRequest", "outputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "", "type": "tuple" } ], "stateMutability": "pure", "type": "function" } ]`},
|
||||||
imports: `
|
imports: `
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
`,
|
`,
|
||||||
@@ -1986,7 +1991,7 @@ var bindTests = []struct {
|
|||||||
var (
|
var (
|
||||||
key, _ = crypto.GenerateKey()
|
key, _ = crypto.GenerateKey()
|
||||||
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
||||||
)
|
)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
|
|
||||||
@@ -1996,7 +2001,7 @@ var bindTests = []struct {
|
|||||||
}
|
}
|
||||||
sim.Commit()
|
sim.Commit()
|
||||||
|
|
||||||
if _, err = bind.WaitDeployed(nil, sim, tx); err != nil {
|
if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil {
|
||||||
t.Logf("Deployment tx: %+v", tx)
|
t.Logf("Deployment tx: %+v", tx)
|
||||||
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)
|
t.Errorf("bind.WaitDeployed(nil, %T, <deployment tx>) got err %v; want nil err", sim, err)
|
||||||
}
|
}
|
||||||
@@ -2014,11 +2019,12 @@ var bindTests = []struct {
|
|||||||
bytecode: []string{"0x608060405234801561001057600080fd5b5060dc8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063527a119f14602d575b600080fd5b60436004803603810190603f9190605b565b6045565b005b50565b6000813590506055816092565b92915050565b600060208284031215606e57606d608d565b5b6000607a848285016048565b91505092915050565b6000819050919050565b600080fd5b6099816083565b811460a357600080fd5b5056fea2646970667358221220d4f4525e2615516394055d369fb17df41c359e5e962734f27fd683ea81fd9db164736f6c63430008070033"},
|
bytecode: []string{"0x608060405234801561001057600080fd5b5060dc8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063527a119f14602d575b600080fd5b60436004803603810190603f9190605b565b6045565b005b50565b6000813590506055816092565b92915050565b600060208284031215606e57606d608d565b5b6000607a848285016048565b91505092915050565b6000819050919050565b600080fd5b6099816083565b811460a357600080fd5b5056fea2646970667358221220d4f4525e2615516394055d369fb17df41c359e5e962734f27fd683ea81fd9db164736f6c63430008070033"},
|
||||||
abi: []string{`[{"inputs":[{"internalType":"uint256","name":"range","type":"uint256"}],"name":"functionWithKeywordParameter","outputs":[],"stateMutability":"pure","type":"function"}]`},
|
abi: []string{`[{"inputs":[{"internalType":"uint256","name":"range","type":"uint256"}],"name":"functionWithKeywordParameter","outputs":[],"stateMutability":"pure","type":"function"}]`},
|
||||||
imports: `
|
imports: `
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
`,
|
`,
|
||||||
@@ -2026,7 +2032,7 @@ var bindTests = []struct {
|
|||||||
var (
|
var (
|
||||||
key, _ = crypto.GenerateKey()
|
key, _ = crypto.GenerateKey()
|
||||||
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
user, _ = bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
|
||||||
sim = backends.NewSimulatedBackend(core.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
sim = backends.NewSimulatedBackend(types.GenesisAlloc{user.From: {Balance: big.NewInt(1000000000000000000)}}, ethconfig.Defaults.Miner.GasCeil)
|
||||||
)
|
)
|
||||||
_, tx, _, err := DeployRangeKeyword(user, sim)
|
_, tx, _, err := DeployRangeKeyword(user, sim)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2034,7 +2040,7 @@ var bindTests = []struct {
|
|||||||
}
|
}
|
||||||
sim.Commit()
|
sim.Commit()
|
||||||
|
|
||||||
if _, err = bind.WaitDeployed(nil, sim, tx); err != nil {
|
if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil {
|
||||||
t.Errorf("error deploying the contract: %v", err)
|
t.Errorf("error deploying the contract: %v", err)
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -2067,6 +2073,7 @@ var bindTests = []struct {
|
|||||||
// Tests that packages generated by the binder can be successfully compiled and
|
// Tests that packages generated by the binder can be successfully compiled and
|
||||||
// the requested tester run against it.
|
// the requested tester run against it.
|
||||||
func TestGolangBindings(t *testing.T) {
|
func TestGolangBindings(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// Skip the test if no Go command can be found
|
// Skip the test if no Go command can be found
|
||||||
gocmd := runtime.GOROOT() + "/bin/go"
|
gocmd := runtime.GOROOT() + "/bin/go"
|
||||||
if !common.FileExist(gocmd) {
|
if !common.FileExist(gocmd) {
|
||||||
|
|||||||
487
accounts/abi/bind/source.go.tpl
Normal file
487
accounts/abi/bind/source.go.tpl
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
// Code generated - DO NOT EDIT.
|
||||||
|
// This file is a generated binding and any manual changes will be lost.
|
||||||
|
|
||||||
|
package {{.Package}}
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
ethereum "github.com/ethereum/go-ethereum"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var (
|
||||||
|
_ = errors.New
|
||||||
|
_ = big.NewInt
|
||||||
|
_ = strings.NewReader
|
||||||
|
_ = ethereum.NotFound
|
||||||
|
_ = bind.Bind
|
||||||
|
_ = common.Big1
|
||||||
|
_ = types.BloomLookup
|
||||||
|
_ = event.NewSubscription
|
||||||
|
_ = abi.ConvertType
|
||||||
|
)
|
||||||
|
|
||||||
|
{{$structs := .Structs}}
|
||||||
|
{{range $structs}}
|
||||||
|
// {{.Name}} is an auto generated low-level Go binding around an user-defined struct.
|
||||||
|
type {{.Name}} struct {
|
||||||
|
{{range $field := .Fields}}
|
||||||
|
{{$field.Name}} {{$field.Type}}{{end}}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range $contract := .Contracts}}
|
||||||
|
// {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract.
|
||||||
|
var {{.Type}}MetaData = &bind.MetaData{
|
||||||
|
ABI: "{{.InputABI}}",
|
||||||
|
{{if $contract.FuncSigs -}}
|
||||||
|
Sigs: map[string]string{
|
||||||
|
{{range $strsig, $binsig := .FuncSigs}}"{{$binsig}}": "{{$strsig}}",
|
||||||
|
{{end}}
|
||||||
|
},
|
||||||
|
{{end -}}
|
||||||
|
{{if .InputBin -}}
|
||||||
|
Bin: "0x{{.InputBin}}",
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
// {{.Type}}ABI is the input ABI used to generate the binding from.
|
||||||
|
// Deprecated: Use {{.Type}}MetaData.ABI instead.
|
||||||
|
var {{.Type}}ABI = {{.Type}}MetaData.ABI
|
||||||
|
|
||||||
|
{{if $contract.FuncSigs}}
|
||||||
|
// Deprecated: Use {{.Type}}MetaData.Sigs instead.
|
||||||
|
// {{.Type}}FuncSigs maps the 4-byte function signature to its string representation.
|
||||||
|
var {{.Type}}FuncSigs = {{.Type}}MetaData.Sigs
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .InputBin}}
|
||||||
|
// {{.Type}}Bin is the compiled bytecode used for deploying new contracts.
|
||||||
|
// Deprecated: Use {{.Type}}MetaData.Bin instead.
|
||||||
|
var {{.Type}}Bin = {{.Type}}MetaData.Bin
|
||||||
|
|
||||||
|
// Deploy{{.Type}} deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
|
||||||
|
func Deploy{{.Type}}(auth *bind.TransactOpts, backend bind.ContractBackend {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type $structs}}{{end}}) (common.Address, *types.Transaction, *{{.Type}}, error) {
|
||||||
|
parsed, err := {{.Type}}MetaData.GetAbi()
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, nil, err
|
||||||
|
}
|
||||||
|
if parsed == nil {
|
||||||
|
return common.Address{}, nil, nil, errors.New("GetABI returned nil")
|
||||||
|
}
|
||||||
|
{{range $pattern, $name := .Libraries}}
|
||||||
|
{{decapitalise $name}}Addr, _, _, _ := Deploy{{capitalise $name}}(auth, backend)
|
||||||
|
{{$contract.Type}}Bin = strings.ReplaceAll({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:])
|
||||||
|
{{end}}
|
||||||
|
address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}})
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, nil, err
|
||||||
|
}
|
||||||
|
return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}} struct {
|
||||||
|
{{.Type}}Caller // Read-only binding to the contract
|
||||||
|
{{.Type}}Transactor // Write-only binding to the contract
|
||||||
|
{{.Type}}Filterer // Log filterer for contract events
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}Caller struct {
|
||||||
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Transactor is an auto generated write-only Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}Transactor struct {
|
||||||
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Filterer is an auto generated log filtering Go binding around an Ethereum contract events.
|
||||||
|
type {{.Type}}Filterer struct {
|
||||||
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Session is an auto generated Go binding around an Ethereum contract,
|
||||||
|
// with pre-set call and transact options.
|
||||||
|
type {{.Type}}Session struct {
|
||||||
|
Contract *{{.Type}} // Generic contract binding to set the session for
|
||||||
|
CallOpts bind.CallOpts // Call options to use throughout this session
|
||||||
|
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}CallerSession is an auto generated read-only Go binding around an Ethereum contract,
|
||||||
|
// with pre-set call options.
|
||||||
|
type {{.Type}}CallerSession struct {
|
||||||
|
Contract *{{.Type}}Caller // Generic contract caller binding to set the session for
|
||||||
|
CallOpts bind.CallOpts // Call options to use throughout this session
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}TransactorSession is an auto generated write-only Go binding around an Ethereum contract,
|
||||||
|
// with pre-set transact options.
|
||||||
|
type {{.Type}}TransactorSession struct {
|
||||||
|
Contract *{{.Type}}Transactor // Generic contract transactor binding to set the session for
|
||||||
|
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Raw is an auto generated low-level Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}Raw struct {
|
||||||
|
Contract *{{.Type}} // Generic contract binding to access the raw methods on
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}CallerRaw struct {
|
||||||
|
Contract *{{.Type}}Caller // Generic read-only contract binding to access the raw methods on
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}TransactorRaw struct {
|
||||||
|
Contract *{{.Type}}Transactor // Generic write-only contract binding to access the raw methods on
|
||||||
|
}
|
||||||
|
|
||||||
|
// New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) {
|
||||||
|
contract, err := bind{{.Type}}(address, backend, backend, backend)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) {
|
||||||
|
contract, err := bind{{.Type}}(address, caller, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{.Type}}Caller{contract: contract}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) {
|
||||||
|
contract, err := bind{{.Type}}(address, nil, transactor, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{.Type}}Transactor{contract: contract}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New{{.Type}}Filterer creates a new log filterer instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
func New{{.Type}}Filterer(address common.Address, filterer bind.ContractFilterer) (*{{.Type}}Filterer, error) {
|
||||||
|
contract, err := bind{{.Type}}(address, nil, nil, filterer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{.Type}}Filterer{contract: contract}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind{{.Type}} binds a generic wrapper to an already deployed contract.
|
||||||
|
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
|
||||||
|
parsed, err := {{.Type}}MetaData.GetAbi()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call invokes the (constant) contract method with params as input values and
|
||||||
|
// sets the output to result. The result type might be a single field for simple
|
||||||
|
// returns, a slice of interfaces for anonymous returns and a struct for named
|
||||||
|
// returns.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
|
||||||
|
return _{{$contract.Type}}.Contract.{{$contract.Type}}Caller.contract.Call(opts, result, method, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||||
|
// its default method if one is available.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transfer(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transact invokes the (paid) contract method with params as input values.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transact(opts, method, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call invokes the (constant) contract method with params as input values and
|
||||||
|
// sets the output to result. The result type might be a single field for simple
|
||||||
|
// returns, a slice of interfaces for anonymous returns and a struct for named
|
||||||
|
// returns.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
|
||||||
|
return _{{$contract.Type}}.Contract.contract.Call(opts, result, method, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||||
|
// its default method if one is available.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.contract.Transfer(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transact invokes the (paid) contract method with params as input values.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.contract.Transact(opts, method, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
{{range .Calls}}
|
||||||
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) {
|
||||||
|
var out []interface{}
|
||||||
|
err := _{{$contract.Type}}.contract.Call(opts, &out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
{{if .Structured}}
|
||||||
|
outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} })
|
||||||
|
if err != nil {
|
||||||
|
return *outstruct, err
|
||||||
|
}
|
||||||
|
{{range $i, $t := .Normalized.Outputs}}
|
||||||
|
outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}}
|
||||||
|
|
||||||
|
return *outstruct, err
|
||||||
|
{{else}}
|
||||||
|
if err != nil {
|
||||||
|
return {{range $i, $_ := .Normalized.Outputs}}*new({{bindtype .Type $structs}}), {{end}} err
|
||||||
|
}
|
||||||
|
{{range $i, $t := .Normalized.Outputs}}
|
||||||
|
out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}}
|
||||||
|
|
||||||
|
return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range .Transacts}}
|
||||||
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.contract.Transact(opts, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .Fallback}}
|
||||||
|
// Fallback is a paid mutator transaction binding the contract fallback function.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Fallback.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.contract.RawTransact(opts, calldata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback is a paid mutator transaction binding the contract fallback function.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Fallback.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Session) Fallback(calldata []byte) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback is a paid mutator transaction binding the contract fallback function.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Fallback.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Fallback(calldata []byte) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata)
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .Receive}}
|
||||||
|
// Receive is a paid mutator transaction binding the contract receive function.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Receive.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.contract.RawTransact(opts, nil) // calldata is disallowed for receive function
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive is a paid mutator transaction binding the contract receive function.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Receive.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Session) Receive() (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive is a paid mutator transaction binding the contract receive function.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Receive.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Receive() (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts)
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range .Events}}
|
||||||
|
// {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract.
|
||||||
|
type {{$contract.Type}}{{.Normalized.Name}}Iterator struct {
|
||||||
|
Event *{{$contract.Type}}{{.Normalized.Name}} // Event containing the contract specifics and raw log
|
||||||
|
|
||||||
|
contract *bind.BoundContract // Generic contract to use for unpacking event data
|
||||||
|
event string // Event name to use for unpacking event data
|
||||||
|
|
||||||
|
logs chan types.Log // Log channel receiving the found contract events
|
||||||
|
sub ethereum.Subscription // Subscription for errors, completion and termination
|
||||||
|
done bool // Whether the subscription completed delivering logs
|
||||||
|
fail error // Occurred error to stop iteration
|
||||||
|
}
|
||||||
|
// Next advances the iterator to the subsequent event, returning whether there
|
||||||
|
// are any more events found. In case of a retrieval or parsing error, false is
|
||||||
|
// returned and Error() can be queried for the exact failure.
|
||||||
|
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Next() bool {
|
||||||
|
// If the iterator failed, stop iterating
|
||||||
|
if (it.fail != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// If the iterator completed, deliver directly whatever's available
|
||||||
|
if (it.done) {
|
||||||
|
select {
|
||||||
|
case log := <-it.logs:
|
||||||
|
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
|
||||||
|
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
||||||
|
it.fail = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
it.Event.Raw = log
|
||||||
|
return true
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Iterator still in progress, wait for either a data or an error event
|
||||||
|
select {
|
||||||
|
case log := <-it.logs:
|
||||||
|
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
|
||||||
|
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
||||||
|
it.fail = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
it.Event.Raw = log
|
||||||
|
return true
|
||||||
|
|
||||||
|
case err := <-it.sub.Err():
|
||||||
|
it.done = true
|
||||||
|
it.fail = err
|
||||||
|
return it.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Error returns any retrieval or parsing error occurred during filtering.
|
||||||
|
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Error() error {
|
||||||
|
return it.fail
|
||||||
|
}
|
||||||
|
// Close terminates the iteration process, releasing any pending underlying
|
||||||
|
// resources.
|
||||||
|
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Close() error {
|
||||||
|
it.sub.Unsubscribe()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract.
|
||||||
|
type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}}
|
||||||
|
{{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type $structs}}{{else}}{{bindtype .Type $structs}}{{end}}; {{end}}
|
||||||
|
Raw types.Log // Blockchain specific contextual infos
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) {
|
||||||
|
{{range .Normalized.Inputs}}
|
||||||
|
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
||||||
|
for _, {{.Name}}Item := range {{.Name}} {
|
||||||
|
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
||||||
|
}{{end}}{{end}}
|
||||||
|
|
||||||
|
logs, sub, err := _{{$contract.Type}}.contract.FilterLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{$contract.Type}}{{.Normalized.Name}}Iterator{contract: _{{$contract.Type}}.contract, event: "{{.Original.Name}}", logs: logs, sub: sub}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (event.Subscription, error) {
|
||||||
|
{{range .Normalized.Inputs}}
|
||||||
|
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
||||||
|
for _, {{.Name}}Item := range {{.Name}} {
|
||||||
|
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
||||||
|
}{{end}}{{end}}
|
||||||
|
|
||||||
|
logs, sub, err := _{{$contract.Type}}.contract.WatchLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case log := <-logs:
|
||||||
|
// New log arrived, parse the event and forward to the user
|
||||||
|
event := new({{$contract.Type}}{{.Normalized.Name}})
|
||||||
|
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
event.Raw = log
|
||||||
|
|
||||||
|
select {
|
||||||
|
case sink <- event:
|
||||||
|
case err := <-sub.Err():
|
||||||
|
return err
|
||||||
|
case <-quit:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case err := <-sub.Err():
|
||||||
|
return err
|
||||||
|
case <-quit:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
|
||||||
|
event := new({{$contract.Type}}{{.Normalized.Name}})
|
||||||
|
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
event.Raw = log
|
||||||
|
return event, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
@@ -16,7 +16,11 @@
|
|||||||
|
|
||||||
package bind
|
package bind
|
||||||
|
|
||||||
import "github.com/ethereum/go-ethereum/accounts/abi"
|
import (
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
|
)
|
||||||
|
|
||||||
// tmplData is the data structure required to fill the binding template.
|
// tmplData is the data structure required to fill the binding template.
|
||||||
type tmplData struct {
|
type tmplData struct {
|
||||||
@@ -80,492 +84,6 @@ var tmplSource = map[Lang]string{
|
|||||||
|
|
||||||
// tmplSourceGo is the Go source template that the generated Go contract binding
|
// tmplSourceGo is the Go source template that the generated Go contract binding
|
||||||
// is based on.
|
// is based on.
|
||||||
const tmplSourceGo = `
|
//
|
||||||
// Code generated - DO NOT EDIT.
|
//go:embed source.go.tpl
|
||||||
// This file is a generated binding and any manual changes will be lost.
|
var tmplSourceGo string
|
||||||
|
|
||||||
package {{.Package}}
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
ethereum "github.com/ethereum/go-ethereum"
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reference imports to suppress errors if they are not otherwise used.
|
|
||||||
var (
|
|
||||||
_ = errors.New
|
|
||||||
_ = big.NewInt
|
|
||||||
_ = strings.NewReader
|
|
||||||
_ = ethereum.NotFound
|
|
||||||
_ = bind.Bind
|
|
||||||
_ = common.Big1
|
|
||||||
_ = types.BloomLookup
|
|
||||||
_ = event.NewSubscription
|
|
||||||
_ = abi.ConvertType
|
|
||||||
)
|
|
||||||
|
|
||||||
{{$structs := .Structs}}
|
|
||||||
{{range $structs}}
|
|
||||||
// {{.Name}} is an auto generated low-level Go binding around an user-defined struct.
|
|
||||||
type {{.Name}} struct {
|
|
||||||
{{range $field := .Fields}}
|
|
||||||
{{$field.Name}} {{$field.Type}}{{end}}
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{range $contract := .Contracts}}
|
|
||||||
// {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract.
|
|
||||||
var {{.Type}}MetaData = &bind.MetaData{
|
|
||||||
ABI: "{{.InputABI}}",
|
|
||||||
{{if $contract.FuncSigs -}}
|
|
||||||
Sigs: map[string]string{
|
|
||||||
{{range $strsig, $binsig := .FuncSigs}}"{{$binsig}}": "{{$strsig}}",
|
|
||||||
{{end}}
|
|
||||||
},
|
|
||||||
{{end -}}
|
|
||||||
{{if .InputBin -}}
|
|
||||||
Bin: "0x{{.InputBin}}",
|
|
||||||
{{end}}
|
|
||||||
}
|
|
||||||
// {{.Type}}ABI is the input ABI used to generate the binding from.
|
|
||||||
// Deprecated: Use {{.Type}}MetaData.ABI instead.
|
|
||||||
var {{.Type}}ABI = {{.Type}}MetaData.ABI
|
|
||||||
|
|
||||||
{{if $contract.FuncSigs}}
|
|
||||||
// Deprecated: Use {{.Type}}MetaData.Sigs instead.
|
|
||||||
// {{.Type}}FuncSigs maps the 4-byte function signature to its string representation.
|
|
||||||
var {{.Type}}FuncSigs = {{.Type}}MetaData.Sigs
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if .InputBin}}
|
|
||||||
// {{.Type}}Bin is the compiled bytecode used for deploying new contracts.
|
|
||||||
// Deprecated: Use {{.Type}}MetaData.Bin instead.
|
|
||||||
var {{.Type}}Bin = {{.Type}}MetaData.Bin
|
|
||||||
|
|
||||||
// Deploy{{.Type}} deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
|
|
||||||
func Deploy{{.Type}}(auth *bind.TransactOpts, backend bind.ContractBackend {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type $structs}}{{end}}) (common.Address, *types.Transaction, *{{.Type}}, error) {
|
|
||||||
parsed, err := {{.Type}}MetaData.GetAbi()
|
|
||||||
if err != nil {
|
|
||||||
return common.Address{}, nil, nil, err
|
|
||||||
}
|
|
||||||
if parsed == nil {
|
|
||||||
return common.Address{}, nil, nil, errors.New("GetABI returned nil")
|
|
||||||
}
|
|
||||||
{{range $pattern, $name := .Libraries}}
|
|
||||||
{{decapitalise $name}}Addr, _, _, _ := Deploy{{capitalise $name}}(auth, backend)
|
|
||||||
{{$contract.Type}}Bin = strings.ReplaceAll({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:])
|
|
||||||
{{end}}
|
|
||||||
address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}})
|
|
||||||
if err != nil {
|
|
||||||
return common.Address{}, nil, nil, err
|
|
||||||
}
|
|
||||||
return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
|
|
||||||
type {{.Type}} struct {
|
|
||||||
{{.Type}}Caller // Read-only binding to the contract
|
|
||||||
{{.Type}}Transactor // Write-only binding to the contract
|
|
||||||
{{.Type}}Filterer // Log filterer for contract events
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract.
|
|
||||||
type {{.Type}}Caller struct {
|
|
||||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}Transactor is an auto generated write-only Go binding around an Ethereum contract.
|
|
||||||
type {{.Type}}Transactor struct {
|
|
||||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}Filterer is an auto generated log filtering Go binding around an Ethereum contract events.
|
|
||||||
type {{.Type}}Filterer struct {
|
|
||||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}Session is an auto generated Go binding around an Ethereum contract,
|
|
||||||
// with pre-set call and transact options.
|
|
||||||
type {{.Type}}Session struct {
|
|
||||||
Contract *{{.Type}} // Generic contract binding to set the session for
|
|
||||||
CallOpts bind.CallOpts // Call options to use throughout this session
|
|
||||||
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}CallerSession is an auto generated read-only Go binding around an Ethereum contract,
|
|
||||||
// with pre-set call options.
|
|
||||||
type {{.Type}}CallerSession struct {
|
|
||||||
Contract *{{.Type}}Caller // Generic contract caller binding to set the session for
|
|
||||||
CallOpts bind.CallOpts // Call options to use throughout this session
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}TransactorSession is an auto generated write-only Go binding around an Ethereum contract,
|
|
||||||
// with pre-set transact options.
|
|
||||||
type {{.Type}}TransactorSession struct {
|
|
||||||
Contract *{{.Type}}Transactor // Generic contract transactor binding to set the session for
|
|
||||||
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}Raw is an auto generated low-level Go binding around an Ethereum contract.
|
|
||||||
type {{.Type}}Raw struct {
|
|
||||||
Contract *{{.Type}} // Generic contract binding to access the raw methods on
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
|
|
||||||
type {{.Type}}CallerRaw struct {
|
|
||||||
Contract *{{.Type}}Caller // Generic read-only contract binding to access the raw methods on
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Type}}TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
|
|
||||||
type {{.Type}}TransactorRaw struct {
|
|
||||||
Contract *{{.Type}}Transactor // Generic write-only contract binding to access the raw methods on
|
|
||||||
}
|
|
||||||
|
|
||||||
// New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract.
|
|
||||||
func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) {
|
|
||||||
contract, err := bind{{.Type}}(address, backend, backend, backend)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract.
|
|
||||||
func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) {
|
|
||||||
contract, err := bind{{.Type}}(address, caller, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &{{.Type}}Caller{contract: contract}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract.
|
|
||||||
func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) {
|
|
||||||
contract, err := bind{{.Type}}(address, nil, transactor, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &{{.Type}}Transactor{contract: contract}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// New{{.Type}}Filterer creates a new log filterer instance of {{.Type}}, bound to a specific deployed contract.
|
|
||||||
func New{{.Type}}Filterer(address common.Address, filterer bind.ContractFilterer) (*{{.Type}}Filterer, error) {
|
|
||||||
contract, err := bind{{.Type}}(address, nil, nil, filterer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &{{.Type}}Filterer{contract: contract}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// bind{{.Type}} binds a generic wrapper to an already deployed contract.
|
|
||||||
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
|
|
||||||
parsed, err := {{.Type}}MetaData.GetAbi()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call invokes the (constant) contract method with params as input values and
|
|
||||||
// sets the output to result. The result type might be a single field for simple
|
|
||||||
// returns, a slice of interfaces for anonymous returns and a struct for named
|
|
||||||
// returns.
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
|
|
||||||
return _{{$contract.Type}}.Contract.{{$contract.Type}}Caller.contract.Call(opts, result, method, params...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
|
||||||
// its default method if one is available.
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transfer(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transact invokes the (paid) contract method with params as input values.
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transact(opts, method, params...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call invokes the (constant) contract method with params as input values and
|
|
||||||
// sets the output to result. The result type might be a single field for simple
|
|
||||||
// returns, a slice of interfaces for anonymous returns and a struct for named
|
|
||||||
// returns.
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
|
|
||||||
return _{{$contract.Type}}.Contract.contract.Call(opts, result, method, params...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
|
||||||
// its default method if one is available.
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.contract.Transfer(opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transact invokes the (paid) contract method with params as input values.
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.contract.Transact(opts, method, params...)
|
|
||||||
}
|
|
||||||
|
|
||||||
{{range .Calls}}
|
|
||||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) {
|
|
||||||
var out []interface{}
|
|
||||||
err := _{{$contract.Type}}.contract.Call(opts, &out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
|
||||||
{{if .Structured}}
|
|
||||||
outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} })
|
|
||||||
if err != nil {
|
|
||||||
return *outstruct, err
|
|
||||||
}
|
|
||||||
{{range $i, $t := .Normalized.Outputs}}
|
|
||||||
outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}}
|
|
||||||
|
|
||||||
return *outstruct, err
|
|
||||||
{{else}}
|
|
||||||
if err != nil {
|
|
||||||
return {{range $i, $_ := .Normalized.Outputs}}*new({{bindtype .Type $structs}}), {{end}} err
|
|
||||||
}
|
|
||||||
{{range $i, $t := .Normalized.Outputs}}
|
|
||||||
out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}}
|
|
||||||
|
|
||||||
return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err
|
|
||||||
{{end}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) {
|
|
||||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) {
|
|
||||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{range .Transacts}}
|
|
||||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.contract.Transact(opts, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if .Fallback}}
|
|
||||||
// Fallback is a paid mutator transaction binding the contract fallback function.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Fallback.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.contract.RawTransact(opts, calldata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback is a paid mutator transaction binding the contract fallback function.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Fallback.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Session) Fallback(calldata []byte) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback is a paid mutator transaction binding the contract fallback function.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Fallback.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Fallback(calldata []byte) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata)
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if .Receive}}
|
|
||||||
// Receive is a paid mutator transaction binding the contract receive function.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Receive.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.contract.RawTransact(opts, nil) // calldata is disallowed for receive function
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive is a paid mutator transaction binding the contract receive function.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Receive.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Session) Receive() (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive is a paid mutator transaction binding the contract receive function.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Receive.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Receive() (*types.Transaction, error) {
|
|
||||||
return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts)
|
|
||||||
}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{range .Events}}
|
|
||||||
// {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract.
|
|
||||||
type {{$contract.Type}}{{.Normalized.Name}}Iterator struct {
|
|
||||||
Event *{{$contract.Type}}{{.Normalized.Name}} // Event containing the contract specifics and raw log
|
|
||||||
|
|
||||||
contract *bind.BoundContract // Generic contract to use for unpacking event data
|
|
||||||
event string // Event name to use for unpacking event data
|
|
||||||
|
|
||||||
logs chan types.Log // Log channel receiving the found contract events
|
|
||||||
sub ethereum.Subscription // Subscription for errors, completion and termination
|
|
||||||
done bool // Whether the subscription completed delivering logs
|
|
||||||
fail error // Occurred error to stop iteration
|
|
||||||
}
|
|
||||||
// Next advances the iterator to the subsequent event, returning whether there
|
|
||||||
// are any more events found. In case of a retrieval or parsing error, false is
|
|
||||||
// returned and Error() can be queried for the exact failure.
|
|
||||||
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Next() bool {
|
|
||||||
// If the iterator failed, stop iterating
|
|
||||||
if (it.fail != nil) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// If the iterator completed, deliver directly whatever's available
|
|
||||||
if (it.done) {
|
|
||||||
select {
|
|
||||||
case log := <-it.logs:
|
|
||||||
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
|
|
||||||
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
|
||||||
it.fail = err
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
it.Event.Raw = log
|
|
||||||
return true
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Iterator still in progress, wait for either a data or an error event
|
|
||||||
select {
|
|
||||||
case log := <-it.logs:
|
|
||||||
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
|
|
||||||
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
|
||||||
it.fail = err
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
it.Event.Raw = log
|
|
||||||
return true
|
|
||||||
|
|
||||||
case err := <-it.sub.Err():
|
|
||||||
it.done = true
|
|
||||||
it.fail = err
|
|
||||||
return it.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Error returns any retrieval or parsing error occurred during filtering.
|
|
||||||
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Error() error {
|
|
||||||
return it.fail
|
|
||||||
}
|
|
||||||
// Close terminates the iteration process, releasing any pending underlying
|
|
||||||
// resources.
|
|
||||||
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Close() error {
|
|
||||||
it.sub.Unsubscribe()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract.
|
|
||||||
type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}}
|
|
||||||
{{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type $structs}}{{else}}{{bindtype .Type $structs}}{{end}}; {{end}}
|
|
||||||
Raw types.Log // Blockchain specific contextual infos
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.ID}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) {
|
|
||||||
{{range .Normalized.Inputs}}
|
|
||||||
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
|
||||||
for _, {{.Name}}Item := range {{.Name}} {
|
|
||||||
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
|
||||||
}{{end}}{{end}}
|
|
||||||
|
|
||||||
logs, sub, err := _{{$contract.Type}}.contract.FilterLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &{{$contract.Type}}{{.Normalized.Name}}Iterator{contract: _{{$contract.Type}}.contract, event: "{{.Original.Name}}", logs: logs, sub: sub}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.ID}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (event.Subscription, error) {
|
|
||||||
{{range .Normalized.Inputs}}
|
|
||||||
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
|
||||||
for _, {{.Name}}Item := range {{.Name}} {
|
|
||||||
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
|
||||||
}{{end}}{{end}}
|
|
||||||
|
|
||||||
logs, sub, err := _{{$contract.Type}}.contract.WatchLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case log := <-logs:
|
|
||||||
// New log arrived, parse the event and forward to the user
|
|
||||||
event := new({{$contract.Type}}{{.Normalized.Name}})
|
|
||||||
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
event.Raw = log
|
|
||||||
|
|
||||||
select {
|
|
||||||
case sink <- event:
|
|
||||||
case err := <-sub.Err():
|
|
||||||
return err
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
case err := <-sub.Err():
|
|
||||||
return err
|
|
||||||
case <-quit:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.ID}}.
|
|
||||||
//
|
|
||||||
// Solidity: {{.Original.String}}
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
|
|
||||||
event := new({{$contract.Type}}{{.Normalized.Name}})
|
|
||||||
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
event.Raw = log
|
|
||||||
return event, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
`
|
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/ethclient/simulated"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
@@ -53,21 +53,21 @@ var waitDeployedTests = map[string]struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWaitDeployed(t *testing.T) {
|
func TestWaitDeployed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
for name, test := range waitDeployedTests {
|
for name, test := range waitDeployedTests {
|
||||||
backend := backends.NewSimulatedBackend(
|
backend := simulated.NewBackend(
|
||||||
core.GenesisAlloc{
|
types.GenesisAlloc{
|
||||||
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
|
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
|
||||||
},
|
},
|
||||||
10000000,
|
|
||||||
)
|
)
|
||||||
defer backend.Close()
|
defer backend.Close()
|
||||||
|
|
||||||
// Create the transaction
|
// Create the transaction
|
||||||
head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
head, _ := backend.Client().HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
||||||
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
|
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(params.GWei))
|
||||||
|
|
||||||
tx := types.NewContractCreation(0, big.NewInt(0), test.gas, gasPrice, common.FromHex(test.code))
|
tx := types.NewContractCreation(0, big.NewInt(0), test.gas, gasPrice, common.FromHex(test.code))
|
||||||
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
tx, _ = types.SignTx(tx, types.LatestSignerForChainID(big.NewInt(1337)), testKey)
|
||||||
|
|
||||||
// Wait for it to get mined in the background.
|
// Wait for it to get mined in the background.
|
||||||
var (
|
var (
|
||||||
@@ -77,12 +77,12 @@ func TestWaitDeployed(t *testing.T) {
|
|||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
address, err = bind.WaitDeployed(ctx, backend, tx)
|
address, err = bind.WaitDeployed(ctx, backend.Client(), tx)
|
||||||
close(mined)
|
close(mined)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Send and mine the transaction.
|
// Send and mine the transaction.
|
||||||
backend.SendTransaction(ctx, tx)
|
backend.Client().SendTransaction(ctx, tx)
|
||||||
backend.Commit()
|
backend.Commit()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@@ -100,41 +100,40 @@ func TestWaitDeployed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWaitDeployedCornerCases(t *testing.T) {
|
func TestWaitDeployedCornerCases(t *testing.T) {
|
||||||
backend := backends.NewSimulatedBackend(
|
backend := simulated.NewBackend(
|
||||||
core.GenesisAlloc{
|
types.GenesisAlloc{
|
||||||
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
|
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
|
||||||
},
|
},
|
||||||
10000000,
|
|
||||||
)
|
)
|
||||||
defer backend.Close()
|
defer backend.Close()
|
||||||
|
|
||||||
head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
head, _ := backend.Client().HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
||||||
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
|
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
|
||||||
|
|
||||||
// Create a transaction to an account.
|
// Create a transaction to an account.
|
||||||
code := "6060604052600a8060106000396000f360606040526008565b00"
|
code := "6060604052600a8060106000396000f360606040526008565b00"
|
||||||
tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
|
tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
|
||||||
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
backend.SendTransaction(ctx, tx)
|
backend.Client().SendTransaction(ctx, tx)
|
||||||
backend.Commit()
|
backend.Commit()
|
||||||
notContentCreation := errors.New("tx is not contract creation")
|
notContractCreation := errors.New("tx is not contract creation")
|
||||||
if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != notContentCreation.Error() {
|
if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != notContractCreation.Error() {
|
||||||
t.Errorf("error missmatch: want %q, got %q, ", notContentCreation, err)
|
t.Errorf("error mismatch: want %q, got %q, ", notContractCreation, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a transaction that is not mined.
|
// Create a transaction that is not mined.
|
||||||
tx = types.NewContractCreation(1, big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
|
tx = types.NewContractCreation(1, big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
|
||||||
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
contextCanceled := errors.New("context canceled")
|
contextCanceled := errors.New("context canceled")
|
||||||
if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != contextCanceled.Error() {
|
if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != contextCanceled.Error() {
|
||||||
t.Errorf("error missmatch: want %q, got %q, ", contextCanceled, err)
|
t.Errorf("error mismatch: want %q, got %q, ", contextCanceled, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
backend.SendTransaction(ctx, tx)
|
backend.Client().SendTransaction(ctx, tx)
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package abi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -32,7 +31,7 @@ type Error struct {
|
|||||||
str string
|
str string
|
||||||
|
|
||||||
// Sig contains the string signature according to the ABI spec.
|
// Sig contains the string signature according to the ABI spec.
|
||||||
// e.g. error foo(uint32 a, int b) = "foo(uint32,int256)"
|
// e.g. error foo(uint32 a, int b) = "foo(uint32,int256)"
|
||||||
// Please note that "int" is substitute for its canonical representation "int256"
|
// Please note that "int" is substitute for its canonical representation "int256"
|
||||||
Sig string
|
Sig string
|
||||||
|
|
||||||
@@ -84,10 +83,10 @@ func (e Error) String() string {
|
|||||||
|
|
||||||
func (e *Error) Unpack(data []byte) (interface{}, error) {
|
func (e *Error) Unpack(data []byte) (interface{}, error) {
|
||||||
if len(data) < 4 {
|
if len(data) < 4 {
|
||||||
return "", errors.New("invalid data for unpacking")
|
return "", fmt.Errorf("insufficient data for unpacking: have %d, want at least 4", len(data))
|
||||||
}
|
}
|
||||||
if !bytes.Equal(data[:4], e.ID[:4]) {
|
if !bytes.Equal(data[:4], e.ID[:4]) {
|
||||||
return "", errors.New("invalid data for unpacking")
|
return "", fmt.Errorf("invalid identifier, have %#x want %#x", data[:4], e.ID[:4])
|
||||||
}
|
}
|
||||||
return e.Inputs.Unpack(data[4:])
|
return e.Inputs.Unpack(data[4:])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa
|
|||||||
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
|
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
|
||||||
|
|
||||||
func TestEventId(t *testing.T) {
|
func TestEventId(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var table = []struct {
|
var table = []struct {
|
||||||
definition string
|
definition string
|
||||||
expectations map[string]common.Hash
|
expectations map[string]common.Hash
|
||||||
@@ -112,6 +113,7 @@ func TestEventId(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEventString(t *testing.T) {
|
func TestEventString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var table = []struct {
|
var table = []struct {
|
||||||
definition string
|
definition string
|
||||||
expectations map[string]string
|
expectations map[string]string
|
||||||
@@ -146,6 +148,7 @@ func TestEventString(t *testing.T) {
|
|||||||
|
|
||||||
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
|
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
|
||||||
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -161,6 +164,7 @@ func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEventTupleUnpack(t *testing.T) {
|
func TestEventTupleUnpack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
type EventTransfer struct {
|
type EventTransfer struct {
|
||||||
Value *big.Int
|
Value *big.Int
|
||||||
}
|
}
|
||||||
@@ -351,6 +355,7 @@ func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, ass
|
|||||||
|
|
||||||
// TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder.
|
// TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder.
|
||||||
func TestEventUnpackIndexed(t *testing.T) {
|
func TestEventUnpackIndexed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
||||||
type testStruct struct {
|
type testStruct struct {
|
||||||
Value1 uint8 // indexed
|
Value1 uint8 // indexed
|
||||||
@@ -368,6 +373,7 @@ func TestEventUnpackIndexed(t *testing.T) {
|
|||||||
|
|
||||||
// TestEventIndexedWithArrayUnpack verifies that decoder will not overflow when static array is indexed input.
|
// TestEventIndexedWithArrayUnpack verifies that decoder will not overflow when static array is indexed input.
|
||||||
func TestEventIndexedWithArrayUnpack(t *testing.T) {
|
func TestEventIndexedWithArrayUnpack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]`
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]`
|
||||||
type testStruct struct {
|
type testStruct struct {
|
||||||
Value1 [2]uint8 // indexed
|
Value1 [2]uint8 // indexed
|
||||||
|
|||||||
@@ -117,24 +117,23 @@ func NewMethod(name string, rawName string, funType FunctionType, mutability str
|
|||||||
sig = fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ","))
|
sig = fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ","))
|
||||||
id = crypto.Keccak256([]byte(sig))[:4]
|
id = crypto.Keccak256([]byte(sig))[:4]
|
||||||
}
|
}
|
||||||
// Extract meaningful state mutability of solidity method.
|
|
||||||
// If it's default value, never print it.
|
|
||||||
state := mutability
|
|
||||||
if state == "nonpayable" {
|
|
||||||
state = ""
|
|
||||||
}
|
|
||||||
if state != "" {
|
|
||||||
state = state + " "
|
|
||||||
}
|
|
||||||
identity := fmt.Sprintf("function %v", rawName)
|
identity := fmt.Sprintf("function %v", rawName)
|
||||||
if funType == Fallback {
|
switch funType {
|
||||||
|
case Fallback:
|
||||||
identity = "fallback"
|
identity = "fallback"
|
||||||
} else if funType == Receive {
|
case Receive:
|
||||||
identity = "receive"
|
identity = "receive"
|
||||||
} else if funType == Constructor {
|
case Constructor:
|
||||||
identity = "constructor"
|
identity = "constructor"
|
||||||
}
|
}
|
||||||
str := fmt.Sprintf("%v(%v) %sreturns(%v)", identity, strings.Join(inputNames, ", "), state, strings.Join(outputNames, ", "))
|
var str string
|
||||||
|
// Extract meaningful state mutability of solidity method.
|
||||||
|
// If it's empty string or default value "nonpayable", never print it.
|
||||||
|
if mutability == "" || mutability == "nonpayable" {
|
||||||
|
str = fmt.Sprintf("%v(%v) returns(%v)", identity, strings.Join(inputNames, ", "), strings.Join(outputNames, ", "))
|
||||||
|
} else {
|
||||||
|
str = fmt.Sprintf("%v(%v) %s returns(%v)", identity, strings.Join(inputNames, ", "), mutability, strings.Join(outputNames, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
return Method{
|
return Method{
|
||||||
Name: name,
|
Name: name,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const methoddata = `
|
|||||||
]`
|
]`
|
||||||
|
|
||||||
func TestMethodString(t *testing.T) {
|
func TestMethodString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var table = []struct {
|
var table = []struct {
|
||||||
method string
|
method string
|
||||||
expectation string
|
expectation string
|
||||||
@@ -84,11 +85,12 @@ func TestMethodString(t *testing.T) {
|
|||||||
|
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
var got string
|
var got string
|
||||||
if test.method == "fallback" {
|
switch test.method {
|
||||||
|
case "fallback":
|
||||||
got = abi.Fallback.String()
|
got = abi.Fallback.String()
|
||||||
} else if test.method == "receive" {
|
case "receive":
|
||||||
got = abi.Receive.String()
|
got = abi.Receive.String()
|
||||||
} else {
|
default:
|
||||||
got = abi.Methods[test.method].String()
|
got = abi.Methods[test.method].String()
|
||||||
}
|
}
|
||||||
if got != test.expectation {
|
if got != test.expectation {
|
||||||
@@ -98,6 +100,7 @@ func TestMethodString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMethodSig(t *testing.T) {
|
func TestMethodSig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var cases = []struct {
|
var cases = []struct {
|
||||||
method string
|
method string
|
||||||
expect string
|
expect string
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) {
|
|||||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||||
}
|
}
|
||||||
if reflectValue.Type() != reflect.TypeOf([]byte{}) {
|
if reflectValue.Type() != reflect.TypeOf([]byte{}) {
|
||||||
return []byte{}, errors.New("Bytes type is neither slice nor array")
|
return []byte{}, errors.New("bytes type is neither slice nor array")
|
||||||
}
|
}
|
||||||
return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil
|
return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil
|
||||||
case FixedBytesTy, FunctionTy:
|
case FixedBytesTy, FunctionTy:
|
||||||
@@ -66,7 +66,7 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return common.RightPadBytes(reflectValue.Bytes(), 32), nil
|
return common.RightPadBytes(reflectValue.Bytes(), 32), nil
|
||||||
default:
|
default:
|
||||||
return []byte{}, fmt.Errorf("Could not pack element, unknown type: %v", t.T)
|
return []byte{}, fmt.Errorf("could not pack element, unknown type: %v", t.T)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,11 @@ import (
|
|||||||
|
|
||||||
// TestPack tests the general pack/unpack tests in packing_test.go
|
// TestPack tests the general pack/unpack tests in packing_test.go
|
||||||
func TestPack(t *testing.T) {
|
func TestPack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
for i, test := range packUnpackTests {
|
for i, test := range packUnpackTests {
|
||||||
|
i, test := i, test
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
encb, err := hex.DecodeString(test.packed)
|
encb, err := hex.DecodeString(test.packed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("invalid hex %s: %v", test.packed, err)
|
t.Fatalf("invalid hex %s: %v", test.packed, err)
|
||||||
@@ -57,6 +60,7 @@ func TestPack(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMethodPack(t *testing.T) {
|
func TestMethodPack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi, err := JSON(strings.NewReader(jsondata))
|
abi, err := JSON(strings.NewReader(jsondata))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -177,6 +181,7 @@ func TestMethodPack(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPackNumber(t *testing.T) {
|
func TestPackNumber(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
value reflect.Value
|
value reflect.Value
|
||||||
packed []byte
|
packed []byte
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConvertType converts an interface of a runtime type into a interface of the
|
// ConvertType converts an interface of a runtime type into an interface of the
|
||||||
// given type, e.g. turn this code:
|
// given type, e.g. turn this code:
|
||||||
//
|
//
|
||||||
// var fields []reflect.StructField
|
// var fields []reflect.StructField
|
||||||
@@ -33,7 +33,7 @@ import (
|
|||||||
// Name: "X",
|
// Name: "X",
|
||||||
// Type: reflect.TypeOf(new(big.Int)),
|
// Type: reflect.TypeOf(new(big.Int)),
|
||||||
// Tag: reflect.StructTag("json:\"" + "x" + "\""),
|
// Tag: reflect.StructTag("json:\"" + "x" + "\""),
|
||||||
// }
|
// })
|
||||||
//
|
//
|
||||||
// into:
|
// into:
|
||||||
//
|
//
|
||||||
@@ -134,7 +134,7 @@ func setSlice(dst, src reflect.Value) error {
|
|||||||
dst.Set(slice)
|
dst.Set(slice)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New("Cannot set slice, destination not settable")
|
return errors.New("cannot set slice, destination not settable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setArray(dst, src reflect.Value) error {
|
func setArray(dst, src reflect.Value) error {
|
||||||
@@ -155,7 +155,7 @@ func setArray(dst, src reflect.Value) error {
|
|||||||
dst.Set(array)
|
dst.Set(array)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New("Cannot set array, destination not settable")
|
return errors.New("cannot set array, destination not settable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setStruct(dst, src reflect.Value) error {
|
func setStruct(dst, src reflect.Value) error {
|
||||||
@@ -163,7 +163,7 @@ func setStruct(dst, src reflect.Value) error {
|
|||||||
srcField := src.Field(i)
|
srcField := src.Field(i)
|
||||||
dstField := dst.Field(i)
|
dstField := dst.Field(i)
|
||||||
if !dstField.IsValid() || !srcField.IsValid() {
|
if !dstField.IsValid() || !srcField.IsValid() {
|
||||||
return fmt.Errorf("Could not find src field: %v value: %v in destination", srcField.Type().Name(), srcField)
|
return fmt.Errorf("could not find src field: %v value: %v in destination", srcField.Type().Name(), srcField)
|
||||||
}
|
}
|
||||||
if err := set(dstField, srcField); err != nil {
|
if err := set(dstField, srcField); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -228,7 +228,7 @@ func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[stri
|
|||||||
structFieldName := ToCamelCase(argName)
|
structFieldName := ToCamelCase(argName)
|
||||||
|
|
||||||
if structFieldName == "" {
|
if structFieldName == "" {
|
||||||
return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct")
|
return nil, errors.New("abi: purely underscored output cannot unpack to struct")
|
||||||
}
|
}
|
||||||
|
|
||||||
// this abi has already been paired, skip it... unless there exists another, yet unassigned
|
// this abi has already been paired, skip it... unless there exists another, yet unassigned
|
||||||
|
|||||||
@@ -170,8 +170,11 @@ var reflectTests = []reflectTest{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReflectNameToStruct(t *testing.T) {
|
func TestReflectNameToStruct(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
for _, test := range reflectTests {
|
for _, test := range reflectTests {
|
||||||
|
test := test
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc))
|
m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc))
|
||||||
if len(test.err) > 0 {
|
if len(test.err) > 0 {
|
||||||
if err == nil || err.Error() != test.err {
|
if err == nil || err.Error() != test.err {
|
||||||
@@ -192,6 +195,7 @@ func TestReflectNameToStruct(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConvertType(t *testing.T) {
|
func TestConvertType(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// Test Basic Struct
|
// Test Basic Struct
|
||||||
type T struct {
|
type T struct {
|
||||||
X *big.Int
|
X *big.Int
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ func isIdentifierSymbol(c byte) bool {
|
|||||||
|
|
||||||
func parseToken(unescapedSelector string, isIdent bool) (string, string, error) {
|
func parseToken(unescapedSelector string, isIdent bool) (string, string, error) {
|
||||||
if len(unescapedSelector) == 0 {
|
if len(unescapedSelector) == 0 {
|
||||||
return "", "", fmt.Errorf("empty token")
|
return "", "", errors.New("empty token")
|
||||||
}
|
}
|
||||||
firstChar := unescapedSelector[0]
|
firstChar := unescapedSelector[0]
|
||||||
position := 1
|
position := 1
|
||||||
@@ -110,7 +111,7 @@ func parseCompositeType(unescapedSelector string) ([]interface{}, string, error)
|
|||||||
|
|
||||||
func parseType(unescapedSelector string) (interface{}, string, error) {
|
func parseType(unescapedSelector string) (interface{}, string, error) {
|
||||||
if len(unescapedSelector) == 0 {
|
if len(unescapedSelector) == 0 {
|
||||||
return nil, "", fmt.Errorf("empty type")
|
return nil, "", errors.New("empty type")
|
||||||
}
|
}
|
||||||
if unescapedSelector[0] == '(' {
|
if unescapedSelector[0] == '(' {
|
||||||
return parseCompositeType(unescapedSelector)
|
return parseCompositeType(unescapedSelector)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestParseSelector(t *testing.T) {
|
func TestParseSelector(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
mkType := func(types ...interface{}) []ArgumentMarshaling {
|
mkType := func(types ...interface{}) []ArgumentMarshaling {
|
||||||
var result []ArgumentMarshaling
|
var result []ArgumentMarshaling
|
||||||
for i, typeOrComponents := range types {
|
for i, typeOrComponents := range types {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,8 +42,7 @@ func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) {
|
|||||||
case common.Address:
|
case common.Address:
|
||||||
copy(topic[common.HashLength-common.AddressLength:], rule[:])
|
copy(topic[common.HashLength-common.AddressLength:], rule[:])
|
||||||
case *big.Int:
|
case *big.Int:
|
||||||
blob := rule.Bytes()
|
copy(topic[:], math.U256Bytes(rule))
|
||||||
copy(topic[common.HashLength-len(blob):], blob)
|
|
||||||
case bool:
|
case bool:
|
||||||
if rule {
|
if rule {
|
||||||
topic[common.HashLength-1] = 1
|
topic[common.HashLength-1] = 1
|
||||||
@@ -75,7 +75,7 @@ func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) {
|
|||||||
copy(topic[:], hash[:])
|
copy(topic[:], hash[:])
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// todo(rjl493456442) according solidity documentation, indexed event
|
// todo(rjl493456442) according to solidity documentation, indexed event
|
||||||
// parameters that are not value types i.e. arrays and structs are not
|
// parameters that are not value types i.e. arrays and structs are not
|
||||||
// stored directly but instead a keccak256-hash of an encoding is stored.
|
// stored directly but instead a keccak256-hash of an encoding is stored.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -26,6 +27,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMakeTopics(t *testing.T) {
|
func TestMakeTopics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
type args struct {
|
type args struct {
|
||||||
query [][]interface{}
|
query [][]interface{}
|
||||||
}
|
}
|
||||||
@@ -54,9 +56,27 @@ func TestMakeTopics(t *testing.T) {
|
|||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"support *big.Int types in topics",
|
"support positive *big.Int types in topics",
|
||||||
args{[][]interface{}{{big.NewInt(1).Lsh(big.NewInt(2), 254)}}},
|
args{[][]interface{}{
|
||||||
[][]common.Hash{{common.Hash{128}}},
|
{big.NewInt(1)},
|
||||||
|
{big.NewInt(1).Lsh(big.NewInt(2), 254)},
|
||||||
|
}},
|
||||||
|
[][]common.Hash{
|
||||||
|
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001")},
|
||||||
|
{common.Hash{128}},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"support negative *big.Int types in topics",
|
||||||
|
args{[][]interface{}{
|
||||||
|
{big.NewInt(-1)},
|
||||||
|
{big.NewInt(math.MinInt64)},
|
||||||
|
}},
|
||||||
|
[][]common.Hash{
|
||||||
|
{common.MaxHash},
|
||||||
|
{common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffff8000000000000000")},
|
||||||
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -117,7 +137,9 @@ func TestMakeTopics(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
got, err := MakeTopics(tt.args.query...)
|
got, err := MakeTopics(tt.args.query...)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
@@ -347,10 +369,13 @@ func setupTopicsTests() []topicTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTopics(t *testing.T) {
|
func TestParseTopics(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := setupTopicsTests()
|
tests := setupTopicsTests()
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
createObj := tt.args.createObj()
|
createObj := tt.args.createObj()
|
||||||
if err := ParseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
|
if err := ParseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("parseTopics() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("parseTopics() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
@@ -364,10 +389,13 @@ func TestParseTopics(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTopicsIntoMap(t *testing.T) {
|
func TestParseTopicsIntoMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := setupTopicsTests()
|
tests := setupTopicsTests()
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
outMap := make(map[string]interface{})
|
outMap := make(map[string]interface{})
|
||||||
if err := ParseTopicsIntoMap(outMap, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
|
if err := ParseTopicsIntoMap(outMap, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("parseTopicsIntoMap() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("parseTopicsIntoMap() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
|||||||
@@ -64,13 +64,16 @@ type Type struct {
|
|||||||
var (
|
var (
|
||||||
// typeRegex parses the abi sub types
|
// typeRegex parses the abi sub types
|
||||||
typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?")
|
typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?")
|
||||||
|
|
||||||
|
// sliceSizeRegex grab the slice size
|
||||||
|
sliceSizeRegex = regexp.MustCompile("[0-9]+")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewType creates a new reflection type of abi type given in t.
|
// NewType creates a new reflection type of abi type given in t.
|
||||||
func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) {
|
func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) {
|
||||||
// check that array brackets are equal if they exist
|
// check that array brackets are equal if they exist
|
||||||
if strings.Count(t, "[") != strings.Count(t, "]") {
|
if strings.Count(t, "[") != strings.Count(t, "]") {
|
||||||
return Type{}, fmt.Errorf("invalid arg type in abi")
|
return Type{}, errors.New("invalid arg type in abi")
|
||||||
}
|
}
|
||||||
typ.stringKind = t
|
typ.stringKind = t
|
||||||
|
|
||||||
@@ -91,8 +94,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
|
|||||||
// grab the last cell and create a type from there
|
// grab the last cell and create a type from there
|
||||||
sliced := t[i:]
|
sliced := t[i:]
|
||||||
// grab the slice size with regexp
|
// grab the slice size with regexp
|
||||||
re := regexp.MustCompile("[0-9]+")
|
intz := sliceSizeRegex.FindAllString(sliced, -1)
|
||||||
intz := re.FindAllString(sliced, -1)
|
|
||||||
|
|
||||||
if len(intz) == 0 {
|
if len(intz) == 0 {
|
||||||
// is a slice
|
// is a slice
|
||||||
@@ -109,7 +111,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
|
|||||||
}
|
}
|
||||||
typ.stringKind = embeddedType.stringKind + sliced
|
typ.stringKind = embeddedType.stringKind + sliced
|
||||||
} else {
|
} else {
|
||||||
return Type{}, fmt.Errorf("invalid formatting of array type")
|
return Type{}, errors.New("invalid formatting of array type")
|
||||||
}
|
}
|
||||||
return typ, err
|
return typ, err
|
||||||
}
|
}
|
||||||
@@ -179,9 +181,6 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty
|
|||||||
return Type{}, errors.New("abi: purely anonymous or underscored field is not supported")
|
return Type{}, errors.New("abi: purely anonymous or underscored field is not supported")
|
||||||
}
|
}
|
||||||
fieldName := ResolveNameConflict(name, func(s string) bool { return used[s] })
|
fieldName := ResolveNameConflict(name, func(s string) bool { return used[s] })
|
||||||
if err != nil {
|
|
||||||
return Type{}, err
|
|
||||||
}
|
|
||||||
used[fieldName] = true
|
used[fieldName] = true
|
||||||
if !isValidFieldName(fieldName) {
|
if !isValidFieldName(fieldName) {
|
||||||
return Type{}, fmt.Errorf("field %d has invalid name", idx)
|
return Type{}, fmt.Errorf("field %d has invalid name", idx)
|
||||||
@@ -348,7 +347,7 @@ func (t Type) pack(v reflect.Value) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// requireLengthPrefix returns whether the type requires any sort of length
|
// requiresLengthPrefix returns whether the type requires any sort of length
|
||||||
// prefixing.
|
// prefixing.
|
||||||
func (t Type) requiresLengthPrefix() bool {
|
func (t Type) requiresLengthPrefix() bool {
|
||||||
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
|
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
|
||||||
|
|||||||
@@ -25,12 +25,13 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// typeWithoutStringer is a alias for the Type type which simply doesn't implement
|
// typeWithoutStringer is an alias for the Type type which simply doesn't implement
|
||||||
// the stringer interface to allow printing type details in the tests below.
|
// the stringer interface to allow printing type details in the tests below.
|
||||||
type typeWithoutStringer Type
|
type typeWithoutStringer Type
|
||||||
|
|
||||||
// Tests that all allowed types get recognized by the type parser.
|
// Tests that all allowed types get recognized by the type parser.
|
||||||
func TestTypeRegexp(t *testing.T) {
|
func TestTypeRegexp(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
blob string
|
blob string
|
||||||
components []ArgumentMarshaling
|
components []ArgumentMarshaling
|
||||||
@@ -117,6 +118,7 @@ func TestTypeRegexp(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTypeCheck(t *testing.T) {
|
func TestTypeCheck(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
for i, test := range []struct {
|
for i, test := range []struct {
|
||||||
typ string
|
typ string
|
||||||
components []ArgumentMarshaling
|
components []ArgumentMarshaling
|
||||||
@@ -308,6 +310,7 @@ func TestTypeCheck(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInternalType(t *testing.T) {
|
func TestInternalType(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
components := []ArgumentMarshaling{{Name: "a", Type: "int64"}}
|
components := []ArgumentMarshaling{{Name: "a", Type: "int64"}}
|
||||||
internalType := "struct a.b[]"
|
internalType := "struct a.b[]"
|
||||||
kind := Type{
|
kind := Type{
|
||||||
@@ -332,6 +335,7 @@ func TestInternalType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetTypeSize(t *testing.T) {
|
func TestGetTypeSize(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var testCases = []struct {
|
var testCases = []struct {
|
||||||
typ string
|
typ string
|
||||||
components []ArgumentMarshaling
|
components []ArgumentMarshaling
|
||||||
@@ -368,6 +372,7 @@ func TestGetTypeSize(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFixedBytesOver32(t *testing.T) {
|
func TestNewFixedBytesOver32(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, err := NewType("bytes4096", "", nil)
|
_, err := NewType("bytes4096", "", nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("fixed bytes with size over 32 is not spec'd")
|
t.Errorf("fixed bytes with size over 32 is not spec'd")
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package abi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
@@ -125,7 +126,7 @@ func readBool(word []byte) (bool, error) {
|
|||||||
// readFunctionType enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes)
|
// readFunctionType enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes)
|
||||||
func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
||||||
if t.T != FunctionTy {
|
if t.T != FunctionTy {
|
||||||
return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array")
|
return [24]byte{}, errors.New("abi: invalid type in call to make function type byte array")
|
||||||
}
|
}
|
||||||
if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 {
|
if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 {
|
||||||
err = fmt.Errorf("abi: got improperly encoded function type, got %v", word)
|
err = fmt.Errorf("abi: got improperly encoded function type, got %v", word)
|
||||||
@@ -138,7 +139,7 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
|||||||
// ReadFixedBytes uses reflection to create a fixed array to be read from.
|
// ReadFixedBytes uses reflection to create a fixed array to be read from.
|
||||||
func ReadFixedBytes(t Type, word []byte) (interface{}, error) {
|
func ReadFixedBytes(t Type, word []byte) (interface{}, error) {
|
||||||
if t.T != FixedBytesTy {
|
if t.T != FixedBytesTy {
|
||||||
return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array")
|
return nil, errors.New("abi: invalid type in call to make fixed byte array")
|
||||||
}
|
}
|
||||||
// convert
|
// convert
|
||||||
array := reflect.New(t.GetType()).Elem()
|
array := reflect.New(t.GetType()).Elem()
|
||||||
@@ -159,14 +160,15 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
|
|||||||
// this value will become our slice or our array, depending on the type
|
// this value will become our slice or our array, depending on the type
|
||||||
var refSlice reflect.Value
|
var refSlice reflect.Value
|
||||||
|
|
||||||
if t.T == SliceTy {
|
switch t.T {
|
||||||
|
case SliceTy:
|
||||||
// declare our slice
|
// declare our slice
|
||||||
refSlice = reflect.MakeSlice(t.GetType(), size, size)
|
refSlice = reflect.MakeSlice(t.GetType(), size, size)
|
||||||
} else if t.T == ArrayTy {
|
case ArrayTy:
|
||||||
// declare our array
|
// declare our array
|
||||||
refSlice = reflect.New(t.GetType()).Elem()
|
refSlice = reflect.New(t.GetType()).Elem()
|
||||||
} else {
|
default:
|
||||||
return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage")
|
return nil, errors.New("abi: invalid type in array/slice unpacking stage")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arrays have packed elements, resulting in longer unpack steps.
|
// Arrays have packed elements, resulting in longer unpack steps.
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import (
|
|||||||
|
|
||||||
// TestUnpack tests the general pack/unpack tests in packing_test.go
|
// TestUnpack tests the general pack/unpack tests in packing_test.go
|
||||||
func TestUnpack(t *testing.T) {
|
func TestUnpack(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
for i, test := range packUnpackTests {
|
for i, test := range packUnpackTests {
|
||||||
t.Run(strconv.Itoa(i)+" "+test.def, func(t *testing.T) {
|
t.Run(strconv.Itoa(i)+" "+test.def, func(t *testing.T) {
|
||||||
//Unpack
|
//Unpack
|
||||||
@@ -206,13 +207,13 @@ var unpackTests = []unpackTest{
|
|||||||
def: `[{"type":"bool"}]`,
|
def: `[{"type":"bool"}]`,
|
||||||
enc: "",
|
enc: "",
|
||||||
want: false,
|
want: false,
|
||||||
err: "abi: attempting to unmarshall an empty string while arguments are expected",
|
err: "abi: attempting to unmarshal an empty string while arguments are expected",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
def: `[{"type":"bytes32","indexed":true},{"type":"uint256","indexed":false}]`,
|
def: `[{"type":"bytes32","indexed":true},{"type":"uint256","indexed":false}]`,
|
||||||
enc: "",
|
enc: "",
|
||||||
want: false,
|
want: false,
|
||||||
err: "abi: attempting to unmarshall an empty string while arguments are expected",
|
err: "abi: attempting to unmarshal an empty string while arguments are expected",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
def: `[{"type":"bool","indexed":true},{"type":"uint64","indexed":true}]`,
|
def: `[{"type":"bool","indexed":true},{"type":"uint64","indexed":true}]`,
|
||||||
@@ -224,6 +225,7 @@ var unpackTests = []unpackTest{
|
|||||||
// TestLocalUnpackTests runs test specially designed only for unpacking.
|
// TestLocalUnpackTests runs test specially designed only for unpacking.
|
||||||
// All test cases that can be used to test packing and unpacking should move to packing_test.go
|
// All test cases that can be used to test packing and unpacking should move to packing_test.go
|
||||||
func TestLocalUnpackTests(t *testing.T) {
|
func TestLocalUnpackTests(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
for i, test := range unpackTests {
|
for i, test := range unpackTests {
|
||||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
//Unpack
|
//Unpack
|
||||||
@@ -251,6 +253,7 @@ func TestLocalUnpackTests(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackIntoInterfaceSetDynamicArrayOutput(t *testing.T) {
|
func TestUnpackIntoInterfaceSetDynamicArrayOutput(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
abi, err := JSON(strings.NewReader(`[{"constant":true,"inputs":[],"name":"testDynamicFixedBytes15","outputs":[{"name":"","type":"bytes15[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"testDynamicFixedBytes32","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"}]`))
|
abi, err := JSON(strings.NewReader(`[{"constant":true,"inputs":[],"name":"testDynamicFixedBytes15","outputs":[{"name":"","type":"bytes15[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"testDynamicFixedBytes32","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"}]`))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -321,6 +324,7 @@ func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOut
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMethodMultiReturn(t *testing.T) {
|
func TestMethodMultiReturn(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
type reversed struct {
|
type reversed struct {
|
||||||
String string
|
String string
|
||||||
Int *big.Int
|
Int *big.Int
|
||||||
@@ -400,6 +404,7 @@ func TestMethodMultiReturn(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiReturnWithArray(t *testing.T) {
|
func TestMultiReturnWithArray(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]`
|
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]`
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -423,6 +428,7 @@ func TestMultiReturnWithArray(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiReturnWithStringArray(t *testing.T) {
|
func TestMultiReturnWithStringArray(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]`
|
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]`
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -453,6 +459,7 @@ func TestMultiReturnWithStringArray(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiReturnWithStringSlice(t *testing.T) {
|
func TestMultiReturnWithStringSlice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]`
|
const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]`
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -485,6 +492,7 @@ func TestMultiReturnWithStringSlice(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
|
func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
// Similar to TestMultiReturnWithArray, but with a special case in mind:
|
// Similar to TestMultiReturnWithArray, but with a special case in mind:
|
||||||
// values of nested static arrays count towards the size as well, and any element following
|
// values of nested static arrays count towards the size as well, and any element following
|
||||||
// after such nested array argument should be read with the correct offset,
|
// after such nested array argument should be read with the correct offset,
|
||||||
@@ -525,6 +533,7 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnmarshal(t *testing.T) {
|
func TestUnmarshal(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const definition = `[
|
const definition = `[
|
||||||
{ "name" : "int", "type": "function", "outputs": [ { "type": "uint256" } ] },
|
{ "name" : "int", "type": "function", "outputs": [ { "type": "uint256" } ] },
|
||||||
{ "name" : "bool", "type": "function", "outputs": [ { "type": "bool" } ] },
|
{ "name" : "bool", "type": "function", "outputs": [ { "type": "bool" } ] },
|
||||||
@@ -774,6 +783,7 @@ func TestUnmarshal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnpackTuple(t *testing.T) {
|
func TestUnpackTuple(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
const simpleTuple = `[{"name":"tuple","type":"function","outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]`
|
const simpleTuple = `[{"name":"tuple","type":"function","outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]`
|
||||||
abi, err := JSON(strings.NewReader(simpleTuple))
|
abi, err := JSON(strings.NewReader(simpleTuple))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -876,6 +886,7 @@ func TestUnpackTuple(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOOMMaliciousInput(t *testing.T) {
|
func TestOOMMaliciousInput(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
oomTests := []unpackTest{
|
oomTests := []unpackTest{
|
||||||
{
|
{
|
||||||
def: `[{"type": "uint8[]"}]`,
|
def: `[{"type": "uint8[]"}]`,
|
||||||
@@ -946,6 +957,7 @@ func TestOOMMaliciousInput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPackAndUnpackIncompatibleNumber(t *testing.T) {
|
func TestPackAndUnpackIncompatibleNumber(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
var encodeABI Arguments
|
var encodeABI Arguments
|
||||||
uint256Ty, err := NewType("uint256", "", nil)
|
uint256Ty, err := NewType("uint256", "", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ func TextHash(data []byte) []byte {
|
|||||||
//
|
//
|
||||||
// This gives context to the signed message and prevents signing of transactions.
|
// This gives context to the signed message and prevents signing of transactions.
|
||||||
func TextAndHash(data []byte) ([]byte, string) {
|
func TextAndHash(data []byte) ([]byte, string) {
|
||||||
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data))
|
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
hasher := sha3.NewLegacyKeccak256()
|
||||||
hasher.Write([]byte(msg))
|
hasher.Write([]byte(msg))
|
||||||
return hasher.Sum(nil), msg
|
return hasher.Sum(nil), msg
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestTextHash(t *testing.T) {
|
func TestTextHash(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
hash := TextHash([]byte("Hello Joe"))
|
hash := TextHash([]byte("Hello Joe"))
|
||||||
want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b")
|
want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b")
|
||||||
if !bytes.Equal(hash, want) {
|
if !bytes.Equal(hash, want) {
|
||||||
|
|||||||
28
accounts/external/backend.go
vendored
28
accounts/external/backend.go
vendored
@@ -17,6 +17,7 @@
|
|||||||
package external
|
package external
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -98,11 +99,11 @@ func (api *ExternalSigner) Status() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) Open(passphrase string) error {
|
func (api *ExternalSigner) Open(passphrase string) error {
|
||||||
return fmt.Errorf("operation not supported on external signers")
|
return errors.New("operation not supported on external signers")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) Close() error {
|
func (api *ExternalSigner) Close() error {
|
||||||
return fmt.Errorf("operation not supported on external signers")
|
return errors.New("operation not supported on external signers")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) Accounts() []accounts.Account {
|
func (api *ExternalSigner) Accounts() []accounts.Account {
|
||||||
@@ -145,7 +146,7 @@ func (api *ExternalSigner) Contains(account accounts.Account) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
|
func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
|
||||||
return accounts.Account{}, fmt.Errorf("operation not supported on external signers")
|
return accounts.Account{}, errors.New("operation not supported on external signers")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
||||||
@@ -204,7 +205,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
|
|||||||
to = &t
|
to = &t
|
||||||
}
|
}
|
||||||
args := &apitypes.SendTxArgs{
|
args := &apitypes.SendTxArgs{
|
||||||
Data: &data,
|
Input: &data,
|
||||||
Nonce: hexutil.Uint64(tx.Nonce()),
|
Nonce: hexutil.Uint64(tx.Nonce()),
|
||||||
Value: hexutil.Big(*tx.Value()),
|
Value: hexutil.Big(*tx.Value()),
|
||||||
Gas: hexutil.Uint64(tx.Gas()),
|
Gas: hexutil.Uint64(tx.Gas()),
|
||||||
@@ -214,7 +215,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
|
|||||||
switch tx.Type() {
|
switch tx.Type() {
|
||||||
case types.LegacyTxType, types.AccessListTxType:
|
case types.LegacyTxType, types.AccessListTxType:
|
||||||
args.GasPrice = (*hexutil.Big)(tx.GasPrice())
|
args.GasPrice = (*hexutil.Big)(tx.GasPrice())
|
||||||
case types.DynamicFeeTxType:
|
case types.DynamicFeeTxType, types.BlobTxType:
|
||||||
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
|
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
|
||||||
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
|
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
|
||||||
default:
|
default:
|
||||||
@@ -234,6 +235,17 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
|
|||||||
accessList := tx.AccessList()
|
accessList := tx.AccessList()
|
||||||
args.AccessList = &accessList
|
args.AccessList = &accessList
|
||||||
}
|
}
|
||||||
|
if tx.Type() == types.BlobTxType {
|
||||||
|
args.BlobHashes = tx.BlobHashes()
|
||||||
|
sidecar := tx.BlobTxSidecar()
|
||||||
|
if sidecar == nil {
|
||||||
|
return nil, errors.New("blobs must be present for signing")
|
||||||
|
}
|
||||||
|
args.Blobs = sidecar.Blobs
|
||||||
|
args.Commitments = sidecar.Commitments
|
||||||
|
args.Proofs = sidecar.Proofs
|
||||||
|
}
|
||||||
|
|
||||||
var res signTransactionResult
|
var res signTransactionResult
|
||||||
if err := api.client.Call(&res, "account_signTransaction", args); err != nil {
|
if err := api.client.Call(&res, "account_signTransaction", args); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -242,14 +254,14 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||||
return []byte{}, fmt.Errorf("password-operations not supported on external signers")
|
return []byte{}, errors.New("password-operations not supported on external signers")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
return nil, fmt.Errorf("password-operations not supported on external signers")
|
return nil, errors.New("password-operations not supported on external signers")
|
||||||
}
|
}
|
||||||
func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
|
func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
|
||||||
return nil, fmt.Errorf("password-operations not supported on external signers")
|
return nil, errors.New("password-operations not supported on external signers")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ExternalSigner) listAccounts() ([]common.Address, error) {
|
func (api *ExternalSigner) listAccounts() ([]common.Address, error) {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
// Tests that HD derivation paths can be correctly parsed into our internal binary
|
// Tests that HD derivation paths can be correctly parsed into our internal binary
|
||||||
// representation.
|
// representation.
|
||||||
func TestHDPathParsing(t *testing.T) {
|
func TestHDPathParsing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
output DerivationPath
|
output DerivationPath
|
||||||
@@ -89,6 +90,7 @@ func testDerive(t *testing.T, next func() DerivationPath, expected []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHdPathIteration(t *testing.T) {
|
func TestHdPathIteration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
testDerive(t, DefaultIterator(DefaultBaseDerivationPath),
|
testDerive(t, DefaultIterator(DefaultBaseDerivationPath),
|
||||||
[]string{
|
[]string{
|
||||||
"m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1",
|
"m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -38,11 +39,10 @@ import (
|
|||||||
// exist yet, the code will attempt to create a watcher at most this often.
|
// exist yet, the code will attempt to create a watcher at most this often.
|
||||||
const minReloadInterval = 2 * time.Second
|
const minReloadInterval = 2 * time.Second
|
||||||
|
|
||||||
type accountsByURL []accounts.Account
|
// byURL defines the sorting order for accounts.
|
||||||
|
func byURL(a, b accounts.Account) int {
|
||||||
func (s accountsByURL) Len() int { return len(s) }
|
return a.URL.Cmp(b.URL)
|
||||||
func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 }
|
}
|
||||||
func (s accountsByURL) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
|
|
||||||
// AmbiguousAddrError is returned when attempting to unlock
|
// AmbiguousAddrError is returned when attempting to unlock
|
||||||
// an address for which more than one file exists.
|
// an address for which more than one file exists.
|
||||||
@@ -67,7 +67,7 @@ type accountCache struct {
|
|||||||
keydir string
|
keydir string
|
||||||
watcher *watcher
|
watcher *watcher
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
all accountsByURL
|
all []accounts.Account
|
||||||
byAddr map[common.Address][]accounts.Account
|
byAddr map[common.Address][]accounts.Account
|
||||||
throttle *time.Timer
|
throttle *time.Timer
|
||||||
notify chan struct{}
|
notify chan struct{}
|
||||||
@@ -194,7 +194,7 @@ func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
|
|||||||
default:
|
default:
|
||||||
err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
|
err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
|
||||||
copy(err.Matches, matches)
|
copy(err.Matches, matches)
|
||||||
sort.Sort(accountsByURL(err.Matches))
|
slices.SortFunc(err.Matches, byURL)
|
||||||
return accounts.Account{}, err
|
return accounts.Account{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,13 @@
|
|||||||
package keystore
|
package keystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// waitWatcherStarts waits up to 1s for the keystore watcher to start.
|
// waitWatcherStart waits up to 1s for the keystore watcher to start.
|
||||||
func waitWatcherStart(ks *KeyStore) bool {
|
func waitWatcherStart(ks *KeyStore) bool {
|
||||||
// On systems where file watch is not supported, just return "ok".
|
// On systems where file watch is not supported, just return "ok".
|
||||||
if !ks.cache.watcher.enabled() {
|
if !ks.cache.watcher.enabled() {
|
||||||
@@ -67,14 +68,14 @@ func waitWatcherStart(ks *KeyStore) bool {
|
|||||||
|
|
||||||
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
||||||
var list []accounts.Account
|
var list []accounts.Account
|
||||||
for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(200 * time.Millisecond) {
|
for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(100 * time.Millisecond) {
|
||||||
list = ks.Accounts()
|
list = ks.Accounts()
|
||||||
if reflect.DeepEqual(list, wantAccounts) {
|
if reflect.DeepEqual(list, wantAccounts) {
|
||||||
// ks should have also received change notifications
|
// ks should have also received change notifications
|
||||||
select {
|
select {
|
||||||
case <-ks.changes:
|
case <-ks.changes:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("wasn't notified of new accounts")
|
return errors.New("wasn't notified of new accounts")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -85,7 +86,7 @@ func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
|||||||
func TestWatchNewFile(t *testing.T) {
|
func TestWatchNewFile(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
dir, ks := tmpKeyStore(t, false)
|
dir, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
// Ensure the watcher is started before adding any files.
|
// Ensure the watcher is started before adding any files.
|
||||||
ks.Accounts()
|
ks.Accounts()
|
||||||
@@ -151,6 +152,7 @@ func TestWatchNoDir(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheInitialReload(t *testing.T) {
|
func TestCacheInitialReload(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cache, _ := newAccountCache(cachetestDir)
|
cache, _ := newAccountCache(cachetestDir)
|
||||||
accounts := cache.accounts()
|
accounts := cache.accounts()
|
||||||
if !reflect.DeepEqual(accounts, cachetestAccounts) {
|
if !reflect.DeepEqual(accounts, cachetestAccounts) {
|
||||||
@@ -159,6 +161,7 @@ func TestCacheInitialReload(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheAddDeleteOrder(t *testing.T) {
|
func TestCacheAddDeleteOrder(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
cache, _ := newAccountCache("testdata/no-such-dir")
|
cache, _ := newAccountCache("testdata/no-such-dir")
|
||||||
cache.watcher.running = true // prevent unexpected reloads
|
cache.watcher.running = true // prevent unexpected reloads
|
||||||
|
|
||||||
@@ -202,7 +205,7 @@ func TestCacheAddDeleteOrder(t *testing.T) {
|
|||||||
// Check that the account list is sorted by filename.
|
// Check that the account list is sorted by filename.
|
||||||
wantAccounts := make([]accounts.Account, len(accs))
|
wantAccounts := make([]accounts.Account, len(accs))
|
||||||
copy(wantAccounts, accs)
|
copy(wantAccounts, accs)
|
||||||
sort.Sort(accountsByURL(wantAccounts))
|
slices.SortFunc(wantAccounts, byURL)
|
||||||
list := cache.accounts()
|
list := cache.accounts()
|
||||||
if !reflect.DeepEqual(list, wantAccounts) {
|
if !reflect.DeepEqual(list, wantAccounts) {
|
||||||
t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
|
t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
|
||||||
@@ -243,6 +246,7 @@ func TestCacheAddDeleteOrder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheFind(t *testing.T) {
|
func TestCacheFind(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir := filepath.Join("testdata", "dir")
|
dir := filepath.Join("testdata", "dir")
|
||||||
cache, _ := newAccountCache(dir)
|
cache, _ := newAccountCache(dir)
|
||||||
cache.watcher.running = true // prevent unexpected reloads
|
cache.watcher.running = true // prevent unexpected reloads
|
||||||
@@ -321,7 +325,8 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Create a temporary keystore to test with
|
// Create a temporary keystore to test with
|
||||||
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-updatedkeyfilecontents-test-%d-%d", os.Getpid(), rand.Int()))
|
dir := t.TempDir()
|
||||||
|
|
||||||
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
||||||
|
|
||||||
list := ks.Accounts()
|
list := ks.Accounts()
|
||||||
@@ -331,9 +336,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
|||||||
if !waitWatcherStart(ks) {
|
if !waitWatcherStart(ks) {
|
||||||
t.Fatal("keystore watcher didn't start in time")
|
t.Fatal("keystore watcher didn't start in time")
|
||||||
}
|
}
|
||||||
// Create the directory and copy a key file into it.
|
// Copy a key file into it
|
||||||
os.MkdirAll(dir, 0700)
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
file := filepath.Join(dir, "aaa")
|
file := filepath.Join(dir, "aaa")
|
||||||
|
|
||||||
// Place one of our testfiles in there
|
// Place one of our testfiles in there
|
||||||
@@ -349,7 +352,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
||||||
time.Sleep(time.Second)
|
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second))
|
||||||
|
|
||||||
// Now replace file contents
|
// Now replace file contents
|
||||||
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
|
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
|
||||||
@@ -365,7 +368,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
||||||
time.Sleep(time.Second)
|
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second))
|
||||||
|
|
||||||
// Now replace file contents again
|
// Now replace file contents again
|
||||||
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
|
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
|
||||||
@@ -381,7 +384,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// needed so that modTime of `file` is different to its current value after os.WriteFile
|
// needed so that modTime of `file` is different to its current value after os.WriteFile
|
||||||
time.Sleep(time.Second)
|
os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second))
|
||||||
|
|
||||||
// Now replace file contents with crap
|
// Now replace file contents with crap
|
||||||
if err := os.WriteFile(file, []byte("foo"), 0600); err != nil {
|
if err := os.WriteFile(file, []byte("foo"), 0600); err != nil {
|
||||||
|
|||||||
@@ -87,15 +87,6 @@ func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
|
|||||||
return ks
|
return ks
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPlaintextKeyStore creates a keystore for the given directory.
|
|
||||||
// Deprecated: Use NewKeyStore.
|
|
||||||
func NewPlaintextKeyStore(keydir string) *KeyStore {
|
|
||||||
keydir, _ = filepath.Abs(keydir)
|
|
||||||
ks := &KeyStore{storage: &keyStorePlain{keydir}}
|
|
||||||
ks.init(keydir)
|
|
||||||
return ks
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ks *KeyStore) init(keydir string) {
|
func (ks *KeyStore) init(keydir string) {
|
||||||
// Lock the mutex since the account cache might call back with events
|
// Lock the mutex since the account cache might call back with events
|
||||||
ks.mu.Lock()
|
ks.mu.Lock()
|
||||||
@@ -321,11 +312,10 @@ func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error {
|
|||||||
// Lock removes the private key with the given address from memory.
|
// Lock removes the private key with the given address from memory.
|
||||||
func (ks *KeyStore) Lock(addr common.Address) error {
|
func (ks *KeyStore) Lock(addr common.Address) error {
|
||||||
ks.mu.Lock()
|
ks.mu.Lock()
|
||||||
if unl, found := ks.unlocked[addr]; found {
|
unl, found := ks.unlocked[addr]
|
||||||
ks.mu.Unlock()
|
ks.mu.Unlock()
|
||||||
|
if found {
|
||||||
ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
|
ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
|
||||||
} else {
|
|
||||||
ks.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -509,7 +499,5 @@ func (ks *KeyStore) isUpdating() bool {
|
|||||||
// zeroKey zeroes a private key in memory.
|
// zeroKey zeroes a private key in memory.
|
||||||
func zeroKey(k *ecdsa.PrivateKey) {
|
func zeroKey(k *ecdsa.PrivateKey) {
|
||||||
b := k.D.Bits()
|
b := k.D.Bits()
|
||||||
for i := range b {
|
clear(b)
|
||||||
b[i] = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
34
accounts/keystore/keystore_fuzzing_test.go
Normal file
34
accounts/keystore/keystore_fuzzing_test.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package keystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FuzzPassword(f *testing.F) {
|
||||||
|
f.Fuzz(func(t *testing.T, password string) {
|
||||||
|
ks := NewKeyStore(t.TempDir(), LightScryptN, LightScryptP)
|
||||||
|
a, err := ks.NewAccount(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := ks.Unlock(a, password); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -36,7 +36,8 @@ import (
|
|||||||
var testSigData = make([]byte, 32)
|
var testSigData = make([]byte, 32)
|
||||||
|
|
||||||
func TestKeyStore(t *testing.T) {
|
func TestKeyStore(t *testing.T) {
|
||||||
dir, ks := tmpKeyStore(t, true)
|
t.Parallel()
|
||||||
|
dir, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
a, err := ks.NewAccount("foo")
|
a, err := ks.NewAccount("foo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -70,7 +71,8 @@ func TestKeyStore(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSign(t *testing.T) {
|
func TestSign(t *testing.T) {
|
||||||
_, ks := tmpKeyStore(t, true)
|
t.Parallel()
|
||||||
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
pass := "" // not used but required by API
|
pass := "" // not used but required by API
|
||||||
a1, err := ks.NewAccount(pass)
|
a1, err := ks.NewAccount(pass)
|
||||||
@@ -86,7 +88,8 @@ func TestSign(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSignWithPassphrase(t *testing.T) {
|
func TestSignWithPassphrase(t *testing.T) {
|
||||||
_, ks := tmpKeyStore(t, true)
|
t.Parallel()
|
||||||
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
pass := "passwd"
|
pass := "passwd"
|
||||||
acc, err := ks.NewAccount(pass)
|
acc, err := ks.NewAccount(pass)
|
||||||
@@ -114,7 +117,7 @@ func TestSignWithPassphrase(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimedUnlock(t *testing.T) {
|
func TestTimedUnlock(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, true)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
pass := "foo"
|
pass := "foo"
|
||||||
a1, err := ks.NewAccount(pass)
|
a1, err := ks.NewAccount(pass)
|
||||||
@@ -149,7 +152,7 @@ func TestTimedUnlock(t *testing.T) {
|
|||||||
|
|
||||||
func TestOverrideUnlock(t *testing.T) {
|
func TestOverrideUnlock(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, false)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
pass := "foo"
|
pass := "foo"
|
||||||
a1, err := ks.NewAccount(pass)
|
a1, err := ks.NewAccount(pass)
|
||||||
@@ -190,7 +193,7 @@ func TestOverrideUnlock(t *testing.T) {
|
|||||||
// This test should fail under -race if signing races the expiration goroutine.
|
// This test should fail under -race if signing races the expiration goroutine.
|
||||||
func TestSignRace(t *testing.T) {
|
func TestSignRace(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
_, ks := tmpKeyStore(t, false)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
// Create a test account.
|
// Create a test account.
|
||||||
a1, err := ks.NewAccount("")
|
a1, err := ks.NewAccount("")
|
||||||
@@ -235,7 +238,7 @@ func waitForKsUpdating(t *testing.T, ks *KeyStore, wantStatus bool, maxTime time
|
|||||||
func TestWalletNotifierLifecycle(t *testing.T) {
|
func TestWalletNotifierLifecycle(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// Create a temporary keystore to test with
|
// Create a temporary keystore to test with
|
||||||
_, ks := tmpKeyStore(t, false)
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
// Ensure that the notification updater is not running yet
|
// Ensure that the notification updater is not running yet
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(250 * time.Millisecond)
|
||||||
@@ -280,7 +283,8 @@ type walletEvent struct {
|
|||||||
// Tests that wallet notifications and correctly fired when accounts are added
|
// Tests that wallet notifications and correctly fired when accounts are added
|
||||||
// or deleted from the keystore.
|
// or deleted from the keystore.
|
||||||
func TestWalletNotifications(t *testing.T) {
|
func TestWalletNotifications(t *testing.T) {
|
||||||
_, ks := tmpKeyStore(t, false)
|
t.Parallel()
|
||||||
|
_, ks := tmpKeyStore(t)
|
||||||
|
|
||||||
// Subscribe to the wallet feed and collect events.
|
// Subscribe to the wallet feed and collect events.
|
||||||
var (
|
var (
|
||||||
@@ -339,9 +343,10 @@ func TestWalletNotifications(t *testing.T) {
|
|||||||
checkEvents(t, wantEvents, events)
|
checkEvents(t, wantEvents, events)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestImportExport tests the import functionality of a keystore.
|
// TestImportECDSA tests the import functionality of a keystore.
|
||||||
func TestImportECDSA(t *testing.T) {
|
func TestImportECDSA(t *testing.T) {
|
||||||
_, ks := tmpKeyStore(t, true)
|
t.Parallel()
|
||||||
|
_, ks := tmpKeyStore(t)
|
||||||
key, err := crypto.GenerateKey()
|
key, err := crypto.GenerateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to generate key: %v", key)
|
t.Fatalf("failed to generate key: %v", key)
|
||||||
@@ -357,9 +362,10 @@ func TestImportECDSA(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestImportECDSA tests the import and export functionality of a keystore.
|
// TestImportExport tests the import and export functionality of a keystore.
|
||||||
func TestImportExport(t *testing.T) {
|
func TestImportExport(t *testing.T) {
|
||||||
_, ks := tmpKeyStore(t, true)
|
t.Parallel()
|
||||||
|
_, ks := tmpKeyStore(t)
|
||||||
acc, err := ks.NewAccount("old")
|
acc, err := ks.NewAccount("old")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create account: %v", acc)
|
t.Fatalf("failed to create account: %v", acc)
|
||||||
@@ -368,7 +374,7 @@ func TestImportExport(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to export account: %v", acc)
|
t.Fatalf("failed to export account: %v", acc)
|
||||||
}
|
}
|
||||||
_, ks2 := tmpKeyStore(t, true)
|
_, ks2 := tmpKeyStore(t)
|
||||||
if _, err = ks2.Import(json, "old", "old"); err == nil {
|
if _, err = ks2.Import(json, "old", "old"); err == nil {
|
||||||
t.Errorf("importing with invalid password succeeded")
|
t.Errorf("importing with invalid password succeeded")
|
||||||
}
|
}
|
||||||
@@ -387,7 +393,8 @@ func TestImportExport(t *testing.T) {
|
|||||||
// TestImportRace tests the keystore on races.
|
// TestImportRace tests the keystore on races.
|
||||||
// This test should fail under -race if importing races.
|
// This test should fail under -race if importing races.
|
||||||
func TestImportRace(t *testing.T) {
|
func TestImportRace(t *testing.T) {
|
||||||
_, ks := tmpKeyStore(t, true)
|
t.Parallel()
|
||||||
|
_, ks := tmpKeyStore(t)
|
||||||
acc, err := ks.NewAccount("old")
|
acc, err := ks.NewAccount("old")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create account: %v", acc)
|
t.Fatalf("failed to create account: %v", acc)
|
||||||
@@ -396,20 +403,20 @@ func TestImportRace(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to export account: %v", acc)
|
t.Fatalf("failed to export account: %v", acc)
|
||||||
}
|
}
|
||||||
_, ks2 := tmpKeyStore(t, true)
|
_, ks2 := tmpKeyStore(t)
|
||||||
var atom uint32
|
var atom atomic.Uint32
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if _, err := ks2.Import(json, "new", "new"); err != nil {
|
if _, err := ks2.Import(json, "new", "new"); err != nil {
|
||||||
atomic.AddUint32(&atom, 1)
|
atom.Add(1)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
if atom != 1 {
|
if atom.Load() != 1 {
|
||||||
t.Errorf("Import is racy")
|
t.Errorf("Import is racy")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -424,7 +431,7 @@ func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, walle
|
|||||||
for _, account := range live {
|
for _, account := range live {
|
||||||
liveList = append(liveList, account)
|
liveList = append(liveList, account)
|
||||||
}
|
}
|
||||||
sort.Sort(accountsByURL(liveList))
|
slices.SortFunc(liveList, byURL)
|
||||||
for j, wallet := range wallets {
|
for j, wallet := range wallets {
|
||||||
if accs := wallet.Accounts(); len(accs) != 1 {
|
if accs := wallet.Accounts(); len(accs) != 1 {
|
||||||
t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))
|
t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))
|
||||||
@@ -450,11 +457,7 @@ func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) {
|
func tmpKeyStore(t *testing.T) (string, *KeyStore) {
|
||||||
d := t.TempDir()
|
d := t.TempDir()
|
||||||
newKs := NewPlaintextKeyStore
|
return d, NewKeyStore(d, veryLightScryptN, veryLightScryptP)
|
||||||
if encrypted {
|
|
||||||
newKs = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) }
|
|
||||||
}
|
|
||||||
return d, newKs(d)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ func (ks keyStorePassphrase) JoinPath(filename string) string {
|
|||||||
return filepath.Join(ks.keysDirPath, filename)
|
return filepath.Join(ks.keysDirPath, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encryptdata encrypts the data given as 'data' with the password 'auth'.
|
// EncryptDataV3 encrypts the data given as 'data' with the password 'auth'.
|
||||||
func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
|
func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
|
||||||
salt := make([]byte, 32)
|
salt := make([]byte, 32)
|
||||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||||
@@ -225,10 +225,13 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
key := crypto.ToECDSAUnsafe(keyBytes)
|
key, err := crypto.ToECDSA(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid key: %w", err)
|
||||||
|
}
|
||||||
id, err := uuid.FromBytes(keyId)
|
id, err := uuid.FromBytes(keyId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("invalid UUID: %w", err)
|
||||||
}
|
}
|
||||||
return &Key{
|
return &Key{
|
||||||
Id: id,
|
Id: id,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const (
|
|||||||
|
|
||||||
// Tests that a json key file can be decrypted and encrypted in multiple rounds.
|
// Tests that a json key file can be decrypted and encrypted in multiple rounds.
|
||||||
func TestKeyEncryptDecrypt(t *testing.T) {
|
func TestKeyEncryptDecrypt(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
keyjson, err := os.ReadFile("testdata/very-light-scrypt.json")
|
keyjson, err := os.ReadFile("testdata/very-light-scrypt.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -54,7 +55,7 @@ func TestKeyEncryptDecrypt(t *testing.T) {
|
|||||||
// Recrypt with a new password and start over
|
// Recrypt with a new password and start over
|
||||||
password += "new data appended" // nolint: gosec
|
password += "new data appended" // nolint: gosec
|
||||||
if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil {
|
if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil {
|
||||||
t.Errorf("test %d: failed to recrypt key %v", i, err)
|
t.Errorf("test %d: failed to re-encrypt key %v", i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyStorePlain(t *testing.T) {
|
func TestKeyStorePlain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, ks := tmpKeyStoreIface(t, false)
|
_, ks := tmpKeyStoreIface(t, false)
|
||||||
|
|
||||||
pass := "" // not used but required by API
|
pass := "" // not used but required by API
|
||||||
@@ -60,6 +61,7 @@ func TestKeyStorePlain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyStorePassphrase(t *testing.T) {
|
func TestKeyStorePassphrase(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, ks := tmpKeyStoreIface(t, true)
|
_, ks := tmpKeyStoreIface(t, true)
|
||||||
|
|
||||||
pass := "foo"
|
pass := "foo"
|
||||||
@@ -80,6 +82,7 @@ func TestKeyStorePassphrase(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
|
func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
_, ks := tmpKeyStoreIface(t, true)
|
_, ks := tmpKeyStoreIface(t, true)
|
||||||
|
|
||||||
pass := "foo"
|
pass := "foo"
|
||||||
@@ -93,6 +96,7 @@ func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestImportPreSaleKey(t *testing.T) {
|
func TestImportPreSaleKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
dir, ks := tmpKeyStoreIface(t, true)
|
dir, ks := tmpKeyStoreIface(t, true)
|
||||||
|
|
||||||
// file content of a presale key file generated with:
|
// file content of a presale key file generated with:
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package keystore
|
package keystore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
@@ -77,7 +78,9 @@ func (w *watcher) loop() {
|
|||||||
}
|
}
|
||||||
defer watcher.Close()
|
defer watcher.Close()
|
||||||
if err := watcher.Add(w.ac.keydir); err != nil {
|
if err := watcher.Add(w.ac.keydir); err != nil {
|
||||||
logger.Warn("Failed to watch keystore folder", "err", err)
|
if !os.IsNotExist(err) {
|
||||||
|
logger.Warn("Failed to watch keystore folder", "err", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +125,7 @@ func (w *watcher) loop() {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("Filsystem watcher error", "err", err)
|
log.Info("Filesystem watcher error", "err", err)
|
||||||
case <-debounce.C:
|
case <-debounce.C:
|
||||||
w.ac.scanAccounts()
|
w.ac.scanAccounts()
|
||||||
rescanTriggered = false
|
rescanTriggered = false
|
||||||
|
|||||||
@@ -98,6 +98,9 @@ func NewManager(config *Config, backends ...Backend) *Manager {
|
|||||||
|
|
||||||
// Close terminates the account manager's internal notification processes.
|
// Close terminates the account manager's internal notification processes.
|
||||||
func (am *Manager) Close() error {
|
func (am *Manager) Close() error {
|
||||||
|
for _, w := range am.wallets {
|
||||||
|
w.Close()
|
||||||
|
}
|
||||||
errc := make(chan error)
|
errc := make(chan error)
|
||||||
am.quit <- errc
|
am.quit <- errc
|
||||||
return <-errc
|
return <-errc
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
## Preparing the smartcard
|
## Preparing the smartcard
|
||||||
|
|
||||||
**WARNING: FOILLOWING THESE INSTRUCTIONS WILL DESTROY THE MASTER KEY ON YOUR CARD. ONLY PROCEED IF NO FUNDS ARE ASSOCIATED WITH THESE ACCOUNTS**
|
**WARNING: FOLLOWING THESE INSTRUCTIONS WILL DESTROY THE MASTER KEY ON YOUR CARD. ONLY PROCEED IF NO FUNDS ARE ASSOCIATED WITH THESE ACCOUNTS**
|
||||||
|
|
||||||
You can use status' [keycard-cli](https://github.com/status-im/keycard-cli) and you should get _at least_ version 2.1.1 of their [smartcard application](https://github.com/status-im/status-keycard/releases/download/2.2.1/keycard_v2.2.1.cap)
|
You can use status' [keycard-cli](https://github.com/status-im/keycard-cli) and you should get _at least_ version 2.1.1 of their [smartcard application](https://github.com/status-im/status-keycard/releases/download/2.2.1/keycard_v2.2.1.cap)
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ func (hub *Hub) readPairings() error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer pairingFile.Close()
|
||||||
|
|
||||||
pairingData, err := io.ReadAll(pairingFile)
|
pairingData, err := io.ReadAll(pairingFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -241,7 +242,7 @@ func (hub *Hub) refreshWallets() {
|
|||||||
card.Disconnect(pcsc.LeaveCard)
|
card.Disconnect(pcsc.LeaveCard)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Card connected, start tracking in amongs the wallets
|
// Card connected, start tracking among the wallets
|
||||||
hub.wallets[reader] = wallet
|
hub.wallets[reader] = wallet
|
||||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
|
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
@@ -71,11 +71,11 @@ func NewSecureChannelSession(card *pcsc.Card, keyData []byte) (*SecureChannelSes
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not unmarshal public key from card: %v", err)
|
return nil, fmt.Errorf("could not unmarshal public key from card: %v", err)
|
||||||
}
|
}
|
||||||
secret, _ := key.Curve.ScalarMult(cardPublic.X, cardPublic.Y, key.D.Bytes())
|
secret, _ := crypto.S256().ScalarMult(cardPublic.X, cardPublic.Y, key.D.Bytes())
|
||||||
return &SecureChannelSession{
|
return &SecureChannelSession{
|
||||||
card: card,
|
card: card,
|
||||||
secret: secret.Bytes(),
|
secret: secret.Bytes(),
|
||||||
publicKey: elliptic.Marshal(crypto.S256(), key.PublicKey.X, key.PublicKey.Y),
|
publicKey: crypto.FromECDSAPub(&key.PublicKey),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ func (s *SecureChannelSession) Pair(pairingPassword []byte) error {
|
|||||||
// Unpair disestablishes an existing pairing.
|
// Unpair disestablishes an existing pairing.
|
||||||
func (s *SecureChannelSession) Unpair() error {
|
func (s *SecureChannelSession) Unpair() error {
|
||||||
if s.PairingKey == nil {
|
if s.PairingKey == nil {
|
||||||
return fmt.Errorf("cannot unpair: not paired")
|
return errors.New("cannot unpair: not paired")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.transmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{})
|
_, err := s.transmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{})
|
||||||
@@ -141,7 +141,7 @@ func (s *SecureChannelSession) Unpair() error {
|
|||||||
// Open initializes the secure channel.
|
// Open initializes the secure channel.
|
||||||
func (s *SecureChannelSession) Open() error {
|
func (s *SecureChannelSession) Open() error {
|
||||||
if s.iv != nil {
|
if s.iv != nil {
|
||||||
return fmt.Errorf("session already opened")
|
return errors.New("session already opened")
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := s.open()
|
response, err := s.open()
|
||||||
@@ -215,7 +215,7 @@ func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*responseAPDU, error
|
|||||||
// transmitEncrypted sends an encrypted message, and decrypts and returns the response.
|
// transmitEncrypted sends an encrypted message, and decrypts and returns the response.
|
||||||
func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) {
|
func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) {
|
||||||
if s.iv == nil {
|
if s.iv == nil {
|
||||||
return nil, fmt.Errorf("channel not open")
|
return nil, errors.New("channel not open")
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := s.encryptAPDU(data)
|
data, err := s.encryptAPDU(data)
|
||||||
@@ -254,7 +254,7 @@ func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []b
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !bytes.Equal(s.iv, rmac) {
|
if !bytes.Equal(s.iv, rmac) {
|
||||||
return nil, fmt.Errorf("invalid MAC in response")
|
return nil, errors.New("invalid MAC in response")
|
||||||
}
|
}
|
||||||
|
|
||||||
rapdu := &responseAPDU{}
|
rapdu := &responseAPDU{}
|
||||||
@@ -319,7 +319,7 @@ func unpad(data []byte, terminator byte) ([]byte, error) {
|
|||||||
return nil, fmt.Errorf("expected end of padding, got %d", data[len(data)-i])
|
return nil, fmt.Errorf("expected end of padding, got %d", data[len(data)-i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("expected end of padding, got 0")
|
return nil, errors.New("expected end of padding, got 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateIV is an internal method that updates the initialization vector after
|
// updateIV is an internal method that updates the initialization vector after
|
||||||
|
|||||||
@@ -73,6 +73,14 @@ var (
|
|||||||
DerivationSignatureHash = sha256.Sum256(common.Hash{}.Bytes())
|
DerivationSignatureHash = sha256.Sum256(common.Hash{}.Bytes())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// PinRegexp is the regular expression used to validate PIN codes.
|
||||||
|
pinRegexp = regexp.MustCompile(`^[0-9]{6,}$`)
|
||||||
|
|
||||||
|
// PukRegexp is the regular expression used to validate PUK codes.
|
||||||
|
pukRegexp = regexp.MustCompile(`^[0-9]{12,}$`)
|
||||||
|
)
|
||||||
|
|
||||||
// List of APDU command-related constants
|
// List of APDU command-related constants
|
||||||
const (
|
const (
|
||||||
claISO7816 = 0
|
claISO7816 = 0
|
||||||
@@ -252,7 +260,7 @@ func (w *Wallet) release() error {
|
|||||||
// with the wallet.
|
// with the wallet.
|
||||||
func (w *Wallet) pair(puk []byte) error {
|
func (w *Wallet) pair(puk []byte) error {
|
||||||
if w.session.paired() {
|
if w.session.paired() {
|
||||||
return fmt.Errorf("wallet already paired")
|
return errors.New("wallet already paired")
|
||||||
}
|
}
|
||||||
pairing, err := w.session.pair(puk)
|
pairing, err := w.session.pair(puk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -380,7 +388,7 @@ func (w *Wallet) Open(passphrase string) error {
|
|||||||
case passphrase == "":
|
case passphrase == "":
|
||||||
return ErrPINUnblockNeeded
|
return ErrPINUnblockNeeded
|
||||||
case status.PinRetryCount > 0:
|
case status.PinRetryCount > 0:
|
||||||
if !regexp.MustCompile(`^[0-9]{6,}$`).MatchString(passphrase) {
|
if !pinRegexp.MatchString(passphrase) {
|
||||||
w.log.Error("PIN needs to be at least 6 digits")
|
w.log.Error("PIN needs to be at least 6 digits")
|
||||||
return ErrPINNeeded
|
return ErrPINNeeded
|
||||||
}
|
}
|
||||||
@@ -388,7 +396,7 @@ func (w *Wallet) Open(passphrase string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if !regexp.MustCompile(`^[0-9]{12,}$`).MatchString(passphrase) {
|
if !pukRegexp.MatchString(passphrase) {
|
||||||
w.log.Error("PUK needs to be at least 12 digits")
|
w.log.Error("PUK needs to be at least 12 digits")
|
||||||
return ErrPINUnblockNeeded
|
return ErrPINUnblockNeeded
|
||||||
}
|
}
|
||||||
@@ -776,16 +784,16 @@ func (w *Wallet) findAccountPath(account accounts.Account) (accounts.DerivationP
|
|||||||
return nil, fmt.Errorf("scheme %s does not match wallet scheme %s", account.URL.Scheme, w.Hub.scheme)
|
return nil, fmt.Errorf("scheme %s does not match wallet scheme %s", account.URL.Scheme, w.Hub.scheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.SplitN(account.URL.Path, "/", 2)
|
url, path, found := strings.Cut(account.URL.Path, "/")
|
||||||
if len(parts) != 2 {
|
if !found {
|
||||||
return nil, fmt.Errorf("invalid URL format: %s", account.URL)
|
return nil, fmt.Errorf("invalid URL format: %s", account.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
if parts[0] != fmt.Sprintf("%x", w.PublicKey[1:3]) {
|
if url != fmt.Sprintf("%x", w.PublicKey[1:3]) {
|
||||||
return nil, fmt.Errorf("URL %s is not for this wallet", account.URL)
|
return nil, fmt.Errorf("URL %s is not for this wallet", account.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
return accounts.ParseDerivationPath(parts[1])
|
return accounts.ParseDerivationPath(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session represents a secured communication session with the wallet.
|
// Session represents a secured communication session with the wallet.
|
||||||
@@ -813,7 +821,7 @@ func (s *Session) pair(secret []byte) (smartcardPairing, error) {
|
|||||||
// unpair deletes an existing pairing.
|
// unpair deletes an existing pairing.
|
||||||
func (s *Session) unpair() error {
|
func (s *Session) unpair() error {
|
||||||
if !s.verified {
|
if !s.verified {
|
||||||
return fmt.Errorf("unpair requires that the PIN be verified")
|
return errors.New("unpair requires that the PIN be verified")
|
||||||
}
|
}
|
||||||
return s.Channel.Unpair()
|
return s.Channel.Unpair()
|
||||||
}
|
}
|
||||||
@@ -907,7 +915,7 @@ func (s *Session) initialize(seed []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status == "Online" {
|
if status == "Online" {
|
||||||
return fmt.Errorf("card is already initialized, cowardly refusing to proceed")
|
return errors.New("card is already initialized, cowardly refusing to proceed")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Wallet.lock.Lock()
|
s.Wallet.lock.Lock()
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestURLParsing(t *testing.T) {
|
func TestURLParsing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
url, err := parseURL("https://ethereum.org")
|
url, err := parseURL("https://ethereum.org")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
@@ -40,6 +41,7 @@ func TestURLParsing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestURLString(t *testing.T) {
|
func TestURLString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
url := URL{Scheme: "https", Path: "ethereum.org"}
|
url := URL{Scheme: "https", Path: "ethereum.org"}
|
||||||
if url.String() != "https://ethereum.org" {
|
if url.String() != "https://ethereum.org" {
|
||||||
t.Errorf("expected: %v, got: %v", "https://ethereum.org", url.String())
|
t.Errorf("expected: %v, got: %v", "https://ethereum.org", url.String())
|
||||||
@@ -52,10 +54,11 @@ func TestURLString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestURLMarshalJSON(t *testing.T) {
|
func TestURLMarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
url := URL{Scheme: "https", Path: "ethereum.org"}
|
url := URL{Scheme: "https", Path: "ethereum.org"}
|
||||||
json, err := url.MarshalJSON()
|
json, err := url.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpcted error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if string(json) != "\"https://ethereum.org\"" {
|
if string(json) != "\"https://ethereum.org\"" {
|
||||||
t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json))
|
t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json))
|
||||||
@@ -63,10 +66,11 @@ func TestURLMarshalJSON(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestURLUnmarshalJSON(t *testing.T) {
|
func TestURLUnmarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
url := &URL{}
|
url := &URL{}
|
||||||
err := url.UnmarshalJSON([]byte("\"https://ethereum.org\""))
|
err := url.UnmarshalJSON([]byte("\"https://ethereum.org\""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpcted error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if url.Scheme != "https" {
|
if url.Scheme != "https" {
|
||||||
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
|
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
|
||||||
@@ -77,6 +81,7 @@ func TestURLUnmarshalJSON(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestURLComparison(t *testing.T) {
|
func TestURLComparison(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
urlA URL
|
urlA URL
|
||||||
urlB URL
|
urlB URL
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/karalabe/usb"
|
"github.com/karalabe/hid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
||||||
@@ -63,9 +63,9 @@ type Hub struct {
|
|||||||
stateLock sync.RWMutex // Protects the internals of the hub from racey access
|
stateLock sync.RWMutex // Protects the internals of the hub from racey access
|
||||||
|
|
||||||
// TODO(karalabe): remove if hotplug lands on Windows
|
// TODO(karalabe): remove if hotplug lands on Windows
|
||||||
commsPend int // Number of operations blocking enumeration
|
commsPend int // Number of operations blocking enumeration
|
||||||
commsLock sync.Mutex // Lock protecting the pending counter and enumeration
|
commsLock sync.Mutex // Lock protecting the pending counter and enumeration
|
||||||
enumFails uint32 // Number of times enumeration has failed
|
enumFails atomic.Uint32 // Number of times enumeration has failed
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLedgerHub creates a new hardware wallet manager for Ledger devices.
|
// NewLedgerHub creates a new hardware wallet manager for Ledger devices.
|
||||||
@@ -109,7 +109,7 @@ func NewTrezorHubWithWebUSB() (*Hub, error) {
|
|||||||
|
|
||||||
// newHub creates a new hardware wallet manager for generic USB devices.
|
// newHub creates a new hardware wallet manager for generic USB devices.
|
||||||
func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, endpointID int, makeDriver func(log.Logger) driver) (*Hub, error) {
|
func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, endpointID int, makeDriver func(log.Logger) driver) (*Hub, error) {
|
||||||
if !usb.Supported() {
|
if !hid.Supported() {
|
||||||
return nil, errors.New("unsupported platform")
|
return nil, errors.New("unsupported platform")
|
||||||
}
|
}
|
||||||
hub := &Hub{
|
hub := &Hub{
|
||||||
@@ -151,11 +151,11 @@ func (hub *Hub) refreshWallets() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// If USB enumeration is continually failing, don't keep trying indefinitely
|
// If USB enumeration is continually failing, don't keep trying indefinitely
|
||||||
if atomic.LoadUint32(&hub.enumFails) > 2 {
|
if hub.enumFails.Load() > 2 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Retrieve the current list of USB wallet devices
|
// Retrieve the current list of USB wallet devices
|
||||||
var devices []usb.DeviceInfo
|
var devices []hid.DeviceInfo
|
||||||
|
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
// hidapi on Linux opens the device during enumeration to retrieve some infos,
|
// hidapi on Linux opens the device during enumeration to retrieve some infos,
|
||||||
@@ -170,9 +170,9 @@ func (hub *Hub) refreshWallets() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
infos, err := usb.Enumerate(hub.vendorID, 0)
|
infos, err := hid.Enumerate(hub.vendorID, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
failcount := atomic.AddUint32(&hub.enumFails, 1)
|
failcount := hub.enumFails.Add(1)
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
// See rationale before the enumeration why this is needed and only on Linux.
|
// See rationale before the enumeration why this is needed and only on Linux.
|
||||||
hub.commsLock.Unlock()
|
hub.commsLock.Unlock()
|
||||||
@@ -181,7 +181,7 @@ func (hub *Hub) refreshWallets() {
|
|||||||
"vendor", hub.vendorID, "failcount", failcount, "err", err)
|
"vendor", hub.vendorID, "failcount", failcount, "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
atomic.StoreUint32(&hub.enumFails, 0)
|
hub.enumFails.Store(0)
|
||||||
|
|
||||||
for _, info := range infos {
|
for _, info := range infos {
|
||||||
for _, id := range hub.productIDs {
|
for _, id := range hub.productIDs {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
// This file contains the implementation for interacting with the Ledger hardware
|
// This file contains the implementation for interacting with the Ledger hardware
|
||||||
// wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo:
|
// wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo:
|
||||||
// https://raw.githubusercontent.com/LedgerHQ/blue-app-eth/master/doc/ethapp.asc
|
// https://github.com/LedgerHQ/app-ethereum/blob/develop/doc/ethapp.adoc
|
||||||
|
|
||||||
package usbwallet
|
package usbwallet
|
||||||
|
|
||||||
@@ -279,7 +279,7 @@ func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, er
|
|||||||
}
|
}
|
||||||
hexstr := reply[1 : 1+int(reply[0])]
|
hexstr := reply[1 : 1+int(reply[0])]
|
||||||
|
|
||||||
// Decode the hex sting into an Ethereum address and return
|
// Decode the hex string into an Ethereum address and return
|
||||||
var address common.Address
|
var address common.Address
|
||||||
if _, err = hex.Decode(address[:], hexstr); err != nil {
|
if _, err = hex.Decode(address[:], hexstr); err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,8 @@
|
|||||||
syntax = "proto2";
|
syntax = "proto2";
|
||||||
package hw.trezor.messages.common;
|
package hw.trezor.messages.common;
|
||||||
|
|
||||||
|
option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response: Success of the previous request
|
* Response: Success of the previous request
|
||||||
* @end
|
* @end
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,8 @@
|
|||||||
syntax = "proto2";
|
syntax = "proto2";
|
||||||
package hw.trezor.messages.ethereum;
|
package hw.trezor.messages.ethereum;
|
||||||
|
|
||||||
|
option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor";
|
||||||
|
|
||||||
// Sugar for easier handling in Java
|
// Sugar for easier handling in Java
|
||||||
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||||
option java_outer_classname = "TrezorMessageEthereum";
|
option java_outer_classname = "TrezorMessageEthereum";
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,8 @@
|
|||||||
syntax = "proto2";
|
syntax = "proto2";
|
||||||
package hw.trezor.messages.management;
|
package hw.trezor.messages.management;
|
||||||
|
|
||||||
|
option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor";
|
||||||
|
|
||||||
// Sugar for easier handling in Java
|
// Sugar for easier handling in Java
|
||||||
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||||
option java_outer_classname = "TrezorMessageManagement";
|
option java_outer_classname = "TrezorMessageManagement";
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,10 +9,13 @@ package hw.trezor.messages;
|
|||||||
* Messages for TREZOR communication
|
* Messages for TREZOR communication
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor";
|
||||||
|
|
||||||
// Sugar for easier handling in Java
|
// Sugar for easier handling in Java
|
||||||
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||||
option java_outer_classname = "TrezorMessage";
|
option java_outer_classname = "TrezorMessage";
|
||||||
|
|
||||||
|
|
||||||
import "google/protobuf/descriptor.proto";
|
import "google/protobuf/descriptor.proto";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
// This file contains the implementation for interacting with the Trezor hardware
|
// This file contains the implementation for interacting with the Trezor hardware
|
||||||
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
||||||
// https://wiki.trezor.io/Developers_guide-Message_Workflows
|
// https://docs.trezor.io/trezor-firmware/common/message-workflows.html
|
||||||
|
|
||||||
// !!! STAHP !!!
|
// !!! STAHP !!!
|
||||||
//
|
//
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
// - Grab the latest Go plugin `go get -u github.com/golang/protobuf/protoc-gen-go`
|
// - Grab the latest Go plugin `go get -u github.com/golang/protobuf/protoc-gen-go`
|
||||||
// - Vendor in the latest Go plugin `govendor fetch github.com/golang/protobuf/...`
|
// - Vendor in the latest Go plugin `govendor fetch github.com/golang/protobuf/...`
|
||||||
|
|
||||||
//go:generate protoc -I/usr/local/include:. --go_out=import_path=trezor:. messages.proto messages-common.proto messages-management.proto messages-ethereum.proto
|
//go:generate protoc -I/usr/local/include:. --go_out=paths=source_relative:. messages.proto messages-common.proto messages-management.proto messages-ethereum.proto
|
||||||
|
|
||||||
// Package trezor contains the wire protocol.
|
// Package trezor contains the wire protocol.
|
||||||
package trezor
|
package trezor
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/karalabe/usb"
|
"github.com/karalabe/hid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Maximum time between wallet health checks to detect USB unplugs.
|
// Maximum time between wallet health checks to detect USB unplugs.
|
||||||
@@ -79,8 +79,8 @@ type wallet struct {
|
|||||||
driver driver // Hardware implementation of the low level device operations
|
driver driver // Hardware implementation of the low level device operations
|
||||||
url *accounts.URL // Textual URL uniquely identifying this wallet
|
url *accounts.URL // Textual URL uniquely identifying this wallet
|
||||||
|
|
||||||
info usb.DeviceInfo // Known USB device infos about the wallet
|
info hid.DeviceInfo // Known USB device infos about the wallet
|
||||||
device usb.Device // USB device advertising itself as a hardware wallet
|
device hid.Device // USB device advertising itself as a hardware wallet
|
||||||
|
|
||||||
accounts []accounts.Account // List of derive accounts pinned on the hardware wallet
|
accounts []accounts.Account // List of derive accounts pinned on the hardware wallet
|
||||||
paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
|
paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
|
||||||
@@ -483,6 +483,10 @@ func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun
|
|||||||
w.stateLock.Lock()
|
w.stateLock.Lock()
|
||||||
defer w.stateLock.Unlock()
|
defer w.stateLock.Unlock()
|
||||||
|
|
||||||
|
if w.device == nil {
|
||||||
|
return accounts.Account{}, accounts.ErrWalletClosed
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := w.paths[address]; !ok {
|
if _, ok := w.paths[address]; !ok {
|
||||||
w.accounts = append(w.accounts, account)
|
w.accounts = append(w.accounts, account)
|
||||||
w.paths[address] = make(accounts.DerivationPath, len(path))
|
w.paths[address] = make(accounts.DerivationPath, len(path))
|
||||||
@@ -624,7 +628,7 @@ func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID
|
|||||||
return signed, nil
|
return signed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary
|
// SignTextWithPassphrase implements accounts.Wallet, however signing arbitrary
|
||||||
// data is not supported for Ledger wallets, so this method will always return
|
// data is not supported for Ledger wallets, so this method will always return
|
||||||
// an error.
|
// an error.
|
||||||
func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ for:
|
|||||||
- image: Ubuntu
|
- image: Ubuntu
|
||||||
build_script:
|
build_script:
|
||||||
- go run build/ci.go lint
|
- go run build/ci.go lint
|
||||||
|
- go run build/ci.go generate -verify
|
||||||
- go run build/ci.go install -dlgo
|
- go run build/ci.go install -dlgo
|
||||||
test_script:
|
test_script:
|
||||||
- go run build/ci.go test -dlgo
|
- go run build/ci.go test -dlgo -short
|
||||||
|
|
||||||
# linux/386 is disabled.
|
# linux/386 is disabled.
|
||||||
- matrix:
|
- matrix:
|
||||||
@@ -54,4 +55,4 @@ for:
|
|||||||
- go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||||
- go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||||
test_script:
|
test_script:
|
||||||
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC%
|
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short
|
||||||
|
|||||||
163
beacon/blsync/block_sync.go
Executable file
163
beacon/blsync/block_sync.go
Executable file
@@ -0,0 +1,163 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package blsync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/sync"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// beaconBlockSync implements request.Module; it fetches the beacon blocks belonging
|
||||||
|
// to the validated and prefetch heads.
|
||||||
|
type beaconBlockSync struct {
|
||||||
|
recentBlocks *lru.Cache[common.Hash, *types.BeaconBlock]
|
||||||
|
locked map[common.Hash]request.ServerAndID
|
||||||
|
serverHeads map[request.Server]common.Hash
|
||||||
|
headTracker headTracker
|
||||||
|
|
||||||
|
lastHeadInfo types.HeadInfo
|
||||||
|
chainHeadFeed event.FeedOf[types.ChainHeadEvent]
|
||||||
|
}
|
||||||
|
|
||||||
|
type headTracker interface {
|
||||||
|
PrefetchHead() types.HeadInfo
|
||||||
|
ValidatedOptimistic() (types.OptimisticUpdate, bool)
|
||||||
|
ValidatedFinality() (types.FinalityUpdate, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBeaconBlockSync returns a new beaconBlockSync.
|
||||||
|
func newBeaconBlockSync(headTracker headTracker) *beaconBlockSync {
|
||||||
|
return &beaconBlockSync{
|
||||||
|
headTracker: headTracker,
|
||||||
|
recentBlocks: lru.NewCache[common.Hash, *types.BeaconBlock](10),
|
||||||
|
locked: make(map[common.Hash]request.ServerAndID),
|
||||||
|
serverHeads: make(map[request.Server]common.Hash),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *beaconBlockSync) SubscribeChainHead(ch chan<- types.ChainHeadEvent) event.Subscription {
|
||||||
|
return s.chainHeadFeed.Subscribe(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process implements request.Module.
|
||||||
|
func (s *beaconBlockSync) Process(requester request.Requester, events []request.Event) {
|
||||||
|
for _, event := range events {
|
||||||
|
switch event.Type {
|
||||||
|
case request.EvResponse, request.EvFail, request.EvTimeout:
|
||||||
|
sid, req, resp := event.RequestInfo()
|
||||||
|
blockRoot := common.Hash(req.(sync.ReqBeaconBlock))
|
||||||
|
log.Debug("Beacon block event", "type", event.Type.Name, "hash", blockRoot)
|
||||||
|
if resp != nil {
|
||||||
|
s.recentBlocks.Add(blockRoot, resp.(*types.BeaconBlock))
|
||||||
|
}
|
||||||
|
if s.locked[blockRoot] == sid {
|
||||||
|
delete(s.locked, blockRoot)
|
||||||
|
}
|
||||||
|
case sync.EvNewHead:
|
||||||
|
s.serverHeads[event.Server] = event.Data.(types.HeadInfo).BlockRoot
|
||||||
|
case request.EvUnregistered:
|
||||||
|
delete(s.serverHeads, event.Server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.updateEventFeed()
|
||||||
|
// request validated head block if unavailable and not yet requested
|
||||||
|
if vh, ok := s.headTracker.ValidatedOptimistic(); ok {
|
||||||
|
s.tryRequestBlock(requester, vh.Attested.Hash(), false)
|
||||||
|
}
|
||||||
|
// request prefetch head if the given server has announced it
|
||||||
|
if prefetchHead := s.headTracker.PrefetchHead().BlockRoot; prefetchHead != (common.Hash{}) {
|
||||||
|
s.tryRequestBlock(requester, prefetchHead, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *beaconBlockSync) tryRequestBlock(requester request.Requester, blockRoot common.Hash, needSameHead bool) {
|
||||||
|
if _, ok := s.recentBlocks.Get(blockRoot); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := s.locked[blockRoot]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, server := range requester.CanSendTo() {
|
||||||
|
if needSameHead && (s.serverHeads[server] != blockRoot) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id := requester.Send(server, sync.ReqBeaconBlock(blockRoot))
|
||||||
|
s.locked[blockRoot] = request.ServerAndID{Server: server, ID: id}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockHeadInfo(block *types.BeaconBlock) types.HeadInfo {
|
||||||
|
if block == nil {
|
||||||
|
return types.HeadInfo{}
|
||||||
|
}
|
||||||
|
return types.HeadInfo{Slot: block.Slot(), BlockRoot: block.Root()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *beaconBlockSync) updateEventFeed() {
|
||||||
|
optimistic, ok := s.headTracker.ValidatedOptimistic()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validatedHead := optimistic.Attested.Hash()
|
||||||
|
headBlock, ok := s.recentBlocks.Get(validatedHead)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var finalizedHash common.Hash
|
||||||
|
if finality, ok := s.headTracker.ValidatedFinality(); ok {
|
||||||
|
he := optimistic.Attested.Epoch()
|
||||||
|
fe := finality.Attested.Header.Epoch()
|
||||||
|
switch {
|
||||||
|
case he == fe:
|
||||||
|
finalizedHash = finality.Finalized.PayloadHeader.BlockHash()
|
||||||
|
case he < fe:
|
||||||
|
return
|
||||||
|
case he == fe+1:
|
||||||
|
parent, ok := s.recentBlocks.Get(optimistic.Attested.ParentRoot)
|
||||||
|
if !ok || parent.Slot()/params.EpochLength == fe {
|
||||||
|
return // head is at first slot of next epoch, wait for finality update
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headInfo := blockHeadInfo(headBlock)
|
||||||
|
if headInfo == s.lastHeadInfo {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.lastHeadInfo = headInfo
|
||||||
|
|
||||||
|
// new head block and finality info available; extract executable data and send event to feed
|
||||||
|
execBlock, err := headBlock.ExecutionPayload()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error extracting execution block from validated beacon block", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.chainHeadFeed.Send(types.ChainHeadEvent{
|
||||||
|
BeaconHead: optimistic.Attested.Header,
|
||||||
|
Block: execBlock,
|
||||||
|
Finalized: finalizedHash,
|
||||||
|
})
|
||||||
|
}
|
||||||
160
beacon/blsync/block_sync_test.go
Normal file
160
beacon/blsync/block_sync_test.go
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package blsync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/sync"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
zrntcommon "github.com/protolambda/zrnt/eth2/beacon/common"
|
||||||
|
"github.com/protolambda/zrnt/eth2/beacon/deneb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testServer1 = testServer("testServer1")
|
||||||
|
testServer2 = testServer("testServer2")
|
||||||
|
|
||||||
|
testBlock1 = types.NewBeaconBlock(&deneb.BeaconBlock{
|
||||||
|
Slot: 123,
|
||||||
|
Body: deneb.BeaconBlockBody{
|
||||||
|
ExecutionPayload: deneb.ExecutionPayload{
|
||||||
|
BlockNumber: 456,
|
||||||
|
BlockHash: zrntcommon.Hash32(common.HexToHash("905ac721c4058d9ed40b27b6b9c1bdd10d4333e4f3d9769100bf9dfb80e5d1f6")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testBlock2 = types.NewBeaconBlock(&deneb.BeaconBlock{
|
||||||
|
Slot: 124,
|
||||||
|
Body: deneb.BeaconBlockBody{
|
||||||
|
ExecutionPayload: deneb.ExecutionPayload{
|
||||||
|
BlockNumber: 457,
|
||||||
|
BlockHash: zrntcommon.Hash32(common.HexToHash("011703f39c664efc1c6cf5f49ca09b595581eec572d4dfddd3d6179a9e63e655")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
type testServer string
|
||||||
|
|
||||||
|
func (t testServer) Name() string {
|
||||||
|
return string(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlockSync(t *testing.T) {
|
||||||
|
ht := &testHeadTracker{}
|
||||||
|
blockSync := newBeaconBlockSync(ht)
|
||||||
|
headCh := make(chan types.ChainHeadEvent, 16)
|
||||||
|
blockSync.SubscribeChainHead(headCh)
|
||||||
|
ts := sync.NewTestScheduler(t, blockSync)
|
||||||
|
ts.AddServer(testServer1, 1)
|
||||||
|
ts.AddServer(testServer2, 1)
|
||||||
|
|
||||||
|
expHeadBlock := func(expHead *types.BeaconBlock) {
|
||||||
|
t.Helper()
|
||||||
|
var expNumber, headNumber uint64
|
||||||
|
if expHead != nil {
|
||||||
|
p, _ := expHead.ExecutionPayload()
|
||||||
|
expNumber = p.NumberU64()
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case event := <-headCh:
|
||||||
|
headNumber = event.Block.NumberU64()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if headNumber != expNumber {
|
||||||
|
t.Errorf("Wrong head block, expected block number %d, got %d)", expNumber, headNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no block requests expected until head tracker knows about a head
|
||||||
|
ts.Run(1)
|
||||||
|
expHeadBlock(nil)
|
||||||
|
|
||||||
|
// set block 1 as prefetch head, announced by server 2
|
||||||
|
head1 := blockHeadInfo(testBlock1)
|
||||||
|
ht.prefetch = head1
|
||||||
|
ts.ServerEvent(sync.EvNewHead, testServer2, head1)
|
||||||
|
|
||||||
|
// expect request to server 2 which has announced the head
|
||||||
|
ts.Run(2, testServer2, sync.ReqBeaconBlock(head1.BlockRoot))
|
||||||
|
|
||||||
|
// valid response
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(2, 1), testBlock1)
|
||||||
|
ts.AddAllowance(testServer2, 1)
|
||||||
|
ts.Run(3)
|
||||||
|
// head block still not expected as the fetched block is not the validated head yet
|
||||||
|
expHeadBlock(nil)
|
||||||
|
|
||||||
|
// set as validated head, expect no further requests but block 1 set as head block
|
||||||
|
ht.validated.Header = testBlock1.Header()
|
||||||
|
ts.Run(4)
|
||||||
|
expHeadBlock(testBlock1)
|
||||||
|
|
||||||
|
// set block 2 as prefetch head, announced by server 1
|
||||||
|
head2 := blockHeadInfo(testBlock2)
|
||||||
|
ht.prefetch = head2
|
||||||
|
ts.ServerEvent(sync.EvNewHead, testServer1, head2)
|
||||||
|
// expect request to server 1
|
||||||
|
ts.Run(5, testServer1, sync.ReqBeaconBlock(head2.BlockRoot))
|
||||||
|
|
||||||
|
// req2 fails, no further requests expected because server 2 has not announced it
|
||||||
|
ts.RequestEvent(request.EvFail, ts.Request(5, 1), nil)
|
||||||
|
ts.Run(6)
|
||||||
|
|
||||||
|
// set as validated head before retrieving block; now it's assumed to be available from server 2 too
|
||||||
|
ht.validated.Header = testBlock2.Header()
|
||||||
|
// expect req2 retry to server 2
|
||||||
|
ts.Run(7, testServer2, sync.ReqBeaconBlock(head2.BlockRoot))
|
||||||
|
// now head block should be unavailable again
|
||||||
|
expHeadBlock(nil)
|
||||||
|
|
||||||
|
// valid response, now head block should be block 2 immediately as it is already validated
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(7, 1), testBlock2)
|
||||||
|
ts.Run(8)
|
||||||
|
expHeadBlock(testBlock2)
|
||||||
|
}
|
||||||
|
|
||||||
|
type testHeadTracker struct {
|
||||||
|
prefetch types.HeadInfo
|
||||||
|
validated types.SignedHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *testHeadTracker) PrefetchHead() types.HeadInfo {
|
||||||
|
return h.prefetch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *testHeadTracker) ValidatedOptimistic() (types.OptimisticUpdate, bool) {
|
||||||
|
return types.OptimisticUpdate{
|
||||||
|
Attested: types.HeaderWithExecProof{Header: h.validated.Header},
|
||||||
|
Signature: h.validated.Signature,
|
||||||
|
SignatureSlot: h.validated.SignatureSlot,
|
||||||
|
}, h.validated.Header != (types.Header{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add test case for finality
|
||||||
|
func (h *testHeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
|
||||||
|
finalized := types.NewExecutionHeader(new(deneb.ExecutionPayloadHeader))
|
||||||
|
return types.FinalityUpdate{
|
||||||
|
Attested: types.HeaderWithExecProof{Header: h.validated.Header},
|
||||||
|
Finalized: types.HeaderWithExecProof{PayloadHeader: finalized},
|
||||||
|
Signature: h.validated.Signature,
|
||||||
|
SignatureSlot: h.validated.SignatureSlot,
|
||||||
|
}, h.validated.Header != (types.Header{})
|
||||||
|
}
|
||||||
115
beacon/blsync/client.go
Normal file
115
beacon/blsync/client.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package blsync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/api"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/sync"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
urls []string
|
||||||
|
customHeader map[string]string
|
||||||
|
chainConfig *lightClientConfig
|
||||||
|
scheduler *request.Scheduler
|
||||||
|
blockSync *beaconBlockSync
|
||||||
|
engineRPC *rpc.Client
|
||||||
|
|
||||||
|
chainHeadSub event.Subscription
|
||||||
|
engineClient *engineClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(ctx *cli.Context) *Client {
|
||||||
|
if !ctx.IsSet(utils.BeaconApiFlag.Name) {
|
||||||
|
utils.Fatalf("Beacon node light client API URL not specified")
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
chainConfig = makeChainConfig(ctx)
|
||||||
|
customHeader = make(map[string]string)
|
||||||
|
)
|
||||||
|
for _, s := range ctx.StringSlice(utils.BeaconApiHeaderFlag.Name) {
|
||||||
|
kv := strings.Split(s, ":")
|
||||||
|
if len(kv) != 2 {
|
||||||
|
utils.Fatalf("Invalid custom API header entry: %s", s)
|
||||||
|
}
|
||||||
|
customHeader[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// create data structures
|
||||||
|
var (
|
||||||
|
db = memorydb.New()
|
||||||
|
threshold = ctx.Int(utils.BeaconThresholdFlag.Name)
|
||||||
|
committeeChain = light.NewCommitteeChain(db, chainConfig.ChainConfig, threshold, !ctx.Bool(utils.BeaconNoFilterFlag.Name))
|
||||||
|
headTracker = light.NewHeadTracker(committeeChain, threshold)
|
||||||
|
)
|
||||||
|
headSync := sync.NewHeadSync(headTracker, committeeChain)
|
||||||
|
|
||||||
|
// set up scheduler and sync modules
|
||||||
|
scheduler := request.NewScheduler()
|
||||||
|
checkpointInit := sync.NewCheckpointInit(committeeChain, chainConfig.Checkpoint)
|
||||||
|
forwardSync := sync.NewForwardUpdateSync(committeeChain)
|
||||||
|
beaconBlockSync := newBeaconBlockSync(headTracker)
|
||||||
|
scheduler.RegisterTarget(headTracker)
|
||||||
|
scheduler.RegisterTarget(committeeChain)
|
||||||
|
scheduler.RegisterModule(checkpointInit, "checkpointInit")
|
||||||
|
scheduler.RegisterModule(forwardSync, "forwardSync")
|
||||||
|
scheduler.RegisterModule(headSync, "headSync")
|
||||||
|
scheduler.RegisterModule(beaconBlockSync, "beaconBlockSync")
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
scheduler: scheduler,
|
||||||
|
urls: ctx.StringSlice(utils.BeaconApiFlag.Name),
|
||||||
|
customHeader: customHeader,
|
||||||
|
chainConfig: &chainConfig,
|
||||||
|
blockSync: beaconBlockSync,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetEngineRPC(engine *rpc.Client) {
|
||||||
|
c.engineRPC = engine
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Start() error {
|
||||||
|
headCh := make(chan types.ChainHeadEvent, 16)
|
||||||
|
c.chainHeadSub = c.blockSync.SubscribeChainHead(headCh)
|
||||||
|
c.engineClient = startEngineClient(c.chainConfig, c.engineRPC, headCh)
|
||||||
|
|
||||||
|
c.scheduler.Start()
|
||||||
|
for _, url := range c.urls {
|
||||||
|
beaconApi := api.NewBeaconLightApi(url, c.customHeader)
|
||||||
|
c.scheduler.RegisterServer(request.NewServer(api.NewApiServer(beaconApi), &mclock.System{}))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Stop() error {
|
||||||
|
c.engineClient.stop()
|
||||||
|
c.chainHeadSub.Unsubscribe()
|
||||||
|
c.scheduler.Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
129
beacon/blsync/config.go
Normal file
129
beacon/blsync/config.go
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package blsync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// lightClientConfig contains beacon light client configuration
|
||||||
|
type lightClientConfig struct {
|
||||||
|
*types.ChainConfig
|
||||||
|
Checkpoint common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
MainnetConfig = lightClientConfig{
|
||||||
|
ChainConfig: (&types.ChainConfig{
|
||||||
|
GenesisValidatorsRoot: common.HexToHash("0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95"),
|
||||||
|
GenesisTime: 1606824023,
|
||||||
|
}).
|
||||||
|
AddFork("GENESIS", 0, []byte{0, 0, 0, 0}).
|
||||||
|
AddFork("ALTAIR", 74240, []byte{1, 0, 0, 0}).
|
||||||
|
AddFork("BELLATRIX", 144896, []byte{2, 0, 0, 0}).
|
||||||
|
AddFork("CAPELLA", 194048, []byte{3, 0, 0, 0}).
|
||||||
|
AddFork("DENEB", 269568, []byte{4, 0, 0, 0}),
|
||||||
|
Checkpoint: common.HexToHash("0x388be41594ec7d6a6894f18c73f3469f07e2c19a803de4755d335817ed8e2e5a"),
|
||||||
|
}
|
||||||
|
|
||||||
|
SepoliaConfig = lightClientConfig{
|
||||||
|
ChainConfig: (&types.ChainConfig{
|
||||||
|
GenesisValidatorsRoot: common.HexToHash("0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078"),
|
||||||
|
GenesisTime: 1655733600,
|
||||||
|
}).
|
||||||
|
AddFork("GENESIS", 0, []byte{144, 0, 0, 105}).
|
||||||
|
AddFork("ALTAIR", 50, []byte{144, 0, 0, 112}).
|
||||||
|
AddFork("BELLATRIX", 100, []byte{144, 0, 0, 113}).
|
||||||
|
AddFork("CAPELLA", 56832, []byte{144, 0, 0, 114}).
|
||||||
|
AddFork("DENEB", 132608, []byte{144, 0, 0, 115}),
|
||||||
|
Checkpoint: common.HexToHash("0x1005a6d9175e96bfbce4d35b80f468e9bff0b674e1e861d16e09e10005a58e81"),
|
||||||
|
}
|
||||||
|
|
||||||
|
GoerliConfig = lightClientConfig{
|
||||||
|
ChainConfig: (&types.ChainConfig{
|
||||||
|
GenesisValidatorsRoot: common.HexToHash("0x043db0d9a83813551ee2f33450d23797757d430911a9320530ad8a0eabc43efb"),
|
||||||
|
GenesisTime: 1614588812,
|
||||||
|
}).
|
||||||
|
AddFork("GENESIS", 0, []byte{0, 0, 16, 32}).
|
||||||
|
AddFork("ALTAIR", 36660, []byte{1, 0, 16, 32}).
|
||||||
|
AddFork("BELLATRIX", 112260, []byte{2, 0, 16, 32}).
|
||||||
|
AddFork("CAPELLA", 162304, []byte{3, 0, 16, 32}).
|
||||||
|
AddFork("DENEB", 231680, []byte{4, 0, 16, 32}),
|
||||||
|
Checkpoint: common.HexToHash("0x53a0f4f0a378e2c4ae0a9ee97407eb69d0d737d8d8cd0a5fb1093f42f7b81c49"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeChainConfig(ctx *cli.Context) lightClientConfig {
|
||||||
|
var config lightClientConfig
|
||||||
|
customConfig := ctx.IsSet(utils.BeaconConfigFlag.Name)
|
||||||
|
utils.CheckExclusive(ctx, utils.MainnetFlag, utils.GoerliFlag, utils.SepoliaFlag, utils.BeaconConfigFlag)
|
||||||
|
switch {
|
||||||
|
case ctx.Bool(utils.MainnetFlag.Name):
|
||||||
|
config = MainnetConfig
|
||||||
|
case ctx.Bool(utils.SepoliaFlag.Name):
|
||||||
|
config = SepoliaConfig
|
||||||
|
case ctx.Bool(utils.GoerliFlag.Name):
|
||||||
|
config = GoerliConfig
|
||||||
|
default:
|
||||||
|
if !customConfig {
|
||||||
|
config = MainnetConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Genesis root and time should always be specified together with custom chain config
|
||||||
|
if customConfig {
|
||||||
|
if !ctx.IsSet(utils.BeaconGenesisRootFlag.Name) {
|
||||||
|
utils.Fatalf("Custom beacon chain config is specified but genesis root is missing")
|
||||||
|
}
|
||||||
|
if !ctx.IsSet(utils.BeaconGenesisTimeFlag.Name) {
|
||||||
|
utils.Fatalf("Custom beacon chain config is specified but genesis time is missing")
|
||||||
|
}
|
||||||
|
if !ctx.IsSet(utils.BeaconCheckpointFlag.Name) {
|
||||||
|
utils.Fatalf("Custom beacon chain config is specified but checkpoint is missing")
|
||||||
|
}
|
||||||
|
config.ChainConfig = &types.ChainConfig{
|
||||||
|
GenesisTime: ctx.Uint64(utils.BeaconGenesisTimeFlag.Name),
|
||||||
|
}
|
||||||
|
if c, err := hexutil.Decode(ctx.String(utils.BeaconGenesisRootFlag.Name)); err == nil && len(c) <= 32 {
|
||||||
|
copy(config.GenesisValidatorsRoot[:len(c)], c)
|
||||||
|
} else {
|
||||||
|
utils.Fatalf("Invalid hex string", "beacon.genesis.gvroot", ctx.String(utils.BeaconGenesisRootFlag.Name), "error", err)
|
||||||
|
}
|
||||||
|
if err := config.ChainConfig.LoadForks(ctx.String(utils.BeaconConfigFlag.Name)); err != nil {
|
||||||
|
utils.Fatalf("Could not load beacon chain config file", "file name", ctx.String(utils.BeaconConfigFlag.Name), "error", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ctx.IsSet(utils.BeaconGenesisRootFlag.Name) {
|
||||||
|
utils.Fatalf("Genesis root is specified but custom beacon chain config is missing")
|
||||||
|
}
|
||||||
|
if ctx.IsSet(utils.BeaconGenesisTimeFlag.Name) {
|
||||||
|
utils.Fatalf("Genesis time is specified but custom beacon chain config is missing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Checkpoint is required with custom chain config and is optional with pre-defined config
|
||||||
|
if ctx.IsSet(utils.BeaconCheckpointFlag.Name) {
|
||||||
|
if c, err := hexutil.Decode(ctx.String(utils.BeaconCheckpointFlag.Name)); err == nil && len(c) <= 32 {
|
||||||
|
copy(config.Checkpoint[:len(c)], c)
|
||||||
|
} else {
|
||||||
|
utils.Fatalf("Invalid hex string", "beacon.checkpoint", ctx.String(utils.BeaconCheckpointFlag.Name), "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
150
beacon/blsync/engineclient.go
Normal file
150
beacon/blsync/engineclient.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package blsync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
ctypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type engineClient struct {
|
||||||
|
config *lightClientConfig
|
||||||
|
rpc *rpc.Client
|
||||||
|
rootCtx context.Context
|
||||||
|
cancelRoot context.CancelFunc
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func startEngineClient(config *lightClientConfig, rpc *rpc.Client, headCh <-chan types.ChainHeadEvent) *engineClient {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
ec := &engineClient{
|
||||||
|
config: config,
|
||||||
|
rpc: rpc,
|
||||||
|
rootCtx: ctx,
|
||||||
|
cancelRoot: cancel,
|
||||||
|
}
|
||||||
|
ec.wg.Add(1)
|
||||||
|
go ec.updateLoop(headCh)
|
||||||
|
return ec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *engineClient) stop() {
|
||||||
|
ec.cancelRoot()
|
||||||
|
ec.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *engineClient) updateLoop(headCh <-chan types.ChainHeadEvent) {
|
||||||
|
defer ec.wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ec.rootCtx.Done():
|
||||||
|
log.Debug("Stopping engine API update loop")
|
||||||
|
return
|
||||||
|
|
||||||
|
case event := <-headCh:
|
||||||
|
if ec.rpc == nil { // dry run, no engine API specified
|
||||||
|
log.Info("New execution block retrieved", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "finalized", event.Finalized)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fork := ec.config.ForkAtEpoch(event.BeaconHead.Epoch())
|
||||||
|
forkName := strings.ToLower(fork.Name)
|
||||||
|
|
||||||
|
log.Debug("Calling NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash())
|
||||||
|
if status, err := ec.callNewPayload(forkName, event); err == nil {
|
||||||
|
log.Info("Successful NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "status", status)
|
||||||
|
} else {
|
||||||
|
log.Error("Failed NewPayload", "number", event.Block.NumberU64(), "hash", event.Block.Hash(), "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("Calling ForkchoiceUpdated", "head", event.Block.Hash())
|
||||||
|
if status, err := ec.callForkchoiceUpdated(forkName, event); err == nil {
|
||||||
|
log.Info("Successful ForkchoiceUpdated", "head", event.Block.Hash(), "status", status)
|
||||||
|
} else {
|
||||||
|
log.Error("Failed ForkchoiceUpdated", "head", event.Block.Hash(), "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent) (string, error) {
|
||||||
|
execData := engine.BlockToExecutableData(event.Block, nil, nil).ExecutionPayload
|
||||||
|
|
||||||
|
var (
|
||||||
|
method string
|
||||||
|
params = []any{execData}
|
||||||
|
)
|
||||||
|
switch fork {
|
||||||
|
case "deneb":
|
||||||
|
method = "engine_newPayloadV3"
|
||||||
|
parentBeaconRoot := event.BeaconHead.ParentRoot
|
||||||
|
blobHashes := collectBlobHashes(event.Block)
|
||||||
|
params = append(params, blobHashes, parentBeaconRoot)
|
||||||
|
case "capella":
|
||||||
|
method = "engine_newPayloadV2"
|
||||||
|
default:
|
||||||
|
method = "engine_newPayloadV1"
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ec.rootCtx, time.Second*5)
|
||||||
|
defer cancel()
|
||||||
|
var resp engine.PayloadStatusV1
|
||||||
|
err := ec.rpc.CallContext(ctx, &resp, method, params...)
|
||||||
|
return resp.Status, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectBlobHashes(b *ctypes.Block) []common.Hash {
|
||||||
|
list := make([]common.Hash, 0)
|
||||||
|
for _, tx := range b.Transactions() {
|
||||||
|
list = append(list, tx.BlobHashes()...)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *engineClient) callForkchoiceUpdated(fork string, event types.ChainHeadEvent) (string, error) {
|
||||||
|
update := engine.ForkchoiceStateV1{
|
||||||
|
HeadBlockHash: event.Block.Hash(),
|
||||||
|
SafeBlockHash: event.Finalized,
|
||||||
|
FinalizedBlockHash: event.Finalized,
|
||||||
|
}
|
||||||
|
|
||||||
|
var method string
|
||||||
|
switch fork {
|
||||||
|
case "deneb":
|
||||||
|
method = "engine_forkchoiceUpdatedV3"
|
||||||
|
case "capella":
|
||||||
|
method = "engine_forkchoiceUpdatedV2"
|
||||||
|
default:
|
||||||
|
method = "engine_forkchoiceUpdatedV1"
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ec.rootCtx, time.Second*5)
|
||||||
|
defer cancel()
|
||||||
|
var resp engine.ForkChoiceResponse
|
||||||
|
err := ec.rpc.CallContext(ctx, &resp, method, update, nil)
|
||||||
|
return resp.PayloadStatus.Status, err
|
||||||
|
}
|
||||||
@@ -80,6 +80,7 @@ var (
|
|||||||
InvalidPayloadAttributes = &EngineAPIError{code: -38003, msg: "Invalid payload attributes"}
|
InvalidPayloadAttributes = &EngineAPIError{code: -38003, msg: "Invalid payload attributes"}
|
||||||
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
|
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
|
||||||
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
|
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
|
||||||
|
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
|
||||||
|
|
||||||
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
|
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
|
||||||
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
|
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
|
||||||
|
|||||||
@@ -20,12 +20,14 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
|
|||||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
|
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
|
||||||
}
|
}
|
||||||
var enc PayloadAttributes
|
var enc PayloadAttributes
|
||||||
enc.Timestamp = hexutil.Uint64(p.Timestamp)
|
enc.Timestamp = hexutil.Uint64(p.Timestamp)
|
||||||
enc.Random = p.Random
|
enc.Random = p.Random
|
||||||
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
|
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
|
||||||
enc.Withdrawals = p.Withdrawals
|
enc.Withdrawals = p.Withdrawals
|
||||||
|
enc.BeaconRoot = p.BeaconRoot
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +38,7 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
|
|||||||
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
||||||
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
|
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
|
||||||
}
|
}
|
||||||
var dec PayloadAttributes
|
var dec PayloadAttributes
|
||||||
if err := json.Unmarshal(input, &dec); err != nil {
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
@@ -56,5 +59,8 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
|
|||||||
if dec.Withdrawals != nil {
|
if dec.Withdrawals != nil {
|
||||||
p.Withdrawals = dec.Withdrawals
|
p.Withdrawals = dec.Withdrawals
|
||||||
}
|
}
|
||||||
|
if dec.BeaconRoot != nil {
|
||||||
|
p.BeaconRoot = dec.BeaconRoot
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
|
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||||
|
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||||
}
|
}
|
||||||
var enc ExecutableData
|
var enc ExecutableData
|
||||||
enc.ParentHash = e.ParentHash
|
enc.ParentHash = e.ParentHash
|
||||||
@@ -54,6 +56,8 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
enc.Withdrawals = e.Withdrawals
|
enc.Withdrawals = e.Withdrawals
|
||||||
|
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
|
||||||
|
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +79,8 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||||||
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
||||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
|
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||||
|
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||||
}
|
}
|
||||||
var dec ExecutableData
|
var dec ExecutableData
|
||||||
if err := json.Unmarshal(input, &dec); err != nil {
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
@@ -142,5 +148,11 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||||||
if dec.Withdrawals != nil {
|
if dec.Withdrawals != nil {
|
||||||
e.Withdrawals = dec.Withdrawals
|
e.Withdrawals = dec.Withdrawals
|
||||||
}
|
}
|
||||||
|
if dec.BlobGasUsed != nil {
|
||||||
|
e.BlobGasUsed = (*uint64)(dec.BlobGasUsed)
|
||||||
|
}
|
||||||
|
if dec.ExcessBlobGas != nil {
|
||||||
|
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,14 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) {
|
|||||||
type ExecutionPayloadEnvelope struct {
|
type ExecutionPayloadEnvelope struct {
|
||||||
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
||||||
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
||||||
|
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||||
|
Override bool `json:"shouldOverrideBuilder"`
|
||||||
}
|
}
|
||||||
var enc ExecutionPayloadEnvelope
|
var enc ExecutionPayloadEnvelope
|
||||||
enc.ExecutionPayload = e.ExecutionPayload
|
enc.ExecutionPayload = e.ExecutionPayload
|
||||||
enc.BlockValue = (*hexutil.Big)(e.BlockValue)
|
enc.BlockValue = (*hexutil.Big)(e.BlockValue)
|
||||||
|
enc.BlobsBundle = e.BlobsBundle
|
||||||
|
enc.Override = e.Override
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +33,8 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
|
|||||||
type ExecutionPayloadEnvelope struct {
|
type ExecutionPayloadEnvelope struct {
|
||||||
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
||||||
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
||||||
|
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||||
|
Override *bool `json:"shouldOverrideBuilder"`
|
||||||
}
|
}
|
||||||
var dec ExecutionPayloadEnvelope
|
var dec ExecutionPayloadEnvelope
|
||||||
if err := json.Unmarshal(input, &dec); err != nil {
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
@@ -42,5 +48,11 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
|
|||||||
return errors.New("missing required field 'blockValue' for ExecutionPayloadEnvelope")
|
return errors.New("missing required field 'blockValue' for ExecutionPayloadEnvelope")
|
||||||
}
|
}
|
||||||
e.BlockValue = (*big.Int)(dec.BlockValue)
|
e.BlockValue = (*big.Int)(dec.BlockValue)
|
||||||
|
if dec.BlobsBundle != nil {
|
||||||
|
e.BlobsBundle = dec.BlobsBundle
|
||||||
|
}
|
||||||
|
if dec.Override != nil {
|
||||||
|
e.Override = *dec.Override
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package engine
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
@@ -26,6 +27,16 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PayloadVersion denotes the version of PayloadAttributes used to request the
|
||||||
|
// building of the payload to commence.
|
||||||
|
type PayloadVersion byte
|
||||||
|
|
||||||
|
var (
|
||||||
|
PayloadV1 PayloadVersion = 0x1
|
||||||
|
PayloadV2 PayloadVersion = 0x2
|
||||||
|
PayloadV3 PayloadVersion = 0x3
|
||||||
|
)
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
|
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
|
||||||
|
|
||||||
// PayloadAttributes describes the environment context in which a block should
|
// PayloadAttributes describes the environment context in which a block should
|
||||||
@@ -35,6 +46,7 @@ type PayloadAttributes struct {
|
|||||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
|
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON type overrides for PayloadAttributes.
|
// JSON type overrides for PayloadAttributes.
|
||||||
@@ -61,6 +73,8 @@ type ExecutableData struct {
|
|||||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||||
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
|
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
||||||
|
ExcessBlobGas *uint64 `json:"excessBlobGas"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON type overrides for executableData.
|
// JSON type overrides for executableData.
|
||||||
@@ -73,6 +87,8 @@ type executableDataMarshaling struct {
|
|||||||
ExtraData hexutil.Bytes
|
ExtraData hexutil.Bytes
|
||||||
LogsBloom hexutil.Bytes
|
LogsBloom hexutil.Bytes
|
||||||
Transactions []hexutil.Bytes
|
Transactions []hexutil.Bytes
|
||||||
|
BlobGasUsed *hexutil.Uint64
|
||||||
|
ExcessBlobGas *hexutil.Uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go
|
//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go
|
||||||
@@ -80,6 +96,14 @@ type executableDataMarshaling struct {
|
|||||||
type ExecutionPayloadEnvelope struct {
|
type ExecutionPayloadEnvelope struct {
|
||||||
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
||||||
BlockValue *big.Int `json:"blockValue" gencodec:"required"`
|
BlockValue *big.Int `json:"blockValue" gencodec:"required"`
|
||||||
|
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||||
|
Override bool `json:"shouldOverrideBuilder"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlobsBundleV1 struct {
|
||||||
|
Commitments []hexutil.Bytes `json:"commitments"`
|
||||||
|
Proofs []hexutil.Bytes `json:"proofs"`
|
||||||
|
Blobs []hexutil.Bytes `json:"blobs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON type overrides for ExecutionPayloadEnvelope.
|
// JSON type overrides for ExecutionPayloadEnvelope.
|
||||||
@@ -102,6 +126,16 @@ type TransitionConfigurationV1 struct {
|
|||||||
// PayloadID is an identifier of the payload build process
|
// PayloadID is an identifier of the payload build process
|
||||||
type PayloadID [8]byte
|
type PayloadID [8]byte
|
||||||
|
|
||||||
|
// Version returns the payload version associated with the identifier.
|
||||||
|
func (b PayloadID) Version() PayloadVersion {
|
||||||
|
return PayloadVersion(b[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is returns whether the identifier matches any of provided payload versions.
|
||||||
|
func (b PayloadID) Is(versions ...PayloadVersion) bool {
|
||||||
|
return slices.Contains(versions, b.Version())
|
||||||
|
}
|
||||||
|
|
||||||
func (b PayloadID) String() string {
|
func (b PayloadID) String() string {
|
||||||
return hexutil.Encode(b[:])
|
return hexutil.Encode(b[:])
|
||||||
}
|
}
|
||||||
@@ -152,14 +186,15 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
|
|||||||
// ExecutableDataToBlock constructs a block from executable data.
|
// ExecutableDataToBlock constructs a block from executable data.
|
||||||
// It verifies that the following fields:
|
// It verifies that the following fields:
|
||||||
//
|
//
|
||||||
// len(extraData) <= 32
|
// len(extraData) <= 32
|
||||||
// uncleHash = emptyUncleHash
|
// uncleHash = emptyUncleHash
|
||||||
// difficulty = 0
|
// difficulty = 0
|
||||||
|
// if versionedHashes != nil, versionedHashes match to blob transactions
|
||||||
//
|
//
|
||||||
// and that the blockhash of the constructed block matches the parameters. Nil
|
// and that the blockhash of the constructed block matches the parameters. Nil
|
||||||
// Withdrawals value will propagate through the returned block. Empty
|
// Withdrawals value will propagate through the returned block. Empty
|
||||||
// Withdrawals value must be passed via non-nil, length 0 value in params.
|
// Withdrawals value must be passed via non-nil, length 0 value in params.
|
||||||
func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
|
func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) {
|
||||||
txs, err := decodeTransactions(params.Transactions)
|
txs, err := decodeTransactions(params.Transactions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -174,6 +209,18 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
|
|||||||
if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) {
|
if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) {
|
||||||
return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas)
|
return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas)
|
||||||
}
|
}
|
||||||
|
var blobHashes = make([]common.Hash, 0, len(txs))
|
||||||
|
for _, tx := range txs {
|
||||||
|
blobHashes = append(blobHashes, tx.BlobHashes()...)
|
||||||
|
}
|
||||||
|
if len(blobHashes) != len(versionedHashes) {
|
||||||
|
return nil, fmt.Errorf("invalid number of versionedHashes: %v blobHashes: %v", versionedHashes, blobHashes)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(blobHashes); i++ {
|
||||||
|
if blobHashes[i] != versionedHashes[i] {
|
||||||
|
return nil, fmt.Errorf("invalid versionedHash at %v: %v blobHashes: %v", i, versionedHashes, blobHashes)
|
||||||
|
}
|
||||||
|
}
|
||||||
// Only set withdrawalsRoot if it is non-nil. This allows CLs to use
|
// Only set withdrawalsRoot if it is non-nil. This allows CLs to use
|
||||||
// ExecutableData before withdrawals are enabled by marshaling
|
// ExecutableData before withdrawals are enabled by marshaling
|
||||||
// Withdrawals as the json null value.
|
// Withdrawals as the json null value.
|
||||||
@@ -183,24 +230,27 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
|
|||||||
withdrawalsRoot = &h
|
withdrawalsRoot = &h
|
||||||
}
|
}
|
||||||
header := &types.Header{
|
header := &types.Header{
|
||||||
ParentHash: params.ParentHash,
|
ParentHash: params.ParentHash,
|
||||||
UncleHash: types.EmptyUncleHash,
|
UncleHash: types.EmptyUncleHash,
|
||||||
Coinbase: params.FeeRecipient,
|
Coinbase: params.FeeRecipient,
|
||||||
Root: params.StateRoot,
|
Root: params.StateRoot,
|
||||||
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
|
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
|
||||||
ReceiptHash: params.ReceiptsRoot,
|
ReceiptHash: params.ReceiptsRoot,
|
||||||
Bloom: types.BytesToBloom(params.LogsBloom),
|
Bloom: types.BytesToBloom(params.LogsBloom),
|
||||||
Difficulty: common.Big0,
|
Difficulty: common.Big0,
|
||||||
Number: new(big.Int).SetUint64(params.Number),
|
Number: new(big.Int).SetUint64(params.Number),
|
||||||
GasLimit: params.GasLimit,
|
GasLimit: params.GasLimit,
|
||||||
GasUsed: params.GasUsed,
|
GasUsed: params.GasUsed,
|
||||||
Time: params.Timestamp,
|
Time: params.Timestamp,
|
||||||
BaseFee: params.BaseFeePerGas,
|
BaseFee: params.BaseFeePerGas,
|
||||||
Extra: params.ExtraData,
|
Extra: params.ExtraData,
|
||||||
MixDigest: params.Random,
|
MixDigest: params.Random,
|
||||||
WithdrawalsHash: withdrawalsRoot,
|
WithdrawalsHash: withdrawalsRoot,
|
||||||
|
ExcessBlobGas: params.ExcessBlobGas,
|
||||||
|
BlobGasUsed: params.BlobGasUsed,
|
||||||
|
ParentBeaconRoot: beaconRoot,
|
||||||
}
|
}
|
||||||
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals)
|
block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: params.Withdrawals})
|
||||||
if block.Hash() != params.BlockHash {
|
if block.Hash() != params.BlockHash {
|
||||||
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash())
|
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash())
|
||||||
}
|
}
|
||||||
@@ -209,7 +259,7 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) {
|
|||||||
|
|
||||||
// BlockToExecutableData constructs the ExecutableData structure by filling the
|
// BlockToExecutableData constructs the ExecutableData structure by filling the
|
||||||
// fields from the given block. It assumes the given block is post-merge block.
|
// fields from the given block. It assumes the given block is post-merge block.
|
||||||
func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadEnvelope {
|
func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar) *ExecutionPayloadEnvelope {
|
||||||
data := &ExecutableData{
|
data := &ExecutableData{
|
||||||
BlockHash: block.Hash(),
|
BlockHash: block.Hash(),
|
||||||
ParentHash: block.ParentHash(),
|
ParentHash: block.ParentHash(),
|
||||||
@@ -226,8 +276,22 @@ func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadE
|
|||||||
Random: block.MixDigest(),
|
Random: block.MixDigest(),
|
||||||
ExtraData: block.Extra(),
|
ExtraData: block.Extra(),
|
||||||
Withdrawals: block.Withdrawals(),
|
Withdrawals: block.Withdrawals(),
|
||||||
|
BlobGasUsed: block.BlobGasUsed(),
|
||||||
|
ExcessBlobGas: block.ExcessBlobGas(),
|
||||||
}
|
}
|
||||||
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees}
|
bundle := BlobsBundleV1{
|
||||||
|
Commitments: make([]hexutil.Bytes, 0),
|
||||||
|
Blobs: make([]hexutil.Bytes, 0),
|
||||||
|
Proofs: make([]hexutil.Bytes, 0),
|
||||||
|
}
|
||||||
|
for _, sidecar := range sidecars {
|
||||||
|
for j := range sidecar.Blobs {
|
||||||
|
bundle.Blobs = append(bundle.Blobs, hexutil.Bytes(sidecar.Blobs[j][:]))
|
||||||
|
bundle.Commitments = append(bundle.Commitments, hexutil.Bytes(sidecar.Commitments[j][:]))
|
||||||
|
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees, BlobsBundle: &bundle, Override: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1
|
// ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1
|
||||||
@@ -235,3 +299,21 @@ type ExecutionPayloadBodyV1 struct {
|
|||||||
TransactionData []hexutil.Bytes `json:"transactions"`
|
TransactionData []hexutil.Bytes `json:"transactions"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client identifiers to support ClientVersionV1.
|
||||||
|
const (
|
||||||
|
ClientCode = "GE"
|
||||||
|
ClientName = "go-ethereum"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientVersionV1 contains information which identifies a client implementation.
|
||||||
|
type ClientVersionV1 struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Commit string `json:"commit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ClientVersionV1) String() string {
|
||||||
|
return fmt.Sprintf("%s-%s-%s-%s", v.Code, v.Name, v.Version, v.Commit)
|
||||||
|
}
|
||||||
|
|||||||
114
beacon/light/api/api_server.go
Executable file
114
beacon/light/api/api_server.go
Executable file
@@ -0,0 +1,114 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/sync"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApiServer is a wrapper around BeaconLightApi that implements request.requestServer.
|
||||||
|
type ApiServer struct {
|
||||||
|
api *BeaconLightApi
|
||||||
|
eventCallback func(event request.Event)
|
||||||
|
unsubscribe func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApiServer creates a new ApiServer.
|
||||||
|
func NewApiServer(api *BeaconLightApi) *ApiServer {
|
||||||
|
return &ApiServer{api: api}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe implements request.requestServer.
|
||||||
|
func (s *ApiServer) Subscribe(eventCallback func(event request.Event)) {
|
||||||
|
s.eventCallback = eventCallback
|
||||||
|
listener := HeadEventListener{
|
||||||
|
OnNewHead: func(slot uint64, blockRoot common.Hash) {
|
||||||
|
log.Debug("New head received", "slot", slot, "blockRoot", blockRoot)
|
||||||
|
eventCallback(request.Event{Type: sync.EvNewHead, Data: types.HeadInfo{Slot: slot, BlockRoot: blockRoot}})
|
||||||
|
},
|
||||||
|
OnOptimistic: func(update types.OptimisticUpdate) {
|
||||||
|
log.Debug("New optimistic update received", "slot", update.Attested.Slot, "blockRoot", update.Attested.Hash(), "signerCount", update.Signature.SignerCount())
|
||||||
|
eventCallback(request.Event{Type: sync.EvNewOptimisticUpdate, Data: update})
|
||||||
|
},
|
||||||
|
OnFinality: func(update types.FinalityUpdate) {
|
||||||
|
log.Debug("New finality update received", "slot", update.Attested.Slot, "blockRoot", update.Attested.Hash(), "signerCount", update.Signature.SignerCount())
|
||||||
|
eventCallback(request.Event{Type: sync.EvNewFinalityUpdate, Data: update})
|
||||||
|
},
|
||||||
|
OnError: func(err error) {
|
||||||
|
log.Warn("Head event stream error", "err", err)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s.unsubscribe = s.api.StartHeadListener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendRequest implements request.requestServer.
|
||||||
|
func (s *ApiServer) SendRequest(id request.ID, req request.Request) {
|
||||||
|
go func() {
|
||||||
|
var resp request.Response
|
||||||
|
var err error
|
||||||
|
switch data := req.(type) {
|
||||||
|
case sync.ReqUpdates:
|
||||||
|
log.Debug("Beacon API: requesting light client update", "reqid", id, "period", data.FirstPeriod, "count", data.Count)
|
||||||
|
var r sync.RespUpdates
|
||||||
|
r.Updates, r.Committees, err = s.api.GetBestUpdatesAndCommittees(data.FirstPeriod, data.Count)
|
||||||
|
resp = r
|
||||||
|
case sync.ReqHeader:
|
||||||
|
var r sync.RespHeader
|
||||||
|
log.Debug("Beacon API: requesting header", "reqid", id, "hash", common.Hash(data))
|
||||||
|
r.Header, r.Canonical, r.Finalized, err = s.api.GetHeader(common.Hash(data))
|
||||||
|
resp = r
|
||||||
|
case sync.ReqCheckpointData:
|
||||||
|
log.Debug("Beacon API: requesting checkpoint data", "reqid", id, "hash", common.Hash(data))
|
||||||
|
resp, err = s.api.GetCheckpointData(common.Hash(data))
|
||||||
|
case sync.ReqBeaconBlock:
|
||||||
|
log.Debug("Beacon API: requesting block", "reqid", id, "hash", common.Hash(data))
|
||||||
|
resp, err = s.api.GetBeaconBlock(common.Hash(data))
|
||||||
|
case sync.ReqFinality:
|
||||||
|
log.Debug("Beacon API: requesting finality update")
|
||||||
|
resp, err = s.api.GetFinalityUpdate()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Beacon API request failed", "type", reflect.TypeOf(req), "reqid", id, "err", err)
|
||||||
|
s.eventCallback(request.Event{Type: request.EvFail, Data: request.RequestResponse{ID: id, Request: req}})
|
||||||
|
} else {
|
||||||
|
log.Debug("Beacon API request answered", "type", reflect.TypeOf(req), "reqid", id)
|
||||||
|
s.eventCallback(request.Event{Type: request.EvResponse, Data: request.RequestResponse{ID: id, Request: req, Response: resp}})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsubscribe implements request.requestServer.
|
||||||
|
// Note: Unsubscribe should not be called concurrently with Subscribe.
|
||||||
|
func (s *ApiServer) Unsubscribe() {
|
||||||
|
if s.unsubscribe != nil {
|
||||||
|
s.unsubscribe()
|
||||||
|
s.unsubscribe = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements request.Server
|
||||||
|
func (s *ApiServer) Name() string {
|
||||||
|
return s.api.url
|
||||||
|
}
|
||||||
578
beacon/light/api/light_api.go
Executable file
578
beacon/light/api/light_api.go
Executable file
@@ -0,0 +1,578 @@
|
|||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more detaiapi.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/donovanhide/eventsource"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/merkle"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound = errors.New("404 Not Found")
|
||||||
|
ErrInternal = errors.New("500 Internal Server Error")
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommitteeUpdate struct {
|
||||||
|
Version string
|
||||||
|
Update types.LightClientUpdate
|
||||||
|
NextSyncCommittee types.SerializedSyncCommittee
|
||||||
|
}
|
||||||
|
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientupdate
|
||||||
|
type committeeUpdateJson struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Data committeeUpdateData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type committeeUpdateData struct {
|
||||||
|
Header jsonBeaconHeader `json:"attested_header"`
|
||||||
|
NextSyncCommittee types.SerializedSyncCommittee `json:"next_sync_committee"`
|
||||||
|
NextSyncCommitteeBranch merkle.Values `json:"next_sync_committee_branch"`
|
||||||
|
FinalizedHeader *jsonBeaconHeader `json:"finalized_header,omitempty"`
|
||||||
|
FinalityBranch merkle.Values `json:"finality_branch,omitempty"`
|
||||||
|
SyncAggregate types.SyncAggregate `json:"sync_aggregate"`
|
||||||
|
SignatureSlot common.Decimal `json:"signature_slot"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonBeaconHeader struct {
|
||||||
|
Beacon types.Header `json:"beacon"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonHeaderWithExecProof struct {
|
||||||
|
Beacon types.Header `json:"beacon"`
|
||||||
|
Execution json.RawMessage `json:"execution"`
|
||||||
|
ExecutionBranch merkle.Values `json:"execution_branch"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (u *CommitteeUpdate) UnmarshalJSON(input []byte) error {
|
||||||
|
var dec committeeUpdateJson
|
||||||
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.Version = dec.Version
|
||||||
|
u.NextSyncCommittee = dec.Data.NextSyncCommittee
|
||||||
|
u.Update = types.LightClientUpdate{
|
||||||
|
AttestedHeader: types.SignedHeader{
|
||||||
|
Header: dec.Data.Header.Beacon,
|
||||||
|
Signature: dec.Data.SyncAggregate,
|
||||||
|
SignatureSlot: uint64(dec.Data.SignatureSlot),
|
||||||
|
},
|
||||||
|
NextSyncCommitteeRoot: u.NextSyncCommittee.Root(),
|
||||||
|
NextSyncCommitteeBranch: dec.Data.NextSyncCommitteeBranch,
|
||||||
|
FinalityBranch: dec.Data.FinalityBranch,
|
||||||
|
}
|
||||||
|
if dec.Data.FinalizedHeader != nil {
|
||||||
|
u.Update.FinalizedHeader = &dec.Data.FinalizedHeader.Beacon
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetcher is an interface useful for debug-harnessing the http api.
|
||||||
|
type fetcher interface {
|
||||||
|
Do(req *http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeaconLightApi requests light client information from a beacon node REST API.
|
||||||
|
// Note: all required API endpoints are currently only implemented by Lodestar.
|
||||||
|
type BeaconLightApi struct {
|
||||||
|
url string
|
||||||
|
client fetcher
|
||||||
|
customHeaders map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBeaconLightApi(url string, customHeaders map[string]string) *BeaconLightApi {
|
||||||
|
return &BeaconLightApi{
|
||||||
|
url: url,
|
||||||
|
client: &http.Client{
|
||||||
|
Timeout: time.Second * 10,
|
||||||
|
},
|
||||||
|
customHeaders: customHeaders,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BeaconLightApi) httpGet(path string) ([]byte, error) {
|
||||||
|
req, err := http.NewRequest("GET", api.url+path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for k, v := range api.customHeaders {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
resp, err := api.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case 200:
|
||||||
|
return io.ReadAll(resp.Body)
|
||||||
|
case 404:
|
||||||
|
return nil, ErrNotFound
|
||||||
|
case 500:
|
||||||
|
return nil, ErrInternal
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected error from API endpoint \"%s\": status code %d", path, resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BeaconLightApi) httpGetf(format string, params ...any) ([]byte, error) {
|
||||||
|
return api.httpGet(fmt.Sprintf(format, params...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBestUpdatesAndCommittees fetches and validates LightClientUpdate for given
|
||||||
|
// period and full serialized committee for the next period (committee root hash
|
||||||
|
// equals update.NextSyncCommitteeRoot).
|
||||||
|
// Note that the results are validated but the update signature should be verified
|
||||||
|
// by the caller as its validity depends on the update chain.
|
||||||
|
func (api *BeaconLightApi) GetBestUpdatesAndCommittees(firstPeriod, count uint64) ([]*types.LightClientUpdate, []*types.SerializedSyncCommittee, error) {
|
||||||
|
resp, err := api.httpGetf("/eth/v1/beacon/light_client/updates?start_period=%d&count=%d", firstPeriod, count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []CommitteeUpdate
|
||||||
|
if err := json.Unmarshal(resp, &data); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if len(data) != int(count) {
|
||||||
|
return nil, nil, errors.New("invalid number of committee updates")
|
||||||
|
}
|
||||||
|
updates := make([]*types.LightClientUpdate, int(count))
|
||||||
|
committees := make([]*types.SerializedSyncCommittee, int(count))
|
||||||
|
for i, d := range data {
|
||||||
|
if d.Update.AttestedHeader.Header.SyncPeriod() != firstPeriod+uint64(i) {
|
||||||
|
return nil, nil, errors.New("wrong committee update header period")
|
||||||
|
}
|
||||||
|
if err := d.Update.Validate(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if d.NextSyncCommittee.Root() != d.Update.NextSyncCommitteeRoot {
|
||||||
|
return nil, nil, errors.New("wrong sync committee root")
|
||||||
|
}
|
||||||
|
updates[i], committees[i] = new(types.LightClientUpdate), new(types.SerializedSyncCommittee)
|
||||||
|
*updates[i], *committees[i] = d.Update, d.NextSyncCommittee
|
||||||
|
}
|
||||||
|
return updates, committees, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOptimisticUpdate fetches the latest available optimistic update.
|
||||||
|
// Note that the signature should be verified by the caller as its validity
|
||||||
|
// depends on the update chain.
|
||||||
|
//
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate
|
||||||
|
func (api *BeaconLightApi) GetOptimisticUpdate() (types.OptimisticUpdate, error) {
|
||||||
|
resp, err := api.httpGet("/eth/v1/beacon/light_client/optimistic_update")
|
||||||
|
if err != nil {
|
||||||
|
return types.OptimisticUpdate{}, err
|
||||||
|
}
|
||||||
|
return decodeOptimisticUpdate(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeOptimisticUpdate(enc []byte) (types.OptimisticUpdate, error) {
|
||||||
|
var data struct {
|
||||||
|
Version string
|
||||||
|
Data struct {
|
||||||
|
Attested jsonHeaderWithExecProof `json:"attested_header"`
|
||||||
|
Aggregate types.SyncAggregate `json:"sync_aggregate"`
|
||||||
|
SignatureSlot common.Decimal `json:"signature_slot"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(enc, &data); err != nil {
|
||||||
|
return types.OptimisticUpdate{}, err
|
||||||
|
}
|
||||||
|
// Decode the execution payload headers.
|
||||||
|
attestedExecHeader, err := types.ExecutionHeaderFromJSON(data.Version, data.Data.Attested.Execution)
|
||||||
|
if err != nil {
|
||||||
|
return types.OptimisticUpdate{}, fmt.Errorf("invalid attested header: %v", err)
|
||||||
|
}
|
||||||
|
if data.Data.Attested.Beacon.StateRoot == (common.Hash{}) {
|
||||||
|
// workaround for different event encoding format in Lodestar
|
||||||
|
if err := json.Unmarshal(enc, &data.Data); err != nil {
|
||||||
|
return types.OptimisticUpdate{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data.Data.Aggregate.Signers) != params.SyncCommitteeBitmaskSize {
|
||||||
|
return types.OptimisticUpdate{}, errors.New("invalid sync_committee_bits length")
|
||||||
|
}
|
||||||
|
if len(data.Data.Aggregate.Signature) != params.BLSSignatureSize {
|
||||||
|
return types.OptimisticUpdate{}, errors.New("invalid sync_committee_signature length")
|
||||||
|
}
|
||||||
|
return types.OptimisticUpdate{
|
||||||
|
Attested: types.HeaderWithExecProof{
|
||||||
|
Header: data.Data.Attested.Beacon,
|
||||||
|
PayloadHeader: attestedExecHeader,
|
||||||
|
PayloadBranch: data.Data.Attested.ExecutionBranch,
|
||||||
|
},
|
||||||
|
Signature: data.Data.Aggregate,
|
||||||
|
SignatureSlot: uint64(data.Data.SignatureSlot),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFinalityUpdate fetches the latest available finality update.
|
||||||
|
//
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientfinalityupdate
|
||||||
|
func (api *BeaconLightApi) GetFinalityUpdate() (types.FinalityUpdate, error) {
|
||||||
|
resp, err := api.httpGet("/eth/v1/beacon/light_client/finality_update")
|
||||||
|
if err != nil {
|
||||||
|
return types.FinalityUpdate{}, err
|
||||||
|
}
|
||||||
|
return decodeFinalityUpdate(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeFinalityUpdate(enc []byte) (types.FinalityUpdate, error) {
|
||||||
|
var data struct {
|
||||||
|
Version string
|
||||||
|
Data struct {
|
||||||
|
Attested jsonHeaderWithExecProof `json:"attested_header"`
|
||||||
|
Finalized jsonHeaderWithExecProof `json:"finalized_header"`
|
||||||
|
FinalityBranch merkle.Values `json:"finality_branch"`
|
||||||
|
Aggregate types.SyncAggregate `json:"sync_aggregate"`
|
||||||
|
SignatureSlot common.Decimal `json:"signature_slot"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(enc, &data); err != nil {
|
||||||
|
return types.FinalityUpdate{}, err
|
||||||
|
}
|
||||||
|
// Decode the execution payload headers.
|
||||||
|
attestedExecHeader, err := types.ExecutionHeaderFromJSON(data.Version, data.Data.Attested.Execution)
|
||||||
|
if err != nil {
|
||||||
|
return types.FinalityUpdate{}, fmt.Errorf("invalid attested header: %v", err)
|
||||||
|
}
|
||||||
|
finalizedExecHeader, err := types.ExecutionHeaderFromJSON(data.Version, data.Data.Finalized.Execution)
|
||||||
|
if err != nil {
|
||||||
|
return types.FinalityUpdate{}, fmt.Errorf("invalid finalized header: %v", err)
|
||||||
|
}
|
||||||
|
// Perform sanity checks.
|
||||||
|
if len(data.Data.Aggregate.Signers) != params.SyncCommitteeBitmaskSize {
|
||||||
|
return types.FinalityUpdate{}, errors.New("invalid sync_committee_bits length")
|
||||||
|
}
|
||||||
|
if len(data.Data.Aggregate.Signature) != params.BLSSignatureSize {
|
||||||
|
return types.FinalityUpdate{}, errors.New("invalid sync_committee_signature length")
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.FinalityUpdate{
|
||||||
|
Attested: types.HeaderWithExecProof{
|
||||||
|
Header: data.Data.Attested.Beacon,
|
||||||
|
PayloadHeader: attestedExecHeader,
|
||||||
|
PayloadBranch: data.Data.Attested.ExecutionBranch,
|
||||||
|
},
|
||||||
|
Finalized: types.HeaderWithExecProof{
|
||||||
|
Header: data.Data.Finalized.Beacon,
|
||||||
|
PayloadHeader: finalizedExecHeader,
|
||||||
|
PayloadBranch: data.Data.Finalized.ExecutionBranch,
|
||||||
|
},
|
||||||
|
FinalityBranch: data.Data.FinalityBranch,
|
||||||
|
Signature: data.Data.Aggregate,
|
||||||
|
SignatureSlot: uint64(data.Data.SignatureSlot),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeader fetches and validates the beacon header with the given blockRoot.
|
||||||
|
// If blockRoot is null hash then the latest head header is fetched.
|
||||||
|
// The values of the canonical and finalized flags are also returned. Note that
|
||||||
|
// these flags are not validated.
|
||||||
|
func (api *BeaconLightApi) GetHeader(blockRoot common.Hash) (types.Header, bool, bool, error) {
|
||||||
|
var blockId string
|
||||||
|
if blockRoot == (common.Hash{}) {
|
||||||
|
blockId = "head"
|
||||||
|
} else {
|
||||||
|
blockId = blockRoot.Hex()
|
||||||
|
}
|
||||||
|
resp, err := api.httpGetf("/eth/v1/beacon/headers/%s", blockId)
|
||||||
|
if err != nil {
|
||||||
|
return types.Header{}, false, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Finalized bool `json:"finalized"`
|
||||||
|
Data struct {
|
||||||
|
Root common.Hash `json:"root"`
|
||||||
|
Canonical bool `json:"canonical"`
|
||||||
|
Header struct {
|
||||||
|
Message types.Header `json:"message"`
|
||||||
|
Signature hexutil.Bytes `json:"signature"`
|
||||||
|
} `json:"header"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(resp, &data); err != nil {
|
||||||
|
return types.Header{}, false, false, err
|
||||||
|
}
|
||||||
|
header := data.Data.Header.Message
|
||||||
|
if blockRoot == (common.Hash{}) {
|
||||||
|
blockRoot = data.Data.Root
|
||||||
|
}
|
||||||
|
if header.Hash() != blockRoot {
|
||||||
|
return types.Header{}, false, false, errors.New("retrieved beacon header root does not match")
|
||||||
|
}
|
||||||
|
return header, data.Data.Canonical, data.Finalized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCheckpointData fetches and validates bootstrap data belonging to the given checkpoint.
|
||||||
|
func (api *BeaconLightApi) GetCheckpointData(checkpointHash common.Hash) (*types.BootstrapData, error) {
|
||||||
|
resp, err := api.httpGetf("/eth/v1/beacon/light_client/bootstrap/0x%x", checkpointHash[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// See data structure definition here:
|
||||||
|
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#lightclientbootstrap
|
||||||
|
type bootstrapData struct {
|
||||||
|
Data struct {
|
||||||
|
Header jsonBeaconHeader `json:"header"`
|
||||||
|
Committee *types.SerializedSyncCommittee `json:"current_sync_committee"`
|
||||||
|
CommitteeBranch merkle.Values `json:"current_sync_committee_branch"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var data bootstrapData
|
||||||
|
if err := json.Unmarshal(resp, &data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if data.Data.Committee == nil {
|
||||||
|
return nil, errors.New("sync committee is missing")
|
||||||
|
}
|
||||||
|
header := data.Data.Header.Beacon
|
||||||
|
if header.Hash() != checkpointHash {
|
||||||
|
return nil, fmt.Errorf("invalid checkpoint block header, have %v want %v", header.Hash(), checkpointHash)
|
||||||
|
}
|
||||||
|
checkpoint := &types.BootstrapData{
|
||||||
|
Header: header,
|
||||||
|
CommitteeBranch: data.Data.CommitteeBranch,
|
||||||
|
CommitteeRoot: data.Data.Committee.Root(),
|
||||||
|
Committee: data.Data.Committee,
|
||||||
|
}
|
||||||
|
if err := checkpoint.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid checkpoint: %w", err)
|
||||||
|
}
|
||||||
|
if checkpoint.Header.Hash() != checkpointHash {
|
||||||
|
return nil, errors.New("wrong checkpoint hash")
|
||||||
|
}
|
||||||
|
return checkpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *BeaconLightApi) GetBeaconBlock(blockRoot common.Hash) (*types.BeaconBlock, error) {
|
||||||
|
resp, err := api.httpGetf("/eth/v2/beacon/blocks/0x%x", blockRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var beaconBlockMessage struct {
|
||||||
|
Version string
|
||||||
|
Data struct {
|
||||||
|
Message json.RawMessage `json:"message"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(resp, &beaconBlockMessage); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid block json data: %v", err)
|
||||||
|
}
|
||||||
|
block, err := types.BlockFromJSON(beaconBlockMessage.Version, beaconBlockMessage.Data.Message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
computedRoot := block.Root()
|
||||||
|
if computedRoot != blockRoot {
|
||||||
|
return nil, fmt.Errorf("Beacon block root hash mismatch (expected: %x, got: %x)", blockRoot, computedRoot)
|
||||||
|
}
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeHeadEvent(enc []byte) (uint64, common.Hash, error) {
|
||||||
|
var data struct {
|
||||||
|
Slot common.Decimal `json:"slot"`
|
||||||
|
Block common.Hash `json:"block"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(enc, &data); err != nil {
|
||||||
|
return 0, common.Hash{}, err
|
||||||
|
}
|
||||||
|
return uint64(data.Slot), data.Block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type HeadEventListener struct {
|
||||||
|
OnNewHead func(slot uint64, blockRoot common.Hash)
|
||||||
|
OnOptimistic func(head types.OptimisticUpdate)
|
||||||
|
OnFinality func(head types.FinalityUpdate)
|
||||||
|
OnError func(err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartHeadListener creates an event subscription for heads and signed (optimistic)
|
||||||
|
// head updates and calls the specified callback functions when they are received.
|
||||||
|
// The callbacks are also called for the current head and optimistic head at startup.
|
||||||
|
// They are never called concurrently.
|
||||||
|
func (api *BeaconLightApi) StartHeadListener(listener HeadEventListener) func() {
|
||||||
|
var (
|
||||||
|
ctx, closeCtx = context.WithCancel(context.Background())
|
||||||
|
streamCh = make(chan *eventsource.Stream, 1)
|
||||||
|
wg sync.WaitGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
// When connected to a Lodestar node the subscription blocks until the first actual
|
||||||
|
// event arrives; therefore we create the subscription in a separate goroutine while
|
||||||
|
// letting the main goroutine sync up to the current head.
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
stream := api.startEventStream(ctx, &listener)
|
||||||
|
if stream == nil {
|
||||||
|
// This case happens when the context was closed.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Stream was opened, wait for close signal.
|
||||||
|
streamCh <- stream
|
||||||
|
<-ctx.Done()
|
||||||
|
stream.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
// Request initial data.
|
||||||
|
log.Trace("Requesting initial head header")
|
||||||
|
if head, _, _, err := api.GetHeader(common.Hash{}); err == nil {
|
||||||
|
log.Trace("Retrieved initial head header", "slot", head.Slot, "hash", head.Hash())
|
||||||
|
listener.OnNewHead(head.Slot, head.Hash())
|
||||||
|
} else {
|
||||||
|
log.Debug("Failed to retrieve initial head header", "error", err)
|
||||||
|
}
|
||||||
|
log.Trace("Requesting initial optimistic update")
|
||||||
|
if optimisticUpdate, err := api.GetOptimisticUpdate(); err == nil {
|
||||||
|
log.Trace("Retrieved initial optimistic update", "slot", optimisticUpdate.Attested.Slot, "hash", optimisticUpdate.Attested.Hash())
|
||||||
|
listener.OnOptimistic(optimisticUpdate)
|
||||||
|
} else {
|
||||||
|
log.Debug("Failed to retrieve initial optimistic update", "error", err)
|
||||||
|
}
|
||||||
|
log.Trace("Requesting initial finality update")
|
||||||
|
if finalityUpdate, err := api.GetFinalityUpdate(); err == nil {
|
||||||
|
log.Trace("Retrieved initial finality update", "slot", finalityUpdate.Finalized.Slot, "hash", finalityUpdate.Finalized.Hash())
|
||||||
|
listener.OnFinality(finalityUpdate)
|
||||||
|
} else {
|
||||||
|
log.Debug("Failed to retrieve initial finality update", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace("Starting event stream processing loop")
|
||||||
|
// Receive the stream.
|
||||||
|
var stream *eventsource.Stream
|
||||||
|
select {
|
||||||
|
case stream = <-streamCh:
|
||||||
|
case <-ctx.Done():
|
||||||
|
log.Trace("Stopping event stream processing loop")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-stream.Events:
|
||||||
|
if !ok {
|
||||||
|
log.Trace("Event stream closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Trace("New event received from event stream", "type", event.Event())
|
||||||
|
switch event.Event() {
|
||||||
|
case "head":
|
||||||
|
slot, blockRoot, err := decodeHeadEvent([]byte(event.Data()))
|
||||||
|
if err == nil {
|
||||||
|
listener.OnNewHead(slot, blockRoot)
|
||||||
|
} else {
|
||||||
|
listener.OnError(fmt.Errorf("error decoding head event: %v", err))
|
||||||
|
}
|
||||||
|
case "light_client_optimistic_update":
|
||||||
|
optimisticUpdate, err := decodeOptimisticUpdate([]byte(event.Data()))
|
||||||
|
if err == nil {
|
||||||
|
listener.OnOptimistic(optimisticUpdate)
|
||||||
|
} else {
|
||||||
|
listener.OnError(fmt.Errorf("error decoding optimistic update event: %v", err))
|
||||||
|
}
|
||||||
|
case "light_client_finality_update":
|
||||||
|
finalityUpdate, err := decodeFinalityUpdate([]byte(event.Data()))
|
||||||
|
if err == nil {
|
||||||
|
listener.OnFinality(finalityUpdate)
|
||||||
|
} else {
|
||||||
|
listener.OnError(fmt.Errorf("error decoding finality update event: %v", err))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
listener.OnError(fmt.Errorf("unexpected event: %s", event.Event()))
|
||||||
|
}
|
||||||
|
|
||||||
|
case err, ok := <-stream.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listener.OnError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
closeCtx()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startEventStream establishes an event stream. This will keep retrying until the stream has been
|
||||||
|
// established. It can only return nil when the context is canceled.
|
||||||
|
func (api *BeaconLightApi) startEventStream(ctx context.Context, listener *HeadEventListener) *eventsource.Stream {
|
||||||
|
for retry := true; retry; retry = ctxSleep(ctx, 5*time.Second) {
|
||||||
|
path := "/eth/v1/events?topics=head&topics=light_client_finality_update&topics=light_client_optimistic_update"
|
||||||
|
log.Trace("Sending event subscription request")
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", api.url+path, nil)
|
||||||
|
if err != nil {
|
||||||
|
listener.OnError(fmt.Errorf("error creating event subscription request: %v", err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for k, v := range api.customHeaders {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
stream, err := eventsource.SubscribeWithRequest("", req)
|
||||||
|
if err != nil {
|
||||||
|
listener.OnError(fmt.Errorf("error creating event subscription: %v", err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Trace("Successfully created event stream")
|
||||||
|
return stream
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ctxSleep(ctx context.Context, timeout time.Duration) (ok bool) {
|
||||||
|
timer := time.NewTimer(timeout)
|
||||||
|
defer timer.Stop()
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
return true
|
||||||
|
case <-ctx.Done():
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
125
beacon/light/canonical.go
Normal file
125
beacon/light/canonical.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package light
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// canonicalStore stores instances of the given type in a database and caches
|
||||||
|
// them in memory, associated with a continuous range of period numbers.
|
||||||
|
// Note: canonicalStore is not thread safe and it is the caller's responsibility
|
||||||
|
// to avoid concurrent access.
|
||||||
|
type canonicalStore[T any] struct {
|
||||||
|
keyPrefix []byte
|
||||||
|
periods periodRange
|
||||||
|
cache *lru.Cache[uint64, T]
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCanonicalStore creates a new canonicalStore and loads all keys associated
|
||||||
|
// with the keyPrefix in order to determine the ranges available in the database.
|
||||||
|
func newCanonicalStore[T any](db ethdb.Iteratee, keyPrefix []byte) (*canonicalStore[T], error) {
|
||||||
|
cs := &canonicalStore[T]{
|
||||||
|
keyPrefix: keyPrefix,
|
||||||
|
cache: lru.NewCache[uint64, T](100),
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
iter = db.NewIterator(keyPrefix, nil)
|
||||||
|
kl = len(keyPrefix)
|
||||||
|
first = true
|
||||||
|
)
|
||||||
|
defer iter.Release()
|
||||||
|
|
||||||
|
for iter.Next() {
|
||||||
|
if len(iter.Key()) != kl+8 {
|
||||||
|
log.Warn("Invalid key length in the canonical chain database", "key", fmt.Sprintf("%#x", iter.Key()))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
period := binary.BigEndian.Uint64(iter.Key()[kl : kl+8])
|
||||||
|
if first {
|
||||||
|
cs.periods.Start = period
|
||||||
|
} else if cs.periods.End != period {
|
||||||
|
return nil, fmt.Errorf("gap in the canonical chain database between periods %d and %d", cs.periods.End, period-1)
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
cs.periods.End = period + 1
|
||||||
|
}
|
||||||
|
return cs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// databaseKey returns the database key belonging to the given period.
|
||||||
|
func (cs *canonicalStore[T]) databaseKey(period uint64) []byte {
|
||||||
|
return binary.BigEndian.AppendUint64(append([]byte{}, cs.keyPrefix...), period)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add adds the given item to the database. It also ensures that the range remains
|
||||||
|
// continuous. Can be used either with a batch or database backend.
|
||||||
|
func (cs *canonicalStore[T]) add(backend ethdb.KeyValueWriter, period uint64, value T) error {
|
||||||
|
if !cs.periods.canExpand(period) {
|
||||||
|
return fmt.Errorf("period expansion is not allowed, first: %d, next: %d, period: %d", cs.periods.Start, cs.periods.End, period)
|
||||||
|
}
|
||||||
|
enc, err := rlp.EncodeToBytes(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := backend.Put(cs.databaseKey(period), enc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cs.cache.Add(period, value)
|
||||||
|
cs.periods.expand(period)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteFrom removes items starting from the given period.
|
||||||
|
func (cs *canonicalStore[T]) deleteFrom(db ethdb.KeyValueWriter, fromPeriod uint64) (deleted periodRange) {
|
||||||
|
keepRange, deleteRange := cs.periods.split(fromPeriod)
|
||||||
|
deleteRange.each(func(period uint64) {
|
||||||
|
db.Delete(cs.databaseKey(period))
|
||||||
|
cs.cache.Remove(period)
|
||||||
|
})
|
||||||
|
cs.periods = keepRange
|
||||||
|
return deleteRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// get returns the item at the given period or the null value of the given type
|
||||||
|
// if no item is present.
|
||||||
|
func (cs *canonicalStore[T]) get(backend ethdb.KeyValueReader, period uint64) (T, bool) {
|
||||||
|
var null, value T
|
||||||
|
if !cs.periods.contains(period) {
|
||||||
|
return null, false
|
||||||
|
}
|
||||||
|
if value, ok := cs.cache.Get(period); ok {
|
||||||
|
return value, true
|
||||||
|
}
|
||||||
|
enc, err := backend.Get(cs.databaseKey(period))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Canonical store value not found", "period", period, "start", cs.periods.Start, "end", cs.periods.End)
|
||||||
|
return null, false
|
||||||
|
}
|
||||||
|
if err := rlp.DecodeBytes(enc, &value); err != nil {
|
||||||
|
log.Error("Error decoding canonical store value", "error", err)
|
||||||
|
return null, false
|
||||||
|
}
|
||||||
|
cs.cache.Add(period, value)
|
||||||
|
return value, true
|
||||||
|
}
|
||||||
529
beacon/light/committee_chain.go
Normal file
529
beacon/light/committee_chain.go
Normal file
@@ -0,0 +1,529 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package light
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNeedCommittee = errors.New("sync committee required")
|
||||||
|
ErrInvalidUpdate = errors.New("invalid committee update")
|
||||||
|
ErrInvalidPeriod = errors.New("invalid update period")
|
||||||
|
ErrWrongCommitteeRoot = errors.New("wrong committee root")
|
||||||
|
ErrCannotReorg = errors.New("can not reorg committee chain")
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommitteeChain is a passive data structure that can validate, hold and update
|
||||||
|
// a chain of beacon light sync committees and updates. It requires at least one
|
||||||
|
// externally set fixed committee root at the beginning of the chain which can
|
||||||
|
// be set either based on a BootstrapData or a trusted source (a local beacon
|
||||||
|
// full node). This makes the structure useful for both light client and light
|
||||||
|
// server setups.
|
||||||
|
//
|
||||||
|
// It always maintains the following consistency constraints:
|
||||||
|
// - a committee can only be present if its root hash matches an existing fixed
|
||||||
|
// root or if it is proven by an update at the previous period
|
||||||
|
// - an update can only be present if a committee is present at the same period
|
||||||
|
// and the update signature is valid and has enough participants.
|
||||||
|
// The committee at the next period (proven by the update) should also be
|
||||||
|
// present (note that this means they can only be added together if neither
|
||||||
|
// is present yet). If a fixed root is present at the next period then the
|
||||||
|
// update can only be present if it proves the same committee root.
|
||||||
|
//
|
||||||
|
// Once synced to the current sync period, CommitteeChain can also validate
|
||||||
|
// signed beacon headers.
|
||||||
|
type CommitteeChain struct {
|
||||||
|
// chainmu guards against concurrent access to the canonicalStore structures
|
||||||
|
// (updates, committees, fixedCommitteeRoots) and ensures that they stay consistent
|
||||||
|
// with each other and with committeeCache.
|
||||||
|
chainmu sync.RWMutex
|
||||||
|
db ethdb.KeyValueStore
|
||||||
|
updates *canonicalStore[*types.LightClientUpdate]
|
||||||
|
committees *canonicalStore[*types.SerializedSyncCommittee]
|
||||||
|
fixedCommitteeRoots *canonicalStore[common.Hash]
|
||||||
|
committeeCache *lru.Cache[uint64, syncCommittee] // cache deserialized committees
|
||||||
|
changeCounter uint64
|
||||||
|
|
||||||
|
clock mclock.Clock // monotonic clock (simulated clock in tests)
|
||||||
|
unixNano func() int64 // system clock (simulated clock in tests)
|
||||||
|
sigVerifier committeeSigVerifier // BLS sig verifier (dummy verifier in tests)
|
||||||
|
|
||||||
|
config *types.ChainConfig
|
||||||
|
signerThreshold int
|
||||||
|
minimumUpdateScore types.UpdateScore
|
||||||
|
enforceTime bool // enforceTime specifies whether the age of a signed header should be checked
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommitteeChain creates a new CommitteeChain.
|
||||||
|
func NewCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool) *CommitteeChain {
|
||||||
|
return newCommitteeChain(db, config, signerThreshold, enforceTime, blsVerifier{}, &mclock.System{}, func() int64 { return time.Now().UnixNano() })
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTestCommitteeChain creates a new CommitteeChain for testing.
|
||||||
|
func NewTestCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool, clock *mclock.Simulated) *CommitteeChain {
|
||||||
|
return newCommitteeChain(db, config, signerThreshold, enforceTime, dummyVerifier{}, clock, func() int64 { return int64(clock.Now()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// newCommitteeChain creates a new CommitteeChain with the option of replacing the
|
||||||
|
// clock source and signature verification for testing purposes.
|
||||||
|
func newCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool, sigVerifier committeeSigVerifier, clock mclock.Clock, unixNano func() int64) *CommitteeChain {
|
||||||
|
s := &CommitteeChain{
|
||||||
|
committeeCache: lru.NewCache[uint64, syncCommittee](10),
|
||||||
|
db: db,
|
||||||
|
sigVerifier: sigVerifier,
|
||||||
|
clock: clock,
|
||||||
|
unixNano: unixNano,
|
||||||
|
config: config,
|
||||||
|
signerThreshold: signerThreshold,
|
||||||
|
enforceTime: enforceTime,
|
||||||
|
minimumUpdateScore: types.UpdateScore{
|
||||||
|
SignerCount: uint32(signerThreshold),
|
||||||
|
SubPeriodIndex: params.SyncPeriodLength / 16,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var err1, err2, err3 error
|
||||||
|
if s.fixedCommitteeRoots, err1 = newCanonicalStore[common.Hash](db, rawdb.FixedCommitteeRootKey); err1 != nil {
|
||||||
|
log.Error("Error creating fixed committee root store", "error", err1)
|
||||||
|
}
|
||||||
|
if s.committees, err2 = newCanonicalStore[*types.SerializedSyncCommittee](db, rawdb.SyncCommitteeKey); err2 != nil {
|
||||||
|
log.Error("Error creating committee store", "error", err2)
|
||||||
|
}
|
||||||
|
if s.updates, err3 = newCanonicalStore[*types.LightClientUpdate](db, rawdb.BestUpdateKey); err3 != nil {
|
||||||
|
log.Error("Error creating update store", "error", err3)
|
||||||
|
}
|
||||||
|
if err1 != nil || err2 != nil || err3 != nil || !s.checkConstraints() {
|
||||||
|
log.Info("Resetting invalid committee chain")
|
||||||
|
s.Reset()
|
||||||
|
}
|
||||||
|
// roll back invalid updates (might be necessary if forks have been changed since last time)
|
||||||
|
for !s.updates.periods.isEmpty() {
|
||||||
|
update, ok := s.updates.get(s.db, s.updates.periods.End-1)
|
||||||
|
if !ok {
|
||||||
|
log.Error("Sync committee update missing", "period", s.updates.periods.End-1)
|
||||||
|
s.Reset()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if valid, err := s.verifyUpdate(update); err != nil {
|
||||||
|
log.Error("Error validating update", "period", s.updates.periods.End-1, "error", err)
|
||||||
|
} else if valid {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err := s.rollback(s.updates.periods.End); err != nil {
|
||||||
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !s.committees.periods.isEmpty() {
|
||||||
|
log.Trace("Sync committee chain loaded", "first period", s.committees.periods.Start, "last period", s.committees.periods.End-1)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkConstraints checks committee chain validity constraints
|
||||||
|
func (s *CommitteeChain) checkConstraints() bool {
|
||||||
|
isNotInFixedCommitteeRootRange := func(r periodRange) bool {
|
||||||
|
return s.fixedCommitteeRoots.periods.isEmpty() ||
|
||||||
|
r.Start < s.fixedCommitteeRoots.periods.Start ||
|
||||||
|
r.Start >= s.fixedCommitteeRoots.periods.End
|
||||||
|
}
|
||||||
|
|
||||||
|
valid := true
|
||||||
|
if !s.updates.periods.isEmpty() {
|
||||||
|
if isNotInFixedCommitteeRootRange(s.updates.periods) {
|
||||||
|
log.Error("Start update is not in the fixed roots range")
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
if s.committees.periods.Start > s.updates.periods.Start || s.committees.periods.End <= s.updates.periods.End {
|
||||||
|
log.Error("Missing committees in update range")
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !s.committees.periods.isEmpty() {
|
||||||
|
if isNotInFixedCommitteeRootRange(s.committees.periods) {
|
||||||
|
log.Error("Start committee is not in the fixed roots range")
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
if s.committees.periods.End > s.fixedCommitteeRoots.periods.End && s.committees.periods.End > s.updates.periods.End+1 {
|
||||||
|
log.Error("Last committee is neither in the fixed roots range nor proven by updates")
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return valid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the committee chain.
|
||||||
|
func (s *CommitteeChain) Reset() {
|
||||||
|
s.chainmu.Lock()
|
||||||
|
defer s.chainmu.Unlock()
|
||||||
|
|
||||||
|
if err := s.rollback(0); err != nil {
|
||||||
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
|
}
|
||||||
|
s.changeCounter++
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckpointInit initializes a CommitteeChain based on a checkpoint.
|
||||||
|
// Note: if the chain is already initialized and the committees proven by the
|
||||||
|
// checkpoint do match the existing chain then the chain is retained and the
|
||||||
|
// new checkpoint becomes fixed.
|
||||||
|
func (s *CommitteeChain) CheckpointInit(bootstrap types.BootstrapData) error {
|
||||||
|
s.chainmu.Lock()
|
||||||
|
defer s.chainmu.Unlock()
|
||||||
|
|
||||||
|
if err := bootstrap.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
period := bootstrap.Header.SyncPeriod()
|
||||||
|
if err := s.deleteFixedCommitteeRootsFrom(period + 2); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot) != nil {
|
||||||
|
s.Reset()
|
||||||
|
if err := s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.addFixedCommitteeRoot(period+1, common.Hash(bootstrap.CommitteeBranch[0])); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.addCommittee(period, bootstrap.Committee); err != nil {
|
||||||
|
s.Reset()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.changeCounter++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addFixedCommitteeRoot sets a fixed committee root at the given period.
|
||||||
|
// Note that the period where the first committee is added has to have a fixed
|
||||||
|
// root which can either come from a BootstrapData or a trusted source.
|
||||||
|
func (s *CommitteeChain) addFixedCommitteeRoot(period uint64, root common.Hash) error {
|
||||||
|
if root == (common.Hash{}) {
|
||||||
|
return ErrWrongCommitteeRoot
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := s.db.NewBatch()
|
||||||
|
oldRoot := s.getCommitteeRoot(period)
|
||||||
|
if !s.fixedCommitteeRoots.periods.canExpand(period) {
|
||||||
|
// Note: the fixed committee root range should always be continuous and
|
||||||
|
// therefore the expected syncing method is to forward sync and optionally
|
||||||
|
// backward sync periods one by one, starting from a checkpoint. The only
|
||||||
|
// case when a root that is not adjacent to the already fixed ones can be
|
||||||
|
// fixed is when the same root has already been proven by an update chain.
|
||||||
|
// In this case the all roots in between can and should be fixed.
|
||||||
|
// This scenario makes sense when a new trusted checkpoint is added to an
|
||||||
|
// existing chain, ensuring that it will not be rolled back (might be
|
||||||
|
// important in case of low signer participation rate).
|
||||||
|
if root != oldRoot {
|
||||||
|
return ErrInvalidPeriod
|
||||||
|
}
|
||||||
|
// if the old root exists and matches the new one then it is guaranteed
|
||||||
|
// that the given period is after the existing fixed range and the roots
|
||||||
|
// in between can also be fixed.
|
||||||
|
for p := s.fixedCommitteeRoots.periods.End; p < period; p++ {
|
||||||
|
if err := s.fixedCommitteeRoots.add(batch, p, s.getCommitteeRoot(p)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if oldRoot != (common.Hash{}) && (oldRoot != root) {
|
||||||
|
// existing old root was different, we have to reorg the chain
|
||||||
|
if err := s.rollback(period); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.fixedCommitteeRoots.add(batch, period, root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteFixedCommitteeRootsFrom deletes fixed roots starting from the given period.
|
||||||
|
// It also maintains chain consistency, meaning that it also deletes updates and
|
||||||
|
// committees if they are no longer supported by a valid update chain.
|
||||||
|
func (s *CommitteeChain) deleteFixedCommitteeRootsFrom(period uint64) error {
|
||||||
|
if period >= s.fixedCommitteeRoots.periods.End {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
batch := s.db.NewBatch()
|
||||||
|
s.fixedCommitteeRoots.deleteFrom(batch, period)
|
||||||
|
if s.updates.periods.isEmpty() || period <= s.updates.periods.Start {
|
||||||
|
// Note: the first period of the update chain should always be fixed so if
|
||||||
|
// the fixed root at the first update is removed then the entire update chain
|
||||||
|
// and the proven committees have to be removed. Earlier committees in the
|
||||||
|
// remaining fixed root range can stay.
|
||||||
|
s.updates.deleteFrom(batch, period)
|
||||||
|
s.deleteCommitteesFrom(batch, period)
|
||||||
|
} else {
|
||||||
|
// The update chain stays intact, some previously fixed committee roots might
|
||||||
|
// get unfixed but are still proven by the update chain. If there were
|
||||||
|
// committees present after the range proven by updates, those should be
|
||||||
|
// removed if the belonging fixed roots are also removed.
|
||||||
|
fromPeriod := s.updates.periods.End + 1 // not proven by updates
|
||||||
|
if period > fromPeriod {
|
||||||
|
fromPeriod = period // also not justified by fixed roots
|
||||||
|
}
|
||||||
|
s.deleteCommitteesFrom(batch, fromPeriod)
|
||||||
|
}
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteCommitteesFrom deletes committees starting from the given period.
|
||||||
|
func (s *CommitteeChain) deleteCommitteesFrom(batch ethdb.Batch, period uint64) {
|
||||||
|
deleted := s.committees.deleteFrom(batch, period)
|
||||||
|
for period := deleted.Start; period < deleted.End; period++ {
|
||||||
|
s.committeeCache.Remove(period)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addCommittee adds a committee at the given period if possible.
|
||||||
|
func (s *CommitteeChain) addCommittee(period uint64, committee *types.SerializedSyncCommittee) error {
|
||||||
|
if !s.committees.periods.canExpand(period) {
|
||||||
|
return ErrInvalidPeriod
|
||||||
|
}
|
||||||
|
root := s.getCommitteeRoot(period)
|
||||||
|
if root == (common.Hash{}) {
|
||||||
|
return ErrInvalidPeriod
|
||||||
|
}
|
||||||
|
if root != committee.Root() {
|
||||||
|
return ErrWrongCommitteeRoot
|
||||||
|
}
|
||||||
|
if !s.committees.periods.contains(period) {
|
||||||
|
if err := s.committees.add(s.db, period, committee); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.committeeCache.Remove(period)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertUpdate adds a new update if possible.
|
||||||
|
func (s *CommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error {
|
||||||
|
s.chainmu.Lock()
|
||||||
|
defer s.chainmu.Unlock()
|
||||||
|
|
||||||
|
period := update.AttestedHeader.Header.SyncPeriod()
|
||||||
|
if !s.updates.periods.canExpand(period) || !s.committees.periods.contains(period) {
|
||||||
|
return ErrInvalidPeriod
|
||||||
|
}
|
||||||
|
if s.minimumUpdateScore.BetterThan(update.Score()) {
|
||||||
|
return ErrInvalidUpdate
|
||||||
|
}
|
||||||
|
oldRoot := s.getCommitteeRoot(period + 1)
|
||||||
|
reorg := oldRoot != (common.Hash{}) && oldRoot != update.NextSyncCommitteeRoot
|
||||||
|
if oldUpdate, ok := s.updates.get(s.db, period); ok && !update.Score().BetterThan(oldUpdate.Score()) {
|
||||||
|
// a better or equal update already exists; no changes, only fail if new one tried to reorg
|
||||||
|
if reorg {
|
||||||
|
return ErrCannotReorg
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if s.fixedCommitteeRoots.periods.contains(period+1) && reorg {
|
||||||
|
return ErrCannotReorg
|
||||||
|
}
|
||||||
|
if ok, err := s.verifyUpdate(update); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !ok {
|
||||||
|
return ErrInvalidUpdate
|
||||||
|
}
|
||||||
|
addCommittee := !s.committees.periods.contains(period+1) || reorg
|
||||||
|
if addCommittee {
|
||||||
|
if nextCommittee == nil {
|
||||||
|
return ErrNeedCommittee
|
||||||
|
}
|
||||||
|
if nextCommittee.Root() != update.NextSyncCommitteeRoot {
|
||||||
|
return ErrWrongCommitteeRoot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.changeCounter++
|
||||||
|
if reorg {
|
||||||
|
if err := s.rollback(period + 1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
batch := s.db.NewBatch()
|
||||||
|
if addCommittee {
|
||||||
|
if err := s.committees.add(batch, period+1, nextCommittee); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.committeeCache.Remove(period + 1)
|
||||||
|
}
|
||||||
|
if err := s.updates.add(batch, period, update); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Inserted new committee update", "period", period, "next committee root", update.NextSyncCommitteeRoot)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextSyncPeriod returns the next period where an update can be added and also
|
||||||
|
// whether the chain is initialized at all.
|
||||||
|
func (s *CommitteeChain) NextSyncPeriod() (uint64, bool) {
|
||||||
|
s.chainmu.RLock()
|
||||||
|
defer s.chainmu.RUnlock()
|
||||||
|
|
||||||
|
if s.committees.periods.isEmpty() {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
if !s.updates.periods.isEmpty() {
|
||||||
|
return s.updates.periods.End, true
|
||||||
|
}
|
||||||
|
return s.committees.periods.End - 1, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommitteeChain) ChangeCounter() uint64 {
|
||||||
|
s.chainmu.RLock()
|
||||||
|
defer s.chainmu.RUnlock()
|
||||||
|
|
||||||
|
return s.changeCounter
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollback removes all committees and fixed roots from the given period and updates
|
||||||
|
// starting from the previous period.
|
||||||
|
func (s *CommitteeChain) rollback(period uint64) error {
|
||||||
|
max := s.updates.periods.End + 1
|
||||||
|
if s.committees.periods.End > max {
|
||||||
|
max = s.committees.periods.End
|
||||||
|
}
|
||||||
|
if s.fixedCommitteeRoots.periods.End > max {
|
||||||
|
max = s.fixedCommitteeRoots.periods.End
|
||||||
|
}
|
||||||
|
for max > period {
|
||||||
|
max--
|
||||||
|
batch := s.db.NewBatch()
|
||||||
|
s.deleteCommitteesFrom(batch, max)
|
||||||
|
s.fixedCommitteeRoots.deleteFrom(batch, max)
|
||||||
|
if max > 0 {
|
||||||
|
s.updates.deleteFrom(batch, max-1)
|
||||||
|
}
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCommitteeRoot returns the committee root at the given period, either fixed,
|
||||||
|
// proven by a previous update or both. It returns an empty hash if the committee
|
||||||
|
// root is unknown.
|
||||||
|
func (s *CommitteeChain) getCommitteeRoot(period uint64) common.Hash {
|
||||||
|
if root, ok := s.fixedCommitteeRoots.get(s.db, period); ok || period == 0 {
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
if update, ok := s.updates.get(s.db, period-1); ok {
|
||||||
|
return update.NextSyncCommitteeRoot
|
||||||
|
}
|
||||||
|
return common.Hash{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSyncCommittee returns the deserialized sync committee at the given period.
|
||||||
|
func (s *CommitteeChain) getSyncCommittee(period uint64) (syncCommittee, error) {
|
||||||
|
if c, ok := s.committeeCache.Get(period); ok {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
if sc, ok := s.committees.get(s.db, period); ok {
|
||||||
|
c, err := s.sigVerifier.deserializeSyncCommittee(sc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("sync committee #%d deserialization error: %v", period, err)
|
||||||
|
}
|
||||||
|
s.committeeCache.Add(period, c)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("missing serialized sync committee #%d", period)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifySignedHeader returns true if the given signed header has a valid signature
|
||||||
|
// according to the local committee chain. The caller should ensure that the
|
||||||
|
// committees advertised by the same source where the signed header came from are
|
||||||
|
// synced before verifying the signature.
|
||||||
|
// The age of the header is also returned (the time elapsed since the beginning
|
||||||
|
// of the given slot, according to the local system clock). If enforceTime is
|
||||||
|
// true then negative age (future) headers are rejected.
|
||||||
|
func (s *CommitteeChain) VerifySignedHeader(head types.SignedHeader) (bool, time.Duration, error) {
|
||||||
|
s.chainmu.RLock()
|
||||||
|
defer s.chainmu.RUnlock()
|
||||||
|
|
||||||
|
return s.verifySignedHeader(head)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommitteeChain) verifySignedHeader(head types.SignedHeader) (bool, time.Duration, error) {
|
||||||
|
var age time.Duration
|
||||||
|
now := s.unixNano()
|
||||||
|
if head.Header.Slot < (uint64(now-math.MinInt64)/uint64(time.Second)-s.config.GenesisTime)/12 {
|
||||||
|
age = time.Duration(now - int64(time.Second)*int64(s.config.GenesisTime+head.Header.Slot*12))
|
||||||
|
} else {
|
||||||
|
age = time.Duration(math.MinInt64)
|
||||||
|
}
|
||||||
|
if s.enforceTime && age < 0 {
|
||||||
|
return false, age, nil
|
||||||
|
}
|
||||||
|
committee, err := s.getSyncCommittee(types.SyncPeriod(head.SignatureSlot))
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
if committee == nil {
|
||||||
|
return false, age, nil
|
||||||
|
}
|
||||||
|
if signingRoot, err := s.config.Forks.SigningRoot(head.Header); err == nil {
|
||||||
|
return s.sigVerifier.verifySignature(committee, signingRoot, &head.Signature), age, nil
|
||||||
|
}
|
||||||
|
return false, age, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyUpdate checks whether the header signature is correct and the update
|
||||||
|
// fits into the specified constraints (assumes that the update has been
|
||||||
|
// successfully validated previously)
|
||||||
|
func (s *CommitteeChain) verifyUpdate(update *types.LightClientUpdate) (bool, error) {
|
||||||
|
// Note: SignatureSlot determines the sync period of the committee used for signature
|
||||||
|
// verification. Though in reality SignatureSlot is always bigger than update.Header.Slot,
|
||||||
|
// setting them as equal here enforces the rule that they have to be in the same sync
|
||||||
|
// period in order for the light client update proof to be meaningful.
|
||||||
|
ok, age, err := s.verifySignedHeader(update.AttestedHeader)
|
||||||
|
if age < 0 {
|
||||||
|
log.Warn("Future committee update received", "age", age)
|
||||||
|
}
|
||||||
|
return ok, err
|
||||||
|
}
|
||||||
356
beacon/light/committee_chain_test.go
Normal file
356
beacon/light/committee_chain_test.go
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
// Copyright 2022 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package light
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testGenesis = newTestGenesis()
|
||||||
|
testGenesis2 = newTestGenesis()
|
||||||
|
|
||||||
|
tfBase = newTestForks(testGenesis, types.Forks{
|
||||||
|
&types.Fork{Epoch: 0, Version: []byte{0}},
|
||||||
|
})
|
||||||
|
tfAlternative = newTestForks(testGenesis, types.Forks{
|
||||||
|
&types.Fork{Epoch: 0, Version: []byte{0}},
|
||||||
|
&types.Fork{Epoch: 0x700, Version: []byte{1}},
|
||||||
|
})
|
||||||
|
tfAnotherGenesis = newTestForks(testGenesis2, types.Forks{
|
||||||
|
&types.Fork{Epoch: 0, Version: []byte{0}},
|
||||||
|
})
|
||||||
|
|
||||||
|
tcBase = newTestCommitteeChain(nil, tfBase, true, 0, 10, 400, false)
|
||||||
|
tcBaseWithInvalidUpdates = newTestCommitteeChain(tcBase, tfBase, false, 5, 10, 200, false) // signer count too low
|
||||||
|
tcBaseWithBetterUpdates = newTestCommitteeChain(tcBase, tfBase, false, 5, 10, 440, false)
|
||||||
|
tcReorgWithWorseUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 400, false)
|
||||||
|
tcReorgWithWorseUpdates2 = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 380, false)
|
||||||
|
tcReorgWithBetterUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 420, false)
|
||||||
|
tcReorgWithFinalizedUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 400, true)
|
||||||
|
tcFork = newTestCommitteeChain(tcBase, tfAlternative, true, 7, 10, 400, false)
|
||||||
|
tcAnotherGenesis = newTestCommitteeChain(nil, tfAnotherGenesis, true, 0, 10, 400, false)
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommitteeChainFixedCommitteeRoots(t *testing.T) {
|
||||||
|
for _, reload := range []bool{false, true} {
|
||||||
|
c := newCommitteeChainTest(t, tfBase, 300, true)
|
||||||
|
c.setClockPeriod(7)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 4, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 5, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 6, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 8, ErrInvalidPeriod) // range has to be continuous
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 3, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 2, nil)
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.addCommittee(tcBase, 4, nil)
|
||||||
|
c.addCommittee(tcBase, 6, ErrInvalidPeriod) // range has to be continuous
|
||||||
|
c.addCommittee(tcBase, 5, nil)
|
||||||
|
c.addCommittee(tcBase, 6, nil)
|
||||||
|
c.addCommittee(tcAnotherGenesis, 3, ErrWrongCommitteeRoot)
|
||||||
|
c.addCommittee(tcBase, 3, nil)
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 3, 6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommitteeChainCheckpointSync(t *testing.T) {
|
||||||
|
for _, enforceTime := range []bool{false, true} {
|
||||||
|
for _, reload := range []bool{false, true} {
|
||||||
|
c := newCommitteeChainTest(t, tfBase, 300, enforceTime)
|
||||||
|
if enforceTime {
|
||||||
|
c.setClockPeriod(6)
|
||||||
|
}
|
||||||
|
c.insertUpdate(tcBase, 3, true, ErrInvalidPeriod)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 3, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 4, nil)
|
||||||
|
c.insertUpdate(tcBase, 4, true, ErrInvalidPeriod) // still no committee
|
||||||
|
c.addCommittee(tcBase, 3, nil)
|
||||||
|
c.addCommittee(tcBase, 4, nil)
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 3, 4)
|
||||||
|
c.insertUpdate(tcBase, 3, false, nil) // update can be added without committee here
|
||||||
|
c.insertUpdate(tcBase, 4, false, ErrNeedCommittee) // but not here as committee 5 is not there yet
|
||||||
|
c.insertUpdate(tcBase, 4, true, nil)
|
||||||
|
c.verifyRange(tcBase, 3, 5)
|
||||||
|
c.insertUpdate(tcBaseWithInvalidUpdates, 5, true, ErrInvalidUpdate) // signer count too low
|
||||||
|
c.insertUpdate(tcBase, 5, true, nil)
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
if enforceTime {
|
||||||
|
c.insertUpdate(tcBase, 6, true, ErrInvalidUpdate) // future update rejected
|
||||||
|
c.setClockPeriod(7)
|
||||||
|
}
|
||||||
|
c.insertUpdate(tcBase, 6, true, nil) // when the time comes it's accepted
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
if enforceTime {
|
||||||
|
c.verifyRange(tcBase, 3, 6) // committee 7 is there but still in the future
|
||||||
|
c.setClockPeriod(8)
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 3, 7) // now period 7 can also be verified
|
||||||
|
// try reverse syncing an update
|
||||||
|
c.insertUpdate(tcBase, 2, false, ErrInvalidPeriod) // fixed committee is needed first
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 2, nil)
|
||||||
|
c.addCommittee(tcBase, 2, nil)
|
||||||
|
c.insertUpdate(tcBase, 2, false, nil)
|
||||||
|
c.verifyRange(tcBase, 2, 7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommitteeChainReorg(t *testing.T) {
|
||||||
|
for _, reload := range []bool{false, true} {
|
||||||
|
for _, addBetterUpdates := range []bool{false, true} {
|
||||||
|
c := newCommitteeChainTest(t, tfBase, 300, true)
|
||||||
|
c.setClockPeriod(11)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 3, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 4, nil)
|
||||||
|
c.addCommittee(tcBase, 3, nil)
|
||||||
|
for period := uint64(3); period < 10; period++ {
|
||||||
|
c.insertUpdate(tcBase, period, true, nil)
|
||||||
|
}
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 3, 10)
|
||||||
|
c.insertUpdate(tcReorgWithWorseUpdates, 5, true, ErrCannotReorg)
|
||||||
|
c.insertUpdate(tcReorgWithWorseUpdates2, 5, true, ErrCannotReorg)
|
||||||
|
if addBetterUpdates {
|
||||||
|
// add better updates for the base chain and expect first reorg to fail
|
||||||
|
// (only add updates as committees should be the same)
|
||||||
|
for period := uint64(5); period < 10; period++ {
|
||||||
|
c.insertUpdate(tcBaseWithBetterUpdates, period, false, nil)
|
||||||
|
}
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 3, 10) // still on the same chain
|
||||||
|
c.insertUpdate(tcReorgWithBetterUpdates, 5, true, ErrCannotReorg)
|
||||||
|
} else {
|
||||||
|
// reorg with better updates
|
||||||
|
c.insertUpdate(tcReorgWithBetterUpdates, 5, false, ErrNeedCommittee)
|
||||||
|
c.verifyRange(tcBase, 3, 10) // no success yet, still on the base chain
|
||||||
|
c.verifyRange(tcReorgWithBetterUpdates, 3, 5)
|
||||||
|
c.insertUpdate(tcReorgWithBetterUpdates, 5, true, nil)
|
||||||
|
// successful reorg, base chain should only match before the reorg period
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 3, 5)
|
||||||
|
c.verifyRange(tcReorgWithBetterUpdates, 3, 6)
|
||||||
|
for period := uint64(6); period < 10; period++ {
|
||||||
|
c.insertUpdate(tcReorgWithBetterUpdates, period, true, nil)
|
||||||
|
}
|
||||||
|
c.verifyRange(tcReorgWithBetterUpdates, 3, 10)
|
||||||
|
}
|
||||||
|
// reorg with finalized updates; should succeed even if base chain updates
|
||||||
|
// have been improved because a finalized update beats everything else
|
||||||
|
c.insertUpdate(tcReorgWithFinalizedUpdates, 5, false, ErrNeedCommittee)
|
||||||
|
c.insertUpdate(tcReorgWithFinalizedUpdates, 5, true, nil)
|
||||||
|
if reload {
|
||||||
|
c.reloadChain()
|
||||||
|
}
|
||||||
|
c.verifyRange(tcReorgWithFinalizedUpdates, 3, 6)
|
||||||
|
for period := uint64(6); period < 10; period++ {
|
||||||
|
c.insertUpdate(tcReorgWithFinalizedUpdates, period, true, nil)
|
||||||
|
}
|
||||||
|
c.verifyRange(tcReorgWithFinalizedUpdates, 3, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommitteeChainFork(t *testing.T) {
|
||||||
|
c := newCommitteeChainTest(t, tfAlternative, 300, true)
|
||||||
|
c.setClockPeriod(11)
|
||||||
|
// trying to sync a chain on an alternative fork with the base chain data
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 0, nil)
|
||||||
|
c.addFixedCommitteeRoot(tcBase, 1, nil)
|
||||||
|
c.addCommittee(tcBase, 0, nil)
|
||||||
|
// shared section should sync without errors
|
||||||
|
for period := uint64(0); period < 7; period++ {
|
||||||
|
c.insertUpdate(tcBase, period, true, nil)
|
||||||
|
}
|
||||||
|
c.insertUpdate(tcBase, 7, true, ErrInvalidUpdate) // wrong fork
|
||||||
|
// committee root #7 is still the same but signatures are already signed with
|
||||||
|
// a different fork id so period 7 should only verify on the alternative fork
|
||||||
|
c.verifyRange(tcBase, 0, 6)
|
||||||
|
c.verifyRange(tcFork, 0, 7)
|
||||||
|
for period := uint64(7); period < 10; period++ {
|
||||||
|
c.insertUpdate(tcFork, period, true, nil)
|
||||||
|
}
|
||||||
|
c.verifyRange(tcFork, 0, 10)
|
||||||
|
// reload the chain while switching to the base fork
|
||||||
|
c.config = tfBase
|
||||||
|
c.reloadChain()
|
||||||
|
// updates 7..9 should be rolled back now
|
||||||
|
c.verifyRange(tcFork, 0, 6) // again, period 7 only verifies on the right fork
|
||||||
|
c.verifyRange(tcBase, 0, 7)
|
||||||
|
c.insertUpdate(tcFork, 7, true, ErrInvalidUpdate) // wrong fork
|
||||||
|
for period := uint64(7); period < 10; period++ {
|
||||||
|
c.insertUpdate(tcBase, period, true, nil)
|
||||||
|
}
|
||||||
|
c.verifyRange(tcBase, 0, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
type committeeChainTest struct {
|
||||||
|
t *testing.T
|
||||||
|
db *memorydb.Database
|
||||||
|
clock *mclock.Simulated
|
||||||
|
config types.ChainConfig
|
||||||
|
signerThreshold int
|
||||||
|
enforceTime bool
|
||||||
|
chain *CommitteeChain
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommitteeChainTest(t *testing.T, config types.ChainConfig, signerThreshold int, enforceTime bool) *committeeChainTest {
|
||||||
|
c := &committeeChainTest{
|
||||||
|
t: t,
|
||||||
|
db: memorydb.New(),
|
||||||
|
clock: &mclock.Simulated{},
|
||||||
|
config: config,
|
||||||
|
signerThreshold: signerThreshold,
|
||||||
|
enforceTime: enforceTime,
|
||||||
|
}
|
||||||
|
c.chain = NewTestCommitteeChain(c.db, &config, signerThreshold, enforceTime, c.clock)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) reloadChain() {
|
||||||
|
c.chain = NewTestCommitteeChain(c.db, &c.config, c.signerThreshold, c.enforceTime, c.clock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) setClockPeriod(period float64) {
|
||||||
|
target := mclock.AbsTime(period * float64(time.Second*12*params.SyncPeriodLength))
|
||||||
|
wait := time.Duration(target - c.clock.Now())
|
||||||
|
if wait < 0 {
|
||||||
|
c.t.Fatalf("Invalid setClockPeriod")
|
||||||
|
}
|
||||||
|
c.clock.Run(wait)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) addFixedCommitteeRoot(tc *testCommitteeChain, period uint64, expErr error) {
|
||||||
|
if err := c.chain.addFixedCommitteeRoot(period, tc.periods[period].committee.Root()); err != expErr {
|
||||||
|
c.t.Errorf("Incorrect error output from addFixedCommitteeRoot at period %d (expected %v, got %v)", period, expErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) addCommittee(tc *testCommitteeChain, period uint64, expErr error) {
|
||||||
|
if err := c.chain.addCommittee(period, tc.periods[period].committee); err != expErr {
|
||||||
|
c.t.Errorf("Incorrect error output from addCommittee at period %d (expected %v, got %v)", period, expErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) insertUpdate(tc *testCommitteeChain, period uint64, addCommittee bool, expErr error) {
|
||||||
|
var committee *types.SerializedSyncCommittee
|
||||||
|
if addCommittee {
|
||||||
|
committee = tc.periods[period+1].committee
|
||||||
|
}
|
||||||
|
if err := c.chain.InsertUpdate(tc.periods[period].update, committee); err != expErr {
|
||||||
|
c.t.Errorf("Incorrect error output from InsertUpdate at period %d (expected %v, got %v)", period, expErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) verifySignedHeader(tc *testCommitteeChain, period float64, expOk bool) {
|
||||||
|
slot := uint64(period * float64(params.SyncPeriodLength))
|
||||||
|
signedHead := GenerateTestSignedHeader(types.Header{Slot: slot}, &tc.config, tc.periods[types.SyncPeriod(slot)].committee, slot+1, 400)
|
||||||
|
if ok, _, _ := c.chain.VerifySignedHeader(signedHead); ok != expOk {
|
||||||
|
c.t.Errorf("Incorrect output from VerifySignedHeader at period %f (expected %v, got %v)", period, expOk, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *committeeChainTest) verifyRange(tc *testCommitteeChain, begin, end uint64) {
|
||||||
|
if begin > 0 {
|
||||||
|
c.verifySignedHeader(tc, float64(begin)-0.5, false)
|
||||||
|
}
|
||||||
|
for period := begin; period <= end; period++ {
|
||||||
|
c.verifySignedHeader(tc, float64(period)+0.5, true)
|
||||||
|
}
|
||||||
|
c.verifySignedHeader(tc, float64(end)+1.5, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestGenesis() types.ChainConfig {
|
||||||
|
var config types.ChainConfig
|
||||||
|
rand.Read(config.GenesisValidatorsRoot[:])
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestForks(config types.ChainConfig, forks types.Forks) types.ChainConfig {
|
||||||
|
for _, fork := range forks {
|
||||||
|
config.AddFork(fork.Name, fork.Epoch, fork.Version)
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestCommitteeChain(parent *testCommitteeChain, config types.ChainConfig, newCommittees bool, begin, end int, signerCount int, finalizedHeader bool) *testCommitteeChain {
|
||||||
|
tc := &testCommitteeChain{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
if parent != nil {
|
||||||
|
tc.periods = make([]testPeriod, len(parent.periods))
|
||||||
|
copy(tc.periods, parent.periods)
|
||||||
|
}
|
||||||
|
if newCommittees {
|
||||||
|
if begin == 0 {
|
||||||
|
tc.fillCommittees(begin, end+1)
|
||||||
|
} else {
|
||||||
|
tc.fillCommittees(begin+1, end+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tc.fillUpdates(begin, end, signerCount, finalizedHeader)
|
||||||
|
return tc
|
||||||
|
}
|
||||||
|
|
||||||
|
type testPeriod struct {
|
||||||
|
committee *types.SerializedSyncCommittee
|
||||||
|
update *types.LightClientUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCommitteeChain struct {
|
||||||
|
periods []testPeriod
|
||||||
|
config types.ChainConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *testCommitteeChain) fillCommittees(begin, end int) {
|
||||||
|
if len(tc.periods) <= end {
|
||||||
|
tc.periods = append(tc.periods, make([]testPeriod, end+1-len(tc.periods))...)
|
||||||
|
}
|
||||||
|
for i := begin; i <= end; i++ {
|
||||||
|
tc.periods[i].committee = GenerateTestCommittee()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *testCommitteeChain) fillUpdates(begin, end int, signerCount int, finalizedHeader bool) {
|
||||||
|
for i := begin; i <= end; i++ {
|
||||||
|
tc.periods[i].update = GenerateTestUpdate(&tc.config, uint64(i), tc.periods[i].committee, tc.periods[i+1].committee, signerCount, finalizedHeader)
|
||||||
|
}
|
||||||
|
}
|
||||||
161
beacon/light/head_tracker.go
Normal file
161
beacon/light/head_tracker.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package light
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeadTracker keeps track of the latest validated head and the "prefetch" head
|
||||||
|
// which is the (not necessarily validated) head announced by the majority of
|
||||||
|
// servers.
|
||||||
|
type HeadTracker struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
committeeChain *CommitteeChain
|
||||||
|
minSignerCount int
|
||||||
|
optimisticUpdate types.OptimisticUpdate
|
||||||
|
hasOptimisticUpdate bool
|
||||||
|
finalityUpdate types.FinalityUpdate
|
||||||
|
hasFinalityUpdate bool
|
||||||
|
prefetchHead types.HeadInfo
|
||||||
|
changeCounter uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHeadTracker creates a new HeadTracker.
|
||||||
|
func NewHeadTracker(committeeChain *CommitteeChain, minSignerCount int) *HeadTracker {
|
||||||
|
return &HeadTracker{
|
||||||
|
committeeChain: committeeChain,
|
||||||
|
minSignerCount: minSignerCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidatedOptimistic returns the latest validated optimistic update.
|
||||||
|
func (h *HeadTracker) ValidatedOptimistic() (types.OptimisticUpdate, bool) {
|
||||||
|
h.lock.RLock()
|
||||||
|
defer h.lock.RUnlock()
|
||||||
|
|
||||||
|
return h.optimisticUpdate, h.hasOptimisticUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidatedFinality returns the latest validated finality update.
|
||||||
|
func (h *HeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
|
||||||
|
h.lock.RLock()
|
||||||
|
defer h.lock.RUnlock()
|
||||||
|
|
||||||
|
return h.finalityUpdate, h.hasFinalityUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateOptimistic validates the given optimistic update. If the update is
|
||||||
|
// successfully validated and it is better than the old validated update (higher
|
||||||
|
// slot or same slot and more signers) then ValidatedOptimistic is updated.
|
||||||
|
// The boolean return flag signals if ValidatedOptimistic has been changed.
|
||||||
|
func (h *HeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, error) {
|
||||||
|
h.lock.Lock()
|
||||||
|
defer h.lock.Unlock()
|
||||||
|
|
||||||
|
if err := update.Validate(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
replace, err := h.validate(update.SignedHeader(), h.optimisticUpdate.SignedHeader())
|
||||||
|
if replace {
|
||||||
|
h.optimisticUpdate, h.hasOptimisticUpdate = update, true
|
||||||
|
h.changeCounter++
|
||||||
|
}
|
||||||
|
return replace, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateFinality validates the given finality update. If the update is
|
||||||
|
// successfully validated and it is better than the old validated update (higher
|
||||||
|
// slot or same slot and more signers) then ValidatedFinality is updated.
|
||||||
|
// The boolean return flag signals if ValidatedFinality has been changed.
|
||||||
|
func (h *HeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) {
|
||||||
|
h.lock.Lock()
|
||||||
|
defer h.lock.Unlock()
|
||||||
|
|
||||||
|
if err := update.Validate(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
replace, err := h.validate(update.SignedHeader(), h.finalityUpdate.SignedHeader())
|
||||||
|
if replace {
|
||||||
|
h.finalityUpdate, h.hasFinalityUpdate = update, true
|
||||||
|
h.changeCounter++
|
||||||
|
}
|
||||||
|
return replace, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HeadTracker) validate(head, oldHead types.SignedHeader) (bool, error) {
|
||||||
|
signerCount := head.Signature.SignerCount()
|
||||||
|
if signerCount < h.minSignerCount {
|
||||||
|
return false, errors.New("low signer count")
|
||||||
|
}
|
||||||
|
if head.Header.Slot < oldHead.Header.Slot || (head.Header.Slot == oldHead.Header.Slot && signerCount <= oldHead.Signature.SignerCount()) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
sigOk, age, err := h.committeeChain.VerifySignedHeader(head)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if age < 0 {
|
||||||
|
log.Warn("Future signed head received", "age", age)
|
||||||
|
}
|
||||||
|
if age > time.Minute*2 {
|
||||||
|
log.Warn("Old signed head received", "age", age)
|
||||||
|
}
|
||||||
|
if !sigOk {
|
||||||
|
return false, errors.New("invalid header signature")
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrefetchHead returns the latest known prefetch head's head info.
|
||||||
|
// This head can be used to start fetching related data hoping that it will be
|
||||||
|
// validated soon.
|
||||||
|
// Note that the prefetch head cannot be validated cryptographically so it should
|
||||||
|
// only be used as a performance optimization hint.
|
||||||
|
func (h *HeadTracker) PrefetchHead() types.HeadInfo {
|
||||||
|
h.lock.RLock()
|
||||||
|
defer h.lock.RUnlock()
|
||||||
|
|
||||||
|
return h.prefetchHead
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrefetchHead sets the prefetch head info.
|
||||||
|
// Note that HeadTracker does not verify the prefetch head, just acts as a thread
|
||||||
|
// safe bulletin board.
|
||||||
|
func (h *HeadTracker) SetPrefetchHead(head types.HeadInfo) {
|
||||||
|
h.lock.Lock()
|
||||||
|
defer h.lock.Unlock()
|
||||||
|
|
||||||
|
if head == h.prefetchHead {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.prefetchHead = head
|
||||||
|
h.changeCounter++
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeCounter implements request.targetData
|
||||||
|
func (h *HeadTracker) ChangeCounter() uint64 {
|
||||||
|
h.lock.RLock()
|
||||||
|
defer h.lock.RUnlock()
|
||||||
|
|
||||||
|
return h.changeCounter
|
||||||
|
}
|
||||||
78
beacon/light/range.go
Normal file
78
beacon/light/range.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package light
|
||||||
|
|
||||||
|
// periodRange represents a (possibly zero-length) range of integers (sync periods).
|
||||||
|
type periodRange struct {
|
||||||
|
Start, End uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEmpty returns true if the length of the range is zero.
|
||||||
|
func (a periodRange) isEmpty() bool {
|
||||||
|
return a.End == a.Start
|
||||||
|
}
|
||||||
|
|
||||||
|
// contains returns true if the range includes the given period.
|
||||||
|
func (a periodRange) contains(period uint64) bool {
|
||||||
|
return period >= a.Start && period < a.End
|
||||||
|
}
|
||||||
|
|
||||||
|
// canExpand returns true if the range includes or can be expanded with the given
|
||||||
|
// period (either the range is empty or the given period is inside, right before or
|
||||||
|
// right after the range).
|
||||||
|
func (a periodRange) canExpand(period uint64) bool {
|
||||||
|
return a.isEmpty() || (period+1 >= a.Start && period <= a.End)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expand expands the range with the given period.
|
||||||
|
// This method assumes that canExpand returned true: otherwise this is a no-op.
|
||||||
|
func (a *periodRange) expand(period uint64) {
|
||||||
|
if a.isEmpty() {
|
||||||
|
a.Start, a.End = period, period+1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if a.Start == period+1 {
|
||||||
|
a.Start--
|
||||||
|
}
|
||||||
|
if a.End == period {
|
||||||
|
a.End++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// split splits the range into two ranges. The 'fromPeriod' will be the first
|
||||||
|
// element in the second range (if present).
|
||||||
|
// The original range is unchanged by this operation
|
||||||
|
func (a *periodRange) split(fromPeriod uint64) (periodRange, periodRange) {
|
||||||
|
if fromPeriod <= a.Start {
|
||||||
|
// First range empty, everything in second range,
|
||||||
|
return periodRange{}, *a
|
||||||
|
}
|
||||||
|
if fromPeriod >= a.End {
|
||||||
|
// Second range empty, everything in first range,
|
||||||
|
return *a, periodRange{}
|
||||||
|
}
|
||||||
|
x := periodRange{a.Start, fromPeriod}
|
||||||
|
y := periodRange{fromPeriod, a.End}
|
||||||
|
return x, y
|
||||||
|
}
|
||||||
|
|
||||||
|
// each invokes the supplied function fn once per period in range
|
||||||
|
func (a *periodRange) each(fn func(uint64)) {
|
||||||
|
for p := a.Start; p < a.End; p++ {
|
||||||
|
fn(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
403
beacon/light/request/scheduler.go
Normal file
403
beacon/light/request/scheduler.go
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Module represents a mechanism which is typically responsible for downloading
|
||||||
|
// and updating a passive data structure. It does not directly interact with the
|
||||||
|
// servers. It can start requests using the Requester interface, maintain its
|
||||||
|
// internal state by receiving and processing Events and update its target data
|
||||||
|
// structure based on the obtained data.
|
||||||
|
// It is the Scheduler's responsibility to feed events to the modules, call
|
||||||
|
// Process as long as there might be something to process and then generate request
|
||||||
|
// candidates using MakeRequest and start the best possible requests.
|
||||||
|
// Modules are called by Scheduler whenever a global trigger is fired. All events
|
||||||
|
// fire the trigger. Changing a target data structure also triggers a next
|
||||||
|
// processing round as it could make further actions possible either by the same
|
||||||
|
// or another Module.
|
||||||
|
type Module interface {
|
||||||
|
// Process is a non-blocking function responsible for starting requests,
|
||||||
|
// processing events and updating the target data structures(s) and the
|
||||||
|
// internal state of the module. Module state typically consists of information
|
||||||
|
// about pending requests and registered servers.
|
||||||
|
// Process is always called after an event is received or after a target data
|
||||||
|
// structure has been changed.
|
||||||
|
//
|
||||||
|
// Note: Process functions of different modules are never called concurrently;
|
||||||
|
// they are called by Scheduler in the same order of priority as they were
|
||||||
|
// registered in.
|
||||||
|
Process(Requester, []Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Requester allows Modules to obtain the list of momentarily available servers,
|
||||||
|
// start new requests and report server failure when a response has been proven
|
||||||
|
// to be invalid in the processing phase.
|
||||||
|
// Note that all Requester functions should be safe to call from Module.Process.
|
||||||
|
type Requester interface {
|
||||||
|
CanSendTo() []Server
|
||||||
|
Send(Server, Request) ID
|
||||||
|
Fail(Server, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scheduler is a modular network data retrieval framework that coordinates multiple
|
||||||
|
// servers and retrieval mechanisms (modules). It implements a trigger mechanism
|
||||||
|
// that calls the Process function of registered modules whenever either the state
|
||||||
|
// of existing data structures or events coming from registered servers could
|
||||||
|
// allow new operations.
|
||||||
|
type Scheduler struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
modules []Module // first has the highest priority
|
||||||
|
names map[Module]string
|
||||||
|
servers map[server]struct{}
|
||||||
|
targets map[targetData]uint64
|
||||||
|
|
||||||
|
requesterLock sync.RWMutex
|
||||||
|
serverOrder []server
|
||||||
|
pending map[ServerAndID]pendingRequest
|
||||||
|
|
||||||
|
// eventLock guards access to the events list. Note that eventLock can be
|
||||||
|
// locked either while lock is locked or unlocked but lock cannot be locked
|
||||||
|
// while eventLock is locked.
|
||||||
|
eventLock sync.Mutex
|
||||||
|
events []Event
|
||||||
|
stopCh chan chan struct{}
|
||||||
|
|
||||||
|
triggerCh chan struct{} // restarts waiting sync loop
|
||||||
|
// if trigger has already been fired then send to testWaitCh blocks until
|
||||||
|
// the triggered processing round is finished
|
||||||
|
testWaitCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Server identifies a server without allowing any direct interaction.
|
||||||
|
// Note: server interface is used by Scheduler and Tracker but not used by
|
||||||
|
// the modules that do not interact with them directly.
|
||||||
|
// In order to make module testing easier, Server interface is used in
|
||||||
|
// events and modules.
|
||||||
|
Server interface {
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
Request any
|
||||||
|
Response any
|
||||||
|
ID uint64
|
||||||
|
ServerAndID struct {
|
||||||
|
Server Server
|
||||||
|
ID ID
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// targetData represents a registered target data structure that increases its
|
||||||
|
// ChangeCounter whenever it has been changed.
|
||||||
|
type targetData interface {
|
||||||
|
ChangeCounter() uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// pendingRequest keeps track of sent and not yet finalized requests and their
|
||||||
|
// sender modules.
|
||||||
|
type pendingRequest struct {
|
||||||
|
request Request
|
||||||
|
module Module
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScheduler creates a new Scheduler.
|
||||||
|
func NewScheduler() *Scheduler {
|
||||||
|
s := &Scheduler{
|
||||||
|
servers: make(map[server]struct{}),
|
||||||
|
names: make(map[Module]string),
|
||||||
|
pending: make(map[ServerAndID]pendingRequest),
|
||||||
|
targets: make(map[targetData]uint64),
|
||||||
|
stopCh: make(chan chan struct{}),
|
||||||
|
// Note: testWaitCh should not have capacity in order to ensure
|
||||||
|
// that after a trigger happens testWaitCh will block until the resulting
|
||||||
|
// processing round has been finished
|
||||||
|
triggerCh: make(chan struct{}, 1),
|
||||||
|
testWaitCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterTarget registers a target data structure, ensuring that any changes
|
||||||
|
// made to it trigger a new round of Module.Process calls, giving a chance to
|
||||||
|
// modules to react to the changes.
|
||||||
|
func (s *Scheduler) RegisterTarget(t targetData) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.targets[t] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterModule registers a module. Should be called before starting the scheduler.
|
||||||
|
// In each processing round the order of module processing depends on the order of
|
||||||
|
// registration.
|
||||||
|
func (s *Scheduler) RegisterModule(m Module, name string) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.modules = append(s.modules, m)
|
||||||
|
s.names[m] = name
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterServer registers a new server.
|
||||||
|
func (s *Scheduler) RegisterServer(server server) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.addEvent(Event{Type: EvRegistered, Server: server})
|
||||||
|
server.subscribe(func(event Event) {
|
||||||
|
event.Server = server
|
||||||
|
s.addEvent(event)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnregisterServer removes a registered server.
|
||||||
|
func (s *Scheduler) UnregisterServer(server server) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
server.unsubscribe()
|
||||||
|
s.addEvent(Event{Type: EvUnregistered, Server: server})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the scheduler. It should be called after registering all modules
|
||||||
|
// and before registering any servers.
|
||||||
|
func (s *Scheduler) Start() {
|
||||||
|
go s.syncLoop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the scheduler.
|
||||||
|
func (s *Scheduler) Stop() {
|
||||||
|
stop := make(chan struct{})
|
||||||
|
s.stopCh <- stop
|
||||||
|
<-stop
|
||||||
|
s.lock.Lock()
|
||||||
|
for server := range s.servers {
|
||||||
|
server.unsubscribe()
|
||||||
|
}
|
||||||
|
s.servers = nil
|
||||||
|
s.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncLoop is the main event loop responsible for event/data processing and
|
||||||
|
// sending new requests.
|
||||||
|
// A round of processing starts whenever the global trigger is fired. Triggers
|
||||||
|
// fired during a processing round ensure that there is going to be a next round.
|
||||||
|
func (s *Scheduler) syncLoop() {
|
||||||
|
for {
|
||||||
|
s.lock.Lock()
|
||||||
|
s.processRound()
|
||||||
|
s.lock.Unlock()
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case stop := <-s.stopCh:
|
||||||
|
close(stop)
|
||||||
|
return
|
||||||
|
case <-s.triggerCh:
|
||||||
|
break loop
|
||||||
|
case <-s.testWaitCh:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// targetChanged returns true if a registered target data structure has been
|
||||||
|
// changed since the last call to this function.
|
||||||
|
func (s *Scheduler) targetChanged() (changed bool) {
|
||||||
|
for target, counter := range s.targets {
|
||||||
|
if newCounter := target.ChangeCounter(); newCounter != counter {
|
||||||
|
s.targets[target] = newCounter
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// processRound runs an entire processing round. It calls the Process functions
|
||||||
|
// of all modules, passing all relevant events and repeating Process calls as
|
||||||
|
// long as any changes have been made to the registered target data structures.
|
||||||
|
// Once all events have been processed and a stable state has been achieved,
|
||||||
|
// requests are generated and sent if necessary and possible.
|
||||||
|
func (s *Scheduler) processRound() {
|
||||||
|
for {
|
||||||
|
log.Trace("Processing modules")
|
||||||
|
filteredEvents := s.filterEvents()
|
||||||
|
for _, module := range s.modules {
|
||||||
|
log.Trace("Processing module", "name", s.names[module], "events", len(filteredEvents[module]))
|
||||||
|
module.Process(requester{s, module}, filteredEvents[module])
|
||||||
|
}
|
||||||
|
if !s.targetChanged() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger starts a new processing round. If fired during processing, it ensures
|
||||||
|
// another full round of processing all modules.
|
||||||
|
func (s *Scheduler) Trigger() {
|
||||||
|
select {
|
||||||
|
case s.triggerCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addEvent adds an event to be processed in the next round. Note that it can be
|
||||||
|
// called regardless of the state of the lock mutex, making it safe for use in
|
||||||
|
// the server event callback.
|
||||||
|
func (s *Scheduler) addEvent(event Event) {
|
||||||
|
s.eventLock.Lock()
|
||||||
|
s.events = append(s.events, event)
|
||||||
|
s.eventLock.Unlock()
|
||||||
|
s.Trigger()
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterEvent sorts each Event either as a request event or a server event,
|
||||||
|
// depending on its type. Request events are also sorted in a map based on the
|
||||||
|
// module that originally initiated the request. It also ensures that no events
|
||||||
|
// related to a server are returned before EvRegistered or after EvUnregistered.
|
||||||
|
// In case of an EvUnregistered server event it also closes all pending requests
|
||||||
|
// to the given server by adding a failed request event (EvFail), ensuring that
|
||||||
|
// all requests get finalized and thereby allowing the module logic to be safe
|
||||||
|
// and simple.
|
||||||
|
func (s *Scheduler) filterEvents() map[Module][]Event {
|
||||||
|
s.eventLock.Lock()
|
||||||
|
events := s.events
|
||||||
|
s.events = nil
|
||||||
|
s.eventLock.Unlock()
|
||||||
|
|
||||||
|
s.requesterLock.Lock()
|
||||||
|
defer s.requesterLock.Unlock()
|
||||||
|
|
||||||
|
filteredEvents := make(map[Module][]Event)
|
||||||
|
for _, event := range events {
|
||||||
|
server := event.Server.(server)
|
||||||
|
if _, ok := s.servers[server]; !ok && event.Type != EvRegistered {
|
||||||
|
continue // before EvRegister or after EvUnregister, discard
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.IsRequestEvent() {
|
||||||
|
sid, _, _ := event.RequestInfo()
|
||||||
|
pending, ok := s.pending[sid]
|
||||||
|
if !ok {
|
||||||
|
continue // request already closed, ignore further events
|
||||||
|
}
|
||||||
|
if event.Type == EvResponse || event.Type == EvFail {
|
||||||
|
delete(s.pending, sid) // final event, close pending request
|
||||||
|
}
|
||||||
|
filteredEvents[pending.module] = append(filteredEvents[pending.module], event)
|
||||||
|
} else {
|
||||||
|
switch event.Type {
|
||||||
|
case EvRegistered:
|
||||||
|
s.servers[server] = struct{}{}
|
||||||
|
s.serverOrder = append(s.serverOrder, nil)
|
||||||
|
copy(s.serverOrder[1:], s.serverOrder[:len(s.serverOrder)-1])
|
||||||
|
s.serverOrder[0] = server
|
||||||
|
case EvUnregistered:
|
||||||
|
s.closePending(event.Server, filteredEvents)
|
||||||
|
delete(s.servers, server)
|
||||||
|
for i, srv := range s.serverOrder {
|
||||||
|
if srv == server {
|
||||||
|
copy(s.serverOrder[i:len(s.serverOrder)-1], s.serverOrder[i+1:])
|
||||||
|
s.serverOrder = s.serverOrder[:len(s.serverOrder)-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, module := range s.modules {
|
||||||
|
filteredEvents[module] = append(filteredEvents[module], event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredEvents
|
||||||
|
}
|
||||||
|
|
||||||
|
// closePending closes all pending requests to the given server and adds an EvFail
|
||||||
|
// event to properly finalize them
|
||||||
|
func (s *Scheduler) closePending(server Server, filteredEvents map[Module][]Event) {
|
||||||
|
for sid, pending := range s.pending {
|
||||||
|
if sid.Server == server {
|
||||||
|
filteredEvents[pending.module] = append(filteredEvents[pending.module], Event{
|
||||||
|
Type: EvFail,
|
||||||
|
Server: server,
|
||||||
|
Data: RequestResponse{
|
||||||
|
ID: sid.ID,
|
||||||
|
Request: pending.request,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
delete(s.pending, sid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// requester implements Requester. Note that while requester basically wraps
|
||||||
|
// Scheduler (with the added information of the currently processed Module), all
|
||||||
|
// functions are safe to call from Module.Process which is running while
|
||||||
|
// the Scheduler.lock mutex is held.
|
||||||
|
type requester struct {
|
||||||
|
*Scheduler
|
||||||
|
module Module
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanSendTo returns the list of currently available servers. It also returns
|
||||||
|
// them in an order of least to most recently used, ensuring a round-robin usage
|
||||||
|
// of suitable servers if the module always chooses the first suitable one.
|
||||||
|
func (s requester) CanSendTo() []Server {
|
||||||
|
s.requesterLock.RLock()
|
||||||
|
defer s.requesterLock.RUnlock()
|
||||||
|
|
||||||
|
list := make([]Server, 0, len(s.serverOrder))
|
||||||
|
for _, server := range s.serverOrder {
|
||||||
|
if server.canRequestNow() {
|
||||||
|
list = append(list, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends a request and adds an entry to Scheduler.pending map, ensuring that
|
||||||
|
// related request events will be delivered to the sender Module.
|
||||||
|
func (s requester) Send(srv Server, req Request) ID {
|
||||||
|
s.requesterLock.Lock()
|
||||||
|
defer s.requesterLock.Unlock()
|
||||||
|
|
||||||
|
server := srv.(server)
|
||||||
|
id := server.sendRequest(req)
|
||||||
|
sid := ServerAndID{Server: srv, ID: id}
|
||||||
|
s.pending[sid] = pendingRequest{request: req, module: s.module}
|
||||||
|
for i, ss := range s.serverOrder {
|
||||||
|
if ss == server {
|
||||||
|
copy(s.serverOrder[i:len(s.serverOrder)-1], s.serverOrder[i+1:])
|
||||||
|
s.serverOrder[len(s.serverOrder)-1] = server
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Error("Target server not found in ordered list of registered servers")
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail should be called when a server delivers invalid or useless information.
|
||||||
|
// Calling Fail disables the given server for a period that is initially short
|
||||||
|
// but is exponentially growing if it happens frequently. This results in a
|
||||||
|
// somewhat fault tolerant operation that avoids hammering servers with requests
|
||||||
|
// that they cannot serve but still gives them a chance periodically.
|
||||||
|
func (s requester) Fail(srv Server, desc string) {
|
||||||
|
srv.(server).fail(desc)
|
||||||
|
}
|
||||||
126
beacon/light/request/scheduler_test.go
Normal file
126
beacon/light/request/scheduler_test.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEventFilter(t *testing.T) {
|
||||||
|
s := NewScheduler()
|
||||||
|
module1 := &testModule{name: "module1"}
|
||||||
|
module2 := &testModule{name: "module2"}
|
||||||
|
s.RegisterModule(module1, "module1")
|
||||||
|
s.RegisterModule(module2, "module2")
|
||||||
|
s.Start()
|
||||||
|
// startup process round without events
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, nil)
|
||||||
|
module2.expProcess(t, nil)
|
||||||
|
srv := &testServer{}
|
||||||
|
// register server; both modules should receive server event
|
||||||
|
s.RegisterServer(srv)
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, []Event{
|
||||||
|
{Type: EvRegistered, Server: srv},
|
||||||
|
})
|
||||||
|
module2.expProcess(t, []Event{
|
||||||
|
{Type: EvRegistered, Server: srv},
|
||||||
|
})
|
||||||
|
// let module1 send a request
|
||||||
|
srv.canRequest = 1
|
||||||
|
module1.sendReq = testRequest
|
||||||
|
s.Trigger()
|
||||||
|
// in first triggered round module1 sends the request, no events yet
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, nil)
|
||||||
|
module2.expProcess(t, nil)
|
||||||
|
// server emits EvTimeout; only module1 should receive it
|
||||||
|
srv.eventCb(Event{Type: EvTimeout, Data: RequestResponse{ID: 1, Request: testRequest}})
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, []Event{
|
||||||
|
{Type: EvTimeout, Server: srv, Data: RequestResponse{ID: 1, Request: testRequest}},
|
||||||
|
})
|
||||||
|
module2.expProcess(t, nil)
|
||||||
|
// unregister server; both modules should receive server event
|
||||||
|
s.UnregisterServer(srv)
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, []Event{
|
||||||
|
// module1 should also receive EvFail on its pending request
|
||||||
|
{Type: EvFail, Server: srv, Data: RequestResponse{ID: 1, Request: testRequest}},
|
||||||
|
{Type: EvUnregistered, Server: srv},
|
||||||
|
})
|
||||||
|
module2.expProcess(t, []Event{
|
||||||
|
{Type: EvUnregistered, Server: srv},
|
||||||
|
})
|
||||||
|
// response after server unregistered; should be discarded
|
||||||
|
srv.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}})
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expProcess(t, nil)
|
||||||
|
module2.expProcess(t, nil)
|
||||||
|
// no more process rounds expected; shut down
|
||||||
|
s.testWaitCh <- struct{}{}
|
||||||
|
module1.expNoMoreProcess(t)
|
||||||
|
module2.expNoMoreProcess(t)
|
||||||
|
s.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
type testServer struct {
|
||||||
|
eventCb func(Event)
|
||||||
|
lastID ID
|
||||||
|
canRequest int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) Name() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) subscribe(eventCb func(Event)) {
|
||||||
|
s.eventCb = eventCb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) canRequestNow() bool {
|
||||||
|
return s.canRequest > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) sendRequest(req Request) ID {
|
||||||
|
s.canRequest--
|
||||||
|
s.lastID++
|
||||||
|
return s.lastID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) fail(string) {}
|
||||||
|
func (s *testServer) unsubscribe() {}
|
||||||
|
|
||||||
|
type testModule struct {
|
||||||
|
name string
|
||||||
|
processed [][]Event
|
||||||
|
sendReq Request
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testModule) Process(requester Requester, events []Event) {
|
||||||
|
m.processed = append(m.processed, events)
|
||||||
|
if m.sendReq != nil {
|
||||||
|
if cs := requester.CanSendTo(); len(cs) > 0 {
|
||||||
|
requester.Send(cs[0], m.sendReq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testModule) expProcess(t *testing.T, expEvents []Event) {
|
||||||
|
if len(m.processed) == 0 {
|
||||||
|
t.Errorf("Missing call to %s.Process", m.name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
events := m.processed[0]
|
||||||
|
m.processed = m.processed[1:]
|
||||||
|
if !reflect.DeepEqual(events, expEvents) {
|
||||||
|
t.Errorf("Call to %s.Process with wrong events (expected %v, got %v)", m.name, expEvents, events)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *testModule) expNoMoreProcess(t *testing.T) {
|
||||||
|
for len(m.processed) > 0 {
|
||||||
|
t.Errorf("Unexpected call to %s.Process with events %v", m.name, m.processed[0])
|
||||||
|
m.processed = m.processed[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
451
beacon/light/request/server.go
Normal file
451
beacon/light/request/server.go
Normal file
@@ -0,0 +1,451 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// request events
|
||||||
|
EvResponse = &EventType{Name: "response", requestEvent: true} // data: RequestResponse; sent by requestServer
|
||||||
|
EvFail = &EventType{Name: "fail", requestEvent: true} // data: RequestResponse; sent by requestServer
|
||||||
|
EvTimeout = &EventType{Name: "timeout", requestEvent: true} // data: RequestResponse; sent by serverWithTimeout
|
||||||
|
// server events
|
||||||
|
EvRegistered = &EventType{Name: "registered"} // data: nil; sent by Scheduler
|
||||||
|
EvUnregistered = &EventType{Name: "unregistered"} // data: nil; sent by Scheduler
|
||||||
|
EvCanRequestAgain = &EventType{Name: "canRequestAgain"} // data: nil; sent by serverWithLimits
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
softRequestTimeout = time.Second // allow resending request to a different server but do not cancel yet
|
||||||
|
hardRequestTimeout = time.Second * 10 // cancel request
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// serverWithLimits parameters
|
||||||
|
parallelAdjustUp = 0.1 // adjust parallelLimit up in case of success under full load
|
||||||
|
parallelAdjustDown = 1 // adjust parallelLimit down in case of timeout/failure
|
||||||
|
minParallelLimit = 1 // parallelLimit lower bound
|
||||||
|
defaultParallelLimit = 3 // parallelLimit initial value
|
||||||
|
minFailureDelay = time.Millisecond * 100 // minimum disable time in case of request failure
|
||||||
|
maxFailureDelay = time.Minute // maximum disable time in case of request failure
|
||||||
|
maxServerEventBuffer = 5 // server event allowance buffer limit
|
||||||
|
maxServerEventRate = time.Second // server event allowance buffer recharge rate
|
||||||
|
)
|
||||||
|
|
||||||
|
// requestServer can send requests in a non-blocking way and feed back events
|
||||||
|
// through the event callback. After each request it should send back either
|
||||||
|
// EvResponse or EvFail. Additionally, it may also send application-defined
|
||||||
|
// events that the Modules can interpret.
|
||||||
|
type requestServer interface {
|
||||||
|
Name() string
|
||||||
|
Subscribe(eventCallback func(Event))
|
||||||
|
SendRequest(ID, Request)
|
||||||
|
Unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// server is implemented by a requestServer wrapped into serverWithTimeout and
|
||||||
|
// serverWithLimits and is used by Scheduler.
|
||||||
|
// In addition to requestServer functionality, server can also handle timeouts,
|
||||||
|
// limit the number of parallel in-flight requests and temporarily disable
|
||||||
|
// new requests based on timeouts and response failures.
|
||||||
|
type server interface {
|
||||||
|
Server
|
||||||
|
subscribe(eventCallback func(Event))
|
||||||
|
canRequestNow() bool
|
||||||
|
sendRequest(Request) ID
|
||||||
|
fail(string)
|
||||||
|
unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer wraps a requestServer and returns a server
|
||||||
|
func NewServer(rs requestServer, clock mclock.Clock) server {
|
||||||
|
s := &serverWithLimits{}
|
||||||
|
s.parent = rs
|
||||||
|
s.serverWithTimeout.init(clock)
|
||||||
|
s.init()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventType identifies an event type, either related to a request or the server
|
||||||
|
// in general. Server events can also be externally defined.
|
||||||
|
type EventType struct {
|
||||||
|
Name string
|
||||||
|
requestEvent bool // all request events are pre-defined in request package
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event describes an event where the type of Data depends on Type.
|
||||||
|
// Server field is not required when sent through the event callback; it is filled
|
||||||
|
// out when processed by the Scheduler. Note that the Scheduler can also create
|
||||||
|
// and send events (EvRegistered, EvUnregistered) directly.
|
||||||
|
type Event struct {
|
||||||
|
Type *EventType
|
||||||
|
Server Server // filled by Scheduler
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRequestEvent returns true if the event is a request event
|
||||||
|
func (e *Event) IsRequestEvent() bool {
|
||||||
|
return e.Type.requestEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestInfo assumes that the event is a request event and returns its contents
|
||||||
|
// in a convenient form.
|
||||||
|
func (e *Event) RequestInfo() (ServerAndID, Request, Response) {
|
||||||
|
data := e.Data.(RequestResponse)
|
||||||
|
return ServerAndID{Server: e.Server, ID: data.ID}, data.Request, data.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestResponse is the Data type of request events.
|
||||||
|
type RequestResponse struct {
|
||||||
|
ID ID
|
||||||
|
Request Request
|
||||||
|
Response Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverWithTimeout wraps a requestServer and introduces timeouts.
|
||||||
|
// The request's lifecycle is concluded if EvResponse or EvFail emitted by the
|
||||||
|
// parent requestServer. If this does not happen until softRequestTimeout then
|
||||||
|
// EvTimeout is emitted, after which the final EvResponse or EvFail is still
|
||||||
|
// guaranteed to follow.
|
||||||
|
// If the parent fails to send this final event for hardRequestTimeout then
|
||||||
|
// serverWithTimeout emits EvFail and discards any further events from the
|
||||||
|
// parent related to the given request.
|
||||||
|
type serverWithTimeout struct {
|
||||||
|
parent requestServer
|
||||||
|
lock sync.Mutex
|
||||||
|
clock mclock.Clock
|
||||||
|
childEventCb func(event Event)
|
||||||
|
timeouts map[ID]mclock.Timer
|
||||||
|
lastID ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements request.Server
|
||||||
|
func (s *serverWithTimeout) Name() string {
|
||||||
|
return s.parent.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// init initializes serverWithTimeout
|
||||||
|
func (s *serverWithTimeout) init(clock mclock.Clock) {
|
||||||
|
s.clock = clock
|
||||||
|
s.timeouts = make(map[ID]mclock.Timer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// subscribe subscribes to events which include parent (requestServer) events
|
||||||
|
// plus EvTimeout.
|
||||||
|
func (s *serverWithTimeout) subscribe(eventCallback func(event Event)) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.childEventCb = eventCallback
|
||||||
|
s.parent.Subscribe(s.eventCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendRequest generated a new request ID, emits EvRequest, sets up the timeout
|
||||||
|
// timer, then sends the request through the parent (requestServer).
|
||||||
|
func (s *serverWithTimeout) sendRequest(request Request) (reqId ID) {
|
||||||
|
s.lock.Lock()
|
||||||
|
s.lastID++
|
||||||
|
id := s.lastID
|
||||||
|
s.startTimeout(RequestResponse{ID: id, Request: request})
|
||||||
|
s.lock.Unlock()
|
||||||
|
s.parent.SendRequest(id, request)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventCallback is called by parent (requestServer) event subscription.
|
||||||
|
func (s *serverWithTimeout) eventCallback(event Event) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
switch event.Type {
|
||||||
|
case EvResponse, EvFail:
|
||||||
|
id := event.Data.(RequestResponse).ID
|
||||||
|
if timer, ok := s.timeouts[id]; ok {
|
||||||
|
// Note: if stopping the timer is unsuccessful then the resulting AfterFunc
|
||||||
|
// call will just do nothing
|
||||||
|
timer.Stop()
|
||||||
|
delete(s.timeouts, id)
|
||||||
|
if s.childEventCb != nil {
|
||||||
|
s.childEventCb(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if s.childEventCb != nil {
|
||||||
|
s.childEventCb(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startTimeout starts a timeout timer for the given request.
|
||||||
|
func (s *serverWithTimeout) startTimeout(reqData RequestResponse) {
|
||||||
|
id := reqData.ID
|
||||||
|
s.timeouts[id] = s.clock.AfterFunc(softRequestTimeout, func() {
|
||||||
|
s.lock.Lock()
|
||||||
|
if _, ok := s.timeouts[id]; !ok {
|
||||||
|
s.lock.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.timeouts[id] = s.clock.AfterFunc(hardRequestTimeout-softRequestTimeout, func() {
|
||||||
|
s.lock.Lock()
|
||||||
|
if _, ok := s.timeouts[id]; !ok {
|
||||||
|
s.lock.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(s.timeouts, id)
|
||||||
|
childEventCb := s.childEventCb
|
||||||
|
s.lock.Unlock()
|
||||||
|
if childEventCb != nil {
|
||||||
|
childEventCb(Event{Type: EvFail, Data: reqData})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
childEventCb := s.childEventCb
|
||||||
|
s.lock.Unlock()
|
||||||
|
if childEventCb != nil {
|
||||||
|
childEventCb(Event{Type: EvTimeout, Data: reqData})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsubscribe stops all goroutines associated with the server.
|
||||||
|
func (s *serverWithTimeout) unsubscribe() {
|
||||||
|
s.lock.Lock()
|
||||||
|
for _, timer := range s.timeouts {
|
||||||
|
if timer != nil {
|
||||||
|
timer.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.lock.Unlock()
|
||||||
|
s.parent.Unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverWithLimits wraps serverWithTimeout and implements server. It limits the
|
||||||
|
// number of parallel in-flight requests and prevents sending new requests when a
|
||||||
|
// pending one has already timed out. Server events are also rate limited.
|
||||||
|
// It also implements a failure delay mechanism that adds an exponentially growing
|
||||||
|
// delay each time a request fails (wrong answer or hard timeout). This makes the
|
||||||
|
// syncing mechanism less brittle as temporary failures of the server might happen
|
||||||
|
// sometimes, but still avoids hammering a non-functional server with requests.
|
||||||
|
type serverWithLimits struct {
|
||||||
|
serverWithTimeout
|
||||||
|
lock sync.Mutex
|
||||||
|
childEventCb func(event Event)
|
||||||
|
softTimeouts map[ID]struct{}
|
||||||
|
pendingCount, timeoutCount int
|
||||||
|
parallelLimit float32
|
||||||
|
sendEvent bool
|
||||||
|
delayTimer mclock.Timer
|
||||||
|
delayCounter int
|
||||||
|
failureDelayEnd mclock.AbsTime
|
||||||
|
failureDelay float64
|
||||||
|
serverEventBuffer int
|
||||||
|
eventBufferUpdated mclock.AbsTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// init initializes serverWithLimits
|
||||||
|
func (s *serverWithLimits) init() {
|
||||||
|
s.softTimeouts = make(map[ID]struct{})
|
||||||
|
s.parallelLimit = defaultParallelLimit
|
||||||
|
s.serverEventBuffer = maxServerEventBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// subscribe subscribes to events which include parent (serverWithTimeout) events
|
||||||
|
// plus EvCanRequestAgain.
|
||||||
|
func (s *serverWithLimits) subscribe(eventCallback func(event Event)) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.childEventCb = eventCallback
|
||||||
|
s.serverWithTimeout.subscribe(s.eventCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventCallback is called by parent (serverWithTimeout) event subscription.
|
||||||
|
func (s *serverWithLimits) eventCallback(event Event) {
|
||||||
|
s.lock.Lock()
|
||||||
|
var sendCanRequestAgain bool
|
||||||
|
passEvent := true
|
||||||
|
switch event.Type {
|
||||||
|
case EvTimeout:
|
||||||
|
id := event.Data.(RequestResponse).ID
|
||||||
|
s.softTimeouts[id] = struct{}{}
|
||||||
|
s.timeoutCount++
|
||||||
|
s.parallelLimit -= parallelAdjustDown
|
||||||
|
if s.parallelLimit < minParallelLimit {
|
||||||
|
s.parallelLimit = minParallelLimit
|
||||||
|
}
|
||||||
|
log.Debug("Server timeout", "count", s.timeoutCount, "parallelLimit", s.parallelLimit)
|
||||||
|
case EvResponse, EvFail:
|
||||||
|
id := event.Data.(RequestResponse).ID
|
||||||
|
if _, ok := s.softTimeouts[id]; ok {
|
||||||
|
delete(s.softTimeouts, id)
|
||||||
|
s.timeoutCount--
|
||||||
|
log.Debug("Server timeout finalized", "count", s.timeoutCount, "parallelLimit", s.parallelLimit)
|
||||||
|
}
|
||||||
|
if event.Type == EvResponse && s.pendingCount >= int(s.parallelLimit) {
|
||||||
|
s.parallelLimit += parallelAdjustUp
|
||||||
|
}
|
||||||
|
s.pendingCount--
|
||||||
|
if s.canRequest() {
|
||||||
|
sendCanRequestAgain = s.sendEvent
|
||||||
|
s.sendEvent = false
|
||||||
|
}
|
||||||
|
if event.Type == EvFail {
|
||||||
|
s.failLocked("failed request")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// server event; check rate limit
|
||||||
|
if s.serverEventBuffer < maxServerEventBuffer {
|
||||||
|
now := s.clock.Now()
|
||||||
|
sinceUpdate := time.Duration(now - s.eventBufferUpdated)
|
||||||
|
if sinceUpdate >= maxServerEventRate*time.Duration(maxServerEventBuffer-s.serverEventBuffer) {
|
||||||
|
s.serverEventBuffer = maxServerEventBuffer
|
||||||
|
s.eventBufferUpdated = now
|
||||||
|
} else {
|
||||||
|
addBuffer := int(sinceUpdate / maxServerEventRate)
|
||||||
|
s.serverEventBuffer += addBuffer
|
||||||
|
s.eventBufferUpdated += mclock.AbsTime(maxServerEventRate * time.Duration(addBuffer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.serverEventBuffer > 0 {
|
||||||
|
s.serverEventBuffer--
|
||||||
|
} else {
|
||||||
|
passEvent = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
childEventCb := s.childEventCb
|
||||||
|
s.lock.Unlock()
|
||||||
|
if passEvent && childEventCb != nil {
|
||||||
|
childEventCb(event)
|
||||||
|
}
|
||||||
|
if sendCanRequestAgain && childEventCb != nil {
|
||||||
|
childEventCb(Event{Type: EvCanRequestAgain})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendRequest sends a request through the parent (serverWithTimeout).
|
||||||
|
func (s *serverWithLimits) sendRequest(request Request) (reqId ID) {
|
||||||
|
s.lock.Lock()
|
||||||
|
s.pendingCount++
|
||||||
|
s.lock.Unlock()
|
||||||
|
return s.serverWithTimeout.sendRequest(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsubscribe stops all goroutines associated with the server.
|
||||||
|
func (s *serverWithLimits) unsubscribe() {
|
||||||
|
s.lock.Lock()
|
||||||
|
if s.delayTimer != nil {
|
||||||
|
s.delayTimer.Stop()
|
||||||
|
s.delayTimer = nil
|
||||||
|
}
|
||||||
|
s.childEventCb = nil
|
||||||
|
s.lock.Unlock()
|
||||||
|
s.serverWithTimeout.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// canRequest checks whether a new request can be started.
|
||||||
|
func (s *serverWithLimits) canRequest() bool {
|
||||||
|
if s.delayTimer != nil || s.pendingCount >= int(s.parallelLimit) || s.timeoutCount > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s.parallelLimit < minParallelLimit {
|
||||||
|
s.parallelLimit = minParallelLimit
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// canRequestNow checks whether a new request can be started, according to the
|
||||||
|
// current in-flight request count and parallelLimit, and also the failure delay
|
||||||
|
// timer.
|
||||||
|
// If it returns false then it is guaranteed that an EvCanRequestAgain will be
|
||||||
|
// sent whenever the server becomes available for requesting again.
|
||||||
|
func (s *serverWithLimits) canRequestNow() bool {
|
||||||
|
var sendCanRequestAgain bool
|
||||||
|
s.lock.Lock()
|
||||||
|
canRequest := s.canRequest()
|
||||||
|
if canRequest {
|
||||||
|
sendCanRequestAgain = s.sendEvent
|
||||||
|
s.sendEvent = false
|
||||||
|
}
|
||||||
|
childEventCb := s.childEventCb
|
||||||
|
s.lock.Unlock()
|
||||||
|
if sendCanRequestAgain && childEventCb != nil {
|
||||||
|
childEventCb(Event{Type: EvCanRequestAgain})
|
||||||
|
}
|
||||||
|
return canRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
// delay sets the delay timer to the given duration, disabling new requests for
|
||||||
|
// the given period.
|
||||||
|
func (s *serverWithLimits) delay(delay time.Duration) {
|
||||||
|
if s.delayTimer != nil {
|
||||||
|
// Note: if stopping the timer is unsuccessful then the resulting AfterFunc
|
||||||
|
// call will just do nothing
|
||||||
|
s.delayTimer.Stop()
|
||||||
|
s.delayTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.delayCounter++
|
||||||
|
delayCounter := s.delayCounter
|
||||||
|
log.Debug("Server delay started", "length", delay)
|
||||||
|
s.delayTimer = s.clock.AfterFunc(delay, func() {
|
||||||
|
log.Debug("Server delay ended", "length", delay)
|
||||||
|
var sendCanRequestAgain bool
|
||||||
|
s.lock.Lock()
|
||||||
|
if s.delayTimer != nil && s.delayCounter == delayCounter { // do nothing if there is a new timer now
|
||||||
|
s.delayTimer = nil
|
||||||
|
if s.canRequest() {
|
||||||
|
sendCanRequestAgain = s.sendEvent
|
||||||
|
s.sendEvent = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
childEventCb := s.childEventCb
|
||||||
|
s.lock.Unlock()
|
||||||
|
if sendCanRequestAgain && childEventCb != nil {
|
||||||
|
childEventCb(Event{Type: EvCanRequestAgain})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// fail reports that a response from the server was found invalid by the processing
|
||||||
|
// Module, disabling new requests for a dynamically adjusted time period.
|
||||||
|
func (s *serverWithLimits) fail(desc string) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
s.failLocked(desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// failLocked calculates the dynamic failure delay and applies it.
|
||||||
|
func (s *serverWithLimits) failLocked(desc string) {
|
||||||
|
log.Debug("Server error", "description", desc)
|
||||||
|
s.failureDelay *= 2
|
||||||
|
now := s.clock.Now()
|
||||||
|
if now > s.failureDelayEnd {
|
||||||
|
s.failureDelay *= math.Pow(2, -float64(now-s.failureDelayEnd)/float64(maxFailureDelay))
|
||||||
|
}
|
||||||
|
if s.failureDelay < float64(minFailureDelay) {
|
||||||
|
s.failureDelay = float64(minFailureDelay)
|
||||||
|
}
|
||||||
|
s.failureDelayEnd = now + mclock.AbsTime(s.failureDelay)
|
||||||
|
s.delay(time.Duration(s.failureDelay))
|
||||||
|
}
|
||||||
182
beacon/light/request/server_test.go
Normal file
182
beacon/light/request/server_test.go
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testRequest = "Life, the Universe, and Everything"
|
||||||
|
testResponse = 42
|
||||||
|
)
|
||||||
|
|
||||||
|
var testEventType = &EventType{Name: "testEvent"}
|
||||||
|
|
||||||
|
func TestServerEvents(t *testing.T) {
|
||||||
|
rs := &testRequestServer{}
|
||||||
|
clock := &mclock.Simulated{}
|
||||||
|
srv := NewServer(rs, clock)
|
||||||
|
var lastEventType *EventType
|
||||||
|
srv.subscribe(func(event Event) { lastEventType = event.Type })
|
||||||
|
evTypeName := func(evType *EventType) string {
|
||||||
|
if evType == nil {
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
return evType.Name
|
||||||
|
}
|
||||||
|
expEvent := func(expType *EventType) {
|
||||||
|
if lastEventType != expType {
|
||||||
|
t.Errorf("Wrong event type (expected %s, got %s)", evTypeName(expType), evTypeName(lastEventType))
|
||||||
|
}
|
||||||
|
lastEventType = nil
|
||||||
|
}
|
||||||
|
// user events should simply be passed through
|
||||||
|
rs.eventCb(Event{Type: testEventType})
|
||||||
|
expEvent(testEventType)
|
||||||
|
// send request, soft timeout, then valid response
|
||||||
|
srv.sendRequest(testRequest)
|
||||||
|
clock.WaitForTimers(1)
|
||||||
|
clock.Run(softRequestTimeout)
|
||||||
|
expEvent(EvTimeout)
|
||||||
|
rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}})
|
||||||
|
expEvent(EvResponse)
|
||||||
|
// send request, hard timeout (response after hard timeout should be ignored)
|
||||||
|
srv.sendRequest(testRequest)
|
||||||
|
clock.WaitForTimers(1)
|
||||||
|
clock.Run(softRequestTimeout)
|
||||||
|
expEvent(EvTimeout)
|
||||||
|
clock.WaitForTimers(1)
|
||||||
|
clock.Run(hardRequestTimeout)
|
||||||
|
expEvent(EvFail)
|
||||||
|
rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}})
|
||||||
|
expEvent(nil)
|
||||||
|
srv.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerParallel(t *testing.T) {
|
||||||
|
rs := &testRequestServer{}
|
||||||
|
srv := NewServer(rs, &mclock.Simulated{})
|
||||||
|
srv.subscribe(func(event Event) {})
|
||||||
|
|
||||||
|
expSend := func(expSent int) {
|
||||||
|
var sent int
|
||||||
|
for sent <= expSent {
|
||||||
|
if !srv.canRequestNow() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sent++
|
||||||
|
srv.sendRequest(testRequest)
|
||||||
|
}
|
||||||
|
if sent != expSent {
|
||||||
|
t.Errorf("Wrong number of parallel requests accepted (expected %d, got %d)", expSent, sent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// max out parallel allowance
|
||||||
|
expSend(defaultParallelLimit)
|
||||||
|
// 1 answered, should accept 1 more
|
||||||
|
rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}})
|
||||||
|
expSend(1)
|
||||||
|
// 2 answered, should accept 2 more
|
||||||
|
rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 2, Request: testRequest, Response: testResponse}})
|
||||||
|
rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 3, Request: testRequest, Response: testResponse}})
|
||||||
|
expSend(2)
|
||||||
|
// failed request, should decrease allowance and not accept more
|
||||||
|
rs.eventCb(Event{Type: EvFail, Data: RequestResponse{ID: 4, Request: testRequest}})
|
||||||
|
expSend(0)
|
||||||
|
srv.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerFail(t *testing.T) {
|
||||||
|
rs := &testRequestServer{}
|
||||||
|
clock := &mclock.Simulated{}
|
||||||
|
srv := NewServer(rs, clock)
|
||||||
|
srv.subscribe(func(event Event) {})
|
||||||
|
expCanRequest := func(expCanRequest bool) {
|
||||||
|
if canRequest := srv.canRequestNow(); canRequest != expCanRequest {
|
||||||
|
t.Errorf("Wrong result for canRequestNow (expected %v, got %v)", expCanRequest, canRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// timed out request
|
||||||
|
expCanRequest(true)
|
||||||
|
srv.sendRequest(testRequest)
|
||||||
|
clock.WaitForTimers(1)
|
||||||
|
expCanRequest(true)
|
||||||
|
clock.Run(softRequestTimeout)
|
||||||
|
expCanRequest(false) // cannot request when there is a timed out request
|
||||||
|
rs.eventCb(Event{Type: EvResponse, Data: RequestResponse{ID: 1, Request: testRequest, Response: testResponse}})
|
||||||
|
expCanRequest(true)
|
||||||
|
// explicit server.Fail
|
||||||
|
srv.fail("")
|
||||||
|
clock.WaitForTimers(1)
|
||||||
|
expCanRequest(false) // cannot request for a while after a failure
|
||||||
|
clock.Run(minFailureDelay)
|
||||||
|
expCanRequest(true)
|
||||||
|
// request returned with EvFail
|
||||||
|
srv.sendRequest(testRequest)
|
||||||
|
rs.eventCb(Event{Type: EvFail, Data: RequestResponse{ID: 2, Request: testRequest}})
|
||||||
|
clock.WaitForTimers(1)
|
||||||
|
expCanRequest(false) // EvFail should also start failure delay
|
||||||
|
clock.Run(minFailureDelay)
|
||||||
|
expCanRequest(false) // second failure delay is longer, should still be disabled
|
||||||
|
clock.Run(minFailureDelay)
|
||||||
|
expCanRequest(true)
|
||||||
|
srv.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerEventRateLimit(t *testing.T) {
|
||||||
|
rs := &testRequestServer{}
|
||||||
|
clock := &mclock.Simulated{}
|
||||||
|
srv := NewServer(rs, clock)
|
||||||
|
var eventCount int
|
||||||
|
srv.subscribe(func(event Event) {
|
||||||
|
eventCount++
|
||||||
|
})
|
||||||
|
expEvents := func(send, expAllowed int) {
|
||||||
|
eventCount = 0
|
||||||
|
for sent := 0; sent < send; sent++ {
|
||||||
|
rs.eventCb(Event{Type: testEventType})
|
||||||
|
}
|
||||||
|
if eventCount != expAllowed {
|
||||||
|
t.Errorf("Wrong number of server events passing rate limitation (sent %d, expected %d, got %d)", send, expAllowed, eventCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expEvents(maxServerEventBuffer+5, maxServerEventBuffer)
|
||||||
|
clock.Run(maxServerEventRate)
|
||||||
|
expEvents(5, 1)
|
||||||
|
clock.Run(maxServerEventRate * maxServerEventBuffer * 2)
|
||||||
|
expEvents(maxServerEventBuffer+5, maxServerEventBuffer)
|
||||||
|
srv.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerUnsubscribe(t *testing.T) {
|
||||||
|
rs := &testRequestServer{}
|
||||||
|
clock := &mclock.Simulated{}
|
||||||
|
srv := NewServer(rs, clock)
|
||||||
|
var eventCount int
|
||||||
|
srv.subscribe(func(event Event) {
|
||||||
|
eventCount++
|
||||||
|
})
|
||||||
|
eventCb := rs.eventCb
|
||||||
|
eventCb(Event{Type: testEventType})
|
||||||
|
if eventCount != 1 {
|
||||||
|
t.Errorf("Server event callback not called before unsubscribe")
|
||||||
|
}
|
||||||
|
srv.unsubscribe()
|
||||||
|
if rs.eventCb != nil {
|
||||||
|
t.Errorf("Server event callback not removed after unsubscribe")
|
||||||
|
}
|
||||||
|
eventCb(Event{Type: testEventType})
|
||||||
|
if eventCount != 1 {
|
||||||
|
t.Errorf("Server event callback called after unsubscribe")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testRequestServer struct {
|
||||||
|
eventCb func(Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *testRequestServer) Name() string { return "" }
|
||||||
|
func (rs *testRequestServer) Subscribe(eventCb func(Event)) { rs.eventCb = eventCb }
|
||||||
|
func (rs *testRequestServer) SendRequest(ID, Request) {}
|
||||||
|
func (rs *testRequestServer) Unsubscribe() { rs.eventCb = nil }
|
||||||
202
beacon/light/sync/head_sync.go
Normal file
202
beacon/light/sync/head_sync.go
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type headTracker interface {
|
||||||
|
ValidateOptimistic(update types.OptimisticUpdate) (bool, error)
|
||||||
|
ValidateFinality(head types.FinalityUpdate) (bool, error)
|
||||||
|
ValidatedFinality() (types.FinalityUpdate, bool)
|
||||||
|
SetPrefetchHead(head types.HeadInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeadSync implements request.Module; it updates the validated and prefetch
|
||||||
|
// heads of HeadTracker based on the EvHead and EvSignedHead events coming from
|
||||||
|
// registered servers.
|
||||||
|
// It can also postpone the validation of the latest announced signed head
|
||||||
|
// until the committee chain is synced up to at least the required period.
|
||||||
|
type HeadSync struct {
|
||||||
|
headTracker headTracker
|
||||||
|
chain committeeChain
|
||||||
|
nextSyncPeriod uint64
|
||||||
|
chainInit bool
|
||||||
|
unvalidatedOptimistic map[request.Server]types.OptimisticUpdate
|
||||||
|
unvalidatedFinality map[request.Server]types.FinalityUpdate
|
||||||
|
serverHeads map[request.Server]types.HeadInfo
|
||||||
|
reqFinalityEpoch map[request.Server]uint64 // next epoch to request finality update
|
||||||
|
headServerCount map[types.HeadInfo]headServerCount
|
||||||
|
headCounter uint64
|
||||||
|
prefetchHead types.HeadInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// headServerCount is associated with most recently seen head infos; it counts
|
||||||
|
// the number of servers currently having the given head info as their announced
|
||||||
|
// head and a counter signaling how recent that head is.
|
||||||
|
// This data is used for selecting the prefetch head.
|
||||||
|
type headServerCount struct {
|
||||||
|
serverCount int
|
||||||
|
headCounter uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHeadSync creates a new HeadSync.
|
||||||
|
func NewHeadSync(headTracker headTracker, chain committeeChain) *HeadSync {
|
||||||
|
s := &HeadSync{
|
||||||
|
headTracker: headTracker,
|
||||||
|
chain: chain,
|
||||||
|
unvalidatedOptimistic: make(map[request.Server]types.OptimisticUpdate),
|
||||||
|
unvalidatedFinality: make(map[request.Server]types.FinalityUpdate),
|
||||||
|
serverHeads: make(map[request.Server]types.HeadInfo),
|
||||||
|
headServerCount: make(map[types.HeadInfo]headServerCount),
|
||||||
|
reqFinalityEpoch: make(map[request.Server]uint64),
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process implements request.Module.
|
||||||
|
func (s *HeadSync) Process(requester request.Requester, events []request.Event) {
|
||||||
|
nextPeriod, chainInit := s.chain.NextSyncPeriod()
|
||||||
|
if nextPeriod != s.nextSyncPeriod || chainInit != s.chainInit {
|
||||||
|
s.nextSyncPeriod, s.chainInit = nextPeriod, chainInit
|
||||||
|
s.processUnvalidatedUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, event := range events {
|
||||||
|
switch event.Type {
|
||||||
|
case EvNewHead:
|
||||||
|
s.setServerHead(event.Server, event.Data.(types.HeadInfo))
|
||||||
|
case EvNewOptimisticUpdate:
|
||||||
|
update := event.Data.(types.OptimisticUpdate)
|
||||||
|
s.newOptimisticUpdate(event.Server, update)
|
||||||
|
epoch := update.Attested.Epoch()
|
||||||
|
if epoch < s.reqFinalityEpoch[event.Server] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if finality, ok := s.headTracker.ValidatedFinality(); ok && finality.Attested.Header.Epoch() >= epoch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
requester.Send(event.Server, ReqFinality{})
|
||||||
|
s.reqFinalityEpoch[event.Server] = epoch + 1
|
||||||
|
case EvNewFinalityUpdate:
|
||||||
|
s.newFinalityUpdate(event.Server, event.Data.(types.FinalityUpdate))
|
||||||
|
case request.EvResponse:
|
||||||
|
_, _, resp := event.RequestInfo()
|
||||||
|
s.newFinalityUpdate(event.Server, resp.(types.FinalityUpdate))
|
||||||
|
case request.EvUnregistered:
|
||||||
|
s.setServerHead(event.Server, types.HeadInfo{})
|
||||||
|
delete(s.serverHeads, event.Server)
|
||||||
|
delete(s.unvalidatedOptimistic, event.Server)
|
||||||
|
delete(s.unvalidatedFinality, event.Server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newOptimisticUpdate handles received optimistic update; either validates it if
|
||||||
|
// the chain is properly synced or stores it for further validation.
|
||||||
|
func (s *HeadSync) newOptimisticUpdate(server request.Server, optimisticUpdate types.OptimisticUpdate) {
|
||||||
|
if !s.chainInit || types.SyncPeriod(optimisticUpdate.SignatureSlot) > s.nextSyncPeriod {
|
||||||
|
s.unvalidatedOptimistic[server] = optimisticUpdate
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := s.headTracker.ValidateOptimistic(optimisticUpdate); err != nil {
|
||||||
|
log.Debug("Error validating optimistic update", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFinalityUpdate handles received finality update; either validates it if
|
||||||
|
// the chain is properly synced or stores it for further validation.
|
||||||
|
func (s *HeadSync) newFinalityUpdate(server request.Server, finalityUpdate types.FinalityUpdate) {
|
||||||
|
if !s.chainInit || types.SyncPeriod(finalityUpdate.SignatureSlot) > s.nextSyncPeriod {
|
||||||
|
s.unvalidatedFinality[server] = finalityUpdate
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := s.headTracker.ValidateFinality(finalityUpdate); err != nil {
|
||||||
|
log.Debug("Error validating finality update", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processUnvalidatedUpdates iterates the list of unvalidated updates and validates
|
||||||
|
// those which can be validated.
|
||||||
|
func (s *HeadSync) processUnvalidatedUpdates() {
|
||||||
|
if !s.chainInit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for server, optimisticUpdate := range s.unvalidatedOptimistic {
|
||||||
|
if types.SyncPeriod(optimisticUpdate.SignatureSlot) <= s.nextSyncPeriod {
|
||||||
|
if _, err := s.headTracker.ValidateOptimistic(optimisticUpdate); err != nil {
|
||||||
|
log.Debug("Error validating deferred optimistic update", "error", err)
|
||||||
|
}
|
||||||
|
delete(s.unvalidatedOptimistic, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for server, finalityUpdate := range s.unvalidatedFinality {
|
||||||
|
if types.SyncPeriod(finalityUpdate.SignatureSlot) <= s.nextSyncPeriod {
|
||||||
|
if _, err := s.headTracker.ValidateFinality(finalityUpdate); err != nil {
|
||||||
|
log.Debug("Error validating deferred finality update", "error", err)
|
||||||
|
}
|
||||||
|
delete(s.unvalidatedFinality, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setServerHead processes non-validated server head announcements and updates
|
||||||
|
// the prefetch head if necessary.
|
||||||
|
func (s *HeadSync) setServerHead(server request.Server, head types.HeadInfo) bool {
|
||||||
|
if oldHead, ok := s.serverHeads[server]; ok {
|
||||||
|
if head == oldHead {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
h := s.headServerCount[oldHead]
|
||||||
|
if h.serverCount--; h.serverCount > 0 {
|
||||||
|
s.headServerCount[oldHead] = h
|
||||||
|
} else {
|
||||||
|
delete(s.headServerCount, oldHead)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if head != (types.HeadInfo{}) {
|
||||||
|
h, ok := s.headServerCount[head]
|
||||||
|
if !ok {
|
||||||
|
s.headCounter++
|
||||||
|
h.headCounter = s.headCounter
|
||||||
|
}
|
||||||
|
h.serverCount++
|
||||||
|
s.headServerCount[head] = h
|
||||||
|
s.serverHeads[server] = head
|
||||||
|
} else {
|
||||||
|
delete(s.serverHeads, server)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
bestHead types.HeadInfo
|
||||||
|
bestHeadInfo headServerCount
|
||||||
|
)
|
||||||
|
for head, headServerCount := range s.headServerCount {
|
||||||
|
if headServerCount.serverCount > bestHeadInfo.serverCount ||
|
||||||
|
(headServerCount.serverCount == bestHeadInfo.serverCount && headServerCount.headCounter > bestHeadInfo.headCounter) {
|
||||||
|
bestHead, bestHeadInfo = head, headServerCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bestHead == s.prefetchHead {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s.prefetchHead = bestHead
|
||||||
|
s.headTracker.SetPrefetchHead(bestHead)
|
||||||
|
return true
|
||||||
|
}
|
||||||
183
beacon/light/sync/head_sync_test.go
Normal file
183
beacon/light/sync/head_sync_test.go
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testServer1 = testServer("testServer1")
|
||||||
|
testServer2 = testServer("testServer2")
|
||||||
|
testServer3 = testServer("testServer3")
|
||||||
|
testServer4 = testServer("testServer4")
|
||||||
|
testServer5 = testServer("testServer5")
|
||||||
|
|
||||||
|
testHead0 = types.HeadInfo{}
|
||||||
|
testHead1 = types.HeadInfo{Slot: 123, BlockRoot: common.Hash{1}}
|
||||||
|
testHead2 = types.HeadInfo{Slot: 124, BlockRoot: common.Hash{2}}
|
||||||
|
testHead3 = types.HeadInfo{Slot: 124, BlockRoot: common.Hash{3}}
|
||||||
|
testHead4 = types.HeadInfo{Slot: 125, BlockRoot: common.Hash{4}}
|
||||||
|
|
||||||
|
testOptUpdate1 = types.OptimisticUpdate{SignatureSlot: 0x0124, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x0123, StateRoot: common.Hash{1}}}}
|
||||||
|
testOptUpdate2 = types.OptimisticUpdate{SignatureSlot: 0x2010, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x200e, StateRoot: common.Hash{2}}}}
|
||||||
|
// testOptUpdate3 is at the end of period 1 but signed in period 2
|
||||||
|
testOptUpdate3 = types.OptimisticUpdate{SignatureSlot: 0x4000, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x3fff, StateRoot: common.Hash{3}}}}
|
||||||
|
testOptUpdate4 = types.OptimisticUpdate{SignatureSlot: 0x6444, Attested: types.HeaderWithExecProof{Header: types.Header{Slot: 0x6443, StateRoot: common.Hash{4}}}}
|
||||||
|
)
|
||||||
|
|
||||||
|
func finality(opt types.OptimisticUpdate) types.FinalityUpdate {
|
||||||
|
return types.FinalityUpdate{
|
||||||
|
SignatureSlot: opt.SignatureSlot,
|
||||||
|
Attested: opt.Attested,
|
||||||
|
Finalized: types.HeaderWithExecProof{Header: types.Header{Slot: (opt.Attested.Header.Slot - 64) & uint64(0xffffffffffffffe0)}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testServer string
|
||||||
|
|
||||||
|
func (t testServer) Name() string {
|
||||||
|
return string(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatedHead(t *testing.T) {
|
||||||
|
chain := &TestCommitteeChain{}
|
||||||
|
ht := &TestHeadTracker{}
|
||||||
|
headSync := NewHeadSync(ht, chain)
|
||||||
|
ts := NewTestScheduler(t, headSync)
|
||||||
|
|
||||||
|
ht.ExpValidated(t, 0, nil)
|
||||||
|
|
||||||
|
ts.AddServer(testServer1, 1)
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate1)
|
||||||
|
ts.Run(1, testServer1, ReqFinality{})
|
||||||
|
// announced head should be queued because of uninitialized chain
|
||||||
|
ht.ExpValidated(t, 1, nil)
|
||||||
|
|
||||||
|
chain.SetNextSyncPeriod(0) // initialize chain
|
||||||
|
ts.Run(2)
|
||||||
|
// expect previously queued head to be validated
|
||||||
|
ht.ExpValidated(t, 2, []types.OptimisticUpdate{testOptUpdate1})
|
||||||
|
|
||||||
|
chain.SetNextSyncPeriod(1)
|
||||||
|
ts.ServerEvent(EvNewFinalityUpdate, testServer1, finality(testOptUpdate2))
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate2)
|
||||||
|
ts.AddServer(testServer2, 1)
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer2, testOptUpdate2)
|
||||||
|
ts.Run(3)
|
||||||
|
// expect both head announcements to be validated instantly
|
||||||
|
ht.ExpValidated(t, 3, []types.OptimisticUpdate{testOptUpdate2, testOptUpdate2})
|
||||||
|
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer1, testOptUpdate3)
|
||||||
|
ts.AddServer(testServer3, 1)
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer3, testOptUpdate4)
|
||||||
|
// finality should be requested from both servers
|
||||||
|
ts.Run(4, testServer1, ReqFinality{}, testServer3, ReqFinality{})
|
||||||
|
// future period announced heads should be queued
|
||||||
|
ht.ExpValidated(t, 4, nil)
|
||||||
|
|
||||||
|
chain.SetNextSyncPeriod(2)
|
||||||
|
ts.Run(5)
|
||||||
|
// testOptUpdate3 can be validated now but not testOptUpdate4
|
||||||
|
ht.ExpValidated(t, 5, []types.OptimisticUpdate{testOptUpdate3})
|
||||||
|
|
||||||
|
ts.AddServer(testServer4, 1)
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer4, testOptUpdate3)
|
||||||
|
// new server joined with recent optimistic update but still no finality; should be requested
|
||||||
|
ts.Run(6, testServer4, ReqFinality{})
|
||||||
|
ht.ExpValidated(t, 6, []types.OptimisticUpdate{testOptUpdate3})
|
||||||
|
|
||||||
|
ts.AddServer(testServer5, 1)
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(6, 1), finality(testOptUpdate3))
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer5, testOptUpdate3)
|
||||||
|
// finality update request answered; new server should not be requested
|
||||||
|
ts.Run(7)
|
||||||
|
ht.ExpValidated(t, 7, []types.OptimisticUpdate{testOptUpdate3})
|
||||||
|
|
||||||
|
// server 3 disconnected without proving period 3, its announced head should be dropped
|
||||||
|
ts.RemoveServer(testServer3)
|
||||||
|
ts.Run(8)
|
||||||
|
ht.ExpValidated(t, 8, nil)
|
||||||
|
|
||||||
|
chain.SetNextSyncPeriod(3)
|
||||||
|
ts.Run(9)
|
||||||
|
// testOptUpdate4 could be validated now but it's not queued by any registered server
|
||||||
|
ht.ExpValidated(t, 9, nil)
|
||||||
|
|
||||||
|
ts.ServerEvent(EvNewFinalityUpdate, testServer2, finality(testOptUpdate4))
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer2, testOptUpdate4)
|
||||||
|
ts.Run(10)
|
||||||
|
// now testOptUpdate4 should be validated
|
||||||
|
ht.ExpValidated(t, 10, []types.OptimisticUpdate{testOptUpdate4})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrefetchHead(t *testing.T) {
|
||||||
|
chain := &TestCommitteeChain{}
|
||||||
|
ht := &TestHeadTracker{}
|
||||||
|
headSync := NewHeadSync(ht, chain)
|
||||||
|
ts := NewTestScheduler(t, headSync)
|
||||||
|
|
||||||
|
ht.ExpPrefetch(t, 0, testHead0) // no servers registered
|
||||||
|
|
||||||
|
ts.AddServer(testServer1, 1)
|
||||||
|
ts.ServerEvent(EvNewHead, testServer1, testHead1)
|
||||||
|
ts.Run(1)
|
||||||
|
ht.ExpPrefetch(t, 1, testHead1) // s1: h1
|
||||||
|
|
||||||
|
ts.AddServer(testServer2, 1)
|
||||||
|
ts.ServerEvent(EvNewHead, testServer2, testHead2)
|
||||||
|
ts.Run(2)
|
||||||
|
ht.ExpPrefetch(t, 2, testHead2) // s1: h1, s2: h2
|
||||||
|
|
||||||
|
ts.ServerEvent(EvNewHead, testServer1, testHead2)
|
||||||
|
ts.Run(3)
|
||||||
|
ht.ExpPrefetch(t, 3, testHead2) // s1: h2, s2: h2
|
||||||
|
|
||||||
|
ts.AddServer(testServer3, 1)
|
||||||
|
ts.ServerEvent(EvNewHead, testServer3, testHead3)
|
||||||
|
ts.Run(4)
|
||||||
|
ht.ExpPrefetch(t, 4, testHead2) // s1: h2, s2: h2, s3: h3
|
||||||
|
|
||||||
|
ts.AddServer(testServer4, 1)
|
||||||
|
ts.ServerEvent(EvNewHead, testServer4, testHead4)
|
||||||
|
ts.Run(5)
|
||||||
|
ht.ExpPrefetch(t, 5, testHead2) // s1: h2, s2: h2, s3: h3, s4: h4
|
||||||
|
|
||||||
|
ts.ServerEvent(EvNewHead, testServer2, testHead3)
|
||||||
|
ts.Run(6)
|
||||||
|
ht.ExpPrefetch(t, 6, testHead3) // s1: h2, s2: h3, s3: h3, s4: h4
|
||||||
|
|
||||||
|
ts.RemoveServer(testServer3)
|
||||||
|
ts.Run(7)
|
||||||
|
ht.ExpPrefetch(t, 7, testHead4) // s1: h2, s2: h3, s4: h4
|
||||||
|
|
||||||
|
ts.RemoveServer(testServer1)
|
||||||
|
ts.Run(8)
|
||||||
|
ht.ExpPrefetch(t, 8, testHead4) // s2: h3, s4: h4
|
||||||
|
|
||||||
|
ts.RemoveServer(testServer4)
|
||||||
|
ts.Run(9)
|
||||||
|
ht.ExpPrefetch(t, 9, testHead3) // s2: h3
|
||||||
|
|
||||||
|
ts.RemoveServer(testServer2)
|
||||||
|
ts.Run(10)
|
||||||
|
ht.ExpPrefetch(t, 10, testHead0) // no servers registered
|
||||||
|
}
|
||||||
259
beacon/light/sync/test_helpers.go
Normal file
259
beacon/light/sync/test_helpers.go
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type requestWithID struct {
|
||||||
|
sid request.ServerAndID
|
||||||
|
request request.Request
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestScheduler struct {
|
||||||
|
t *testing.T
|
||||||
|
module request.Module
|
||||||
|
events []request.Event
|
||||||
|
servers []request.Server
|
||||||
|
allowance map[request.Server]int
|
||||||
|
sent map[int][]requestWithID
|
||||||
|
testIndex int
|
||||||
|
expFail map[request.Server]int // expected Server.Fail calls during next Run
|
||||||
|
lastId request.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestScheduler(t *testing.T, module request.Module) *TestScheduler {
|
||||||
|
return &TestScheduler{
|
||||||
|
t: t,
|
||||||
|
module: module,
|
||||||
|
allowance: make(map[request.Server]int),
|
||||||
|
expFail: make(map[request.Server]int),
|
||||||
|
sent: make(map[int][]requestWithID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) Run(testIndex int, exp ...any) {
|
||||||
|
expReqs := make([]requestWithID, len(exp)/2)
|
||||||
|
id := ts.lastId
|
||||||
|
for i := range expReqs {
|
||||||
|
id++
|
||||||
|
expReqs[i] = requestWithID{
|
||||||
|
sid: request.ServerAndID{Server: exp[i*2].(request.Server), ID: id},
|
||||||
|
request: exp[i*2+1].(request.Request),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(expReqs) == 0 {
|
||||||
|
expReqs = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.testIndex = testIndex
|
||||||
|
ts.module.Process(ts, ts.events)
|
||||||
|
ts.events = nil
|
||||||
|
|
||||||
|
for server, count := range ts.expFail {
|
||||||
|
delete(ts.expFail, server)
|
||||||
|
if count == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ts.t.Errorf("Missing %d Server.Fail(s) from server %s in test case #%d", count, server.Name(), testIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(ts.sent[testIndex], expReqs) {
|
||||||
|
ts.t.Errorf("Wrong sent requests in test case #%d (expected %v, got %v)", testIndex, expReqs, ts.sent[testIndex])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) CanSendTo() (cs []request.Server) {
|
||||||
|
for _, server := range ts.servers {
|
||||||
|
if ts.allowance[server] > 0 {
|
||||||
|
cs = append(cs, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) Send(server request.Server, req request.Request) request.ID {
|
||||||
|
ts.lastId++
|
||||||
|
ts.sent[ts.testIndex] = append(ts.sent[ts.testIndex], requestWithID{
|
||||||
|
sid: request.ServerAndID{Server: server, ID: ts.lastId},
|
||||||
|
request: req,
|
||||||
|
})
|
||||||
|
ts.allowance[server]--
|
||||||
|
return ts.lastId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) Fail(server request.Server, desc string) {
|
||||||
|
if ts.expFail[server] == 0 {
|
||||||
|
ts.t.Errorf("Unexpected Fail from server %s in test case #%d: %s", server.Name(), ts.testIndex, desc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ts.expFail[server]--
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) Request(testIndex, reqIndex int) requestWithID {
|
||||||
|
if len(ts.sent[testIndex]) < reqIndex {
|
||||||
|
ts.t.Errorf("Missing request from test case %d index %d", testIndex, reqIndex)
|
||||||
|
return requestWithID{}
|
||||||
|
}
|
||||||
|
return ts.sent[testIndex][reqIndex-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) ServerEvent(evType *request.EventType, server request.Server, data any) {
|
||||||
|
ts.events = append(ts.events, request.Event{
|
||||||
|
Type: evType,
|
||||||
|
Server: server,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) RequestEvent(evType *request.EventType, req requestWithID, resp request.Response) {
|
||||||
|
if req.request == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ts.events = append(ts.events, request.Event{
|
||||||
|
Type: evType,
|
||||||
|
Server: req.sid.Server,
|
||||||
|
Data: request.RequestResponse{
|
||||||
|
ID: req.sid.ID,
|
||||||
|
Request: req.request,
|
||||||
|
Response: resp,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) AddServer(server request.Server, allowance int) {
|
||||||
|
ts.servers = append(ts.servers, server)
|
||||||
|
ts.allowance[server] = allowance
|
||||||
|
ts.ServerEvent(request.EvRegistered, server, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) RemoveServer(server request.Server) {
|
||||||
|
ts.servers = append(ts.servers, server)
|
||||||
|
for i, s := range ts.servers {
|
||||||
|
if s == server {
|
||||||
|
copy(ts.servers[i:len(ts.servers)-1], ts.servers[i+1:])
|
||||||
|
ts.servers = ts.servers[:len(ts.servers)-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(ts.allowance, server)
|
||||||
|
ts.ServerEvent(request.EvUnregistered, server, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) AddAllowance(server request.Server, allowance int) {
|
||||||
|
ts.allowance[server] += allowance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TestScheduler) ExpFail(server request.Server) {
|
||||||
|
ts.expFail[server]++
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestCommitteeChain struct {
|
||||||
|
fsp, nsp uint64
|
||||||
|
init bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TestCommitteeChain) CheckpointInit(bootstrap types.BootstrapData) error {
|
||||||
|
tc.fsp, tc.nsp, tc.init = bootstrap.Header.SyncPeriod(), bootstrap.Header.SyncPeriod()+2, true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TestCommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error {
|
||||||
|
period := update.AttestedHeader.Header.SyncPeriod()
|
||||||
|
if period < tc.fsp || period > tc.nsp || !tc.init {
|
||||||
|
return light.ErrInvalidPeriod
|
||||||
|
}
|
||||||
|
if period == tc.nsp {
|
||||||
|
tc.nsp++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TestCommitteeChain) NextSyncPeriod() (uint64, bool) {
|
||||||
|
return tc.nsp, tc.init
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TestCommitteeChain) ExpInit(t *testing.T, ExpInit bool) {
|
||||||
|
if tc.init != ExpInit {
|
||||||
|
t.Errorf("Incorrect init flag (expected %v, got %v)", ExpInit, tc.init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TestCommitteeChain) SetNextSyncPeriod(nsp uint64) {
|
||||||
|
tc.init, tc.nsp = true, nsp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TestCommitteeChain) ExpNextSyncPeriod(t *testing.T, expNsp uint64) {
|
||||||
|
tc.ExpInit(t, true)
|
||||||
|
if tc.nsp != expNsp {
|
||||||
|
t.Errorf("Incorrect NextSyncPeriod (expected %d, got %d)", expNsp, tc.nsp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestHeadTracker struct {
|
||||||
|
phead types.HeadInfo
|
||||||
|
validated []types.OptimisticUpdate
|
||||||
|
finality types.FinalityUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ht *TestHeadTracker) ValidateOptimistic(update types.OptimisticUpdate) (bool, error) {
|
||||||
|
ht.validated = append(ht.validated, update)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ht *TestHeadTracker) ValidateFinality(update types.FinalityUpdate) (bool, error) {
|
||||||
|
ht.finality = update
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ht *TestHeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
|
||||||
|
return ht.finality, ht.finality.Attested.Header != (types.Header{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ht *TestHeadTracker) ExpValidated(t *testing.T, tci int, expHeads []types.OptimisticUpdate) {
|
||||||
|
for i, expHead := range expHeads {
|
||||||
|
if i >= len(ht.validated) {
|
||||||
|
t.Errorf("Missing validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got none)", tci, i, expHead.Attested.Header.Slot, expHead.Attested.Header.Hash())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ht.validated[i], expHead) {
|
||||||
|
vhead := ht.validated[i].Attested.Header
|
||||||
|
t.Errorf("Wrong validated head in test case #%d index #%d (expected {slot %d blockRoot %x}, got {slot %d blockRoot %x})", tci, i, expHead.Attested.Header.Slot, expHead.Attested.Header.Hash(), vhead.Slot, vhead.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := len(expHeads); i < len(ht.validated); i++ {
|
||||||
|
vhead := ht.validated[i].Attested.Header
|
||||||
|
t.Errorf("Unexpected validated head in test case #%d index #%d (expected none, got {slot %d blockRoot %x})", tci, i, vhead.Slot, vhead.Hash())
|
||||||
|
}
|
||||||
|
ht.validated = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ht *TestHeadTracker) SetPrefetchHead(head types.HeadInfo) {
|
||||||
|
ht.phead = head
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ht *TestHeadTracker) ExpPrefetch(t *testing.T, tci int, exp types.HeadInfo) {
|
||||||
|
if ht.phead != exp {
|
||||||
|
t.Errorf("Wrong prefetch head in test case #%d (expected {slot %d blockRoot %x}, got {slot %d blockRoot %x})", tci, exp.Slot, exp.BlockRoot, ht.phead.Slot, ht.phead.BlockRoot)
|
||||||
|
}
|
||||||
|
}
|
||||||
47
beacon/light/sync/types.go
Normal file
47
beacon/light/sync/types.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
EvNewHead = &request.EventType{Name: "newHead"} // data: types.HeadInfo
|
||||||
|
EvNewOptimisticUpdate = &request.EventType{Name: "newOptimisticUpdate"} // data: types.OptimisticUpdate
|
||||||
|
EvNewFinalityUpdate = &request.EventType{Name: "newFinalityUpdate"} // data: types.FinalityUpdate
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
ReqUpdates struct {
|
||||||
|
FirstPeriod, Count uint64
|
||||||
|
}
|
||||||
|
RespUpdates struct {
|
||||||
|
Updates []*types.LightClientUpdate
|
||||||
|
Committees []*types.SerializedSyncCommittee
|
||||||
|
}
|
||||||
|
ReqHeader common.Hash
|
||||||
|
RespHeader struct {
|
||||||
|
Header types.Header
|
||||||
|
Canonical, Finalized bool
|
||||||
|
}
|
||||||
|
ReqCheckpointData common.Hash
|
||||||
|
ReqBeaconBlock common.Hash
|
||||||
|
ReqFinality struct{}
|
||||||
|
)
|
||||||
398
beacon/light/sync/update_sync.go
Normal file
398
beacon/light/sync/update_sync.go
Normal file
@@ -0,0 +1,398 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/params"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxUpdateRequest = 8 // maximum number of updates requested in a single request
|
||||||
|
|
||||||
|
type committeeChain interface {
|
||||||
|
CheckpointInit(bootstrap types.BootstrapData) error
|
||||||
|
InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error
|
||||||
|
NextSyncPeriod() (uint64, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckpointInit implements request.Module; it fetches the light client bootstrap
|
||||||
|
// data belonging to the given checkpoint hash and initializes the committee chain
|
||||||
|
// if successful.
|
||||||
|
type CheckpointInit struct {
|
||||||
|
chain committeeChain
|
||||||
|
checkpointHash common.Hash
|
||||||
|
locked request.ServerAndID
|
||||||
|
initialized bool
|
||||||
|
// per-server state is used to track the state of requesting checkpoint header
|
||||||
|
// info. Part of this info (canonical and finalized state) is not validated
|
||||||
|
// and therefore it is requested from each server separately after it has
|
||||||
|
// reported a missing checkpoint (which is also not validated info).
|
||||||
|
serverState map[request.Server]serverState
|
||||||
|
// the following fields are used to determine whether the checkpoint is on
|
||||||
|
// epoch boundary. This information is validated and therefore stored globally.
|
||||||
|
parentHash common.Hash
|
||||||
|
hasEpochInfo, epochBoundary bool
|
||||||
|
cpSlot, parentSlot uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ssDefault = iota // no action yet or checkpoint requested
|
||||||
|
ssNeedHeader // checkpoint req failed, need cp header
|
||||||
|
ssHeaderRequested // cp header requested
|
||||||
|
ssNeedParent // cp header slot %32 != 0, need parent to check epoch boundary
|
||||||
|
ssParentRequested // cp parent header requested
|
||||||
|
ssPrintStatus // has all necessary info, print log message if init still not successful
|
||||||
|
ssDone // log message printed, no more action required
|
||||||
|
)
|
||||||
|
|
||||||
|
type serverState struct {
|
||||||
|
state int
|
||||||
|
hasHeader, canonical, finalized bool // stored per server because not validated
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCheckpointInit creates a new CheckpointInit.
|
||||||
|
func NewCheckpointInit(chain committeeChain, checkpointHash common.Hash) *CheckpointInit {
|
||||||
|
return &CheckpointInit{
|
||||||
|
chain: chain,
|
||||||
|
checkpointHash: checkpointHash,
|
||||||
|
serverState: make(map[request.Server]serverState),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process implements request.Module.
|
||||||
|
func (s *CheckpointInit) Process(requester request.Requester, events []request.Event) {
|
||||||
|
if s.initialized {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, event := range events {
|
||||||
|
switch event.Type {
|
||||||
|
case request.EvResponse, request.EvFail, request.EvTimeout:
|
||||||
|
sid, req, resp := event.RequestInfo()
|
||||||
|
if s.locked == sid {
|
||||||
|
s.locked = request.ServerAndID{}
|
||||||
|
}
|
||||||
|
if event.Type == request.EvTimeout {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch s.serverState[sid.Server].state {
|
||||||
|
case ssDefault:
|
||||||
|
if resp != nil {
|
||||||
|
if checkpoint := resp.(*types.BootstrapData); checkpoint.Header.Hash() == common.Hash(req.(ReqCheckpointData)) {
|
||||||
|
s.chain.CheckpointInit(*checkpoint)
|
||||||
|
s.initialized = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
requester.Fail(event.Server, "invalid checkpoint data")
|
||||||
|
}
|
||||||
|
s.serverState[sid.Server] = serverState{state: ssNeedHeader}
|
||||||
|
case ssHeaderRequested:
|
||||||
|
if resp == nil {
|
||||||
|
s.serverState[sid.Server] = serverState{state: ssPrintStatus}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newState := serverState{
|
||||||
|
hasHeader: true,
|
||||||
|
canonical: resp.(RespHeader).Canonical,
|
||||||
|
finalized: resp.(RespHeader).Finalized,
|
||||||
|
}
|
||||||
|
s.cpSlot, s.parentHash = resp.(RespHeader).Header.Slot, resp.(RespHeader).Header.ParentRoot
|
||||||
|
if s.cpSlot%params.EpochLength == 0 {
|
||||||
|
s.hasEpochInfo, s.epochBoundary = true, true
|
||||||
|
}
|
||||||
|
if s.hasEpochInfo {
|
||||||
|
newState.state = ssPrintStatus
|
||||||
|
} else {
|
||||||
|
newState.state = ssNeedParent
|
||||||
|
}
|
||||||
|
s.serverState[sid.Server] = newState
|
||||||
|
case ssParentRequested:
|
||||||
|
s.parentSlot = resp.(RespHeader).Header.Slot
|
||||||
|
s.hasEpochInfo, s.epochBoundary = true, s.cpSlot/params.EpochLength > s.parentSlot/params.EpochLength
|
||||||
|
newState := s.serverState[sid.Server]
|
||||||
|
newState.state = ssPrintStatus
|
||||||
|
s.serverState[sid.Server] = newState
|
||||||
|
}
|
||||||
|
|
||||||
|
case request.EvUnregistered:
|
||||||
|
delete(s.serverState, event.Server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start a request if possible
|
||||||
|
for _, server := range requester.CanSendTo() {
|
||||||
|
switch s.serverState[server].state {
|
||||||
|
case ssDefault:
|
||||||
|
if s.locked == (request.ServerAndID{}) {
|
||||||
|
id := requester.Send(server, ReqCheckpointData(s.checkpointHash))
|
||||||
|
s.locked = request.ServerAndID{Server: server, ID: id}
|
||||||
|
}
|
||||||
|
case ssNeedHeader:
|
||||||
|
requester.Send(server, ReqHeader(s.checkpointHash))
|
||||||
|
newState := s.serverState[server]
|
||||||
|
newState.state = ssHeaderRequested
|
||||||
|
s.serverState[server] = newState
|
||||||
|
case ssNeedParent:
|
||||||
|
requester.Send(server, ReqHeader(s.parentHash))
|
||||||
|
newState := s.serverState[server]
|
||||||
|
newState.state = ssParentRequested
|
||||||
|
s.serverState[server] = newState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print log message if necessary
|
||||||
|
for server, state := range s.serverState {
|
||||||
|
if state.state != ssPrintStatus {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case !state.hasHeader:
|
||||||
|
log.Error("blsync: checkpoint block is not available, reported as unknown", "server", server.Name())
|
||||||
|
case !state.canonical:
|
||||||
|
log.Error("blsync: checkpoint block is not available, reported as non-canonical", "server", server.Name())
|
||||||
|
case !s.hasEpochInfo:
|
||||||
|
// should be available if hasHeader is true and state is ssPrintStatus
|
||||||
|
panic("checkpoint epoch info not available when printing retrieval status")
|
||||||
|
case !s.epochBoundary:
|
||||||
|
log.Error("blsync: checkpoint block is not first of epoch", "slot", s.cpSlot, "parent", s.parentSlot, "server", server.Name())
|
||||||
|
case !state.finalized:
|
||||||
|
log.Error("blsync: checkpoint block is reported as non-finalized", "server", server.Name())
|
||||||
|
default:
|
||||||
|
log.Error("blsync: checkpoint not available, but reported as finalized; specified checkpoint hash might be too old", "server", server.Name())
|
||||||
|
}
|
||||||
|
s.serverState[server] = serverState{state: ssDone}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForwardUpdateSync implements request.Module; it fetches updates between the
|
||||||
|
// committee chain head and each server's announced head. Updates are fetched
|
||||||
|
// in batches and multiple batches can also be requested in parallel.
|
||||||
|
// Out of order responses are also handled; if a batch of updates cannot be added
|
||||||
|
// to the chain immediately because of a gap then the future updates are
|
||||||
|
// remembered until they can be processed.
|
||||||
|
type ForwardUpdateSync struct {
|
||||||
|
chain committeeChain
|
||||||
|
rangeLock rangeLock
|
||||||
|
lockedIDs map[request.ServerAndID]struct{}
|
||||||
|
processQueue []updateResponse
|
||||||
|
nextSyncPeriod map[request.Server]uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewForwardUpdateSync creates a new ForwardUpdateSync.
|
||||||
|
func NewForwardUpdateSync(chain committeeChain) *ForwardUpdateSync {
|
||||||
|
return &ForwardUpdateSync{
|
||||||
|
chain: chain,
|
||||||
|
rangeLock: make(rangeLock),
|
||||||
|
lockedIDs: make(map[request.ServerAndID]struct{}),
|
||||||
|
nextSyncPeriod: make(map[request.Server]uint64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rangeLock allows locking sections of an integer space, preventing the syncing
|
||||||
|
// mechanism from making requests again for sections where a not timed out request
|
||||||
|
// is already pending or where already fetched and unprocessed data is available.
|
||||||
|
type rangeLock map[uint64]int
|
||||||
|
|
||||||
|
// lock locks or unlocks the given section, depending on the sign of the add parameter.
|
||||||
|
func (r rangeLock) lock(first, count uint64, add int) {
|
||||||
|
for i := first; i < first+count; i++ {
|
||||||
|
if v := r[i] + add; v > 0 {
|
||||||
|
r[i] = v
|
||||||
|
} else {
|
||||||
|
delete(r, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// firstUnlocked returns the first unlocked section starting at or after start
|
||||||
|
// and not longer than maxCount.
|
||||||
|
func (r rangeLock) firstUnlocked(start, maxCount uint64) (first, count uint64) {
|
||||||
|
first = start
|
||||||
|
for {
|
||||||
|
if _, ok := r[first]; !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
first++
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
count++
|
||||||
|
if count == maxCount {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, ok := r[first+count]; ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// lockRange locks the range belonging to the given update request, unless the
|
||||||
|
// same request has already been locked
|
||||||
|
func (s *ForwardUpdateSync) lockRange(sid request.ServerAndID, req ReqUpdates) {
|
||||||
|
if _, ok := s.lockedIDs[sid]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.lockedIDs[sid] = struct{}{}
|
||||||
|
s.rangeLock.lock(req.FirstPeriod, req.Count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlockRange unlocks the range belonging to the given update request, unless
|
||||||
|
// same request has already been unlocked
|
||||||
|
func (s *ForwardUpdateSync) unlockRange(sid request.ServerAndID, req ReqUpdates) {
|
||||||
|
if _, ok := s.lockedIDs[sid]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(s.lockedIDs, sid)
|
||||||
|
s.rangeLock.lock(req.FirstPeriod, req.Count, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyRange returns true if the number of updates and the individual update
|
||||||
|
// periods in the response match the requested section.
|
||||||
|
func (s *ForwardUpdateSync) verifyRange(request ReqUpdates, response RespUpdates) bool {
|
||||||
|
if uint64(len(response.Updates)) != request.Count || uint64(len(response.Committees)) != request.Count {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, update := range response.Updates {
|
||||||
|
if update.AttestedHeader.Header.SyncPeriod() != request.FirstPeriod+uint64(i) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateResponse is a response that has passed initial verification and has been
|
||||||
|
// queued for processing. Note that an update response cannot be processed until
|
||||||
|
// the previous updates have also been added to the chain.
|
||||||
|
type updateResponse struct {
|
||||||
|
sid request.ServerAndID
|
||||||
|
request ReqUpdates
|
||||||
|
response RespUpdates
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateResponseList implements sort.Sort and sorts update request/response events by FirstPeriod.
|
||||||
|
type updateResponseList []updateResponse
|
||||||
|
|
||||||
|
func (u updateResponseList) Len() int { return len(u) }
|
||||||
|
func (u updateResponseList) Swap(i, j int) { u[i], u[j] = u[j], u[i] }
|
||||||
|
func (u updateResponseList) Less(i, j int) bool {
|
||||||
|
return u[i].request.FirstPeriod < u[j].request.FirstPeriod
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process implements request.Module.
|
||||||
|
func (s *ForwardUpdateSync) Process(requester request.Requester, events []request.Event) {
|
||||||
|
for _, event := range events {
|
||||||
|
switch event.Type {
|
||||||
|
case request.EvResponse, request.EvFail, request.EvTimeout:
|
||||||
|
sid, rq, rs := event.RequestInfo()
|
||||||
|
req := rq.(ReqUpdates)
|
||||||
|
var queued bool
|
||||||
|
if event.Type == request.EvResponse {
|
||||||
|
resp := rs.(RespUpdates)
|
||||||
|
if s.verifyRange(req, resp) {
|
||||||
|
// there is a response with a valid format; put it in the process queue
|
||||||
|
s.processQueue = append(s.processQueue, updateResponse{sid: sid, request: req, response: resp})
|
||||||
|
s.lockRange(sid, req)
|
||||||
|
queued = true
|
||||||
|
} else {
|
||||||
|
requester.Fail(event.Server, "invalid update range")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !queued {
|
||||||
|
s.unlockRange(sid, req)
|
||||||
|
}
|
||||||
|
case EvNewOptimisticUpdate:
|
||||||
|
update := event.Data.(types.OptimisticUpdate)
|
||||||
|
s.nextSyncPeriod[event.Server] = types.SyncPeriod(update.SignatureSlot + 256)
|
||||||
|
case request.EvUnregistered:
|
||||||
|
delete(s.nextSyncPeriod, event.Server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try processing ordered list of available responses
|
||||||
|
sort.Sort(updateResponseList(s.processQueue))
|
||||||
|
for s.processQueue != nil {
|
||||||
|
u := s.processQueue[0]
|
||||||
|
if !s.processResponse(requester, u) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s.unlockRange(u.sid, u.request)
|
||||||
|
s.processQueue = s.processQueue[1:]
|
||||||
|
if len(s.processQueue) == 0 {
|
||||||
|
s.processQueue = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start new requests if possible
|
||||||
|
startPeriod, chainInit := s.chain.NextSyncPeriod()
|
||||||
|
if !chainInit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
firstPeriod, maxCount := s.rangeLock.firstUnlocked(startPeriod, maxUpdateRequest)
|
||||||
|
var (
|
||||||
|
sendTo request.Server
|
||||||
|
bestCount uint64
|
||||||
|
)
|
||||||
|
for _, server := range requester.CanSendTo() {
|
||||||
|
nextPeriod := s.nextSyncPeriod[server]
|
||||||
|
if nextPeriod <= firstPeriod {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
count := maxCount
|
||||||
|
if nextPeriod < firstPeriod+maxCount {
|
||||||
|
count = nextPeriod - firstPeriod
|
||||||
|
}
|
||||||
|
if count > bestCount {
|
||||||
|
sendTo, bestCount = server, count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sendTo == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req := ReqUpdates{FirstPeriod: firstPeriod, Count: bestCount}
|
||||||
|
id := requester.Send(sendTo, req)
|
||||||
|
s.lockRange(request.ServerAndID{Server: sendTo, ID: id}, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processResponse adds the fetched updates and committees to the committee chain.
|
||||||
|
// Returns true in case of full or partial success.
|
||||||
|
func (s *ForwardUpdateSync) processResponse(requester request.Requester, u updateResponse) (success bool) {
|
||||||
|
for i, update := range u.response.Updates {
|
||||||
|
if err := s.chain.InsertUpdate(update, u.response.Committees[i]); err != nil {
|
||||||
|
if err == light.ErrInvalidPeriod {
|
||||||
|
// there is a gap in the update periods; stop processing without
|
||||||
|
// failing and try again next time
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == light.ErrInvalidUpdate || err == light.ErrWrongCommitteeRoot || err == light.ErrCannotReorg {
|
||||||
|
requester.Fail(u.sid.Server, "invalid update received")
|
||||||
|
} else {
|
||||||
|
log.Error("Unexpected InsertUpdate error", "error", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
success = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
219
beacon/light/sync/update_sync_test.go
Normal file
219
beacon/light/sync/update_sync_test.go
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckpointInit(t *testing.T) {
|
||||||
|
chain := &TestCommitteeChain{}
|
||||||
|
checkpoint := &types.BootstrapData{Header: types.Header{Slot: 0x2000*4 + 0x1000}} // period 4
|
||||||
|
checkpointHash := checkpoint.Header.Hash()
|
||||||
|
chkInit := NewCheckpointInit(chain, checkpointHash)
|
||||||
|
ts := NewTestScheduler(t, chkInit)
|
||||||
|
// add 2 servers
|
||||||
|
ts.AddServer(testServer1, 1)
|
||||||
|
ts.AddServer(testServer2, 1)
|
||||||
|
|
||||||
|
// expect bootstrap request to server 1
|
||||||
|
ts.Run(1, testServer1, ReqCheckpointData(checkpointHash))
|
||||||
|
|
||||||
|
// server 1 times out; expect request to server 2
|
||||||
|
ts.RequestEvent(request.EvTimeout, ts.Request(1, 1), nil)
|
||||||
|
ts.Run(2, testServer2, ReqCheckpointData(checkpointHash))
|
||||||
|
|
||||||
|
// invalid response from server 2; expect init state to still be false
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(2, 1), &types.BootstrapData{Header: types.Header{Slot: 123456}})
|
||||||
|
ts.ExpFail(testServer2)
|
||||||
|
ts.Run(3)
|
||||||
|
chain.ExpInit(t, false)
|
||||||
|
|
||||||
|
// server 1 fails (hard timeout)
|
||||||
|
ts.RequestEvent(request.EvFail, ts.Request(1, 1), nil)
|
||||||
|
ts.Run(4)
|
||||||
|
chain.ExpInit(t, false)
|
||||||
|
|
||||||
|
// server 3 is registered; expect bootstrap request to server 3
|
||||||
|
ts.AddServer(testServer3, 1)
|
||||||
|
ts.Run(5, testServer3, ReqCheckpointData(checkpointHash))
|
||||||
|
|
||||||
|
// valid response from server 3; expect chain to be initialized
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(5, 1), checkpoint)
|
||||||
|
ts.Run(6)
|
||||||
|
chain.ExpInit(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateSyncParallel(t *testing.T) {
|
||||||
|
chain := &TestCommitteeChain{}
|
||||||
|
chain.SetNextSyncPeriod(0)
|
||||||
|
updateSync := NewForwardUpdateSync(chain)
|
||||||
|
ts := NewTestScheduler(t, updateSync)
|
||||||
|
// add 2 servers, head at period 100; allow 3-3 parallel requests for each
|
||||||
|
ts.AddServer(testServer1, 3)
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer1, types.OptimisticUpdate{SignatureSlot: 0x2000*100 + 0x1000})
|
||||||
|
ts.AddServer(testServer2, 3)
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer2, types.OptimisticUpdate{SignatureSlot: 0x2000*100 + 0x1000})
|
||||||
|
|
||||||
|
// expect 6 requests to be sent
|
||||||
|
ts.Run(1,
|
||||||
|
testServer1, ReqUpdates{FirstPeriod: 0, Count: 8},
|
||||||
|
testServer1, ReqUpdates{FirstPeriod: 8, Count: 8},
|
||||||
|
testServer1, ReqUpdates{FirstPeriod: 16, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 24, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 32, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 40, Count: 8})
|
||||||
|
|
||||||
|
// valid response to request 1; expect 8 periods synced and a new request started
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 1), testRespUpdate(ts.Request(1, 1)))
|
||||||
|
ts.AddAllowance(testServer1, 1)
|
||||||
|
ts.Run(2, testServer1, ReqUpdates{FirstPeriod: 48, Count: 8})
|
||||||
|
chain.ExpNextSyncPeriod(t, 8)
|
||||||
|
|
||||||
|
// valid response to requests 4 and 5
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 4), testRespUpdate(ts.Request(1, 4)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 5), testRespUpdate(ts.Request(1, 5)))
|
||||||
|
ts.AddAllowance(testServer2, 2)
|
||||||
|
// expect 2 more requests but no sync progress (responses 4 and 5 cannot be added before 2 and 3)
|
||||||
|
ts.Run(3,
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 56, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 64, Count: 8})
|
||||||
|
chain.ExpNextSyncPeriod(t, 8)
|
||||||
|
|
||||||
|
// soft timeout for requests 2 and 3 (server 1 is overloaded)
|
||||||
|
ts.RequestEvent(request.EvTimeout, ts.Request(1, 2), nil)
|
||||||
|
ts.RequestEvent(request.EvTimeout, ts.Request(1, 3), nil)
|
||||||
|
// no allowance, no more requests
|
||||||
|
ts.Run(4)
|
||||||
|
|
||||||
|
// valid response to requests 6 and 8 and 9
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 6), testRespUpdate(ts.Request(1, 6)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(3, 1), testRespUpdate(ts.Request(3, 1)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(3, 2), testRespUpdate(ts.Request(3, 2)))
|
||||||
|
ts.AddAllowance(testServer2, 3)
|
||||||
|
// server 2 can now resend requests 2 and 3 (timed out by server 1) and also send a new one
|
||||||
|
ts.Run(5,
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 8, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 16, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 72, Count: 8})
|
||||||
|
|
||||||
|
// server 1 finally answers timed out request 2
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 2), testRespUpdate(ts.Request(1, 2)))
|
||||||
|
ts.AddAllowance(testServer1, 1)
|
||||||
|
// expect sync progress and one new request
|
||||||
|
ts.Run(6, testServer1, ReqUpdates{FirstPeriod: 80, Count: 8})
|
||||||
|
chain.ExpNextSyncPeriod(t, 16)
|
||||||
|
|
||||||
|
// server 2 answers requests 11 and 12 (resends of requests 2 and 3)
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(5, 1), testRespUpdate(ts.Request(5, 1)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(5, 2), testRespUpdate(ts.Request(5, 2)))
|
||||||
|
ts.AddAllowance(testServer2, 2)
|
||||||
|
ts.Run(7,
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 88, Count: 8},
|
||||||
|
testServer2, ReqUpdates{FirstPeriod: 96, Count: 4})
|
||||||
|
// finally the gap is filled, update can process responses up to req6
|
||||||
|
chain.ExpNextSyncPeriod(t, 48)
|
||||||
|
|
||||||
|
// all remaining requests are answered
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 3), testRespUpdate(ts.Request(1, 3)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(2, 1), testRespUpdate(ts.Request(2, 1)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(5, 3), testRespUpdate(ts.Request(5, 3)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(6, 1), testRespUpdate(ts.Request(6, 1)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(7, 1), testRespUpdate(ts.Request(7, 1)))
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(7, 2), testRespUpdate(ts.Request(7, 2)))
|
||||||
|
ts.Run(8)
|
||||||
|
// expect chain to be fully synced
|
||||||
|
chain.ExpNextSyncPeriod(t, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateSyncDifferentHeads(t *testing.T) {
|
||||||
|
chain := &TestCommitteeChain{}
|
||||||
|
chain.SetNextSyncPeriod(10)
|
||||||
|
updateSync := NewForwardUpdateSync(chain)
|
||||||
|
ts := NewTestScheduler(t, updateSync)
|
||||||
|
// add 3 servers with different announced head periods
|
||||||
|
ts.AddServer(testServer1, 1)
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer1, types.OptimisticUpdate{SignatureSlot: 0x2000*15 + 0x1000})
|
||||||
|
ts.AddServer(testServer2, 1)
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer2, types.OptimisticUpdate{SignatureSlot: 0x2000*16 + 0x1000})
|
||||||
|
ts.AddServer(testServer3, 1)
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer3, types.OptimisticUpdate{SignatureSlot: 0x2000*17 + 0x1000})
|
||||||
|
|
||||||
|
// expect request to the best announced head
|
||||||
|
ts.Run(1, testServer3, ReqUpdates{FirstPeriod: 10, Count: 7})
|
||||||
|
|
||||||
|
// request times out, expect request to the next best head
|
||||||
|
ts.RequestEvent(request.EvTimeout, ts.Request(1, 1), nil)
|
||||||
|
ts.Run(2, testServer2, ReqUpdates{FirstPeriod: 10, Count: 6})
|
||||||
|
|
||||||
|
// request times out, expect request to the last available server
|
||||||
|
ts.RequestEvent(request.EvTimeout, ts.Request(2, 1), nil)
|
||||||
|
ts.Run(3, testServer1, ReqUpdates{FirstPeriod: 10, Count: 5})
|
||||||
|
|
||||||
|
// valid response to request 3, expect chain synced to period 15
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(3, 1), testRespUpdate(ts.Request(3, 1)))
|
||||||
|
ts.AddAllowance(testServer1, 1)
|
||||||
|
ts.Run(4)
|
||||||
|
chain.ExpNextSyncPeriod(t, 15)
|
||||||
|
|
||||||
|
// invalid response to request 1, server can only deliver updates up to period 15 despite announced head
|
||||||
|
truncated := ts.Request(1, 1)
|
||||||
|
truncated.request = ReqUpdates{FirstPeriod: 10, Count: 5}
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(1, 1), testRespUpdate(truncated))
|
||||||
|
ts.ExpFail(testServer3)
|
||||||
|
ts.Run(5)
|
||||||
|
// expect no progress of chain head
|
||||||
|
chain.ExpNextSyncPeriod(t, 15)
|
||||||
|
|
||||||
|
// valid response to request 2, expect chain synced to period 16
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(2, 1), testRespUpdate(ts.Request(2, 1)))
|
||||||
|
ts.AddAllowance(testServer2, 1)
|
||||||
|
ts.Run(6)
|
||||||
|
chain.ExpNextSyncPeriod(t, 16)
|
||||||
|
|
||||||
|
// a new server is registered with announced head period 17
|
||||||
|
ts.AddServer(testServer4, 1)
|
||||||
|
ts.ServerEvent(EvNewOptimisticUpdate, testServer4, types.OptimisticUpdate{SignatureSlot: 0x2000*17 + 0x1000})
|
||||||
|
// expect request to sync one more period
|
||||||
|
ts.Run(7, testServer4, ReqUpdates{FirstPeriod: 16, Count: 1})
|
||||||
|
|
||||||
|
// valid response, expect chain synced to period 17
|
||||||
|
ts.RequestEvent(request.EvResponse, ts.Request(7, 1), testRespUpdate(ts.Request(7, 1)))
|
||||||
|
ts.AddAllowance(testServer4, 1)
|
||||||
|
ts.Run(8)
|
||||||
|
chain.ExpNextSyncPeriod(t, 17)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRespUpdate(request requestWithID) request.Response {
|
||||||
|
var resp RespUpdates
|
||||||
|
if request.request == nil {
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
req := request.request.(ReqUpdates)
|
||||||
|
resp.Updates = make([]*types.LightClientUpdate, int(req.Count))
|
||||||
|
resp.Committees = make([]*types.SerializedSyncCommittee, int(req.Count))
|
||||||
|
period := req.FirstPeriod
|
||||||
|
for i := range resp.Updates {
|
||||||
|
resp.Updates[i] = &types.LightClientUpdate{AttestedHeader: types.SignedHeader{Header: types.Header{Slot: 0x2000*period + 0x1000}}}
|
||||||
|
resp.Committees[i] = new(types.SerializedSyncCommittee)
|
||||||
|
period++
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user