Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c459a13dca | ||
|
|
b409c8f2be | ||
|
|
f69cce4089 | ||
|
|
cda0326cc3 | ||
|
|
3bdcae9a59 | ||
|
|
1163d36b02 | ||
|
|
d764d86e89 | ||
|
|
3921623861 | ||
|
|
22c684deb6 | ||
|
|
df801470ca | ||
|
|
01685c44e4 | ||
|
|
2c895e8df2 | ||
|
|
54c0ae17f4 | ||
|
|
d254fb228b | ||
|
|
9b1d2f4c13 | ||
|
|
fd2262f51f | ||
|
|
ee6269756c | ||
|
|
7ff468ce85 | ||
|
|
c4de2373fd | ||
|
|
18d1ab0cb9 | ||
|
|
6e7f21de03 | ||
|
|
c3cc2eea46 | ||
|
|
7bfcff2c18 | ||
|
|
f2f797f088 | ||
|
|
033cfa8f47 | ||
|
|
0cfddae711 | ||
|
|
a87899ddb6 | ||
|
|
bbd4ffe842 | ||
|
|
605560a981 | ||
|
|
b4c9ba0a8b | ||
|
|
9a53bf8f83 | ||
|
|
41f4f1fd83 | ||
|
|
6b9fd74f8e | ||
|
|
ccba495840 | ||
|
|
21ffe188f4 | ||
|
|
5f1cc6cefd | ||
|
|
8d03a9e739 | ||
|
|
17642b5ecb | ||
|
|
62a503d686 | ||
|
|
bb49546728 | ||
|
|
3f017688c7 | ||
|
|
bb07c0a5df | ||
|
|
7ec58255bf | ||
|
|
1a5c20ca75 | ||
|
|
c9cb39d5a0 | ||
|
|
858513559b | ||
|
|
12b63d59b2 | ||
|
|
2fd860324f | ||
|
|
f9d8b051f4 | ||
|
|
1e2e62bad6 | ||
|
|
e13a251cc8 | ||
|
|
39d609a661 | ||
|
|
5530a7bef2 | ||
|
|
bc23b0ed4f | ||
|
|
dc9df10f5a | ||
|
|
69125f15f9 | ||
|
|
3f0312a728 | ||
|
|
ebd0e93a99 | ||
|
|
3332290626 | ||
|
|
0dbca1377a | ||
|
|
b245fd0b7d | ||
|
|
e81f182840 | ||
|
|
a3602d4fcf | ||
|
|
bf6b76a4fb | ||
|
|
2836b521f3 | ||
|
|
f01378f83f | ||
|
|
b0cf3d3228 | ||
|
|
57a1418ff8 | ||
|
|
a79b31c230 | ||
|
|
1081679360 | ||
|
|
b9effac437 | ||
|
|
8fbeebb86d | ||
|
|
24d8a84ad7 | ||
|
|
56bf503210 | ||
|
|
9a5b26c50c | ||
|
|
4952d4be77 | ||
|
|
68081a7a1f | ||
|
|
67dd5937ca | ||
|
|
ebecca64a1 | ||
|
|
0e7b7c9fdb | ||
|
|
d3d4612575 | ||
|
|
1254f9de0c | ||
|
|
d78b7e57c7 | ||
|
|
509e3b653c | ||
|
|
631b8b5802 | ||
|
|
40286e4192 | ||
|
|
8e46753e38 | ||
|
|
74db060cdb | ||
|
|
0285a9741c | ||
|
|
53b6de6c4b | ||
|
|
8645839974 | ||
|
|
683aded05c | ||
|
|
20c80f977c | ||
|
|
eac8a909e9 | ||
|
|
99b5ec96a2 | ||
|
|
74aa3e05fa | ||
|
|
66e547230d | ||
|
|
04bd56a068 | ||
|
|
e1c29ca469 | ||
|
|
3c6e46cd70 | ||
|
|
27b5a8c3ba | ||
|
|
49800b8fec | ||
|
|
cf6ac44b6e | ||
|
|
50c773c6a3 | ||
|
|
41d3ac2159 | ||
|
|
18f7b9bb1d | ||
|
|
ccf64a8534 | ||
|
|
42bd4eccbe | ||
|
|
0964c0a8a1 | ||
|
|
874cb46cb3 | ||
|
|
ddba20ccdf | ||
|
|
516e31d37c | ||
|
|
5836dfae41 | ||
|
|
3bb992fe6f | ||
|
|
2f3fc31f11 | ||
|
|
4e12c4cb69 | ||
|
|
79e93760b7 | ||
|
|
6c9beb9e87 | ||
|
|
1d0acf35d2 | ||
|
|
ea5dc461a1 | ||
|
|
e5fdae45b7 | ||
|
|
db50396eb5 | ||
|
|
ff1cceb7bd | ||
|
|
d3a007fd0a | ||
|
|
4e0cf1644f | ||
|
|
fe49bcd9fa | ||
|
|
e4d8f266c6 | ||
|
|
faab606bd4 | ||
|
|
978ab67c73 | ||
|
|
4679c35e61 | ||
|
|
16e493a67a | ||
|
|
8b88c17503 | ||
|
|
d6d68b9096 | ||
|
|
d3a1261f1e | ||
|
|
19178cca08 | ||
|
|
3e7654d459 | ||
|
|
bb3758f648 | ||
|
|
9d56dabb31 | ||
|
|
c3393ee185 | ||
|
|
17039a5672 | ||
|
|
809a3ba07c | ||
|
|
5de7b62dbc | ||
|
|
5fb76ddad2 | ||
|
|
cc02092aae | ||
|
|
3e4b80e7f6 | ||
|
|
14ae8c7272 | ||
|
|
2a050ca326 | ||
|
|
567b8082c3 | ||
|
|
f3f92ce5bc | ||
|
|
c4158f12f1 | ||
|
|
3429941639 | ||
|
|
11dc902d53 | ||
|
|
41b775fdd4 | ||
|
|
e9d2a3edd9 | ||
|
|
42696372ee | ||
|
|
39625eada7 | ||
|
|
7a7e5a85ad | ||
|
|
254295b9c3 | ||
|
|
4dd79ec6fa | ||
|
|
9ccd25b072 | ||
|
|
5e7f561133 | ||
|
|
c68a8f802e | ||
|
|
29f5f451b3 | ||
|
|
2caa7164a6 | ||
|
|
21148033e2 | ||
|
|
bc88c632d1 | ||
|
|
1e17ea3fff | ||
|
|
ca7e5f5e16 | ||
|
|
cd2595da67 | ||
|
|
3a9f1bf9a2 | ||
|
|
9094405537 | ||
|
|
4db61e71da | ||
|
|
a6417dc24b | ||
|
|
1bc9fbca20 | ||
|
|
369ec8221f | ||
|
|
052fc35a91 | ||
|
|
b8ab549bcb | ||
|
|
4f60c1afe7 | ||
|
|
9cd34509ee | ||
|
|
a2fd0aad41 | ||
|
|
01ac4e5da3 | ||
|
|
01e230e810 | ||
|
|
78ee64d9f8 | ||
|
|
e5a7cb635c | ||
|
|
49ddcdb4b0 | ||
|
|
b5ae3c055b | ||
|
|
9712bbc854 | ||
|
|
3134fcb54a | ||
|
|
87d668390e | ||
|
|
710286d6e7 | ||
|
|
04edeccbf4 | ||
|
|
db5929f4a5 | ||
|
|
e65215f1b5 | ||
|
|
c97720e731 | ||
|
|
3aaa4fb692 | ||
|
|
7e1e9bcd60 | ||
|
|
8c347d8764 | ||
|
|
f7bffdf63a | ||
|
|
bda43a4627 | ||
|
|
19195ddf96 | ||
|
|
5fa3021a79 | ||
|
|
a5935486ef | ||
|
|
ffd012906c | ||
|
|
f8a1fb2321 | ||
|
|
605e9bbbd7 | ||
|
|
168b0f8df6 | ||
|
|
70d8378404 | ||
|
|
00e38ce126 | ||
|
|
02c3f8ed80 | ||
|
|
e949a458ec | ||
|
|
228ff3ba57 | ||
|
|
4c1dfd7eda | ||
|
|
e1799f1524 | ||
|
|
8f0e04027e | ||
|
|
abdafc8ee4 | ||
|
|
9030b559e8 | ||
|
|
cd34d02377 | ||
|
|
a362ef97df | ||
|
|
92f392d422 | ||
|
|
e8ddaab1c3 | ||
|
|
da78ff9151 | ||
|
|
e576c7a08e | ||
|
|
2d180cdacf | ||
|
|
77c7eb6fd9 | ||
|
|
857b5962da | ||
|
|
8c54c09ab7 | ||
|
|
6927a532cf | ||
|
|
49dccf4284 | ||
|
|
a4751ddd01 | ||
|
|
c811133aac | ||
|
|
c16ea42fad | ||
|
|
05e521ab03 | ||
|
|
1ef490feea | ||
|
|
3b826e41f6 | ||
|
|
d232299c77 | ||
|
|
f774d2aab1 | ||
|
|
642aed93d9 | ||
|
|
4f9c907248 | ||
|
|
612b2fe9d7 | ||
|
|
0fddf3e114 | ||
|
|
c3898e4abf | ||
|
|
726c09618a | ||
|
|
12f10ac414 | ||
|
|
77b3bf8bc2 | ||
|
|
a0e1d5f567 | ||
|
|
4e32c7edb3 | ||
|
|
c72def55fe | ||
|
|
982962ef0a | ||
|
|
90a470ed78 | ||
|
|
048d0e0d7a | ||
|
|
f812cac142 | ||
|
|
aeab3a709a | ||
|
|
7058f4eb63 | ||
|
|
8d51a20143 | ||
|
|
a4f6269724 | ||
|
|
fbfe57cd63 | ||
|
|
0118a78967 | ||
|
|
659c723ea3 | ||
|
|
bbb9a9cd1a | ||
|
|
5052a87a82 | ||
|
|
b9180a99d4 | ||
|
|
db1773d708 | ||
|
|
a1b009328d | ||
|
|
393a9ca4f2 | ||
|
|
e910a045d0 | ||
|
|
5d9a775699 | ||
|
|
8233143578 | ||
|
|
950cb8bc03 | ||
|
|
eafb04004e | ||
|
|
3d3e384d5c | ||
|
|
38533631bf | ||
|
|
6fe39ef065 | ||
|
|
47c3e14d4b | ||
|
|
d8b647cc3a | ||
|
|
abad4a3941 | ||
|
|
2c0a4914cf | ||
|
|
e54b49b442 | ||
|
|
50aa1e6baa | ||
|
|
e3c897fb68 | ||
|
|
728afc0587 | ||
|
|
95a9dc9db0 | ||
|
|
b62fef7541 | ||
|
|
dd490220d7 | ||
|
|
7f932b96ae | ||
|
|
4af9a20903 | ||
|
|
f9cb549fbe | ||
|
|
7e342e60d1 | ||
|
|
f98063d604 | ||
|
|
10c4fae8c0 | ||
|
|
cae24a226b | ||
|
|
0162322833 | ||
|
|
06dfe3c5ae | ||
|
|
a98c4038f4 | ||
|
|
4c760ec1b1 | ||
|
|
176d0bb7c0 | ||
|
|
bc90b857c9 | ||
|
|
ef0abf2b98 | ||
|
|
008a2969b2 | ||
|
|
7d9f9ba756 | ||
|
|
358b36bc66 | ||
|
|
f1e686f706 | ||
|
|
0b1fa54a4d | ||
|
|
49281ef599 | ||
|
|
24c7f47566 | ||
|
|
4037d707c9 | ||
|
|
001d4ab002 | ||
|
|
f115603093 | ||
|
|
d7bb284e57 | ||
|
|
11c53c26ff | ||
|
|
d28bbf4674 | ||
|
|
a7d662a7d8 | ||
|
|
6f8c464393 | ||
|
|
c8104a03e6 | ||
|
|
69559bcf4f | ||
|
|
d524821c86 | ||
|
|
e864244226 | ||
|
|
5074cf8beb | ||
|
|
b448de422e | ||
|
|
99d3571772 | ||
|
|
0354275b39 | ||
|
|
309fbc15b2 | ||
|
|
28e3c863bf | ||
|
|
06162290af | ||
|
|
628872d0e4 | ||
|
|
960523f45f | ||
|
|
fb04b17fad | ||
|
|
8adc831a31 | ||
|
|
566a99240d | ||
|
|
7b2241f051 | ||
|
|
55ed45f108 | ||
|
|
f8e7901b89 | ||
|
|
9e70e95242 | ||
|
|
68d38eff33 | ||
|
|
abd400801c | ||
|
|
d24a750ba4 | ||
|
|
9903eba7fb | ||
|
|
3752311f44 | ||
|
|
b71f771acb | ||
|
|
9f3145aec0 | ||
|
|
8638076ecb | ||
|
|
8649baccf7 | ||
|
|
c33d9e9668 | ||
|
|
03d70e1513 | ||
|
|
d20255b1b4 | ||
|
|
cd91e42a56 | ||
|
|
e2435be0b9 | ||
|
|
5adc188466 | ||
|
|
101ba0b796 | ||
|
|
8cd2012d27 | ||
|
|
4959596527 | ||
|
|
956c397c3c | ||
|
|
1249ede2b7 | ||
|
|
596ed330dd | ||
|
|
b4c1594d99 | ||
|
|
98cb08d853 | ||
|
|
6b86dcde4a | ||
|
|
fbeaab466a | ||
|
|
5e77b5da9f | ||
|
|
7c4225d01c | ||
|
|
ffa7be86fe | ||
|
|
c41c210ced | ||
|
|
de7d6c255c | ||
|
|
99060fbb64 | ||
|
|
0c8d648998 | ||
|
|
aa18d1985e | ||
|
|
b0edf924b4 | ||
|
|
997a46a90f | ||
|
|
b99b8f55f6 | ||
|
|
c0a50a2143 | ||
|
|
382734b057 | ||
|
|
90fbbea372 | ||
|
|
55d9ad0b55 | ||
|
|
e5e5d0d9e4 | ||
|
|
324b659d33 | ||
|
|
edcf899be1 | ||
|
|
2bf781d591 | ||
|
|
cc179c5270 | ||
|
|
b417725e3e | ||
|
|
c02d8225a8 | ||
|
|
659f570cfc | ||
|
|
c7e470bd5e | ||
|
|
fe4efa0e0e | ||
|
|
02234d00b7 | ||
|
|
bef5bfcbb6 | ||
|
|
290512ee68 | ||
|
|
d129d6eada | ||
|
|
5c5a3f2264 | ||
|
|
d4644a28c1 | ||
|
|
d66c70d1e6 | ||
|
|
b91e7a9860 | ||
|
|
fcdfda8ba2 | ||
|
|
2800fd919b | ||
|
|
512bbb99ca | ||
|
|
ed5b74b1ae | ||
|
|
791dff5a87 | ||
|
|
402cd8be19 | ||
|
|
bdcb1e3592 | ||
|
|
e60dd219dc | ||
|
|
9d7be5dcbf | ||
|
|
70e964d9f9 | ||
|
|
673a643184 | ||
|
|
2761872ea7 | ||
|
|
fc7195f19a | ||
|
|
8b8bb5e833 | ||
|
|
5d4f1bb3f1 | ||
|
|
139c3297ab | ||
|
|
20503de3ba | ||
|
|
2676892886 | ||
|
|
5acf5bf102 | ||
|
|
ec693008d7 | ||
|
|
4ba1846c11 | ||
|
|
5eceed81e1 | ||
|
|
8b725b4b8c | ||
|
|
6253c39100 | ||
|
|
b845013836 | ||
|
|
9690bc9c63 | ||
|
|
c6a8fe16c6 | ||
|
|
ec9a334f93 | ||
|
|
35d9de6b6e | ||
|
|
4d0df5d599 | ||
|
|
7fd082e941 | ||
|
|
9d17e0be9d | ||
|
|
030472d61c | ||
|
|
6bff52483b | ||
|
|
32d8ca2f21 | ||
|
|
fd6c8769dd | ||
|
|
bac6a47c67 | ||
|
|
18dd1c277b | ||
|
|
ab4c11c096 | ||
|
|
6bbea74a7d | ||
|
|
4f1efbe38f | ||
|
|
eeb5053919 | ||
|
|
d8a517f992 | ||
|
|
aa513d920e | ||
|
|
3327ed50a8 | ||
|
|
f31fcdae08 | ||
|
|
66b15b7076 | ||
|
|
c5a9f180af | ||
|
|
a9abf3ceb2 | ||
|
|
ed3b2716af | ||
|
|
69948cd09c | ||
|
|
f8c4b64782 | ||
|
|
a422647819 | ||
|
|
9262661135 | ||
|
|
01c4726fd5 | ||
|
|
0230362d33 | ||
|
|
f8669d4e95 | ||
|
|
12298f8953 | ||
|
|
daca8651f4 | ||
|
|
8039701f9e | ||
|
|
01f0bb3124 | ||
|
|
58a79773ee | ||
|
|
c832c0bf3f | ||
|
|
907364ce67 | ||
|
|
18cf7b046e | ||
|
|
87e5c38e32 | ||
|
|
c98972ab23 | ||
|
|
2a78ebabd9 | ||
|
|
a23a87a17f | ||
|
|
49b1acba43 | ||
|
|
464f6a6b08 | ||
|
|
7e7fac05e0 | ||
|
|
7296ce7287 | ||
|
|
70c8f7b1ee | ||
|
|
50b04d6b2b | ||
|
|
12056c8904 | ||
|
|
fdbda6f608 | ||
|
|
c822816f8e | ||
|
|
6d36c030e0 | ||
|
|
82c6be14f4 | ||
|
|
fb4ad043dd | ||
|
|
91ea039077 | ||
|
|
8e025db656 | ||
|
|
4e9293ba04 | ||
|
|
19afb57ac7 | ||
|
|
a032bbe087 | ||
|
|
a2943a6b7f | ||
|
|
8ad2b3c3d9 | ||
|
|
cd69e57a33 | ||
|
|
ca8a00f8a1 | ||
|
|
9077dea0cd | ||
|
|
d670257402 | ||
|
|
3460fb0d47 | ||
|
|
82eb7786d4 | ||
|
|
00385acc1d | ||
|
|
813940762c | ||
|
|
bd25d6db06 | ||
|
|
9fbc7aafb4 | ||
|
|
227819dc1e | ||
|
|
2c401f9041 | ||
|
|
c7e3a5cff4 | ||
|
|
5469df7dd1 | ||
|
|
071ddf6563 | ||
|
|
36b327b57b | ||
|
|
7587e6c08b | ||
|
|
c90d35869b | ||
|
|
67110ce739 | ||
|
|
5421d0d1c5 | ||
|
|
c458b4712a | ||
|
|
0200e3def5 | ||
|
|
6acc9a62f8 | ||
|
|
d6b2519843 | ||
|
|
c8046f4597 | ||
|
|
1c583ab487 | ||
|
|
936f84dfa6 | ||
|
|
491f080a37 | ||
|
|
8354bd91db | ||
|
|
fb1d5197fa | ||
|
|
cf321f7667 | ||
|
|
2d6b2e3811 | ||
|
|
d4dbfdbe91 | ||
|
|
c9a845d3aa | ||
|
|
1e41ca125d | ||
|
|
500ddd6585 | ||
|
|
9457df9ddd | ||
|
|
c3f23ddce8 | ||
|
|
8b308972a4 | ||
|
|
4f632c893c | ||
|
|
41066bd45e | ||
|
|
ac1048fcd7 | ||
|
|
14ea847ef6 | ||
|
|
2ce3a5f887 | ||
|
|
27858506ff | ||
|
|
6ba5c5d0e1 | ||
|
|
dc112a5a00 | ||
|
|
fe60ffb6ff | ||
|
|
512270593b | ||
|
|
2cd6347a13 | ||
|
|
b1f66b181c | ||
|
|
d5dae844bd | ||
|
|
e0a8e993bc | ||
|
|
1aff04faab | ||
|
|
295c0861c4 | ||
|
|
ba3f73acb8 | ||
|
|
68959582b9 | ||
|
|
1f9b563412 | ||
|
|
99bc46b8b8 | ||
|
|
e2d2af3afa | ||
|
|
af4c4d4f1f | ||
|
|
6d06bfb452 | ||
|
|
159a1a4645 | ||
|
|
d4b230c137 | ||
|
|
6f501119bf | ||
|
|
a00df0da90 | ||
|
|
6d90f12bb6 | ||
|
|
469eef8231 | ||
|
|
1ccad22227 | ||
|
|
5cf76ba8b3 | ||
|
|
81bc8471e3 | ||
|
|
5feea2e0f8 | ||
|
|
79b35c59a5 | ||
|
|
35749f0330 | ||
|
|
2889efb608 | ||
|
|
686c543778 | ||
|
|
093dc907b3 | ||
|
|
1144049c66 | ||
|
|
6e11ff3796 | ||
|
|
19013b09e0 | ||
|
|
7539d88f54 | ||
|
|
28f895cec2 | ||
|
|
751bc45bef | ||
|
|
ae83ca6893 | ||
|
|
5f4efedbdd | ||
|
|
9d49c6861f | ||
|
|
3b56288a5e | ||
|
|
f257f4febb | ||
|
|
df867f3349 | ||
|
|
5270c20263 | ||
|
|
7762cd0a68 | ||
|
|
e91cc9cd08 | ||
|
|
741825a04e | ||
|
|
49b27da426 | ||
|
|
bf4bbd15e5 | ||
|
|
baa69c3c81 | ||
|
|
ac644605f2 | ||
|
|
94da339622 | ||
|
|
366359803a | ||
|
|
3b13397165 | ||
|
|
163c2b2544 | ||
|
|
9e2603c1bf | ||
|
|
d27750590a | ||
|
|
33a00ab9e5 | ||
|
|
f765fe36d7 | ||
|
|
be7a95c9a5 | ||
|
|
2571480bb6 | ||
|
|
73d928751d | ||
|
|
0c19544d94 | ||
|
|
a80dce2e3b | ||
|
|
fb09d1ad2b | ||
|
|
963e4dd2fc | ||
|
|
a5d81c6cc9 | ||
|
|
b167e73acb | ||
|
|
e7af464033 | ||
|
|
28e454fd4a | ||
|
|
55a9c9ae0a | ||
|
|
b3eef76cdd | ||
|
|
3f81ff3507 | ||
|
|
2347ba6f39 | ||
|
|
b9d3c26eae | ||
|
|
cd6670205c | ||
|
|
9f4ac4f4f3 | ||
|
|
e4bed4d963 | ||
|
|
76b5c8b145 | ||
|
|
9836c45e03 | ||
|
|
51f1712564 | ||
|
|
e9eff8be5c | ||
|
|
4855045ea2 | ||
|
|
6b06ae528c | ||
|
|
813961b458 | ||
|
|
7ae9d26cd1 | ||
|
|
9e5cc7c910 | ||
|
|
7d166fd730 | ||
|
|
60101fd97b | ||
|
|
0bc174697a | ||
|
|
7bee5b3abc | ||
|
|
656b285195 | ||
|
|
86083531c6 | ||
|
|
c01f70105f | ||
|
|
5aafa91e22 | ||
|
|
bada564c10 | ||
|
|
f9f0d86ddf | ||
|
|
6beabb5e8a | ||
|
|
3719938849 | ||
|
|
6420990007 | ||
|
|
1fab24b9bc | ||
|
|
8bbe63bd9d | ||
|
|
fa3b1552cf | ||
|
|
c625b30781 | ||
|
|
b339750757 | ||
|
|
da960a9de4 | ||
|
|
ce30162907 | ||
|
|
c0cdcbb9e0 | ||
|
|
954a20ff4c | ||
|
|
8057b994cf | ||
|
|
ea111b058e | ||
|
|
ce33074626 | ||
|
|
5f95a92135 | ||
|
|
cd817906b2 | ||
|
|
29dbc26dbd | ||
|
|
04f0130530 | ||
|
|
f210a6e91a | ||
|
|
aded0706be | ||
|
|
57567db017 | ||
|
|
5574e4f11b | ||
|
|
4b01b666f6 | ||
|
|
35740895ed | ||
|
|
4c5d84d106 | ||
|
|
a8915f0331 | ||
|
|
7f962e19c2 | ||
|
|
186fd4da74 | ||
|
|
2fdcf5347b | ||
|
|
4a983995a8 | ||
|
|
263bdc9687 | ||
|
|
42c76aaebc | ||
|
|
9326cb5411 | ||
|
|
415b69155b | ||
|
|
3423d29815 | ||
|
|
673a972fe9 | ||
|
|
1fd20b636b | ||
|
|
d7a2556292 | ||
|
|
8aa158d4e6 | ||
|
|
3595b1f8e9 | ||
|
|
9b867a221a | ||
|
|
46a016cdce | ||
|
|
bea9cd2fe2 | ||
|
|
ee9d9a52a9 | ||
|
|
1e8a875edb | ||
|
|
36a93c90eb | ||
|
|
38d714a28a | ||
|
|
bbb954afc5 | ||
|
|
de5d16eeac | ||
|
|
1968195da9 | ||
|
|
22851df145 | ||
|
|
96ade5f2dd | ||
|
|
5e727819ad | ||
|
|
5605aae269 | ||
|
|
c6b6c2990c | ||
|
|
a49dc90386 | ||
|
|
64f4e80446 | ||
|
|
e67253b6a8 | ||
|
|
c73326e96d | ||
|
|
7003960fdd | ||
|
|
0acc2a2a63 | ||
|
|
596f78f9e6 | ||
|
|
17f1eec81f | ||
|
|
f0d44ea396 | ||
|
|
ad4712b593 | ||
|
|
92b8d9ad17 | ||
|
|
12ff9fd76b | ||
|
|
e26cf4e003 | ||
|
|
b753cd4503 | ||
|
|
ee29a92021 | ||
|
|
c3916e3a7f | ||
|
|
ec0baefc48 | ||
|
|
bac1e3f901 | ||
|
|
94555ea8c1 | ||
|
|
cc233c6956 | ||
|
|
2a3d79f055 | ||
|
|
1c01799a4b | ||
|
|
4ef330ba8b | ||
|
|
cd2bf04a6b | ||
|
|
f7a71f4be7 | ||
|
|
e5c24ff32b | ||
|
|
f87054bde4 | ||
|
|
e954cc0d67 | ||
|
|
43c887f2b6 | ||
|
|
1cf9b9a67f | ||
|
|
7dd25c3ac5 | ||
|
|
241c3fa673 | ||
|
|
785eeb1eee | ||
|
|
30d82ba962 | ||
|
|
002d021eb7 | ||
|
|
71eb41b9aa | ||
|
|
f06369166e | ||
|
|
5129fed7c8 | ||
|
|
f06794ae84 | ||
|
|
1cabecedee | ||
|
|
f17c32c593 | ||
|
|
b59f88324c | ||
|
|
0c4db8cf6b | ||
|
|
9c020e54b5 | ||
|
|
9811375d18 | ||
|
|
2632621f7e | ||
|
|
d742efe165 | ||
|
|
492ffac913 | ||
|
|
21415a9847 | ||
|
|
4acf0c7ec0 | ||
|
|
fa69adf45c | ||
|
|
fa04da2995 | ||
|
|
e2a554c2e6 | ||
|
|
91da81c8bc | ||
|
|
7bd18290db | ||
|
|
8e55c91cd2 | ||
|
|
514339e855 | ||
|
|
9f8ecb0ac4 | ||
|
|
be1bbe88b0 | ||
|
|
f1ccbe496a | ||
|
|
495554e565 | ||
|
|
17c378c18a | ||
|
|
6e63ea6cce | ||
|
|
10753f14d0 | ||
|
|
0af7cd1448 | ||
|
|
de3edc2ec7 | ||
|
|
d0eb4278ae | ||
|
|
b807bdbea0 | ||
|
|
386b818d16 | ||
|
|
8639409f74 | ||
|
|
b372881e91 | ||
|
|
fc351d08b0 | ||
|
|
eb325e6814 | ||
|
|
7232e5481c | ||
|
|
f26a336afb | ||
|
|
70e690dac0 | ||
|
|
78e0c96f22 | ||
|
|
d6615b6286 | ||
|
|
4604b60fb1 | ||
|
|
75041b96e4 | ||
|
|
8544bda14a | ||
|
|
745fba7d1f | ||
|
|
6986204657 | ||
|
|
3f9cde28fc | ||
|
|
aa682a377d | ||
|
|
af1d54501f | ||
|
|
009cd92010 | ||
|
|
6386107b80 | ||
|
|
218b2b82c1 | ||
|
|
b06b0147a9 | ||
|
|
b74e8f5922 | ||
|
|
94a7ef1c10 | ||
|
|
02afd7d71f | ||
|
|
a10690359c | ||
|
|
fa4ff89dca | ||
|
|
bcd84cbe9d | ||
|
|
61daaa774b | ||
|
|
d5346f8326 | ||
|
|
8ce6921b18 | ||
|
|
c8f28103f3 | ||
|
|
3cd3785385 | ||
|
|
9089aadbbf | ||
|
|
02ca1937f8 | ||
|
|
6897110fe5 | ||
|
|
b799af3e9a | ||
|
|
9641d93615 | ||
|
|
da6768c94d | ||
|
|
54a8b88d6c | ||
|
|
ba6217cc2c | ||
|
|
314da26da3 | ||
|
|
646800b8a8 | ||
|
|
92d58ab191 | ||
|
|
5531f27798 | ||
|
|
cc98117df2 | ||
|
|
ffd340086e | ||
|
|
805686be2e | ||
|
|
0d3f602d58 | ||
|
|
83436842c5 | ||
|
|
bafb922ff7 | ||
|
|
6e024d9994 | ||
|
|
d597c3fb0d | ||
|
|
24cebb8b2f | ||
|
|
0cc8446a79 | ||
|
|
3e383d909a | ||
|
|
3264a9faad | ||
|
|
c52b75d59e | ||
|
|
652c257763 | ||
|
|
060f23d748 | ||
|
|
e68ab286f6 | ||
|
|
92e5cc5ad7 | ||
|
|
6831ca9e03 | ||
|
|
f7325a69c1 | ||
|
|
bb78292faf | ||
|
|
eeca031525 | ||
|
|
f82e0e61e1 | ||
|
|
75e47db520 | ||
|
|
3da1a0d8cc | ||
|
|
a293bd798f | ||
|
|
ae60001dcc | ||
|
|
cea7c41023 | ||
|
|
0fe7a0b2e3 | ||
|
|
267368d56c | ||
|
|
f5451ad455 | ||
|
|
555537c528 | ||
|
|
cc5875c6bd | ||
|
|
c54d050cb7 | ||
|
|
cb88237305 | ||
|
|
cc44167f2c | ||
|
|
78c656e4c1 | ||
|
|
607bcc401c | ||
|
|
7fd481ea6f | ||
|
|
9c91daf19a | ||
|
|
c1ef9d1731 | ||
|
|
eaf69b160c | ||
|
|
269286e587 | ||
|
|
f99c5416ce | ||
|
|
8f6c3c2cc7 | ||
|
|
a2e25897e9 | ||
|
|
fc9e046ca7 | ||
|
|
5730d46179 | ||
|
|
9165f14693 | ||
|
|
9305eca97c | ||
|
|
0c2e4e2d4f | ||
|
|
59725b3af3 | ||
|
|
a0ab3483ed | ||
|
|
e6f3b79b23 | ||
|
|
1ed6183344 | ||
|
|
29b1ac964b | ||
|
|
8d637c515a | ||
|
|
5220a61fc5 | ||
|
|
d9502693f0 | ||
|
|
9a0d0511c7 | ||
|
|
72eb32ae3c | ||
|
|
cf9e576028 | ||
|
|
9804bd3c4c | ||
|
|
30e2642f4d | ||
|
|
54b3eafd6d | ||
|
|
f6589407c1 | ||
|
|
5c73602d24 | ||
|
|
70657e35ba | ||
|
|
908053015b | ||
|
|
8bae22ef61 | ||
|
|
1012a64110 | ||
|
|
9eb2399539 | ||
|
|
d539be4875 | ||
|
|
13fc3f2f54 | ||
|
|
33ec4d332b | ||
|
|
d6bb0500df | ||
|
|
b90666b293 | ||
|
|
173a72f374 | ||
|
|
0ae0e89ddd | ||
|
|
c4d01c04a4 | ||
|
|
0b56ca3c21 | ||
|
|
022348f426 | ||
|
|
ad108d6051 | ||
|
|
dbe2a1e2cd | ||
|
|
f86a2ced06 | ||
|
|
f7a45f6c12 | ||
|
|
658f78d75f | ||
|
|
e9eaee6ef8 | ||
|
|
505e30c7b2 | ||
|
|
1ac6f3fa33 | ||
|
|
88fa9726cc | ||
|
|
af87920b6f | ||
|
|
881b0517b1 | ||
|
|
9b2b72819b | ||
|
|
2e74e1d2ae | ||
|
|
edae6a3ae8 | ||
|
|
987571dd8c | ||
|
|
934b2df763 | ||
|
|
81bc159b3e | ||
|
|
50e05ae1ba | ||
|
|
432c69c616 | ||
|
|
e714294273 | ||
|
|
c3a144bcb6 | ||
|
|
dd86a289ce | ||
|
|
6e0dac06f4 | ||
|
|
5bd0e92068 | ||
|
|
b032d3524b | ||
|
|
6933df918a | ||
|
|
d1997b5857 | ||
|
|
aafa00abe4 | ||
|
|
d3eb808ff4 | ||
|
|
d009b70171 | ||
|
|
d358da43f7 | ||
|
|
6cfc7e3fa0 | ||
|
|
b5cb1fe1e0 | ||
|
|
17dace9086 | ||
|
|
50fae56baf | ||
|
|
42269e17cc | ||
|
|
a9689d8345 | ||
|
|
32dad4d43b | ||
|
|
a88700bef4 | ||
|
|
8d85aedeff | ||
|
|
744ae23c2c | ||
|
|
4e13d284b7 | ||
|
|
0533cf22e7 | ||
|
|
17e969bc4a | ||
|
|
8b0c0a9439 | ||
|
|
7d2462ce6b | ||
|
|
e34b20b839 | ||
|
|
363f575e6c | ||
|
|
2f272d2703 | ||
|
|
9a21b75c66 | ||
|
|
bbd2fbc434 | ||
|
|
b3fc18c988 | ||
|
|
4a8e45a88c | ||
|
|
e12e2b4e4e | ||
|
|
40972762df | ||
|
|
4f624465c2 | ||
|
|
41c019b3d2 | ||
|
|
ea6121a951 | ||
|
|
1d8c8b9a8e | ||
|
|
41ee20013e | ||
|
|
01192105b1 | ||
|
|
29a8259b2a | ||
|
|
427cb1b14d | ||
|
|
27fd150491 | ||
|
|
c2acfb5c31 | ||
|
|
c642dce97f | ||
|
|
4c64f0ca11 | ||
|
|
29b592f62f | ||
|
|
d124d10f7c | ||
|
|
b857c8f9ba | ||
|
|
aec168e05f | ||
|
|
686c92ba02 | ||
|
|
141c77e17e | ||
|
|
3c673f0086 | ||
|
|
5726c4bd30 | ||
|
|
12e373fd43 | ||
|
|
e3e86ec983 | ||
|
|
3a3e390e90 | ||
|
|
b462d7eb0b | ||
|
|
8b326c9dfe | ||
|
|
150346ae51 | ||
|
|
ed67e4c5a9 | ||
|
|
dcd42eec11 | ||
|
|
6447198e0e | ||
|
|
3003bb5731 | ||
|
|
d7b8cc6ef9 | ||
|
|
9597e4c501 | ||
|
|
f7314646ec | ||
|
|
46fb5f8602 | ||
|
|
ba389598ee | ||
|
|
e8d92a5f31 | ||
|
|
741cfb7f00 | ||
|
|
6056a52978 | ||
|
|
4ea021d151 | ||
|
|
6712560779 | ||
|
|
6bf2dcd97c | ||
|
|
d3a99456ae | ||
|
|
ecd180ecf4 | ||
|
|
cab07f61c5 | ||
|
|
ec83b9cf7c | ||
|
|
0704ac4636 | ||
|
|
6002e359ae | ||
|
|
9a1d8cecfe | ||
|
|
f73366630f | ||
|
|
728ae01012 | ||
|
|
6c2c33db56 | ||
|
|
e7065df2d4 | ||
|
|
fc2d61a55b | ||
|
|
581e7e6fde | ||
|
|
66d67f66a7 | ||
|
|
85dc2eb36f | ||
|
|
4cd861eb87 | ||
|
|
7747c3eb40 | ||
|
|
00c107548e | ||
|
|
e4cb28a310 | ||
|
|
da73b62074 | ||
|
|
d415aa779c | ||
|
|
f8c15e2d2d | ||
|
|
b74e487af2 | ||
|
|
8a69467982 | ||
|
|
1ade0e7b92 | ||
|
|
e5a0252126 | ||
|
|
c36bfb36e5 | ||
|
|
fbfab94114 | ||
|
|
b23cef8d1e | ||
|
|
85240c938e | ||
|
|
27c4cd6360 | ||
|
|
5d1b9f854e | ||
|
|
14bbbe4feb | ||
|
|
34deb5e194 | ||
|
|
0f75cb4dfd | ||
|
|
dd79c66d41 | ||
|
|
75642f393d | ||
|
|
97130684a2 | ||
|
|
981b449a3d | ||
|
|
187e386740 | ||
|
|
b76e229f8e | ||
|
|
fa59cdc2ad | ||
|
|
523a7a9d8d | ||
|
|
51142f45c3 | ||
|
|
5a7fdfe3ed | ||
|
|
ed117719aa | ||
|
|
66cafc7c71 | ||
|
|
397efddbc6 | ||
|
|
749db0bf45 | ||
|
|
60b78a74f7 | ||
|
|
31c14dc908 | ||
|
|
368fff246b | ||
|
|
8a9862bdb4 | ||
|
|
8b58f22c2e | ||
|
|
25c10e4b36 | ||
|
|
486fda17a2 | ||
|
|
1cd932fedd | ||
|
|
50291408c7 | ||
|
|
74510adaaa | ||
|
|
479a456279 | ||
|
|
eb24baa70c | ||
|
|
4e5179557d | ||
|
|
5824714cdc | ||
|
|
a9866c757e | ||
|
|
299a017ef5 | ||
|
|
a553d438c0 | ||
|
|
f865ae9f10 | ||
|
|
1bd88b24d4 | ||
|
|
d7af5c67e9 | ||
|
|
ecd85c3b02 | ||
|
|
4a68d02a4e | ||
|
|
cf2e8b2cab | ||
|
|
da72dfae3d | ||
|
|
80f7492d06 | ||
|
|
65752a9cc4 | ||
|
|
f7e6e45442 | ||
|
|
8770cf2f4b | ||
|
|
51f92b6ed0 | ||
|
|
2e0a3fca1a | ||
|
|
4f959adbc8 | ||
|
|
de701a06de | ||
|
|
e3f6e586ee | ||
|
|
1b6f6b295b | ||
|
|
2a2b63086a | ||
|
|
805d07bca6 | ||
|
|
112aca7896 | ||
|
|
cfcf946556 | ||
|
|
c649fbf43b | ||
|
|
c196570dd8 | ||
|
|
63bc34a1a0 | ||
|
|
584a4aba4c | ||
|
|
35b1963eeb | ||
|
|
4014cbdb44 | ||
|
|
be2a357102 | ||
|
|
971282a9a5 | ||
|
|
f9be73c73e | ||
|
|
c616e5e14e | ||
|
|
bc1fa74d0c | ||
|
|
9f2df68906 | ||
|
|
6077edb3d0 | ||
|
|
d9acd484c0 | ||
|
|
b535a801bc | ||
|
|
d096c75c6e | ||
|
|
897b269139 | ||
|
|
a6bb8ec688 | ||
|
|
72c27a9f73 | ||
|
|
2e1441cde7 | ||
|
|
414dd9f74e | ||
|
|
bee25d221f | ||
|
|
75076ba422 | ||
|
|
de8499a69b | ||
|
|
4d51f9f700 | ||
|
|
15847756cc | ||
|
|
c4852e04c9 | ||
|
|
441734647d | ||
|
|
847a916cd7 | ||
|
|
a1a2bf1a0e | ||
|
|
ed500dbbdf | ||
|
|
02c936c2ff | ||
|
|
6caf725e14 | ||
|
|
5026091623 | ||
|
|
6d1914ce43 | ||
|
|
f18f6bb994 | ||
|
|
a1bb005b7c | ||
|
|
931a862abc | ||
|
|
4b3cc2ca7e | ||
|
|
6cd3e7f7cb | ||
|
|
a047e158ed | ||
|
|
bd1ac4d91b | ||
|
|
59dcbe93a4 | ||
|
|
e345525672 | ||
|
|
d9a1d912fd | ||
|
|
d7d2b12a32 | ||
|
|
f16ae9dffb | ||
|
|
ae420fc9f4 | ||
|
|
f6c4c2b72e | ||
|
|
a0bdf95845 | ||
|
|
0d4bf2e980 | ||
|
|
be439d3b13 | ||
|
|
81e3b714a2 | ||
|
|
e2b0e00547 | ||
|
|
a29f19e482 | ||
|
|
fce2ec0470 | ||
|
|
5ec76d4b7b | ||
|
|
d73b369fa5 | ||
|
|
c66415ae4c | ||
|
|
639b800505 | ||
|
|
35fcf04d3d | ||
|
|
e4bb94e939 | ||
|
|
43aca717d1 | ||
|
|
cb5e5a1325 | ||
|
|
7de882b4d0 | ||
|
|
426bd4cfed | ||
|
|
1e38e02456 | ||
|
|
89b19af43f | ||
|
|
ba12b9ac3f | ||
|
|
f8f6e28985 | ||
|
|
9570ebfe0a | ||
|
|
667fb17b43 | ||
|
|
7d7c83ef79 | ||
|
|
6b56652768 | ||
|
|
c7939ff580 | ||
|
|
068e9a29d2 | ||
|
|
014092443f | ||
|
|
cf8691ed87 | ||
|
|
eb4f4675a5 | ||
|
|
41d925ab8a | ||
|
|
2ead8c089e | ||
|
|
1b0c0e10c6 | ||
|
|
f186e1a4f7 | ||
|
|
fb1788a3e9 | ||
|
|
ac6573cca7 | ||
|
|
6a27c65201 | ||
|
|
5c6cf8b619 | ||
|
|
6014530145 | ||
|
|
160bc0676f | ||
|
|
cd5cbcfe4f | ||
|
|
ae47bc15ff | ||
|
|
33076d5b43 | ||
|
|
9dcb42328e | ||
|
|
3ec736bc04 | ||
|
|
c760a88c22 | ||
|
|
8d35aacd31 | ||
|
|
4579e9bb84 | ||
|
|
5900d765fd | ||
|
|
e65d293ba4 | ||
|
|
b2dda08368 | ||
|
|
c807469a5a | ||
|
|
bfa93bb933 | ||
|
|
d3796abac7 | ||
|
|
bd3a43a367 | ||
|
|
b7c6cc5321 | ||
|
|
939d7a7a0b | ||
|
|
33a419fbdb | ||
|
|
fa1b559b2c | ||
|
|
e46c4652a5 | ||
|
|
832657edfb | ||
|
|
c3b1cf19a4 | ||
|
|
1565e10fff | ||
|
|
e38484a109 | ||
|
|
0522af155d | ||
|
|
1947f26cdb | ||
|
|
0f0589bcf5 | ||
|
|
e8e2b3a6f9 | ||
|
|
5287119f4e | ||
|
|
fd7af33d4b | ||
|
|
af794a6281 | ||
|
|
360d87cbab | ||
|
|
bf0e50d717 | ||
|
|
55f7c81aaf | ||
|
|
a02bf83332 | ||
|
|
36cd9826aa | ||
|
|
7c22dabb63 | ||
|
|
45722772fc | ||
|
|
1a708b2e2a | ||
|
|
ab776352b1 | ||
|
|
15e07b38b6 | ||
|
|
e34ae07b9a | ||
|
|
a28e64227b | ||
|
|
1b6aa537d3 | ||
|
|
88ccd73139 | ||
|
|
db2e1fc2c4 | ||
|
|
fbd346a36b | ||
|
|
7ee42e61d5 | ||
|
|
9b55f4b471 | ||
|
|
86ccaa1981 | ||
|
|
a9b62bcc5c | ||
|
|
016d0d15c3 | ||
|
|
da7db5fa98 | ||
|
|
322ab8fd6f | ||
|
|
8ee296d130 | ||
|
|
e6ce6211de | ||
|
|
96f9d58066 | ||
|
|
f4948dcba0 | ||
|
|
309f54aa0c | ||
|
|
ef1d485c6e | ||
|
|
041e9e14a2 | ||
|
|
233c84ef3e | ||
|
|
c137b0cf73 | ||
|
|
86e7f5914b | ||
|
|
40f56243d1 | ||
|
|
faa9002cce | ||
|
|
df5e3f6227 | ||
|
|
959d91ec65 | ||
|
|
9b598e5ec3 | ||
|
|
58fe29d705 | ||
|
|
70f21d6ca5 | ||
|
|
ecdedd33e2 | ||
|
|
b761a2994b | ||
|
|
1169fb4b86 | ||
|
|
c4302ddc1f | ||
|
|
332b6b9e0a | ||
|
|
42c5c6b4e4 | ||
|
|
c89c21b47a | ||
|
|
4e1dddab67 | ||
|
|
1d5059c281 | ||
|
|
7e7caa8859 | ||
|
|
18f4e9d828 | ||
|
|
93b30ab865 | ||
|
|
f7228f9465 | ||
|
|
f0fec60841 | ||
|
|
6b2b2b678f | ||
|
|
262d7b2372 | ||
|
|
9c704db12b | ||
|
|
9199c03f0d | ||
|
|
744a02eb5a | ||
|
|
83ad29648e | ||
|
|
c8d23b5947 | ||
|
|
b621754514 | ||
|
|
2a97045317 | ||
|
|
c64dbfcf40 | ||
|
|
05df832ae4 | ||
|
|
f1ea33c658 | ||
|
|
8f500046ac | ||
|
|
3285dd0cf1 | ||
|
|
d873d8489d | ||
|
|
28c5697578 | ||
|
|
a9c98eab8d | ||
|
|
162784e98f | ||
|
|
3ca71e146c | ||
|
|
fd7f4ef080 | ||
|
|
1f2b767696 | ||
|
|
1e810eb2c8 | ||
|
|
95c13ddd92 | ||
|
|
f960cccc51 | ||
|
|
4d5e3201c3 | ||
|
|
e3f2dfaec0 | ||
|
|
9f9ae155e3 | ||
|
|
eba0743d47 | ||
|
|
24c764c91e | ||
|
|
f10eed3ae3 | ||
|
|
9923c909c1 | ||
|
|
c406b875fa | ||
|
|
8be9494672 | ||
|
|
fcc51f3591 | ||
|
|
c2806fe189 | ||
|
|
f43ce74604 | ||
|
|
00eb0e6378 | ||
|
|
b941b29fe5 | ||
|
|
ecc38d1bd8 | ||
|
|
d99f40797e | ||
|
|
a86595bc88 | ||
|
|
851a7dc180 | ||
|
|
216072468f | ||
|
|
6dd2f5369e | ||
|
|
32e45557fa | ||
|
|
f11b1bbbeb | ||
|
|
bb7bb2600a | ||
|
|
2de6a38c8b | ||
|
|
3962194fad | ||
|
|
9c5cda0efa | ||
|
|
91f56742c4 | ||
|
|
fe6d666150 | ||
|
|
99a3902c07 | ||
|
|
ba73b9d3c4 | ||
|
|
3b7ae0d6d9 | ||
|
|
712e2683d6 | ||
|
|
073962a60d | ||
|
|
24b7661837 | ||
|
|
e6270e4291 | ||
|
|
850803f138 | ||
|
|
a40872f74d | ||
|
|
8e13920e7b | ||
|
|
8828668541 | ||
|
|
93b2b6d65d | ||
|
|
ebc2895d15 | ||
|
|
95bd55b754 | ||
|
|
f7666bc641 | ||
|
|
769cf43b52 | ||
|
|
cdbf79fff5 | ||
|
|
bab352fb44 | ||
|
|
708988244d | ||
|
|
4153f128ef | ||
|
|
706c0b8d3a | ||
|
|
15a4565714 | ||
|
|
918b759d03 | ||
|
|
50a8b6a184 | ||
|
|
dc2a67f13f | ||
|
|
0db5fc422a | ||
|
|
6eaafcf79d | ||
|
|
06970a662c | ||
|
|
8594af970f | ||
|
|
6a8e4bf87e | ||
|
|
4a93ad33f9 | ||
|
|
14ad808a00 | ||
|
|
afdfc8807d | ||
|
|
8338be3a6e | ||
|
|
98f3cab24e | ||
|
|
f88e400d74 | ||
|
|
246a9f0e45 | ||
|
|
85993848be | ||
|
|
0336b32fcd | ||
|
|
16347270e0 | ||
|
|
149bb56b8e | ||
|
|
8a83415596 | ||
|
|
c0204cdd83 | ||
|
|
efd435d763 | ||
|
|
fc933858e1 | ||
|
|
c34fb24cef | ||
|
|
56eaf143db | ||
|
|
c2db85b99b | ||
|
|
08e74a90f9 | ||
|
|
05536042f9 | ||
|
|
4e6839b5ef | ||
|
|
0feab0aad4 | ||
|
|
0c5e3e2533 | ||
|
|
eb501f7290 | ||
|
|
5ab9636365 | ||
|
|
624dd7d2ce | ||
|
|
3e976fd74b | ||
|
|
2a10a79460 | ||
|
|
674be39af1 | ||
|
|
e4641fc7ed | ||
|
|
5bf80a60f1 | ||
|
|
bb3789b130 | ||
|
|
ac8aaf2739 | ||
|
|
349b2890bf | ||
|
|
2db9c24817 | ||
|
|
ff5494af4e | ||
|
|
a4cdfe89fd | ||
|
|
23dc776bd1 | ||
|
|
aa5cec39ca | ||
|
|
704978a6ba | ||
|
|
a2554952d1 | ||
|
|
d3ffe48fcb | ||
|
|
e053cd6b9c | ||
|
|
613e61222b | ||
|
|
3426611340 | ||
|
|
1c75bf30ac | ||
|
|
072dd8b744 | ||
|
|
0f0015f45c | ||
|
|
19573c0319 | ||
|
|
4a12a98451 | ||
|
|
6dffe9377f | ||
|
|
b0fe8b8b4c | ||
|
|
37e7c512e6 | ||
|
|
70d654e4a6 | ||
|
|
09b013b7a5 | ||
|
|
83416e2758 | ||
|
|
ac271b021d | ||
|
|
7ee6388686 | ||
|
|
029ffc2dc3 | ||
|
|
1a3ab63e47 | ||
|
|
c5c18659a4 | ||
|
|
4bf784b44f | ||
|
|
ef6fb1b756 | ||
|
|
17e9feb633 | ||
|
|
1bd28bdb6a | ||
|
|
f17929f032 | ||
|
|
661d373fa2 | ||
|
|
d909119bb6 | ||
|
|
f4f649acf4 | ||
|
|
d71ba5039b | ||
|
|
ce4978d8c3 | ||
|
|
69353e8210 | ||
|
|
be85f2aff7 | ||
|
|
9a17beba9e | ||
|
|
2d2b89fcd8 | ||
|
|
f1c890ca54 | ||
|
|
8e0ca306d2 | ||
|
|
2b0bf372a7 | ||
|
|
c16806eb78 | ||
|
|
cda2be93f5 | ||
|
|
00cd89750d | ||
|
|
1120298b05 | ||
|
|
d73a9f4f34 | ||
|
|
056284c00b | ||
|
|
9fd1b95fd5 | ||
|
|
9c250f24fa | ||
|
|
afdb1db529 | ||
|
|
7075eaba26 | ||
|
|
09aa1507c8 | ||
|
|
e36f356908 | ||
|
|
f2dd0f046e | ||
|
|
5524241645 | ||
|
|
7e96b15091 | ||
|
|
9b99850585 | ||
|
|
110c869dd3 | ||
|
|
6603fc5975 | ||
|
|
298e942edf | ||
|
|
086a66efc9 | ||
|
|
43f4db2308 | ||
|
|
4ee8254133 | ||
|
|
0170418fc1 | ||
|
|
c268b5b7ac | ||
|
|
2f4732760a | ||
|
|
ab44ae1354 | ||
|
|
2781e16ef4 | ||
|
|
f53250ee90 | ||
|
|
dcd9e7da27 | ||
|
|
650a31b050 | ||
|
|
3ad16c58d7 | ||
|
|
6c285898a8 | ||
|
|
68fa9f20c3 | ||
|
|
312bab0843 | ||
|
|
8448771ce6 | ||
|
|
f05be81275 | ||
|
|
303d4a31fe | ||
|
|
9a7aa3fc87 | ||
|
|
209e8b3041 | ||
|
|
bb6e8de056 | ||
|
|
ab51501849 | ||
|
|
2d5becaaba | ||
|
|
4c6fa1b2c0 | ||
|
|
4d92798f5b | ||
|
|
6284fad33e | ||
|
|
a63a0b3077 | ||
|
|
147e18e28e | ||
|
|
a1b35aa9d5 | ||
|
|
847bdb203a | ||
|
|
2ddbb19c3a | ||
|
|
23d6dc67cf | ||
|
|
27e81f37f3 | ||
|
|
3ba2b0e365 | ||
|
|
dd6adcd240 | ||
|
|
5b6bbfe903 | ||
|
|
2bdedefa05 | ||
|
|
7a475d5ca5 | ||
|
|
89b15c03cf | ||
|
|
212c346c21 | ||
|
|
11777c5f24 | ||
|
|
beaa3dffab | ||
|
|
d060dda0c5 | ||
|
|
91c9f447cd | ||
|
|
0417e4bd0c | ||
|
|
a5552e4c36 | ||
|
|
698f529602 | ||
|
|
48821e6272 | ||
|
|
8a265a0e8d | ||
|
|
c7e206b993 | ||
|
|
c41ad767c5 | ||
|
|
31571cf655 | ||
|
|
aa23266c6f | ||
|
|
acfb7cee07 | ||
|
|
f6adbc369a | ||
|
|
fe1d61b276 | ||
|
|
c55501024e | ||
|
|
5b9ee1dea9 | ||
|
|
7bbc8a4652 | ||
|
|
67a47f428f | ||
|
|
f15eb8b172 | ||
|
|
fb287e8385 | ||
|
|
fa86359549 | ||
|
|
7ef08bfbdf | ||
|
|
ff510b7542 | ||
|
|
013f441658 | ||
|
|
565099f06f | ||
|
|
f727f1db26 | ||
|
|
d88c2f8a43 | ||
|
|
27d57264d9 | ||
|
|
f6c260c49f | ||
|
|
cdd97ce959 | ||
|
|
8c05251cce | ||
|
|
417d434973 | ||
|
|
12f288016d | ||
|
|
77a84e7e6b | ||
|
|
7d2f9366b4 | ||
|
|
4ae1238c0c | ||
|
|
13f2c21e1d | ||
|
|
ea135991e3 | ||
|
|
0526ee8042 | ||
|
|
ae3f7522f0 | ||
|
|
0c69e99b7c | ||
|
|
be4ac89b57 | ||
|
|
663e2874a1 | ||
|
|
16cc3e94ac | ||
|
|
5e2a94e22f | ||
|
|
b78ea464e5 | ||
|
|
8ec19812e3 | ||
|
|
8f89501118 | ||
|
|
7c15d1e8c3 | ||
|
|
34e9e9fe34 | ||
|
|
95ee1c1cde | ||
|
|
eb6bc0399a | ||
|
|
c66d3dfad7 | ||
|
|
de1f2be505 | ||
|
|
ddd3b3f587 | ||
|
|
cba97000c8 | ||
|
|
f882c5b229 | ||
|
|
97a7b449e6 | ||
|
|
dc49a81a05 | ||
|
|
3470ed7847 | ||
|
|
93a90fbe82 | ||
|
|
a0b25c3718 | ||
|
|
838a50be39 | ||
|
|
bb7820cfd7 | ||
|
|
672956af5d | ||
|
|
1b50be09af | ||
|
|
59e77ba6c8 | ||
|
|
090261f304 | ||
|
|
06cfbf5742 | ||
|
|
e050e0307e | ||
|
|
326490be86 | ||
|
|
437a418afe | ||
|
|
7b19e26692 | ||
|
|
dc5159ce74 | ||
|
|
1e1d0676ae | ||
|
|
4be54a976c | ||
|
|
ac3ca7981f | ||
|
|
6399499e9b | ||
|
|
edf03c9ba8 | ||
|
|
7a6eb723ad | ||
|
|
300c89f541 | ||
|
|
78b4872793 | ||
|
|
d686fce7f9 | ||
|
|
ae69e0fef1 | ||
|
|
5a7d18e9f3 | ||
|
|
91c640197c | ||
|
|
02e5148720 | ||
|
|
42c9d53ae1 | ||
|
|
05f52b1fad | ||
|
|
1aef03bdd7 | ||
|
|
6851b8d7bf | ||
|
|
3896902d49 | ||
|
|
088d693eea | ||
|
|
00cfb3bdcf | ||
|
|
60ec264470 | ||
|
|
7b9872c473 | ||
|
|
ebcefa80e3 | ||
|
|
c951e0cc83 | ||
|
|
3a0df321d1 | ||
|
|
3896631a1c | ||
|
|
490f85e690 | ||
|
|
ad15750be3 | ||
|
|
654f933d3f | ||
|
|
09a46e5d80 | ||
|
|
08ca60cfa8 | ||
|
|
201d0a4b19 | ||
|
|
cfc65f525a | ||
|
|
ecf53f88ce | ||
|
|
60ea9e6ec8 | ||
|
|
f7b0bb0e9c | ||
|
|
146bfe75f8 | ||
|
|
ec0d0a51db | ||
|
|
5de17e903c | ||
|
|
62cf9a5dd7 | ||
|
|
c7122597be | ||
|
|
5b8c6b687e | ||
|
|
66a682b55d | ||
|
|
726c9c1fe7 | ||
|
|
a2162672a7 | ||
|
|
71f0acd7a3 | ||
|
|
1d16857997 | ||
|
|
d350a9d415 | ||
|
|
d5b1bfe197 | ||
|
|
df13cfa52b | ||
|
|
753e8ff7f5 | ||
|
|
9ccc02d664 | ||
|
|
bc914de388 | ||
|
|
14591e718d | ||
|
|
9e67bef4bd | ||
|
|
2228f13f99 | ||
|
|
6fe245a278 | ||
|
|
066b755b6a | ||
|
|
72c6552b0b | ||
|
|
05d43af3fc | ||
|
|
1c55c6afba | ||
|
|
8bc4195b7a | ||
|
|
d74ede5a79 | ||
|
|
8559c89560 | ||
|
|
719cec13d5 | ||
|
|
8d1fab1e05 | ||
|
|
0bb37d1fd6 | ||
|
|
a3ba8eb0d7 | ||
|
|
80d0f1f2c1 | ||
|
|
cb1e7bd1b1 | ||
|
|
eeec5a1bba | ||
|
|
5e4a7e54df | ||
|
|
cd618dfdbd | ||
|
|
d43f2a2e3b | ||
|
|
881ed6aef7 | ||
|
|
df9aa0e357 | ||
|
|
09c9fcce00 | ||
|
|
66d71d9974 | ||
|
|
971fe6fdfd | ||
|
|
f1dbd35407 | ||
|
|
f3781e8329 | ||
|
|
9d7f19b950 | ||
|
|
2e9a052f62 | ||
|
|
34f755b2b9 | ||
|
|
1076a1a89d | ||
|
|
21255eddee | ||
|
|
6fcfd0a4f0 | ||
|
|
921f11db34 | ||
|
|
709fd034b4 | ||
|
|
e6aa01653a | ||
|
|
c6f67911de | ||
|
|
c57c0e7f55 | ||
|
|
aad449f28a | ||
|
|
a3fd1d4b51 | ||
|
|
90f43d99ca | ||
|
|
628a598ca0 | ||
|
|
0ca935e0b9 | ||
|
|
f9bd0fb2ce | ||
|
|
588a55c9d7 | ||
|
|
84fd969710 | ||
|
|
badaa5fad1 | ||
|
|
4ba4de3379 | ||
|
|
ef288b8a6c | ||
|
|
438aac190f | ||
|
|
7b69697439 | ||
|
|
9df4fce282 | ||
|
|
78de82158d | ||
|
|
5e46c0b4e9 | ||
|
|
c874bd6a15 | ||
|
|
e6b0eaed61 | ||
|
|
100fc47812 | ||
|
|
ac09a6432c | ||
|
|
ae40654907 | ||
|
|
076cc428af | ||
|
|
e0c15b24fc | ||
|
|
978a718159 | ||
|
|
314fba049f | ||
|
|
80cca91584 | ||
|
|
d87b7c264d | ||
|
|
b96aedd720 | ||
|
|
905fc968bf | ||
|
|
2fa3f2578f | ||
|
|
1ed7779701 | ||
|
|
825a3e1d5c | ||
|
|
2d1e30ae3b | ||
|
|
6d9b8ee0dc | ||
|
|
1fb70fc0e0 | ||
|
|
da631478a8 | ||
|
|
d21cb96ddb | ||
|
|
94ebbd9d90 | ||
|
|
7b0fc4ae7e | ||
|
|
f1327b75c3 | ||
|
|
3676daf7dc | ||
|
|
ddf1beac6c | ||
|
|
500ce56371 | ||
|
|
ff5a6773b1 | ||
|
|
366db2f38c | ||
|
|
4458d090b3 | ||
|
|
f59bf6fab1 | ||
|
|
a8dd6a831c | ||
|
|
14e387e3ac | ||
|
|
b2d4d37539 | ||
|
|
6622576cf8 | ||
|
|
ec3e86ad18 | ||
|
|
eacf991bb2 | ||
|
|
163527a021 | ||
|
|
6fda3c877e | ||
|
|
4dfe467e66 | ||
|
|
993505b461 | ||
|
|
f22f427cde | ||
|
|
51cf5d5f1b | ||
|
|
8d0b2947d6 | ||
|
|
66a950942e | ||
|
|
df0a19d5c4 | ||
|
|
d8c8832a37 | ||
|
|
24fb72f926 | ||
|
|
5f2caffb51 | ||
|
|
f259bc6a09 | ||
|
|
d8ce5078d9 | ||
|
|
de1c984b48 | ||
|
|
9e6ec71520 | ||
|
|
7e997e40f3 | ||
|
|
416a0d2722 | ||
|
|
1f9fdf0c5c | ||
|
|
77019c547b | ||
|
|
a71906525a | ||
|
|
630df050c0 | ||
|
|
06ce4f21c5 | ||
|
|
1bc55714ed | ||
|
|
f4bf6ee186 | ||
|
|
b6d5ea629b | ||
|
|
e6ec20b4ca | ||
|
|
96730bae75 | ||
|
|
2c7f579838 | ||
|
|
a75ad492f1 | ||
|
|
bc7fede68b | ||
|
|
86871e5abc | ||
|
|
7371274ea7 | ||
|
|
56968d858c | ||
|
|
954085d33e | ||
|
|
4777ca21c3 | ||
|
|
21b34ba133 | ||
|
|
8d870a03b5 | ||
|
|
a0f1c8c87f | ||
|
|
66add465bd | ||
|
|
ad753c90ff | ||
|
|
3d6577838c | ||
|
|
71afcb0333 | ||
|
|
fb91ea2470 | ||
|
|
4d572d3ae8 | ||
|
|
3971bf0c99 | ||
|
|
9bad73b995 | ||
|
|
d48d073bcf | ||
|
|
16d087cce4 | ||
|
|
7e2e30f94b | ||
|
|
eb61a36fd7 | ||
|
|
65506376a4 | ||
|
|
d4c0dfa851 | ||
|
|
07df6e010f | ||
|
|
6faffd832c | ||
|
|
61e5b05262 | ||
|
|
f8f0d7def7 | ||
|
|
14f9724bc9 | ||
|
|
f3c4288311 | ||
|
|
cc7d25394c | ||
|
|
baca881473 | ||
|
|
da52596d79 | ||
|
|
1beab85d61 | ||
|
|
d19f5e411c | ||
|
|
eeca91e43d | ||
|
|
e9a7c8c7fa | ||
|
|
a9dc2aa58b | ||
|
|
238d022d8d | ||
|
|
bccec5af95 | ||
|
|
0fe55f81f6 | ||
|
|
7b5e7178f4 | ||
|
|
8be9134e32 | ||
|
|
85e17fa5b0 | ||
|
|
114cb2d2aa | ||
|
|
69a854de8b | ||
|
|
c655162dd6 | ||
|
|
dcd12bff7c | ||
|
|
47e3b5b902 | ||
|
|
2bf9f10f16 | ||
|
|
9daaaa3ec2 | ||
|
|
e396454138 | ||
|
|
5d6d75f546 | ||
|
|
26374e2644 | ||
|
|
0c3f4eb7dd | ||
|
|
f3798f9d40 | ||
|
|
24956a49f4 | ||
|
|
0c2af7a8ee | ||
|
|
26ca72e252 | ||
|
|
fe85c4111a | ||
|
|
b3d8273a03 | ||
|
|
01087e01d2 | ||
|
|
946fff39a6 | ||
|
|
70552e3e0d | ||
|
|
f8159dcbfc | ||
|
|
b6880c5f50 | ||
|
|
2eb06760a3 | ||
|
|
06d18484f6 | ||
|
|
5902daf3b5 | ||
|
|
f1ff55f8b6 | ||
|
|
e6ba4a227a | ||
|
|
72bf00bf00 | ||
|
|
1647798396 | ||
|
|
13a8e2c09d | ||
|
|
2ff4a2846b | ||
|
|
6981cfb8bc | ||
|
|
0b14ab8000 | ||
|
|
33aa9bd03f | ||
|
|
1cf26fc652 | ||
|
|
c10142816e | ||
|
|
f9c38ac35e | ||
|
|
93137b02be | ||
|
|
33fcf8eb44 | ||
|
|
c87ebe8590 | ||
|
|
4b576cde50 | ||
|
|
f01e8bc509 | ||
|
|
7c94f2f3be | ||
|
|
a46296d340 | ||
|
|
bf1d3579c1 | ||
|
|
ba0c667cad | ||
|
|
3a94d9d176 | ||
|
|
4aa0669e61 | ||
|
|
bf6948f8d6 | ||
|
|
9216a1eb07 | ||
|
|
4260cc38c9 | ||
|
|
697ab30304 | ||
|
|
85f01e4ded | ||
|
|
ab51c7e2d2 | ||
|
|
cb1e797d72 | ||
|
|
24bf720a9a | ||
|
|
c553a6f458 | ||
|
|
bd708fa205 | ||
|
|
9c75c1365f | ||
|
|
f41f324fbc | ||
|
|
b24fb44fba | ||
|
|
ea38116571 | ||
|
|
0b5991850c | ||
|
|
cdd37bda1c | ||
|
|
34ad13fc22 | ||
|
|
1a31571493 | ||
|
|
ebca777efd | ||
|
|
7628a64a55 | ||
|
|
84a887cc58 | ||
|
|
8f7f2e77c3 | ||
|
|
a2f4d467fd | ||
|
|
f17962182f | ||
|
|
55f155a72c | ||
|
|
a962479ce7 | ||
|
|
d70fe95104 | ||
|
|
4101147a5b | ||
|
|
8a6b3cff57 | ||
|
|
eef48c4be3 | ||
|
|
9f5f70a93f | ||
|
|
7662f03629 | ||
|
|
3a923e8aae | ||
|
|
340afc3503 | ||
|
|
2d63b007f2 | ||
|
|
3cab8bd5d1 | ||
|
|
3d1a5f4bf3 | ||
|
|
79237a9309 | ||
|
|
e51fa5c9ac | ||
|
|
61802b9d42 | ||
|
|
38241d66cb | ||
|
|
b019e41f1c | ||
|
|
ea0dcdcfac | ||
|
|
05a95cace5 | ||
|
|
30d28a942e | ||
|
|
277c037676 | ||
|
|
09c543c526 | ||
|
|
b15c476698 | ||
|
|
3e67ca855a | ||
|
|
bc5cd3c2a1 | ||
|
|
16096e2c71 | ||
|
|
52d0ecd977 | ||
|
|
0b9c770b4e | ||
|
|
7d3e16986c | ||
|
|
1183e082af | ||
|
|
ec00636b52 | ||
|
|
1a2eb56b54 | ||
|
|
e0d0d49ea0 | ||
|
|
ac3d0dc30c | ||
|
|
56cb88819c | ||
|
|
6992fd4dba | ||
|
|
4244ab72d5 | ||
|
|
8a7f249aaa | ||
|
|
0ef6c756b6 | ||
|
|
1c052b7c81 | ||
|
|
a5c6d83cd9 | ||
|
|
d10b84d8cd | ||
|
|
16cb27b5f9 | ||
|
|
1461e475da | ||
|
|
71c87ac32f | ||
|
|
b8394c3573 | ||
|
|
00f5ee6064 | ||
|
|
f4b07a7293 | ||
|
|
a34a6c65e7 | ||
|
|
4e41479464 | ||
|
|
e4728eb719 | ||
|
|
1a0bcfe018 | ||
|
|
2cd06e3ea0 | ||
|
|
a5e5918fc5 | ||
|
|
fa5bc3a5a9 | ||
|
|
7d95e134eb | ||
|
|
f06bf6721e | ||
|
|
d0c748460e | ||
|
|
4beed065b6 | ||
|
|
0760968a3b | ||
|
|
eef3aaee60 | ||
|
|
4e9e3f3dc3 | ||
|
|
c91d970c41 | ||
|
|
e9afba3490 | ||
|
|
f2c518455e | ||
|
|
7db1dd52bd | ||
|
|
76698fe2db | ||
|
|
5076b2b950 | ||
|
|
e483383a44 | ||
|
|
799ff17c7c | ||
|
|
1d3877fab3 | ||
|
|
f50e26e5dd | ||
|
|
37d5baf0b2 | ||
|
|
9bf4aa7546 | ||
|
|
9b98ef5de4 | ||
|
|
4e46d00eb6 | ||
|
|
dcf5fb36f3 | ||
|
|
9169a073b6 | ||
|
|
12d870e274 | ||
|
|
52acfb88ee | ||
|
|
2be2891647 | ||
|
|
99c842e027 | ||
|
|
e7f6554c68 | ||
|
|
0025b3c1dc | ||
|
|
08ce9258d1 | ||
|
|
7b11224130 | ||
|
|
0da2f81252 | ||
|
|
1c864d1eef | ||
|
|
8657b4698f | ||
|
|
dfad346cff | ||
|
|
0f6c365411 | ||
|
|
bd91008462 | ||
|
|
72132c851b | ||
|
|
0de43748f7 | ||
|
|
b60458d236 | ||
|
|
8f1bf7a0ed | ||
|
|
33ca9dabef | ||
|
|
d6723935b9 | ||
|
|
a0dbb6bdd6 | ||
|
|
72c0dbd8e5 | ||
|
|
41bf668189 | ||
|
|
c51a83b404 | ||
|
|
9beacabd66 | ||
|
|
4f2baa8953 | ||
|
|
3abb59a550 | ||
|
|
2cca6ab3db | ||
|
|
c362d75a38 | ||
|
|
bce50a802b | ||
|
|
61fcbd1664 | ||
|
|
e1570eedf3 | ||
|
|
06ea2f573e | ||
|
|
ca882725ce | ||
|
|
60b19c51ef | ||
|
|
65cd13301c | ||
|
|
8283bb994a | ||
|
|
33af2409e8 | ||
|
|
98dbb2f70f | ||
|
|
f4310e8f69 | ||
|
|
3c7880cc10 | ||
|
|
ee3e631b1a | ||
|
|
4521ff3e95 | ||
|
|
fed548623d | ||
|
|
bda5315866 | ||
|
|
0d7afef0c5 | ||
|
|
342e85d378 | ||
|
|
9794c04b94 | ||
|
|
f54a555aa8 | ||
|
|
fa245c1e53 | ||
|
|
194093469a | ||
|
|
c7508a0512 | ||
|
|
1a07cb7bbf | ||
|
|
cf2eefdfbe | ||
|
|
fe8db295e7 | ||
|
|
f6bda96d5a | ||
|
|
115167659d | ||
|
|
713059275d | ||
|
|
5b657a2905 | ||
|
|
b03d2e2f9e | ||
|
|
0945d8725e | ||
|
|
2a1253e45a | ||
|
|
96555aa74b | ||
|
|
44f73103db | ||
|
|
5a4f917c60 | ||
|
|
e1aa4d6265 | ||
|
|
43bcae17b3 | ||
|
|
d5b8defcbb | ||
|
|
7db39115e8 | ||
|
|
a97e7ea52a | ||
|
|
7f731db9dd | ||
|
|
353c5d8b12 | ||
|
|
b9c51e359f | ||
|
|
00efd097c7 | ||
|
|
4dc35285dd | ||
|
|
9161b865f3 | ||
|
|
58f8481a12 | ||
|
|
f83bbc2cbe | ||
|
|
d8dda1ee66 | ||
|
|
e4dcb7441b | ||
|
|
df6e3e83bf | ||
|
|
01ea48a8fe | ||
|
|
48619dc238 | ||
|
|
2aeab13aae | ||
|
|
6c12068fb7 | ||
|
|
c1979f2c7f | ||
|
|
767392332e | ||
|
|
0eeb6c16d9 | ||
|
|
cb09e48e8c | ||
|
|
cf0a3af1f8 | ||
|
|
a3504dd51e | ||
|
|
0ce86e999a | ||
|
|
2d2aefea16 | ||
|
|
6b1e781602 | ||
|
|
f9dac3a291 | ||
|
|
1b6646b55a | ||
|
|
0a9e0acc6f | ||
|
|
ae634ef7df | ||
|
|
cd10a3126b | ||
|
|
f1d9033194 | ||
|
|
1920981713 | ||
|
|
0b845c9b1d | ||
|
|
e2617f2780 | ||
|
|
5d9b869d93 | ||
|
|
794429dd55 | ||
|
|
d787aa5007 | ||
|
|
def0e0a7a1 | ||
|
|
9f0e0ced5e | ||
|
|
a6a63d35e5 | ||
|
|
06cb004c33 | ||
|
|
25e99c6a64 | ||
|
|
929161c070 | ||
|
|
ba6d1091f2 | ||
|
|
cff1fcfe9d | ||
|
|
6df2cf552a | ||
|
|
c0ef65f442 | ||
|
|
66624b1a8f | ||
|
|
1d417685d1 | ||
|
|
917df9a5b8 | ||
|
|
a82d156ccb | ||
|
|
7d499df001 | ||
|
|
97425ea1c9 | ||
|
|
226685dc1c | ||
|
|
1381baf088 | ||
|
|
03b8ab724a | ||
|
|
b8428423a1 | ||
|
|
99c0e14fd8 | ||
|
|
b12482df89 | ||
|
|
e655e8bc19 | ||
|
|
57064c97e0 | ||
|
|
111d35deef | ||
|
|
3b29aacfdc | ||
|
|
18627e77ff | ||
|
|
4e98f2e438 | ||
|
|
d6dffed203 | ||
|
|
5b96b648b4 | ||
|
|
837dd0d241 | ||
|
|
ec50048a29 | ||
|
|
54315ce50f | ||
|
|
49ece5fddf | ||
|
|
852e888232 | ||
|
|
a6268f055d | ||
|
|
104871d6c9 | ||
|
|
c744dc06c6 | ||
|
|
57c43183e6 | ||
|
|
1ad725a04c | ||
|
|
a898b18fd6 | ||
|
|
00d7bd1a77 | ||
|
|
28ec00500b | ||
|
|
69ec3c205a | ||
|
|
35d14788d3 | ||
|
|
c69c9e62c2 | ||
|
|
835e3d803f | ||
|
|
9c31f61db4 | ||
|
|
cb4ae013c6 | ||
|
|
3394645f7a | ||
|
|
0fa362cc23 | ||
|
|
61401e5e82 | ||
|
|
e9019c8bcc | ||
|
|
c742d45b19 | ||
|
|
6afeb2e72b | ||
|
|
b5d50f2b52 | ||
|
|
7e0be91cfd | ||
|
|
4f6ec225cf | ||
|
|
b2a0ad68dd | ||
|
|
6d0eec6395 | ||
|
|
60970008c8 | ||
|
|
4ad160ef80 | ||
|
|
038582ba44 | ||
|
|
4750f32ab6 | ||
|
|
1c9072bf6e | ||
|
|
656c022ed6 | ||
|
|
4d5af19b9d | ||
|
|
fc227561ba | ||
|
|
d26a2bcf6b | ||
|
|
ca0bed47c4 | ||
|
|
4f4f18482a | ||
|
|
d0c158b019 | ||
|
|
140187dc2a | ||
|
|
3e4ebd6c9d | ||
|
|
8af80169c0 | ||
|
|
4ad63d9b63 | ||
|
|
af8c379ac8 | ||
|
|
9f7bd983b3 | ||
|
|
1dafcf24f1 | ||
|
|
b6354dfb74 | ||
|
|
d6cf76a91a | ||
|
|
a34537ae9f | ||
|
|
85c7864f78 | ||
|
|
5ced1ab905 | ||
|
|
a92099258b | ||
|
|
d5027bfbb2 | ||
|
|
c544c2b5c5 | ||
|
|
48c5494cf9 | ||
|
|
74bf9119cc | ||
|
|
f24d947388 | ||
|
|
36aed6f871 | ||
|
|
e1a593c22e | ||
|
|
e5ab64a3ab | ||
|
|
91b2d2c2fe | ||
|
|
4684446315 | ||
|
|
0d6cfc879d | ||
|
|
5afaca6fa5 | ||
|
|
78c0c9b21a | ||
|
|
720e29848b | ||
|
|
255f511abd | ||
|
|
220bd376b4 | ||
|
|
6bf87e28e9 | ||
|
|
49167cc55f | ||
|
|
a2b6aa8e67 | ||
|
|
c7ffaad763 | ||
|
|
14580b6144 | ||
|
|
d68041c394 | ||
|
|
8610bcbec3 | ||
|
|
50702f309f | ||
|
|
5c3ed4d91a | ||
|
|
243b295559 | ||
|
|
0aaf7b25a9 | ||
|
|
526cfea568 | ||
|
|
0661a1bbfc | ||
|
|
22047d20ef | ||
|
|
ca3eabafef | ||
|
|
23b662fe7d | ||
|
|
048759a147 | ||
|
|
a4f6b739be | ||
|
|
5133ff16ba | ||
|
|
63e801e00f | ||
|
|
201eb3bab8 | ||
|
|
c4b790a910 | ||
|
|
dbcf2c3cf5 | ||
|
|
a9cb1d6543 | ||
|
|
3343e82307 | ||
|
|
4d5c4d89ce | ||
|
|
91ca4a841a | ||
|
|
07ae3e49a3 | ||
|
|
e326a444b1 | ||
|
|
fa2c6605c2 | ||
|
|
bc15d5234e | ||
|
|
05c262eb9f | ||
|
|
a593a4f6f4 | ||
|
|
1e1668e949 | ||
|
|
6dce181314 | ||
|
|
c0ed0dda8e | ||
|
|
7e70536bf1 | ||
|
|
c72727b4fd | ||
|
|
41daf5ad28 | ||
|
|
13435aebdc | ||
|
|
352fb9f9ea | ||
|
|
fc46938383 | ||
|
|
0309e2332c | ||
|
|
17c3d158d2 | ||
|
|
d4338047af | ||
|
|
3aa521600a | ||
|
|
fc8bf8c15d | ||
|
|
3d3c1dbbb4 | ||
|
|
1d0d429435 | ||
|
|
604c234484 | ||
|
|
72c89ab89b | ||
|
|
53d5efa944 | ||
|
|
44c1e3a347 | ||
|
|
709fd12b7f | ||
|
|
21a603ed8f | ||
|
|
d2c5d81e59 | ||
|
|
85f91a1114 | ||
|
|
411a69233b | ||
|
|
cbf63aab1e | ||
|
|
dbb504e3d8 | ||
|
|
d42cd6a5e2 | ||
|
|
97b645cd55 | ||
|
|
faf05cafcf | ||
|
|
e7a1be4dde | ||
|
|
e9470d873e | ||
|
|
cf8e90fa3e | ||
|
|
62146b7bb3 | ||
|
|
c013ef3b30 | ||
|
|
575e962d16 | ||
|
|
70da3e124a | ||
|
|
f357edb8a4 | ||
|
|
028a113737 | ||
|
|
4bd6d7be1a | ||
|
|
8d1ef188b1 | ||
|
|
63af9f2a9e | ||
|
|
1a9c775407 | ||
|
|
955b86deea | ||
|
|
6eb342b8ee | ||
|
|
5bc1cb2aa3 | ||
|
|
cb05001e5a | ||
|
|
ad9e4db3d6 | ||
|
|
5e6ab42cb8 | ||
|
|
63ee6b6791 | ||
|
|
54076e9a6b | ||
|
|
d9f3b6c121 | ||
|
|
d7548ab2f7 | ||
|
|
0e81c1cf7e | ||
|
|
f837cb5513 | ||
|
|
b7ff44485a | ||
|
|
9cee31193c | ||
|
|
9935e3acfc | ||
|
|
90ecfbe0e4 | ||
|
|
0ca349033d | ||
|
|
30a922513b | ||
|
|
8c169bb236 | ||
|
|
cd976ad79f | ||
|
|
baa3d09577 | ||
|
|
c73c5987c7 | ||
|
|
dfe87c9f6f | ||
|
|
5cc089be12 | ||
|
|
a8d18c8ac9 | ||
|
|
cc0635335c | ||
|
|
65d8769002 | ||
|
|
d8f5330c29 | ||
|
|
ea2f53745e | ||
|
|
c9bd682c33 | ||
|
|
04c8bca073 | ||
|
|
7f1335a8dd | ||
|
|
ff77198a08 | ||
|
|
8da64d710e | ||
|
|
e453a38865 | ||
|
|
ce7be477ce | ||
|
|
01b6ddbbd3 | ||
|
|
37929ff75f | ||
|
|
b6ba0ed47c | ||
|
|
87f7315f5a | ||
|
|
1b6ad6d2a1 | ||
|
|
a62572bb76 | ||
|
|
f752770a47 | ||
|
|
d117cb61b6 | ||
|
|
c071b2dc47 | ||
|
|
d70ebd2ba5 | ||
|
|
cda7f08b76 | ||
|
|
19a65319ee | ||
|
|
7402b14df3 | ||
|
|
0622eb7634 | ||
|
|
95f344540e | ||
|
|
ee9060aa14 | ||
|
|
c6fd45727d | ||
|
|
cd27f5a4bc | ||
|
|
8ad8fa902d | ||
|
|
7861b5e6cb | ||
|
|
70aaa8fc92 | ||
|
|
e441b48ea7 | ||
|
|
6ed57ede6a | ||
|
|
aefc463586 | ||
|
|
f2340579a5 | ||
|
|
de5577ca70 | ||
|
|
316b11445e | ||
|
|
b42e481600 | ||
|
|
e5a48800ab | ||
|
|
db50d01db9 | ||
|
|
cec2085790 | ||
|
|
75ee71e35a | ||
|
|
7f9a3ac584 | ||
|
|
1e9f293994 | ||
|
|
b5f9a385ae | ||
|
|
f1114d3852 | ||
|
|
d27187b1de | ||
|
|
4aaa758543 | ||
|
|
03f07042ff | ||
|
|
bcbfc931ba | ||
|
|
e2b2026fa5 | ||
|
|
4b8b79dbe0 | ||
|
|
9e5cc46638 | ||
|
|
ba542cf67a | ||
|
|
3f58ffe572 | ||
|
|
25f544f3db | ||
|
|
df747c8216 | ||
|
|
53da298c4f | ||
|
|
73c7f40ce3 | ||
|
|
66bfadb71a | ||
|
|
5af64c53b6 | ||
|
|
b60860456d | ||
|
|
00941e4461 | ||
|
|
095a309aef | ||
|
|
834383b7e5 | ||
|
|
503b16f6b1 | ||
|
|
433f9ff211 | ||
|
|
be26c56362 | ||
|
|
1a2c3adb2f | ||
|
|
b8077fadaa | ||
|
|
17a4a99f9c | ||
|
|
71289b58ff | ||
|
|
1cdf29244c | ||
|
|
cb665caf9e | ||
|
|
eb4b85aea5 | ||
|
|
9c6c5aef5d | ||
|
|
050d552b9d | ||
|
|
cf86bc6e23 | ||
|
|
af61dfd8cd | ||
|
|
2a2a18a163 | ||
|
|
29f3d6aa28 | ||
|
|
4379ff60f6 | ||
|
|
a55a37c17b | ||
|
|
5532093d58 | ||
|
|
752be27815 | ||
|
|
80903e86fa | ||
|
|
dc076c6441 | ||
|
|
c6e63db7b4 | ||
|
|
dff6e87ce9 | ||
|
|
c765289da9 | ||
|
|
3106f512b2 | ||
|
|
2841edfb96 | ||
|
|
c9c437cb3b | ||
|
|
c0e317f829 | ||
|
|
54f2e6ea3a | ||
|
|
dbd51ff8e7 | ||
|
|
63a5124f57 | ||
|
|
eda726ec34 | ||
|
|
bab23aa7b1 | ||
|
|
7b258eddc4 | ||
|
|
3bf0bd6ce9 | ||
|
|
0ed981be19 | ||
|
|
77113844a2 | ||
|
|
9da4296e21 | ||
|
|
9fc5f0cd9d | ||
|
|
2f2e7be12a | ||
|
|
9892bf4379 | ||
|
|
351a683812 | ||
|
|
445b7f3388 | ||
|
|
9e95372e5d | ||
|
|
b5660b58b3 | ||
|
|
b472ea371a | ||
|
|
6703bd2c71 | ||
|
|
36388de4e1 | ||
|
|
10159ba1bb | ||
|
|
792a2cad82 | ||
|
|
7f1b12d29e | ||
|
|
23b46a05f7 | ||
|
|
cd14a5eb40 | ||
|
|
2256e62648 | ||
|
|
6b42a78c3f | ||
|
|
6b2b22782b | ||
|
|
de52d3cadd | ||
|
|
7e43c40529 | ||
|
|
df9dfa9d0c | ||
|
|
08a2e0f82c | ||
|
|
01dc9c767e | ||
|
|
67b441838b | ||
|
|
94f2948020 | ||
|
|
f5540195de | ||
|
|
558e4c7669 | ||
|
|
c3a5da8586 | ||
|
|
02c8ab7839 | ||
|
|
8414569428 | ||
|
|
bd97eb98c5 | ||
|
|
e67fbda305 | ||
|
|
26e6bc32ee | ||
|
|
11a6bff416 | ||
|
|
e67ee227a3 | ||
|
|
f8ec1d9d22 | ||
|
|
7fb3360d6f | ||
|
|
cb35625a53 | ||
|
|
26870501a7 | ||
|
|
f5d061f34f | ||
|
|
362fbbf913 | ||
|
|
f8d5f610b6 | ||
|
|
e43174f2af | ||
|
|
16523c2ef9 | ||
|
|
d3a9006272 | ||
|
|
a404d535ee | ||
|
|
dcd01df2d3 | ||
|
|
ba5a5d1248 | ||
|
|
052b090bbf | ||
|
|
4654d9c192 | ||
|
|
1e877eae86 | ||
|
|
3c934ea21f | ||
|
|
52718e514c | ||
|
|
4310cc23ce | ||
|
|
bfb1cc6379 | ||
|
|
cbfc675f4f | ||
|
|
e7b5d678a9 | ||
|
|
effacec817 | ||
|
|
3a89388039 | ||
|
|
3366c02a36 | ||
|
|
a26cc96a49 | ||
|
|
6d9ac84359 | ||
|
|
882507e915 | ||
|
|
08e1b52281 | ||
|
|
cf153f89df | ||
|
|
d2401bc339 | ||
|
|
fd0bfc8777 | ||
|
|
7b295d7bb0 | ||
|
|
b6563ffc29 | ||
|
|
2594ff6233 | ||
|
|
807d803a81 | ||
|
|
2076cb106d | ||
|
|
5b304fa20a | ||
|
|
fbc0330fa8 | ||
|
|
f5bfaff003 | ||
|
|
eac17a8328 | ||
|
|
f9c9316990 | ||
|
|
d9f5f6e2c6 | ||
|
|
955b755055 | ||
|
|
0c6e4c6c40 | ||
|
|
57812fc61d | ||
|
|
62f05d4c14 | ||
|
|
e4de5c55ac | ||
|
|
efaf2c720b | ||
|
|
d1f419c9d1 | ||
|
|
c5ece2a892 | ||
|
|
7712d97270 | ||
|
|
8e90d5fb17 | ||
|
|
b2fcca87ff | ||
|
|
a46a9bb42e | ||
|
|
62d661f898 | ||
|
|
4436148fa1 | ||
|
|
3886e050b0 | ||
|
|
ff4b0c8f5d | ||
|
|
f82ee0a2a7 | ||
|
|
b5d603dd46 | ||
|
|
0b8ae006d3 | ||
|
|
55fd0a4e60 | ||
|
|
3c4acbc6cb | ||
|
|
8224aab851 | ||
|
|
28604f5a4c | ||
|
|
533081454a | ||
|
|
e94e64652b | ||
|
|
489ecef47b | ||
|
|
8b42ca76f8 | ||
|
|
be2d138aac | ||
|
|
1e782b3de2 | ||
|
|
8bd62165f5 | ||
|
|
ae8a9a93bf | ||
|
|
f7ab2103ce | ||
|
|
c3d5a9848b | ||
|
|
6d6d82a304 | ||
|
|
bc83c0221b | ||
|
|
c6bf252a21 | ||
|
|
f9ea6969c3 | ||
|
|
9e956ff9f4 | ||
|
|
0a45e97699 | ||
|
|
3962d4204b | ||
|
|
fd76216652 | ||
|
|
2a7dcb6e17 | ||
|
|
b02eaee0d4 | ||
|
|
6ccf24ce6c | ||
|
|
c6186a03f1 | ||
|
|
ee4f503e10 | ||
|
|
0c227e89b2 | ||
|
|
128933a655 | ||
|
|
055b215bc7 | ||
|
|
611dd77442 | ||
|
|
fb173c058e | ||
|
|
2edb32979f | ||
|
|
e3bed8d12a | ||
|
|
84de47377c | ||
|
|
06c99d0e18 | ||
|
|
0aebec6697 | ||
|
|
d4c50b8ea0 | ||
|
|
cc424d64fe | ||
|
|
48ea36d37f | ||
|
|
ffdd436b52 | ||
|
|
ec9286eb4b | ||
|
|
68f8d60323 | ||
|
|
5cabb068c0 | ||
|
|
01079b0815 | ||
|
|
7e15aa243e | ||
|
|
e9ca6c0871 | ||
|
|
604bf64ef6 | ||
|
|
925e057acb | ||
|
|
c47e96ca45 | ||
|
|
6354a8ce03 | ||
|
|
85ec972abb | ||
|
|
a8d38ad82f | ||
|
|
be6badc6a5 | ||
|
|
cf9275a2a2 | ||
|
|
66f707c243 | ||
|
|
1163e46622 | ||
|
|
08b99ff413 | ||
|
|
23472aa4f2 | ||
|
|
05edf50931 | ||
|
|
a8e39962d0 | ||
|
|
5a42bb166e | ||
|
|
3953227702 | ||
|
|
d8d8ce7526 | ||
|
|
95e754601e | ||
|
|
5e11da7316 | ||
|
|
24e36852af | ||
|
|
458acbf33d | ||
|
|
8a9707788c | ||
|
|
e9351135bc | ||
|
|
1bb9cfee53 | ||
|
|
7da41f0fdc | ||
|
|
f22e933ab1 | ||
|
|
affa4c1b07 | ||
|
|
0011c321e2 | ||
|
|
1924eb1471 | ||
|
|
ce8c2fd656 | ||
|
|
c2fdca470d | ||
|
|
4f1bead0de | ||
|
|
2dc2e0ba56 | ||
|
|
14128819de | ||
|
|
a1e2a4452c | ||
|
|
3b34eba612 | ||
|
|
11ac026b62 | ||
|
|
05970ffadd | ||
|
|
488a156e6f | ||
|
|
a0f2bb0755 | ||
|
|
9fded49470 | ||
|
|
69752b419d | ||
|
|
3d3e85c52d | ||
|
|
94a21e3aeb | ||
|
|
2ca0c152ba | ||
|
|
a2bcc1fec2 | ||
|
|
6cdac5df4f | ||
|
|
92357b3a8b | ||
|
|
872ee5b7e7 | ||
|
|
4d30ab77be | ||
|
|
85f0f0bf81 | ||
|
|
d6174292b3 | ||
|
|
0bf41bcd10 | ||
|
|
027a0e805b | ||
|
|
fefd21ad9c | ||
|
|
d9c803ea9e | ||
|
|
0414e99fe7 | ||
|
|
0fb51a95cc | ||
|
|
9298d183bf | ||
|
|
0402abe36b | ||
|
|
44112da971 | ||
|
|
2ed89f496f | ||
|
|
2fecf3a8f1 | ||
|
|
2ef8c1ae0c | ||
|
|
2fea9433d8 | ||
|
|
e06c19b51c | ||
|
|
34abc3d023 | ||
|
|
d78a5e0a9f | ||
|
|
719171faa3 | ||
|
|
a896b30039 | ||
|
|
50bac00329 | ||
|
|
b6fda30719 | ||
|
|
fd4af3f7a9 | ||
|
|
f6d41ee77d | ||
|
|
0ce1bf706b | ||
|
|
56653eeca6 | ||
|
|
83605dfd95 | ||
|
|
353f93fddf | ||
|
|
65ecb33630 | ||
|
|
0a4eeb7cb1 | ||
|
|
469136ee86 | ||
|
|
ccf359d057 | ||
|
|
d6b0648610 | ||
|
|
da4528932a | ||
|
|
5f460b380d | ||
|
|
55f76f19d3 | ||
|
|
0bafc00b52 | ||
|
|
d309b56ad6 | ||
|
|
6c9289df0a | ||
|
|
a82a0769fa | ||
|
|
61849ef7ea | ||
|
|
960b6012c2 | ||
|
|
da2a11ed76 | ||
|
|
f0f8d83980 | ||
|
|
45e576addf | ||
|
|
7534ac1e1a | ||
|
|
bda85e4097 | ||
|
|
5e16aab51b | ||
|
|
f572d9735d | ||
|
|
fac47bfc54 | ||
|
|
cc56389dbb | ||
|
|
60969c69a7 | ||
|
|
99309ec0dc | ||
|
|
d08898ef37 | ||
|
|
b7e35ae3ed | ||
|
|
7ba34535b7 | ||
|
|
8cbcbbd7a4 | ||
|
|
36a5c7d8f5 | ||
|
|
5b74a71dd3 | ||
|
|
716b2a4bcb | ||
|
|
13c8fe3fb3 | ||
|
|
da38822dfd | ||
|
|
f36d81070b | ||
|
|
959b725ab6 | ||
|
|
3891f015d7 | ||
|
|
8cfb4aafcd | ||
|
|
6ce9a7f225 | ||
|
|
1ed62628a7 | ||
|
|
b5328d01cf | ||
|
|
72a62d49b3 | ||
|
|
886898b137 | ||
|
|
a9ca6d4065 | ||
|
|
00683549ae | ||
|
|
f32bacb32d | ||
|
|
5a12d60c6f | ||
|
|
f627ff2383 | ||
|
|
79028397ad | ||
|
|
1b5e1c4753 | ||
|
|
855fb26a81 | ||
|
|
ab922ed1ba | ||
|
|
f198e81af1 | ||
|
|
83cbf141a8 | ||
|
|
9fd183e74f | ||
|
|
31173ff4b4 | ||
|
|
bf9aa4465b | ||
|
|
5312340bf9 | ||
|
|
61e2568b8d | ||
|
|
1392dcc273 | ||
|
|
117bea4e4e | ||
|
|
86a22a3896 | ||
|
|
95a89ad192 | ||
|
|
5153f67a2e | ||
|
|
4de01190a1 | ||
|
|
d213bd1fee | ||
|
|
80c19df4bb | ||
|
|
897cf72933 | ||
|
|
11bfab67f9 | ||
|
|
62db7a6dda | ||
|
|
a11e8093e5 | ||
|
|
3965e1053e | ||
|
|
e41dbdad90 | ||
|
|
64496690bb | ||
|
|
f572cdea47 | ||
|
|
5e775929be | ||
|
|
65724133ea | ||
|
|
a63c34a902 | ||
|
|
dbc729d049 | ||
|
|
7b134b8317 | ||
|
|
22e9815928 | ||
|
|
a109ae1490 | ||
|
|
f019b80af0 | ||
|
|
0ffec0a9ad | ||
|
|
841f9c3627 | ||
|
|
20321bb476 | ||
|
|
4bbc693d9c | ||
|
|
3ae1bd5b78 | ||
|
|
ce837da935 | ||
|
|
1b4ddefe27 | ||
|
|
abf6acc211 | ||
|
|
7873213b11 | ||
|
|
97ae184d0a | ||
|
|
8f0c02043b | ||
|
|
1e7f10e387 | ||
|
|
718acb0a86 | ||
|
|
85097a871f | ||
|
|
14ecd1929b | ||
|
|
8418464d91 | ||
|
|
f6b96480d1 | ||
|
|
29af953cec | ||
|
|
8e407acd76 | ||
|
|
2635028933 | ||
|
|
605562467d | ||
|
|
2d22f9c3d3 | ||
|
|
19db1d88bc | ||
|
|
6a740a6d67 | ||
|
|
9fd56318fe | ||
|
|
fdbbf65fea | ||
|
|
f6c6fba812 | ||
|
|
a24fca671e | ||
|
|
d313d97947 | ||
|
|
5ecd5093a9 | ||
|
|
99de8aa557 | ||
|
|
5e04e5b85d | ||
|
|
0458062bce | ||
|
|
9ae6bcc5df | ||
|
|
727ae2b877 | ||
|
|
30b857bc8a | ||
|
|
dafad17a9b | ||
|
|
8098d23768 | ||
|
|
5d9d51ba2d | ||
|
|
eea9144ebe | ||
|
|
97cf3f70a5 | ||
|
|
2e9655a4e0 | ||
|
|
3d666b3992 | ||
|
|
a246be587e | ||
|
|
f2ea12571f | ||
|
|
165ac1d556 | ||
|
|
1018999096 | ||
|
|
04691a13da | ||
|
|
e34e5b27ba | ||
|
|
66ee9d209d | ||
|
|
830f2cbdd3 | ||
|
|
b6b06f67dc | ||
|
|
3edc93f117 | ||
|
|
86f078dbea | ||
|
|
f739b7e377 |
+137
-40
@@ -1,12 +1,15 @@
|
||||
version: 2.1
|
||||
orbs:
|
||||
go: gotest/tools@0.0.9
|
||||
go: gotest/tools@0.0.13
|
||||
|
||||
executors:
|
||||
golang:
|
||||
docker:
|
||||
- image: circleci/golang:1.13
|
||||
- image: circleci/golang:1.14.2
|
||||
resource_class: 2xlarge
|
||||
ubuntu:
|
||||
docker:
|
||||
- image: ubuntu:19.10
|
||||
|
||||
commands:
|
||||
install-deps:
|
||||
@@ -24,6 +27,8 @@ commands:
|
||||
description: is a darwin build environment?
|
||||
type: boolean
|
||||
steps:
|
||||
- checkout
|
||||
- git_fetch_all_tags
|
||||
- checkout
|
||||
- when:
|
||||
condition: << parameters.linux >>
|
||||
@@ -37,17 +42,36 @@ commands:
|
||||
- restore_cache:
|
||||
name: Restore parameters cache
|
||||
keys:
|
||||
- 'v20-1k-lotus-params-{{ checksum "build/proof-params/parameters.json" }}-{{ checksum "build/paramfetch.go" }}'
|
||||
- 'v20-1k-lotus-params-{{ checksum "build/proof-params/parameters.json" }}-'
|
||||
- 'v25-2k-lotus-params'
|
||||
paths:
|
||||
- /var/tmp/filecoin-proof-parameters/
|
||||
- run: ./lotus fetch-params --proving-params 1024
|
||||
- run: ./lotus fetch-params 2048
|
||||
- save_cache:
|
||||
name: Save parameters cache
|
||||
key: 'v20-1k-lotus-params-{{ checksum "build/proof-params/parameters.json" }}-{{ checksum "build/paramfetch.go" }}'
|
||||
key: 'v25-2k-lotus-params'
|
||||
paths:
|
||||
- /var/tmp/filecoin-proof-parameters/
|
||||
|
||||
install_ipfs:
|
||||
steps:
|
||||
- run: |
|
||||
apt update
|
||||
apt install -y wget
|
||||
wget https://github.com/ipfs/go-ipfs/releases/download/v0.4.22/go-ipfs_v0.4.22_linux-amd64.tar.gz
|
||||
wget https://github.com/ipfs/go-ipfs/releases/download/v0.4.22/go-ipfs_v0.4.22_linux-amd64.tar.gz.sha512
|
||||
if [ "$(sha512sum go-ipfs_v0.4.22_linux-amd64.tar.gz)" != "$(cat go-ipfs_v0.4.22_linux-amd64.tar.gz.sha512)" ]
|
||||
then
|
||||
echo "ipfs failed checksum check"
|
||||
exit 1
|
||||
fi
|
||||
tar -xf go-ipfs_v0.4.22_linux-amd64.tar.gz
|
||||
mv go-ipfs/ipfs /usr/local/bin/ipfs
|
||||
chmod +x /usr/local/bin/ipfs
|
||||
git_fetch_all_tags:
|
||||
steps:
|
||||
- run:
|
||||
name: fetch all tags
|
||||
command: |
|
||||
git fetch --all
|
||||
|
||||
jobs:
|
||||
mod-tidy-check:
|
||||
@@ -55,7 +79,6 @@ jobs:
|
||||
steps:
|
||||
- install-deps
|
||||
- prepare
|
||||
- go/mod-download
|
||||
- go/mod-tidy-check
|
||||
|
||||
build-all:
|
||||
@@ -63,18 +86,29 @@ jobs:
|
||||
steps:
|
||||
- install-deps
|
||||
- prepare
|
||||
- go/mod-download
|
||||
- run: sudo apt-get update
|
||||
- run: sudo apt-get install npm
|
||||
- restore_cache:
|
||||
name: restore go mod cache
|
||||
key: v1-go-deps-{{ arch }}-{{ checksum "/home/circleci/project/go.mod" }}
|
||||
- run:
|
||||
command: make buildall
|
||||
- store_artifacts:
|
||||
path: lotus
|
||||
- store_artifacts:
|
||||
path: lotus-storage-miner
|
||||
- store_artifacts:
|
||||
path: lotus-seal-worker
|
||||
- run: mkdir linux && mv lotus lotus-storage-miner lotus-seal-worker linux/
|
||||
- persist_to_workspace:
|
||||
root: "."
|
||||
paths:
|
||||
- linux
|
||||
|
||||
build-debug:
|
||||
executor: golang
|
||||
steps:
|
||||
- install-deps
|
||||
- prepare
|
||||
- run:
|
||||
command: make debug
|
||||
|
||||
test: &test
|
||||
description: |
|
||||
@@ -91,6 +125,9 @@ jobs:
|
||||
type: string
|
||||
default: "./..."
|
||||
description: Import paths of packages to be tested.
|
||||
winpost-test:
|
||||
type: string
|
||||
default: "0"
|
||||
test-suite-name:
|
||||
type: string
|
||||
default: unit
|
||||
@@ -101,7 +138,7 @@ jobs:
|
||||
description: gotestsum format. https://github.com/gotestyourself/gotestsum#format
|
||||
coverage:
|
||||
type: string
|
||||
default: -coverprofile=coverage.txt
|
||||
default: -coverprofile=coverage.txt -coverpkg=github.com/filecoin-project/lotus/...
|
||||
description: Coverage flag. Set to the empty string to disable.
|
||||
codecov-upload:
|
||||
type: boolean
|
||||
@@ -113,10 +150,6 @@ jobs:
|
||||
steps:
|
||||
- install-deps
|
||||
- prepare
|
||||
- go/mod-download
|
||||
- restore_cache:
|
||||
name: restore go mod cache
|
||||
key: v1-go-deps-{{ arch }}-{{ checksum "/home/circleci/project/go.mod" }}
|
||||
- run:
|
||||
command: make deps lotus
|
||||
no_output_timeout: 30m
|
||||
@@ -128,6 +161,7 @@ jobs:
|
||||
environment:
|
||||
GOTESTSUM_JUNITFILE: /tmp/test-reports/<< parameters.test-suite-name >>/junit.xml
|
||||
GOTESTSUM_FORMAT: << parameters.gotestsum-format >>
|
||||
LOTUS_TEST_WINDOW_POST: << parameters.winpost-test >>
|
||||
command: |
|
||||
mkdir -p /tmp/test-reports/<< parameters.test-suite-name >>
|
||||
gotestsum -- \
|
||||
@@ -146,15 +180,10 @@ jobs:
|
||||
shell: /bin/bash -eo pipefail
|
||||
command: |
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
- save_cache:
|
||||
name: save go mod cache
|
||||
key: v1-go-deps-{{ arch }}-{{ checksum "/home/circleci/project/go.mod" }}
|
||||
paths:
|
||||
- "~/go/pkg"
|
||||
- "~/go/src/github.com"
|
||||
- "~/go/src/golang.org"
|
||||
|
||||
test-short:
|
||||
test-short:
|
||||
<<: *test
|
||||
test-window-post:
|
||||
<<: *test
|
||||
|
||||
build-macos:
|
||||
@@ -169,8 +198,8 @@ jobs:
|
||||
- run:
|
||||
name: Install go
|
||||
command: |
|
||||
curl -O https://dl.google.com/go/go1.13.4.darwin-amd64.pkg && \
|
||||
sudo installer -pkg go1.13.4.darwin-amd64.pkg -target /
|
||||
curl -O https://dl.google.com/go/go1.14.2.darwin-amd64.pkg && \
|
||||
sudo installer -pkg go1.14.2.darwin-amd64.pkg -target /
|
||||
- run:
|
||||
name: Install pkg-config
|
||||
command: HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config
|
||||
@@ -182,21 +211,26 @@ jobs:
|
||||
- run:
|
||||
name: Install jq
|
||||
command: |
|
||||
mkdir $HOME/.bin
|
||||
curl --location https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64 --output $HOME/.bin/jq
|
||||
chmod +x $HOME/.bin/jq
|
||||
curl --location https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64 --output /usr/local/bin/jq
|
||||
chmod +x /usr/local/bin/jq
|
||||
- restore_cache:
|
||||
name: restore go mod and cargo cache
|
||||
name: restore cargo cache
|
||||
key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }}
|
||||
- install-deps
|
||||
- go/mod-download
|
||||
- run:
|
||||
command: make buildall
|
||||
command: make build
|
||||
no_output_timeout: 30m
|
||||
- store_artifacts:
|
||||
path: lotus
|
||||
- store_artifacts:
|
||||
path: lotus-storage-miner
|
||||
- store_artifacts:
|
||||
path: lotus-seal-worker
|
||||
- run: mkdir darwin && mv lotus lotus-storage-miner lotus-seal-worker darwin/
|
||||
- persist_to_workspace:
|
||||
root: "."
|
||||
paths:
|
||||
- darwin
|
||||
- save_cache:
|
||||
name: save cargo cache
|
||||
key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }}
|
||||
@@ -204,6 +238,14 @@ jobs:
|
||||
- "~/.rustup"
|
||||
- "~/.cargo"
|
||||
|
||||
gofmt:
|
||||
executor: golang
|
||||
steps:
|
||||
- install-deps
|
||||
- prepare
|
||||
- run:
|
||||
command: "! go fmt ./... 2>&1 | read"
|
||||
|
||||
lint: &lint
|
||||
description: |
|
||||
Run golangci-lint.
|
||||
@@ -213,7 +255,7 @@ jobs:
|
||||
default: golang
|
||||
golangci-lint-version:
|
||||
type: string
|
||||
default: 1.17.1
|
||||
default: 1.27.0
|
||||
concurrency:
|
||||
type: string
|
||||
default: '2'
|
||||
@@ -229,7 +271,6 @@ jobs:
|
||||
steps:
|
||||
- install-deps
|
||||
- prepare
|
||||
- go/mod-download
|
||||
- run:
|
||||
command: make deps
|
||||
no_output_timeout: 30m
|
||||
@@ -239,7 +280,7 @@ jobs:
|
||||
- run:
|
||||
name: Lint
|
||||
command: |
|
||||
$HOME/.local/bin/golangci-lint run -v \
|
||||
$HOME/.local/bin/golangci-lint run -v --timeout 2m \
|
||||
--concurrency << parameters.concurrency >> << parameters.args >>
|
||||
lint-changes:
|
||||
<<: *lint
|
||||
@@ -247,6 +288,26 @@ jobs:
|
||||
lint-all:
|
||||
<<: *lint
|
||||
|
||||
publish:
|
||||
description: publish binary artifacts
|
||||
executor: ubuntu
|
||||
steps:
|
||||
- run:
|
||||
name: Install git jq curl
|
||||
command: apt update && apt install -y git jq curl
|
||||
- checkout
|
||||
- git_fetch_all_tags
|
||||
- checkout
|
||||
- install_ipfs
|
||||
- attach_workspace:
|
||||
at: "."
|
||||
- run:
|
||||
name: Create bundles
|
||||
command: ./scripts/build-bundle.sh
|
||||
- run:
|
||||
name: Publish release
|
||||
command: ./scripts/publish-release.sh
|
||||
|
||||
|
||||
workflows:
|
||||
version: 2.1
|
||||
@@ -254,9 +315,45 @@ workflows:
|
||||
jobs:
|
||||
- lint-changes:
|
||||
args: "--new-from-rev origin/master"
|
||||
- test
|
||||
- mod-tidy-check
|
||||
- gofmt
|
||||
- test:
|
||||
codecov-upload: true
|
||||
- test-window-post:
|
||||
go-test-flags: "-run=TestWindowedPost"
|
||||
winpost-test: "1"
|
||||
- test-short:
|
||||
go-test-flags: "--timeout 10m --short"
|
||||
- mod-tidy-check
|
||||
- build-all
|
||||
- build-macos
|
||||
filters:
|
||||
tags:
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+$/
|
||||
- build-debug
|
||||
- build-all:
|
||||
requires:
|
||||
- test-short
|
||||
filters:
|
||||
tags:
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+$/
|
||||
- build-macos:
|
||||
requires:
|
||||
- test-short
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- /.*/
|
||||
tags:
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+$/
|
||||
- publish:
|
||||
requires:
|
||||
- build-all
|
||||
- build-macos
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- /.*/
|
||||
tags:
|
||||
only:
|
||||
- /^v\d+\.\d+\.\d+$/
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
comment: off
|
||||
ignore:
|
||||
- "cbor_gen.go"
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
.gitignore
|
||||
@@ -21,7 +21,7 @@ A clear and concise description of what you expected to happen.
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Version (run `lotus --version`):**
|
||||
**Version (run `lotus version`):**
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: Sealing Issues
|
||||
about: Create a report for help with sealing (commit) failures.
|
||||
title: ''
|
||||
labels: 'sealing'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please provide all the information requested here to help us troubleshoot "commit failed" issues.
|
||||
If the information requested is missing, we will probably have to just ask you to provide it anyway,
|
||||
before we can help debug.
|
||||
|
||||
**Describe the problem**
|
||||
|
||||
A brief description of the problem you encountered while proving (sealing) a sector.
|
||||
|
||||
Including what commands you ran, and a description of your setup, is very helpful.
|
||||
|
||||
**Sectors list**
|
||||
|
||||
The output of `./lotus-storage-miner sectors list`.
|
||||
|
||||
**Sectors status**
|
||||
|
||||
The output of `./lotus-storage-miner sectors status --log <sectorId>` for the failed sector(s).
|
||||
|
||||
**Lotus storage miner logs**
|
||||
|
||||
Please go through the logs of your storage miner, and include screenshots of any error-like messages you find.
|
||||
|
||||
**Version**
|
||||
|
||||
The output of `./lotus --version`.
|
||||
+6
-1
@@ -2,6 +2,8 @@
|
||||
/lotus-storage-miner
|
||||
/lotus-seal-worker
|
||||
/lotus-seed
|
||||
/lotus-health
|
||||
/lotus-shed
|
||||
/pond
|
||||
/townhall
|
||||
/fountain
|
||||
@@ -13,7 +15,6 @@
|
||||
/cmd/lotus-townhall/townhall/node_modules
|
||||
/cmd/lotus-townhall/townhall/build
|
||||
extern/filecoin-ffi/rust/target
|
||||
**/*.h
|
||||
**/*.a
|
||||
**/*.pc
|
||||
/**/*/.DS_STORE
|
||||
@@ -25,9 +26,13 @@ build/paramfetch.sh
|
||||
/blocks.svg
|
||||
/chainwatch
|
||||
/chainwatch.db
|
||||
/bundle
|
||||
/darwin
|
||||
/linux
|
||||
|
||||
*-fuzz.zip
|
||||
/chain/types/work_msg/
|
||||
bin/ipget
|
||||
bin/tmp/*
|
||||
.idea
|
||||
scratchpad
|
||||
|
||||
@@ -2,3 +2,6 @@
|
||||
path = extern/filecoin-ffi
|
||||
url = https://github.com/filecoin-project/filecoin-ffi.git
|
||||
branch = master
|
||||
[submodule "extern/serialization-vectors"]
|
||||
path = extern/serialization-vectors
|
||||
url = https://github.com/filecoin-project/serialization-vectors
|
||||
|
||||
+17
-2
@@ -1,7 +1,8 @@
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- vet
|
||||
- gofmt
|
||||
- govet
|
||||
- goimports
|
||||
- misspell
|
||||
- goconst
|
||||
@@ -21,13 +22,27 @@ issues:
|
||||
- "func name will be used as test\\.Test.* by other packages, and that stutters; consider calling this"
|
||||
- "Potential file inclusion via variable"
|
||||
- "should have( a package)? comment"
|
||||
- "Error return value of `logging.SetLogLevel` is not checked"
|
||||
|
||||
exclude-use-default: false
|
||||
exclude-rules:
|
||||
- path: lotuspond
|
||||
linters:
|
||||
- errcheck
|
||||
|
||||
- path: node/modules/lp2p
|
||||
linters:
|
||||
- golint
|
||||
- path: ".*_test.go"
|
||||
|
||||
- path: build/params_.*\.go
|
||||
linters:
|
||||
- golint
|
||||
|
||||
- path: api/apistruct/struct.go
|
||||
linters:
|
||||
- golint
|
||||
|
||||
- path: .*_test.go
|
||||
linters:
|
||||
- gosec
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ SHELL=/usr/bin/env bash
|
||||
all: build
|
||||
.PHONY: all
|
||||
|
||||
unexport GOFLAGS
|
||||
|
||||
GOVERSION:=$(shell go version | cut -d' ' -f 3 | cut -d. -f 2)
|
||||
ifeq ($(shell expr $(GOVERSION) \< 13), 1)
|
||||
ifeq ($(shell expr $(GOVERSION) \< 14), 1)
|
||||
$(warning Your Golang version is go 1.$(GOVERSION))
|
||||
$(error Update Golang to version $(shell grep '^go' go.mod))
|
||||
endif
|
||||
@@ -14,12 +16,19 @@ MODULES:=
|
||||
|
||||
CLEAN:=
|
||||
BINS:=
|
||||
GOFLAGS+=-ldflags="-X "github.com/filecoin-project/lotus/build".CurrentCommit=+git$(subst -,.,$(shell git describe --always --match=NeVeRmAtCh --dirty 2>/dev/null || git rev-parse --short HEAD 2>/dev/null))"
|
||||
|
||||
ldflags=-X=github.com/filecoin-project/lotus/build.CurrentCommit=+git.$(subst -,.,$(shell git describe --always --match=NeVeRmAtCh --dirty 2>/dev/null || git rev-parse --short HEAD 2>/dev/null))
|
||||
ifneq ($(strip $(LDFLAGS)),)
|
||||
ldflags+=-extldflags=$(LDFLAGS)
|
||||
endif
|
||||
|
||||
GOFLAGS+=-ldflags="$(ldflags)"
|
||||
|
||||
|
||||
## FFI
|
||||
|
||||
FFI_PATH:=extern/filecoin-ffi/
|
||||
FFI_DEPS:=libfilecoin.a filecoin.pc filecoin.h
|
||||
FFI_DEPS:=.install-filcrypto
|
||||
FFI_DEPS:=$(addprefix $(FFI_PATH),$(FFI_DEPS))
|
||||
|
||||
$(FFI_DEPS): build/.filecoin-install ;
|
||||
@@ -51,6 +60,9 @@ deps: $(BUILD_DEPS)
|
||||
debug: GOFLAGS+=-tags=debug
|
||||
debug: lotus lotus-storage-miner lotus-seal-worker lotus-seed
|
||||
|
||||
2k: GOFLAGS+=-tags=2k
|
||||
2k: lotus lotus-storage-miner lotus-seal-worker lotus-seed
|
||||
|
||||
lotus: $(BUILD_DEPS)
|
||||
rm -f lotus
|
||||
go build $(GOFLAGS) -o lotus ./cmd/lotus
|
||||
@@ -76,8 +88,9 @@ BINS+=lotus-seal-worker
|
||||
lotus-shed: $(BUILD_DEPS)
|
||||
rm -f lotus-shed
|
||||
go build $(GOFLAGS) -o lotus-shed ./cmd/lotus-shed
|
||||
.PHONY: lotus-seal-worker
|
||||
BINS+=lotus-seal-worker
|
||||
go run github.com/GeertJohan/go.rice/rice append --exec lotus-shed -i ./build
|
||||
.PHONY: lotus-shed
|
||||
BINS+=lotus-shed
|
||||
|
||||
build: lotus lotus-storage-miner lotus-seal-worker
|
||||
@[[ $$(type -P "lotus") ]] && echo "Caution: you have \
|
||||
@@ -90,6 +103,21 @@ install:
|
||||
install -C ./lotus-storage-miner /usr/local/bin/lotus-storage-miner
|
||||
install -C ./lotus-seal-worker /usr/local/bin/lotus-seal-worker
|
||||
|
||||
install-services: install
|
||||
mkdir -p /usr/local/lib/systemd/system
|
||||
mkdir -p /var/log/lotus
|
||||
install -C -m 0644 ./scripts/lotus-daemon.service /usr/local/lib/systemd/system/lotus-daemon.service
|
||||
install -C -m 0644 ./scripts/lotus-miner.service /usr/local/lib/systemd/system/lotus-miner.service
|
||||
systemctl daemon-reload
|
||||
@echo
|
||||
@echo "lotus-daemon and lotus-miner services installed. Don't forget to 'systemctl enable lotus-daemon|lotus-miner' for it to be enabled on startup."
|
||||
|
||||
clean-services:
|
||||
rm -f /usr/local/lib/systemd/system/lotus-daemon.service
|
||||
rm -f /usr/local/lib/systemd/system/lotus-miner.service
|
||||
rm -f /usr/local/lib/systemd/system/chainwatch.service
|
||||
systemctl daemon-reload
|
||||
|
||||
# TOOLS
|
||||
|
||||
lotus-seed: $(BUILD_DEPS)
|
||||
@@ -106,7 +134,7 @@ benchmarks:
|
||||
@curl -X POST 'http://benchmark.kittyhawk.wtf/benchmark' -d '@bench.json' -u "${benchmark_http_cred}"
|
||||
.PHONY: benchmarks
|
||||
|
||||
pond: build
|
||||
pond: 2k
|
||||
go build -o pond ./lotuspond
|
||||
(cd lotuspond/front && npm i && CI=false npm run build)
|
||||
.PHONY: pond
|
||||
@@ -123,17 +151,23 @@ BINS+=townhall
|
||||
fountain:
|
||||
rm -f fountain
|
||||
go build -o fountain ./cmd/lotus-fountain
|
||||
go run github.com/GeertJohan/go.rice/rice append --exec fountain -i ./cmd/lotus-fountain
|
||||
go run github.com/GeertJohan/go.rice/rice append --exec fountain -i ./cmd/lotus-fountain -i ./build
|
||||
.PHONY: fountain
|
||||
BINS+=fountain
|
||||
|
||||
chainwatch:
|
||||
rm -f chainwatch
|
||||
go build -o chainwatch ./cmd/lotus-chainwatch
|
||||
go run github.com/GeertJohan/go.rice/rice append --exec chainwatch -i ./cmd/lotus-chainwatch
|
||||
.PHONY: chainwatch
|
||||
BINS+=chainwatch
|
||||
|
||||
install-chainwatch-service: chainwatch
|
||||
install -C ./chainwatch /usr/local/bin/chainwatch
|
||||
install -C -m 0644 ./scripts/chainwatch.service /usr/local/lib/systemd/system/chainwatch.service
|
||||
systemctl daemon-reload
|
||||
@echo
|
||||
@echo "chainwatch installed. Don't forget to 'systemctl enable chainwatch' for it to be enabled on startup."
|
||||
|
||||
bench:
|
||||
rm -f bench
|
||||
go build -o bench ./cmd/lotus-bench
|
||||
@@ -144,13 +178,40 @@ BINS+=bench
|
||||
stats:
|
||||
rm -f stats
|
||||
go build -o stats ./tools/stats
|
||||
go run github.com/GeertJohan/go.rice/rice append --exec stats -i ./build
|
||||
.PHONY: stats
|
||||
BINS+=stats
|
||||
|
||||
health:
|
||||
rm -f lotus-health
|
||||
go build -o lotus-health ./cmd/lotus-health
|
||||
go run github.com/GeertJohan/go.rice/rice append --exec lotus-health -i ./build
|
||||
|
||||
.PHONY: health
|
||||
BINS+=health
|
||||
|
||||
testground:
|
||||
go build -tags testground -o /dev/null ./cmd/lotus
|
||||
|
||||
.PHONY: testground
|
||||
BINS+=testground
|
||||
|
||||
# MISC
|
||||
|
||||
buildall: $(BINS)
|
||||
|
||||
completions:
|
||||
./scripts/make-completions.sh lotus
|
||||
./scripts/make-completions.sh lotus-storage-miner
|
||||
.PHONY: completions
|
||||
|
||||
install-completions:
|
||||
mkdir -p /usr/share/bash-completion/completions /usr/local/share/zsh/site-functions/
|
||||
install -C ./scripts/bash-completion/lotus /usr/share/bash-completion/completions/lotus
|
||||
install -C ./scripts/bash-completion/lotus-storage-miner /usr/share/bash-completion/completions/lotus-storage-miner
|
||||
install -C ./scripts/zsh-completion/lotus /usr/local/share/zsh/site-functions/_lotus
|
||||
install -C ./scripts/zsh-completion/lotus-storage-miner /usr/local/share/zsh/site-functions/_lotus-storage-miner
|
||||
|
||||
clean:
|
||||
rm -rf $(CLEAN) $(BINS)
|
||||
-$(MAKE) -C $(FFI_PATH) clean
|
||||
@@ -164,5 +225,10 @@ dist-clean:
|
||||
type-gen:
|
||||
go run ./gen/main.go
|
||||
|
||||
method-gen:
|
||||
(cd ./lotuspond/front/src/chain && go run ./methodgen.go)
|
||||
|
||||
gen: type-gen method-gen
|
||||
|
||||
print-%:
|
||||
@echo $*=$($*)
|
||||
|
||||
@@ -2,16 +2,25 @@
|
||||
|
||||
# Project Lotus - 莲
|
||||
|
||||
Lotus is an experimental implementation of the Filecoin Distributed Storage Network. For more details about Filecoin, check out the [Filecoin Spec](https://github.com/filecoin-project/specs).
|
||||
|
||||
## Development
|
||||
|
||||
All work is tracked via issues. An attempt at keeping an up-to-date view on remaining work is in the [lotus testnet github project board](https://github.com/filecoin-project/lotus/projects/1).
|
||||
Lotus is an implementation of the Filecoin Distributed Storage Network. For more details about Filecoin, check out the [Filecoin Spec](https://github.com/filecoin-project/specs).
|
||||
|
||||
## Building & Documentation
|
||||
|
||||
For instructions on how to build lotus from source, please visit [https://docs.lotu.sh](https://docs.lotu.sh) or read the source [here](https://github.com/filecoin-project/lotus/tree/master/documentation).
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please send an email to security@filecoin.org. See our [security policy](SECURITY.md) for more details.
|
||||
|
||||
## Development
|
||||
|
||||
All work is tracked via issues. An attempt at keeping an up-to-date view on remaining work is in the [lotus testnet github project board](https://github.com/filecoin-project/lotus/projects/1).
|
||||
|
||||
The main branches under development at the moment are:
|
||||
* [`master`](https://github.com/filecoin-project/lotus): current testnet.
|
||||
* [`next`](https://github.com/filecoin-project/lotus/tree/next): working branch with chain-breaking changes.
|
||||
* [`ntwk-calibration`](https://github.com/filecoin-project/lotus/tree/ntwk-calibration): devnet running one of `next` commits.
|
||||
|
||||
## License
|
||||
|
||||
Dual-licensed under [MIT](https://github.com/filecoin-project/lotus/blob/master/LICENSE-MIT) + [Apache 2.0](https://github.com/filecoin-project/lotus/blob/master/LICENSE-APACHE)
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
For *critical* bugs, please send an email to security@filecoin.org.
|
||||
|
||||
The bug reporting process differs between bugs that are critical and may crash the network, and others that are unlikely to cause problems if malicious parties know about it. For non-critical bugs, please simply file a GitHub [issue](https://github.com/filecoin-project/lotus/issues/new?template=bug_report.md).
|
||||
|
||||
Please try to provide a clear description of any bugs reported, along with how to reproduce the bug if possible. More detailed bug reports (especially those with a PoC included) will help us move forward much faster. Additionally, please avoid reporting bugs that already have open issues. Take a moment to search the issue list of the related GitHub repositories before writing up a new report.
|
||||
|
||||
Here are some examples of bugs we would consider 'critical':
|
||||
|
||||
* If you can spend from a `multisig` wallet you do not control the keys for.
|
||||
* If you can cause a miner to be slashed without them actually misbehaving.
|
||||
* If you can maintain power without submitting windowed posts regularly.
|
||||
* If you can craft a message that causes lotus nodes to panic.
|
||||
* If you can cause your miner to win significantly more blocks than it should.
|
||||
* If you can craft a message that causes a persistent fork in the network.
|
||||
* If you can cause the total amount of Filecoin in the network to no longer be 2 billion.
|
||||
|
||||
This is not an exhaustive list, but should provide some idea of what we consider 'critical'.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
* TODO: This should be defined and set up by Mainnet launch.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| Testnet | :white_check_mark: |
|
||||
+22
-7
@@ -4,31 +4,46 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
|
||||
"github.com/filecoin-project/go-jsonrpc/auth"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
)
|
||||
|
||||
type Permission = string
|
||||
|
||||
type Common interface {
|
||||
// Auth
|
||||
AuthVerify(ctx context.Context, token string) ([]Permission, error)
|
||||
AuthNew(ctx context.Context, perms []Permission) ([]byte, error)
|
||||
|
||||
// network
|
||||
// MethodGroup: Auth
|
||||
|
||||
AuthVerify(ctx context.Context, token string) ([]auth.Permission, error)
|
||||
AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error)
|
||||
|
||||
// MethodGroup: Net
|
||||
|
||||
NetConnectedness(context.Context, peer.ID) (network.Connectedness, error)
|
||||
NetPeers(context.Context) ([]peer.AddrInfo, error)
|
||||
NetConnect(context.Context, peer.AddrInfo) error
|
||||
NetAddrsListen(context.Context) (peer.AddrInfo, error)
|
||||
NetDisconnect(context.Context, peer.ID) error
|
||||
NetFindPeer(context.Context, peer.ID) (peer.AddrInfo, error)
|
||||
NetPubsubScores(context.Context) ([]PubsubScore, error)
|
||||
|
||||
// MethodGroup: Common
|
||||
|
||||
// ID returns peerID of libp2p node backing this API
|
||||
ID(context.Context) (peer.ID, error)
|
||||
|
||||
// Version provides information about API provider
|
||||
Version(context.Context) (Version, error)
|
||||
|
||||
LogList(context.Context) ([]string, error)
|
||||
LogSetLevel(context.Context, string, string) error
|
||||
|
||||
// trigger graceful shutdown
|
||||
Shutdown(context.Context) error
|
||||
|
||||
Closing(context.Context) (<-chan struct{}, error)
|
||||
}
|
||||
|
||||
// Version provides various build-time information
|
||||
|
||||
+414
-101
@@ -8,79 +8,208 @@ import (
|
||||
"github.com/ipfs/go-filestore"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-fil-markets/storagemarket"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/market"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/power"
|
||||
"github.com/filecoin-project/specs-actors/actors/crypto"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
)
|
||||
|
||||
// FullNode API is a low-level interface to the Filecoin network full node
|
||||
type FullNode interface {
|
||||
Common
|
||||
|
||||
// chain
|
||||
// MethodGroup: Chain
|
||||
// The Chain method group contains methods for interacting with the
|
||||
// blockchain, but that do not require any form of state computation.
|
||||
|
||||
// ChainNotify returns channel with chain head updates
|
||||
// First message is guaranteed to be of len == 1, and type == 'current'
|
||||
ChainNotify(context.Context) (<-chan []*store.HeadChange, error)
|
||||
// ChainNotify returns channel with chain head updates.
|
||||
// First message is guaranteed to be of len == 1, and type == 'current'.
|
||||
ChainNotify(context.Context) (<-chan []*HeadChange, error)
|
||||
|
||||
// ChainHead returns the current head of the chain.
|
||||
ChainHead(context.Context) (*types.TipSet, error)
|
||||
ChainGetRandomness(context.Context, types.TipSetKey, int64) ([]byte, error)
|
||||
ChainGetBlock(context.Context, cid.Cid) (*types.BlockHeader, error)
|
||||
ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error)
|
||||
ChainGetBlockMessages(context.Context, cid.Cid) (*BlockMessages, error)
|
||||
ChainGetParentReceipts(context.Context, cid.Cid) ([]*types.MessageReceipt, error)
|
||||
ChainGetParentMessages(context.Context, cid.Cid) ([]Message, error)
|
||||
ChainGetTipSetByHeight(context.Context, uint64, *types.TipSet) (*types.TipSet, error)
|
||||
ChainReadObj(context.Context, cid.Cid) ([]byte, error)
|
||||
ChainSetHead(context.Context, *types.TipSet) error
|
||||
ChainGetGenesis(context.Context) (*types.TipSet, error)
|
||||
ChainTipSetWeight(context.Context, *types.TipSet) (types.BigInt, error)
|
||||
ChainGetNode(ctx context.Context, p string) (interface{}, error)
|
||||
|
||||
// syncer
|
||||
// ChainGetRandomness is used to sample the chain for randomness.
|
||||
ChainGetRandomness(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error)
|
||||
|
||||
// ChainGetBlock returns the block specified by the given CID.
|
||||
ChainGetBlock(context.Context, cid.Cid) (*types.BlockHeader, error)
|
||||
// ChainGetTipSet returns the tipset specified by the given TipSetKey.
|
||||
ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error)
|
||||
|
||||
// ChainGetBlockMessages returns messages stored in the specified block.
|
||||
ChainGetBlockMessages(ctx context.Context, blockCid cid.Cid) (*BlockMessages, error)
|
||||
|
||||
// ChainGetParentReceipts returns receipts for messages in parent tipset of
|
||||
// the specified block.
|
||||
ChainGetParentReceipts(ctx context.Context, blockCid cid.Cid) ([]*types.MessageReceipt, error)
|
||||
|
||||
// ChainGetParentMessages returns messages stored in parent tipset of the
|
||||
// specified block.
|
||||
ChainGetParentMessages(ctx context.Context, blockCid cid.Cid) ([]Message, error)
|
||||
|
||||
// ChainGetTipSetByHeight looks back for a tipset at the specified epoch.
|
||||
// If there are no blocks at the specified epoch, a tipset at an earlier epoch
|
||||
// will be returned.
|
||||
ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error)
|
||||
|
||||
// ChainReadObj reads ipld nodes referenced by the specified CID from chain
|
||||
// blockstore and returns raw bytes.
|
||||
ChainReadObj(context.Context, cid.Cid) ([]byte, error)
|
||||
|
||||
// ChainHasObj checks if a given CID exists in the chain blockstore.
|
||||
ChainHasObj(context.Context, cid.Cid) (bool, error)
|
||||
ChainStatObj(context.Context, cid.Cid, cid.Cid) (ObjStat, error)
|
||||
|
||||
// ChainSetHead forcefully sets current chain head. Use with caution.
|
||||
ChainSetHead(context.Context, types.TipSetKey) error
|
||||
|
||||
// ChainGetGenesis returns the genesis tipset.
|
||||
ChainGetGenesis(context.Context) (*types.TipSet, error)
|
||||
|
||||
// ChainTipSetWeight computes weight for the specified tipset.
|
||||
ChainTipSetWeight(context.Context, types.TipSetKey) (types.BigInt, error)
|
||||
ChainGetNode(ctx context.Context, p string) (*IpldObject, error)
|
||||
|
||||
// ChainGetMessage reads a message referenced by the specified CID from the
|
||||
// chain blockstore.
|
||||
ChainGetMessage(context.Context, cid.Cid) (*types.Message, error)
|
||||
|
||||
// ChainGetPath returns a set of revert/apply operations needed to get from
|
||||
// one tipset to another, for example:
|
||||
//```
|
||||
// to
|
||||
// ^
|
||||
// from tAA
|
||||
// ^ ^
|
||||
// tBA tAB
|
||||
// ^---*--^
|
||||
// ^
|
||||
// tRR
|
||||
//```
|
||||
// Would return `[revert(tBA), apply(tAB), apply(tAA)]`
|
||||
ChainGetPath(ctx context.Context, from types.TipSetKey, to types.TipSetKey) ([]*HeadChange, error)
|
||||
|
||||
// ChainExport returns a stream of bytes with CAR dump of chain data.
|
||||
ChainExport(context.Context, types.TipSetKey) (<-chan []byte, error)
|
||||
|
||||
// MethodGroup: Sync
|
||||
// The Sync method group contains methods for interacting with and
|
||||
// observing the lotus sync service.
|
||||
|
||||
// SyncState returns the current status of the lotus sync system.
|
||||
SyncState(context.Context) (*SyncState, error)
|
||||
|
||||
// SyncSubmitBlock can be used to submit a newly created block to the.
|
||||
// network through this node
|
||||
SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) error
|
||||
|
||||
// SyncIncomingBlocks returns a channel streaming incoming, potentially not
|
||||
// yet synced block headers.
|
||||
SyncIncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error)
|
||||
|
||||
// messages
|
||||
MpoolPending(context.Context, *types.TipSet) ([]*types.SignedMessage, error)
|
||||
MpoolPush(context.Context, *types.SignedMessage) error // TODO: remove
|
||||
MpoolPushMessage(context.Context, *types.Message) (*types.SignedMessage, error) // get nonce, sign, push
|
||||
// SyncMarkBad marks a blocks as bad, meaning that it won't ever by synced.
|
||||
// Use with extreme caution.
|
||||
SyncMarkBad(ctx context.Context, bcid cid.Cid) error
|
||||
|
||||
// SyncCheckBad checks if a block was marked as bad, and if it was, returns
|
||||
// the reason.
|
||||
SyncCheckBad(ctx context.Context, bcid cid.Cid) (string, error)
|
||||
|
||||
// MethodGroup: Mpool
|
||||
// The Mpool methods are for interacting with the message pool. The message pool
|
||||
// manages all incoming and outgoing 'messages' going over the network.
|
||||
|
||||
// MpoolPending returns pending mempool messages.
|
||||
MpoolPending(context.Context, types.TipSetKey) ([]*types.SignedMessage, error)
|
||||
|
||||
// MpoolPush pushes a signed message to mempool.
|
||||
MpoolPush(context.Context, *types.SignedMessage) (cid.Cid, error)
|
||||
|
||||
// MpoolPushMessage atomically assigns a nonce, signs, and pushes a message
|
||||
// to mempool.
|
||||
MpoolPushMessage(context.Context, *types.Message) (*types.SignedMessage, error)
|
||||
|
||||
// MpoolGetNonce gets next nonce for the specified sender.
|
||||
// Note that this method may not be atomic. Use MpoolPushMessage instead.
|
||||
MpoolGetNonce(context.Context, address.Address) (uint64, error)
|
||||
MpoolSub(context.Context) (<-chan MpoolUpdate, error)
|
||||
|
||||
// FullNodeStruct
|
||||
// MpoolEstimateGasPrice estimates what gas price should be used for a
|
||||
// message to have high likelihood of inclusion in `nblocksincl` epochs.
|
||||
MpoolEstimateGasPrice(ctx context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error)
|
||||
|
||||
// miner
|
||||
// MethodGroup: Miner
|
||||
|
||||
MinerCreateBlock(context.Context, address.Address, *types.TipSet, *types.Ticket, *types.EPostProof, []*types.SignedMessage, uint64, uint64) (*types.BlockMsg, error)
|
||||
MinerGetBaseInfo(context.Context, address.Address, abi.ChainEpoch, types.TipSetKey) (*MiningBaseInfo, error)
|
||||
MinerCreateBlock(context.Context, *BlockTemplate) (*types.BlockMsg, error)
|
||||
|
||||
// // UX ?
|
||||
|
||||
// wallet
|
||||
// MethodGroup: Wallet
|
||||
|
||||
WalletNew(context.Context, string) (address.Address, error)
|
||||
// WalletNew creates a new address in the wallet with the given sigType.
|
||||
WalletNew(context.Context, crypto.SigType) (address.Address, error)
|
||||
// WalletHas indicates whether the given address is in the wallet.
|
||||
WalletHas(context.Context, address.Address) (bool, error)
|
||||
// WalletList lists all the addresses in the wallet.
|
||||
WalletList(context.Context) ([]address.Address, error)
|
||||
// WalletBalance returns the balance of the given address at the current head of the chain.
|
||||
WalletBalance(context.Context, address.Address) (types.BigInt, error)
|
||||
WalletSign(context.Context, address.Address, []byte) (*types.Signature, error)
|
||||
// WalletSign signs the given bytes using the given address.
|
||||
WalletSign(context.Context, address.Address, []byte) (*crypto.Signature, error)
|
||||
// WalletSignMessage signs the given message using the given address.
|
||||
WalletSignMessage(context.Context, address.Address, *types.Message) (*types.SignedMessage, error)
|
||||
// WalletVerify takes an address, a signature, and some bytes, and indicates whether the signature is valid.
|
||||
// The address does not have to be in the wallet.
|
||||
WalletVerify(context.Context, address.Address, []byte, *crypto.Signature) bool
|
||||
// WalletDefaultAddress returns the address marked as default in the wallet.
|
||||
WalletDefaultAddress(context.Context) (address.Address, error)
|
||||
// WalletSetDefault marks the given address as as the default one.
|
||||
WalletSetDefault(context.Context, address.Address) error
|
||||
// WalletExport returns the private key of an address in the wallet.
|
||||
WalletExport(context.Context, address.Address) (*types.KeyInfo, error)
|
||||
// WalletImport receives a KeyInfo, which includes a private key, and imports it into the wallet.
|
||||
WalletImport(context.Context, *types.KeyInfo) (address.Address, error)
|
||||
// WalletDelete deletes an address from the wallet.
|
||||
WalletDelete(context.Context, address.Address) error
|
||||
|
||||
// Other
|
||||
|
||||
// ClientImport imports file under the specified path into filestore
|
||||
ClientImport(ctx context.Context, path string) (cid.Cid, error)
|
||||
ClientStartDeal(ctx context.Context, data cid.Cid, addr address.Address, miner address.Address, epochPrice types.BigInt, blocksDuration uint64) (*cid.Cid, error)
|
||||
// MethodGroup: Client
|
||||
// The Client methods all have to do with interacting with the storage and
|
||||
// retrieval markets as a client
|
||||
|
||||
// ClientImport imports file under the specified path into filestore.
|
||||
ClientImport(ctx context.Context, ref FileRef) (cid.Cid, error)
|
||||
// ClientStartDeal proposes a deal with a miner.
|
||||
ClientStartDeal(ctx context.Context, params *StartDealParams) (*cid.Cid, error)
|
||||
// ClientGetDealInfo returns the latest information about a given deal.
|
||||
ClientGetDealInfo(context.Context, cid.Cid) (*DealInfo, error)
|
||||
// ClientListDeals returns information about the deals made by the local client.
|
||||
ClientListDeals(ctx context.Context) ([]DealInfo, error)
|
||||
// ClientHasLocal indicates whether a certain CID is locally stored.
|
||||
ClientHasLocal(ctx context.Context, root cid.Cid) (bool, error)
|
||||
// ClientFindData identifies peers that have a certain file, and returns QueryOffers (one per peer).
|
||||
ClientFindData(ctx context.Context, root cid.Cid) ([]QueryOffer, error)
|
||||
ClientRetrieve(ctx context.Context, order RetrievalOrder, path string) error
|
||||
ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*types.SignedStorageAsk, error)
|
||||
// ClientMinerQueryOffer returns a QueryOffer for the specific miner and file.
|
||||
ClientMinerQueryOffer(ctx context.Context, root cid.Cid, miner address.Address) (QueryOffer, error)
|
||||
// ClientRetrieve initiates the retrieval of a file, as specified in the order.
|
||||
ClientRetrieve(ctx context.Context, order RetrievalOrder, ref *FileRef) error
|
||||
// ClientQueryAsk returns a signed StorageAsk from the specified miner.
|
||||
ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.SignedStorageAsk, error)
|
||||
// ClientCalcCommP calculates the CommP for a specified file, based on the sector size of the provided miner.
|
||||
ClientCalcCommP(ctx context.Context, inpath string, miner address.Address) (*CommPRet, error)
|
||||
// ClientGenCar generates a CAR file for the specified file.
|
||||
ClientGenCar(ctx context.Context, ref FileRef, outpath string) error
|
||||
|
||||
// ClientUnimport removes references to the specified file from filestore
|
||||
//ClientUnimport(path string)
|
||||
@@ -90,52 +219,149 @@ type FullNode interface {
|
||||
|
||||
//ClientListAsks() []Ask
|
||||
|
||||
// if tipset is nil, we'll use heaviest
|
||||
StateCall(context.Context, *types.Message, *types.TipSet) (*types.MessageReceipt, error)
|
||||
StateReplay(context.Context, *types.TipSet, cid.Cid) (*ReplayResults, error)
|
||||
StateGetActor(ctx context.Context, actor address.Address, ts *types.TipSet) (*types.Actor, error)
|
||||
StateReadState(ctx context.Context, act *types.Actor, ts *types.TipSet) (*ActorState, error)
|
||||
// MethodGroup: State
|
||||
// The State methods are used to query, inspect, and interact with chain state.
|
||||
// All methods take a TipSetKey as a parameter. The state looked up is the state at that tipset.
|
||||
// A nil TipSetKey can be provided as a param, this will cause the heaviest tipset in the chain to be used.
|
||||
|
||||
StateMinerSectors(context.Context, address.Address, *types.TipSet) ([]*ChainSectorInfo, error)
|
||||
StateMinerProvingSet(context.Context, address.Address, *types.TipSet) ([]*ChainSectorInfo, error)
|
||||
StateMinerPower(context.Context, address.Address, *types.TipSet) (MinerPower, error)
|
||||
StateMinerWorker(context.Context, address.Address, *types.TipSet) (address.Address, error)
|
||||
StateMinerPeerID(ctx context.Context, m address.Address, ts *types.TipSet) (peer.ID, error)
|
||||
StateMinerElectionPeriodStart(ctx context.Context, actor address.Address, ts *types.TipSet) (uint64, error)
|
||||
StateMinerSectorSize(context.Context, address.Address, *types.TipSet) (uint64, error)
|
||||
StatePledgeCollateral(context.Context, *types.TipSet) (types.BigInt, error)
|
||||
StateWaitMsg(context.Context, cid.Cid) (*MsgWait, error)
|
||||
StateListMiners(context.Context, *types.TipSet) ([]address.Address, error)
|
||||
StateListActors(context.Context, *types.TipSet) ([]address.Address, error)
|
||||
StateMarketBalance(context.Context, address.Address, *types.TipSet) (actors.StorageParticipantBalance, error)
|
||||
StateMarketParticipants(context.Context, *types.TipSet) (map[string]actors.StorageParticipantBalance, error)
|
||||
StateMarketDeals(context.Context, *types.TipSet) (map[string]actors.OnChainDeal, error)
|
||||
StateMarketStorageDeal(context.Context, uint64, *types.TipSet) (*actors.OnChainDeal, error)
|
||||
StateLookupID(context.Context, address.Address, *types.TipSet) (address.Address, error)
|
||||
// StateCall runs the given message and returns its result without any persisted changes.
|
||||
StateCall(context.Context, *types.Message, types.TipSetKey) (*InvocResult, error)
|
||||
// StateReplay returns the result of executing the indicated message, assuming it was executed in the indicated tipset.
|
||||
StateReplay(context.Context, types.TipSetKey, cid.Cid) (*InvocResult, error)
|
||||
// StateGetActor returns the indicated actor's nonce and balance.
|
||||
StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error)
|
||||
// StateReadState returns the indicated actor's state.
|
||||
StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*ActorState, error)
|
||||
// StateListMessages looks back and returns all messages with a matching to or from address, stopping at the given height.
|
||||
StateListMessages(ctx context.Context, match *types.Message, tsk types.TipSetKey, toht abi.ChainEpoch) ([]cid.Cid, error)
|
||||
|
||||
// StateNetworkName returns the name of the network the node is synced to
|
||||
StateNetworkName(context.Context) (dtypes.NetworkName, error)
|
||||
// StateMinerSectors returns info about the given miner's sectors. If the filter bitfield is nil, all sectors are included.
|
||||
// If the filterOut boolean is set to true, any sectors in the filter are excluded.
|
||||
// If false, only those sectors in the filter are included.
|
||||
StateMinerSectors(context.Context, address.Address, *abi.BitField, bool, types.TipSetKey) ([]*ChainSectorInfo, error)
|
||||
// StateMinerProvingSet returns info about those sectors that a given miner is actively proving.
|
||||
StateMinerProvingSet(context.Context, address.Address, types.TipSetKey) ([]*ChainSectorInfo, error)
|
||||
// StateMinerProvingDeadline calculates the deadline at some epoch for a proving period
|
||||
// and returns the deadline-related calculations.
|
||||
StateMinerProvingDeadline(context.Context, address.Address, types.TipSetKey) (*miner.DeadlineInfo, error)
|
||||
// StateMinerPower returns the power of the indicated miner
|
||||
StateMinerPower(context.Context, address.Address, types.TipSetKey) (*MinerPower, error)
|
||||
// StateMinerInfo returns info about the indicated miner
|
||||
StateMinerInfo(context.Context, address.Address, types.TipSetKey) (MinerInfo, error)
|
||||
// StateMinerDeadlines returns all the proving deadlines for the given miner
|
||||
StateMinerDeadlines(context.Context, address.Address, types.TipSetKey) (*miner.Deadlines, error)
|
||||
// StateMinerFaults returns a bitfield indicating the faulty sectors of the given miner
|
||||
StateMinerFaults(context.Context, address.Address, types.TipSetKey) (*abi.BitField, error)
|
||||
// StateAllMinerFaults returns all non-expired Faults that occur within lookback epochs of the given tipset
|
||||
StateAllMinerFaults(ctx context.Context, lookback abi.ChainEpoch, ts types.TipSetKey) ([]*Fault, error)
|
||||
// StateMinerRecoveries returns a bitfield indicating the recovering sectors of the given miner
|
||||
StateMinerRecoveries(context.Context, address.Address, types.TipSetKey) (*abi.BitField, error)
|
||||
// StateMinerInitialPledgeCollateral returns the initial pledge collateral for the specified miner's sector
|
||||
StateMinerInitialPledgeCollateral(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (types.BigInt, error)
|
||||
// StateMinerAvailableBalance returns the portion of a miner's balance that can be withdrawn or spent
|
||||
StateMinerAvailableBalance(context.Context, address.Address, types.TipSetKey) (types.BigInt, error)
|
||||
// StateSectorPreCommitInfo returns the PreCommit info for the specified miner's sector
|
||||
StateSectorPreCommitInfo(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error)
|
||||
// StateSectorGetInfo returns the on-chain info for the specified miner's sector
|
||||
StateSectorGetInfo(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*miner.SectorOnChainInfo, error)
|
||||
StatePledgeCollateral(context.Context, types.TipSetKey) (types.BigInt, error)
|
||||
// StateSearchMsg searches for a message in the chain, and returns its receipt and the tipset where it was executed
|
||||
StateSearchMsg(context.Context, cid.Cid) (*MsgLookup, error)
|
||||
// StateWaitMsg looks back in the chain for a message. If not found, it blocks until the
|
||||
// message arrives on chain, and gets to the indicated confidence depth.
|
||||
StateWaitMsg(ctx context.Context, cid cid.Cid, confidence uint64) (*MsgLookup, error)
|
||||
// StateListMiners returns the addresses of every miner that has claimed power in the Power Actor
|
||||
StateListMiners(context.Context, types.TipSetKey) ([]address.Address, error)
|
||||
// StateListActors returns the addresses of every actor in the state
|
||||
StateListActors(context.Context, types.TipSetKey) ([]address.Address, error)
|
||||
// StateMarketBalance looks up the Escrow and Locked balances of the given address in the Storage Market
|
||||
StateMarketBalance(context.Context, address.Address, types.TipSetKey) (MarketBalance, error)
|
||||
// StateMarketParticipants returns the Escrow and Locked balances of every participant in the Storage Market
|
||||
StateMarketParticipants(context.Context, types.TipSetKey) (map[string]MarketBalance, error)
|
||||
// StateMarketDeals returns information about every deal in the Storage Market
|
||||
StateMarketDeals(context.Context, types.TipSetKey) (map[string]MarketDeal, error)
|
||||
// StateMarketStorageDeal returns information about the indicated deal
|
||||
StateMarketStorageDeal(context.Context, abi.DealID, types.TipSetKey) (*MarketDeal, error)
|
||||
// StateLookupID retrieves the ID address of the given address
|
||||
StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error)
|
||||
// StateAccountKey returns the public key address of the given ID address
|
||||
StateAccountKey(context.Context, address.Address, types.TipSetKey) (address.Address, error)
|
||||
// StateChangedActors returns all the actors whose states change between the two given state CIDs
|
||||
// TODO: Should this take tipset keys instead?
|
||||
StateChangedActors(context.Context, cid.Cid, cid.Cid) (map[string]types.Actor, error)
|
||||
StateGetReceipt(context.Context, cid.Cid, *types.TipSet) (*types.MessageReceipt, error)
|
||||
StateMinerSectorCount(context.Context, address.Address, *types.TipSet) (MinerSectors, error)
|
||||
// StateGetReceipt returns the message receipt for the given message
|
||||
StateGetReceipt(context.Context, cid.Cid, types.TipSetKey) (*types.MessageReceipt, error)
|
||||
// StateMinerSectorCount returns the number of sectors in a miner's sector set and proving set
|
||||
StateMinerSectorCount(context.Context, address.Address, types.TipSetKey) (MinerSectors, error)
|
||||
// StateCompute is a flexible command that applies the given messages on the given tipset.
|
||||
// The messages are run as though the VM were at the provided height.
|
||||
StateCompute(context.Context, abi.ChainEpoch, []*types.Message, types.TipSetKey) (*ComputeStateOutput, error)
|
||||
|
||||
MarketEnsureAvailable(context.Context, address.Address, types.BigInt) error
|
||||
// MethodGroup: Msig
|
||||
// The Msig methods are used to interact with multisig wallets on the
|
||||
// filecoin network
|
||||
|
||||
// MsigGetAvailableBalance returns the portion of a multisig's balance that can be withdrawn or spent
|
||||
MsigGetAvailableBalance(context.Context, address.Address, types.TipSetKey) (types.BigInt, error)
|
||||
// MsigCreate creates a multisig wallet
|
||||
// It takes the following params: <required number of senders>, <approving addresses>, <unlock duration>
|
||||
//<initial balance>, <sender address of the create msg>, <gas price>
|
||||
MsigCreate(context.Context, int64, []address.Address, abi.ChainEpoch, types.BigInt, address.Address, types.BigInt) (cid.Cid, error)
|
||||
// MsigPropose proposes a multisig message
|
||||
// It takes the following params: <multisig address>, <recipient address>, <value to transfer>,
|
||||
// <sender address of the propose msg>, <method to call in the proposed message>, <params to include in the proposed message>
|
||||
MsigPropose(context.Context, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error)
|
||||
// MsigApprove approves a previously-proposed multisig message
|
||||
// It takes the following params: <multisig address>, <proposed message ID>, <proposer address>, <recipient address>, <value to transfer>,
|
||||
// <sender address of the approve msg>, <method to call in the proposed message>, <params to include in the proposed message>
|
||||
MsigApprove(context.Context, address.Address, uint64, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error)
|
||||
// MsigCancel cancels a previously-proposed multisig message
|
||||
// It takes the following params: <multisig address>, <proposed message ID>, <recipient address>, <value to transfer>,
|
||||
// <sender address of the cancel msg>, <method to call in the proposed message>, <params to include in the proposed message>
|
||||
MsigCancel(context.Context, address.Address, uint64, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error)
|
||||
// MsigSwapPropose proposes swapping 2 signers in the multisig
|
||||
// It takes the following params: <multisig address>, <sender address of the propose msg>,
|
||||
// <old signer> <new signer>
|
||||
MsigSwapPropose(context.Context, address.Address, address.Address, address.Address, address.Address) (cid.Cid, error)
|
||||
// MsigSwapApprove approves a previously proposed SwapSigner
|
||||
// It takes the following params: <multisig address>, <sender address of the approve msg>, <proposed message ID>,
|
||||
// <proposer address>, <old signer> <new signer>
|
||||
MsigSwapApprove(context.Context, address.Address, address.Address, uint64, address.Address, address.Address, address.Address) (cid.Cid, error)
|
||||
// MsigSwapCancel cancels a previously proposed SwapSigner message
|
||||
// It takes the following params: <multisig address>, <sender address of the cancel msg>, <proposed message ID>,
|
||||
// <old signer> <new signer>
|
||||
MsigSwapCancel(context.Context, address.Address, address.Address, uint64, address.Address, address.Address) (cid.Cid, error)
|
||||
|
||||
MarketEnsureAvailable(context.Context, address.Address, address.Address, types.BigInt) (cid.Cid, error)
|
||||
// MarketFreeBalance
|
||||
|
||||
// MethodGroup: Paych
|
||||
// The Paych methods are for interacting with and managing payment channels
|
||||
|
||||
PaychGet(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*ChannelInfo, error)
|
||||
PaychList(context.Context) ([]address.Address, error)
|
||||
PaychStatus(context.Context, address.Address) (*PaychStatus, error)
|
||||
PaychClose(context.Context, address.Address) (cid.Cid, error)
|
||||
PaychAllocateLane(ctx context.Context, ch address.Address) (uint64, error)
|
||||
PaychNewPayment(ctx context.Context, from, to address.Address, vouchers []VoucherSpec) (*PaymentInfo, error)
|
||||
PaychVoucherCheckValid(context.Context, address.Address, *types.SignedVoucher) error
|
||||
PaychVoucherCheckSpendable(context.Context, address.Address, *types.SignedVoucher, []byte, []byte) (bool, error)
|
||||
PaychVoucherCreate(context.Context, address.Address, types.BigInt, uint64) (*types.SignedVoucher, error)
|
||||
PaychVoucherAdd(context.Context, address.Address, *types.SignedVoucher, []byte, types.BigInt) (types.BigInt, error)
|
||||
PaychVoucherList(context.Context, address.Address) ([]*types.SignedVoucher, error)
|
||||
PaychVoucherSubmit(context.Context, address.Address, *types.SignedVoucher) (cid.Cid, error)
|
||||
PaychVoucherCheckValid(context.Context, address.Address, *paych.SignedVoucher) error
|
||||
PaychVoucherCheckSpendable(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (bool, error)
|
||||
PaychVoucherCreate(context.Context, address.Address, types.BigInt, uint64) (*paych.SignedVoucher, error)
|
||||
PaychVoucherAdd(context.Context, address.Address, *paych.SignedVoucher, []byte, types.BigInt) (types.BigInt, error)
|
||||
PaychVoucherList(context.Context, address.Address) ([]*paych.SignedVoucher, error)
|
||||
PaychVoucherSubmit(context.Context, address.Address, *paych.SignedVoucher) (cid.Cid, error)
|
||||
}
|
||||
|
||||
type FileRef struct {
|
||||
Path string
|
||||
IsCAR bool
|
||||
}
|
||||
|
||||
type MinerSectors struct {
|
||||
Pset uint64
|
||||
Sset uint64
|
||||
Pset uint64
|
||||
}
|
||||
|
||||
type Import struct {
|
||||
@@ -147,19 +373,24 @@ type Import struct {
|
||||
|
||||
type DealInfo struct {
|
||||
ProposalCid cid.Cid
|
||||
State DealState
|
||||
State storagemarket.StorageDealStatus
|
||||
Message string // more information about deal state, particularly errors
|
||||
Provider address.Address
|
||||
|
||||
PieceRef []byte // cid bytes
|
||||
PieceCID cid.Cid
|
||||
Size uint64
|
||||
|
||||
PricePerEpoch types.BigInt
|
||||
Duration uint64
|
||||
|
||||
DealID abi.DealID
|
||||
}
|
||||
|
||||
type MsgWait struct {
|
||||
Receipt types.MessageReceipt
|
||||
TipSet *types.TipSet
|
||||
type MsgLookup struct {
|
||||
Receipt types.MessageReceipt
|
||||
ReturnDec interface{}
|
||||
TipSet types.TipSetKey
|
||||
Height abi.ChainEpoch
|
||||
}
|
||||
|
||||
type BlockMessages struct {
|
||||
@@ -175,9 +406,8 @@ type Message struct {
|
||||
}
|
||||
|
||||
type ChainSectorInfo struct {
|
||||
SectorID uint64
|
||||
CommD []byte
|
||||
CommR []byte
|
||||
Info miner.SectorOnChainInfo
|
||||
ID abi.SectorNumber
|
||||
}
|
||||
|
||||
type ActorState struct {
|
||||
@@ -206,20 +436,21 @@ type ChannelInfo struct {
|
||||
type PaymentInfo struct {
|
||||
Channel address.Address
|
||||
ChannelMessage *cid.Cid
|
||||
Vouchers []*types.SignedVoucher
|
||||
Vouchers []*paych.SignedVoucher
|
||||
}
|
||||
|
||||
type VoucherSpec struct {
|
||||
Amount types.BigInt
|
||||
TimeLock uint64
|
||||
MinClose uint64
|
||||
Amount types.BigInt
|
||||
TimeLockMin abi.ChainEpoch
|
||||
TimeLockMax abi.ChainEpoch
|
||||
MinSettle abi.ChainEpoch
|
||||
|
||||
Extra *types.ModVerifyParams
|
||||
Extra *paych.ModVerifyParams
|
||||
}
|
||||
|
||||
type MinerPower struct {
|
||||
MinerPower types.BigInt
|
||||
TotalPower types.BigInt
|
||||
MinerPower power.Claim
|
||||
TotalPower power.Claim
|
||||
}
|
||||
|
||||
type QueryOffer struct {
|
||||
@@ -227,42 +458,76 @@ type QueryOffer struct {
|
||||
|
||||
Root cid.Cid
|
||||
|
||||
Size uint64
|
||||
MinPrice types.BigInt
|
||||
|
||||
Miner address.Address
|
||||
MinerPeerID peer.ID
|
||||
Size uint64
|
||||
MinPrice types.BigInt
|
||||
PaymentInterval uint64
|
||||
PaymentIntervalIncrease uint64
|
||||
Miner address.Address
|
||||
MinerPeerID peer.ID
|
||||
}
|
||||
|
||||
func (o *QueryOffer) Order(client address.Address) RetrievalOrder {
|
||||
return RetrievalOrder{
|
||||
Root: o.Root,
|
||||
Size: o.Size,
|
||||
Total: o.MinPrice,
|
||||
|
||||
Client: client,
|
||||
Root: o.Root,
|
||||
Size: o.Size,
|
||||
Total: o.MinPrice,
|
||||
PaymentInterval: o.PaymentInterval,
|
||||
PaymentIntervalIncrease: o.PaymentIntervalIncrease,
|
||||
Client: client,
|
||||
|
||||
Miner: o.Miner,
|
||||
MinerPeerID: o.MinerPeerID,
|
||||
}
|
||||
}
|
||||
|
||||
type MarketBalance struct {
|
||||
Escrow big.Int
|
||||
Locked big.Int
|
||||
}
|
||||
|
||||
type MarketDeal struct {
|
||||
Proposal market.DealProposal
|
||||
State market.DealState
|
||||
}
|
||||
|
||||
type RetrievalOrder struct {
|
||||
// TODO: make this less unixfs specific
|
||||
Root cid.Cid
|
||||
Size uint64
|
||||
// TODO: support offset
|
||||
Total types.BigInt
|
||||
|
||||
Client address.Address
|
||||
Miner address.Address
|
||||
MinerPeerID peer.ID
|
||||
Total types.BigInt
|
||||
PaymentInterval uint64
|
||||
PaymentIntervalIncrease uint64
|
||||
Client address.Address
|
||||
Miner address.Address
|
||||
MinerPeerID peer.ID
|
||||
}
|
||||
|
||||
type ReplayResults struct {
|
||||
Msg *types.Message
|
||||
Receipt *types.MessageReceipt
|
||||
Error string
|
||||
type InvocResult struct {
|
||||
Msg *types.Message
|
||||
MsgRct *types.MessageReceipt
|
||||
ExecutionTrace types.ExecutionTrace
|
||||
Error string
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
type MethodCall struct {
|
||||
types.MessageReceipt
|
||||
Error string
|
||||
}
|
||||
|
||||
type StartDealParams struct {
|
||||
Data *storagemarket.DataRef
|
||||
Wallet address.Address
|
||||
Miner address.Address
|
||||
EpochPrice types.BigInt
|
||||
MinBlocksDuration uint64
|
||||
DealStartEpoch abi.ChainEpoch
|
||||
}
|
||||
|
||||
type IpldObject struct {
|
||||
Cid cid.Cid
|
||||
Obj interface{}
|
||||
}
|
||||
|
||||
type ActiveSync struct {
|
||||
@@ -270,7 +535,7 @@ type ActiveSync struct {
|
||||
Target *types.TipSet
|
||||
|
||||
Stage SyncStateStage
|
||||
Height uint64
|
||||
Height abi.ChainEpoch
|
||||
|
||||
Start time.Time
|
||||
End time.Time
|
||||
@@ -303,3 +568,51 @@ type MpoolUpdate struct {
|
||||
Type MpoolChange
|
||||
Message *types.SignedMessage
|
||||
}
|
||||
|
||||
type ComputeStateOutput struct {
|
||||
Root cid.Cid
|
||||
Trace []*InvocResult
|
||||
}
|
||||
|
||||
type MiningBaseInfo struct {
|
||||
MinerPower types.BigInt
|
||||
NetworkPower types.BigInt
|
||||
Sectors []abi.SectorInfo
|
||||
WorkerKey address.Address
|
||||
SectorSize abi.SectorSize
|
||||
PrevBeaconEntry types.BeaconEntry
|
||||
BeaconEntries []types.BeaconEntry
|
||||
}
|
||||
|
||||
type BlockTemplate struct {
|
||||
Miner address.Address
|
||||
Parents types.TipSetKey
|
||||
Ticket *types.Ticket
|
||||
Eproof *types.ElectionProof
|
||||
BeaconValues []types.BeaconEntry
|
||||
Messages []*types.SignedMessage
|
||||
Epoch abi.ChainEpoch
|
||||
Timestamp uint64
|
||||
WinningPoStProof []abi.PoStProof
|
||||
}
|
||||
|
||||
type CommPRet struct {
|
||||
Root cid.Cid
|
||||
Size abi.UnpaddedPieceSize
|
||||
}
|
||||
type HeadChange struct {
|
||||
Type string
|
||||
Val *types.TipSet
|
||||
}
|
||||
|
||||
type MsigProposeResponse int
|
||||
|
||||
const (
|
||||
MsigApprove MsigProposeResponse = iota
|
||||
MsigCancel
|
||||
)
|
||||
|
||||
type Fault struct {
|
||||
Miner address.Address
|
||||
Epoch abi.ChainEpoch
|
||||
}
|
||||
|
||||
+90
-69
@@ -1,112 +1,133 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/lib/sectorbuilder"
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-fil-markets/storagemarket"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/sector-storage/stores"
|
||||
"github.com/filecoin-project/sector-storage/storiface"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
)
|
||||
|
||||
// alias because cbor-gen doesn't like non-alias types
|
||||
type SectorState = uint64
|
||||
|
||||
const (
|
||||
UndefinedSectorState SectorState = iota
|
||||
|
||||
Empty // TODO: Is this useful
|
||||
Packing // sector not in sealStore, and not on chain
|
||||
|
||||
Unsealed // sealing / queued
|
||||
PreCommitting // on chain pre-commit
|
||||
PreCommitted // waiting for seed
|
||||
Committing
|
||||
CommitWait // waiting for message to land on chain
|
||||
Proving
|
||||
|
||||
SealFailed
|
||||
PreCommitFailed
|
||||
SealCommitFailed
|
||||
CommitFailed
|
||||
|
||||
FailedUnrecoverable
|
||||
|
||||
Faulty // sector is corrupted or gone for some reason
|
||||
FaultReported // sector has been declared as a fault on chain
|
||||
FaultedFinal // fault declared on chain
|
||||
)
|
||||
|
||||
var SectorStates = []string{
|
||||
UndefinedSectorState: "UndefinedSectorState",
|
||||
Empty: "Empty",
|
||||
Packing: "Packing",
|
||||
Unsealed: "Unsealed",
|
||||
PreCommitting: "PreCommitting",
|
||||
PreCommitted: "PreCommitted",
|
||||
Committing: "Committing",
|
||||
CommitWait: "CommitWait",
|
||||
Proving: "Proving",
|
||||
|
||||
SealFailed: "SealFailed",
|
||||
PreCommitFailed: "PreCommitFailed",
|
||||
SealCommitFailed: "SealCommitFailed",
|
||||
CommitFailed: "CommitFailed",
|
||||
|
||||
FailedUnrecoverable: "FailedUnrecoverable",
|
||||
|
||||
Faulty: "Faulty",
|
||||
FaultReported: "FaultReported",
|
||||
FaultedFinal: "FaultedFinal",
|
||||
}
|
||||
|
||||
// StorageMiner is a low-level interface to the Filecoin network storage miner node
|
||||
type StorageMiner interface {
|
||||
Common
|
||||
|
||||
ActorAddress(context.Context) (address.Address, error)
|
||||
|
||||
ActorSectorSize(context.Context, address.Address) (uint64, error)
|
||||
ActorSectorSize(context.Context, address.Address) (abi.SectorSize, error)
|
||||
|
||||
MiningBase(context.Context) (*types.TipSet, error)
|
||||
|
||||
// Temp api for testing
|
||||
PledgeSector(context.Context) error
|
||||
|
||||
// Get the status of a given sector by ID
|
||||
SectorsStatus(context.Context, uint64) (SectorInfo, error)
|
||||
SectorsStatus(context.Context, abi.SectorNumber) (SectorInfo, error)
|
||||
|
||||
// List all staged sectors
|
||||
SectorsList(context.Context) ([]uint64, error)
|
||||
SectorsList(context.Context) ([]abi.SectorNumber, error)
|
||||
|
||||
SectorsRefs(context.Context) (map[string][]SealedRef, error)
|
||||
|
||||
SectorsUpdate(context.Context, uint64, SectorState) error
|
||||
SectorsUpdate(context.Context, abi.SectorNumber, SectorState) error
|
||||
SectorRemove(context.Context, abi.SectorNumber) error
|
||||
|
||||
WorkerStats(context.Context) (sectorbuilder.WorkerStats, error)
|
||||
StorageList(ctx context.Context) (map[stores.ID][]stores.Decl, error)
|
||||
StorageLocal(ctx context.Context) (map[stores.ID]string, error)
|
||||
StorageStat(ctx context.Context, id stores.ID) (stores.FsStat, error)
|
||||
|
||||
// WorkerQueue registers a remote worker
|
||||
WorkerQueue(context.Context, sectorbuilder.WorkerCfg) (<-chan sectorbuilder.WorkerTask, error)
|
||||
// WorkerConnect tells the node to connect to workers RPC
|
||||
WorkerConnect(context.Context, string) error
|
||||
WorkerStats(context.Context) (map[uint64]storiface.WorkerStats, error)
|
||||
|
||||
WorkerDone(ctx context.Context, task uint64, res sectorbuilder.SealRes) error
|
||||
stores.SectorIndex
|
||||
|
||||
MarketImportDealData(ctx context.Context, propcid cid.Cid, path string) error
|
||||
MarketListDeals(ctx context.Context) ([]storagemarket.StorageDeal, error)
|
||||
MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error)
|
||||
MarketSetAsk(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error
|
||||
MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error)
|
||||
|
||||
DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error
|
||||
DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error)
|
||||
DealsConsiderOnlineStorageDeals(context.Context) (bool, error)
|
||||
DealsSetConsiderOnlineStorageDeals(context.Context, bool) error
|
||||
DealsConsiderOnlineRetrievalDeals(context.Context) (bool, error)
|
||||
DealsSetConsiderOnlineRetrievalDeals(context.Context, bool) error
|
||||
DealsPieceCidBlocklist(context.Context) ([]cid.Cid, error)
|
||||
DealsSetPieceCidBlocklist(context.Context, []cid.Cid) error
|
||||
DealsConsiderOfflineStorageDeals(context.Context) (bool, error)
|
||||
DealsSetConsiderOfflineStorageDeals(context.Context, bool) error
|
||||
DealsConsiderOfflineRetrievalDeals(context.Context) (bool, error)
|
||||
DealsSetConsiderOfflineRetrievalDeals(context.Context, bool) error
|
||||
|
||||
StorageAddLocal(ctx context.Context, path string) error
|
||||
}
|
||||
|
||||
type SealRes struct {
|
||||
Err string
|
||||
GoErr error `json:"-"`
|
||||
|
||||
Proof []byte
|
||||
}
|
||||
|
||||
type SectorLog struct {
|
||||
Kind string
|
||||
Timestamp uint64
|
||||
|
||||
Trace string
|
||||
|
||||
Message string
|
||||
}
|
||||
|
||||
type SectorInfo struct {
|
||||
SectorID uint64
|
||||
SectorID abi.SectorNumber
|
||||
State SectorState
|
||||
CommD []byte
|
||||
CommR []byte
|
||||
CommD *cid.Cid
|
||||
CommR *cid.Cid
|
||||
Proof []byte
|
||||
Deals []uint64
|
||||
Ticket sectorbuilder.SealTicket
|
||||
Seed sectorbuilder.SealSeed
|
||||
Deals []abi.DealID
|
||||
Ticket SealTicket
|
||||
Seed SealSeed
|
||||
Retries uint64
|
||||
|
||||
LastErr string
|
||||
|
||||
Log []SectorLog
|
||||
}
|
||||
|
||||
type SealedRef struct {
|
||||
SectorID uint64
|
||||
SectorID abi.SectorNumber
|
||||
Offset uint64
|
||||
Size uint64
|
||||
Size abi.UnpaddedPieceSize
|
||||
}
|
||||
|
||||
type SealedRefs struct {
|
||||
Refs []SealedRef
|
||||
}
|
||||
|
||||
type SealTicket struct {
|
||||
Value abi.SealRandomness
|
||||
Epoch abi.ChainEpoch
|
||||
}
|
||||
|
||||
type SealSeed struct {
|
||||
Value abi.InteractiveSealRandomness
|
||||
Epoch abi.ChainEpoch
|
||||
}
|
||||
|
||||
func (st *SealTicket) Equals(ost *SealTicket) bool {
|
||||
return bytes.Equal(st.Value, ost.Value) && st.Epoch == ost.Epoch
|
||||
}
|
||||
|
||||
func (st *SealSeed) Equals(ost *SealSeed) bool {
|
||||
return bytes.Equal(st.Value, ost.Value) && st.Epoch == ost.Epoch
|
||||
}
|
||||
|
||||
type SectorState string
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func goCmd() string {
|
||||
var exeSuffix string
|
||||
if runtime.GOOS == "windows" {
|
||||
exeSuffix = ".exe"
|
||||
}
|
||||
path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path
|
||||
}
|
||||
return "go"
|
||||
}
|
||||
|
||||
func TestDoesntDependOnFFI(t *testing.T) {
|
||||
deps, err := exec.Command(goCmd(), "list", "-deps", "github.com/filecoin-project/lotus/api").Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, pkg := range strings.Fields(string(deps)) {
|
||||
if pkg == "github.com/filecoin-project/filecoin-ffi" {
|
||||
t.Fatal("api depends on filecoin-ffi")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
"github.com/filecoin-project/sector-storage/sealtasks"
|
||||
"github.com/filecoin-project/sector-storage/stores"
|
||||
"github.com/filecoin-project/sector-storage/storiface"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-storage/storage"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
)
|
||||
|
||||
type WorkerAPI interface {
|
||||
Version(context.Context) (build.Version, error)
|
||||
// TODO: Info() (name, ...) ?
|
||||
|
||||
TaskTypes(context.Context) (map[sealtasks.TaskType]struct{}, error) // TaskType -> Weight
|
||||
Paths(context.Context) ([]stores.StoragePath, error)
|
||||
Info(context.Context) (storiface.WorkerInfo, error)
|
||||
|
||||
storage.Sealer
|
||||
|
||||
MoveStorage(ctx context.Context, sector abi.SectorID) error
|
||||
|
||||
UnsealPiece(context.Context, abi.SectorID, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize, abi.SealRandomness, cid.Cid) error
|
||||
ReadPiece(context.Context, io.Writer, abi.SectorID, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize) error
|
||||
|
||||
Fetch(context.Context, abi.SectorID, stores.SectorFileType, stores.PathType, stores.AcquireMode) error
|
||||
|
||||
Closing(context.Context) (<-chan struct{}, error)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package apibstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
blocks "github.com/ipfs/go-block-format"
|
||||
"github.com/ipfs/go-cid"
|
||||
blockstore "github.com/ipfs/go-ipfs-blockstore"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type ChainIO interface {
|
||||
ChainReadObj(context.Context, cid.Cid) ([]byte, error)
|
||||
ChainHasObj(context.Context, cid.Cid) (bool, error)
|
||||
}
|
||||
|
||||
type apiBStore struct {
|
||||
api ChainIO
|
||||
}
|
||||
|
||||
func NewAPIBlockstore(cio ChainIO) blockstore.Blockstore {
|
||||
return &apiBStore{
|
||||
api: cio,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *apiBStore) DeleteBlock(cid.Cid) error {
|
||||
return xerrors.New("not supported")
|
||||
}
|
||||
|
||||
func (a *apiBStore) Has(c cid.Cid) (bool, error) {
|
||||
return a.api.ChainHasObj(context.TODO(), c)
|
||||
}
|
||||
|
||||
func (a *apiBStore) Get(c cid.Cid) (blocks.Block, error) {
|
||||
bb, err := a.api.ChainReadObj(context.TODO(), c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return blocks.NewBlockWithCid(bb, c)
|
||||
}
|
||||
|
||||
func (a *apiBStore) GetSize(c cid.Cid) (int, error) {
|
||||
bb, err := a.api.ChainReadObj(context.TODO(), c)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(bb), nil
|
||||
}
|
||||
|
||||
func (a *apiBStore) Put(blocks.Block) error {
|
||||
return xerrors.New("not supported")
|
||||
}
|
||||
|
||||
func (a *apiBStore) PutMany([]blocks.Block) error {
|
||||
return xerrors.New("not supported")
|
||||
}
|
||||
|
||||
func (a *apiBStore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) {
|
||||
return nil, xerrors.New("not supported")
|
||||
}
|
||||
|
||||
func (a *apiBStore) HashOnRead(enabled bool) {
|
||||
return
|
||||
}
|
||||
|
||||
var _ blockstore.Blockstore = &apiBStore{}
|
||||
@@ -1,105 +1,38 @@
|
||||
package apistruct
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-jsonrpc/auth"
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
)
|
||||
|
||||
type permKey int
|
||||
|
||||
var permCtxKey permKey
|
||||
|
||||
const (
|
||||
// When changing these, update docs/API.md too
|
||||
|
||||
PermRead api.Permission = "read" // default
|
||||
PermWrite api.Permission = "write"
|
||||
PermSign api.Permission = "sign" // Use wallet keys for signing
|
||||
PermAdmin api.Permission = "admin" // Manage permissions
|
||||
PermRead auth.Permission = "read" // default
|
||||
PermWrite auth.Permission = "write"
|
||||
PermSign auth.Permission = "sign" // Use wallet keys for signing
|
||||
PermAdmin auth.Permission = "admin" // Manage permissions
|
||||
)
|
||||
|
||||
var AllPermissions = []api.Permission{PermRead, PermWrite, PermSign, PermAdmin}
|
||||
var defaultPerms = []api.Permission{PermRead}
|
||||
|
||||
func WithPerm(ctx context.Context, perms []api.Permission) context.Context {
|
||||
return context.WithValue(ctx, permCtxKey, perms)
|
||||
}
|
||||
var AllPermissions = []auth.Permission{PermRead, PermWrite, PermSign, PermAdmin}
|
||||
var DefaultPerms = []auth.Permission{PermRead}
|
||||
|
||||
func PermissionedStorMinerAPI(a api.StorageMiner) api.StorageMiner {
|
||||
var out StorageMinerStruct
|
||||
permissionedAny(a, &out.Internal)
|
||||
permissionedAny(a, &out.CommonStruct.Internal)
|
||||
auth.PermissionedProxy(AllPermissions, DefaultPerms, a, &out.Internal)
|
||||
auth.PermissionedProxy(AllPermissions, DefaultPerms, a, &out.CommonStruct.Internal)
|
||||
return &out
|
||||
}
|
||||
|
||||
func PermissionedFullAPI(a api.FullNode) api.FullNode {
|
||||
var out FullNodeStruct
|
||||
permissionedAny(a, &out.Internal)
|
||||
permissionedAny(a, &out.CommonStruct.Internal)
|
||||
auth.PermissionedProxy(AllPermissions, DefaultPerms, a, &out.Internal)
|
||||
auth.PermissionedProxy(AllPermissions, DefaultPerms, a, &out.CommonStruct.Internal)
|
||||
return &out
|
||||
}
|
||||
|
||||
func HasPerm(ctx context.Context, perm api.Permission) bool {
|
||||
callerPerms, ok := ctx.Value(permCtxKey).([]api.Permission)
|
||||
if !ok {
|
||||
callerPerms = defaultPerms
|
||||
}
|
||||
|
||||
for _, callerPerm := range callerPerms {
|
||||
if callerPerm == perm {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func permissionedAny(in interface{}, out interface{}) {
|
||||
rint := reflect.ValueOf(out).Elem()
|
||||
ra := reflect.ValueOf(in)
|
||||
|
||||
for f := 0; f < rint.NumField(); f++ {
|
||||
field := rint.Type().Field(f)
|
||||
requiredPerm := api.Permission(field.Tag.Get("perm"))
|
||||
if requiredPerm == "" {
|
||||
panic("missing 'perm' tag on " + field.Name) // ok
|
||||
}
|
||||
|
||||
// Validate perm tag
|
||||
ok := false
|
||||
for _, perm := range AllPermissions {
|
||||
if requiredPerm == perm {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
panic("unknown 'perm' tag on " + field.Name) // ok
|
||||
}
|
||||
|
||||
fn := ra.MethodByName(field.Name)
|
||||
|
||||
rint.Field(f).Set(reflect.MakeFunc(field.Type, func(args []reflect.Value) (results []reflect.Value) {
|
||||
ctx := args[0].Interface().(context.Context)
|
||||
if HasPerm(ctx, requiredPerm) {
|
||||
return fn.Call(args)
|
||||
}
|
||||
|
||||
err := xerrors.Errorf("missing permission to invoke '%s' (need '%s')", field.Name, requiredPerm)
|
||||
rerr := reflect.ValueOf(&err).Elem()
|
||||
|
||||
if field.Type.NumOut() == 2 {
|
||||
return []reflect.Value{
|
||||
reflect.Zero(field.Type.Out(0)),
|
||||
rerr,
|
||||
}
|
||||
} else {
|
||||
return []reflect.Value{rerr}
|
||||
}
|
||||
}))
|
||||
|
||||
}
|
||||
func PermissionedWorkerAPI(a api.WorkerAPI) api.WorkerAPI {
|
||||
var out WorkerStruct
|
||||
auth.PermissionedProxy(AllPermissions, DefaultPerms, a, &out.Internal)
|
||||
return &out
|
||||
}
|
||||
|
||||
+636
-167
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
package apistruct
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPermTags(t *testing.T) {
|
||||
_ = PermissionedFullAPI(&FullNodeStruct{})
|
||||
_ = PermissionedStorMinerAPI(&StorageMinerStruct{})
|
||||
_ = PermissionedWorkerAPI(&WorkerStruct{})
|
||||
}
|
||||
+530
-214
@@ -1,16 +1,17 @@
|
||||
// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
xerrors "golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT.
|
||||
|
||||
var _ = xerrors.Errorf
|
||||
|
||||
func (t *PaymentInfo) MarshalCBOR(w io.Writer) error {
|
||||
@@ -22,11 +23,17 @@ func (t *PaymentInfo) MarshalCBOR(w io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
scratch := make([]byte, 9)
|
||||
|
||||
// t.Channel (address.Address) (struct)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len("Channel")))); err != nil {
|
||||
if len("Channel") > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field \"Channel\" was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Channel"))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte("Channel")); err != nil {
|
||||
if _, err := io.WriteString(w, "Channel"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -35,10 +42,14 @@ func (t *PaymentInfo) MarshalCBOR(w io.Writer) error {
|
||||
}
|
||||
|
||||
// t.ChannelMessage (cid.Cid) (struct)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len("ChannelMessage")))); err != nil {
|
||||
if len("ChannelMessage") > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field \"ChannelMessage\" was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("ChannelMessage"))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte("ChannelMessage")); err != nil {
|
||||
if _, err := io.WriteString(w, "ChannelMessage"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -47,20 +58,28 @@ func (t *PaymentInfo) MarshalCBOR(w io.Writer) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := cbg.WriteCid(w, *t.ChannelMessage); err != nil {
|
||||
if err := cbg.WriteCidBuf(scratch, w, *t.ChannelMessage); err != nil {
|
||||
return xerrors.Errorf("failed to write cid field t.ChannelMessage: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// t.Vouchers ([]*types.SignedVoucher) (slice)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len("Vouchers")))); err != nil {
|
||||
// t.Vouchers ([]*paych.SignedVoucher) (slice)
|
||||
if len("Vouchers") > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field \"Vouchers\" was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Vouchers"))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte("Vouchers")); err != nil {
|
||||
if _, err := io.WriteString(w, "Vouchers"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajArray, uint64(len(t.Vouchers)))); err != nil {
|
||||
if len(t.Vouchers) > cbg.MaxLength {
|
||||
return xerrors.Errorf("Slice value in field t.Vouchers was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Vouchers))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range t.Vouchers {
|
||||
@@ -73,8 +92,9 @@ func (t *PaymentInfo) MarshalCBOR(w io.Writer) error {
|
||||
|
||||
func (t *PaymentInfo) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
scratch := make([]byte, 8)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
maj, extra, err := cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -82,114 +102,97 @@ func (t *PaymentInfo) UnmarshalCBOR(r io.Reader) error {
|
||||
return fmt.Errorf("cbor input should be of type map")
|
||||
}
|
||||
|
||||
if extra != 3 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
if extra > cbg.MaxLength {
|
||||
return fmt.Errorf("PaymentInfo: map struct too large (%d)", extra)
|
||||
}
|
||||
|
||||
var name string
|
||||
n := extra
|
||||
|
||||
// t.Channel (address.Address) (struct)
|
||||
for i := uint64(0); i < n; i++ {
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadString(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name = string(sval)
|
||||
}
|
||||
|
||||
if name != "Channel" {
|
||||
return fmt.Errorf("expected struct map entry %s to be Channel", name)
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
if err := t.Channel.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
// t.ChannelMessage (cid.Cid) (struct)
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadString(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name = string(sval)
|
||||
}
|
||||
|
||||
if name != "ChannelMessage" {
|
||||
return fmt.Errorf("expected struct map entry %s to be ChannelMessage", name)
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
pb, err := br.PeekByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pb == cbg.CborNull[0] {
|
||||
var nbuf [1]byte
|
||||
if _, err := br.Read(nbuf[:]); err != nil {
|
||||
{
|
||||
sval, err := cbg.ReadStringBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
||||
c, err := cbg.ReadCid(br)
|
||||
name = string(sval)
|
||||
}
|
||||
|
||||
switch name {
|
||||
// t.Channel (address.Address) (struct)
|
||||
case "Channel":
|
||||
|
||||
{
|
||||
|
||||
if err := t.Channel.UnmarshalCBOR(br); err != nil {
|
||||
return xerrors.Errorf("unmarshaling t.Channel: %w", err)
|
||||
}
|
||||
|
||||
}
|
||||
// t.ChannelMessage (cid.Cid) (struct)
|
||||
case "ChannelMessage":
|
||||
|
||||
{
|
||||
|
||||
pb, err := br.PeekByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pb == cbg.CborNull[0] {
|
||||
var nbuf [1]byte
|
||||
if _, err := br.Read(nbuf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
||||
c, err := cbg.ReadCid(br)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to read cid field t.ChannelMessage: %w", err)
|
||||
}
|
||||
|
||||
t.ChannelMessage = &c
|
||||
}
|
||||
|
||||
}
|
||||
// t.Vouchers ([]*paych.SignedVoucher) (slice)
|
||||
case "Vouchers":
|
||||
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to read cid field t.ChannelMessage: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
t.ChannelMessage = &c
|
||||
if extra > cbg.MaxLength {
|
||||
return fmt.Errorf("t.Vouchers: array too large (%d)", extra)
|
||||
}
|
||||
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
|
||||
if extra > 0 {
|
||||
t.Vouchers = make([]*paych.SignedVoucher, extra)
|
||||
}
|
||||
|
||||
for i := 0; i < int(extra); i++ {
|
||||
|
||||
var v paych.SignedVoucher
|
||||
if err := v.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Vouchers[i] = &v
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown struct field %d: '%s'", i, name)
|
||||
}
|
||||
|
||||
}
|
||||
// t.Vouchers ([]*types.SignedVoucher) (slice)
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadString(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name = string(sval)
|
||||
}
|
||||
|
||||
if name != "Vouchers" {
|
||||
return fmt.Errorf("expected struct map entry %s to be Vouchers", name)
|
||||
}
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if extra > cbg.MaxLength {
|
||||
return fmt.Errorf("t.Vouchers: array too large (%d)", extra)
|
||||
}
|
||||
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
if extra > 0 {
|
||||
t.Vouchers = make([]*types.SignedVoucher, extra)
|
||||
}
|
||||
for i := 0; i < int(extra); i++ {
|
||||
|
||||
var v types.SignedVoucher
|
||||
if err := v.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Vouchers[i] = &v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *SealedRef) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
@@ -199,48 +202,64 @@ func (t *SealedRef) MarshalCBOR(w io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.SectorID (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len("SectorID")))); err != nil {
|
||||
scratch := make([]byte, 9)
|
||||
|
||||
// t.SectorID (abi.SectorNumber) (uint64)
|
||||
if len("SectorID") > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field \"SectorID\" was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("SectorID"))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte("SectorID")); err != nil {
|
||||
if _, err := io.WriteString(w, "SectorID"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.SectorID))); err != nil {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.SectorID)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Offset (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len("Offset")))); err != nil {
|
||||
if len("Offset") > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field \"Offset\" was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Offset"))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte("Offset")); err != nil {
|
||||
if _, err := io.WriteString(w, "Offset"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.Offset))); err != nil {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Offset)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Size (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len("Size")))); err != nil {
|
||||
// t.Size (abi.UnpaddedPieceSize) (uint64)
|
||||
if len("Size") > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field \"Size\" was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Size"))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte("Size")); err != nil {
|
||||
if _, err := io.WriteString(w, "Size"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.Size))); err != nil {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Size)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *SealedRef) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
scratch := make([]byte, 8)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
maj, extra, err := cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -248,84 +267,78 @@ func (t *SealedRef) UnmarshalCBOR(r io.Reader) error {
|
||||
return fmt.Errorf("cbor input should be of type map")
|
||||
}
|
||||
|
||||
if extra != 3 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
if extra > cbg.MaxLength {
|
||||
return fmt.Errorf("SealedRef: map struct too large (%d)", extra)
|
||||
}
|
||||
|
||||
var name string
|
||||
n := extra
|
||||
|
||||
// t.SectorID (uint64) (uint64)
|
||||
for i := uint64(0); i < n; i++ {
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadString(br)
|
||||
if err != nil {
|
||||
return err
|
||||
{
|
||||
sval, err := cbg.ReadStringBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name = string(sval)
|
||||
}
|
||||
|
||||
name = string(sval)
|
||||
}
|
||||
switch name {
|
||||
// t.SectorID (abi.SectorNumber) (uint64)
|
||||
case "SectorID":
|
||||
|
||||
if name != "SectorID" {
|
||||
return fmt.Errorf("expected struct map entry %s to be SectorID", name)
|
||||
}
|
||||
{
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.SectorID = uint64(extra)
|
||||
// t.Offset (uint64) (uint64)
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.SectorID = abi.SectorNumber(extra)
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadString(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// t.Offset (uint64) (uint64)
|
||||
case "Offset":
|
||||
|
||||
{
|
||||
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.Offset = uint64(extra)
|
||||
|
||||
}
|
||||
// t.Size (abi.UnpaddedPieceSize) (uint64)
|
||||
case "Size":
|
||||
|
||||
{
|
||||
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.Size = abi.UnpaddedPieceSize(extra)
|
||||
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown struct field %d: '%s'", i, name)
|
||||
}
|
||||
|
||||
name = string(sval)
|
||||
}
|
||||
|
||||
if name != "Offset" {
|
||||
return fmt.Errorf("expected struct map entry %s to be Offset", name)
|
||||
}
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.Offset = uint64(extra)
|
||||
// t.Size (uint64) (uint64)
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadString(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name = string(sval)
|
||||
}
|
||||
|
||||
if name != "Size" {
|
||||
return fmt.Errorf("expected struct map entry %s to be Size", name)
|
||||
}
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.Size = uint64(extra)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *SealedRefs) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
@@ -335,15 +348,25 @@ func (t *SealedRefs) MarshalCBOR(w io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
scratch := make([]byte, 9)
|
||||
|
||||
// t.Refs ([]api.SealedRef) (slice)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len("Refs")))); err != nil {
|
||||
if len("Refs") > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field \"Refs\" was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Refs"))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte("Refs")); err != nil {
|
||||
if _, err := io.WriteString(w, "Refs"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajArray, uint64(len(t.Refs)))); err != nil {
|
||||
if len(t.Refs) > cbg.MaxLength {
|
||||
return xerrors.Errorf("Slice value in field t.Refs was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Refs))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range t.Refs {
|
||||
@@ -356,8 +379,9 @@ func (t *SealedRefs) MarshalCBOR(w io.Writer) error {
|
||||
|
||||
func (t *SealedRefs) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
scratch := make([]byte, 8)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
maj, extra, err := cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -365,50 +389,342 @@ func (t *SealedRefs) UnmarshalCBOR(r io.Reader) error {
|
||||
return fmt.Errorf("cbor input should be of type map")
|
||||
}
|
||||
|
||||
if extra != 1 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
if extra > cbg.MaxLength {
|
||||
return fmt.Errorf("SealedRefs: map struct too large (%d)", extra)
|
||||
}
|
||||
|
||||
var name string
|
||||
n := extra
|
||||
|
||||
// t.Refs ([]api.SealedRef) (slice)
|
||||
for i := uint64(0); i < n; i++ {
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadString(br)
|
||||
if err != nil {
|
||||
return err
|
||||
{
|
||||
sval, err := cbg.ReadStringBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name = string(sval)
|
||||
}
|
||||
|
||||
name = string(sval)
|
||||
}
|
||||
switch name {
|
||||
// t.Refs ([]api.SealedRef) (slice)
|
||||
case "Refs":
|
||||
|
||||
if name != "Refs" {
|
||||
return fmt.Errorf("expected struct map entry %s to be Refs", name)
|
||||
}
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if extra > cbg.MaxLength {
|
||||
return fmt.Errorf("t.Refs: array too large (%d)", extra)
|
||||
}
|
||||
|
||||
if extra > cbg.MaxLength {
|
||||
return fmt.Errorf("t.Refs: array too large (%d)", extra)
|
||||
}
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
if extra > 0 {
|
||||
t.Refs = make([]SealedRef, extra)
|
||||
}
|
||||
for i := 0; i < int(extra); i++ {
|
||||
if extra > 0 {
|
||||
t.Refs = make([]SealedRef, extra)
|
||||
}
|
||||
|
||||
var v SealedRef
|
||||
if err := v.UnmarshalCBOR(br); err != nil {
|
||||
for i := 0; i < int(extra); i++ {
|
||||
|
||||
var v SealedRef
|
||||
if err := v.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Refs[i] = v
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown struct field %d: '%s'", i, name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (t *SealTicket) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{162}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scratch := make([]byte, 9)
|
||||
|
||||
// t.Value (abi.SealRandomness) (slice)
|
||||
if len("Value") > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field \"Value\" was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Value"))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.WriteString(w, "Value"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(t.Value) > cbg.ByteArrayMaxLen {
|
||||
return xerrors.Errorf("Byte array in field t.Value was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.Value))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(t.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Epoch (abi.ChainEpoch) (int64)
|
||||
if len("Epoch") > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field \"Epoch\" was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Epoch"))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.WriteString(w, "Epoch"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t.Epoch >= 0 {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Epoch)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.Epoch-1)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *SealTicket) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
scratch := make([]byte, 8)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajMap {
|
||||
return fmt.Errorf("cbor input should be of type map")
|
||||
}
|
||||
|
||||
if extra > cbg.MaxLength {
|
||||
return fmt.Errorf("SealTicket: map struct too large (%d)", extra)
|
||||
}
|
||||
|
||||
var name string
|
||||
n := extra
|
||||
|
||||
for i := uint64(0); i < n; i++ {
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadStringBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name = string(sval)
|
||||
}
|
||||
|
||||
switch name {
|
||||
// t.Value (abi.SealRandomness) (slice)
|
||||
case "Value":
|
||||
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if extra > cbg.ByteArrayMaxLen {
|
||||
return fmt.Errorf("t.Value: byte array too large (%d)", extra)
|
||||
}
|
||||
if maj != cbg.MajByteString {
|
||||
return fmt.Errorf("expected byte array")
|
||||
}
|
||||
t.Value = make([]byte, extra)
|
||||
if _, err := io.ReadFull(br, t.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
// t.Epoch (abi.ChainEpoch) (int64)
|
||||
case "Epoch":
|
||||
{
|
||||
maj, extra, err := cbg.CborReadHeaderBuf(br, scratch)
|
||||
var extraI int64
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch maj {
|
||||
case cbg.MajUnsignedInt:
|
||||
extraI = int64(extra)
|
||||
if extraI < 0 {
|
||||
return fmt.Errorf("int64 positive overflow")
|
||||
}
|
||||
case cbg.MajNegativeInt:
|
||||
extraI = int64(extra)
|
||||
if extraI < 0 {
|
||||
return fmt.Errorf("int64 negative oveflow")
|
||||
}
|
||||
extraI = -1 - extraI
|
||||
default:
|
||||
return fmt.Errorf("wrong type for int64 field: %d", maj)
|
||||
}
|
||||
|
||||
t.Epoch = abi.ChainEpoch(extraI)
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown struct field %d: '%s'", i, name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (t *SealSeed) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{162}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scratch := make([]byte, 9)
|
||||
|
||||
// t.Value (abi.InteractiveSealRandomness) (slice)
|
||||
if len("Value") > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field \"Value\" was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Value"))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.WriteString(w, "Value"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(t.Value) > cbg.ByteArrayMaxLen {
|
||||
return xerrors.Errorf("Byte array in field t.Value was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.Value))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(t.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Epoch (abi.ChainEpoch) (int64)
|
||||
if len("Epoch") > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field \"Epoch\" was too long")
|
||||
}
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Epoch"))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.WriteString(w, "Epoch"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t.Epoch >= 0 {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Epoch)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.Epoch-1)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *SealSeed) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
scratch := make([]byte, 8)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajMap {
|
||||
return fmt.Errorf("cbor input should be of type map")
|
||||
}
|
||||
|
||||
if extra > cbg.MaxLength {
|
||||
return fmt.Errorf("SealSeed: map struct too large (%d)", extra)
|
||||
}
|
||||
|
||||
var name string
|
||||
n := extra
|
||||
|
||||
for i := uint64(0); i < n; i++ {
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadStringBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name = string(sval)
|
||||
}
|
||||
|
||||
switch name {
|
||||
// t.Value (abi.InteractiveSealRandomness) (slice)
|
||||
case "Value":
|
||||
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if extra > cbg.ByteArrayMaxLen {
|
||||
return fmt.Errorf("t.Value: byte array too large (%d)", extra)
|
||||
}
|
||||
if maj != cbg.MajByteString {
|
||||
return fmt.Errorf("expected byte array")
|
||||
}
|
||||
t.Value = make([]byte, extra)
|
||||
if _, err := io.ReadFull(br, t.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
// t.Epoch (abi.ChainEpoch) (int64)
|
||||
case "Epoch":
|
||||
{
|
||||
maj, extra, err := cbg.CborReadHeaderBuf(br, scratch)
|
||||
var extraI int64
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch maj {
|
||||
case cbg.MajUnsignedInt:
|
||||
extraI = int64(extra)
|
||||
if extraI < 0 {
|
||||
return fmt.Errorf("int64 positive overflow")
|
||||
}
|
||||
case cbg.MajNegativeInt:
|
||||
extraI = int64(extra)
|
||||
if extraI < 0 {
|
||||
return fmt.Errorf("int64 negative oveflow")
|
||||
}
|
||||
extraI = -1 - extraI
|
||||
default:
|
||||
return fmt.Errorf("wrong type for int64 field: %d", maj)
|
||||
}
|
||||
|
||||
t.Epoch = abi.ChainEpoch(extraI)
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown struct field %d: '%s'", i, name)
|
||||
}
|
||||
|
||||
t.Refs[i] = v
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+21
-4
@@ -1,11 +1,12 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/lotus/api/apistruct"
|
||||
"net/http"
|
||||
|
||||
"github.com/filecoin-project/go-jsonrpc"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/lib/jsonrpc"
|
||||
"github.com/filecoin-project/lotus/api/apistruct"
|
||||
)
|
||||
|
||||
// NewCommonRPC creates a new http jsonrpc client.
|
||||
@@ -14,7 +15,9 @@ func NewCommonRPC(addr string, requestHeader http.Header) (api.Common, jsonrpc.C
|
||||
closer, err := jsonrpc.NewMergeClient(addr, "Filecoin",
|
||||
[]interface{}{
|
||||
&res.Internal,
|
||||
}, requestHeader)
|
||||
},
|
||||
requestHeader,
|
||||
)
|
||||
|
||||
return &res, closer, err
|
||||
}
|
||||
@@ -38,7 +41,21 @@ func NewStorageMinerRPC(addr string, requestHeader http.Header) (api.StorageMine
|
||||
[]interface{}{
|
||||
&res.CommonStruct.Internal,
|
||||
&res.Internal,
|
||||
}, requestHeader)
|
||||
},
|
||||
requestHeader,
|
||||
)
|
||||
|
||||
return &res, closer, err
|
||||
}
|
||||
|
||||
func NewWorkerRPC(addr string, requestHeader http.Header) (api.WorkerAPI, jsonrpc.ClientCloser, error) {
|
||||
var res apistruct.WorkerStruct
|
||||
closer, err := jsonrpc.NewMergeClient(addr, "Filecoin",
|
||||
[]interface{}{
|
||||
&res.Internal,
|
||||
},
|
||||
requestHeader,
|
||||
)
|
||||
|
||||
return &res, closer, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,379 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-bitfield"
|
||||
"github.com/filecoin-project/go-jsonrpc/auth"
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/api/apistruct"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/crypto"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-filestore"
|
||||
"github.com/libp2p/go-libp2p-core/network"
|
||||
peer "github.com/libp2p/go-libp2p-peer"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
var ExampleValues = map[reflect.Type]interface{}{
|
||||
reflect.TypeOf(auth.Permission("")): auth.Permission("write"),
|
||||
reflect.TypeOf(""): "string value",
|
||||
reflect.TypeOf(uint64(42)): uint64(42),
|
||||
reflect.TypeOf(byte(7)): byte(7),
|
||||
reflect.TypeOf([]byte{}): []byte("byte array"),
|
||||
}
|
||||
|
||||
func addExample(v interface{}) {
|
||||
ExampleValues[reflect.TypeOf(v)] = v
|
||||
}
|
||||
|
||||
func init() {
|
||||
c, err := cid.Decode("bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ExampleValues[reflect.TypeOf(c)] = c
|
||||
|
||||
c2, err := cid.Decode("bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tsk := types.NewTipSetKey(c, c2)
|
||||
|
||||
ExampleValues[reflect.TypeOf(tsk)] = tsk
|
||||
|
||||
addr, err := address.NewIDAddress(1234)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ExampleValues[reflect.TypeOf(addr)] = addr
|
||||
|
||||
pid, err := peer.IDB58Decode("12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
addExample(pid)
|
||||
|
||||
addExample(bitfield.NewFromSet([]uint64{5}))
|
||||
addExample(abi.RegisteredSealProof_StackedDrg32GiBV1)
|
||||
addExample(abi.RegisteredPoStProof_StackedDrgWindow32GiBV1)
|
||||
addExample(abi.ChainEpoch(10101))
|
||||
addExample(crypto.SigTypeBLS)
|
||||
addExample(int64(9))
|
||||
addExample(12.3)
|
||||
addExample(123)
|
||||
addExample(uintptr(0))
|
||||
addExample(abi.MethodNum(1))
|
||||
addExample(exitcode.ExitCode(0))
|
||||
addExample(crypto.DomainSeparationTag_ElectionProofProduction)
|
||||
addExample(true)
|
||||
addExample(abi.UnpaddedPieceSize(1024))
|
||||
addExample(abi.UnpaddedPieceSize(1024).Padded())
|
||||
addExample(abi.DealID(5432))
|
||||
addExample(filestore.StatusFileChanged)
|
||||
addExample(abi.SectorNumber(9))
|
||||
addExample(abi.SectorSize(32 * 1024 * 1024 * 1024))
|
||||
addExample(api.MpoolChange(0))
|
||||
addExample(network.Connected)
|
||||
addExample(dtypes.NetworkName("lotus"))
|
||||
addExample(api.SyncStateStage(1))
|
||||
addExample(build.APIVersion)
|
||||
addExample(api.PCHInbound)
|
||||
addExample(time.Minute)
|
||||
addExample(&types.ExecutionTrace{
|
||||
Msg: exampleValue(reflect.TypeOf(&types.Message{}), nil).(*types.Message),
|
||||
MsgRct: exampleValue(reflect.TypeOf(&types.MessageReceipt{}), nil).(*types.MessageReceipt),
|
||||
})
|
||||
addExample(map[string]types.Actor{
|
||||
"t01236": exampleValue(reflect.TypeOf(types.Actor{}), nil).(types.Actor),
|
||||
})
|
||||
addExample(map[string]api.MarketDeal{
|
||||
"t026363": exampleValue(reflect.TypeOf(api.MarketDeal{}), nil).(api.MarketDeal),
|
||||
})
|
||||
addExample(map[string]api.MarketBalance{
|
||||
"t026363": exampleValue(reflect.TypeOf(api.MarketBalance{}), nil).(api.MarketBalance),
|
||||
})
|
||||
|
||||
maddr, err := multiaddr.NewMultiaddr("/ip4/52.36.61.156/tcp/1347/p2p/12D3KooWFETiESTf1v4PGUvtnxMAcEFMzLZbJGg4tjWfGEimYior")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// because reflect.TypeOf(maddr) returns the concrete type...
|
||||
ExampleValues[reflect.TypeOf(struct{ A multiaddr.Multiaddr }{}).Field(0).Type] = maddr
|
||||
|
||||
}
|
||||
|
||||
func exampleValue(t, parent reflect.Type) interface{} {
|
||||
v, ok := ExampleValues[t]
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Slice:
|
||||
out := reflect.New(t).Elem()
|
||||
reflect.Append(out, reflect.ValueOf(exampleValue(t.Elem(), t)))
|
||||
return out.Interface()
|
||||
case reflect.Chan:
|
||||
return exampleValue(t.Elem(), nil)
|
||||
case reflect.Struct:
|
||||
es := exampleStruct(t, parent)
|
||||
v := reflect.ValueOf(es).Elem().Interface()
|
||||
ExampleValues[t] = v
|
||||
return v
|
||||
case reflect.Array:
|
||||
out := reflect.New(t).Elem()
|
||||
for i := 0; i < t.Len(); i++ {
|
||||
out.Index(i).Set(reflect.ValueOf(exampleValue(t.Elem(), t)))
|
||||
}
|
||||
return out.Interface()
|
||||
|
||||
case reflect.Ptr:
|
||||
if t.Elem().Kind() == reflect.Struct {
|
||||
es := exampleStruct(t.Elem(), t)
|
||||
//ExampleValues[t] = es
|
||||
return es
|
||||
}
|
||||
case reflect.Interface:
|
||||
return struct{}{}
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("No example value for type: %s", t))
|
||||
}
|
||||
|
||||
func exampleStruct(t, parent reflect.Type) interface{} {
|
||||
ns := reflect.New(t)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if f.Type == parent {
|
||||
continue
|
||||
}
|
||||
if strings.Title(f.Name) == f.Name {
|
||||
ns.Elem().Field(i).Set(reflect.ValueOf(exampleValue(f.Type, t)))
|
||||
}
|
||||
}
|
||||
|
||||
return ns.Interface()
|
||||
}
|
||||
|
||||
type Visitor struct {
|
||||
Methods map[string]ast.Node
|
||||
}
|
||||
|
||||
func (v *Visitor) Visit(node ast.Node) ast.Visitor {
|
||||
st, ok := node.(*ast.TypeSpec)
|
||||
if !ok {
|
||||
return v
|
||||
}
|
||||
|
||||
if st.Name.Name != "FullNode" {
|
||||
return nil
|
||||
}
|
||||
|
||||
iface := st.Type.(*ast.InterfaceType)
|
||||
for _, m := range iface.Methods.List {
|
||||
if len(m.Names) > 0 {
|
||||
v.Methods[m.Names[0].Name] = m
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
const noComment = "There are not yet any comments for this method."
|
||||
|
||||
func parseApiASTInfo() (map[string]string, map[string]string) { //nolint:golint
|
||||
fset := token.NewFileSet()
|
||||
pkgs, err := parser.ParseDir(fset, "./api", nil, parser.AllErrors|parser.ParseComments)
|
||||
if err != nil {
|
||||
fmt.Println("parse error: ", err)
|
||||
}
|
||||
|
||||
ap := pkgs["api"]
|
||||
|
||||
f := ap.Files["api/api_full.go"]
|
||||
|
||||
cmap := ast.NewCommentMap(fset, f, f.Comments)
|
||||
|
||||
v := &Visitor{make(map[string]ast.Node)}
|
||||
ast.Walk(v, pkgs["api"])
|
||||
|
||||
groupDocs := make(map[string]string)
|
||||
out := make(map[string]string)
|
||||
for mn, node := range v.Methods {
|
||||
cs := cmap.Filter(node).Comments()
|
||||
if len(cs) == 0 {
|
||||
out[mn] = noComment
|
||||
} else {
|
||||
for _, c := range cs {
|
||||
if strings.HasPrefix(c.Text(), "MethodGroup:") {
|
||||
parts := strings.Split(c.Text(), "\n")
|
||||
groupName := strings.TrimSpace(parts[0][12:])
|
||||
comment := strings.Join(parts[1:], "\n")
|
||||
groupDocs[groupName] = comment
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
last := cs[len(cs)-1].Text()
|
||||
if !strings.HasPrefix(last, "MethodGroup:") {
|
||||
out[mn] = last
|
||||
} else {
|
||||
out[mn] = noComment
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, groupDocs
|
||||
}
|
||||
|
||||
type MethodGroup struct {
|
||||
GroupName string
|
||||
Header string
|
||||
Methods []*Method
|
||||
}
|
||||
|
||||
type Method struct {
|
||||
Comment string
|
||||
Name string
|
||||
InputExample string
|
||||
ResponseExample string
|
||||
}
|
||||
|
||||
func methodGroupFromName(mn string) string {
|
||||
i := strings.IndexFunc(mn[1:], func(r rune) bool {
|
||||
return unicode.IsUpper(r)
|
||||
})
|
||||
if i < 0 {
|
||||
return ""
|
||||
}
|
||||
return mn[:i+1]
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
comments, groupComments := parseApiASTInfo()
|
||||
|
||||
groups := make(map[string]*MethodGroup)
|
||||
|
||||
var api struct{ api.FullNode }
|
||||
t := reflect.TypeOf(api)
|
||||
for i := 0; i < t.NumMethod(); i++ {
|
||||
m := t.Method(i)
|
||||
|
||||
groupName := methodGroupFromName(m.Name)
|
||||
|
||||
g, ok := groups[groupName]
|
||||
if !ok {
|
||||
g = new(MethodGroup)
|
||||
g.Header = groupComments[groupName]
|
||||
g.GroupName = groupName
|
||||
groups[groupName] = g
|
||||
}
|
||||
|
||||
var args []interface{}
|
||||
ft := m.Func.Type()
|
||||
for j := 2; j < ft.NumIn(); j++ {
|
||||
inp := ft.In(j)
|
||||
args = append(args, exampleValue(inp, nil))
|
||||
}
|
||||
|
||||
v, err := json.MarshalIndent(args, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
outv := exampleValue(ft.Out(0), nil)
|
||||
|
||||
ov, err := json.MarshalIndent(outv, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
g.Methods = append(g.Methods, &Method{
|
||||
Name: m.Name,
|
||||
Comment: comments[m.Name],
|
||||
InputExample: string(v),
|
||||
ResponseExample: string(ov),
|
||||
})
|
||||
}
|
||||
|
||||
var groupslice []*MethodGroup
|
||||
for _, g := range groups {
|
||||
groupslice = append(groupslice, g)
|
||||
}
|
||||
|
||||
sort.Slice(groupslice, func(i, j int) bool {
|
||||
return groupslice[i].GroupName < groupslice[j].GroupName
|
||||
})
|
||||
|
||||
fmt.Printf("# Groups\n")
|
||||
|
||||
for _, g := range groupslice {
|
||||
fmt.Printf("* [%s](#%s)\n", g.GroupName, g.GroupName)
|
||||
for _, method := range g.Methods {
|
||||
fmt.Printf(" * [%s](#%s)\n", method.Name, method.Name)
|
||||
}
|
||||
}
|
||||
|
||||
permStruct := reflect.TypeOf(apistruct.FullNodeStruct{}.Internal)
|
||||
commonPermStruct := reflect.TypeOf(apistruct.CommonStruct{}.Internal)
|
||||
|
||||
for _, g := range groupslice {
|
||||
g := g
|
||||
fmt.Printf("## %s\n", g.GroupName)
|
||||
fmt.Printf("%s\n\n", g.Header)
|
||||
|
||||
sort.Slice(g.Methods, func(i, j int) bool {
|
||||
return g.Methods[i].Name < g.Methods[j].Name
|
||||
})
|
||||
|
||||
for _, m := range g.Methods {
|
||||
fmt.Printf("### %s\n", m.Name)
|
||||
fmt.Printf("%s\n\n", m.Comment)
|
||||
|
||||
meth, ok := permStruct.FieldByName(m.Name)
|
||||
if !ok {
|
||||
meth, ok = commonPermStruct.FieldByName(m.Name)
|
||||
if !ok {
|
||||
panic("no perms for method: " + m.Name)
|
||||
}
|
||||
}
|
||||
|
||||
perms := meth.Tag.Get("perm")
|
||||
|
||||
fmt.Printf("Perms: %s\n\n", perms)
|
||||
|
||||
if strings.Count(m.InputExample, "\n") > 0 {
|
||||
fmt.Printf("Inputs:\n```json\n%s\n```\n\n", m.InputExample)
|
||||
} else {
|
||||
fmt.Printf("Inputs: `%s`\n\n", m.InputExample)
|
||||
}
|
||||
|
||||
if strings.Count(m.ResponseExample, "\n") > 0 {
|
||||
fmt.Printf("Response:\n```json\n%s\n```\n\n", m.ResponseExample)
|
||||
} else {
|
||||
fmt.Printf("Response: `%s`\n\n", m.ResponseExample)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+148
-38
@@ -8,15 +8,26 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
logging "github.com/ipfs/go-log"
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
files "github.com/ipfs/go-ipfs-files"
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
"github.com/ipld/go-car"
|
||||
|
||||
"github.com/filecoin-project/go-fil-markets/storagemarket"
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
dag "github.com/ipfs/go-merkledag"
|
||||
dstest "github.com/ipfs/go-merkledag/test"
|
||||
unixfile "github.com/ipfs/go-unixfs/file"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/node/impl"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -24,11 +35,11 @@ func init() {
|
||||
build.InsecurePoStValidation = true
|
||||
}
|
||||
|
||||
func TestDealFlow(t *testing.T, b APIBuilder) {
|
||||
os.Setenv("BELLMAN_NO_GPU", "1")
|
||||
func TestDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, carExport bool) {
|
||||
_ = os.Setenv("BELLMAN_NO_GPU", "1")
|
||||
|
||||
ctx := context.Background()
|
||||
n, sn := b(t, 1, []int{0})
|
||||
n, sn := b(t, 1, oneMiner)
|
||||
client := n[0].FullNode.(*impl.FullNodeAPI)
|
||||
miner := sn[0]
|
||||
|
||||
@@ -42,8 +53,67 @@ func TestDealFlow(t *testing.T, b APIBuilder) {
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
data := make([]byte, 1000)
|
||||
rand.New(rand.NewSource(5)).Read(data)
|
||||
mine := int64(1)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
for atomic.LoadInt64(&mine) == 1 {
|
||||
time.Sleep(blocktime)
|
||||
if err := sn[0].MineOne(ctx, func(bool, error) {}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
makeDeal(t, ctx, 6, client, miner, carExport)
|
||||
|
||||
atomic.AddInt64(&mine, -1)
|
||||
fmt.Println("shutting down mining")
|
||||
<-done
|
||||
}
|
||||
|
||||
func TestDoubleDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration) {
|
||||
_ = os.Setenv("BELLMAN_NO_GPU", "1")
|
||||
|
||||
ctx := context.Background()
|
||||
n, sn := b(t, 1, oneMiner)
|
||||
client := n[0].FullNode.(*impl.FullNodeAPI)
|
||||
miner := sn[0]
|
||||
|
||||
addrinfo, err := client.NetAddrsListen(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := miner.NetConnect(ctx, addrinfo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
mine := int64(1)
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(done)
|
||||
for atomic.LoadInt64(&mine) == 1 {
|
||||
time.Sleep(blocktime)
|
||||
if err := sn[0].MineOne(ctx, func(bool, error) {}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
makeDeal(t, ctx, 6, client, miner, false)
|
||||
makeDeal(t, ctx, 7, client, miner, false)
|
||||
|
||||
atomic.AddInt64(&mine, -1)
|
||||
fmt.Println("shutting down mining")
|
||||
<-done
|
||||
}
|
||||
|
||||
func makeDeal(t *testing.T, ctx context.Context, rseed int, client *impl.FullNodeAPI, miner TestStorageNode, carExport bool) {
|
||||
data := make([]byte, 1600)
|
||||
rand.New(rand.NewSource(int64(rseed))).Read(data)
|
||||
|
||||
r := bytes.NewReader(data)
|
||||
fcid, err := client.ClientImportLocal(ctx, r)
|
||||
@@ -51,37 +121,43 @@ func TestDealFlow(t *testing.T, b APIBuilder) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("FILE CID: ", fcid)
|
||||
|
||||
deal := startDeal(t, ctx, miner, client, fcid)
|
||||
|
||||
// TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this
|
||||
time.Sleep(time.Second)
|
||||
waitDealSealed(t, ctx, client, deal)
|
||||
|
||||
// Retrieval
|
||||
|
||||
testRetrieval(t, ctx, err, client, fcid, carExport, data)
|
||||
}
|
||||
|
||||
func startDeal(t *testing.T, ctx context.Context, miner TestStorageNode, client *impl.FullNodeAPI, fcid cid.Cid) *cid.Cid {
|
||||
maddr, err := miner.ActorAddress(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("FILE CID: ", fcid)
|
||||
|
||||
mine := true
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(done)
|
||||
for mine {
|
||||
time.Sleep(time.Second)
|
||||
fmt.Println("mining a block now")
|
||||
if err := sn[0].MineOne(ctx); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
addr, err := client.WalletDefaultAddress(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
deal, err := client.ClientStartDeal(ctx, fcid, addr, maddr, types.NewInt(40000000), 100)
|
||||
deal, err := client.ClientStartDeal(ctx, &api.StartDealParams{
|
||||
Data: &storagemarket.DataRef{Root: fcid},
|
||||
Wallet: addr,
|
||||
Miner: maddr,
|
||||
EpochPrice: types.NewInt(1000000),
|
||||
MinBlocksDuration: 100,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
t.Fatalf("%+v", err)
|
||||
}
|
||||
return deal
|
||||
}
|
||||
|
||||
// TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this
|
||||
time.Sleep(time.Second)
|
||||
func waitDealSealed(t *testing.T, ctx context.Context, client *impl.FullNodeAPI, deal *cid.Cid) {
|
||||
loop:
|
||||
for {
|
||||
di, err := client.ClientGetDealInfo(ctx, *deal)
|
||||
@@ -89,22 +165,22 @@ loop:
|
||||
t.Fatal(err)
|
||||
}
|
||||
switch di.State {
|
||||
case api.DealRejected:
|
||||
case storagemarket.StorageDealProposalRejected:
|
||||
t.Fatal("deal rejected")
|
||||
case api.DealFailed:
|
||||
case storagemarket.StorageDealFailing:
|
||||
t.Fatal("deal failed")
|
||||
case api.DealError:
|
||||
t.Fatal("deal errored")
|
||||
case api.DealComplete:
|
||||
case storagemarket.StorageDealError:
|
||||
t.Fatal("deal errored", di.Message)
|
||||
case storagemarket.StorageDealActive:
|
||||
fmt.Println("COMPLETE", di)
|
||||
break loop
|
||||
}
|
||||
fmt.Println("Deal state: ", api.DealStates[di.State])
|
||||
fmt.Println("Deal state: ", storagemarket.DealStates[di.State])
|
||||
time.Sleep(time.Second / 2)
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieval
|
||||
|
||||
func testRetrieval(t *testing.T, ctx context.Context, err error, client *impl.FullNodeAPI, fcid cid.Cid, carExport bool, data []byte) {
|
||||
offers, err := client.ClientFindData(ctx, fcid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -118,14 +194,18 @@ loop:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(rpath)
|
||||
defer os.RemoveAll(rpath) //nolint:errcheck
|
||||
|
||||
caddr, err := client.WalletDefaultAddress(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = client.ClientRetrieve(ctx, offers[0].Order(caddr), filepath.Join(rpath, "ret"))
|
||||
ref := &api.FileRef{
|
||||
Path: filepath.Join(rpath, "ret"),
|
||||
IsCAR: carExport,
|
||||
}
|
||||
err = client.ClientRetrieve(ctx, offers[0].Order(caddr), ref)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", err)
|
||||
}
|
||||
@@ -135,11 +215,41 @@ loop:
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if carExport {
|
||||
rdata = extractCarData(t, ctx, rdata, rpath)
|
||||
}
|
||||
|
||||
if !bytes.Equal(rdata, data) {
|
||||
t.Fatal("wrong data retrieved")
|
||||
}
|
||||
}
|
||||
|
||||
mine = false
|
||||
fmt.Println("shutting down mining")
|
||||
<-done
|
||||
func extractCarData(t *testing.T, ctx context.Context, rdata []byte, rpath string) []byte {
|
||||
bserv := dstest.Bserv()
|
||||
ch, err := car.LoadCar(bserv.Blockstore(), bytes.NewReader(rdata))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, err := bserv.GetBlock(ctx, ch.Roots[0])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
nd, err := ipld.Decode(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dserv := dag.NewDAGService(bserv)
|
||||
fil, err := unixfile.NewUnixfsFile(ctx, dserv, nd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outPath := filepath.Join(rpath, "retLoadedCAR")
|
||||
if err := files.WriteTo(fil, outPath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rdata, err = ioutil.ReadFile(outPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return rdata
|
||||
}
|
||||
|
||||
+173
-4
@@ -1,31 +1,200 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/node/impl"
|
||||
)
|
||||
|
||||
var log = logging.Logger("apitest")
|
||||
|
||||
func (ts *testSuite) testMining(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
apis, sn := ts.makeNodes(t, 1, []int{0})
|
||||
apis, sn := ts.makeNodes(t, 1, oneMiner)
|
||||
api := apis[0]
|
||||
|
||||
h1, err := api.ChainHead(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(0), h1.Height())
|
||||
require.Equal(t, abi.ChainEpoch(0), h1.Height())
|
||||
|
||||
newHeads, err := api.ChainNotify(ctx)
|
||||
require.NoError(t, err)
|
||||
<-newHeads
|
||||
|
||||
err = sn[0].MineOne(ctx)
|
||||
err = sn[0].MineOne(ctx, func(bool, error) {})
|
||||
require.NoError(t, err)
|
||||
|
||||
<-newHeads
|
||||
|
||||
h2, err := api.ChainHead(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), h2.Height())
|
||||
require.Equal(t, abi.ChainEpoch(1), h2.Height())
|
||||
}
|
||||
|
||||
func (ts *testSuite) testMiningReal(t *testing.T) {
|
||||
build.InsecurePoStValidation = false
|
||||
defer func() {
|
||||
build.InsecurePoStValidation = true
|
||||
}()
|
||||
|
||||
ctx := context.Background()
|
||||
apis, sn := ts.makeNodes(t, 1, oneMiner)
|
||||
api := apis[0]
|
||||
|
||||
h1, err := api.ChainHead(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, abi.ChainEpoch(0), h1.Height())
|
||||
|
||||
newHeads, err := api.ChainNotify(ctx)
|
||||
require.NoError(t, err)
|
||||
<-newHeads
|
||||
|
||||
err = sn[0].MineOne(ctx, func(bool, error) {})
|
||||
require.NoError(t, err)
|
||||
|
||||
<-newHeads
|
||||
|
||||
h2, err := api.ChainHead(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, abi.ChainEpoch(1), h2.Height())
|
||||
|
||||
err = sn[0].MineOne(ctx, func(bool, error) {})
|
||||
require.NoError(t, err)
|
||||
|
||||
<-newHeads
|
||||
|
||||
h2, err = api.ChainHead(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, abi.ChainEpoch(2), h2.Height())
|
||||
}
|
||||
|
||||
func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExport bool) {
|
||||
_ = os.Setenv("BELLMAN_NO_GPU", "1")
|
||||
|
||||
// test making a deal with a fresh miner, and see if it starts to mine
|
||||
|
||||
ctx := context.Background()
|
||||
n, sn := b(t, 1, []StorageMiner{
|
||||
{Full: 0, Preseal: PresealGenesis},
|
||||
{Full: 0, Preseal: 0}, // TODO: Add support for storage miners on non-first full node
|
||||
})
|
||||
client := n[0].FullNode.(*impl.FullNodeAPI)
|
||||
provider := sn[1]
|
||||
genesisMiner := sn[0]
|
||||
|
||||
addrinfo, err := client.NetAddrsListen(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := provider.NetConnect(ctx, addrinfo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := genesisMiner.NetConnect(ctx, addrinfo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
data := make([]byte, 600)
|
||||
rand.New(rand.NewSource(5)).Read(data)
|
||||
|
||||
r := bytes.NewReader(data)
|
||||
fcid, err := client.ClientImportLocal(ctx, r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("FILE CID: ", fcid)
|
||||
|
||||
var mine int32 = 1
|
||||
done := make(chan struct{})
|
||||
minedTwo := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
doneMinedTwo := false
|
||||
defer close(done)
|
||||
|
||||
prevExpect := 0
|
||||
for atomic.LoadInt32(&mine) != 0 {
|
||||
wait := make(chan int, 2)
|
||||
mdone := func(mined bool, err error) {
|
||||
go func() {
|
||||
n := 0
|
||||
if mined {
|
||||
n = 1
|
||||
}
|
||||
wait <- n
|
||||
}()
|
||||
}
|
||||
|
||||
if err := sn[0].MineOne(ctx, mdone); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if err := sn[1].MineOne(ctx, mdone); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
expect := <-wait
|
||||
expect += <-wait
|
||||
|
||||
time.Sleep(blocktime)
|
||||
|
||||
for {
|
||||
n := 0
|
||||
for i, node := range sn {
|
||||
mb, err := node.MiningBase(ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(mb.Cids()) != expect {
|
||||
log.Warnf("node %d mining base not complete (%d, want %d)", i, len(mb.Cids()), expect)
|
||||
continue
|
||||
}
|
||||
n++
|
||||
}
|
||||
if n == len(sn) {
|
||||
break
|
||||
}
|
||||
time.Sleep(blocktime)
|
||||
}
|
||||
|
||||
if prevExpect == 2 && expect == 2 && !doneMinedTwo {
|
||||
close(minedTwo)
|
||||
doneMinedTwo = true
|
||||
}
|
||||
|
||||
prevExpect = expect
|
||||
}
|
||||
}()
|
||||
|
||||
deal := startDeal(t, ctx, provider, client, fcid)
|
||||
|
||||
// TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this
|
||||
time.Sleep(time.Second)
|
||||
|
||||
waitDealSealed(t, ctx, client, deal)
|
||||
|
||||
<-minedTwo
|
||||
|
||||
atomic.StoreInt32(&mine, 0)
|
||||
fmt.Println("shutting down mining")
|
||||
<-done
|
||||
}
|
||||
|
||||
+21
-9
@@ -4,9 +4,11 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type TestNode struct {
|
||||
@@ -16,7 +18,16 @@ type TestNode struct {
|
||||
type TestStorageNode struct {
|
||||
api.StorageMiner
|
||||
|
||||
MineOne func(context.Context) error
|
||||
MineOne func(context.Context, func(bool, error)) error
|
||||
}
|
||||
|
||||
var PresealGenesis = -1
|
||||
|
||||
const GenesisPreseals = 2
|
||||
|
||||
type StorageMiner struct {
|
||||
Full int
|
||||
Preseal int
|
||||
}
|
||||
|
||||
// APIBuilder is a function which is invoked in test suite to provide
|
||||
@@ -24,7 +35,7 @@ type TestStorageNode struct {
|
||||
//
|
||||
// storage array defines storage nodes, numbers in the array specify full node
|
||||
// index the storage node 'belongs' to
|
||||
type APIBuilder func(t *testing.T, nFull int, storage []int) ([]TestNode, []TestStorageNode)
|
||||
type APIBuilder func(t *testing.T, nFull int, storage []StorageMiner) ([]TestNode, []TestStorageNode)
|
||||
type testSuite struct {
|
||||
makeNodes APIBuilder
|
||||
}
|
||||
@@ -39,25 +50,26 @@ func TestApis(t *testing.T, b APIBuilder) {
|
||||
t.Run("id", ts.testID)
|
||||
t.Run("testConnectTwo", ts.testConnectTwo)
|
||||
t.Run("testMining", ts.testMining)
|
||||
t.Run("testMiningReal", ts.testMiningReal)
|
||||
}
|
||||
|
||||
var oneMiner = []StorageMiner{{Full: 0, Preseal: PresealGenesis}}
|
||||
|
||||
func (ts *testSuite) testVersion(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
apis, _ := ts.makeNodes(t, 1, []int{0})
|
||||
apis, _ := ts.makeNodes(t, 1, oneMiner)
|
||||
api := apis[0]
|
||||
|
||||
v, err := api.Version(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v.Version != build.BuildVersion {
|
||||
t.Error("Version didn't work properly")
|
||||
}
|
||||
require.Equal(t, v.Version, build.BuildVersion)
|
||||
}
|
||||
|
||||
func (ts *testSuite) testID(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
apis, _ := ts.makeNodes(t, 1, []int{0})
|
||||
apis, _ := ts.makeNodes(t, 1, oneMiner)
|
||||
api := apis[0]
|
||||
|
||||
id, err := api.ID(ctx)
|
||||
@@ -69,7 +81,7 @@ func (ts *testSuite) testID(t *testing.T) {
|
||||
|
||||
func (ts *testSuite) testConnectTwo(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
apis, _ := ts.makeNodes(t, 2, []int{0})
|
||||
apis, _ := ts.makeNodes(t, 2, oneMiner)
|
||||
|
||||
p, err := apis[0].NetPeers(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
miner2 "github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
sealing "github.com/filecoin-project/storage-fsm"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/node/impl"
|
||||
)
|
||||
|
||||
func TestPledgeSector(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) {
|
||||
os.Setenv("BELLMAN_NO_GPU", "1")
|
||||
|
||||
ctx := context.Background()
|
||||
n, sn := b(t, 1, oneMiner)
|
||||
client := n[0].FullNode.(*impl.FullNodeAPI)
|
||||
miner := sn[0]
|
||||
|
||||
addrinfo, err := client.NetAddrsListen(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := miner.NetConnect(ctx, addrinfo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
mine := true
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
for mine {
|
||||
time.Sleep(blocktime)
|
||||
if err := sn[0].MineOne(ctx, func(bool, error) {}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
pledgeSectors(t, ctx, miner, nSectors)
|
||||
|
||||
mine = false
|
||||
<-done
|
||||
}
|
||||
|
||||
func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
err := miner.PledgeSector(ctx)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
for {
|
||||
s, err := miner.SectorsList(ctx) // Note - the test builder doesn't import genesis sectors into FSM
|
||||
require.NoError(t, err)
|
||||
fmt.Printf("Sectors: %d\n", len(s))
|
||||
if len(s) >= n {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
fmt.Printf("All sectors is fsm\n")
|
||||
|
||||
s, err := miner.SectorsList(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
toCheck := map[abi.SectorNumber]struct{}{}
|
||||
for _, number := range s {
|
||||
toCheck[number] = struct{}{}
|
||||
}
|
||||
|
||||
for len(toCheck) > 0 {
|
||||
for n := range toCheck {
|
||||
st, err := miner.SectorsStatus(ctx, n)
|
||||
require.NoError(t, err)
|
||||
if st.State == api.SectorState(sealing.Proving) {
|
||||
delete(toCheck, n)
|
||||
}
|
||||
if strings.Contains(string(st.State), "Fail") {
|
||||
t.Fatal("sector in a failed state", st.State)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fmt.Printf("WaitSeal: %d\n", len(s))
|
||||
}
|
||||
}
|
||||
|
||||
func TestWindowPost(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) {
|
||||
os.Setenv("BELLMAN_NO_GPU", "1")
|
||||
|
||||
ctx := context.Background()
|
||||
n, sn := b(t, 1, oneMiner)
|
||||
client := n[0].FullNode.(*impl.FullNodeAPI)
|
||||
miner := sn[0]
|
||||
|
||||
addrinfo, err := client.NetAddrsListen(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := miner.NetConnect(ctx, addrinfo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
mine := true
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
for mine {
|
||||
time.Sleep(blocktime)
|
||||
if err := sn[0].MineOne(ctx, func(bool, error) {}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
pledgeSectors(t, ctx, miner, nSectors)
|
||||
|
||||
maddr, err := miner.ActorAddress(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
|
||||
fmt.Printf("Running one proving periods\n")
|
||||
|
||||
for {
|
||||
head, err := client.ChainHead(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
if head.Height() > di.PeriodStart+(miner2.WPoStProvingPeriod)+2 {
|
||||
break
|
||||
}
|
||||
|
||||
if head.Height()%100 == 0 {
|
||||
fmt.Printf("@%d\n", head.Height())
|
||||
}
|
||||
time.Sleep(blocktime)
|
||||
}
|
||||
|
||||
p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK)
|
||||
require.NoError(t, err)
|
||||
|
||||
ssz, err := miner.ActorSectorSize(ctx, maddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, p.MinerPower, p.TotalPower)
|
||||
require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors+GenesisPreseals)))
|
||||
|
||||
// TODO: Inject faults here
|
||||
|
||||
mine = false
|
||||
<-done
|
||||
}
|
||||
+48
-30
@@ -2,41 +2,16 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
type DealState = uint64
|
||||
|
||||
const (
|
||||
DealUnknown = DealState(iota)
|
||||
DealRejected // Provider didn't like the proposal
|
||||
DealAccepted // Proposal accepted, data moved
|
||||
DealStaged // Data put into the sector
|
||||
DealSealing // Data in process of being sealed
|
||||
|
||||
DealFailed
|
||||
DealComplete
|
||||
|
||||
// Internal
|
||||
|
||||
DealError // deal failed with an unexpected error
|
||||
|
||||
DealNoUpdate = DealUnknown
|
||||
)
|
||||
|
||||
var DealStates = []string{
|
||||
"DealUnknown",
|
||||
"DealRejected",
|
||||
"DealAccepted",
|
||||
"DealStaged",
|
||||
"DealSealing",
|
||||
"DealFailed",
|
||||
"DealComplete",
|
||||
"DealError",
|
||||
}
|
||||
|
||||
// TODO: check if this exists anywhere else
|
||||
|
||||
type MultiaddrSlice []ma.Multiaddr
|
||||
|
||||
func (m *MultiaddrSlice) UnmarshalJSON(raw []byte) (err error) {
|
||||
@@ -57,3 +32,46 @@ func (m *MultiaddrSlice) UnmarshalJSON(raw []byte) (err error) {
|
||||
}
|
||||
|
||||
var _ json.Unmarshaler = new(MultiaddrSlice)
|
||||
|
||||
type ObjStat struct {
|
||||
Size uint64
|
||||
Links uint64
|
||||
}
|
||||
|
||||
type PubsubScore struct {
|
||||
ID peer.ID
|
||||
Score float64
|
||||
}
|
||||
|
||||
type MinerInfo struct {
|
||||
Owner address.Address // Must be an ID-address.
|
||||
Worker address.Address // Must be an ID-address.
|
||||
NewWorker address.Address // Must be an ID-address.
|
||||
WorkerChangeEpoch abi.ChainEpoch
|
||||
PeerId peer.ID
|
||||
Multiaddrs []abi.Multiaddrs
|
||||
SealProofType abi.RegisteredSealProof
|
||||
SectorSize abi.SectorSize
|
||||
WindowPoStPartitionSectors uint64
|
||||
}
|
||||
|
||||
func NewApiMinerInfo(info miner.MinerInfo) MinerInfo {
|
||||
mi := MinerInfo{
|
||||
Owner: info.Owner,
|
||||
Worker: info.Worker,
|
||||
NewWorker: address.Undef,
|
||||
WorkerChangeEpoch: -1,
|
||||
PeerId: peer.ID(info.PeerId),
|
||||
Multiaddrs: info.Multiaddrs,
|
||||
SealProofType: info.SealProofType,
|
||||
SectorSize: info.SectorSize,
|
||||
WindowPoStPartitionSectors: info.WindowPoStPartitionSectors,
|
||||
}
|
||||
|
||||
if info.PendingWorkerKey != nil {
|
||||
mi.NewWorker = info.PendingWorkerKey.NewWorker
|
||||
mi.WorkerChangeEpoch = info.PendingWorkerKey.EffectiveAt
|
||||
}
|
||||
|
||||
return mi
|
||||
}
|
||||
|
||||
+5
-5
@@ -3,13 +3,13 @@ package api
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/crypto"
|
||||
)
|
||||
|
||||
type SignFunc = func(context.Context, []byte) (*types.Signature, error)
|
||||
type SignFunc = func(context.Context, []byte) (*crypto.Signature, error)
|
||||
|
||||
type Signer func(context.Context, address.Address, []byte) (*types.Signature, error)
|
||||
type Signer func(context.Context, address.Address, []byte) (*crypto.Signature, error)
|
||||
|
||||
type Signable interface {
|
||||
Sign(context.Context, SignFunc) error
|
||||
@@ -17,7 +17,7 @@ type Signable interface {
|
||||
|
||||
func SignWith(ctx context.Context, signer Signer, addr address.Address, signable ...Signable) error {
|
||||
for _, s := range signable {
|
||||
err := s.Sign(ctx, func(ctx context.Context, b []byte) (*types.Signature, error) {
|
||||
err := s.Sign(ctx, func(ctx context.Context, b []byte) (*crypto.Signature, error) {
|
||||
return signer(ctx, addr, b)
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
GOCC=${GOCC=go}
|
||||
|
||||
die() {
|
||||
echo "$@" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
have_binary() {
|
||||
type "$1" > /dev/null 2> /dev/null
|
||||
}
|
||||
|
||||
check_writable() {
|
||||
printf "" > "$1" && rm "$1"
|
||||
}
|
||||
|
||||
try_download() {
|
||||
url="$1"
|
||||
output="$2"
|
||||
command="$3"
|
||||
util_name="$(set -- $command; echo "$1")"
|
||||
|
||||
if ! have_binary "$util_name"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '==> Using %s to download "%s" to "%s"\n' "$util_name" "$url" "$output"
|
||||
if eval "$command"; then
|
||||
echo "==> Download complete!"
|
||||
return
|
||||
else
|
||||
echo "error: couldn't download with $util_name ($?)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
download() {
|
||||
dl_url="$1"
|
||||
dl_output="$2"
|
||||
|
||||
test "$#" -eq "2" || die "download requires exactly two arguments, was given $@"
|
||||
|
||||
if ! check_writable "$dl_output"; then
|
||||
die "download error: cannot write to $dl_output"
|
||||
fi
|
||||
|
||||
try_download "$dl_url" "$dl_output" "wget '$dl_url' -O '$dl_output'" && return
|
||||
try_download "$dl_url" "$dl_output" "curl --silent --fail --output '$dl_output' '$dl_url'" && return
|
||||
try_download "$dl_url" "$dl_output" "fetch '$dl_url' -o '$dl_output'" && return
|
||||
try_download "$dl_url" "$dl_output" "http '$dl_url' > '$dl_output'" && return
|
||||
try_download "$dl_url" "$dl_output" "ftp -o '$dl_output' '$dl_url'" && return
|
||||
|
||||
die "Unable to download $dl_url. exiting."
|
||||
}
|
||||
|
||||
unarchive() {
|
||||
ua_archivetype="$1"
|
||||
ua_infile="$2"
|
||||
ua_outfile="$3"
|
||||
ua_distname="$4"
|
||||
ua_binpostfix=""
|
||||
ua_os=$(uname -o)
|
||||
|
||||
if [ "$ua_os" = "Msys" ] || [ "$ua_os" = "Cygwin" ] ; then
|
||||
ua_binpostfix=".exe"
|
||||
fi
|
||||
ua_outfile="$ua_outfile$ua_binpostfix"
|
||||
|
||||
if ! check_writable "$ua_outfile"; then
|
||||
die "unarchive error: cannot write to $ua_outfile"
|
||||
fi
|
||||
|
||||
case "$ua_archivetype" in
|
||||
tar.gz)
|
||||
if have_binary tar; then
|
||||
echo "==> using 'tar' to extract binary from archive"
|
||||
< "$ua_infile" tar -Ozxf - "$ua_distname/$ua_distname$ua_binpostfix" > "$ua_outfile" \
|
||||
|| die "tar has failed"
|
||||
else
|
||||
die "no binary on system for extracting tar files"
|
||||
fi
|
||||
;;
|
||||
zip)
|
||||
if have_binary unzip; then
|
||||
echo "==> using 'unzip' to extract binary from archive"
|
||||
unzip -p "$ua_infile" "$ua_distname/$ua_distname$ua_binpostfix" > "$ua_outfile" \
|
||||
|| die "unzip has failed"
|
||||
else
|
||||
die "no installed method for extracting .zip archives"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
die "unrecognized archive type '$ua_archivetype'"
|
||||
esac
|
||||
|
||||
chmod +x "$ua_outfile" || die "chmod has failed"
|
||||
}
|
||||
|
||||
get_go_vars() {
|
||||
if [ ! -z "$GOOS" ] && [ ! -z "$GOARCH" ]; then
|
||||
printf "%s-%s" "$GOOS" "$GOARCH"
|
||||
elif have_binary go; then
|
||||
printf "%s-%s" "$($GOCC env GOOS)" "$($GOCC env GOARCH)"
|
||||
else
|
||||
die "no way of determining system GOOS and GOARCH\nPlease manually set GOOS and GOARCH then retry."
|
||||
fi
|
||||
}
|
||||
|
||||
mkurl() {
|
||||
m_root="$1"
|
||||
m_name="$2"
|
||||
m_vers="$3"
|
||||
m_archive="$4"
|
||||
m_govars=$(get_go_vars) || die "could not get go env vars"
|
||||
|
||||
echo "https://ipfs.io$m_root/$m_name/$m_vers/${m_name}_${m_vers}_$m_govars.$m_archive"
|
||||
}
|
||||
|
||||
distroot="$1"
|
||||
distname="$2"
|
||||
outpath="$3"
|
||||
version="$4"
|
||||
|
||||
if [ -z "$distroot" ] || [ -z "$distname" ] || [ -z "$outpath" ] || [ -z "$version" ]; then
|
||||
die "usage: dist_get <distroot> <distname> <outpath> <version>"
|
||||
fi
|
||||
|
||||
case $version in
|
||||
v*)
|
||||
# correct input
|
||||
;;
|
||||
*)
|
||||
echo "invalid version '$version'" >&2
|
||||
die "versions must begin with 'v', for example: v0.4.0"
|
||||
;;
|
||||
esac
|
||||
|
||||
# TODO: don't depend on the go tool being installed to detect this
|
||||
goenv=$(get_go_vars) || die "could not get go env vars"
|
||||
|
||||
case $goenv in
|
||||
linux-*)
|
||||
archive="tar.gz"
|
||||
;;
|
||||
darwin-*)
|
||||
archive="tar.gz"
|
||||
;;
|
||||
windows-*)
|
||||
archive="zip"
|
||||
;;
|
||||
freebsd-*)
|
||||
archive="tar.gz"
|
||||
;;
|
||||
openbsd-*)
|
||||
archive="tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo "unrecognized system environment: $goenv" >&2
|
||||
die "currently only linux, darwin, windows and freebsd are supported by this script"
|
||||
esac
|
||||
|
||||
|
||||
mkdir -p bin/tmp
|
||||
|
||||
url=$(mkurl "$distroot" "$distname" "$version" "$archive")
|
||||
tmpfi="bin/tmp/$distname.$archive"
|
||||
|
||||
download "$url" "$tmpfi"
|
||||
if [ $? -ne 0 ]; then
|
||||
die "failed to download $url to $tmpfi"
|
||||
fi
|
||||
|
||||
unarchive "$archive" "$tmpfi" "$outpath" "$distname"
|
||||
if [ $? -ne 0 ]; then
|
||||
die "failed to extract archive $tmpfi"
|
||||
fi
|
||||
@@ -13,6 +13,10 @@ import (
|
||||
)
|
||||
|
||||
func BuiltinBootstrap() ([]peer.AddrInfo, error) {
|
||||
if DisableBuiltinAssets {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var out []peer.AddrInfo
|
||||
|
||||
b := rice.MustFindBox("bootstrap")
|
||||
@@ -34,3 +38,12 @@ func BuiltinBootstrap() ([]peer.AddrInfo, error) {
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
func DrandBootstrap() ([]peer.AddrInfo, error) {
|
||||
addrs := []string{
|
||||
"/dnsaddr/pl-eu.testnet.drand.sh/",
|
||||
"/dnsaddr/pl-us.testnet.drand.sh/",
|
||||
"/dnsaddr/pl-sin.testnet.drand.sh/",
|
||||
}
|
||||
return addrutil.ParseAddresses(context.TODO(), addrs)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
/dns4/lotus-bootstrap-0.dfw.fil-test.net/tcp/1347/p2p/12D3KooWHwGBSiLR5ts7KW9MgH4BMzC2iXe18kwAQ8Ee3LUd1jeR
|
||||
/dns4/lotus-bootstrap-1.dfw.fil-test.net/tcp/1347/p2p/12D3KooWCLFaawdhLGcSpiqg43DtZ9QzPQ6HcB8Vvyu2Cnta8UWc
|
||||
/dns4/lotus-bootstrap-0.fra.fil-test.net/tcp/1347/p2p/12D3KooWMmaL7eaUCF6tVAghVmgozxz4uztbuFUQv6dyFpHRarHR
|
||||
/dns4/lotus-bootstrap-1.fra.fil-test.net/tcp/1347/p2p/12D3KooWLLpNYoKdf9NgcWudBhXLdTcXncqAsTzozw1scMMu6nS5
|
||||
/dns4/lotus-bootstrap-0.sin.fil-test.net/tcp/1347/p2p/12D3KooWCNL9vXaXwNs3Bu8uRAJK4pxpCyPeM7jZLSDpJma1wrV8
|
||||
/dns4/lotus-bootstrap-1.sin.fil-test.net/tcp/1347/p2p/12D3KooWNGGxFda1eC5U2YKAgs4ypoFHn3Z3xHCsjmFdrCcytoxm
|
||||
/dns4/bootstrap-0-sin.fil-test.net/tcp/1347/p2p/12D3KooWPdUquftaQvoQEtEdsRBAhwD6jopbF2oweVTzR59VbHEd
|
||||
/ip4/86.109.15.57/tcp/1347/p2p/12D3KooWPdUquftaQvoQEtEdsRBAhwD6jopbF2oweVTzR59VbHEd
|
||||
/dns4/bootstrap-0-dfw.fil-test.net/tcp/1347/p2p/12D3KooWQSCkHCzosEyrh8FgYfLejKgEPM5VB6qWzZE3yDAuXn8d
|
||||
/ip4/139.178.84.45/tcp/1347/p2p/12D3KooWQSCkHCzosEyrh8FgYfLejKgEPM5VB6qWzZE3yDAuXn8d
|
||||
/dns4/bootstrap-0-fra.fil-test.net/tcp/1347/p2p/12D3KooWEXN2eQmoyqnNjde9PBAQfQLHN67jcEdWU6JougWrgXJK
|
||||
/ip4/136.144.49.17/tcp/1347/p2p/12D3KooWEXN2eQmoyqnNjde9PBAQfQLHN67jcEdWU6JougWrgXJK
|
||||
/dns4/bootstrap-1-sin.fil-test.net/tcp/1347/p2p/12D3KooWLmJkZd33mJhjg5RrpJ6NFep9SNLXWc4uVngV4TXKwzYw
|
||||
/ip4/86.109.15.123/tcp/1347/p2p/12D3KooWLmJkZd33mJhjg5RrpJ6NFep9SNLXWc4uVngV4TXKwzYw
|
||||
/dns4/bootstrap-1-dfw.fil-test.net/tcp/1347/p2p/12D3KooWGXLHjiz6pTRu7x2pkgTVCoxcCiVxcNLpMnWcJ3JiNEy5
|
||||
/ip4/139.178.86.3/tcp/1347/p2p/12D3KooWGXLHjiz6pTRu7x2pkgTVCoxcCiVxcNLpMnWcJ3JiNEy5
|
||||
/dns4/bootstrap-1-fra.fil-test.net/tcp/1347/p2p/12D3KooW9szZmKttS9A1FafH3Zc2pxKwwmvCWCGKkRP4KmbhhC4R
|
||||
/ip4/136.144.49.131/tcp/1347/p2p/12D3KooW9szZmKttS9A1FafH3Zc2pxKwwmvCWCGKkRP4KmbhhC4R
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package build
|
||||
|
||||
// DisableBuiltinAssets disables the resolution of go.rice boxes that store
|
||||
// built-in assets, such as proof parameters, bootstrap peers, genesis blocks,
|
||||
// etc.
|
||||
//
|
||||
// When this value is set to true, it is expected that the user will
|
||||
// provide any such configurations through the Lotus API itself.
|
||||
//
|
||||
// This is useful when you're using Lotus as a library, such as to orchestrate
|
||||
// test scenarios, or for other purposes where you don't need to use the
|
||||
// defaults shipped with the binary.
|
||||
//
|
||||
// For this flag to be effective, it must be enabled _before_ instantiating Lotus.
|
||||
var DisableBuiltinAssets = false
|
||||
@@ -1,4 +1 @@
|
||||
package build
|
||||
|
||||
const ForkCCM = 1750
|
||||
const ForkNoPowerEPSUpdates = 16450
|
||||
|
||||
+9
-3
@@ -1,16 +1,22 @@
|
||||
package build
|
||||
|
||||
import rice "github.com/GeertJohan/go.rice"
|
||||
import (
|
||||
rice "github.com/GeertJohan/go.rice"
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
)
|
||||
|
||||
// moved from now-defunct build/paramfetch.go
|
||||
var log = logging.Logger("build")
|
||||
|
||||
func MaybeGenesis() []byte {
|
||||
builtinGen, err := rice.FindBox("genesis")
|
||||
if err != nil {
|
||||
log.Warn("loading built-in genesis: %s", err)
|
||||
log.Warnf("loading built-in genesis: %s", err)
|
||||
return nil
|
||||
}
|
||||
genBytes, err := builtinGen.Bytes("devnet.car")
|
||||
if err != nil {
|
||||
log.Warn("loading built-in genesis: %s", err)
|
||||
log.Warnf("loading built-in genesis: %s", err)
|
||||
}
|
||||
|
||||
return genBytes
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
package build
|
||||
|
||||
import rice "github.com/GeertJohan/go.rice"
|
||||
|
||||
func ParametersJSON() []byte {
|
||||
return rice.MustFindBox("proof-params").MustBytes("parameters.json")
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
rice "github.com/GeertJohan/go.rice"
|
||||
logging "github.com/ipfs/go-log"
|
||||
"github.com/minio/blake2b-simd"
|
||||
"go.uber.org/multierr"
|
||||
"golang.org/x/xerrors"
|
||||
pb "gopkg.in/cheggaaa/pb.v1"
|
||||
)
|
||||
|
||||
var log = logging.Logger("build")
|
||||
|
||||
//const gateway = "http://198.211.99.118/ipfs/"
|
||||
const gateway = "https://ipfs.io/ipfs/"
|
||||
const paramdir = "/var/tmp/filecoin-proof-parameters"
|
||||
const dirEnv = "FIL_PROOFS_PARAMETER_CACHE"
|
||||
|
||||
type paramFile struct {
|
||||
Cid string `json:"cid"`
|
||||
Digest string `json:"digest"`
|
||||
SectorSize uint64 `json:"sector_size"`
|
||||
}
|
||||
|
||||
type fetch struct {
|
||||
wg sync.WaitGroup
|
||||
fetchLk sync.Mutex
|
||||
|
||||
errs []error
|
||||
}
|
||||
|
||||
func getParamDir() string {
|
||||
if os.Getenv(dirEnv) == "" {
|
||||
return paramdir
|
||||
}
|
||||
return os.Getenv(dirEnv)
|
||||
}
|
||||
|
||||
func GetParams(storageSize uint64) error {
|
||||
if err := os.Mkdir(getParamDir(), 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
var params map[string]paramFile
|
||||
|
||||
paramBytes := rice.MustFindBox("proof-params").MustBytes("parameters.json")
|
||||
if err := json.Unmarshal(paramBytes, ¶ms); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ft := &fetch{}
|
||||
|
||||
for name, info := range params {
|
||||
if storageSize != info.SectorSize && strings.HasSuffix(name, ".params") {
|
||||
continue
|
||||
}
|
||||
|
||||
ft.maybeFetchAsync(name, info)
|
||||
}
|
||||
|
||||
return ft.wait()
|
||||
}
|
||||
|
||||
func (ft *fetch) maybeFetchAsync(name string, info paramFile) {
|
||||
ft.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer ft.wg.Done()
|
||||
|
||||
path := filepath.Join(getParamDir(), name)
|
||||
|
||||
err := ft.checkFile(path, info)
|
||||
if !os.IsNotExist(err) && err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ft.fetchLk.Lock()
|
||||
defer ft.fetchLk.Unlock()
|
||||
|
||||
if err := doFetch(path, info); err != nil {
|
||||
ft.errs = append(ft.errs, xerrors.Errorf("fetching file %s failed: %w", path, err))
|
||||
return
|
||||
}
|
||||
err = ft.checkFile(path, info)
|
||||
if err != nil {
|
||||
ft.errs = append(ft.errs, xerrors.Errorf("checking file %s failed: %w", path, err))
|
||||
err := os.Remove(path)
|
||||
if err != nil {
|
||||
ft.errs = append(ft.errs, xerrors.Errorf("remove file %s failed: %w", path, err))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (ft *fetch) checkFile(path string, info paramFile) error {
|
||||
if os.Getenv("TRUST_PARAMS") == "1" {
|
||||
log.Warn("Assuming parameter files are ok. DO NOT USE IN PRODUCTION")
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := blake2b.New512()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sum := h.Sum(nil)
|
||||
strSum := hex.EncodeToString(sum[:16])
|
||||
if strSum == info.Digest {
|
||||
log.Infof("Parameter file %s is ok", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
return xerrors.Errorf("checksum mismatch in param file %s, %s != %s", path, strSum, info.Digest)
|
||||
}
|
||||
|
||||
func (ft *fetch) wait() error {
|
||||
ft.wg.Wait()
|
||||
return multierr.Combine(ft.errs...)
|
||||
}
|
||||
|
||||
func doFetch(out string, info paramFile) error {
|
||||
gw := os.Getenv("IPFS_GATEWAY")
|
||||
if gw == "" {
|
||||
gw = gateway
|
||||
}
|
||||
log.Infof("Fetching %s from %s", out, gw)
|
||||
|
||||
outf, err := os.OpenFile(out, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outf.Close()
|
||||
|
||||
fStat, err := outf.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header := http.Header{}
|
||||
header.Set("Range", "bytes="+strconv.FormatInt(fStat.Size(), 10)+"-")
|
||||
url, err := url.Parse(gw + info.Cid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("GET %s", url)
|
||||
|
||||
req := http.Request{
|
||||
Method: "GET",
|
||||
URL: url,
|
||||
Header: header,
|
||||
Close: true,
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(&req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
bar := pb.New64(resp.ContentLength)
|
||||
bar.Units = pb.U_BYTES
|
||||
bar.ShowSpeed = true
|
||||
bar.Start()
|
||||
|
||||
_, err = io.Copy(outf, bar.NewProxyReader(resp.Body))
|
||||
|
||||
bar.Finish()
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// +build debug 2k
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/power"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/verifreg"
|
||||
)
|
||||
|
||||
func init() {
|
||||
power.ConsensusMinerMinPower = big.NewInt(2048)
|
||||
miner.SupportedProofTypes = map[abi.RegisteredSealProof]struct{}{
|
||||
abi.RegisteredSealProof_StackedDrg2KiBV1: {},
|
||||
}
|
||||
verifreg.MinVerifiedDealSize = big.NewInt(256)
|
||||
|
||||
BuildType |= Build2k
|
||||
}
|
||||
|
||||
const BlockDelaySecs = uint64(2)
|
||||
|
||||
const PropagationDelaySecs = uint64(3)
|
||||
|
||||
// SlashablePowerDelay is the number of epochs after ElectionPeriodStart, after
|
||||
// which the miner is slashed
|
||||
//
|
||||
// Epochs
|
||||
const SlashablePowerDelay = 20
|
||||
|
||||
// Epochs
|
||||
const InteractivePoRepConfidence = 6
|
||||
+4
-31
@@ -2,36 +2,9 @@
|
||||
|
||||
package build
|
||||
|
||||
import "os"
|
||||
|
||||
var SectorSizes = []uint64{1024}
|
||||
|
||||
// Seconds
|
||||
const BlockDelay = 6
|
||||
|
||||
const PropagationDelay = 3
|
||||
|
||||
// FallbackPoStDelay is the number of epochs the miner needs to wait after
|
||||
// ElectionPeriodStart before starting fallback post computation
|
||||
//
|
||||
// Epochs
|
||||
const FallbackPoStDelay = 10
|
||||
|
||||
// SlashablePowerDelay is the number of epochs after ElectionPeriodStart, after
|
||||
// which the miner is slashed
|
||||
//
|
||||
// Epochs
|
||||
const SlashablePowerDelay = 20
|
||||
|
||||
// Epochs
|
||||
const InteractivePoRepDelay = 2
|
||||
|
||||
// Epochs
|
||||
const InteractivePoRepConfidence = 6
|
||||
|
||||
// Bytes
|
||||
var MinimumMinerPower uint64 = 2 << 10 // 2KiB
|
||||
|
||||
func init() {
|
||||
os.Setenv("TRUST_PARAMS", "1")
|
||||
InsecurePoStValidation = true
|
||||
BuildType |= BuildDebug
|
||||
}
|
||||
|
||||
// NOTE: Also includes settings from params_2k
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Core network constants
|
||||
|
||||
// /////
|
||||
// Storage
|
||||
|
||||
const UnixfsChunkSize uint64 = 1 << 20
|
||||
const UnixfsLinksPerLevel = 1024
|
||||
|
||||
func SupportedSectorSize(ssize uint64) bool {
|
||||
for _, ss := range SectorSizes {
|
||||
if ssize == ss {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// /////
|
||||
// Payments
|
||||
|
||||
// Epochs
|
||||
const PaymentChannelClosingDelay = 6 * 60 * 60 / BlockDelay // six hours
|
||||
|
||||
// /////
|
||||
// Consensus / Network
|
||||
|
||||
// Seconds
|
||||
const AllowableClockDrift = 1
|
||||
|
||||
// Epochs
|
||||
const ForkLengthThreshold = Finality
|
||||
|
||||
// Blocks (e)
|
||||
const BlocksPerEpoch = 5
|
||||
|
||||
// Epochs
|
||||
const Finality = 500
|
||||
|
||||
// constants for Weight calculation
|
||||
// The ratio of weight contributed by short-term vs long-term factors in a given round
|
||||
const WRatioNum = int64(1)
|
||||
const WRatioDen = 2
|
||||
|
||||
// /////
|
||||
// Proofs
|
||||
|
||||
// Epochs
|
||||
const SealRandomnessLookback = Finality
|
||||
|
||||
// Epochs
|
||||
const SealRandomnessLookbackLimit = SealRandomnessLookback + 2000
|
||||
|
||||
// 1 / n
|
||||
const SectorChallengeRatioDiv = 25
|
||||
|
||||
const MaxFallbackPostChallengeCount = 10
|
||||
|
||||
// /////
|
||||
// Mining
|
||||
|
||||
// Epochs
|
||||
const EcRandomnessLookback = 300
|
||||
|
||||
const PowerCollateralProportion = 5
|
||||
const PerCapitaCollateralProportion = 1
|
||||
const CollateralPrecision = 1000
|
||||
|
||||
// /////
|
||||
// Devnet settings
|
||||
|
||||
const TotalFilecoin = 2_000_000_000
|
||||
const MiningRewardTotal = 1_400_000_000
|
||||
|
||||
const InitialRewardStr = "153856861913558700202"
|
||||
|
||||
var InitialReward *big.Int
|
||||
|
||||
const FilecoinPrecision = 1_000_000_000_000_000_000
|
||||
|
||||
// TODO: Move other important consts here
|
||||
|
||||
func init() {
|
||||
InitialReward = new(big.Int)
|
||||
|
||||
var ok bool
|
||||
InitialReward, ok = InitialReward.
|
||||
SetString(InitialRewardStr, 10)
|
||||
if !ok {
|
||||
panic("could not parse InitialRewardStr")
|
||||
}
|
||||
}
|
||||
|
||||
// Sync
|
||||
const BadBlockCacheSize = 1 << 15
|
||||
|
||||
// assuming 4000 messages per round, this lets us not lose any messages across a
|
||||
// 10 block reorg.
|
||||
const BlsSignatureCacheSize = 40000
|
||||
@@ -0,0 +1,38 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
)
|
||||
|
||||
func DefaultSectorSize() abi.SectorSize {
|
||||
szs := make([]abi.SectorSize, 0, len(miner.SupportedProofTypes))
|
||||
for spt := range miner.SupportedProofTypes {
|
||||
ss, err := spt.SectorSize()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
szs = append(szs, ss)
|
||||
}
|
||||
|
||||
sort.Slice(szs, func(i, j int) bool {
|
||||
return szs[i] < szs[j]
|
||||
})
|
||||
|
||||
return szs[0]
|
||||
}
|
||||
|
||||
// Core network constants
|
||||
|
||||
func BlocksTopic(netName dtypes.NetworkName) string { return "/fil/blocks/" + string(netName) }
|
||||
func MessagesTopic(netName dtypes.NetworkName) string { return "/fil/msgs/" + string(netName) }
|
||||
func DhtProtocolName(netName dtypes.NetworkName) protocol.ID {
|
||||
return protocol.ID("/fil/kad/" + string(netName))
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// +build !testground
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
)
|
||||
|
||||
// /////
|
||||
// Storage
|
||||
|
||||
const UnixfsChunkSize uint64 = 1 << 20
|
||||
const UnixfsLinksPerLevel = 1024
|
||||
|
||||
// /////
|
||||
// Consensus / Network
|
||||
|
||||
const AllowableClockDriftSecs = uint64(1)
|
||||
|
||||
// Epochs
|
||||
const ForkLengthThreshold = Finality
|
||||
|
||||
// Blocks (e)
|
||||
var BlocksPerEpoch = uint64(builtin.ExpectedLeadersPerEpoch)
|
||||
|
||||
// Epochs
|
||||
const Finality = miner.ChainFinalityish
|
||||
const MessageConfidence = uint64(5)
|
||||
|
||||
// constants for Weight calculation
|
||||
// The ratio of weight contributed by short-term vs long-term factors in a given round
|
||||
const WRatioNum = int64(1)
|
||||
const WRatioDen = uint64(2)
|
||||
|
||||
// /////
|
||||
// Proofs
|
||||
|
||||
// Epochs
|
||||
const SealRandomnessLookback = Finality
|
||||
|
||||
// Epochs
|
||||
const SealRandomnessLookbackLimit = SealRandomnessLookback + 2000 // TODO: Get from spec specs-actors
|
||||
|
||||
// Maximum lookback that randomness can be sourced from for a seal proof submission
|
||||
const MaxSealLookback = SealRandomnessLookbackLimit + 2000 // TODO: Get from specs-actors
|
||||
|
||||
// /////
|
||||
// Mining
|
||||
|
||||
// Epochs
|
||||
const TicketRandomnessLookback = abi.ChainEpoch(1)
|
||||
|
||||
const WinningPoStSectorSetLookback = abi.ChainEpoch(10)
|
||||
|
||||
// /////
|
||||
// Devnet settings
|
||||
|
||||
const TotalFilecoin = uint64(2_000_000_000)
|
||||
const MiningRewardTotal = uint64(1_400_000_000)
|
||||
|
||||
const FilecoinPrecision = uint64(1_000_000_000_000_000_000)
|
||||
|
||||
var InitialRewardBalance *big.Int
|
||||
|
||||
// TODO: Move other important consts here
|
||||
|
||||
func init() {
|
||||
InitialRewardBalance = big.NewInt(int64(MiningRewardTotal))
|
||||
InitialRewardBalance = InitialRewardBalance.Mul(InitialRewardBalance, big.NewInt(int64(FilecoinPrecision)))
|
||||
}
|
||||
|
||||
// Sync
|
||||
const BadBlockCacheSize = 1 << 15
|
||||
|
||||
// assuming 4000 messages per round, this lets us not lose any messages across a
|
||||
// 10 block reorg.
|
||||
const BlsSignatureCacheSize = 40000
|
||||
|
||||
// Size of signature verification cache
|
||||
// 32k keeps the cache around 10MB in size, max
|
||||
const VerifSigCacheSize = 32000
|
||||
|
||||
// ///////
|
||||
// Limits
|
||||
|
||||
// TODO: If this is gonna stay, it should move to specs-actors
|
||||
const BlockMessageLimit = 512
|
||||
const BlockGasLimit = 100_000_000_000
|
||||
|
||||
var DrandConfig = dtypes.DrandConfig{
|
||||
Servers: []string{
|
||||
"https://pl-eu.testnet.drand.sh",
|
||||
"https://pl-us.testnet.drand.sh",
|
||||
"https://pl-sin.testnet.drand.sh",
|
||||
},
|
||||
ChainInfoJSON: `{"public_key":"922a2e93828ff83345bae533f5172669a26c02dc76d6bf59c80892e12ab1455c229211886f35bb56af6d5bea981024df","period":25,"genesis_time":1590445175,"hash":"138a324aa6540f93d0dad002aa89454b1bec2b6e948682cde6bd4db40f4b7c9b"}`,
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// +build testground
|
||||
|
||||
// This file makes hardcoded parameters (const) configurable as vars.
|
||||
//
|
||||
// Its purpose is to unlock various degrees of flexibility and parametrization
|
||||
// when writing Testground plans for Lotus.
|
||||
//
|
||||
package build
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
)
|
||||
|
||||
var (
|
||||
UnixfsChunkSize = uint64(1 << 20)
|
||||
UnixfsLinksPerLevel = 1024
|
||||
|
||||
BlocksPerEpoch = uint64(builtin.ExpectedLeadersPerEpoch)
|
||||
BlockMessageLimit = 512
|
||||
BlockGasLimit = int64(100_000_000_000)
|
||||
BlockDelaySecs = uint64(builtin.EpochDurationSeconds)
|
||||
PropagationDelaySecs = uint64(6)
|
||||
|
||||
AllowableClockDriftSecs = uint64(1)
|
||||
|
||||
Finality = miner.ChainFinalityish
|
||||
ForkLengthThreshold = Finality
|
||||
|
||||
SlashablePowerDelay = 20
|
||||
InteractivePoRepConfidence = 6
|
||||
|
||||
MessageConfidence uint64 = 5
|
||||
|
||||
WRatioNum = int64(1)
|
||||
WRatioDen = uint64(2)
|
||||
|
||||
BadBlockCacheSize = 1 << 15
|
||||
BlsSignatureCacheSize = 40000
|
||||
VerifSigCacheSize = 32000
|
||||
|
||||
SealRandomnessLookback = Finality
|
||||
SealRandomnessLookbackLimit = SealRandomnessLookback + 2000
|
||||
MaxSealLookback = SealRandomnessLookbackLimit + 2000
|
||||
|
||||
TicketRandomnessLookback = abi.ChainEpoch(1)
|
||||
WinningPoStSectorSetLookback = abi.ChainEpoch(10)
|
||||
|
||||
TotalFilecoin uint64 = 2_000_000_000
|
||||
MiningRewardTotal uint64 = 1_400_000_000
|
||||
|
||||
FilecoinPrecision uint64 = 1_000_000_000_000_000_000
|
||||
|
||||
InitialRewardBalance = func() *big.Int {
|
||||
v := big.NewInt(int64(MiningRewardTotal))
|
||||
v = v.Mul(v, big.NewInt(int64(FilecoinPrecision)))
|
||||
return v
|
||||
}()
|
||||
|
||||
DrandConfig = dtypes.DrandConfig{
|
||||
Servers: []string{
|
||||
"https://pl-eu.testnet.drand.sh",
|
||||
"https://pl-us.testnet.drand.sh",
|
||||
"https://pl-sin.testnet.drand.sh",
|
||||
},
|
||||
ChainInfoJSON: `{"public_key":"922a2e93828ff83345bae533f5172669a26c02dc76d6bf59c80892e12ab1455c229211886f35bb56af6d5bea981024df","period":25,"genesis_time":1590445175,"hash":"138a324aa6540f93d0dad002aa89454b1bec2b6e948682cde6bd4db40f4b7c9b"}`,
|
||||
}
|
||||
)
|
||||
+18
-27
@@ -1,34 +1,25 @@
|
||||
// +build !debug
|
||||
// +build !2k
|
||||
// +build !testground
|
||||
|
||||
package build
|
||||
|
||||
var SectorSizes = []uint64{
|
||||
1 << 30,
|
||||
32 << 30,
|
||||
import (
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/power"
|
||||
)
|
||||
|
||||
func init() {
|
||||
power.ConsensusMinerMinPower = big.NewInt(1024 << 30)
|
||||
miner.SupportedProofTypes = map[abi.RegisteredSealProof]struct{}{
|
||||
abi.RegisteredSealProof_StackedDrg32GiBV1: {},
|
||||
abi.RegisteredSealProof_StackedDrg64GiBV1: {},
|
||||
}
|
||||
}
|
||||
|
||||
// Seconds
|
||||
const BlockDelay = 45
|
||||
const BlockDelaySecs = uint64(builtin.EpochDurationSeconds)
|
||||
|
||||
const PropagationDelay = 6
|
||||
|
||||
// FallbackPoStDelay is the number of epochs the miner needs to wait after
|
||||
// ElectionPeriodStart before starting fallback post computation
|
||||
//
|
||||
// Epochs
|
||||
const FallbackPoStDelay = 30
|
||||
|
||||
// SlashablePowerDelay is the number of epochs after ElectionPeriodStart, after
|
||||
// which the miner is slashed
|
||||
//
|
||||
// Epochs
|
||||
const SlashablePowerDelay = 200
|
||||
|
||||
// Epochs
|
||||
const InteractivePoRepDelay = 8
|
||||
|
||||
// Epochs
|
||||
const InteractivePoRepConfidence = 6
|
||||
|
||||
// Bytes
|
||||
var MinimumMinerPower uint64 = 512 << 30 // 512GB
|
||||
const PropagationDelaySecs = uint64(6)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
function b64() {
|
||||
f="$1"
|
||||
case `uname` in
|
||||
Darwin)
|
||||
base64 -i "$f"
|
||||
;;
|
||||
Linux)
|
||||
base64 "$f" -w 0
|
||||
;;
|
||||
esac
|
||||
printf "unsupported system" 1>&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
sed "s/{{PARAMSJSON}}/$(b64 build/proof-params/parameters.json)/g" build/proof-params/paramfetch.sh.template > ./build/paramfetch.sh
|
||||
chmod +x ./build/paramfetch.sh
|
||||
@@ -1,103 +1,152 @@
|
||||
{
|
||||
"v20-proof-of-spacetime-election-5f585aca354eb68e411c8582ed0efd800792430e4e76d73468c4fc03f1a8d6d2.params": {
|
||||
"cid": "QmX7tYeNPWae2fjZ3Am6GB9dmHvLqvoz8dKo3PR98VYxH9",
|
||||
"digest": "39a9edec3355516674f0d12b926be493",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0170db1f394b35d995252228ee359194b13199d259380541dc529fb0099096b0.params": {
|
||||
"cid": "QmeDRyxek34F1H6xJY6AkFdWvPsy5F6dKTrebV3ZtWT4ky",
|
||||
"digest": "f5827f2d8801c62c831e0f972f6dc8bb",
|
||||
"sector_size": 2048
|
||||
},
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0170db1f394b35d995252228ee359194b13199d259380541dc529fb0099096b0.vk": {
|
||||
"cid": "QmUw1ZmG4BBbX19MsbH3zAEGKUc42iFJc5ZAyomDHeJTsA",
|
||||
"digest": "398fecdb4b2de445125852bc3c080b35",
|
||||
"sector_size": 2048
|
||||
},
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0cfb4f178bbb71cf2ecfcd42accce558b27199ab4fb59cb78f2483fe21ef36d9.params": {
|
||||
"cid": "QmUeNKp9YZpiAFm81RV5KuxH1FDGJx2DuwcbU2XNSZLLSv",
|
||||
"digest": "2b6d2972ac9e862e8134d98fb695b0c5",
|
||||
"sector_size": 536870912
|
||||
},
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0cfb4f178bbb71cf2ecfcd42accce558b27199ab4fb59cb78f2483fe21ef36d9.vk": {
|
||||
"cid": "QmQaQmTXX995Akd66ggtJY5bNx6Gkxk8P34JTdMMq8393G",
|
||||
"digest": "3688c9eb256b7b17f411dad78d5ef74a",
|
||||
"sector_size": 536870912
|
||||
},
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-3ea05428c9d11689f23529cde32fd30aabd50f7d2c93657c1d3650bca3e8ea9e.params": {
|
||||
"cid": "QmfEYTMSkwGJTumQx26iKXGNKiYh3mmAC4SkdybZpJCj5p",
|
||||
"digest": "09bff16aed893349d94485cfae366a9c",
|
||||
"sector_size": 2048
|
||||
},
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-3ea05428c9d11689f23529cde32fd30aabd50f7d2c93657c1d3650bca3e8ea9e.vk": {
|
||||
"cid": "QmP4ThPieSUJyRanjibWpT5R5cCMzMAU4j8Y7kBn7CSW1Q",
|
||||
"digest": "142f2f7e8f1b1779290315cabfd2c803",
|
||||
"sector_size": 2048
|
||||
},
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-50c7368dea9593ed0989e70974d28024efa9d156d585b7eea1be22b2e753f331.params": {
|
||||
"cid": "QmcAixrHsz29DgvtZiMc2kQjvPRvWxYUp36QYmRDZbmREm",
|
||||
"digest": "8f987f64d434365562180b96ec12e299",
|
||||
"sector_size": 8388608
|
||||
},
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-50c7368dea9593ed0989e70974d28024efa9d156d585b7eea1be22b2e753f331.vk": {
|
||||
"cid": "QmT4iFnbL6r4txS5PXsiV7NTzbhCxHy54PvdkJJGV2VFXb",
|
||||
"digest": "94b6c24ac01924f4feeecedd16b5d77d",
|
||||
"sector_size": 8388608
|
||||
},
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-5294475db5237a2e83c3e52fd6c2b03859a1831d45ed08c4f35dbf9a803165a9.params": {
|
||||
"cid": "QmbjFst6SFCK1KsTQrfwPdxf3VTNa1raed574tEZZ9PoyQ",
|
||||
"digest": "2c245fe8179839dd6c6cdea207c67ae8",
|
||||
"sector_size": 8388608
|
||||
},
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-5294475db5237a2e83c3e52fd6c2b03859a1831d45ed08c4f35dbf9a803165a9.vk": {
|
||||
"cid": "QmQJKmvZN1a5cQ1Nw6CDyXs3nuRPzvyU5NvCFMUL2BfcZC",
|
||||
"digest": "56ae47bfda53bb8d22981ed8d8d27d72",
|
||||
"sector_size": 8388608
|
||||
},
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-7d739b8cf60f1b0709eeebee7730e297683552e4b69cab6984ec0285663c5781.params": {
|
||||
"cid": "QmQCABxeTpdvXTyjDyk7nPBxkQzCh7MXfGztWnSXEPKMLW",
|
||||
"digest": "7e6b2eb5ecbb11ac651ad66ebbb2075a",
|
||||
"sector_size": 536870912
|
||||
},
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-7d739b8cf60f1b0709eeebee7730e297683552e4b69cab6984ec0285663c5781.vk": {
|
||||
"cid": "QmPBweyugh5Sx4umk8ULhgEGbjY8xmWLfU6M7EMpc8Mad6",
|
||||
"digest": "94a8d9e25a9ab9674d339833664eba25",
|
||||
"sector_size": 536870912
|
||||
},
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-0377ded656c6f524f1618760bffe4e0a1c51d5a70c4509eedae8a27555733edc.params": {
|
||||
"cid": "QmY5yax1E9KymBnCeHksE9Zi8NieZbmwcpoDGoabkeeb9h",
|
||||
"digest": "c909ea9e3fe25ab9b391a64593afdbba",
|
||||
"sector_size": 34359738368
|
||||
},
|
||||
"v20-proof-of-spacetime-election-5f585aca354eb68e411c8582ed0efd800792430e4e76d73468c4fc03f1a8d6d2.vk": {
|
||||
"cid": "QmbNGx7pNbGiEr8ykoHxVXHW2LNSmGdsxKtj1onZCyguCX",
|
||||
"digest": "0227ae7df4f2affe529ebafbbc7540ee",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-0377ded656c6f524f1618760bffe4e0a1c51d5a70c4509eedae8a27555733edc.vk": {
|
||||
"cid": "QmXnPo4yH5mwMguwrvqgRfduSttbmPrXtbBfbwU21wQWHt",
|
||||
"digest": "caf900461e988bbf86dbcaca087b7864",
|
||||
"sector_size": 34359738368
|
||||
},
|
||||
"v20-proof-of-spacetime-election-a4e18190d4b4657ba1b4d08a341871b2a6f398e327cb9951b28ab141fbdbf49d.params": {
|
||||
"cid": "QmRGZsNp4mp1cZshcXqt3VMuWscAEsiMa2iepF4CsWWoiv",
|
||||
"digest": "991041a354b12c280542741f58c7f2ca",
|
||||
"sector_size": 1024
|
||||
},
|
||||
"v20-proof-of-spacetime-election-a4e18190d4b4657ba1b4d08a341871b2a6f398e327cb9951b28ab141fbdbf49d.vk": {
|
||||
"cid": "QmWpmrhCGVcfqLyqp5oGAnhPmCE5hGTPaauHi25mpQwRSU",
|
||||
"digest": "91fac550e1f9bccab213830bb0c85bd6",
|
||||
"sector_size": 1024
|
||||
},
|
||||
"v20-proof-of-spacetime-election-a9eb6d90b896a282ec2d3a875c6143e3fcff778f0da1460709e051833651559b.params": {
|
||||
"cid": "QmenSZXh1EsSyHiSRvA6wb8yaPhYBTjrKehJw96Px5HnN4",
|
||||
"digest": "6322eacd2773163ddd51f9ca7d645fc4",
|
||||
"sector_size": 1073741824
|
||||
},
|
||||
"v20-proof-of-spacetime-election-a9eb6d90b896a282ec2d3a875c6143e3fcff778f0da1460709e051833651559b.vk": {
|
||||
"cid": "QmPvZoMKofw6eDhDg5ESJA2QAZP8HvM6qMQk7fw4pq9bQf",
|
||||
"digest": "0df62745fceac922e3e70847cfc70b52",
|
||||
"sector_size": 1073741824
|
||||
},
|
||||
"v20-proof-of-spacetime-election-bf872523641b1de33553db2a177df13e412d7b3b0103e6696ae0a1cf5d525259.params": {
|
||||
"cid": "QmVibFqzkZoL8cwQmzj8njPokCQGCCx4pBcUH77bzgJgV9",
|
||||
"digest": "de9d71e672f286706a1673bd57abdaac",
|
||||
"sector_size": 16777216
|
||||
},
|
||||
"v20-proof-of-spacetime-election-bf872523641b1de33553db2a177df13e412d7b3b0103e6696ae0a1cf5d525259.vk": {
|
||||
"cid": "QmZa5FX27XyiEXQQLQpHqtMJKLzrcY8wMuj3pxzmSimSyu",
|
||||
"digest": "7f796d3a0f13499181e44b5eee0cc744",
|
||||
"sector_size": 16777216
|
||||
},
|
||||
"v20-proof-of-spacetime-election-ffc3fb192364238b60977839d14e3154d4a98313e30d46694a12af54b6874975.params": {
|
||||
"cid": "Qmbt2SWWAmMcYoY3DAiRDXA8fAuqdqRLWucJMSxYmzBCmN",
|
||||
"digest": "151ae0ae183fc141e8c2bebc28e5cc10",
|
||||
"sector_size": 268435456
|
||||
},
|
||||
"v20-proof-of-spacetime-election-ffc3fb192364238b60977839d14e3154d4a98313e30d46694a12af54b6874975.vk": {
|
||||
"cid": "QmUxvPu4xdVmjMFihUKoYyEdXBqxsXkvmxRweU7KouWHji",
|
||||
"digest": "95eb89588e9d1832aca044c3a13178af",
|
||||
"sector_size": 268435456
|
||||
},
|
||||
"v20-stacked-proof-of-replication-117839dacd1ef31e5968a6fd13bcd6fa86638d85c40c9241a1d07c2a954eb89b.params": {
|
||||
"cid": "QmQZe8eLo2xXbhSDxtyYZNqEjqjdcWGdADywECRvNEZQdX",
|
||||
"digest": "fcd50e2e08a8560a6bb3418e883567ed",
|
||||
"sector_size": 268435456
|
||||
},
|
||||
"v20-stacked-proof-of-replication-117839dacd1ef31e5968a6fd13bcd6fa86638d85c40c9241a1d07c2a954eb89b.vk": {
|
||||
"cid": "Qme1hn6QT1covfoUFGDZkqoE1pMTax9FNW3nWWmTNqFe7y",
|
||||
"digest": "872e244d86499fd659082e3bcf3f13e7",
|
||||
"sector_size": 268435456
|
||||
},
|
||||
"v20-stacked-proof-of-replication-b46f3a1051afbb67f70aae7082da95def62eee943662f3e1bf69837fb08aaae4.params": {
|
||||
"cid": "QmSfrPDC9jwY4MKrjzhCqDBBAG44wSDM8oE5NuDwWSh2xN",
|
||||
"digest": "0a338b941c5f17946340de5fc95cab30",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-559e581f022bb4e4ec6e719e563bf0e026ad6de42e56c18714a2c692b1b88d7e.params": {
|
||||
"cid": "QmZtzzPWwmZEgR7MSMvXRbt9KVK8k4XZ5RLWHybHJW9SdE",
|
||||
"digest": "a2844f0703f186d143a06146a04577d8",
|
||||
"sector_size": 34359738368
|
||||
},
|
||||
"v20-stacked-proof-of-replication-b46f3a1051afbb67f70aae7082da95def62eee943662f3e1bf69837fb08aaae4.vk": {
|
||||
"cid": "QmTDGynCmnbaZNBP3Bv3F3duC3ecKRubCKeMUiQQZYbGpF",
|
||||
"digest": "c752e070a6b7aa8b79aa661a6b600b55",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-559e581f022bb4e4ec6e719e563bf0e026ad6de42e56c18714a2c692b1b88d7e.vk": {
|
||||
"cid": "QmWxEA7EdQCUJTzjNpxg5XTF45D2uVyYnN1QRUb5TRYU8M",
|
||||
"digest": "2306247a1e616dbe07f01b88196c2044",
|
||||
"sector_size": 34359738368
|
||||
},
|
||||
"v20-stacked-proof-of-replication-e71093863cadc71de61f38311ee45816633973bbf34849316b147f8d2e66f199.params": {
|
||||
"cid": "QmXjSSnMUnc7EjQBYtTHhvLU3kXJTbUyhVhJRSTRehh186",
|
||||
"digest": "efa407fd09202dffd15799a8518e73d3",
|
||||
"sector_size": 1024
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-2627e4006b67f99cef990c0a47d5426cb7ab0a0ad58fc1061547bf2d28b09def.params": {
|
||||
"cid": "QmP676KwuvyF9Y64uJnXvLtvD1xcuWQ6wD23RzYtQ6dd4f",
|
||||
"digest": "215b1c667a4f46a1d0178338df568615",
|
||||
"sector_size": 68719476736
|
||||
},
|
||||
"v20-stacked-proof-of-replication-e71093863cadc71de61f38311ee45816633973bbf34849316b147f8d2e66f199.vk": {
|
||||
"cid": "QmYHW3zhQouDP4okFbXSsRMcZ8bokKGvzxqbv7ZrunPMiG",
|
||||
"digest": "b2f09a0ccb62da28c890d5b881c8dcd2",
|
||||
"sector_size": 1024
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-2627e4006b67f99cef990c0a47d5426cb7ab0a0ad58fc1061547bf2d28b09def.vk": {
|
||||
"cid": "QmPvPwbJtcSGyqB1rQJhSF5yvFbX9ZBSsHVej5F8JUyHUJ",
|
||||
"digest": "0c9c423b28b1455fcbc329a1045fd4dd",
|
||||
"sector_size": 68719476736
|
||||
},
|
||||
"v20-stacked-proof-of-replication-e99a585174b6a45b254ba4780d72c89ad808c305c6d11711009ade4f39dba8e9.params": {
|
||||
"cid": "QmUhyfNeLb32LfSkjsUwTFYLXQGMj6JQ8daff4DdVMt79q",
|
||||
"digest": "b53c1916a63839ec345aa2224e9198b7",
|
||||
"sector_size": 1073741824
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-b62098629d07946e9028127e70295ed996fe3ed25b0f9f88eb610a0ab4385a3c.params": {
|
||||
"cid": "QmUxPQfvckzm1t6MFRdDZ1fDK5UJzAjK7pTZ97cwyachdr",
|
||||
"digest": "965132f51ae445b0e6d32692b7561995",
|
||||
"sector_size": 68719476736
|
||||
},
|
||||
"v20-stacked-proof-of-replication-e99a585174b6a45b254ba4780d72c89ad808c305c6d11711009ade4f39dba8e9.vk": {
|
||||
"cid": "QmWReGfbuoozNErbskmFvqV4q36BY6F2WWb4cVFc3zoYkA",
|
||||
"digest": "20d58a3fae7343481f8298a2dd493dd7",
|
||||
"sector_size": 1073741824
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-b62098629d07946e9028127e70295ed996fe3ed25b0f9f88eb610a0ab4385a3c.vk": {
|
||||
"cid": "QmTxq2EBnQWb5R8tS4MHdchj4vNfLYGoSXxwJFvs5xgW4K",
|
||||
"digest": "fc8c3d26e0e56373ad96cb41520d55a6",
|
||||
"sector_size": 68719476736
|
||||
},
|
||||
"v20-stacked-proof-of-replication-f571ee2386f4c65a68e802747f2d78691006fc81a67971c4d9641403fffece16.params": {
|
||||
"cid": "QmSAHu14Pe8iav6BYCt9XkpHJ73XM7tcpY4d9JK9BST9HU",
|
||||
"digest": "7698426202c7e07b26ef056d31485b3a",
|
||||
"sector_size": 16777216
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-032d3138d22506ec0082ed72b2dcba18df18477904e35bafee82b3793b06832f.params": {
|
||||
"cid": "QmRjgZHERgqGoRagR788Kh6ybi26csVYa8mqbqhmZm57Jx",
|
||||
"digest": "cfc7b0897d1eee48c586f7beb89e67f7",
|
||||
"sector_size": 2048
|
||||
},
|
||||
"v20-stacked-proof-of-replication-f571ee2386f4c65a68e802747f2d78691006fc81a67971c4d9641403fffece16.vk": {
|
||||
"cid": "QmaKtFLShnhMGVn7P9UsHjkgqtqRFSwCStqqykBN7u8dax",
|
||||
"digest": "834408e5c3fce6ec5d1bf64e64cee94e",
|
||||
"sector_size": 16777216
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-032d3138d22506ec0082ed72b2dcba18df18477904e35bafee82b3793b06832f.vk": {
|
||||
"cid": "QmNjvnvFP7KgovHUddULoB19fBHT81iz7NcUbzEHZUUPsm",
|
||||
"digest": "fb59bd061c987eac7068008c44de346b",
|
||||
"sector_size": 2048
|
||||
},
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-6babf46ce344ae495d558e7770a585b2382d54f225af8ed0397b8be7c3fcd472.params": {
|
||||
"cid": "QmTpRPBA4dt8fgGpcVzi4L1KA1U2eBHCE8WVmS2GUygMvT",
|
||||
"digest": "36d465915b0afbf96bd08e7915e00952",
|
||||
"sector_size": 536870912
|
||||
},
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-6babf46ce344ae495d558e7770a585b2382d54f225af8ed0397b8be7c3fcd472.vk": {
|
||||
"cid": "QmRzDyVfQCLsxspoVsed5bcQRsG6KiktngJfcNBL3TJPZe",
|
||||
"digest": "99d16df0eb6a7e227a4f4570c4f6b6f1",
|
||||
"sector_size": 536870912
|
||||
},
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-ecd683648512ab1765faa2a5f14bab48f676e633467f0aa8aad4b55dcb0652bb.params": {
|
||||
"cid": "QmV8ZjTSGzDUWmFvsq9NSyPBR7eDDUcvCPNgj2yE7HMAFu",
|
||||
"digest": "34f3ddf1d1c9f41c0cd73b91e8b4bc27",
|
||||
"sector_size": 8388608
|
||||
},
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-ecd683648512ab1765faa2a5f14bab48f676e633467f0aa8aad4b55dcb0652bb.vk": {
|
||||
"cid": "QmTa3VbjTiqJWU6r4WKayaQrUaaBsrpp5UDqYvPDd2C5hs",
|
||||
"digest": "ec62d59651daa5631d3d1e9c782dd940",
|
||||
"sector_size": 8388608
|
||||
},
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-0-sha256_hasher-82a357d2f2ca81dc61bb45f4a762807aedee1b0a53fd6c4e77b46a01bfef7820.params": {
|
||||
"cid": "Qmf8ngfArxrv9tFWDqBcNegdBMymvuakwyHKd1pbW3pbsb",
|
||||
"digest": "a16d6f4c6424fb280236739f84b24f97",
|
||||
"sector_size": 34359738368
|
||||
},
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-0-sha256_hasher-82a357d2f2ca81dc61bb45f4a762807aedee1b0a53fd6c4e77b46a01bfef7820.vk": {
|
||||
"cid": "QmfQgVFerArJ6Jupwyc9tKjLD9n1J9ajLHBdpY465tRM7M",
|
||||
"digest": "7a139d82b8a02e35279d657e197f5c1f",
|
||||
"sector_size": 34359738368
|
||||
},
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-2-sha256_hasher-96f1b4a04c5c51e4759bbf224bbc2ef5a42c7100f16ec0637123f16a845ddfb2.params": {
|
||||
"cid": "QmfDha8271nXJn14Aq3qQeghjMBWbs6HNSGa6VuzCVk4TW",
|
||||
"digest": "5d3cd3f107a3bea8a96d1189efd2965c",
|
||||
"sector_size": 68719476736
|
||||
},
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-2-sha256_hasher-96f1b4a04c5c51e4759bbf224bbc2ef5a42c7100f16ec0637123f16a845ddfb2.vk": {
|
||||
"cid": "QmRVtTtiFzHJTHurYzaCvetGAchux9cktixT4aGHthN6Zt",
|
||||
"digest": "62c366405404e60f171e661492740b1c",
|
||||
"sector_size": 68719476736
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PARAMS='{{PARAMSJSON}}'
|
||||
|
||||
die() {
|
||||
echo "$@" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
have_binary() {
|
||||
type "$1" > /dev/null 2> /dev/null
|
||||
}
|
||||
|
||||
check_writable() {
|
||||
printf "" > "$1" && rm "$1"
|
||||
}
|
||||
|
||||
try_download() {
|
||||
url="$1"
|
||||
output="$2"
|
||||
command="$3"
|
||||
util_name="$(set -- $command; echo "$1")"
|
||||
|
||||
if ! have_binary "$util_name"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '==> Using %s to download "%s" to "%s"\n' "$util_name" "$url" "$output"
|
||||
if eval "$command"; then
|
||||
echo "==> Download complete!"
|
||||
return
|
||||
else
|
||||
echo "error: couldn't download with $util_name ($?)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
download() {
|
||||
dl_url="$1"
|
||||
dl_output="$2"
|
||||
|
||||
test "$#" -eq "2" || die "download requires exactly two arguments, was given $@"
|
||||
|
||||
if ! check_writable "$dl_output"; then
|
||||
die "download error: cannot write to $dl_output"
|
||||
fi
|
||||
|
||||
try_download "$dl_url" "$dl_output" "wget '$dl_url' -O '$dl_output'" && return
|
||||
try_download "$dl_url" "$dl_output" "curl --silent --fail --output '$dl_output' '$dl_url'" && return
|
||||
try_download "$dl_url" "$dl_output" "fetch '$dl_url' -o '$dl_output'" && return
|
||||
try_download "$dl_url" "$dl_output" "http '$dl_url' > '$dl_output'" && return
|
||||
try_download "$dl_url" "$dl_output" "ftp -o '$dl_output' '$dl_url'" && return
|
||||
|
||||
die "Unable to download $dl_url. exiting."
|
||||
}
|
||||
|
||||
fetch_ipget() {
|
||||
local dest="$1"
|
||||
local cid="$2"
|
||||
|
||||
IPGET_PARAMS="--node=spawn -p=/ip4/138.201.67.219/tcp/4002/ws/ipfs/QmUd6zHcbkbcs7SMxwLs48qZVX3vpcM8errYS7xEczwRMA -p=/ip4/138.201.67.218/tcp/4002/ws/ipfs/QmbVWZQhCGrS7DhgLqWbgvdmKN7JueKCREVanfnVpgyq8x -p=/ip4/94.130.135.167/tcp/4002/ws/ipfs/QmUEMvxS2e7iDrereVYc5SWPauXPyNwxcy9BXZrC1QTcHE -p=/ip4/138.201.68.74/tcp/4001/ipfs/QmdnXwLrC8p1ueiq2Qya8joNvk3TVVDAut7PrikmZwubtR -p=/ip4/138.201.67.220/tcp/4001/ipfs/QmNSYxZAiJHeLdkBg38roksAR9So7Y5eojks1yjEcUtZ7i"
|
||||
|
||||
./bin/ipget $IPGET_PARAMS -o "$dest" "$cid"
|
||||
}
|
||||
|
||||
fetch_gateway() {
|
||||
local dest="$1"
|
||||
local cid="$2"
|
||||
|
||||
local url="http://198.211.99.118/ipfs/$cid"
|
||||
|
||||
download "$url" "$dest"
|
||||
}
|
||||
|
||||
OUT_DIR="/var/tmp/filecoin-proof-parameters"
|
||||
|
||||
mkdir -p $OUT_DIR
|
||||
printf $PARAMS | base64 -d | jq '. | to_entries | map("'$OUT_DIR'/\(.key) \(.value.cid) \(.value.digest)") | .[]' --raw-output | \
|
||||
while read -r dest cid digest; do
|
||||
if [[ -f "$dest" ]]; then
|
||||
b2=$(b2sum "$dest" | head -c 32)
|
||||
if [[ "$digest" == "$b2" ]]; then
|
||||
echo "$dest exists and has correct hash"
|
||||
continue
|
||||
else
|
||||
echo "$dest has incorrect hash"
|
||||
rm -f "$dest"
|
||||
fi
|
||||
fi
|
||||
echo "downloading $dest"
|
||||
|
||||
fetch_gateway "$dest" "$cid"
|
||||
done
|
||||
+26
-3
@@ -3,11 +3,33 @@ package build
|
||||
import "fmt"
|
||||
|
||||
var CurrentCommit string
|
||||
var BuildType int
|
||||
|
||||
const (
|
||||
BuildDefault = 0
|
||||
Build2k = 0x1
|
||||
BuildDebug = 0x3
|
||||
)
|
||||
|
||||
func buildType() string {
|
||||
switch BuildType {
|
||||
case BuildDefault:
|
||||
return ""
|
||||
case BuildDebug:
|
||||
return "+debug"
|
||||
case Build2k:
|
||||
return "+2k"
|
||||
default:
|
||||
return "+huh?"
|
||||
}
|
||||
}
|
||||
|
||||
// BuildVersion is the local build version, set by build system
|
||||
const BuildVersion = "0.1.5"
|
||||
const BuildVersion = "0.4.1"
|
||||
|
||||
var UserVersion = BuildVersion + CurrentCommit
|
||||
func UserVersion() string {
|
||||
return BuildVersion + buildType() + CurrentCommit
|
||||
}
|
||||
|
||||
type Version uint32
|
||||
|
||||
@@ -31,8 +53,9 @@ func (ve Version) EqMajorMinor(v2 Version) bool {
|
||||
}
|
||||
|
||||
// APIVersion is a semver version of the rpc api exposed
|
||||
var APIVersion Version = newVer(0, 1, 5)
|
||||
var APIVersion Version = newVer(0, 6, 0)
|
||||
|
||||
//nolint:varcheck,deadcode
|
||||
const (
|
||||
majorMask = 0xff0000
|
||||
minorMask = 0xffff00
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package actors
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
type CronActor struct{}
|
||||
|
||||
type callTuple struct {
|
||||
addr address.Address
|
||||
method uint64
|
||||
}
|
||||
|
||||
var CronActors = []callTuple{
|
||||
{StoragePowerAddress, SPAMethods.CheckProofSubmissions},
|
||||
}
|
||||
|
||||
type CronActorState struct{}
|
||||
|
||||
type cAMethods struct {
|
||||
EpochTick uint64
|
||||
}
|
||||
|
||||
var CAMethods = cAMethods{2}
|
||||
|
||||
func (ca CronActor) Exports() []interface{} {
|
||||
return []interface{}{
|
||||
1: nil,
|
||||
2: ca.EpochTick,
|
||||
}
|
||||
}
|
||||
|
||||
func (ca CronActor) EpochTick(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
|
||||
if vmctx.Message().From != CronAddress {
|
||||
return nil, aerrors.New(1, "EpochTick is only callable as a part of tipset state computation")
|
||||
}
|
||||
|
||||
for _, call := range CronActors {
|
||||
_, err := vmctx.Send(call.addr, call.method, types.NewInt(0), nil)
|
||||
if err != nil {
|
||||
return nil, err // todo: this very bad?
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
package actors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-hamt-ipld"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
logging "github.com/ipfs/go-log"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
var log = logging.Logger("actors")
|
||||
|
||||
var EmptyCBOR cid.Cid
|
||||
|
||||
const (
|
||||
GasCreateActor = 100
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
n, err := cbor.WrapObject(map[string]string{}, mh.SHA2_256, -1)
|
||||
if err != nil {
|
||||
panic(err) // ok
|
||||
}
|
||||
|
||||
EmptyCBOR = n.Cid()
|
||||
}
|
||||
|
||||
type InitActor struct{}
|
||||
|
||||
type InitActorState struct {
|
||||
AddressMap cid.Cid
|
||||
|
||||
NextID uint64
|
||||
}
|
||||
|
||||
type iAMethods struct {
|
||||
Exec uint64
|
||||
}
|
||||
|
||||
var IAMethods = iAMethods{2}
|
||||
|
||||
func (ia InitActor) Exports() []interface{} {
|
||||
return []interface{}{
|
||||
1: nil,
|
||||
2: ia.Exec,
|
||||
}
|
||||
}
|
||||
|
||||
type ExecParams struct {
|
||||
Code cid.Cid
|
||||
Params []byte
|
||||
}
|
||||
|
||||
func CreateExecParams(act cid.Cid, obj cbg.CBORMarshaler) ([]byte, aerrors.ActorError) {
|
||||
encparams, err := SerializeParams(obj)
|
||||
if err != nil {
|
||||
return nil, aerrors.Wrap(err, "creating ExecParams")
|
||||
}
|
||||
|
||||
return SerializeParams(&ExecParams{
|
||||
Code: act,
|
||||
Params: encparams,
|
||||
})
|
||||
}
|
||||
|
||||
func (ia InitActor) Exec(act *types.Actor, vmctx types.VMContext, p *ExecParams) ([]byte, aerrors.ActorError) {
|
||||
beginState := vmctx.Storage().GetHead()
|
||||
|
||||
var self InitActorState
|
||||
if err := vmctx.Storage().Get(beginState, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := vmctx.ChargeGas(GasCreateActor); err != nil {
|
||||
return nil, aerrors.Wrap(err, "run out of gas")
|
||||
}
|
||||
|
||||
// Make sure that only the actors defined in the spec can be launched.
|
||||
if !IsBuiltinActor(p.Code) {
|
||||
return nil, aerrors.New(1,
|
||||
"cannot launch actor instance that is not a builtin actor")
|
||||
}
|
||||
|
||||
// Ensure that singletons can be only launched once.
|
||||
// TODO: do we want to enforce this? If so how should actors be marked as such?
|
||||
if IsSingletonActor(p.Code) {
|
||||
return nil, aerrors.New(1, "cannot launch another actor of this type")
|
||||
}
|
||||
|
||||
// This generates a unique address for this actor that is stable across message
|
||||
// reordering
|
||||
creator := vmctx.Message().From
|
||||
nonce := vmctx.Message().Nonce
|
||||
addr, err := ComputeActorAddress(creator, nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set up the actor itself
|
||||
actor := types.Actor{
|
||||
Code: p.Code,
|
||||
Balance: types.NewInt(0),
|
||||
Head: EmptyCBOR,
|
||||
Nonce: 0,
|
||||
}
|
||||
|
||||
// The call to the actors constructor will set up the initial state
|
||||
// from the given parameters, setting `actor.Head` to a new value when successful.
|
||||
// TODO: can constructors fail?
|
||||
//actor.Constructor(p.Params)
|
||||
|
||||
// Store the mapping of address to actor ID.
|
||||
idAddr, nerr := self.AddActor(vmctx.Ipld(), addr)
|
||||
if nerr != nil {
|
||||
return nil, aerrors.Escalate(err, "adding new actor mapping")
|
||||
}
|
||||
|
||||
// NOTE: This is a privileged call that only the init actor is allowed to make
|
||||
// FIXME: Had to comment this because state is not in interface
|
||||
state, err := vmctx.StateTree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := state.SetActor(idAddr, &actor); err != nil {
|
||||
if xerrors.Is(err, types.ErrActorNotFound) {
|
||||
return nil, aerrors.Absorb(err, 1, "SetActor, actor not found")
|
||||
}
|
||||
return nil, aerrors.Escalate(err, "inserting new actor into state tree")
|
||||
}
|
||||
|
||||
// '1' is reserved for constructor methods
|
||||
_, err = vmctx.Send(idAddr, 1, vmctx.Message().Value, p.Params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := vmctx.Storage().Put(&self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := vmctx.Storage().Commit(beginState, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return idAddr.Bytes(), nil
|
||||
}
|
||||
|
||||
func IsBuiltinActor(code cid.Cid) bool {
|
||||
switch code {
|
||||
case StorageMarketCodeCid, StoragePowerCodeCid, StorageMinerCodeCid, AccountCodeCid, InitCodeCid, MultisigCodeCid, PaymentChannelCodeCid:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func IsSingletonActor(code cid.Cid) bool {
|
||||
return code == StoragePowerCodeCid || code == StorageMarketCodeCid || code == InitCodeCid || code == CronCodeCid
|
||||
}
|
||||
|
||||
func (ias *InitActorState) AddActor(cst *hamt.CborIpldStore, addr address.Address) (address.Address, error) {
|
||||
nid := ias.NextID
|
||||
|
||||
amap, err := hamt.LoadNode(context.TODO(), cst, ias.AddressMap)
|
||||
if err != nil {
|
||||
return address.Undef, err
|
||||
}
|
||||
|
||||
if err := amap.Set(context.TODO(), string(addr.Bytes()), nid); err != nil {
|
||||
return address.Undef, err
|
||||
}
|
||||
|
||||
if err := amap.Flush(context.TODO()); err != nil {
|
||||
return address.Undef, err
|
||||
}
|
||||
|
||||
ncid, err := cst.Put(context.TODO(), amap)
|
||||
if err != nil {
|
||||
return address.Undef, err
|
||||
}
|
||||
ias.AddressMap = ncid
|
||||
ias.NextID++
|
||||
|
||||
return NewIDAddress(nid)
|
||||
}
|
||||
|
||||
func (ias *InitActorState) Lookup(cst *hamt.CborIpldStore, addr address.Address) (address.Address, error) {
|
||||
amap, err := hamt.LoadNode(context.TODO(), cst, ias.AddressMap)
|
||||
if err != nil {
|
||||
return address.Undef, xerrors.Errorf("ias lookup failed loading hamt node: %w", err)
|
||||
}
|
||||
|
||||
var val interface{}
|
||||
err = amap.Find(context.TODO(), string(addr.Bytes()), &val)
|
||||
if err != nil {
|
||||
return address.Undef, xerrors.Errorf("ias lookup failed to do find: %w", err)
|
||||
}
|
||||
|
||||
ival, ok := val.(uint64)
|
||||
if !ok {
|
||||
return address.Undef, fmt.Errorf("invalid value in init actor state, expected uint64, got %T", val)
|
||||
}
|
||||
|
||||
return address.NewIDAddress(ival)
|
||||
}
|
||||
|
||||
type AccountActorState struct {
|
||||
Address address.Address
|
||||
}
|
||||
|
||||
func ComputeActorAddress(creator address.Address, nonce uint64) (address.Address, ActorError) {
|
||||
buf := new(bytes.Buffer)
|
||||
_, err := buf.Write(creator.Bytes())
|
||||
if err != nil {
|
||||
return address.Undef, aerrors.Escalate(err, "could not write address")
|
||||
}
|
||||
|
||||
err = binary.Write(buf, binary.BigEndian, nonce)
|
||||
if err != nil {
|
||||
return address.Undef, aerrors.Escalate(err, "could not write nonce")
|
||||
}
|
||||
|
||||
addr, err := address.NewActorAddress(buf.Bytes())
|
||||
if err != nil {
|
||||
return address.Undef, aerrors.Escalate(err, "could not create address")
|
||||
}
|
||||
return addr, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,157 +0,0 @@
|
||||
package actors_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/stmgr"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/sectorbuilder"
|
||||
hamt "github.com/ipfs/go-hamt-ipld"
|
||||
blockstore "github.com/ipfs/go-ipfs-blockstore"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
)
|
||||
|
||||
func TestMinerCommitSectors(t *testing.T) {
|
||||
var worker, client address.Address
|
||||
var minerAddr address.Address
|
||||
opts := []HarnessOpt{
|
||||
HarnessAddr(&worker, 1000000),
|
||||
HarnessAddr(&client, 1000000),
|
||||
HarnessActor(&minerAddr, &worker, actors.StorageMinerCodeCid,
|
||||
func() cbg.CBORMarshaler {
|
||||
return &actors.StorageMinerConstructorParams{
|
||||
Owner: worker,
|
||||
Worker: worker,
|
||||
SectorSize: 1024,
|
||||
PeerID: "fakepeerid",
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
h := NewHarness(t, opts...)
|
||||
h.vm.Syscalls.ValidatePoRep = func(ctx context.Context, maddr address.Address, ssize uint64, commD, commR, ticket, proof, seed []byte, sectorID uint64) (bool, aerrors.ActorError) {
|
||||
// all proofs are valid
|
||||
return true, nil
|
||||
}
|
||||
|
||||
ret, _ := h.SendFunds(t, worker, minerAddr, types.NewInt(100000))
|
||||
ApplyOK(t, ret)
|
||||
|
||||
ret, _ = h.InvokeWithValue(t, client, actors.StorageMarketAddress, actors.SMAMethods.AddBalance, types.NewInt(2000), nil)
|
||||
ApplyOK(t, ret)
|
||||
|
||||
addSectorToMiner(h, t, minerAddr, worker, client, 1)
|
||||
|
||||
assertSectorIDs(h, t, minerAddr, []uint64{1})
|
||||
}
|
||||
|
||||
func addSectorToMiner(h *Harness, t *testing.T, minerAddr, worker, client address.Address, sid uint64) {
|
||||
t.Helper()
|
||||
s := sectorbuilder.UserBytesForSectorSize(1024)
|
||||
deal := h.makeFakeDeal(t, minerAddr, worker, client, s)
|
||||
ret, _ := h.Invoke(t, worker, actors.StorageMarketAddress, actors.SMAMethods.PublishStorageDeals,
|
||||
&actors.PublishStorageDealsParams{
|
||||
Deals: []actors.StorageDealProposal{*deal},
|
||||
})
|
||||
ApplyOK(t, ret)
|
||||
var dealIds actors.PublishStorageDealResponse
|
||||
if err := dealIds.UnmarshalCBOR(bytes.NewReader(ret.Return)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dealid := dealIds.DealIDs[0]
|
||||
|
||||
ret, _ = h.Invoke(t, worker, minerAddr, actors.MAMethods.PreCommitSector,
|
||||
&actors.SectorPreCommitInfo{
|
||||
SectorNumber: sid,
|
||||
CommR: []byte("cats"),
|
||||
SealEpoch: 10,
|
||||
DealIDs: []uint64{dealid},
|
||||
})
|
||||
ApplyOK(t, ret)
|
||||
|
||||
h.BlockHeight += 100
|
||||
ret, _ = h.Invoke(t, worker, minerAddr, actors.MAMethods.ProveCommitSector,
|
||||
&actors.SectorProveCommitInfo{
|
||||
Proof: []byte("prooofy"),
|
||||
SectorID: sid,
|
||||
DealIDs: []uint64{dealid}, // TODO: weird that i have to pass this again
|
||||
})
|
||||
ApplyOK(t, ret)
|
||||
}
|
||||
|
||||
func assertSectorIDs(h *Harness, t *testing.T, maddr address.Address, ids []uint64) {
|
||||
t.Helper()
|
||||
sectors, err := getMinerSectorSet(context.TODO(), h.vm.StateTree(), h.bs, maddr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(sectors) != len(ids) {
|
||||
t.Fatal("miner has wrong number of sectors in their sector set")
|
||||
}
|
||||
|
||||
all := make(map[uint64]bool)
|
||||
for _, s := range sectors {
|
||||
all[s.SectorID] = true
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
if !all[id] {
|
||||
t.Fatal("expected to find sector ID: ", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getMinerSectorSet(ctx context.Context, st types.StateTree, bs blockstore.Blockstore, maddr address.Address) ([]*api.ChainSectorInfo, error) {
|
||||
mact, err := st.GetActor(maddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cst := hamt.CSTFromBstore(bs)
|
||||
|
||||
var mstate actors.StorageMinerActorState
|
||||
if err := cst.Get(ctx, mact.Head, &mstate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stmgr.LoadSectorsFromSet(ctx, bs, mstate.Sectors)
|
||||
}
|
||||
|
||||
func (h *Harness) makeFakeDeal(t *testing.T, miner, worker, client address.Address, size uint64) *actors.StorageDealProposal {
|
||||
data := make([]byte, size)
|
||||
rand.Read(data)
|
||||
commP, err := sectorbuilder.GeneratePieceCommitment(bytes.NewReader(data), size)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
prop := actors.StorageDealProposal{
|
||||
PieceRef: commP[:],
|
||||
PieceSize: size,
|
||||
//PieceSerialization SerializationMode // Needs to be here as it tells how data in the sector maps to PieceRef cid
|
||||
|
||||
Client: client,
|
||||
Provider: miner,
|
||||
|
||||
ProposalExpiration: 10000,
|
||||
Duration: 150,
|
||||
|
||||
StoragePricePerEpoch: types.NewInt(1),
|
||||
StorageCollateral: types.NewInt(0),
|
||||
}
|
||||
|
||||
if err := api.SignWith(context.TODO(), h.w.Sign, client, &prop); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return &prop
|
||||
}
|
||||
@@ -1,431 +0,0 @@
|
||||
package actors
|
||||
|
||||
import (
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
)
|
||||
|
||||
type MultiSigActor struct{}
|
||||
type MultiSigActorState struct {
|
||||
Signers []address.Address
|
||||
Required uint64
|
||||
NextTxID uint64
|
||||
|
||||
InitialBalance types.BigInt
|
||||
StartingBlock uint64
|
||||
UnlockDuration uint64
|
||||
|
||||
//TODO: make this map/sharray/whatever
|
||||
Transactions []MTransaction
|
||||
}
|
||||
|
||||
func (msas MultiSigActorState) canSpend(act *types.Actor, amnt types.BigInt, height uint64) bool {
|
||||
if msas.UnlockDuration == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
offset := height - msas.StartingBlock
|
||||
if offset > msas.UnlockDuration {
|
||||
return true
|
||||
}
|
||||
|
||||
minBalance := types.BigDiv(msas.InitialBalance, types.NewInt(msas.UnlockDuration))
|
||||
minBalance = types.BigMul(minBalance, types.NewInt(offset))
|
||||
return !minBalance.LessThan(types.BigSub(act.Balance, amnt))
|
||||
}
|
||||
|
||||
func (msas MultiSigActorState) isSigner(addr address.Address) bool {
|
||||
for _, s := range msas.Signers {
|
||||
if s == addr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (msas MultiSigActorState) getTransaction(txid uint64) (*MTransaction, ActorError) {
|
||||
if txid >= uint64(len(msas.Transactions)) {
|
||||
return nil, aerrors.Newf(1, "could not get transaction (numbers of tx %d,want to get txid %d)", len(msas.Transactions), txid)
|
||||
}
|
||||
return &msas.Transactions[txid], nil
|
||||
}
|
||||
|
||||
type MTransaction struct {
|
||||
Created uint64 // NOT USED ??
|
||||
TxID uint64
|
||||
|
||||
To address.Address
|
||||
Value types.BigInt
|
||||
Method uint64
|
||||
Params []byte
|
||||
|
||||
Approved []address.Address
|
||||
Complete bool
|
||||
Canceled bool
|
||||
RetCode uint64
|
||||
}
|
||||
|
||||
func (tx MTransaction) Active() ActorError {
|
||||
if tx.Complete {
|
||||
return aerrors.New(2, "transaction already completed")
|
||||
}
|
||||
if tx.Canceled {
|
||||
return aerrors.New(3, "transaction canceled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type musigMethods struct {
|
||||
MultiSigConstructor uint64
|
||||
Propose uint64
|
||||
Approve uint64
|
||||
Cancel uint64
|
||||
ClearCompleted uint64
|
||||
AddSigner uint64
|
||||
RemoveSigner uint64
|
||||
SwapSigner uint64
|
||||
ChangeRequirement uint64
|
||||
}
|
||||
|
||||
var MultiSigMethods = musigMethods{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
|
||||
func (msa MultiSigActor) Exports() []interface{} {
|
||||
return []interface{}{
|
||||
1: msa.MultiSigConstructor,
|
||||
2: msa.Propose,
|
||||
3: msa.Approve,
|
||||
4: msa.Cancel,
|
||||
//5: msa.ClearCompleted,
|
||||
6: msa.AddSigner,
|
||||
7: msa.RemoveSigner,
|
||||
8: msa.SwapSigner,
|
||||
9: msa.ChangeRequirement,
|
||||
}
|
||||
}
|
||||
|
||||
type MultiSigConstructorParams struct {
|
||||
Signers []address.Address
|
||||
Required uint64
|
||||
UnlockDuration uint64
|
||||
}
|
||||
|
||||
func (MultiSigActor) MultiSigConstructor(act *types.Actor, vmctx types.VMContext,
|
||||
params *MultiSigConstructorParams) ([]byte, ActorError) {
|
||||
self := &MultiSigActorState{
|
||||
Signers: params.Signers,
|
||||
Required: params.Required,
|
||||
}
|
||||
|
||||
if params.UnlockDuration != 0 {
|
||||
self.InitialBalance = vmctx.Message().Value
|
||||
self.UnlockDuration = params.UnlockDuration
|
||||
self.StartingBlock = vmctx.BlockHeight()
|
||||
}
|
||||
|
||||
head, err := vmctx.Storage().Put(self)
|
||||
if err != nil {
|
||||
return nil, aerrors.Wrap(err, "could not put new head")
|
||||
}
|
||||
err = vmctx.Storage().Commit(EmptyCBOR, head)
|
||||
if err != nil {
|
||||
return nil, aerrors.Wrap(err, "could not commit new head")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type MultiSigProposeParams struct {
|
||||
To address.Address
|
||||
Value types.BigInt
|
||||
Method uint64
|
||||
Params []byte
|
||||
}
|
||||
|
||||
func (MultiSigActor) load(vmctx types.VMContext) (cid.Cid, *MultiSigActorState, ActorError) {
|
||||
var self MultiSigActorState
|
||||
head := vmctx.Storage().GetHead()
|
||||
|
||||
err := vmctx.Storage().Get(head, &self)
|
||||
if err != nil {
|
||||
return cid.Undef, nil, aerrors.Wrap(err, "could not get self")
|
||||
}
|
||||
return head, &self, nil
|
||||
}
|
||||
|
||||
func (msa MultiSigActor) loadAndVerify(vmctx types.VMContext) (cid.Cid, *MultiSigActorState, ActorError) {
|
||||
head, self, err := msa.load(vmctx)
|
||||
if err != nil {
|
||||
return cid.Undef, nil, err
|
||||
}
|
||||
|
||||
if !self.isSigner(vmctx.Message().From) {
|
||||
return cid.Undef, nil, aerrors.New(1, "not authorized")
|
||||
}
|
||||
return head, self, nil
|
||||
}
|
||||
|
||||
func (MultiSigActor) save(vmctx types.VMContext, oldHead cid.Cid, self *MultiSigActorState) ActorError {
|
||||
newHead, err := vmctx.Storage().Put(self)
|
||||
if err != nil {
|
||||
return aerrors.Wrap(err, "could not put new head")
|
||||
}
|
||||
err = vmctx.Storage().Commit(oldHead, newHead)
|
||||
if err != nil {
|
||||
return aerrors.Wrap(err, "could not commit new head")
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (msa MultiSigActor) Propose(act *types.Actor, vmctx types.VMContext,
|
||||
params *MultiSigProposeParams) ([]byte, ActorError) {
|
||||
|
||||
head, self, err := msa.loadAndVerify(vmctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txid := self.NextTxID
|
||||
self.NextTxID++
|
||||
|
||||
{
|
||||
tx := MTransaction{
|
||||
TxID: txid,
|
||||
To: params.To,
|
||||
Value: params.Value,
|
||||
Method: params.Method,
|
||||
Params: params.Params,
|
||||
Approved: []address.Address{vmctx.Message().From},
|
||||
}
|
||||
self.Transactions = append(self.Transactions, tx)
|
||||
}
|
||||
|
||||
tx, err := self.getTransaction(txid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if self.Required == 1 {
|
||||
if !self.canSpend(act, tx.Value, vmctx.BlockHeight()) {
|
||||
return nil, aerrors.New(100, "transaction amount exceeds available")
|
||||
}
|
||||
_, err := vmctx.Send(tx.To, tx.Method, tx.Value, tx.Params)
|
||||
if aerrors.IsFatal(err) {
|
||||
return nil, err
|
||||
}
|
||||
tx.RetCode = uint64(aerrors.RetCode(err))
|
||||
tx.Complete = true
|
||||
}
|
||||
|
||||
err = msa.save(vmctx, head, self)
|
||||
if err != nil {
|
||||
return nil, aerrors.Wrap(err, "saving state")
|
||||
}
|
||||
|
||||
// REVIEW: On one hand, I like being very explicit about how we're doing the serialization
|
||||
// on the other, maybe we shouldnt do direct calls to underlying serialization libs?
|
||||
return cbg.CborEncodeMajorType(cbg.MajUnsignedInt, tx.TxID), nil
|
||||
}
|
||||
|
||||
type MultiSigTxID struct {
|
||||
TxID uint64
|
||||
}
|
||||
|
||||
func (msa MultiSigActor) Approve(act *types.Actor, vmctx types.VMContext,
|
||||
params *MultiSigTxID) ([]byte, ActorError) {
|
||||
|
||||
head, self, err := msa.loadAndVerify(vmctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx, err := self.getTransaction(params.TxID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := tx.Active(); err != nil {
|
||||
return nil, aerrors.Wrap(err, "could not approve")
|
||||
}
|
||||
|
||||
for _, signer := range tx.Approved {
|
||||
if signer == vmctx.Message().From {
|
||||
return nil, aerrors.New(4, "already signed this message")
|
||||
}
|
||||
}
|
||||
tx.Approved = append(tx.Approved, vmctx.Message().From)
|
||||
if uint64(len(tx.Approved)) >= self.Required {
|
||||
if !self.canSpend(act, tx.Value, vmctx.BlockHeight()) {
|
||||
return nil, aerrors.New(100, "transaction amount exceeds available")
|
||||
}
|
||||
_, err := vmctx.Send(tx.To, tx.Method, tx.Value, tx.Params)
|
||||
if aerrors.IsFatal(err) {
|
||||
return nil, err
|
||||
}
|
||||
tx.RetCode = uint64(aerrors.RetCode(err))
|
||||
tx.Complete = true
|
||||
}
|
||||
|
||||
return nil, msa.save(vmctx, head, self)
|
||||
}
|
||||
|
||||
func (msa MultiSigActor) Cancel(act *types.Actor, vmctx types.VMContext,
|
||||
params *MultiSigTxID) ([]byte, ActorError) {
|
||||
|
||||
head, self, err := msa.loadAndVerify(vmctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx, err := self.getTransaction(params.TxID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := tx.Active(); err != nil {
|
||||
return nil, aerrors.Wrap(err, "could not cancel")
|
||||
}
|
||||
|
||||
proposer := tx.Approved[0]
|
||||
if proposer != vmctx.Message().From && self.isSigner(proposer) {
|
||||
return nil, aerrors.New(4, "cannot cancel another signers transaction")
|
||||
}
|
||||
tx.Canceled = true
|
||||
|
||||
return nil, msa.save(vmctx, head, self)
|
||||
}
|
||||
|
||||
type MultiSigAddSignerParam struct {
|
||||
Signer address.Address
|
||||
Increase bool
|
||||
}
|
||||
|
||||
func (msa MultiSigActor) AddSigner(act *types.Actor, vmctx types.VMContext,
|
||||
params *MultiSigAddSignerParam) ([]byte, ActorError) {
|
||||
|
||||
head, self, err := msa.load(vmctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := vmctx.Message()
|
||||
if msg.From != msg.To {
|
||||
return nil, aerrors.New(4, "add signer must be called by wallet itself")
|
||||
}
|
||||
if self.isSigner(params.Signer) {
|
||||
return nil, aerrors.New(5, "new address is already a signer")
|
||||
}
|
||||
|
||||
self.Signers = append(self.Signers, params.Signer)
|
||||
if params.Increase {
|
||||
self.Required = self.Required + 1
|
||||
}
|
||||
|
||||
return nil, msa.save(vmctx, head, self)
|
||||
}
|
||||
|
||||
type MultiSigRemoveSignerParam struct {
|
||||
Signer address.Address
|
||||
Decrease bool
|
||||
}
|
||||
|
||||
func (msa MultiSigActor) RemoveSigner(act *types.Actor, vmctx types.VMContext,
|
||||
params *MultiSigRemoveSignerParam) ([]byte, ActorError) {
|
||||
|
||||
head, self, err := msa.load(vmctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := vmctx.Message()
|
||||
if msg.From != msg.To {
|
||||
return nil, aerrors.New(4, "remove signer must be called by wallet itself")
|
||||
}
|
||||
if !self.isSigner(params.Signer) {
|
||||
return nil, aerrors.New(5, "given address was not a signer")
|
||||
}
|
||||
|
||||
newSigners := make([]address.Address, 0, len(self.Signers)-1)
|
||||
for _, s := range self.Signers {
|
||||
if s != params.Signer {
|
||||
newSigners = append(newSigners, s)
|
||||
}
|
||||
}
|
||||
if params.Decrease || uint64(len(self.Signers)-1) < self.Required {
|
||||
self.Required = self.Required - 1
|
||||
}
|
||||
|
||||
self.Signers = newSigners
|
||||
|
||||
return nil, msa.save(vmctx, head, self)
|
||||
}
|
||||
|
||||
type MultiSigSwapSignerParams struct {
|
||||
From address.Address
|
||||
To address.Address
|
||||
}
|
||||
|
||||
func (msa MultiSigActor) SwapSigner(act *types.Actor, vmctx types.VMContext,
|
||||
params *MultiSigSwapSignerParams) ([]byte, ActorError) {
|
||||
|
||||
head, self, err := msa.load(vmctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := vmctx.Message()
|
||||
if msg.From != msg.To {
|
||||
return nil, aerrors.New(4, "swap signer must be called by wallet itself")
|
||||
}
|
||||
|
||||
if !self.isSigner(params.From) {
|
||||
return nil, aerrors.New(5, "given old address was not a signer")
|
||||
}
|
||||
if self.isSigner(params.To) {
|
||||
return nil, aerrors.New(6, "given new address was already a signer")
|
||||
}
|
||||
|
||||
newSigners := make([]address.Address, 0, len(self.Signers))
|
||||
for _, s := range self.Signers {
|
||||
if s != params.From {
|
||||
newSigners = append(newSigners, s)
|
||||
}
|
||||
}
|
||||
newSigners = append(newSigners, params.To)
|
||||
self.Signers = newSigners
|
||||
|
||||
return nil, msa.save(vmctx, head, self)
|
||||
}
|
||||
|
||||
type MultiSigChangeReqParams struct {
|
||||
Req uint64
|
||||
}
|
||||
|
||||
func (msa MultiSigActor) ChangeRequirement(act *types.Actor, vmctx types.VMContext,
|
||||
params *MultiSigChangeReqParams) ([]byte, ActorError) {
|
||||
|
||||
head, self, err := msa.load(vmctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := vmctx.Message()
|
||||
if msg.From != msg.To {
|
||||
return nil, aerrors.New(4, "change requirement must be called by wallet itself")
|
||||
}
|
||||
|
||||
if params.Req < 1 {
|
||||
return nil, aerrors.New(5, "requirement must be at least 1")
|
||||
}
|
||||
|
||||
if params.Req > uint64(len(self.Signers)) {
|
||||
return nil, aerrors.New(6, "requirement must be at most the numbers of signers")
|
||||
}
|
||||
|
||||
self.Required = params.Req
|
||||
return nil, msa.save(vmctx, head, self)
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package actors_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
"github.com/stretchr/testify/assert"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/vm"
|
||||
)
|
||||
|
||||
func TestMultiSigCreate(t *testing.T) {
|
||||
var creatorAddr, sig1Addr, sig2Addr, outsideAddr address.Address
|
||||
opts := []HarnessOpt{
|
||||
HarnessAddr(&creatorAddr, 100000),
|
||||
HarnessAddr(&sig1Addr, 100000),
|
||||
HarnessAddr(&sig2Addr, 100000),
|
||||
HarnessAddr(&outsideAddr, 100000),
|
||||
}
|
||||
|
||||
h := NewHarness(t, opts...)
|
||||
ret, _ := h.CreateActor(t, creatorAddr, actors.MultisigCodeCid,
|
||||
&actors.MultiSigConstructorParams{
|
||||
Signers: []address.Address{creatorAddr, sig1Addr, sig2Addr},
|
||||
Required: 2,
|
||||
})
|
||||
ApplyOK(t, ret)
|
||||
}
|
||||
|
||||
func ApplyOK(t testing.TB, ret *vm.ApplyRet) {
|
||||
t.Helper()
|
||||
if ret.ExitCode != 0 {
|
||||
t.Fatalf("exit code should be 0, got %d, actorErr: %+v", ret.ExitCode, ret.ActorErr)
|
||||
}
|
||||
if ret.ActorErr != nil {
|
||||
t.Fatalf("somehow got an error with exit == 0: %s", ret.ActorErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiSigOps(t *testing.T) {
|
||||
var creatorAddr, sig1Addr, sig2Addr, outsideAddr address.Address
|
||||
var multSigAddr address.Address
|
||||
opts := []HarnessOpt{
|
||||
HarnessAddr(&creatorAddr, 100000),
|
||||
HarnessAddr(&sig1Addr, 100000),
|
||||
HarnessAddr(&sig2Addr, 100000),
|
||||
HarnessAddr(&outsideAddr, 100000),
|
||||
HarnessActor(&multSigAddr, &creatorAddr, actors.MultisigCodeCid,
|
||||
func() cbg.CBORMarshaler {
|
||||
return &actors.MultiSigConstructorParams{
|
||||
Signers: []address.Address{creatorAddr, sig1Addr, sig2Addr},
|
||||
Required: 2,
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
h := NewHarness(t, opts...)
|
||||
{
|
||||
const chargeVal = 2000
|
||||
// Send funds into the multisig
|
||||
ret, _ := h.SendFunds(t, creatorAddr, multSigAddr, types.NewInt(chargeVal))
|
||||
ApplyOK(t, ret)
|
||||
h.AssertBalanceChange(t, creatorAddr, -chargeVal)
|
||||
h.AssertBalanceChange(t, multSigAddr, chargeVal)
|
||||
}
|
||||
|
||||
{
|
||||
// Transfer funds outside of multsig
|
||||
const sendVal = 1000
|
||||
ret, _ := h.Invoke(t, creatorAddr, multSigAddr, actors.MultiSigMethods.Propose,
|
||||
&actors.MultiSigProposeParams{
|
||||
To: outsideAddr,
|
||||
Value: types.NewInt(sendVal),
|
||||
})
|
||||
ApplyOK(t, ret)
|
||||
var txIDParam actors.MultiSigTxID
|
||||
err := cbor.DecodeInto(ret.Return, &txIDParam.TxID)
|
||||
assert.NoError(t, err, "decoding txid")
|
||||
|
||||
ret, _ = h.Invoke(t, outsideAddr, multSigAddr, actors.MultiSigMethods.Approve,
|
||||
&txIDParam)
|
||||
assert.Equal(t, uint8(1), ret.ExitCode, "outsideAddr should not approve")
|
||||
h.AssertBalanceChange(t, multSigAddr, 0)
|
||||
|
||||
ret2, _ := h.Invoke(t, sig1Addr, multSigAddr, actors.MultiSigMethods.Approve,
|
||||
&txIDParam)
|
||||
ApplyOK(t, ret2)
|
||||
|
||||
h.AssertBalanceChange(t, outsideAddr, sendVal)
|
||||
h.AssertBalanceChange(t, multSigAddr, -sendVal)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
package actors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/minio/blake2b-simd"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
type PaymentChannelActor struct{}
|
||||
|
||||
type PaymentInfo struct {
|
||||
PayChActor address.Address
|
||||
Payer address.Address
|
||||
ChannelMessage *cid.Cid
|
||||
|
||||
Vouchers []*types.SignedVoucher
|
||||
}
|
||||
|
||||
type LaneState struct {
|
||||
Closed bool
|
||||
Redeemed types.BigInt
|
||||
Nonce uint64
|
||||
}
|
||||
|
||||
type PaymentChannelActorState struct {
|
||||
From address.Address
|
||||
To address.Address
|
||||
|
||||
ToSend types.BigInt
|
||||
|
||||
ClosingAt uint64
|
||||
MinCloseHeight uint64
|
||||
|
||||
// TODO: needs to be map[uint64]*laneState
|
||||
// waiting on refmt#35 to be fixed
|
||||
LaneStates map[string]*LaneState
|
||||
}
|
||||
|
||||
func (pca PaymentChannelActor) Exports() []interface{} {
|
||||
return []interface{}{
|
||||
1: pca.Constructor,
|
||||
2: pca.UpdateChannelState,
|
||||
3: pca.Close,
|
||||
4: pca.Collect,
|
||||
5: pca.GetOwner,
|
||||
6: pca.GetToSend,
|
||||
}
|
||||
}
|
||||
|
||||
type pcaMethods struct {
|
||||
Constructor uint64
|
||||
UpdateChannelState uint64
|
||||
Close uint64
|
||||
Collect uint64
|
||||
GetOwner uint64
|
||||
GetToSend uint64
|
||||
}
|
||||
|
||||
var PCAMethods = pcaMethods{1, 2, 3, 4, 5, 6}
|
||||
|
||||
type PCAConstructorParams struct {
|
||||
To address.Address
|
||||
}
|
||||
|
||||
func (pca PaymentChannelActor) Constructor(act *types.Actor, vmctx types.VMContext, params *PCAConstructorParams) ([]byte, ActorError) {
|
||||
var self PaymentChannelActorState
|
||||
self.From = vmctx.Origin()
|
||||
self.To = params.To
|
||||
self.LaneStates = make(map[string]*LaneState)
|
||||
|
||||
storage := vmctx.Storage()
|
||||
c, err := storage.Put(&self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := storage.Commit(EmptyCBOR, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type PCAUpdateChannelStateParams struct {
|
||||
Sv types.SignedVoucher
|
||||
Secret []byte
|
||||
Proof []byte
|
||||
}
|
||||
|
||||
func hash(b []byte) []byte {
|
||||
s := blake2b.Sum256(b)
|
||||
return s[:]
|
||||
}
|
||||
|
||||
type PaymentVerifyParams struct {
|
||||
Extra []byte
|
||||
Proof []byte
|
||||
}
|
||||
|
||||
func (pca PaymentChannelActor) UpdateChannelState(act *types.Actor, vmctx types.VMContext, params *PCAUpdateChannelStateParams) ([]byte, ActorError) {
|
||||
var self PaymentChannelActorState
|
||||
oldstate := vmctx.Storage().GetHead()
|
||||
storage := vmctx.Storage()
|
||||
if err := storage.Get(oldstate, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sv := params.Sv
|
||||
|
||||
vb, nerr := sv.SigningBytes()
|
||||
if nerr != nil {
|
||||
return nil, aerrors.Escalate(nerr, "failed to serialize signedvoucher")
|
||||
}
|
||||
|
||||
if err := vmctx.VerifySignature(sv.Signature, self.From, vb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if vmctx.BlockHeight() < sv.TimeLock {
|
||||
return nil, aerrors.New(2, "cannot use this voucher yet!")
|
||||
}
|
||||
|
||||
if len(sv.SecretPreimage) > 0 {
|
||||
if !bytes.Equal(hash(params.Secret), sv.SecretPreimage) {
|
||||
return nil, aerrors.New(3, "incorrect secret!")
|
||||
}
|
||||
}
|
||||
|
||||
if sv.Extra != nil {
|
||||
encoded, err := SerializeParams(&PaymentVerifyParams{sv.Extra.Data, params.Proof})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = vmctx.Send(sv.Extra.Actor, sv.Extra.Method, types.NewInt(0), encoded)
|
||||
if err != nil {
|
||||
return nil, aerrors.Newf(4, "spend voucher verification failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
ls, ok := self.LaneStates[fmt.Sprint(sv.Lane)]
|
||||
if !ok {
|
||||
ls = new(LaneState)
|
||||
ls.Redeemed = types.NewInt(0) // TODO: kinda annoying that this doesnt default to a usable value
|
||||
self.LaneStates[fmt.Sprint(sv.Lane)] = ls
|
||||
}
|
||||
if ls.Closed {
|
||||
return nil, aerrors.New(5, "cannot redeem a voucher on a closed lane")
|
||||
}
|
||||
|
||||
if ls.Nonce > sv.Nonce {
|
||||
return nil, aerrors.New(6, "voucher has an outdated nonce, cannot redeem")
|
||||
}
|
||||
|
||||
mergeValue := types.NewInt(0)
|
||||
for _, merge := range sv.Merges {
|
||||
if merge.Lane == sv.Lane {
|
||||
return nil, aerrors.New(7, "voucher cannot merge its own lane")
|
||||
}
|
||||
|
||||
ols := self.LaneStates[fmt.Sprint(merge.Lane)]
|
||||
|
||||
if ols.Nonce >= merge.Nonce {
|
||||
return nil, aerrors.New(8, "merge in voucher has outdated nonce, cannot redeem")
|
||||
}
|
||||
|
||||
mergeValue = types.BigAdd(mergeValue, ols.Redeemed)
|
||||
ols.Nonce = merge.Nonce
|
||||
}
|
||||
|
||||
ls.Nonce = sv.Nonce
|
||||
balanceDelta := types.BigSub(sv.Amount, types.BigAdd(mergeValue, ls.Redeemed))
|
||||
ls.Redeemed = sv.Amount
|
||||
|
||||
newSendBalance := types.BigAdd(self.ToSend, balanceDelta)
|
||||
if newSendBalance.LessThan(types.NewInt(0)) {
|
||||
// TODO: is this impossible?
|
||||
return nil, aerrors.New(9, "voucher would leave channel balance negative")
|
||||
}
|
||||
|
||||
if newSendBalance.GreaterThan(act.Balance) {
|
||||
return nil, aerrors.New(10, "not enough funds in channel to cover voucher")
|
||||
}
|
||||
|
||||
log.Info("vals: ", newSendBalance, sv.Amount, balanceDelta, mergeValue, ls.Redeemed)
|
||||
self.ToSend = newSendBalance
|
||||
|
||||
if sv.MinCloseHeight != 0 {
|
||||
if self.ClosingAt != 0 && self.ClosingAt < sv.MinCloseHeight {
|
||||
self.ClosingAt = sv.MinCloseHeight
|
||||
}
|
||||
if self.MinCloseHeight < sv.MinCloseHeight {
|
||||
self.MinCloseHeight = sv.MinCloseHeight
|
||||
}
|
||||
}
|
||||
|
||||
ncid, err := storage.Put(&self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := storage.Commit(oldstate, ncid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (pca PaymentChannelActor) Close(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, aerrors.ActorError) {
|
||||
var self PaymentChannelActorState
|
||||
storage := vmctx.Storage()
|
||||
oldstate := storage.GetHead()
|
||||
if err := storage.Get(oldstate, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if vmctx.Message().From != self.From && vmctx.Message().From != self.To {
|
||||
return nil, aerrors.New(1, "not authorized to close channel")
|
||||
}
|
||||
|
||||
if self.ClosingAt != 0 {
|
||||
return nil, aerrors.New(2, "channel already closing")
|
||||
}
|
||||
|
||||
self.ClosingAt = vmctx.BlockHeight() + build.PaymentChannelClosingDelay
|
||||
if self.ClosingAt < self.MinCloseHeight {
|
||||
self.ClosingAt = self.MinCloseHeight
|
||||
}
|
||||
|
||||
ncid, err := storage.Put(&self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := storage.Commit(oldstate, ncid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (pca PaymentChannelActor) Collect(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, aerrors.ActorError) {
|
||||
var self PaymentChannelActorState
|
||||
storage := vmctx.Storage()
|
||||
oldstate := storage.GetHead()
|
||||
if err := storage.Get(oldstate, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if self.ClosingAt == 0 {
|
||||
return nil, aerrors.New(1, "payment channel not closing or closed")
|
||||
}
|
||||
|
||||
if vmctx.BlockHeight() < self.ClosingAt {
|
||||
return nil, aerrors.New(2, "payment channel not closed yet")
|
||||
}
|
||||
_, err := vmctx.Send(self.From, 0, types.BigSub(act.Balance, self.ToSend), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = vmctx.Send(self.To, 0, self.ToSend, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
self.ToSend = types.NewInt(0)
|
||||
|
||||
ncid, err := storage.Put(&self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := storage.Commit(oldstate, ncid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (pca PaymentChannelActor) GetOwner(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, aerrors.ActorError) {
|
||||
var self PaymentChannelActorState
|
||||
storage := vmctx.Storage()
|
||||
if err := storage.Get(storage.GetHead(), &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return self.From.Bytes(), nil
|
||||
}
|
||||
|
||||
func (pca PaymentChannelActor) GetToSend(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, aerrors.ActorError) {
|
||||
var self PaymentChannelActorState
|
||||
storage := vmctx.Storage()
|
||||
if err := storage.Get(storage.GetHead(), &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return self.ToSend.Bytes(), nil
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package actors_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/wallet"
|
||||
)
|
||||
|
||||
func TestPaychCreate(t *testing.T) {
|
||||
var creatorAddr, targetAddr address.Address
|
||||
opts := []HarnessOpt{
|
||||
HarnessAddr(&creatorAddr, 100000),
|
||||
HarnessAddr(&targetAddr, 100000),
|
||||
}
|
||||
|
||||
h := NewHarness(t, opts...)
|
||||
ret, _ := h.CreateActor(t, creatorAddr, actors.PaymentChannelCodeCid,
|
||||
&actors.PCAConstructorParams{
|
||||
To: targetAddr,
|
||||
})
|
||||
ApplyOK(t, ret)
|
||||
}
|
||||
|
||||
func signVoucher(t *testing.T, w *wallet.Wallet, addr address.Address, sv *types.SignedVoucher) {
|
||||
vb, err := sv.SigningBytes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sig, err := w.Sign(context.TODO(), addr, vb)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sv.Signature = sig
|
||||
}
|
||||
|
||||
func TestPaychUpdate(t *testing.T) {
|
||||
var creatorAddr, targetAddr address.Address
|
||||
opts := []HarnessOpt{
|
||||
HarnessAddr(&creatorAddr, 100000),
|
||||
HarnessAddr(&targetAddr, 100000),
|
||||
}
|
||||
|
||||
h := NewHarness(t, opts...)
|
||||
ret, _ := h.CreateActor(t, creatorAddr, actors.PaymentChannelCodeCid,
|
||||
&actors.PCAConstructorParams{
|
||||
To: targetAddr,
|
||||
})
|
||||
ApplyOK(t, ret)
|
||||
pch, err := address.NewFromBytes(ret.Return)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ret, _ = h.SendFunds(t, creatorAddr, pch, types.NewInt(5000))
|
||||
ApplyOK(t, ret)
|
||||
|
||||
sv := &types.SignedVoucher{
|
||||
Amount: types.NewInt(100),
|
||||
Nonce: 1,
|
||||
}
|
||||
signVoucher(t, h.w, creatorAddr, sv)
|
||||
|
||||
ret, _ = h.Invoke(t, targetAddr, pch, actors.PCAMethods.UpdateChannelState, &actors.PCAUpdateChannelStateParams{
|
||||
Sv: *sv,
|
||||
})
|
||||
ApplyOK(t, ret)
|
||||
|
||||
ret, _ = h.Invoke(t, targetAddr, pch, actors.PCAMethods.GetToSend, nil)
|
||||
ApplyOK(t, ret)
|
||||
|
||||
bi := types.BigFromBytes(ret.Return)
|
||||
if bi.String() != "100" {
|
||||
t.Fatal("toSend amount was wrong: ", bi.String())
|
||||
}
|
||||
|
||||
ret, _ = h.Invoke(t, targetAddr, pch, actors.PCAMethods.Close, nil)
|
||||
ApplyOK(t, ret)
|
||||
|
||||
// now we have to 'wait' for the chain to advance.
|
||||
h.BlockHeight = 1000
|
||||
|
||||
ret, _ = h.Invoke(t, targetAddr, pch, actors.PCAMethods.Collect, nil)
|
||||
ApplyOK(t, ret)
|
||||
|
||||
h.AssertBalanceChange(t, targetAddr, 100)
|
||||
h.AssertBalanceChange(t, creatorAddr, -100)
|
||||
}
|
||||
@@ -1,670 +0,0 @@
|
||||
package actors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-amt-ipld"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-hamt-ipld"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/cborutil"
|
||||
"github.com/filecoin-project/lotus/lib/sectorbuilder"
|
||||
)
|
||||
|
||||
type StorageMarketActor struct{}
|
||||
|
||||
type smaMethods struct {
|
||||
Constructor uint64
|
||||
WithdrawBalance uint64
|
||||
AddBalance uint64
|
||||
CheckLockedBalance uint64
|
||||
PublishStorageDeals uint64
|
||||
HandleCronAction uint64
|
||||
SettleExpiredDeals uint64
|
||||
ProcessStorageDealsPayment uint64
|
||||
SlashStorageDealCollateral uint64
|
||||
GetLastExpirationFromDealIDs uint64
|
||||
ActivateStorageDeals uint64
|
||||
ComputeDataCommitment uint64
|
||||
}
|
||||
|
||||
var SMAMethods = smaMethods{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
|
||||
|
||||
func (sma StorageMarketActor) Exports() []interface{} {
|
||||
return []interface{}{
|
||||
2: sma.WithdrawBalance,
|
||||
3: sma.AddBalance,
|
||||
// 4: sma.CheckLockedBalance,
|
||||
5: sma.PublishStorageDeals,
|
||||
// 6: sma.HandleCronAction,
|
||||
// 7: sma.SettleExpiredDeals,
|
||||
// 8: sma.ProcessStorageDealsPayment,
|
||||
// 9: sma.SlashStorageDealCollateral,
|
||||
// 10: sma.GetLastExpirationFromDealIDs,
|
||||
11: sma.ActivateStorageDeals, // TODO: move under PublishStorageDeals after specs team approves
|
||||
12: sma.ComputeDataCommitment,
|
||||
}
|
||||
}
|
||||
|
||||
type StorageParticipantBalance struct {
|
||||
Locked types.BigInt
|
||||
Available types.BigInt
|
||||
}
|
||||
|
||||
type StorageMarketState struct {
|
||||
Balances cid.Cid // hamt<addr, StorageParticipantBalance>
|
||||
Deals cid.Cid // amt<StorageDeal>
|
||||
|
||||
NextDealID uint64 // TODO: spec
|
||||
}
|
||||
|
||||
// TODO: Drop in favour of car storage
|
||||
type SerializationMode = uint64
|
||||
|
||||
const (
|
||||
SerializationUnixFSv0 = iota
|
||||
// IPLD / car
|
||||
)
|
||||
|
||||
type StorageDealProposal struct {
|
||||
PieceRef []byte // cid bytes // TODO: spec says to use cid.Cid, probably not a good idea
|
||||
PieceSize uint64
|
||||
|
||||
Client address.Address
|
||||
Provider address.Address
|
||||
|
||||
ProposalExpiration uint64
|
||||
Duration uint64 // TODO: spec
|
||||
|
||||
StoragePricePerEpoch types.BigInt
|
||||
StorageCollateral types.BigInt
|
||||
|
||||
ProposerSignature *types.Signature
|
||||
}
|
||||
|
||||
func (sdp *StorageDealProposal) TotalStoragePrice() types.BigInt {
|
||||
return types.BigMul(sdp.StoragePricePerEpoch, types.NewInt(sdp.Duration))
|
||||
}
|
||||
|
||||
type SignFunc = func(context.Context, []byte) (*types.Signature, error)
|
||||
|
||||
func (sdp *StorageDealProposal) Sign(ctx context.Context, sign SignFunc) error {
|
||||
if sdp.ProposerSignature != nil {
|
||||
return xerrors.New("signature already present in StorageDealProposal")
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := sdp.MarshalCBOR(&buf); err != nil {
|
||||
return err
|
||||
}
|
||||
sig, err := sign(ctx, buf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sdp.ProposerSignature = sig
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sdp *StorageDealProposal) Cid() (cid.Cid, error) {
|
||||
nd, err := cborutil.AsIpld(sdp)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
return nd.Cid(), nil
|
||||
}
|
||||
|
||||
func (sdp *StorageDealProposal) Verify() error {
|
||||
unsigned := *sdp
|
||||
unsigned.ProposerSignature = nil
|
||||
var buf bytes.Buffer
|
||||
if err := unsigned.MarshalCBOR(&buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sdp.ProposerSignature.Verify(sdp.Client, buf.Bytes())
|
||||
}
|
||||
|
||||
type OnChainDeal struct {
|
||||
PieceRef []byte // cid bytes // TODO: spec says to use cid.Cid, probably not a good idea
|
||||
PieceSize uint64
|
||||
|
||||
Client address.Address
|
||||
Provider address.Address
|
||||
|
||||
ProposalExpiration uint64
|
||||
Duration uint64 // TODO: spec
|
||||
|
||||
StoragePricePerEpoch types.BigInt
|
||||
StorageCollateral types.BigInt
|
||||
ActivationEpoch uint64 // 0 = inactive
|
||||
}
|
||||
|
||||
type WithdrawBalanceParams struct {
|
||||
Balance types.BigInt
|
||||
}
|
||||
|
||||
func (sma StorageMarketActor) WithdrawBalance(act *types.Actor, vmctx types.VMContext, params *WithdrawBalanceParams) ([]byte, ActorError) {
|
||||
// TODO: (spec) this should be 2-stage
|
||||
|
||||
var self StorageMarketState
|
||||
old := vmctx.Storage().GetHead()
|
||||
if err := vmctx.Storage().Get(old, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, bnd, err := GetMarketBalances(vmctx.Context(), vmctx.Ipld(), self.Balances, vmctx.Message().From)
|
||||
if err != nil {
|
||||
return nil, aerrors.Wrap(err, "could not get balance")
|
||||
}
|
||||
|
||||
balance := b[0]
|
||||
|
||||
if balance.Available.LessThan(params.Balance) {
|
||||
return nil, aerrors.Newf(1, "can not withdraw more funds than available: %s > %s", params.Balance, b[0].Available)
|
||||
}
|
||||
|
||||
balance.Available = types.BigSub(balance.Available, params.Balance)
|
||||
|
||||
_, err = vmctx.Send(vmctx.Message().From, 0, params.Balance, nil)
|
||||
if err != nil {
|
||||
return nil, aerrors.Wrap(err, "sending funds failed")
|
||||
}
|
||||
|
||||
bcid, err := setMarketBalances(vmctx, bnd, map[address.Address]StorageParticipantBalance{
|
||||
vmctx.Message().From: balance,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
self.Balances = bcid
|
||||
|
||||
nroot, err := vmctx.Storage().Put(&self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, vmctx.Storage().Commit(old, nroot)
|
||||
}
|
||||
|
||||
func (sma StorageMarketActor) AddBalance(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
|
||||
var self StorageMarketState
|
||||
old := vmctx.Storage().GetHead()
|
||||
if err := vmctx.Storage().Get(old, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, bnd, err := GetMarketBalances(vmctx.Context(), vmctx.Ipld(), self.Balances, vmctx.Message().From)
|
||||
if err != nil {
|
||||
return nil, aerrors.Wrap(err, "could not get balance")
|
||||
}
|
||||
|
||||
balance := b[0]
|
||||
|
||||
balance.Available = types.BigAdd(balance.Available, vmctx.Message().Value)
|
||||
|
||||
bcid, err := setMarketBalances(vmctx, bnd, map[address.Address]StorageParticipantBalance{
|
||||
vmctx.Message().From: balance,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
self.Balances = bcid
|
||||
|
||||
nroot, err := vmctx.Storage().Put(&self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, vmctx.Storage().Commit(old, nroot)
|
||||
}
|
||||
|
||||
func setMarketBalances(vmctx types.VMContext, nd *hamt.Node, set map[address.Address]StorageParticipantBalance) (cid.Cid, ActorError) {
|
||||
keys := make([]address.Address, 0, len(set))
|
||||
for k := range set {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return bytes.Compare(keys[i].Bytes(), keys[j].Bytes()) < 0
|
||||
})
|
||||
for _, addr := range keys {
|
||||
balance := set[addr]
|
||||
if err := nd.Set(vmctx.Context(), string(addr.Bytes()), &balance); err != nil {
|
||||
return cid.Undef, aerrors.HandleExternalError(err, "setting new balance")
|
||||
}
|
||||
}
|
||||
if err := nd.Flush(vmctx.Context()); err != nil {
|
||||
return cid.Undef, aerrors.HandleExternalError(err, "flushing balance hamt")
|
||||
}
|
||||
|
||||
c, err := vmctx.Ipld().Put(vmctx.Context(), nd)
|
||||
if err != nil {
|
||||
return cid.Undef, aerrors.HandleExternalError(err, "failed to balances storage")
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func GetMarketBalances(ctx context.Context, store *hamt.CborIpldStore, rcid cid.Cid, addrs ...address.Address) ([]StorageParticipantBalance, *hamt.Node, ActorError) {
|
||||
ctx, span := trace.StartSpan(ctx, "GetMarketBalances")
|
||||
defer span.End()
|
||||
|
||||
nd, err := hamt.LoadNode(ctx, store, rcid)
|
||||
if err != nil {
|
||||
return nil, nil, aerrors.HandleExternalError(err, "failed to load miner set")
|
||||
}
|
||||
|
||||
out := make([]StorageParticipantBalance, len(addrs))
|
||||
|
||||
for i, a := range addrs {
|
||||
var balance StorageParticipantBalance
|
||||
err = nd.Find(ctx, string(a.Bytes()), &balance)
|
||||
switch err {
|
||||
case hamt.ErrNotFound:
|
||||
out[i] = StorageParticipantBalance{
|
||||
Locked: types.NewInt(0),
|
||||
Available: types.NewInt(0),
|
||||
}
|
||||
case nil:
|
||||
out[i] = balance
|
||||
default:
|
||||
return nil, nil, aerrors.HandleExternalError(err, "failed to do set lookup")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return out, nd, nil
|
||||
}
|
||||
|
||||
/*
|
||||
func (sma StorageMarketActor) CheckLockedBalance(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
type PublishStorageDealsParams struct {
|
||||
Deals []StorageDealProposal
|
||||
}
|
||||
|
||||
type PublishStorageDealResponse struct {
|
||||
DealIDs []uint64
|
||||
}
|
||||
|
||||
func (sma StorageMarketActor) PublishStorageDeals(act *types.Actor, vmctx types.VMContext, params *PublishStorageDealsParams) ([]byte, ActorError) {
|
||||
var self StorageMarketState
|
||||
old := vmctx.Storage().GetHead()
|
||||
if err := vmctx.Storage().Get(old, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deals, err := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.Deals)
|
||||
if err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "loading deals amt")
|
||||
}
|
||||
|
||||
// todo: handle duplicate deals
|
||||
|
||||
if len(params.Deals) == 0 {
|
||||
return nil, aerrors.New(1, "no storage deals in params.Deals")
|
||||
}
|
||||
|
||||
out := PublishStorageDealResponse{
|
||||
DealIDs: make([]uint64, len(params.Deals)),
|
||||
}
|
||||
|
||||
workerBytes, aerr := vmctx.Send(params.Deals[0].Provider, MAMethods.GetWorkerAddr, types.NewInt(0), nil)
|
||||
if aerr != nil {
|
||||
return nil, aerr
|
||||
}
|
||||
providerWorker, err := address.NewFromBytes(workerBytes)
|
||||
if err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "parsing provider worker address bytes")
|
||||
}
|
||||
|
||||
// TODO: REVIEW: Do we want to check if provider exists in the power actor?
|
||||
|
||||
for i, deal := range params.Deals {
|
||||
if err := self.validateDeal(vmctx, deal, providerWorker); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := deals.Set(self.NextDealID, &OnChainDeal{
|
||||
PieceRef: deal.PieceRef,
|
||||
PieceSize: deal.PieceSize,
|
||||
|
||||
Client: deal.Client,
|
||||
Provider: deal.Provider,
|
||||
|
||||
ProposalExpiration: deal.ProposalExpiration,
|
||||
Duration: deal.Duration,
|
||||
|
||||
StoragePricePerEpoch: deal.StoragePricePerEpoch,
|
||||
StorageCollateral: deal.StorageCollateral,
|
||||
ActivationEpoch: 0,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "setting deal in deal AMT")
|
||||
}
|
||||
out.DealIDs[i] = self.NextDealID
|
||||
|
||||
self.NextDealID++
|
||||
}
|
||||
|
||||
dealsCid, err := deals.Flush()
|
||||
if err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "saving deals AMT")
|
||||
}
|
||||
|
||||
self.Deals = dealsCid
|
||||
|
||||
nroot, err := vmctx.Storage().Put(&self)
|
||||
if err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "storing state failed")
|
||||
}
|
||||
|
||||
aerr = vmctx.Storage().Commit(old, nroot)
|
||||
if aerr != nil {
|
||||
return nil, aerr
|
||||
}
|
||||
|
||||
var outBuf bytes.Buffer
|
||||
if err := out.MarshalCBOR(&outBuf); err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "serialising output")
|
||||
}
|
||||
|
||||
return outBuf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (st *StorageMarketState) validateDeal(vmctx types.VMContext, deal StorageDealProposal, providerWorker address.Address) aerrors.ActorError {
|
||||
ctx, span := trace.StartSpan(vmctx.Context(), "validateDeal")
|
||||
defer span.End()
|
||||
|
||||
if vmctx.BlockHeight() > deal.ProposalExpiration {
|
||||
return aerrors.New(1, "deal proposal already expired")
|
||||
}
|
||||
|
||||
if vmctx.Message().From != providerWorker {
|
||||
return aerrors.New(2, "Deals must be submitted by the miner worker")
|
||||
}
|
||||
|
||||
if err := deal.Verify(); err != nil {
|
||||
return aerrors.Absorb(err, 3, "verifying proposer signature")
|
||||
}
|
||||
|
||||
// TODO: do some caching (changes gas so needs to be in spec too)
|
||||
b, bnd, aerr := GetMarketBalances(ctx, vmctx.Ipld(), st.Balances, deal.Client, providerWorker)
|
||||
if aerr != nil {
|
||||
return aerrors.Wrap(aerr, "getting client, and provider balances")
|
||||
}
|
||||
clientBalance := b[0]
|
||||
providerBalance := b[1]
|
||||
|
||||
totalPrice := deal.TotalStoragePrice()
|
||||
|
||||
if clientBalance.Available.LessThan(totalPrice) {
|
||||
return aerrors.Newf(5, "client doesn't have enough available funds to cover storage price; %d < %d", clientBalance.Available, totalPrice)
|
||||
}
|
||||
|
||||
clientBalance = lockFunds(clientBalance, totalPrice)
|
||||
|
||||
// TODO: REVIEW: Not clear who pays for this
|
||||
if providerBalance.Available.LessThan(deal.StorageCollateral) {
|
||||
return aerrors.Newf(6, "provider doesn't have enough available funds to cover StorageCollateral; %d < %d", providerBalance.Available, deal.StorageCollateral)
|
||||
}
|
||||
|
||||
providerBalance = lockFunds(providerBalance, deal.StorageCollateral)
|
||||
|
||||
// TODO: piece checks (e.g. size > sectorSize)?
|
||||
|
||||
bcid, aerr := setMarketBalances(vmctx, bnd, map[address.Address]StorageParticipantBalance{
|
||||
deal.Client: clientBalance,
|
||||
providerWorker: providerBalance,
|
||||
})
|
||||
if aerr != nil {
|
||||
return aerr
|
||||
}
|
||||
|
||||
st.Balances = bcid
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ActivateStorageDealsParams struct {
|
||||
Deals []uint64
|
||||
}
|
||||
|
||||
func (sma StorageMarketActor) ActivateStorageDeals(act *types.Actor, vmctx types.VMContext, params *ActivateStorageDealsParams) ([]byte, ActorError) {
|
||||
var self StorageMarketState
|
||||
old := vmctx.Storage().GetHead()
|
||||
if err := vmctx.Storage().Get(old, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deals, err := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.Deals)
|
||||
if err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "loading deals amt")
|
||||
}
|
||||
|
||||
for _, deal := range params.Deals {
|
||||
var dealInfo OnChainDeal
|
||||
if err := deals.Get(deal, &dealInfo); err != nil {
|
||||
if _, is := err.(*amt.ErrNotFound); is {
|
||||
return nil, aerrors.New(3, "deal not found")
|
||||
}
|
||||
return nil, aerrors.HandleExternalError(err, "getting deal info failed")
|
||||
}
|
||||
|
||||
if vmctx.Message().From != dealInfo.Provider {
|
||||
return nil, aerrors.New(1, "ActivateStorageDeals can only be called by the deal provider")
|
||||
}
|
||||
|
||||
if vmctx.BlockHeight() > dealInfo.ProposalExpiration {
|
||||
return nil, aerrors.New(2, "deal cannot be activated: proposal expired")
|
||||
}
|
||||
|
||||
if dealInfo.ActivationEpoch > 0 {
|
||||
// this probably can't happen in practice
|
||||
return nil, aerrors.New(3, "deal already active")
|
||||
}
|
||||
|
||||
dealInfo.ActivationEpoch = vmctx.BlockHeight()
|
||||
|
||||
if err := deals.Set(deal, &dealInfo); err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "setting deal info in AMT failed")
|
||||
}
|
||||
}
|
||||
|
||||
dealsCid, err := deals.Flush()
|
||||
if err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "saving deals AMT")
|
||||
}
|
||||
|
||||
self.Deals = dealsCid
|
||||
|
||||
nroot, err := vmctx.Storage().Put(&self)
|
||||
if err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "storing state failed")
|
||||
}
|
||||
|
||||
aerr := vmctx.Storage().Commit(old, nroot)
|
||||
if aerr != nil {
|
||||
return nil, aerr
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type ProcessStorageDealsPaymentParams struct {
|
||||
DealIDs []uint64
|
||||
}
|
||||
|
||||
func (sma StorageMarketActor) ProcessStorageDealsPayment(act *types.Actor, vmctx types.VMContext, params *ProcessStorageDealsPaymentParams) ([]byte, ActorError) {
|
||||
var self StorageMarketState
|
||||
old := vmctx.Storage().GetHead()
|
||||
if err := vmctx.Storage().Get(old, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deals, err := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.Deals)
|
||||
if err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "loading deals amt")
|
||||
}
|
||||
|
||||
// TODO: Would be nice if send could assert actor type
|
||||
workerBytes, aerr := vmctx.Send(vmctx.Message().From, MAMethods.GetWorkerAddr, types.NewInt(0), nil)
|
||||
if aerr != nil {
|
||||
return nil, aerr
|
||||
}
|
||||
providerWorker, err := address.NewFromBytes(workerBytes)
|
||||
if err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "parsing provider worker address bytes")
|
||||
}
|
||||
|
||||
for _, deal := range params.DealIDs {
|
||||
var dealInfo OnChainDeal
|
||||
if err := deals.Get(deal, &dealInfo); err != nil {
|
||||
if _, is := err.(*amt.ErrNotFound); is {
|
||||
return nil, aerrors.New(2, "deal not found")
|
||||
}
|
||||
return nil, aerrors.HandleExternalError(err, "getting deal info failed")
|
||||
}
|
||||
|
||||
if dealInfo.Provider != vmctx.Message().From {
|
||||
return nil, aerrors.New(3, "ProcessStorageDealsPayment can only be called by deal provider")
|
||||
}
|
||||
|
||||
if vmctx.BlockHeight() < dealInfo.ActivationEpoch {
|
||||
// TODO: This is probably fatal
|
||||
return nil, aerrors.New(4, "ActivationEpoch lower than block height")
|
||||
}
|
||||
|
||||
if vmctx.BlockHeight() > dealInfo.ActivationEpoch+dealInfo.Duration {
|
||||
// Deal expired, miner should drop it
|
||||
// TODO: process payment for the remainder of last proving period
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
toPay := types.BigMul(dealInfo.StoragePricePerEpoch, types.NewInt(build.SlashablePowerDelay))
|
||||
|
||||
b, bnd, aerr := GetMarketBalances(vmctx.Context(), vmctx.Ipld(), self.Balances, dealInfo.Client, providerWorker)
|
||||
if aerr != nil {
|
||||
return nil, aerr
|
||||
}
|
||||
clientBal := b[0]
|
||||
providerBal := b[1]
|
||||
|
||||
clientBal.Locked, providerBal.Available = transferFunds(clientBal.Locked, providerBal.Available, toPay)
|
||||
|
||||
// TODO: call set once
|
||||
bcid, aerr := setMarketBalances(vmctx, bnd, map[address.Address]StorageParticipantBalance{
|
||||
dealInfo.Client: clientBal,
|
||||
providerWorker: providerBal,
|
||||
})
|
||||
if aerr != nil {
|
||||
return nil, aerr
|
||||
}
|
||||
|
||||
self.Balances = bcid
|
||||
}
|
||||
|
||||
nroot, err := vmctx.Storage().Put(&self)
|
||||
if err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "storing state failed")
|
||||
}
|
||||
|
||||
aerr = vmctx.Storage().Commit(old, nroot)
|
||||
if aerr != nil {
|
||||
return nil, aerr
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func lockFunds(p StorageParticipantBalance, amt types.BigInt) StorageParticipantBalance {
|
||||
p.Available, p.Locked = transferFunds(p.Available, p.Locked, amt)
|
||||
return p
|
||||
}
|
||||
|
||||
func transferFunds(from, to, amt types.BigInt) (types.BigInt, types.BigInt) {
|
||||
// TODO: some asserts
|
||||
return types.BigSub(from, amt), types.BigAdd(to, amt)
|
||||
}
|
||||
|
||||
type ComputeDataCommitmentParams struct {
|
||||
DealIDs []uint64
|
||||
SectorSize uint64
|
||||
}
|
||||
|
||||
func (sma StorageMarketActor) ComputeDataCommitment(act *types.Actor, vmctx types.VMContext, params *ComputeDataCommitmentParams) ([]byte, ActorError) {
|
||||
var self StorageMarketState
|
||||
old := vmctx.Storage().GetHead()
|
||||
if err := vmctx.Storage().Get(old, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deals, err := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.Deals)
|
||||
if err != nil {
|
||||
return nil, aerrors.HandleExternalError(err, "loading deals amt")
|
||||
}
|
||||
|
||||
if len(params.DealIDs) == 0 {
|
||||
return nil, aerrors.New(3, "no deal IDs")
|
||||
}
|
||||
|
||||
var pieces []sectorbuilder.PublicPieceInfo
|
||||
for _, deal := range params.DealIDs {
|
||||
var dealInfo OnChainDeal
|
||||
if err := deals.Get(deal, &dealInfo); err != nil {
|
||||
if _, is := err.(*amt.ErrNotFound); is {
|
||||
return nil, aerrors.New(4, "deal not found")
|
||||
}
|
||||
return nil, aerrors.HandleExternalError(err, "getting deal info failed")
|
||||
}
|
||||
|
||||
if dealInfo.Provider != vmctx.Message().From {
|
||||
return nil, aerrors.New(5, "referenced deal was not from caller")
|
||||
}
|
||||
|
||||
var commP [32]byte
|
||||
copy(commP[:], dealInfo.PieceRef)
|
||||
|
||||
pieces = append(pieces, sectorbuilder.PublicPieceInfo{
|
||||
Size: dealInfo.PieceSize,
|
||||
CommP: commP,
|
||||
})
|
||||
}
|
||||
|
||||
commd, err := sectorbuilder.GenerateDataCommitment(params.SectorSize, pieces)
|
||||
if err != nil {
|
||||
return nil, aerrors.Absorb(err, 6, "failed to generate data commitment from pieces")
|
||||
}
|
||||
|
||||
return commd[:], nil
|
||||
}
|
||||
|
||||
/*
|
||||
func (sma StorageMarketActor) HandleCronAction(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
|
||||
|
||||
}
|
||||
|
||||
func (sma StorageMarketActor) SettleExpiredDeals(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
|
||||
|
||||
}
|
||||
|
||||
func (sma StorageMarketActor) SlashStorageDealCollateral(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
|
||||
|
||||
}
|
||||
|
||||
func (sma StorageMarketActor) GetLastExpirationFromDealIDs(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
|
||||
|
||||
}
|
||||
*/
|
||||
@@ -1,797 +0,0 @@
|
||||
package actors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/filecoin-project/go-amt-ipld"
|
||||
cid "github.com/ipfs/go-cid"
|
||||
hamt "github.com/ipfs/go-hamt-ipld"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
"go.opencensus.io/trace"
|
||||
xerrors "golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
type StoragePowerActor struct{}
|
||||
|
||||
type spaMethods struct {
|
||||
Constructor uint64
|
||||
CreateStorageMiner uint64
|
||||
ArbitrateConsensusFault uint64
|
||||
UpdateStorage uint64
|
||||
GetTotalStorage uint64
|
||||
PowerLookup uint64
|
||||
IsValidMiner uint64
|
||||
PledgeCollateralForSize uint64
|
||||
CheckProofSubmissions uint64
|
||||
}
|
||||
|
||||
var SPAMethods = spaMethods{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
|
||||
func (spa StoragePowerActor) Exports() []interface{} {
|
||||
return []interface{}{
|
||||
//1: spa.StoragePowerConstructor,
|
||||
2: spa.CreateStorageMiner,
|
||||
3: spa.ArbitrateConsensusFault,
|
||||
4: spa.UpdateStorage,
|
||||
5: spa.GetTotalStorage,
|
||||
6: spa.PowerLookup,
|
||||
7: spa.IsValidMiner,
|
||||
8: spa.PledgeCollateralForSize,
|
||||
9: spa.CheckProofSubmissions,
|
||||
}
|
||||
}
|
||||
|
||||
type StoragePowerState struct {
|
||||
Miners cid.Cid
|
||||
ProvingBuckets cid.Cid // amt[ProvingPeriodBucket]hamt[minerAddress]struct{}
|
||||
MinerCount uint64
|
||||
LastMinerCheck uint64
|
||||
|
||||
TotalStorage types.BigInt
|
||||
}
|
||||
|
||||
type CreateStorageMinerParams struct {
|
||||
Owner address.Address
|
||||
Worker address.Address
|
||||
SectorSize uint64
|
||||
PeerID peer.ID
|
||||
}
|
||||
|
||||
func (spa StoragePowerActor) CreateStorageMiner(act *types.Actor, vmctx types.VMContext, params *CreateStorageMinerParams) ([]byte, ActorError) {
|
||||
if !build.SupportedSectorSize(params.SectorSize) {
|
||||
return nil, aerrors.New(1, "Unsupported sector size")
|
||||
}
|
||||
|
||||
var self StoragePowerState
|
||||
old := vmctx.Storage().GetHead()
|
||||
if err := vmctx.Storage().Get(old, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqColl, err := pledgeCollateralForSize(vmctx, types.NewInt(0), self.TotalStorage, self.MinerCount+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if vmctx.Message().Value.LessThan(reqColl) {
|
||||
return nil, aerrors.Newf(1, "not enough funds passed to cover required miner collateral (needed %s, got %s)", reqColl, vmctx.Message().Value)
|
||||
}
|
||||
|
||||
encoded, err := CreateExecParams(StorageMinerCodeCid, &StorageMinerConstructorParams{
|
||||
Owner: params.Owner,
|
||||
Worker: params.Worker,
|
||||
SectorSize: params.SectorSize,
|
||||
PeerID: params.PeerID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret, err := vmctx.Send(InitAddress, IAMethods.Exec, vmctx.Message().Value, encoded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
naddr, nerr := address.NewFromBytes(ret)
|
||||
if nerr != nil {
|
||||
return nil, aerrors.Absorb(nerr, 2, "could not read address of new actor")
|
||||
}
|
||||
|
||||
ncid, err := MinerSetAdd(context.TODO(), vmctx, self.Miners, naddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
self.Miners = ncid
|
||||
self.MinerCount++
|
||||
|
||||
nroot, err := vmctx.Storage().Put(&self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := vmctx.Storage().Commit(old, nroot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return naddr.Bytes(), nil
|
||||
}
|
||||
|
||||
type ArbitrateConsensusFaultParams struct {
|
||||
Block1 *types.BlockHeader
|
||||
Block2 *types.BlockHeader
|
||||
}
|
||||
|
||||
func (spa StoragePowerActor) ArbitrateConsensusFault(act *types.Actor, vmctx types.VMContext, params *ArbitrateConsensusFaultParams) ([]byte, ActorError) {
|
||||
if params.Block1.Miner != params.Block2.Miner {
|
||||
return nil, aerrors.New(2, "blocks must be from the same miner")
|
||||
}
|
||||
|
||||
rval, err := vmctx.Send(params.Block1.Miner, MAMethods.GetWorkerAddr, types.NewInt(0), nil)
|
||||
if err != nil {
|
||||
return nil, aerrors.Wrap(err, "failed to get miner worker")
|
||||
}
|
||||
|
||||
worker, oerr := address.NewFromBytes(rval)
|
||||
if oerr != nil {
|
||||
// REVIEW: should this be fatal? i can't think of a real situation that would get us here
|
||||
return nil, aerrors.Absorb(oerr, 3, "response from 'GetWorkerAddr' was not a valid address")
|
||||
}
|
||||
|
||||
if err := params.Block1.CheckBlockSignature(vmctx.Context(), worker); err != nil {
|
||||
return nil, aerrors.Absorb(err, 4, "block1 did not have valid signature")
|
||||
}
|
||||
|
||||
if err := params.Block2.CheckBlockSignature(vmctx.Context(), worker); err != nil {
|
||||
return nil, aerrors.Absorb(err, 5, "block2 did not have valid signature")
|
||||
}
|
||||
|
||||
// see the "Consensus Faults" section of the faults spec (faults.md)
|
||||
// for details on these slashing conditions.
|
||||
if !shouldSlash(params.Block1, params.Block2) {
|
||||
return nil, aerrors.New(6, "blocks do not prove a slashable offense")
|
||||
}
|
||||
|
||||
var self StoragePowerState
|
||||
old := vmctx.Storage().GetHead()
|
||||
if err := vmctx.Storage().Get(old, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if types.BigCmp(self.TotalStorage, types.NewInt(0)) == 0 {
|
||||
return nil, aerrors.Fatal("invalid state, storage power actor has zero total storage")
|
||||
}
|
||||
|
||||
miner := params.Block1.Miner
|
||||
if has, err := MinerSetHas(vmctx, self.Miners, miner); err != nil {
|
||||
return nil, aerrors.Wrapf(err, "failed to check miner in set")
|
||||
} else if !has {
|
||||
return nil, aerrors.New(7, "either already slashed or not a miner")
|
||||
}
|
||||
|
||||
minerPower, err := powerLookup(context.TODO(), vmctx, &self, miner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slashedCollateral, err := pledgeCollateralForSize(vmctx, minerPower, self.TotalStorage, self.MinerCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enc, err := SerializeParams(&MinerSlashConsensusFault{
|
||||
Slasher: vmctx.Message().From,
|
||||
AtHeight: params.Block1.Height,
|
||||
SlashedCollateral: slashedCollateral,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = vmctx.Send(miner, MAMethods.SlashConsensusFault, types.NewInt(0), enc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remove the miner from the list of network miners
|
||||
ncid, err := MinerSetRemove(context.TODO(), vmctx, self.Miners, miner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
self.Miners = ncid
|
||||
self.MinerCount--
|
||||
|
||||
self.TotalStorage = types.BigSub(self.TotalStorage, minerPower)
|
||||
|
||||
nroot, err := vmctx.Storage().Put(&self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := vmctx.Storage().Commit(old, nroot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func cidArrContains(a []cid.Cid, b cid.Cid) bool {
|
||||
for _, c := range a {
|
||||
if b == c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func shouldSlash(block1, block2 *types.BlockHeader) bool {
|
||||
// First slashing condition, blocks have the same ticket round
|
||||
if block1.Height == block2.Height {
|
||||
return true
|
||||
}
|
||||
|
||||
/* Second slashing condition requires having access to the parent tipset blocks
|
||||
// This might not always be available, needs some thought on the best way to deal with this
|
||||
|
||||
|
||||
// Second slashing condition, miner ignored own block when mining
|
||||
// Case A: block2 could have been in block1's parent set but is not
|
||||
b1ParentHeight := block1.Height - len(block1.Tickets)
|
||||
|
||||
block1ParentTipSet := block1.Parents
|
||||
if !cidArrContains(block1.Parents, block2.Cid()) &&
|
||||
b1ParentHeight == block2.Height &&
|
||||
block1ParentTipSet.ParentCids == block2.ParentCids {
|
||||
return true
|
||||
}
|
||||
|
||||
// Case B: block1 could have been in block2's parent set but is not
|
||||
block2ParentTipSet := parentOf(block2)
|
||||
if !block2Parent.contains(block1) &&
|
||||
block2ParentTipSet.Height == block1.Height &&
|
||||
block2ParentTipSet.ParentCids == block1.ParentCids {
|
||||
return true
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type UpdateStorageParams struct {
|
||||
Delta types.BigInt
|
||||
NextSlashDeadline uint64
|
||||
PreviousSlashDeadline uint64
|
||||
}
|
||||
|
||||
func (spa StoragePowerActor) UpdateStorage(act *types.Actor, vmctx types.VMContext, params *UpdateStorageParams) ([]byte, ActorError) {
|
||||
var self StoragePowerState
|
||||
old := vmctx.Storage().GetHead()
|
||||
if err := vmctx.Storage().Get(old, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
has, err := MinerSetHas(vmctx, self.Miners, vmctx.Message().From)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, aerrors.New(1, "update storage must only be called by a miner actor")
|
||||
}
|
||||
|
||||
self.TotalStorage = types.BigAdd(self.TotalStorage, params.Delta)
|
||||
|
||||
previousBucket := params.PreviousSlashDeadline % build.SlashablePowerDelay
|
||||
nextBucket := params.NextSlashDeadline % build.SlashablePowerDelay
|
||||
|
||||
if previousBucket == nextBucket && params.PreviousSlashDeadline != 0 {
|
||||
nroot, err := vmctx.Storage().Put(&self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := vmctx.Storage().Commit(old, nroot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil // Nothing to do
|
||||
}
|
||||
|
||||
buckets, eerr := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.ProvingBuckets)
|
||||
if eerr != nil {
|
||||
return nil, aerrors.HandleExternalError(eerr, "loading proving buckets amt")
|
||||
}
|
||||
|
||||
if params.PreviousSlashDeadline != 0 { // delete from previous bucket
|
||||
err := deleteMinerFromBucket(vmctx, buckets, previousBucket)
|
||||
if err != nil {
|
||||
return nil, aerrors.Wrapf(err, "delete from bucket %d, next %d", previousBucket, nextBucket)
|
||||
}
|
||||
}
|
||||
|
||||
err = addMinerToBucket(vmctx, buckets, nextBucket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
self.ProvingBuckets, eerr = buckets.Flush()
|
||||
if eerr != nil {
|
||||
return nil, aerrors.HandleExternalError(eerr, "flushing proving buckets")
|
||||
}
|
||||
|
||||
nroot, err := vmctx.Storage().Put(&self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := vmctx.Storage().Commit(old, nroot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func deleteMinerFromBucket(vmctx types.VMContext, buckets *amt.Root, previousBucket uint64) aerrors.ActorError {
|
||||
var bucket cid.Cid
|
||||
err := buckets.Get(previousBucket, &bucket)
|
||||
switch err.(type) {
|
||||
case *amt.ErrNotFound:
|
||||
return aerrors.HandleExternalError(err, "proving bucket missing")
|
||||
case nil: // noop
|
||||
default:
|
||||
return aerrors.HandleExternalError(err, "getting proving bucket")
|
||||
}
|
||||
|
||||
bhamt, err := hamt.LoadNode(vmctx.Context(), vmctx.Ipld(), bucket)
|
||||
if err != nil {
|
||||
return aerrors.HandleExternalError(err, "failed to load proving bucket")
|
||||
}
|
||||
err = bhamt.Delete(vmctx.Context(), string(vmctx.Message().From.Bytes()))
|
||||
if err != nil {
|
||||
return aerrors.HandleExternalError(err, "deleting miner from proving bucket")
|
||||
}
|
||||
|
||||
err = bhamt.Flush(vmctx.Context())
|
||||
if err != nil {
|
||||
return aerrors.HandleExternalError(err, "flushing previous proving bucket")
|
||||
}
|
||||
|
||||
bucket, err = vmctx.Ipld().Put(vmctx.Context(), bhamt)
|
||||
if err != nil {
|
||||
return aerrors.HandleExternalError(err, "putting previous proving bucket hamt")
|
||||
}
|
||||
|
||||
err = buckets.Set(previousBucket, bucket)
|
||||
if err != nil {
|
||||
return aerrors.HandleExternalError(err, "setting previous proving bucket cid in amt")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addMinerToBucket(vmctx types.VMContext, buckets *amt.Root, nextBucket uint64) aerrors.ActorError {
|
||||
var bhamt *hamt.Node
|
||||
var bucket cid.Cid
|
||||
err := buckets.Get(nextBucket, &bucket)
|
||||
switch err.(type) {
|
||||
case *amt.ErrNotFound:
|
||||
bhamt = hamt.NewNode(vmctx.Ipld())
|
||||
case nil:
|
||||
bhamt, err = hamt.LoadNode(vmctx.Context(), vmctx.Ipld(), bucket)
|
||||
if err != nil {
|
||||
return aerrors.HandleExternalError(err, "failed to load proving bucket")
|
||||
}
|
||||
default:
|
||||
return aerrors.HandleExternalError(err, "getting proving bucket")
|
||||
}
|
||||
|
||||
err = bhamt.Set(vmctx.Context(), string(vmctx.Message().From.Bytes()), CborNull)
|
||||
if err != nil {
|
||||
return aerrors.HandleExternalError(err, "setting miner in proving bucket")
|
||||
}
|
||||
|
||||
err = bhamt.Flush(vmctx.Context())
|
||||
if err != nil {
|
||||
return aerrors.HandleExternalError(err, "flushing previous proving bucket")
|
||||
}
|
||||
|
||||
bucket, err = vmctx.Ipld().Put(vmctx.Context(), bhamt)
|
||||
if err != nil {
|
||||
return aerrors.HandleExternalError(err, "putting previous proving bucket hamt")
|
||||
}
|
||||
|
||||
err = buckets.Set(nextBucket, bucket)
|
||||
if err != nil {
|
||||
return aerrors.HandleExternalError(err, "setting previous proving bucket cid in amt")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (spa StoragePowerActor) GetTotalStorage(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, ActorError) {
|
||||
var self StoragePowerState
|
||||
if err := vmctx.Storage().Get(vmctx.Storage().GetHead(), &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return self.TotalStorage.Bytes(), nil
|
||||
}
|
||||
|
||||
type PowerLookupParams struct {
|
||||
Miner address.Address
|
||||
}
|
||||
|
||||
func (spa StoragePowerActor) PowerLookup(act *types.Actor, vmctx types.VMContext, params *PowerLookupParams) ([]byte, ActorError) {
|
||||
var self StoragePowerState
|
||||
if err := vmctx.Storage().Get(vmctx.Storage().GetHead(), &self); err != nil {
|
||||
return nil, aerrors.Wrap(err, "getting head")
|
||||
}
|
||||
|
||||
pow, err := powerLookup(context.TODO(), vmctx, &self, params.Miner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pow.Bytes(), nil
|
||||
}
|
||||
|
||||
func powerLookup(ctx context.Context, vmctx types.VMContext, self *StoragePowerState, miner address.Address) (types.BigInt, ActorError) {
|
||||
has, err := MinerSetHas(vmctx, self.Miners, miner)
|
||||
if err != nil {
|
||||
return types.EmptyInt, err
|
||||
}
|
||||
|
||||
if !has {
|
||||
return types.EmptyInt, aerrors.New(1, "miner not registered with storage power actor")
|
||||
}
|
||||
|
||||
// TODO: Use local amt
|
||||
ret, err := vmctx.Send(miner, MAMethods.GetPower, types.NewInt(0), nil)
|
||||
if err != nil {
|
||||
return types.EmptyInt, aerrors.Wrap(err, "invoke Miner.GetPower")
|
||||
}
|
||||
|
||||
return types.BigFromBytes(ret), nil
|
||||
}
|
||||
|
||||
type IsValidMinerParam struct {
|
||||
Addr address.Address
|
||||
}
|
||||
|
||||
func (spa StoragePowerActor) IsValidMiner(act *types.Actor, vmctx types.VMContext, param *IsValidMinerParam) ([]byte, ActorError) {
|
||||
var self StoragePowerState
|
||||
if err := vmctx.Storage().Get(vmctx.Storage().GetHead(), &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
has, err := MinerSetHas(vmctx, self.Miners, param.Addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !has {
|
||||
log.Warnf("Miner INVALID: not in set: %s", param.Addr)
|
||||
|
||||
return cbg.CborBoolFalse, nil
|
||||
}
|
||||
|
||||
ret, err := vmctx.Send(param.Addr, MAMethods.IsSlashed, types.NewInt(0), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slashed := bytes.Equal(ret, cbg.CborBoolTrue)
|
||||
|
||||
if slashed {
|
||||
log.Warnf("Miner INVALID: /SLASHED/ : %s", param.Addr)
|
||||
}
|
||||
|
||||
return cbg.EncodeBool(!slashed), nil
|
||||
}
|
||||
|
||||
type PledgeCollateralParams struct {
|
||||
Size types.BigInt
|
||||
}
|
||||
|
||||
func (spa StoragePowerActor) PledgeCollateralForSize(act *types.Actor, vmctx types.VMContext, param *PledgeCollateralParams) ([]byte, ActorError) {
|
||||
var self StoragePowerState
|
||||
if err := vmctx.Storage().Get(vmctx.Storage().GetHead(), &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalCollateral, err := pledgeCollateralForSize(vmctx, param.Size, self.TotalStorage, self.MinerCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return totalCollateral.Bytes(), nil
|
||||
}
|
||||
|
||||
func pledgeCollateralForSize(vmctx types.VMContext, size, totalStorage types.BigInt, minerCount uint64) (types.BigInt, aerrors.ActorError) {
|
||||
netBalance, err := vmctx.GetBalance(NetworkAddress)
|
||||
if err != nil {
|
||||
return types.EmptyInt, err
|
||||
}
|
||||
|
||||
// TODO: the spec says to also grab 'total vested filecoin' and include it as available
|
||||
// If we don't factor that in, we effectively assume all of the locked up filecoin is 'available'
|
||||
// the blocker on that right now is that its hard to tell how much filecoin is unlocked
|
||||
|
||||
availableFilecoin := types.BigSub(
|
||||
types.BigMul(types.NewInt(build.TotalFilecoin), types.NewInt(build.FilecoinPrecision)),
|
||||
netBalance,
|
||||
)
|
||||
|
||||
totalPowerCollateral := types.BigDiv(
|
||||
types.BigMul(
|
||||
availableFilecoin,
|
||||
types.NewInt(build.PowerCollateralProportion),
|
||||
),
|
||||
types.NewInt(build.CollateralPrecision),
|
||||
)
|
||||
|
||||
totalPerCapitaCollateral := types.BigDiv(
|
||||
types.BigMul(
|
||||
availableFilecoin,
|
||||
types.NewInt(build.PerCapitaCollateralProportion),
|
||||
),
|
||||
types.NewInt(build.CollateralPrecision),
|
||||
)
|
||||
|
||||
// REVIEW: for bootstrapping purposes, we skip the power portion of the
|
||||
// collateral if there is no collateral in the network yet
|
||||
powerCollateral := types.NewInt(0)
|
||||
if types.BigCmp(totalStorage, types.NewInt(0)) != 0 {
|
||||
powerCollateral = types.BigDiv(
|
||||
types.BigMul(
|
||||
totalPowerCollateral,
|
||||
size,
|
||||
),
|
||||
totalStorage,
|
||||
)
|
||||
}
|
||||
|
||||
perCapCollateral := types.BigDiv(
|
||||
totalPerCapitaCollateral,
|
||||
types.NewInt(minerCount),
|
||||
)
|
||||
|
||||
return types.BigAdd(powerCollateral, perCapCollateral), nil
|
||||
}
|
||||
|
||||
func (spa StoragePowerActor) CheckProofSubmissions(act *types.Actor, vmctx types.VMContext, param *struct{}) ([]byte, ActorError) {
|
||||
if vmctx.Message().From != CronAddress {
|
||||
return nil, aerrors.New(1, "CheckProofSubmissions is only callable from the cron actor")
|
||||
}
|
||||
|
||||
var self StoragePowerState
|
||||
old := vmctx.Storage().GetHead()
|
||||
if err := vmctx.Storage().Get(old, &self); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := self.LastMinerCheck; i < vmctx.BlockHeight(); i++ {
|
||||
height := i + 1
|
||||
|
||||
err := checkProofSubmissionsAtH(vmctx, &self, height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
self.LastMinerCheck = vmctx.BlockHeight()
|
||||
|
||||
nroot, aerr := vmctx.Storage().Put(&self)
|
||||
if aerr != nil {
|
||||
return nil, aerr
|
||||
}
|
||||
|
||||
if err := vmctx.Storage().Commit(old, nroot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func checkProofSubmissionsAtH(vmctx types.VMContext, self *StoragePowerState, height uint64) aerrors.ActorError {
|
||||
bucketID := height % build.SlashablePowerDelay
|
||||
|
||||
buckets, eerr := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.ProvingBuckets)
|
||||
if eerr != nil {
|
||||
return aerrors.HandleExternalError(eerr, "loading proving buckets amt")
|
||||
}
|
||||
|
||||
var bucket cid.Cid
|
||||
err := buckets.Get(bucketID, &bucket)
|
||||
switch err.(type) {
|
||||
case *amt.ErrNotFound:
|
||||
return nil // nothing to do
|
||||
case nil:
|
||||
default:
|
||||
return aerrors.HandleExternalError(err, "getting proving bucket")
|
||||
}
|
||||
|
||||
bhamt, err := hamt.LoadNode(vmctx.Context(), vmctx.Ipld(), bucket)
|
||||
if err != nil {
|
||||
return aerrors.HandleExternalError(err, "failed to load proving bucket")
|
||||
}
|
||||
|
||||
err = bhamt.ForEach(vmctx.Context(), func(k string, val interface{}) error {
|
||||
_, span := trace.StartSpan(vmctx.Context(), "StoragePowerActor.CheckProofSubmissions.loop")
|
||||
defer span.End()
|
||||
|
||||
maddr, err := address.NewFromBytes([]byte(k))
|
||||
if err != nil {
|
||||
return aerrors.Escalate(err, "parsing miner address")
|
||||
}
|
||||
|
||||
span.AddAttributes(trace.StringAttribute("miner", maddr.String()))
|
||||
|
||||
params, err := SerializeParams(&CheckMinerParams{NetworkPower: self.TotalStorage})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret, err := vmctx.Send(maddr, MAMethods.CheckMiner, types.NewInt(0), params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(ret) == 0 {
|
||||
return nil // miner is fine
|
||||
}
|
||||
|
||||
var power types.BigInt
|
||||
if err := power.UnmarshalCBOR(bytes.NewReader(ret)); err != nil {
|
||||
return xerrors.Errorf("unmarshaling CheckMiner response (%x): %w", ret, err)
|
||||
}
|
||||
|
||||
if power.GreaterThan(types.NewInt(0)) {
|
||||
log.Warnf("slashing miner %s for missed PoSt (%s B, H: %d, Bucket: %d)", maddr, power, height, bucketID)
|
||||
|
||||
self.TotalStorage = types.BigSub(self.TotalStorage, power)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return aerrors.HandleExternalError(err, "iterating miners in proving bucket")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func MinerSetHas(vmctx types.VMContext, rcid cid.Cid, maddr address.Address) (bool, aerrors.ActorError) {
|
||||
nd, err := hamt.LoadNode(vmctx.Context(), vmctx.Ipld(), rcid)
|
||||
if err != nil {
|
||||
return false, aerrors.HandleExternalError(err, "failed to load miner set")
|
||||
}
|
||||
|
||||
err = nd.Find(vmctx.Context(), string(maddr.Bytes()), nil)
|
||||
switch err {
|
||||
case hamt.ErrNotFound:
|
||||
return false, nil
|
||||
case nil:
|
||||
return true, nil
|
||||
default:
|
||||
return false, aerrors.HandleExternalError(err, "failed to do set lookup")
|
||||
}
|
||||
}
|
||||
|
||||
func MinerSetList(ctx context.Context, cst *hamt.CborIpldStore, rcid cid.Cid) ([]address.Address, error) {
|
||||
nd, err := hamt.LoadNode(ctx, cst, rcid)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to load miner set: %w", err)
|
||||
}
|
||||
|
||||
var out []address.Address
|
||||
err = nd.ForEach(ctx, func(k string, val interface{}) error {
|
||||
addr, err := address.NewFromBytes([]byte(k))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out = append(out, addr)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func MinerSetAdd(ctx context.Context, vmctx types.VMContext, rcid cid.Cid, maddr address.Address) (cid.Cid, aerrors.ActorError) {
|
||||
nd, err := hamt.LoadNode(ctx, vmctx.Ipld(), rcid)
|
||||
if err != nil {
|
||||
return cid.Undef, aerrors.HandleExternalError(err, "failed to load miner set")
|
||||
}
|
||||
|
||||
mkey := string(maddr.Bytes())
|
||||
err = nd.Find(ctx, mkey, nil)
|
||||
if err == nil {
|
||||
return cid.Undef, aerrors.New(20, "miner already in set")
|
||||
}
|
||||
|
||||
if !xerrors.Is(err, hamt.ErrNotFound) {
|
||||
return cid.Undef, aerrors.HandleExternalError(err, "failed to do miner set check")
|
||||
}
|
||||
|
||||
if err := nd.Set(ctx, mkey, uint64(1)); err != nil {
|
||||
return cid.Undef, aerrors.HandleExternalError(err, "adding miner address to set failed")
|
||||
}
|
||||
|
||||
if err := nd.Flush(ctx); err != nil {
|
||||
return cid.Undef, aerrors.HandleExternalError(err, "failed to flush miner set")
|
||||
}
|
||||
|
||||
c, err := vmctx.Ipld().Put(ctx, nd)
|
||||
if err != nil {
|
||||
return cid.Undef, aerrors.HandleExternalError(err, "failed to persist miner set to storage")
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func MinerSetRemove(ctx context.Context, vmctx types.VMContext, rcid cid.Cid, maddr address.Address) (cid.Cid, aerrors.ActorError) {
|
||||
nd, err := hamt.LoadNode(ctx, vmctx.Ipld(), rcid)
|
||||
if err != nil {
|
||||
return cid.Undef, aerrors.HandleExternalError(err, "failed to load miner set")
|
||||
}
|
||||
|
||||
mkey := string(maddr.Bytes())
|
||||
switch nd.Delete(ctx, mkey) {
|
||||
default:
|
||||
return cid.Undef, aerrors.HandleExternalError(err, "failed to delete miner from set")
|
||||
case hamt.ErrNotFound:
|
||||
return cid.Undef, aerrors.New(1, "miner not found in set on delete")
|
||||
case nil:
|
||||
}
|
||||
|
||||
if err := nd.Flush(ctx); err != nil {
|
||||
return cid.Undef, aerrors.HandleExternalError(err, "failed to flush miner set")
|
||||
}
|
||||
|
||||
c, err := vmctx.Ipld().Put(ctx, nd)
|
||||
if err != nil {
|
||||
return cid.Undef, aerrors.HandleExternalError(err, "failed to persist miner set to storage")
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type cbgNull struct{}
|
||||
|
||||
var CborNull = &cbgNull{}
|
||||
|
||||
func (cbgNull) MarshalCBOR(w io.Writer) error {
|
||||
n, err := w.Write(cbg.CborNull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != 1 {
|
||||
return xerrors.New("expected to write 1 byte")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cbgNull) UnmarshalCBOR(r io.Reader) error {
|
||||
b := [1]byte{}
|
||||
n, err := r.Read(b[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != 1 {
|
||||
return xerrors.New("expected 1 byte")
|
||||
}
|
||||
if !bytes.Equal(b[:], cbg.CborNull) {
|
||||
return xerrors.New("expected cbor null")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
package actors_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
|
||||
. "github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/vm"
|
||||
"github.com/filecoin-project/lotus/chain/wallet"
|
||||
|
||||
cid "github.com/ipfs/go-cid"
|
||||
hamt "github.com/ipfs/go-hamt-ipld"
|
||||
bstore "github.com/ipfs/go-ipfs-blockstore"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStorageMarketCreateAndSlashMiner(t *testing.T) {
|
||||
var ownerAddr, workerAddr address.Address
|
||||
|
||||
opts := []HarnessOpt{
|
||||
HarnessAddr(&ownerAddr, 1000000),
|
||||
HarnessAddr(&workerAddr, 100000),
|
||||
}
|
||||
|
||||
h := NewHarness(t, opts...)
|
||||
|
||||
var minerAddr address.Address
|
||||
{
|
||||
// cheating the bootstrapping problem
|
||||
cheatStorageMarketTotal(t, h.vm, h.cs.Blockstore())
|
||||
|
||||
ret, _ := h.InvokeWithValue(t, ownerAddr, StoragePowerAddress, SPAMethods.CreateStorageMiner,
|
||||
types.NewInt(500000),
|
||||
&CreateStorageMinerParams{
|
||||
Owner: ownerAddr,
|
||||
Worker: workerAddr,
|
||||
SectorSize: build.SectorSizes[0],
|
||||
PeerID: "fakepeerid",
|
||||
})
|
||||
ApplyOK(t, ret)
|
||||
var err error
|
||||
minerAddr, err = address.NewFromBytes(ret.Return)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
{
|
||||
ret, _ := h.Invoke(t, ownerAddr, StoragePowerAddress, SPAMethods.IsValidMiner,
|
||||
&IsValidMinerParam{Addr: minerAddr})
|
||||
ApplyOK(t, ret)
|
||||
|
||||
var output bool
|
||||
err := cbor.DecodeInto(ret.Return, &output)
|
||||
if err != nil {
|
||||
t.Fatalf("error decoding: %+v", err)
|
||||
}
|
||||
|
||||
if !output {
|
||||
t.Fatalf("%s is miner but IsValidMiner call returned false", minerAddr)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ret, _ := h.Invoke(t, ownerAddr, StoragePowerAddress, SPAMethods.PowerLookup,
|
||||
&PowerLookupParams{Miner: minerAddr})
|
||||
ApplyOK(t, ret)
|
||||
power := types.BigFromBytes(ret.Return)
|
||||
|
||||
if types.BigCmp(power, types.NewInt(0)) != 0 {
|
||||
t.Fatalf("power should be zero, is: %s", power)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ret, _ := h.Invoke(t, ownerAddr, minerAddr, MAMethods.GetOwner, nil)
|
||||
ApplyOK(t, ret)
|
||||
oA, err := address.NewFromBytes(ret.Return)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ownerAddr, oA, "return from GetOwner should be equal to the owner")
|
||||
}
|
||||
|
||||
{
|
||||
b1 := fakeBlock(t, minerAddr, 100)
|
||||
b2 := fakeBlock(t, minerAddr, 101)
|
||||
|
||||
signBlock(t, h.w, workerAddr, b1)
|
||||
signBlock(t, h.w, workerAddr, b2)
|
||||
|
||||
ret, _ := h.Invoke(t, ownerAddr, StoragePowerAddress, SPAMethods.ArbitrateConsensusFault,
|
||||
&ArbitrateConsensusFaultParams{
|
||||
Block1: b1,
|
||||
Block2: b2,
|
||||
})
|
||||
ApplyOK(t, ret)
|
||||
}
|
||||
|
||||
{
|
||||
ret, _ := h.Invoke(t, ownerAddr, StoragePowerAddress, SPAMethods.PowerLookup,
|
||||
&PowerLookupParams{Miner: minerAddr})
|
||||
assert.Equal(t, ret.ExitCode, byte(1))
|
||||
}
|
||||
|
||||
{
|
||||
ret, _ := h.Invoke(t, ownerAddr, StoragePowerAddress, SPAMethods.IsValidMiner, &IsValidMinerParam{minerAddr})
|
||||
ApplyOK(t, ret)
|
||||
assert.Equal(t, ret.Return, cbg.CborBoolFalse)
|
||||
}
|
||||
}
|
||||
|
||||
func cheatStorageMarketTotal(t *testing.T, vm *vm.VM, bs bstore.Blockstore) {
|
||||
t.Helper()
|
||||
|
||||
sma, err := vm.StateTree().GetActor(StoragePowerAddress)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cst := hamt.CSTFromBstore(bs)
|
||||
|
||||
var smastate StoragePowerState
|
||||
if err := cst.Get(context.TODO(), sma.Head, &smastate); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
smastate.TotalStorage = types.NewInt(10000)
|
||||
|
||||
c, err := cst.Put(context.TODO(), &smastate)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sma.Head = c
|
||||
|
||||
if err := vm.StateTree().SetActor(StoragePowerAddress, sma); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func fakeBlock(t *testing.T, minerAddr address.Address, ts uint64) *types.BlockHeader {
|
||||
c := fakeCid(t, 1)
|
||||
return &types.BlockHeader{Height: 5, Miner: minerAddr, Timestamp: ts, ParentStateRoot: c, Messages: c, ParentMessageReceipts: c, BLSAggregate: types.Signature{Type: types.KTBLS}}
|
||||
}
|
||||
|
||||
func fakeCid(t *testing.T, s int) cid.Cid {
|
||||
t.Helper()
|
||||
c, err := cid.NewPrefixV1(cid.Raw, mh.IDENTITY).Sum([]byte(fmt.Sprintf("%d", s)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func signBlock(t *testing.T, w *wallet.Wallet, worker address.Address, blk *types.BlockHeader) {
|
||||
t.Helper()
|
||||
sb, err := blk.SigningBytes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sig, err := w.Sign(context.TODO(), worker, sb)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
blk.BlockSig = sig
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package actors
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
)
|
||||
|
||||
var AccountCodeCid cid.Cid
|
||||
var CronCodeCid cid.Cid
|
||||
var StoragePowerCodeCid cid.Cid
|
||||
var StorageMarketCodeCid cid.Cid
|
||||
var StorageMinerCodeCid cid.Cid
|
||||
var MultisigCodeCid cid.Cid
|
||||
var InitCodeCid cid.Cid
|
||||
var PaymentChannelCodeCid cid.Cid
|
||||
|
||||
var InitAddress = mustIDAddress(0)
|
||||
var NetworkAddress = mustIDAddress(1)
|
||||
var StoragePowerAddress = mustIDAddress(2)
|
||||
var StorageMarketAddress = mustIDAddress(3) // TODO: missing from spec
|
||||
var CronAddress = mustIDAddress(4)
|
||||
var BurntFundsAddress = mustIDAddress(99)
|
||||
|
||||
func mustIDAddress(i uint64) address.Address {
|
||||
a, err := address.NewIDAddress(i)
|
||||
if err != nil {
|
||||
panic(err) // ok
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func init() {
|
||||
pref := cid.NewPrefixV1(cid.Raw, mh.IDENTITY)
|
||||
mustSum := func(s string) cid.Cid {
|
||||
c, err := pref.Sum([]byte(s))
|
||||
if err != nil {
|
||||
panic(err) // ok
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
AccountCodeCid = mustSum("fil/1/account") // TODO: spec
|
||||
CronCodeCid = mustSum("fil/1/cron")
|
||||
StoragePowerCodeCid = mustSum("fil/1/power")
|
||||
StorageMarketCodeCid = mustSum("fil/1/market")
|
||||
StorageMinerCodeCid = mustSum("fil/1/miner")
|
||||
MultisigCodeCid = mustSum("fil/1/multisig")
|
||||
InitCodeCid = mustSum("fil/1/init")
|
||||
PaymentChannelCodeCid = mustSum("fil/1/paych")
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
package actors_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
|
||||
. "github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/gen"
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/vm"
|
||||
dstore "github.com/ipfs/go-datastore"
|
||||
bstore "github.com/ipfs/go-ipfs-blockstore"
|
||||
)
|
||||
|
||||
func blsaddr(n uint64) address.Address {
|
||||
buf := make([]byte, 48)
|
||||
binary.PutUvarint(buf, n)
|
||||
|
||||
addr, err := address.NewBLSAddress(buf)
|
||||
if err != nil {
|
||||
panic(err) // ok
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
func setupVMTestEnv(t *testing.T) (*vm.VM, []address.Address, bstore.Blockstore) {
|
||||
bs := bstore.NewBlockstore(dstore.NewMapDatastore())
|
||||
|
||||
from := blsaddr(0)
|
||||
maddr := blsaddr(1)
|
||||
|
||||
actors := map[address.Address]types.BigInt{
|
||||
from: types.NewInt(1000000),
|
||||
maddr: types.NewInt(0),
|
||||
}
|
||||
st, err := gen.MakeInitialStateTree(bs, actors)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stateroot, err := st.Flush()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cs := store.NewChainStore(bs, nil)
|
||||
|
||||
// TODO: should probabaly mock out the randomness bit, nil works for now
|
||||
vm, err := vm.NewVM(stateroot, 1, nil, maddr, cs.Blockstore())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return vm, []address.Address{from, maddr}, bs
|
||||
}
|
||||
|
||||
func TestVMInvokeMethod(t *testing.T) {
|
||||
vm, addrs, _ := setupVMTestEnv(t)
|
||||
from := addrs[0]
|
||||
|
||||
var err error
|
||||
cenc, err := SerializeParams(&StorageMinerConstructorParams{Owner: from, Worker: from})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
execparams := &ExecParams{
|
||||
Code: StorageMinerCodeCid,
|
||||
Params: cenc,
|
||||
}
|
||||
enc, err := SerializeParams(execparams)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
msg := &types.Message{
|
||||
To: InitAddress,
|
||||
From: from,
|
||||
Method: IAMethods.Exec,
|
||||
Params: enc,
|
||||
GasPrice: types.NewInt(1),
|
||||
GasLimit: types.NewInt(10000),
|
||||
Value: types.NewInt(0),
|
||||
}
|
||||
|
||||
ret, err := vm.ApplyMessage(context.TODO(), msg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ret.ExitCode != 0 {
|
||||
t.Fatal("invocation failed")
|
||||
}
|
||||
|
||||
outaddr, err := address.NewFromBytes(ret.Return)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if outaddr.String() != "t0102" {
|
||||
t.Fatal("hold up")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorageMarketActorCreateMiner(t *testing.T) {
|
||||
vm, addrs, bs := setupVMTestEnv(t)
|
||||
from := addrs[0]
|
||||
maddr := addrs[1]
|
||||
|
||||
cheatStorageMarketTotal(t, vm, bs)
|
||||
|
||||
params := &StorageMinerConstructorParams{
|
||||
Owner: maddr,
|
||||
Worker: maddr,
|
||||
SectorSize: build.SectorSizes[0],
|
||||
PeerID: "fakepeerid",
|
||||
}
|
||||
var err error
|
||||
enc, err := SerializeParams(params)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
msg := &types.Message{
|
||||
To: StoragePowerAddress,
|
||||
From: from,
|
||||
Method: SPAMethods.CreateStorageMiner,
|
||||
Params: enc,
|
||||
GasPrice: types.NewInt(1),
|
||||
GasLimit: types.NewInt(10000),
|
||||
Value: types.NewInt(50000),
|
||||
}
|
||||
|
||||
ret, err := vm.ApplyMessage(context.TODO(), msg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ret.ExitCode != 0 {
|
||||
fmt.Println(ret.ActorErr)
|
||||
t.Fatal("invocation failed: ", ret.ExitCode)
|
||||
}
|
||||
|
||||
outaddr, err := address.NewFromBytes(ret.Return)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if outaddr.String() != "t0102" {
|
||||
t.Fatal("hold up")
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package actors
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
)
|
||||
|
||||
func NewIDAddress(id uint64) (address.Address, ActorError) {
|
||||
a, err := address.NewIDAddress(id)
|
||||
if err != nil {
|
||||
return address.Undef, aerrors.Escalate(err, "could not create ID Address")
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
@@ -3,13 +3,14 @@ package aerrors
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func IsFatal(err ActorError) bool {
|
||||
return err != nil && err.IsFatal()
|
||||
}
|
||||
func RetCode(err ActorError) uint8 {
|
||||
func RetCode(err ActorError) exitcode.ExitCode {
|
||||
if err == nil {
|
||||
return 0
|
||||
}
|
||||
@@ -25,12 +26,12 @@ type internalActorError interface {
|
||||
type ActorError interface {
|
||||
error
|
||||
IsFatal() bool
|
||||
RetCode() uint8
|
||||
RetCode() exitcode.ExitCode
|
||||
}
|
||||
|
||||
type actorError struct {
|
||||
fatal bool
|
||||
retCode uint8
|
||||
retCode exitcode.ExitCode
|
||||
|
||||
msg string
|
||||
frame xerrors.Frame
|
||||
@@ -41,7 +42,7 @@ func (e *actorError) IsFatal() bool {
|
||||
return e.fatal
|
||||
}
|
||||
|
||||
func (e *actorError) RetCode() uint8 {
|
||||
func (e *actorError) RetCode() exitcode.ExitCode {
|
||||
return e.retCode
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
. "github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/xerrors"
|
||||
@@ -31,5 +32,5 @@ func TestAbsorbeError(t *testing.T) {
|
||||
aw3 := Wrap(aw2, "creating miner in storage market")
|
||||
t.Logf("Verbose error: %+v", aw3)
|
||||
t.Logf("Normal error: %v", aw3)
|
||||
assert.Equal(t, uint8(35), RetCode(aw3))
|
||||
assert.Equal(t, exitcode.ExitCode(35), RetCode(aw3))
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
hamt "github.com/ipfs/go-hamt-ipld"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// New creates a new non-fatal error
|
||||
func New(retCode uint8, message string) ActorError {
|
||||
func New(retCode exitcode.ExitCode, message string) ActorError {
|
||||
if retCode == 0 {
|
||||
return &actorError{
|
||||
fatal: true,
|
||||
@@ -29,7 +30,7 @@ func New(retCode uint8, message string) ActorError {
|
||||
}
|
||||
|
||||
// Newf creates a new non-fatal error
|
||||
func Newf(retCode uint8, format string, args ...interface{}) ActorError {
|
||||
func Newf(retCode exitcode.ExitCode, format string, args ...interface{}) ActorError {
|
||||
if retCode == 0 {
|
||||
return &actorError{
|
||||
fatal: true,
|
||||
@@ -48,6 +49,27 @@ func Newf(retCode uint8, format string, args ...interface{}) ActorError {
|
||||
}
|
||||
}
|
||||
|
||||
// todo: bit hacky
|
||||
|
||||
func NewfSkip(skip int, retCode exitcode.ExitCode, format string, args ...interface{}) ActorError {
|
||||
if retCode == 0 {
|
||||
return &actorError{
|
||||
fatal: true,
|
||||
retCode: 0,
|
||||
|
||||
msg: "tried creating an error and setting RetCode to 0",
|
||||
frame: xerrors.Caller(skip),
|
||||
err: fmt.Errorf(format, args...),
|
||||
}
|
||||
}
|
||||
return &actorError{
|
||||
retCode: retCode,
|
||||
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
frame: xerrors.Caller(skip),
|
||||
}
|
||||
}
|
||||
|
||||
func Fatal(message string, args ...interface{}) ActorError {
|
||||
return &actorError{
|
||||
fatal: true,
|
||||
@@ -95,7 +117,7 @@ func Wrapf(err ActorError, format string, args ...interface{}) ActorError {
|
||||
}
|
||||
|
||||
// Absorb takes and error and makes in not fatal ActorError
|
||||
func Absorb(err error, retCode uint8, msg string) ActorError {
|
||||
func Absorb(err error, retCode exitcode.ExitCode, msg string) ActorError {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -160,7 +182,7 @@ func HandleExternalError(err error, msg string) ActorError {
|
||||
}
|
||||
}
|
||||
|
||||
if xerrors.Is(err, &hamt.SerializationError{}) {
|
||||
if xerrors.Is(err, &cbor.SerializationError{}) {
|
||||
return &actorError{
|
||||
fatal: false,
|
||||
retCode: 253,
|
||||
@@ -171,7 +193,8 @@ func HandleExternalError(err error, msg string) ActorError {
|
||||
}
|
||||
|
||||
return &actorError{
|
||||
fatal: true,
|
||||
fatal: false,
|
||||
retCode: 219,
|
||||
|
||||
msg: msg,
|
||||
frame: xerrors.Caller(1),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
||||
package actors
|
||||
|
||||
import "github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
|
||||
type ActorError = aerrors.ActorError
|
||||
@@ -1,46 +0,0 @@
|
||||
package actors
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
type update struct {
|
||||
start uint64
|
||||
method interface{}
|
||||
}
|
||||
|
||||
func withUpdates(updates ...update) interface{} {
|
||||
sort.Slice(updates, func(i, j int) bool { // so we iterate from newest below
|
||||
return updates[i].start > updates[j].start
|
||||
})
|
||||
|
||||
// <script type="application/javascript">
|
||||
|
||||
typ := reflect.TypeOf(updates[0].method)
|
||||
|
||||
out := reflect.MakeFunc(typ, func(args []reflect.Value) (results []reflect.Value) {
|
||||
vmctx := args[1].Interface().(types.VMContext)
|
||||
|
||||
for _, u := range updates {
|
||||
if vmctx.BlockHeight() >= u.start {
|
||||
return reflect.ValueOf(u.method).Call(args)
|
||||
}
|
||||
}
|
||||
|
||||
return reflect.ValueOf(notFound(vmctx)).Call([]reflect.Value{})
|
||||
})
|
||||
|
||||
return out.Interface()
|
||||
|
||||
// </script>
|
||||
}
|
||||
|
||||
func notFound(vmctx types.VMContext) func() ([]byte, ActorError) {
|
||||
return func() ([]byte, ActorError) {
|
||||
return nil, aerrors.Fatal("no code for method %d at height %d", vmctx.Message().Method, vmctx.BlockHeight())
|
||||
}
|
||||
}
|
||||
@@ -1,337 +0,0 @@
|
||||
package actors_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
dstore "github.com/ipfs/go-datastore"
|
||||
hamt "github.com/ipfs/go-hamt-ipld"
|
||||
blockstore "github.com/ipfs/go-ipfs-blockstore"
|
||||
bstore "github.com/ipfs/go-ipfs-blockstore"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/gen"
|
||||
"github.com/filecoin-project/lotus/chain/state"
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/vm"
|
||||
"github.com/filecoin-project/lotus/chain/wallet"
|
||||
)
|
||||
|
||||
const testGasLimit = 10000
|
||||
|
||||
type HarnessInit struct {
|
||||
NAddrs uint64
|
||||
Addrs map[address.Address]types.BigInt
|
||||
Miner address.Address
|
||||
}
|
||||
|
||||
type HarnessStage int
|
||||
|
||||
const (
|
||||
HarnessPreInit HarnessStage = iota
|
||||
HarnessPostInit
|
||||
)
|
||||
|
||||
type HarnessOpt func(testing.TB, *Harness) error
|
||||
|
||||
type Harness struct {
|
||||
HI HarnessInit
|
||||
Stage HarnessStage
|
||||
Nonces map[address.Address]uint64
|
||||
GasCharges map[address.Address]types.BigInt
|
||||
Rand vm.Rand
|
||||
BlockHeight uint64
|
||||
|
||||
lastBalanceCheck map[address.Address]types.BigInt
|
||||
|
||||
ctx context.Context
|
||||
bs blockstore.Blockstore
|
||||
vm *vm.VM
|
||||
cs *store.ChainStore
|
||||
w *wallet.Wallet
|
||||
}
|
||||
|
||||
var HarnessMinerFunds = types.NewInt(1000000)
|
||||
|
||||
func HarnessAddr(addr *address.Address, value uint64) HarnessOpt {
|
||||
return func(t testing.TB, h *Harness) error {
|
||||
if h.Stage != HarnessPreInit {
|
||||
return nil
|
||||
}
|
||||
hi := &h.HI
|
||||
if addr.Empty() {
|
||||
k, err := h.w.GenerateKey(types.KTSecp256k1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
*addr = k
|
||||
}
|
||||
hi.Addrs[*addr] = types.NewInt(value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func HarnessMiner(addr *address.Address) HarnessOpt {
|
||||
return func(_ testing.TB, h *Harness) error {
|
||||
if h.Stage != HarnessPreInit {
|
||||
return nil
|
||||
}
|
||||
hi := &h.HI
|
||||
if addr.Empty() {
|
||||
*addr = hi.Miner
|
||||
return nil
|
||||
}
|
||||
delete(hi.Addrs, hi.Miner)
|
||||
hi.Miner = *addr
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func HarnessActor(actor *address.Address, creator *address.Address, code cid.Cid, params func() cbg.CBORMarshaler) HarnessOpt {
|
||||
return func(t testing.TB, h *Harness) error {
|
||||
if h.Stage != HarnessPostInit {
|
||||
return nil
|
||||
}
|
||||
if !actor.Empty() {
|
||||
return xerrors.New("actor address should be empty")
|
||||
}
|
||||
|
||||
ret, _ := h.CreateActor(t, *creator, code, params())
|
||||
if ret.ExitCode != 0 {
|
||||
return xerrors.Errorf("creating actor: %w", ret.ActorErr)
|
||||
}
|
||||
var err error
|
||||
*actor, err = address.NewFromBytes(ret.Return)
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func HarnessCtx(ctx context.Context) HarnessOpt {
|
||||
return func(t testing.TB, h *Harness) error {
|
||||
h.ctx = ctx
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func NewHarness(t *testing.T, options ...HarnessOpt) *Harness {
|
||||
w, err := wallet.NewWallet(wallet.NewMemKeyStore())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h := &Harness{
|
||||
Stage: HarnessPreInit,
|
||||
Nonces: make(map[address.Address]uint64),
|
||||
Rand: &fakeRand{},
|
||||
HI: HarnessInit{
|
||||
NAddrs: 1,
|
||||
Miner: blsaddr(0),
|
||||
Addrs: map[address.Address]types.BigInt{
|
||||
blsaddr(0): HarnessMinerFunds,
|
||||
},
|
||||
},
|
||||
GasCharges: make(map[address.Address]types.BigInt),
|
||||
|
||||
lastBalanceCheck: make(map[address.Address]types.BigInt),
|
||||
w: w,
|
||||
ctx: context.Background(),
|
||||
bs: bstore.NewBlockstore(dstore.NewMapDatastore()),
|
||||
BlockHeight: 0,
|
||||
}
|
||||
for _, opt := range options {
|
||||
err := opt(t, h)
|
||||
if err != nil {
|
||||
t.Fatalf("Applying options: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
st, err := gen.MakeInitialStateTree(h.bs, h.HI.Addrs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stateroot, err := st.Flush()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stateroot, err = gen.SetupStorageMarketActor(h.bs, stateroot, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
h.cs = store.NewChainStore(h.bs, nil)
|
||||
h.vm, err = vm.NewVM(stateroot, 1, h.Rand, h.HI.Miner, h.cs.Blockstore())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h.Stage = HarnessPostInit
|
||||
for _, opt := range options {
|
||||
err := opt(t, h)
|
||||
if err != nil {
|
||||
t.Fatalf("Applying options: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *Harness) Apply(t testing.TB, msg types.Message) (*vm.ApplyRet, *state.StateTree) {
|
||||
t.Helper()
|
||||
if msg.Nonce == 0 {
|
||||
msg.Nonce, _ = h.Nonces[msg.From]
|
||||
h.Nonces[msg.From] = msg.Nonce + 1
|
||||
}
|
||||
|
||||
ret, err := h.vm.ApplyMessage(h.ctx, &msg)
|
||||
if err != nil {
|
||||
t.Fatalf("Applying message: %+v", err)
|
||||
}
|
||||
|
||||
if ret != nil {
|
||||
if prev, ok := h.GasCharges[msg.From]; ok {
|
||||
h.GasCharges[msg.From] = types.BigAdd(prev, ret.GasUsed)
|
||||
} else {
|
||||
h.GasCharges[msg.From] = ret.GasUsed
|
||||
}
|
||||
}
|
||||
|
||||
stateroot, err := h.vm.Flush(context.TODO())
|
||||
if err != nil {
|
||||
t.Fatalf("Flushing VM: %+v", err)
|
||||
}
|
||||
cst := hamt.CSTFromBstore(h.bs)
|
||||
state, err := state.LoadStateTree(cst, stateroot)
|
||||
if err != nil {
|
||||
t.Fatalf("Loading state tree: %+v", err)
|
||||
}
|
||||
return ret, state
|
||||
}
|
||||
|
||||
func (h *Harness) CreateActor(t testing.TB, from address.Address,
|
||||
code cid.Cid, params cbg.CBORMarshaler) (*vm.ApplyRet, *state.StateTree) {
|
||||
t.Helper()
|
||||
|
||||
return h.Apply(t, types.Message{
|
||||
To: actors.InitAddress,
|
||||
From: from,
|
||||
Method: actors.IAMethods.Exec,
|
||||
Params: DumpObject(t,
|
||||
&actors.ExecParams{
|
||||
Code: code,
|
||||
Params: DumpObject(t, params),
|
||||
}),
|
||||
GasPrice: types.NewInt(1),
|
||||
GasLimit: types.NewInt(testGasLimit),
|
||||
Value: types.NewInt(0),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Harness) SendFunds(t testing.TB, from address.Address, to address.Address,
|
||||
value types.BigInt) (*vm.ApplyRet, *state.StateTree) {
|
||||
t.Helper()
|
||||
return h.Apply(t, types.Message{
|
||||
To: to,
|
||||
From: from,
|
||||
Method: 0,
|
||||
Value: value,
|
||||
GasPrice: types.NewInt(1),
|
||||
GasLimit: types.NewInt(testGasLimit),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Harness) Invoke(t testing.TB, from address.Address, to address.Address,
|
||||
method uint64, params cbg.CBORMarshaler) (*vm.ApplyRet, *state.StateTree) {
|
||||
t.Helper()
|
||||
return h.InvokeWithValue(t, from, to, method, types.NewInt(0), params)
|
||||
}
|
||||
|
||||
func (h *Harness) InvokeWithValue(t testing.TB, from address.Address, to address.Address,
|
||||
method uint64, value types.BigInt, params cbg.CBORMarshaler) (*vm.ApplyRet, *state.StateTree) {
|
||||
t.Helper()
|
||||
h.vm.SetBlockHeight(h.BlockHeight)
|
||||
return h.Apply(t, types.Message{
|
||||
To: to,
|
||||
From: from,
|
||||
Method: method,
|
||||
Value: value,
|
||||
Params: DumpObject(t, params),
|
||||
GasPrice: types.NewInt(1),
|
||||
GasLimit: types.NewInt(testGasLimit),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Harness) AssertBalance(t testing.TB, addr address.Address, amt uint64) {
|
||||
t.Helper()
|
||||
|
||||
b, err := h.vm.ActorBalance(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", err)
|
||||
}
|
||||
|
||||
if types.BigCmp(types.NewInt(amt), b) != 0 {
|
||||
t.Errorf("expected %s to have balanced of %d. Instead has %s", addr, amt, b)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Harness) AssertBalanceChange(t testing.TB, addr address.Address, amt int64) {
|
||||
t.Helper()
|
||||
lastBalance, ok := h.lastBalanceCheck[addr]
|
||||
if !ok {
|
||||
lastBalance, ok = h.HI.Addrs[addr]
|
||||
if !ok {
|
||||
lastBalance = types.NewInt(0)
|
||||
}
|
||||
}
|
||||
|
||||
var expected types.BigInt
|
||||
|
||||
if amt >= 0 {
|
||||
expected = types.BigAdd(lastBalance, types.NewInt(uint64(amt)))
|
||||
} else {
|
||||
expected = types.BigSub(lastBalance, types.NewInt(uint64(-amt)))
|
||||
}
|
||||
|
||||
h.lastBalanceCheck[addr] = expected
|
||||
|
||||
if gasUsed, ok := h.GasCharges[addr]; ok {
|
||||
expected = types.BigSub(expected, gasUsed)
|
||||
}
|
||||
|
||||
b, err := h.vm.ActorBalance(addr)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", err)
|
||||
}
|
||||
|
||||
if types.BigCmp(expected, b) != 0 {
|
||||
t.Errorf("expected %s to have balanced of %d. Instead has %s", addr, amt, b)
|
||||
}
|
||||
}
|
||||
|
||||
func DumpObject(t testing.TB, obj cbg.CBORMarshaler) []byte {
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
t.Helper()
|
||||
b := new(bytes.Buffer)
|
||||
if err := obj.MarshalCBOR(b); err != nil {
|
||||
t.Fatalf("dumping params: %+v", err)
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
type fakeRand struct{}
|
||||
|
||||
func (fr *fakeRand) GetRandomness(ctx context.Context, h int64) ([]byte, error) {
|
||||
out := make([]byte, 32)
|
||||
rand.New(rand.NewSource(h)).Read(out)
|
||||
return out, nil
|
||||
}
|
||||
@@ -7,10 +7,6 @@ import (
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
)
|
||||
|
||||
var (
|
||||
EmptyStructCBOR = []byte{0xa0}
|
||||
)
|
||||
|
||||
func SerializeParams(i cbg.CBORMarshaler) ([]byte, aerrors.ActorError) {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := i.MarshalCBOR(buf); err != nil {
|
||||
|
||||
@@ -1,415 +0,0 @@
|
||||
package address
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
bls "github.com/filecoin-project/filecoin-ffi"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
"github.com/minio/blake2b-simd"
|
||||
"github.com/multiformats/go-varint"
|
||||
"github.com/polydawn/refmt/obj/atlas"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cbor.RegisterCborType(addressAtlasEntry)
|
||||
}
|
||||
|
||||
var addressAtlasEntry = atlas.BuildEntry(Address{}).Transform().
|
||||
TransformMarshal(atlas.MakeMarshalTransformFunc(
|
||||
func(a Address) (string, error) {
|
||||
return string(a.Bytes()), nil
|
||||
})).
|
||||
TransformUnmarshal(atlas.MakeUnmarshalTransformFunc(
|
||||
func(x string) (Address, error) {
|
||||
return NewFromBytes([]byte(x))
|
||||
})).
|
||||
Complete()
|
||||
|
||||
// Address is the go type that represents an address in the filecoin network.
|
||||
type Address struct{ str string }
|
||||
|
||||
// Undef is the type that represents an undefined address.
|
||||
var Undef = Address{}
|
||||
|
||||
// Network represents which network an address belongs to.
|
||||
type Network = byte
|
||||
|
||||
const (
|
||||
// Mainnet is the main network.
|
||||
Mainnet Network = iota
|
||||
// Testnet is the test network.
|
||||
Testnet
|
||||
)
|
||||
|
||||
// MainnetPrefix is the main network prefix.
|
||||
const MainnetPrefix = "f"
|
||||
|
||||
// TestnetPrefix is the main network prefix.
|
||||
const TestnetPrefix = "t"
|
||||
|
||||
// Protocol represents which protocol an address uses.
|
||||
type Protocol = byte
|
||||
|
||||
const (
|
||||
// ID represents the address ID protocol.
|
||||
ID Protocol = iota
|
||||
// SECP256K1 represents the address SECP256K1 protocol.
|
||||
SECP256K1
|
||||
// Actor represents the address Actor protocol.
|
||||
Actor
|
||||
// BLS represents the address BLS protocol.
|
||||
BLS
|
||||
|
||||
Unknown = Protocol(255)
|
||||
)
|
||||
|
||||
// Protocol returns the protocol used by the address.
|
||||
func (a Address) Protocol() Protocol {
|
||||
if len(a.str) == 0 {
|
||||
return Unknown
|
||||
}
|
||||
return a.str[0]
|
||||
}
|
||||
|
||||
// Payload returns the payload of the address.
|
||||
func (a Address) Payload() []byte {
|
||||
return []byte(a.str[1:])
|
||||
}
|
||||
|
||||
// Bytes returns the address as bytes.
|
||||
func (a Address) Bytes() []byte {
|
||||
return []byte(a.str)
|
||||
}
|
||||
|
||||
// String returns an address encoded as a string.
|
||||
func (a Address) String() string {
|
||||
str, err := encode(Testnet, a)
|
||||
if err != nil {
|
||||
panic(err) // I don't know if this one is okay
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// Empty returns true if the address is empty, false otherwise.
|
||||
func (a Address) Empty() bool {
|
||||
return a == Undef
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals the cbor bytes into the address.
|
||||
func (a Address) Unmarshal(b []byte) error {
|
||||
return cbor.DecodeInto(b, &a)
|
||||
}
|
||||
|
||||
// Marshal marshals the address to cbor.
|
||||
func (a Address) Marshal() ([]byte, error) {
|
||||
return cbor.DumpObject(a)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json unmarshal interface.
|
||||
func (a *Address) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addr, err := decode(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*a = addr
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json marshal interface.
|
||||
func (a Address) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + a.String() + `"`), nil
|
||||
}
|
||||
|
||||
// Format implements the Formatter interface.
|
||||
func (a Address) Format(f fmt.State, c rune) {
|
||||
switch c {
|
||||
case 'v':
|
||||
if a.Empty() {
|
||||
fmt.Fprint(f, UndefAddressString) //nolint: errcheck
|
||||
} else {
|
||||
fmt.Fprintf(f, "[%x - %x]", a.Protocol(), a.Payload()) // nolint: errcheck
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(f, "%s", a.String()) // nolint: errcheck
|
||||
default:
|
||||
fmt.Fprintf(f, "%"+string(c), a.Bytes()) // nolint: errcheck
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Address) Scan(value interface{}) error {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
a1, err := decode(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*a = a1
|
||||
|
||||
return nil
|
||||
default:
|
||||
return xerrors.New("non-string types unsupported")
|
||||
}
|
||||
}
|
||||
|
||||
// NewIDAddress returns an address using the ID protocol.
|
||||
func NewIDAddress(id uint64) (Address, error) {
|
||||
return newAddress(ID, varint.ToUvarint(id))
|
||||
}
|
||||
|
||||
// NewSecp256k1Address returns an address using the SECP256K1 protocol.
|
||||
func NewSecp256k1Address(pubkey []byte) (Address, error) {
|
||||
return newAddress(SECP256K1, addressHash(pubkey))
|
||||
}
|
||||
|
||||
// NewActorAddress returns an address using the Actor protocol.
|
||||
func NewActorAddress(data []byte) (Address, error) {
|
||||
return newAddress(Actor, addressHash(data))
|
||||
}
|
||||
|
||||
// NewBLSAddress returns an address using the BLS protocol.
|
||||
func NewBLSAddress(pubkey []byte) (Address, error) {
|
||||
return newAddress(BLS, pubkey)
|
||||
}
|
||||
|
||||
// NewFromString returns the address represented by the string `addr`.
|
||||
func NewFromString(addr string) (Address, error) {
|
||||
return decode(addr)
|
||||
}
|
||||
|
||||
// NewFromBytes return the address represented by the bytes `addr`.
|
||||
func NewFromBytes(addr []byte) (Address, error) {
|
||||
if len(addr) == 0 {
|
||||
return Undef, nil
|
||||
}
|
||||
if len(addr) == 1 {
|
||||
return Undef, ErrInvalidLength
|
||||
}
|
||||
return newAddress(addr[0], addr[1:])
|
||||
}
|
||||
|
||||
// Checksum returns the checksum of `ingest`.
|
||||
func Checksum(ingest []byte) []byte {
|
||||
return hash(ingest, checksumHashConfig)
|
||||
}
|
||||
|
||||
// ValidateChecksum returns true if the checksum of `ingest` is equal to `expected`>
|
||||
func ValidateChecksum(ingest, expect []byte) bool {
|
||||
digest := Checksum(ingest)
|
||||
return bytes.Equal(digest, expect)
|
||||
}
|
||||
|
||||
func addressHash(ingest []byte) []byte {
|
||||
return hash(ingest, payloadHashConfig)
|
||||
}
|
||||
|
||||
func newAddress(protocol Protocol, payload []byte) (Address, error) {
|
||||
switch protocol {
|
||||
case ID:
|
||||
_, n, err := varint.FromUvarint(payload)
|
||||
if err != nil {
|
||||
return Undef, xerrors.Errorf("could not decode: %v: %w", err, ErrInvalidPayload)
|
||||
}
|
||||
if n != len(payload) {
|
||||
return Undef, xerrors.Errorf("different varint length (v:%d != p:%d): %w",
|
||||
n, len(payload), ErrInvalidPayload)
|
||||
}
|
||||
case SECP256K1, Actor:
|
||||
if len(payload) != PayloadHashLength {
|
||||
return Undef, ErrInvalidPayload
|
||||
}
|
||||
case BLS:
|
||||
if len(payload) != bls.PublicKeyBytes {
|
||||
return Undef, ErrInvalidPayload
|
||||
}
|
||||
default:
|
||||
return Undef, ErrUnknownProtocol
|
||||
}
|
||||
explen := 1 + len(payload)
|
||||
buf := make([]byte, explen)
|
||||
|
||||
buf[0] = protocol
|
||||
copy(buf[1:], payload)
|
||||
|
||||
return Address{string(buf)}, nil
|
||||
}
|
||||
|
||||
func encode(network Network, addr Address) (string, error) {
|
||||
if addr == Undef {
|
||||
return UndefAddressString, nil
|
||||
}
|
||||
var ntwk string
|
||||
switch network {
|
||||
case Mainnet:
|
||||
ntwk = MainnetPrefix
|
||||
case Testnet:
|
||||
ntwk = TestnetPrefix
|
||||
default:
|
||||
return UndefAddressString, ErrUnknownNetwork
|
||||
}
|
||||
|
||||
var strAddr string
|
||||
switch addr.Protocol() {
|
||||
case SECP256K1, Actor, BLS:
|
||||
cksm := Checksum(append([]byte{addr.Protocol()}, addr.Payload()...))
|
||||
strAddr = ntwk + fmt.Sprintf("%d", addr.Protocol()) + AddressEncoding.WithPadding(-1).EncodeToString(append(addr.Payload(), cksm[:]...))
|
||||
case ID:
|
||||
i, n, err := varint.FromUvarint(addr.Payload())
|
||||
if err != nil {
|
||||
return UndefAddressString, xerrors.Errorf("could not decode varint: %w", err)
|
||||
}
|
||||
if n != len(addr.Payload()) {
|
||||
return UndefAddressString, xerrors.Errorf("payload contains additional bytes")
|
||||
}
|
||||
strAddr = fmt.Sprintf("%s%d%d", ntwk, addr.Protocol(), i)
|
||||
default:
|
||||
return UndefAddressString, ErrUnknownProtocol
|
||||
}
|
||||
return strAddr, nil
|
||||
}
|
||||
|
||||
func decode(a string) (Address, error) {
|
||||
if len(a) == 0 {
|
||||
return Undef, nil
|
||||
}
|
||||
if a == UndefAddressString {
|
||||
return Undef, nil
|
||||
}
|
||||
if len(a) > MaxAddressStringLength || len(a) < 3 {
|
||||
return Undef, ErrInvalidLength
|
||||
}
|
||||
|
||||
if string(a[0]) != MainnetPrefix && string(a[0]) != TestnetPrefix {
|
||||
return Undef, ErrUnknownNetwork
|
||||
}
|
||||
|
||||
var protocol Protocol
|
||||
switch a[1] {
|
||||
case '0':
|
||||
protocol = ID
|
||||
case '1':
|
||||
protocol = SECP256K1
|
||||
case '2':
|
||||
protocol = Actor
|
||||
case '3':
|
||||
protocol = BLS
|
||||
default:
|
||||
return Undef, ErrUnknownProtocol
|
||||
}
|
||||
|
||||
raw := a[2:]
|
||||
if protocol == ID {
|
||||
// 20 is length of math.MaxUint64 as a string
|
||||
if len(raw) > 20 {
|
||||
return Undef, ErrInvalidLength
|
||||
}
|
||||
id, err := strconv.ParseUint(raw, 10, 64)
|
||||
if err != nil {
|
||||
return Undef, ErrInvalidPayload
|
||||
}
|
||||
return newAddress(protocol, varint.ToUvarint(id))
|
||||
}
|
||||
|
||||
payloadcksm, err := AddressEncoding.WithPadding(-1).DecodeString(raw)
|
||||
if err != nil {
|
||||
return Undef, err
|
||||
}
|
||||
payload := payloadcksm[:len(payloadcksm)-ChecksumHashLength]
|
||||
cksm := payloadcksm[len(payloadcksm)-ChecksumHashLength:]
|
||||
|
||||
if protocol == SECP256K1 || protocol == Actor {
|
||||
if len(payload) != 20 {
|
||||
return Undef, ErrInvalidPayload
|
||||
}
|
||||
}
|
||||
|
||||
if !ValidateChecksum(append([]byte{protocol}, payload...), cksm) {
|
||||
return Undef, ErrInvalidChecksum
|
||||
}
|
||||
|
||||
return newAddress(protocol, payload)
|
||||
}
|
||||
|
||||
func hash(ingest []byte, cfg *blake2b.Config) []byte {
|
||||
hasher, err := blake2b.New(cfg)
|
||||
if err != nil {
|
||||
// If this happens sth is very wrong.
|
||||
panic(fmt.Sprintf("invalid address hash configuration: %v", err)) // ok
|
||||
}
|
||||
if _, err := hasher.Write(ingest); err != nil {
|
||||
// blake2bs Write implementation never returns an error in its current
|
||||
// setup. So if this happens sth went very wrong.
|
||||
panic(fmt.Sprintf("blake2b is unable to process hashes: %v", err)) // ok
|
||||
}
|
||||
return hasher.Sum(nil)
|
||||
}
|
||||
|
||||
func (a Address) MarshalCBOR(w io.Writer) error {
|
||||
if a == Undef {
|
||||
return fmt.Errorf("cannot marshal undefined address")
|
||||
}
|
||||
|
||||
abytes := a.Bytes()
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajByteString, uint64(len(abytes)))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(abytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Address) UnmarshalCBOR(br io.Reader) error {
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if maj != cbg.MajByteString {
|
||||
return fmt.Errorf("cbor type for address unmarshal was not byte string")
|
||||
}
|
||||
|
||||
if extra > 64 {
|
||||
return fmt.Errorf("too many bytes to unmarshal for an address")
|
||||
}
|
||||
|
||||
buf := make([]byte, int(extra))
|
||||
if _, err := io.ReadFull(br, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addr, err := NewFromBytes(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if addr == Undef {
|
||||
return fmt.Errorf("cbor input should not contain empty addresses")
|
||||
}
|
||||
|
||||
*a = addr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IDFromAddress(addr Address) (uint64, error) {
|
||||
if addr.Protocol() != ID {
|
||||
return 0, xerrors.Errorf("cannot get id from non id address")
|
||||
}
|
||||
|
||||
i, _, err := varint.FromUvarint(addr.Payload())
|
||||
return i, err
|
||||
}
|
||||
@@ -1,542 +0,0 @@
|
||||
package address
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ffi "github.com/filecoin-project/filecoin-ffi"
|
||||
"github.com/multiformats/go-varint"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/filecoin-project/lotus/lib/crypto"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
}
|
||||
|
||||
func TestRandomIDAddress(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
addr, err := NewIDAddress(uint64(rand.Int()))
|
||||
assert.NoError(err)
|
||||
assert.Equal(ID, addr.Protocol())
|
||||
|
||||
str, err := encode(Testnet, addr)
|
||||
assert.NoError(err)
|
||||
|
||||
maybe, err := decode(str)
|
||||
assert.NoError(err)
|
||||
assert.Equal(addr, maybe)
|
||||
|
||||
}
|
||||
|
||||
var allTestAddresses = []string{
|
||||
"t00",
|
||||
"t01",
|
||||
"t010",
|
||||
"t0150",
|
||||
"t0499",
|
||||
"t01024",
|
||||
"t01729",
|
||||
"t0999999",
|
||||
"t15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7pdrq",
|
||||
"t12fiakbhe2gwd5cnmrenekasyn6v5tnaxaqizq6a",
|
||||
"t1wbxhu3ypkuo6eyp6hjx6davuelxaxrvwb2kuwva",
|
||||
"t1xtwapqc6nh4si2hcwpr3656iotzmlwumogqbuaa",
|
||||
"t1xcbgdhkgkwht3hrrnui3jdopeejsoatkzmoltqy",
|
||||
"t17uoq6tp427uzv7fztkbsnn64iwotfrristwpryy",
|
||||
"t24vg6ut43yw2h2jqydgbg2xq7x6f4kub3bg6as6i",
|
||||
"t25nml2cfbljvn4goqtclhifepvfnicv6g7mfmmvq",
|
||||
"t2nuqrg7vuysaue2pistjjnt3fadsdzvyuatqtfei",
|
||||
"t24dd4ox4c2vpf5vk5wkadgyyn6qtuvgcpxxon64a",
|
||||
"t2gfvuyh7v2sx3patm5k23wdzmhyhtmqctasbr23y",
|
||||
"t3vvmn62lofvhjd2ugzca6sof2j2ubwok6cj4xxbfzz4yuxfkgobpihhd2thlanmsh3w2ptld2gqkn2jvlss4a",
|
||||
"t3wmuu6crofhqmm3v4enos73okk2l366ck6yc4owxwbdtkmpk42ohkqxfitcpa57pjdcftql4tojda2poeruwa",
|
||||
"t3s2q2hzhkpiknjgmf4zq3ejab2rh62qbndueslmsdzervrhapxr7dftie4kpnpdiv2n6tvkr743ndhrsw6d3a",
|
||||
"t3q22fijmmlckhl56rn5nkyamkph3mcfu5ed6dheq53c244hfmnq2i7efdma3cj5voxenwiummf2ajlsbxc65a",
|
||||
"t3u5zgwa4ael3vuocgc5mfgygo4yuqocrntuuhcklf4xzg5tcaqwbyfabxetwtj4tsam3pbhnwghyhijr5mixa",
|
||||
}
|
||||
|
||||
func TestVectorsIDAddress(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input uint64
|
||||
expected string
|
||||
}{
|
||||
{uint64(0), "t00"},
|
||||
{uint64(1), "t01"},
|
||||
{uint64(10), "t010"},
|
||||
{uint64(150), "t0150"},
|
||||
{uint64(499), "t0499"},
|
||||
{uint64(1024), "t01024"},
|
||||
{uint64(1729), "t01729"},
|
||||
{uint64(999999), "t0999999"},
|
||||
{math.MaxUint64, fmt.Sprintf("t0%s", strconv.FormatUint(math.MaxUint64, 10))},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(fmt.Sprintf("testing actorID address: %s", tc.expected), func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Round trip encoding and decoding from string
|
||||
addr, err := NewIDAddress(tc.input)
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.expected, addr.String())
|
||||
|
||||
maybeAddr, err := NewFromString(tc.expected)
|
||||
assert.NoError(err)
|
||||
assert.Equal(ID, maybeAddr.Protocol())
|
||||
id, _, err := varint.FromUvarint(maybeAddr.Payload())
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.input, id)
|
||||
|
||||
// Round trip to and from bytes
|
||||
maybeAddrBytes, err := NewFromBytes(maybeAddr.Bytes())
|
||||
assert.NoError(err)
|
||||
assert.Equal(maybeAddr, maybeAddrBytes)
|
||||
|
||||
// Round trip encoding and decoding json
|
||||
b, err := addr.MarshalJSON()
|
||||
assert.NoError(err)
|
||||
|
||||
var newAddr Address
|
||||
err = newAddr.UnmarshalJSON(b)
|
||||
assert.NoError(err)
|
||||
assert.Equal(addr, newAddr)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSecp256k1Address(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
sk, err := crypto.GenerateKey()
|
||||
assert.NoError(err)
|
||||
|
||||
addr, err := NewSecp256k1Address(crypto.PublicKey(sk))
|
||||
assert.NoError(err)
|
||||
assert.Equal(SECP256K1, addr.Protocol())
|
||||
|
||||
str, err := encode(Mainnet, addr)
|
||||
assert.NoError(err)
|
||||
|
||||
maybe, err := decode(str)
|
||||
assert.NoError(err)
|
||||
assert.Equal(addr, maybe)
|
||||
|
||||
}
|
||||
|
||||
func TestVectorSecp256k1Address(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input []byte
|
||||
expected string
|
||||
}{
|
||||
{[]byte{4, 148, 2, 250, 195, 126, 100, 50, 164, 22, 163, 160, 202, 84,
|
||||
38, 181, 24, 90, 179, 178, 79, 97, 52, 239, 162, 92, 228, 135, 200,
|
||||
45, 46, 78, 19, 191, 69, 37, 17, 224, 210, 36, 84, 33, 248, 97, 59,
|
||||
193, 13, 114, 250, 33, 102, 102, 169, 108, 59, 193, 57, 32, 211,
|
||||
255, 35, 63, 208, 188, 5},
|
||||
"t15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7pdrq"},
|
||||
|
||||
{[]byte{4, 118, 135, 185, 16, 55, 155, 242, 140, 190, 58, 234, 103, 75,
|
||||
18, 0, 12, 107, 125, 186, 70, 255, 192, 95, 108, 148, 254, 42, 34,
|
||||
187, 204, 38, 2, 255, 127, 92, 118, 242, 28, 165, 93, 54, 149, 145,
|
||||
82, 176, 225, 232, 135, 145, 124, 57, 53, 118, 238, 240, 147, 246,
|
||||
30, 189, 58, 208, 111, 127, 218},
|
||||
"t12fiakbhe2gwd5cnmrenekasyn6v5tnaxaqizq6a"},
|
||||
{[]byte{4, 222, 253, 208, 16, 1, 239, 184, 110, 1, 222, 213, 206, 52,
|
||||
248, 71, 167, 58, 20, 129, 158, 230, 65, 188, 182, 11, 185, 41, 147,
|
||||
89, 111, 5, 220, 45, 96, 95, 41, 133, 248, 209, 37, 129, 45, 172,
|
||||
65, 99, 163, 150, 52, 155, 35, 193, 28, 194, 255, 53, 157, 229, 75,
|
||||
226, 135, 234, 98, 49, 155},
|
||||
"t1wbxhu3ypkuo6eyp6hjx6davuelxaxrvwb2kuwva"},
|
||||
{[]byte{4, 3, 237, 18, 200, 20, 182, 177, 13, 46, 224, 157, 149, 180,
|
||||
104, 141, 178, 209, 128, 208, 169, 163, 122, 107, 106, 125, 182, 61,
|
||||
41, 129, 30, 233, 115, 4, 121, 216, 239, 145, 57, 233, 18, 73, 202,
|
||||
189, 57, 50, 145, 207, 229, 210, 119, 186, 118, 222, 69, 227, 224,
|
||||
133, 163, 118, 129, 191, 54, 69, 210},
|
||||
"t1xtwapqc6nh4si2hcwpr3656iotzmlwumogqbuaa"},
|
||||
{[]byte{4, 247, 150, 129, 154, 142, 39, 22, 49, 175, 124, 24, 151, 151,
|
||||
181, 69, 214, 2, 37, 147, 97, 71, 230, 1, 14, 101, 98, 179, 206, 158,
|
||||
254, 139, 16, 20, 65, 97, 169, 30, 208, 180, 236, 137, 8, 0, 37, 63,
|
||||
166, 252, 32, 172, 144, 251, 241, 251, 242, 113, 48, 164, 236, 195,
|
||||
228, 3, 183, 5, 118},
|
||||
"t1xcbgdhkgkwht3hrrnui3jdopeejsoatkzmoltqy"},
|
||||
{[]byte{4, 66, 131, 43, 248, 124, 206, 158, 163, 69, 185, 3, 80, 222,
|
||||
125, 52, 149, 133, 156, 164, 73, 5, 156, 94, 136, 221, 231, 66, 133,
|
||||
223, 251, 158, 192, 30, 186, 188, 95, 200, 98, 104, 207, 234, 235,
|
||||
167, 174, 5, 191, 184, 214, 142, 183, 90, 82, 104, 120, 44, 248, 111,
|
||||
200, 112, 43, 239, 138, 31, 224},
|
||||
"t17uoq6tp427uzv7fztkbsnn64iwotfrristwpryy"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(fmt.Sprintf("testing secp256k1 address: %s", tc.expected), func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Round trip encoding and decoding from string
|
||||
addr, err := NewSecp256k1Address(tc.input)
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.expected, addr.String())
|
||||
|
||||
maybeAddr, err := NewFromString(tc.expected)
|
||||
assert.NoError(err)
|
||||
assert.Equal(SECP256K1, maybeAddr.Protocol())
|
||||
assert.Equal(addressHash(tc.input), maybeAddr.Payload())
|
||||
|
||||
// Round trip to and from bytes
|
||||
maybeAddrBytes, err := NewFromBytes(maybeAddr.Bytes())
|
||||
assert.NoError(err)
|
||||
assert.Equal(maybeAddr, maybeAddrBytes)
|
||||
|
||||
// Round trip encoding and decoding json
|
||||
b, err := addr.MarshalJSON()
|
||||
assert.NoError(err)
|
||||
|
||||
var newAddr Address
|
||||
err = newAddr.UnmarshalJSON(b)
|
||||
assert.NoError(err)
|
||||
assert.Equal(addr, newAddr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomActorAddress(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
actorMsg := make([]byte, 20)
|
||||
rand.Read(actorMsg)
|
||||
|
||||
addr, err := NewActorAddress(actorMsg)
|
||||
assert.NoError(err)
|
||||
assert.Equal(Actor, addr.Protocol())
|
||||
|
||||
str, err := encode(Mainnet, addr)
|
||||
assert.NoError(err)
|
||||
|
||||
maybe, err := decode(str)
|
||||
assert.NoError(err)
|
||||
assert.Equal(addr, maybe)
|
||||
|
||||
}
|
||||
|
||||
func TestVectorActorAddress(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input []byte
|
||||
expected string
|
||||
}{
|
||||
{[]byte{118, 18, 129, 144, 205, 240, 104, 209, 65, 128, 68, 172, 192,
|
||||
62, 11, 103, 129, 151, 13, 96},
|
||||
"t24vg6ut43yw2h2jqydgbg2xq7x6f4kub3bg6as6i"},
|
||||
{[]byte{44, 175, 184, 226, 224, 107, 186, 152, 234, 101, 124, 92, 245,
|
||||
244, 32, 35, 170, 35, 232, 142},
|
||||
"t25nml2cfbljvn4goqtclhifepvfnicv6g7mfmmvq"},
|
||||
{[]byte{2, 44, 158, 14, 162, 157, 143, 64, 197, 106, 190, 195, 92, 141,
|
||||
88, 125, 160, 166, 76, 24},
|
||||
"t2nuqrg7vuysaue2pistjjnt3fadsdzvyuatqtfei"},
|
||||
{[]byte{223, 236, 3, 14, 32, 79, 15, 89, 216, 15, 29, 94, 233, 29, 253,
|
||||
6, 109, 127, 99, 189},
|
||||
"t24dd4ox4c2vpf5vk5wkadgyyn6qtuvgcpxxon64a"},
|
||||
{[]byte{61, 58, 137, 232, 221, 171, 84, 120, 50, 113, 108, 109, 70, 140,
|
||||
53, 96, 201, 244, 127, 216},
|
||||
"t2gfvuyh7v2sx3patm5k23wdzmhyhtmqctasbr23y"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(fmt.Sprintf("testing Actor address: %s", tc.expected), func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Round trip encoding and decoding from string
|
||||
addr, err := NewActorAddress(tc.input)
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.expected, addr.String())
|
||||
|
||||
maybeAddr, err := NewFromString(tc.expected)
|
||||
assert.NoError(err)
|
||||
assert.Equal(Actor, maybeAddr.Protocol())
|
||||
assert.Equal(addressHash(tc.input), maybeAddr.Payload())
|
||||
|
||||
// Round trip to and from bytes
|
||||
maybeAddrBytes, err := NewFromBytes(maybeAddr.Bytes())
|
||||
assert.NoError(err)
|
||||
assert.Equal(maybeAddr, maybeAddrBytes)
|
||||
|
||||
// Round trip encoding and decoding json
|
||||
b, err := addr.MarshalJSON()
|
||||
assert.NoError(err)
|
||||
|
||||
var newAddr Address
|
||||
err = newAddr.UnmarshalJSON(b)
|
||||
assert.NoError(err)
|
||||
assert.Equal(addr, newAddr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomBLSAddress(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pk := ffi.PrivateKeyPublicKey(ffi.PrivateKeyGenerate())
|
||||
|
||||
addr, err := NewBLSAddress(pk[:])
|
||||
assert.NoError(err)
|
||||
assert.Equal(BLS, addr.Protocol())
|
||||
|
||||
str, err := encode(Mainnet, addr)
|
||||
assert.NoError(err)
|
||||
|
||||
maybe, err := decode(str)
|
||||
assert.NoError(err)
|
||||
assert.Equal(addr, maybe)
|
||||
|
||||
}
|
||||
|
||||
func TestVectorBLSAddress(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input []byte
|
||||
expected string
|
||||
}{
|
||||
{[]byte{173, 88, 223, 105, 110, 45, 78, 145, 234, 134, 200, 129, 233, 56,
|
||||
186, 78, 168, 27, 57, 94, 18, 121, 123, 132, 185, 207, 49, 75, 149, 70,
|
||||
112, 94, 131, 156, 122, 153, 214, 6, 178, 71, 221, 180, 249, 172, 122,
|
||||
52, 20, 221},
|
||||
"t3vvmn62lofvhjd2ugzca6sof2j2ubwok6cj4xxbfzz4yuxfkgobpihhd2thlanmsh3w2ptld2gqkn2jvlss4a"},
|
||||
{[]byte{179, 41, 79, 10, 46, 41, 224, 198, 110, 188, 35, 93, 47, 237,
|
||||
202, 86, 151, 191, 120, 74, 246, 5, 199, 90, 246, 8, 230, 166, 61, 92,
|
||||
211, 142, 168, 92, 168, 152, 158, 14, 253, 233, 24, 139, 56, 47,
|
||||
147, 114, 70, 13},
|
||||
"t3wmuu6crofhqmm3v4enos73okk2l366ck6yc4owxwbdtkmpk42ohkqxfitcpa57pjdcftql4tojda2poeruwa"},
|
||||
{[]byte{150, 161, 163, 228, 234, 122, 20, 212, 153, 133, 230, 97, 178,
|
||||
36, 1, 212, 79, 237, 64, 45, 29, 9, 37, 178, 67, 201, 35, 88, 156,
|
||||
15, 188, 126, 50, 205, 4, 226, 158, 215, 141, 21, 211, 125, 58, 170,
|
||||
63, 230, 218, 51},
|
||||
"t3s2q2hzhkpiknjgmf4zq3ejab2rh62qbndueslmsdzervrhapxr7dftie4kpnpdiv2n6tvkr743ndhrsw6d3a"},
|
||||
{[]byte{134, 180, 84, 37, 140, 88, 148, 117, 247, 209, 111, 90, 172, 1,
|
||||
138, 121, 246, 193, 22, 157, 32, 252, 51, 146, 29, 216, 181, 206, 28,
|
||||
172, 108, 52, 143, 144, 163, 96, 54, 36, 246, 174, 185, 27, 100, 81,
|
||||
140, 46, 128, 149},
|
||||
"t3q22fijmmlckhl56rn5nkyamkph3mcfu5ed6dheq53c244hfmnq2i7efdma3cj5voxenwiummf2ajlsbxc65a"},
|
||||
{[]byte{167, 114, 107, 3, 128, 34, 247, 90, 56, 70, 23, 88, 83, 96, 206,
|
||||
230, 41, 7, 10, 45, 157, 40, 113, 41, 101, 229, 242, 110, 204, 64,
|
||||
133, 131, 130, 128, 55, 36, 237, 52, 242, 114, 3, 54, 240, 157, 182,
|
||||
49, 240, 116},
|
||||
"t3u5zgwa4ael3vuocgc5mfgygo4yuqocrntuuhcklf4xzg5tcaqwbyfabxetwtj4tsam3pbhnwghyhijr5mixa"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(fmt.Sprintf("testing bls address: %s", tc.expected), func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Round trip encoding and decoding from string
|
||||
addr, err := NewBLSAddress(tc.input)
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.expected, addr.String())
|
||||
|
||||
maybeAddr, err := NewFromString(tc.expected)
|
||||
assert.NoError(err)
|
||||
assert.Equal(BLS, maybeAddr.Protocol())
|
||||
assert.Equal(tc.input, maybeAddr.Payload())
|
||||
|
||||
// Round trip to and from bytes
|
||||
maybeAddrBytes, err := NewFromBytes(maybeAddr.Bytes())
|
||||
assert.NoError(err)
|
||||
assert.Equal(maybeAddr, maybeAddrBytes)
|
||||
|
||||
// Round trip encoding and decoding json
|
||||
b, err := addr.MarshalJSON()
|
||||
assert.NoError(err)
|
||||
|
||||
var newAddr Address
|
||||
err = newAddr.UnmarshalJSON(b)
|
||||
assert.NoError(err)
|
||||
assert.Equal(addr, newAddr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidStringAddresses(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
expetErr error
|
||||
}{
|
||||
{"Q2gfvuyh7v2sx3patm5k23wdzmhyhtmqctasbr23y", ErrUnknownNetwork},
|
||||
{"t4gfvuyh7v2sx3patm5k23wdzmhyhtmqctasbr23y", ErrUnknownProtocol},
|
||||
{"t2gfvuyh7v2sx3patm5k23wdzmhyhtmqctasbr24y", ErrInvalidChecksum},
|
||||
{"t0banananananannnnnnnnn", ErrInvalidLength},
|
||||
{"t0banananananannnnnnnn", ErrInvalidPayload},
|
||||
{"t2gfvuyh7v2sx3patm1k23wdzmhyhtmqctasbr24y", base32.CorruptInputError(16)}, // '1' is not in base32 alphabet
|
||||
{"t2gfvuyh7v2sx3paTm1k23wdzmhyhtmqctasbr24y", base32.CorruptInputError(14)}, // 'T' is not in base32 alphabet
|
||||
{"t2", ErrInvalidLength},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(fmt.Sprintf("testing string address: %s", tc.expetErr), func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
_, err := NewFromString(tc.input)
|
||||
assert.Equal(tc.expetErr, err)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestInvalidByteAddresses(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input []byte
|
||||
expetErr error
|
||||
}{
|
||||
// Unknown Protocol
|
||||
{[]byte{4, 4, 4}, ErrUnknownProtocol},
|
||||
|
||||
// ID protocol
|
||||
{[]byte{0}, ErrInvalidLength},
|
||||
|
||||
// SECP256K1 Protocol
|
||||
{append([]byte{1}, make([]byte, PayloadHashLength-1)...), ErrInvalidPayload},
|
||||
{append([]byte{1}, make([]byte, PayloadHashLength+1)...), ErrInvalidPayload},
|
||||
// Actor Protocol
|
||||
{append([]byte{2}, make([]byte, PayloadHashLength-1)...), ErrInvalidPayload},
|
||||
{append([]byte{2}, make([]byte, PayloadHashLength+1)...), ErrInvalidPayload},
|
||||
|
||||
// BLS Protocol
|
||||
{append([]byte{3}, make([]byte, ffi.PublicKeyBytes-1)...), ErrInvalidPayload},
|
||||
{append([]byte{3}, make([]byte, ffi.PrivateKeyBytes+1)...), ErrInvalidPayload},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(fmt.Sprintf("testing byte address: %s", tc.expetErr), func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
_, err := NewFromBytes(tc.input)
|
||||
assert.Equal(tc.expetErr, err)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestChecksum(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
data := []byte("helloworld")
|
||||
bata := []byte("kittinmittins")
|
||||
|
||||
cksm := Checksum(data)
|
||||
assert.Len(cksm, ChecksumHashLength)
|
||||
|
||||
assert.True(ValidateChecksum(data, cksm))
|
||||
assert.False(ValidateChecksum(bata, cksm))
|
||||
|
||||
}
|
||||
|
||||
func TestAddressFormat(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
a, err := NewActorAddress([]byte("hello"))
|
||||
require.NoError(err)
|
||||
|
||||
assert.Equal("t2wvjry4bx6bwj6kkhcmvgu5zafqyi5cjzbtet3va", a.String())
|
||||
assert.Equal("02B5531C7037F06C9F2947132A6A77202C308E8939", fmt.Sprintf("%X", a))
|
||||
assert.Equal("[2 - b5531c7037f06c9f2947132a6a77202c308e8939]", fmt.Sprintf("%v", a))
|
||||
|
||||
assert.Equal("", fmt.Sprintf("%X", Undef))
|
||||
assert.Equal(UndefAddressString, Undef.String())
|
||||
assert.Equal(UndefAddressString, fmt.Sprintf("%v", Undef))
|
||||
}
|
||||
|
||||
func TestCborMarshal(t *testing.T) {
|
||||
for _, a := range allTestAddresses {
|
||||
addr, err := NewFromString(a)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := addr.MarshalCBOR(buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
/*
|
||||
// Note: this is commented out because we're currently serializing addresses as cbor "text strings", not "byte strings".
|
||||
// This is to get around the restriction that refmt only allows string keys in maps.
|
||||
// if you change it to serialize to byte strings and uncomment this, the tests pass fine
|
||||
oldbytes, err := cbor.DumpObject(addr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(oldbytes, buf.Bytes()) {
|
||||
t.Fatalf("serialization doesnt match old serialization: %s", a)
|
||||
}
|
||||
*/
|
||||
|
||||
var out Address
|
||||
if err := out.UnmarshalCBOR(buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if out != addr {
|
||||
t.Fatalf("failed to round trip %s", a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCborMarshal(b *testing.B) {
|
||||
addr, err := NewFromString("t15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7pdrq")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf.Reset()
|
||||
if err := addr.MarshalCBOR(buf); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCborUnmarshal(b *testing.B) {
|
||||
addr, err := NewFromString("t15ihq5ibzwki2b4ep2f46avlkrqzhpqgtga7pdrq")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err := addr.MarshalCBOR(buf); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var a Address
|
||||
if err := a.UnmarshalCBOR(bytes.NewReader(buf.Bytes())); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIDEdgeCase(t *testing.T) {
|
||||
a, err := NewFromBytes([]byte{0, 0x80})
|
||||
_ = a.String()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package address
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func blsaddr(n int64) Address {
|
||||
buf := make([]byte, 48)
|
||||
r := rand.New(rand.NewSource(n))
|
||||
r.Read(buf)
|
||||
|
||||
addr, err := NewBLSAddress(buf)
|
||||
if err != nil {
|
||||
panic(err) // ok
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
func makeActorAddresses(n int) [][]byte {
|
||||
var addrs [][]byte
|
||||
for i := 0; i < n; i++ {
|
||||
a, err := NewActorAddress([]byte(fmt.Sprintf("ACTOR ADDRESS %d", i)))
|
||||
if err != nil {
|
||||
panic(err) // ok
|
||||
}
|
||||
addrs = append(addrs, a.Bytes())
|
||||
}
|
||||
|
||||
return addrs
|
||||
}
|
||||
|
||||
func makeBlsAddresses(n int64) [][]byte {
|
||||
var addrs [][]byte
|
||||
for i := int64(0); i < n; i++ {
|
||||
addrs = append(addrs, blsaddr(n).Bytes())
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func makeSecpAddresses(n int) [][]byte {
|
||||
var addrs [][]byte
|
||||
for i := 0; i < n; i++ {
|
||||
r := rand.New(rand.NewSource(int64(i)))
|
||||
buf := make([]byte, 32)
|
||||
r.Read(buf)
|
||||
|
||||
a, err := NewSecp256k1Address(buf)
|
||||
if err != nil {
|
||||
panic(err) // ok
|
||||
}
|
||||
|
||||
addrs = append(addrs, a.Bytes())
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func makeIDAddresses(n int) [][]byte {
|
||||
var addrs [][]byte
|
||||
for i := 0; i < n; i++ {
|
||||
|
||||
a, err := NewIDAddress(uint64(i))
|
||||
if err != nil {
|
||||
panic(err) // ok
|
||||
}
|
||||
|
||||
addrs = append(addrs, a.Bytes())
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func BenchmarkParseActorAddress(b *testing.B) {
|
||||
benchTestWithAddrs := func(a [][]byte) func(b *testing.B) {
|
||||
return func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := NewFromBytes(a[i%len(a)])
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.Run("actor", benchTestWithAddrs(makeActorAddresses(20)))
|
||||
b.Run("bls", benchTestWithAddrs(makeBlsAddresses(20)))
|
||||
b.Run("secp256k1", benchTestWithAddrs(makeSecpAddresses(20)))
|
||||
b.Run("id", benchTestWithAddrs(makeIDAddresses(20)))
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package address
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"errors"
|
||||
|
||||
"github.com/minio/blake2b-simd"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
var err error
|
||||
|
||||
TestAddress, err = NewActorAddress([]byte("satoshi"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
TestAddress2, err = NewActorAddress([]byte("nakamoto"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// TestAddress is an account with some initial funds in it.
|
||||
TestAddress Address
|
||||
// TestAddress2 is an account with some initial funds in it.
|
||||
TestAddress2 Address
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnknownNetwork is returned when encountering an unknown network in an address.
|
||||
ErrUnknownNetwork = errors.New("unknown address network")
|
||||
|
||||
// ErrUnknownProtocol is returned when encountering an unknown protocol in an address.
|
||||
ErrUnknownProtocol = errors.New("unknown address protocol")
|
||||
// ErrInvalidPayload is returned when encountering an invalid address payload.
|
||||
ErrInvalidPayload = errors.New("invalid address payload")
|
||||
// ErrInvalidLength is returned when encountering an address of invalid length.
|
||||
ErrInvalidLength = errors.New("invalid address length")
|
||||
// ErrInvalidChecksum is returned when encountering an invalid address checksum.
|
||||
ErrInvalidChecksum = errors.New("invalid address checksum")
|
||||
)
|
||||
|
||||
// UndefAddressString is the string used to represent an empty address when encoded to a string.
|
||||
var UndefAddressString = "<empty>"
|
||||
|
||||
// PayloadHashLength defines the hash length taken over addresses using the Actor and SECP256K1 protocols.
|
||||
const PayloadHashLength = 20
|
||||
|
||||
// ChecksumHashLength defines the hash length used for calculating address checksums.
|
||||
const ChecksumHashLength = 4
|
||||
|
||||
// MaxAddressStringLength is the max length of an address encoded as a string
|
||||
// it include the network prefx, protocol, and bls publickey
|
||||
const MaxAddressStringLength = 2 + 84
|
||||
|
||||
var payloadHashConfig = &blake2b.Config{Size: PayloadHashLength}
|
||||
var checksumHashConfig = &blake2b.Config{Size: ChecksumHashLength}
|
||||
|
||||
const encodeStd = "abcdefghijklmnopqrstuvwxyz234567"
|
||||
|
||||
// AddressEncoding defines the base32 config used for address encoding and decoding.
|
||||
var AddressEncoding = base32.NewEncoding(encodeStd)
|
||||
@@ -1,20 +0,0 @@
|
||||
package address
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// NewForTestGetter returns a closure that returns an address unique to that invocation.
|
||||
// The address is unique wrt the closure returned, not globally.
|
||||
func NewForTestGetter() func() Address {
|
||||
i := 0
|
||||
return func() Address {
|
||||
s := fmt.Sprintf("address%d", i)
|
||||
i++
|
||||
newAddr, err := NewActorAddress([]byte(s))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return newAddr
|
||||
}
|
||||
}
|
||||
+40
-4
@@ -1,6 +1,8 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/ipfs/go-cid"
|
||||
@@ -10,6 +12,35 @@ type BadBlockCache struct {
|
||||
badBlocks *lru.ARCCache
|
||||
}
|
||||
|
||||
type BadBlockReason struct {
|
||||
Reason string
|
||||
TipSet []cid.Cid
|
||||
OriginalReason *BadBlockReason
|
||||
}
|
||||
|
||||
func NewBadBlockReason(cid []cid.Cid, format string, i ...interface{}) BadBlockReason {
|
||||
return BadBlockReason{
|
||||
TipSet: cid,
|
||||
Reason: fmt.Sprintf(format, i...),
|
||||
}
|
||||
}
|
||||
|
||||
func (bbr BadBlockReason) Linked(reason string, i ...interface{}) BadBlockReason {
|
||||
or := &bbr
|
||||
if bbr.OriginalReason != nil {
|
||||
or = bbr.OriginalReason
|
||||
}
|
||||
return BadBlockReason{Reason: fmt.Sprintf(reason, i...), OriginalReason: or}
|
||||
}
|
||||
|
||||
func (bbr BadBlockReason) String() string {
|
||||
res := bbr.Reason
|
||||
if bbr.OriginalReason != nil {
|
||||
res += " caused by: " + fmt.Sprintf("%s %s", bbr.OriginalReason.TipSet, bbr.OriginalReason.String())
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func NewBadBlockCache() *BadBlockCache {
|
||||
cache, err := lru.NewARC(build.BadBlockCacheSize)
|
||||
if err != nil {
|
||||
@@ -21,10 +52,15 @@ func NewBadBlockCache() *BadBlockCache {
|
||||
}
|
||||
}
|
||||
|
||||
func (bts *BadBlockCache) Add(c cid.Cid) {
|
||||
bts.badBlocks.Add(c, nil)
|
||||
func (bts *BadBlockCache) Add(c cid.Cid, bbr BadBlockReason) {
|
||||
bts.badBlocks.Add(c, bbr)
|
||||
}
|
||||
|
||||
func (bts *BadBlockCache) Has(c cid.Cid) bool {
|
||||
return bts.badBlocks.Contains(c)
|
||||
func (bts *BadBlockCache) Has(c cid.Cid) (BadBlockReason, bool) {
|
||||
rval, ok := bts.badBlocks.Get(c)
|
||||
if !ok {
|
||||
return BadBlockReason{}, false
|
||||
}
|
||||
|
||||
return rval.(BadBlockReason), true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
logging "github.com/ipfs/go-log"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
var log = logging.Logger("beacon")
|
||||
|
||||
type Response struct {
|
||||
Entry types.BeaconEntry
|
||||
Err error
|
||||
}
|
||||
|
||||
// RandomBeacon represents a system that provides randomness to Lotus.
|
||||
// Other components interrogate the RandomBeacon to acquire randomness that's
|
||||
// valid for a specific chain epoch. Also to verify beacon entries that have
|
||||
// been posted on chain.
|
||||
type RandomBeacon interface {
|
||||
Entry(context.Context, uint64) <-chan Response
|
||||
VerifyEntry(types.BeaconEntry, types.BeaconEntry) error
|
||||
MaxBeaconRoundForEpoch(abi.ChainEpoch, types.BeaconEntry) uint64
|
||||
}
|
||||
|
||||
func ValidateBlockValues(b RandomBeacon, h *types.BlockHeader, prevEntry types.BeaconEntry) error {
|
||||
maxRound := b.MaxBeaconRoundForEpoch(h.Height, prevEntry)
|
||||
if maxRound == prevEntry.Round {
|
||||
if len(h.BeaconEntries) != 0 {
|
||||
return xerrors.Errorf("expected not to have any beacon entries in this block, got %d", len(h.BeaconEntries))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
last := h.BeaconEntries[len(h.BeaconEntries)-1]
|
||||
if last.Round != maxRound {
|
||||
return xerrors.Errorf("expected final beacon entry in block to be at round %d, got %d", maxRound, last.Round)
|
||||
}
|
||||
|
||||
for i, e := range h.BeaconEntries {
|
||||
if err := b.VerifyEntry(e, prevEntry); err != nil {
|
||||
return xerrors.Errorf("beacon entry %d (%d - %x (%d)) was invalid: %w", i, e.Round, e.Data, len(e.Data), err)
|
||||
}
|
||||
prevEntry = e
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func BeaconEntriesForBlock(ctx context.Context, beacon RandomBeacon, round abi.ChainEpoch, prev types.BeaconEntry) ([]types.BeaconEntry, error) {
|
||||
start := time.Now()
|
||||
|
||||
maxRound := beacon.MaxBeaconRoundForEpoch(round, prev)
|
||||
if maxRound == prev.Round {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TODO: this is a sketchy way to handle the genesis block not having a beacon entry
|
||||
if prev.Round == 0 {
|
||||
prev.Round = maxRound - 1
|
||||
}
|
||||
|
||||
cur := maxRound
|
||||
var out []types.BeaconEntry
|
||||
for cur > prev.Round {
|
||||
rch := beacon.Entry(ctx, cur)
|
||||
select {
|
||||
case resp := <-rch:
|
||||
if resp.Err != nil {
|
||||
return nil, xerrors.Errorf("beacon entry request returned error: %w", resp.Err)
|
||||
}
|
||||
|
||||
out = append(out, resp.Entry)
|
||||
cur = resp.Entry.Round - 1
|
||||
case <-ctx.Done():
|
||||
return nil, xerrors.Errorf("context timed out waiting on beacon entry to come back for round %d: %w", round, ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugw("fetching beacon entries", "took", time.Since(start), "numEntries", len(out))
|
||||
reverse(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func reverse(arr []types.BeaconEntry) {
|
||||
for i := 0; i < len(arr)/2; i++ {
|
||||
arr[i], arr[len(arr)-(1+i)] = arr[len(arr)-(1+i)], arr[i]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package drand
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
dchain "github.com/drand/drand/chain"
|
||||
dclient "github.com/drand/drand/client"
|
||||
hclient "github.com/drand/drand/client/http"
|
||||
dlog "github.com/drand/drand/log"
|
||||
gclient "github.com/drand/drand/lp2p/client"
|
||||
"github.com/drand/kyber"
|
||||
kzap "github.com/go-kit/kit/log/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
logging "github.com/ipfs/go-log"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/beacon"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
)
|
||||
|
||||
var log = logging.Logger("drand")
|
||||
|
||||
type drandPeer struct {
|
||||
addr string
|
||||
tls bool
|
||||
}
|
||||
|
||||
func (dp *drandPeer) Address() string {
|
||||
return dp.addr
|
||||
}
|
||||
|
||||
func (dp *drandPeer) IsTLS() bool {
|
||||
return dp.tls
|
||||
}
|
||||
|
||||
// DrandBeacon connects Lotus with a drand network in order to provide
|
||||
// randomness to the system in a way that's aligned with Filecoin rounds/epochs.
|
||||
//
|
||||
// We connect to drand peers via their public HTTP endpoints. The peers are
|
||||
// enumerated in the drandServers variable.
|
||||
//
|
||||
// The root trust for the Drand chain is configured from build.DrandChain.
|
||||
type DrandBeacon struct {
|
||||
client dclient.Client
|
||||
|
||||
pubkey kyber.Point
|
||||
|
||||
// seconds
|
||||
interval time.Duration
|
||||
|
||||
drandGenTime uint64
|
||||
filGenTime uint64
|
||||
filRoundTime uint64
|
||||
|
||||
cacheLk sync.Mutex
|
||||
localCache map[uint64]types.BeaconEntry
|
||||
}
|
||||
|
||||
func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub, config dtypes.DrandConfig) (*DrandBeacon, error) {
|
||||
if genesisTs == 0 {
|
||||
panic("what are you doing this cant be zero")
|
||||
}
|
||||
|
||||
drandChain, err := dchain.InfoFromJSON(bytes.NewReader([]byte(config.ChainInfoJSON)))
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("unable to unmarshal drand chain info: %w", err)
|
||||
}
|
||||
|
||||
dlogger := dlog.NewKitLoggerFrom(kzap.NewZapSugarLogger(
|
||||
log.SugaredLogger.Desugar(), zapcore.InfoLevel))
|
||||
|
||||
var clients []dclient.Client
|
||||
for _, url := range config.Servers {
|
||||
hc, err := hclient.NewWithInfo(url, drandChain, nil)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("could not create http drand client: %w", err)
|
||||
}
|
||||
clients = append(clients, hc)
|
||||
|
||||
}
|
||||
|
||||
opts := []dclient.Option{
|
||||
dclient.WithChainInfo(drandChain),
|
||||
dclient.WithCacheSize(1024),
|
||||
dclient.WithLogger(dlogger),
|
||||
dclient.WithAutoWatch(),
|
||||
}
|
||||
|
||||
if ps != nil {
|
||||
opts = append(opts, gclient.WithPubsub(ps))
|
||||
} else {
|
||||
log.Info("drand beacon without pubsub")
|
||||
}
|
||||
|
||||
client, err := dclient.Wrap(clients, opts...)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("creating drand client")
|
||||
}
|
||||
|
||||
db := &DrandBeacon{
|
||||
client: client,
|
||||
localCache: make(map[uint64]types.BeaconEntry),
|
||||
}
|
||||
|
||||
db.pubkey = drandChain.PublicKey
|
||||
db.interval = drandChain.Period
|
||||
db.drandGenTime = uint64(drandChain.GenesisTime)
|
||||
db.filRoundTime = interval
|
||||
db.filGenTime = genesisTs
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func (db *DrandBeacon) Entry(ctx context.Context, round uint64) <-chan beacon.Response {
|
||||
out := make(chan beacon.Response, 1)
|
||||
if round != 0 {
|
||||
be := db.getCachedValue(round)
|
||||
if be != nil {
|
||||
out <- beacon.Response{Entry: *be}
|
||||
close(out)
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
start := time.Now()
|
||||
log.Infow("start fetching randomness", "round", round)
|
||||
resp, err := db.client.Get(ctx, round)
|
||||
|
||||
var br beacon.Response
|
||||
if err != nil {
|
||||
br.Err = xerrors.Errorf("drand failed Get request: %w", err)
|
||||
} else {
|
||||
br.Entry.Round = resp.Round()
|
||||
br.Entry.Data = resp.Signature()
|
||||
}
|
||||
log.Infow("done fetching randomness", "round", round, "took", time.Since(start))
|
||||
out <- br
|
||||
close(out)
|
||||
}()
|
||||
|
||||
return out
|
||||
}
|
||||
func (db *DrandBeacon) cacheValue(e types.BeaconEntry) {
|
||||
db.cacheLk.Lock()
|
||||
defer db.cacheLk.Unlock()
|
||||
db.localCache[e.Round] = e
|
||||
}
|
||||
|
||||
func (db *DrandBeacon) getCachedValue(round uint64) *types.BeaconEntry {
|
||||
db.cacheLk.Lock()
|
||||
defer db.cacheLk.Unlock()
|
||||
v, ok := db.localCache[round]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return &v
|
||||
}
|
||||
|
||||
func (db *DrandBeacon) VerifyEntry(curr types.BeaconEntry, prev types.BeaconEntry) error {
|
||||
if prev.Round == 0 {
|
||||
// TODO handle genesis better
|
||||
return nil
|
||||
}
|
||||
b := &dchain.Beacon{
|
||||
PreviousSig: prev.Data,
|
||||
Round: curr.Round,
|
||||
Signature: curr.Data,
|
||||
}
|
||||
err := dchain.VerifyBeacon(db.pubkey, b)
|
||||
if err == nil {
|
||||
db.cacheValue(curr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DrandBeacon) MaxBeaconRoundForEpoch(filEpoch abi.ChainEpoch, prevEntry types.BeaconEntry) uint64 {
|
||||
// TODO: sometimes the genesis time for filecoin is zero and this goes negative
|
||||
latestTs := ((uint64(filEpoch) * db.filRoundTime) + db.filGenTime) - db.filRoundTime
|
||||
dround := (latestTs - db.drandGenTime) / uint64(db.interval.Seconds())
|
||||
return dround
|
||||
}
|
||||
|
||||
var _ beacon.RandomBeacon = (*DrandBeacon)(nil)
|
||||
@@ -0,0 +1,25 @@
|
||||
package drand
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
dchain "github.com/drand/drand/chain"
|
||||
hclient "github.com/drand/drand/client/http"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
)
|
||||
|
||||
func TestPrintGroupInfo(t *testing.T) {
|
||||
server := build.DrandConfig.Servers[0]
|
||||
c, err := hclient.New(server, nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cg := c.(interface {
|
||||
FetchChainInfo(groupHash []byte) (*dchain.Info, error)
|
||||
})
|
||||
chain, err := cg.FetchChainInfo(nil)
|
||||
assert.NoError(t, err)
|
||||
err = chain.ToJSON(os.Stdout)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/minio/blake2b-simd"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Mock beacon assumes that filecoin rounds are 1:1 mapped with the beacon rounds
|
||||
type mockBeacon struct {
|
||||
interval time.Duration
|
||||
}
|
||||
|
||||
func NewMockBeacon(interval time.Duration) RandomBeacon {
|
||||
mb := &mockBeacon{interval: interval}
|
||||
|
||||
return mb
|
||||
}
|
||||
|
||||
func (mb *mockBeacon) RoundTime() time.Duration {
|
||||
return mb.interval
|
||||
}
|
||||
|
||||
func (mb *mockBeacon) entryForIndex(index uint64) types.BeaconEntry {
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, index)
|
||||
rval := blake2b.Sum256(buf)
|
||||
return types.BeaconEntry{
|
||||
Round: index,
|
||||
Data: rval[:],
|
||||
}
|
||||
}
|
||||
|
||||
func (mb *mockBeacon) Entry(ctx context.Context, index uint64) <-chan Response {
|
||||
e := mb.entryForIndex(index)
|
||||
out := make(chan Response, 1)
|
||||
out <- Response{Entry: e}
|
||||
return out
|
||||
}
|
||||
|
||||
func (mb *mockBeacon) VerifyEntry(from types.BeaconEntry, to types.BeaconEntry) error {
|
||||
// TODO: cache this, especially for bls
|
||||
oe := mb.entryForIndex(from.Round)
|
||||
if !bytes.Equal(from.Data, oe.Data) {
|
||||
return xerrors.Errorf("mock beacon entry was invalid!")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mb *mockBeacon) IsEntryForEpoch(e types.BeaconEntry, epoch abi.ChainEpoch, nulls int) (bool, error) {
|
||||
return int64(e.Round) <= int64(epoch) && int64(epoch)-int64(nulls) >= int64(e.Round), nil
|
||||
}
|
||||
|
||||
func (mb *mockBeacon) MaxBeaconRoundForEpoch(epoch abi.ChainEpoch, prevEntry types.BeaconEntry) uint64 {
|
||||
return uint64(epoch)
|
||||
}
|
||||
|
||||
var _ RandomBeacon = (*mockBeacon)(nil)
|
||||
@@ -3,18 +3,19 @@ package blocksync
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
"go.opencensus.io/trace"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
cborutil "github.com/filecoin-project/go-cbor-util"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/cborutil"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
logging "github.com/ipfs/go-log"
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
inet "github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
)
|
||||
@@ -25,12 +26,26 @@ type NewStreamFunc func(context.Context, peer.ID, ...protocol.ID) (inet.Stream,
|
||||
|
||||
const BlockSyncProtocolID = "/fil/sync/blk/0.0.1"
|
||||
|
||||
func init() {
|
||||
cbor.RegisterCborType(BlockSyncRequest{})
|
||||
cbor.RegisterCborType(BlockSyncResponse{})
|
||||
cbor.RegisterCborType(BSTipSet{})
|
||||
}
|
||||
const BlockSyncMaxRequestLength = 800
|
||||
|
||||
// BlockSyncService is the component that services BlockSync requests from
|
||||
// peers.
|
||||
//
|
||||
// BlockSync is the basic chain synchronization protocol of Filecoin. BlockSync
|
||||
// is an RPC-oriented protocol, with a single operation to request blocks.
|
||||
//
|
||||
// A request contains a start anchor block (referred to with a CID), and a
|
||||
// amount of blocks requested beyond the anchor (including the anchor itself).
|
||||
//
|
||||
// A client can also pass options, encoded as a 64-bit bitfield. Lotus supports
|
||||
// two options at the moment:
|
||||
//
|
||||
// - include block contents
|
||||
// - include block messages
|
||||
//
|
||||
// The response will include a status code, an optional message, and the
|
||||
// response payload in case of success. The payload is a slice of serialized
|
||||
// tipsets.
|
||||
type BlockSyncService struct {
|
||||
cs *store.ChainStore
|
||||
}
|
||||
@@ -55,8 +70,17 @@ func ParseBSOptions(optfield uint64) *BSOptions {
|
||||
}
|
||||
|
||||
const (
|
||||
BSOptBlocks = 1 << 0
|
||||
BSOptMessages = 1 << 1
|
||||
BSOptBlocks = 1 << iota
|
||||
BSOptMessages
|
||||
)
|
||||
|
||||
const (
|
||||
StatusOK = uint64(0)
|
||||
StatusPartial = uint64(101)
|
||||
StatusNotFound = uint64(201)
|
||||
StatusGoAway = uint64(202)
|
||||
StatusInternalError = uint64(203)
|
||||
StatusBadRequest = uint64(204)
|
||||
)
|
||||
|
||||
type BlockSyncResponse struct {
|
||||
@@ -86,35 +110,37 @@ func (bss *BlockSyncService) HandleStream(s inet.Stream) {
|
||||
ctx, span := trace.StartSpan(context.Background(), "blocksync.HandleStream")
|
||||
defer span.End()
|
||||
|
||||
defer s.Close()
|
||||
defer s.Close() //nolint:errcheck
|
||||
|
||||
var req BlockSyncRequest
|
||||
if err := cborutil.ReadCborRPC(bufio.NewReader(s), &req); err != nil {
|
||||
log.Warnf("failed to read block sync request: %s", err)
|
||||
return
|
||||
}
|
||||
log.Infof("block sync request for: %s %d", req.Start, req.RequestLength)
|
||||
log.Infow("block sync request", "start", req.Start, "len", req.RequestLength)
|
||||
|
||||
resp, err := bss.processRequest(ctx, &req)
|
||||
resp, err := bss.processRequest(ctx, s.Conn().RemotePeer(), &req)
|
||||
if err != nil {
|
||||
log.Warn("failed to process block sync request: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
writeDeadline := 60 * time.Second
|
||||
_ = s.SetDeadline(time.Now().Add(writeDeadline))
|
||||
if err := cborutil.WriteCborRPC(s, resp); err != nil {
|
||||
log.Warn("failed to write back response for handle stream: ", err)
|
||||
log.Warnw("failed to write back response for handle stream", "err", err, "peer", s.Conn().RemotePeer())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (bss *BlockSyncService) processRequest(ctx context.Context, req *BlockSyncRequest) (*BlockSyncResponse, error) {
|
||||
func (bss *BlockSyncService) processRequest(ctx context.Context, p peer.ID, req *BlockSyncRequest) (*BlockSyncResponse, error) {
|
||||
_, span := trace.StartSpan(ctx, "blocksync.ProcessRequest")
|
||||
defer span.End()
|
||||
|
||||
opts := ParseBSOptions(req.Options)
|
||||
if len(req.Start) == 0 {
|
||||
return &BlockSyncResponse{
|
||||
Status: 204,
|
||||
Status: StatusBadRequest,
|
||||
Message: "no cids given in blocksync request",
|
||||
}, nil
|
||||
}
|
||||
@@ -122,35 +148,47 @@ func (bss *BlockSyncService) processRequest(ctx context.Context, req *BlockSyncR
|
||||
span.AddAttributes(
|
||||
trace.BoolAttribute("blocks", opts.IncludeBlocks),
|
||||
trace.BoolAttribute("messages", opts.IncludeMessages),
|
||||
trace.Int64Attribute("reqlen", int64(req.RequestLength)),
|
||||
)
|
||||
|
||||
chain, err := bss.collectChainSegment(types.NewTipSetKey(req.Start...), req.RequestLength, opts)
|
||||
reqlen := req.RequestLength
|
||||
if reqlen > BlockSyncMaxRequestLength {
|
||||
log.Warnw("limiting blocksync request length", "orig", req.RequestLength, "peer", p)
|
||||
reqlen = BlockSyncMaxRequestLength
|
||||
}
|
||||
|
||||
chain, err := collectChainSegment(bss.cs, types.NewTipSetKey(req.Start...), reqlen, opts)
|
||||
if err != nil {
|
||||
log.Warn("encountered error while responding to block sync request: ", err)
|
||||
return &BlockSyncResponse{
|
||||
Status: 203,
|
||||
Status: StatusInternalError,
|
||||
Message: err.Error(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
status := StatusOK
|
||||
if reqlen < req.RequestLength {
|
||||
status = StatusPartial
|
||||
}
|
||||
|
||||
return &BlockSyncResponse{
|
||||
Chain: chain,
|
||||
Status: 0,
|
||||
Status: status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (bss *BlockSyncService) collectChainSegment(start types.TipSetKey, length uint64, opts *BSOptions) ([]*BSTipSet, error) {
|
||||
func collectChainSegment(cs *store.ChainStore, start types.TipSetKey, length uint64, opts *BSOptions) ([]*BSTipSet, error) {
|
||||
var bstips []*BSTipSet
|
||||
cur := start
|
||||
for {
|
||||
var bst BSTipSet
|
||||
ts, err := bss.cs.LoadTipSet(cur)
|
||||
ts, err := cs.LoadTipSet(cur)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed loading tipset %s: %w", cur, err)
|
||||
}
|
||||
|
||||
if opts.IncludeMessages {
|
||||
bmsgs, bmincl, smsgs, smincl, err := bss.gatherMessages(ts)
|
||||
bmsgs, bmincl, smsgs, smincl, err := gatherMessages(cs, ts)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("gather messages failed: %w", err)
|
||||
}
|
||||
@@ -175,7 +213,7 @@ func (bss *BlockSyncService) collectChainSegment(start types.TipSetKey, length u
|
||||
}
|
||||
}
|
||||
|
||||
func (bss *BlockSyncService) gatherMessages(ts *types.TipSet) ([]*types.Message, [][]uint64, []*types.SignedMessage, [][]uint64, error) {
|
||||
func gatherMessages(cs *store.ChainStore, ts *types.TipSet) ([]*types.Message, [][]uint64, []*types.SignedMessage, [][]uint64, error) {
|
||||
blsmsgmap := make(map[cid.Cid]uint64)
|
||||
secpkmsgmap := make(map[cid.Cid]uint64)
|
||||
var secpkmsgs []*types.SignedMessage
|
||||
@@ -183,7 +221,7 @@ func (bss *BlockSyncService) gatherMessages(ts *types.TipSet) ([]*types.Message,
|
||||
var secpkincl, blsincl [][]uint64
|
||||
|
||||
for _, b := range ts.Blocks() {
|
||||
bmsgs, smsgs, err := bss.cs.MessagesForBlock(b)
|
||||
bmsgs, smsgs, err := cs.MessagesForBlock(b)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
@@ -12,53 +12,63 @@ import (
|
||||
blocks "github.com/ipfs/go-block-format"
|
||||
bserv "github.com/ipfs/go-blockservice"
|
||||
"github.com/ipfs/go-cid"
|
||||
graphsync "github.com/ipfs/go-graphsync"
|
||||
gsnet "github.com/ipfs/go-graphsync/network"
|
||||
host "github.com/libp2p/go-libp2p-core/host"
|
||||
inet "github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"go.opencensus.io/trace"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
cborutil "github.com/filecoin-project/go-cbor-util"
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/cborutil"
|
||||
incrt "github.com/filecoin-project/lotus/lib/increadtimeout"
|
||||
"github.com/filecoin-project/lotus/lib/peermgr"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
"github.com/filecoin-project/lotus/peermgr"
|
||||
)
|
||||
|
||||
type BlockSync struct {
|
||||
bserv bserv.BlockService
|
||||
gsync graphsync.GraphExchange
|
||||
host host.Host
|
||||
|
||||
syncPeers *bsPeerTracker
|
||||
peerMgr *peermgr.PeerMgr
|
||||
}
|
||||
|
||||
func NewBlockSyncClient(bserv dtypes.ChainBlockService, h host.Host, pmgr peermgr.MaybePeerMgr) *BlockSync {
|
||||
func NewBlockSyncClient(bserv dtypes.ChainBlockService, h host.Host, pmgr peermgr.MaybePeerMgr, gs dtypes.Graphsync) *BlockSync {
|
||||
return &BlockSync{
|
||||
bserv: bserv,
|
||||
host: h,
|
||||
syncPeers: newPeerTracker(pmgr.Mgr),
|
||||
peerMgr: pmgr.Mgr,
|
||||
gsync: gs,
|
||||
}
|
||||
}
|
||||
|
||||
func (bs *BlockSync) processStatus(req *BlockSyncRequest, res *BlockSyncResponse) error {
|
||||
switch res.Status {
|
||||
case 101: // Partial Response
|
||||
case StatusPartial: // Partial Response
|
||||
return xerrors.Errorf("not handling partial blocksync responses yet")
|
||||
case 201: // req.Start not found
|
||||
case StatusNotFound: // req.Start not found
|
||||
return xerrors.Errorf("not found")
|
||||
case 202: // Go Away
|
||||
case StatusGoAway: // Go Away
|
||||
return xerrors.Errorf("not handling 'go away' blocksync responses yet")
|
||||
case 203: // Internal Error
|
||||
case StatusInternalError: // Internal Error
|
||||
return xerrors.Errorf("block sync peer errored: %s", res.Message)
|
||||
case 204:
|
||||
case StatusBadRequest:
|
||||
return xerrors.Errorf("block sync request invalid: %s", res.Message)
|
||||
default:
|
||||
return xerrors.Errorf("unrecognized response code: %d", res.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// GetBlocks fetches count blocks from the network, from the provided tipset
|
||||
// *backwards*, returning as many tipsets as count.
|
||||
//
|
||||
// {hint/usage}: This is used by the Syncer during normal chain syncing and when
|
||||
// resolving forks.
|
||||
func (bs *BlockSync) GetBlocks(ctx context.Context, tsk types.TipSetKey, count int) ([]*types.TipSet, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "bsync.GetBlocks")
|
||||
defer span.End()
|
||||
@@ -75,7 +85,9 @@ func (bs *BlockSync) GetBlocks(ctx context.Context, tsk types.TipSetKey, count i
|
||||
Options: BSOptBlocks,
|
||||
}
|
||||
|
||||
// this peerset is sorted by latency and failure counting.
|
||||
peers := bs.getPeers()
|
||||
|
||||
// randomize the first few peers so we don't always pick the same peer
|
||||
shufflePrefix(peers)
|
||||
|
||||
@@ -100,7 +112,7 @@ func (bs *BlockSync) GetBlocks(ctx context.Context, tsk types.TipSetKey, count i
|
||||
continue
|
||||
}
|
||||
|
||||
if res.Status == 0 {
|
||||
if res.Status == StatusOK || res.Status == StatusPartial {
|
||||
resp, err := bs.processBlocksResponse(req, res)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("success response from peer failed to process: %w", err)
|
||||
@@ -109,6 +121,7 @@ func (bs *BlockSync) GetBlocks(ctx context.Context, tsk types.TipSetKey, count i
|
||||
bs.host.ConnManager().TagPeer(p, "bsync", 25)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
oerr = bs.processStatus(req, res)
|
||||
if oerr != nil {
|
||||
log.Warnf("BlockSync peer %s response was an error: %s", p.String(), oerr)
|
||||
@@ -140,7 +153,7 @@ func (bs *BlockSync) GetFullTipSet(ctx context.Context, p peer.ID, tsk types.Tip
|
||||
|
||||
return bstsToFullTipSet(bts)
|
||||
case 101: // Partial Response
|
||||
return nil, xerrors.Errorf("partial responses are not handled")
|
||||
return nil, xerrors.Errorf("partial responses are not handled for single tipset fetching")
|
||||
case 201: // req.Start not found
|
||||
return nil, fmt.Errorf("not found")
|
||||
case 202: // Go Away
|
||||
@@ -180,7 +193,7 @@ func (bs *BlockSync) GetChainMessages(ctx context.Context, h *types.TipSet, coun
|
||||
req := &BlockSyncRequest{
|
||||
Start: h.Cids(),
|
||||
RequestLength: count,
|
||||
Options: BSOptMessages | BSOptBlocks,
|
||||
Options: BSOptMessages,
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -194,11 +207,16 @@ func (bs *BlockSync) GetChainMessages(ctx context.Context, h *types.TipSet, coun
|
||||
continue
|
||||
}
|
||||
|
||||
if res.Status == 0 {
|
||||
if res.Status == StatusOK {
|
||||
bs.syncPeers.logGlobalSuccess(time.Since(start))
|
||||
return res.Chain, nil
|
||||
}
|
||||
|
||||
if res.Status == StatusPartial {
|
||||
// TODO: track partial response sizes to ensure we don't overrequest too often
|
||||
return res.Chain, nil
|
||||
}
|
||||
|
||||
err = bs.processStatus(req, res)
|
||||
if err != nil {
|
||||
log.Warnf("BlockSync peer %s response was an error: %s", p.String(), err)
|
||||
@@ -228,29 +246,62 @@ func (bs *BlockSync) sendRequestToPeer(ctx context.Context, p peer.ID, req *Bloc
|
||||
}
|
||||
}()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
if span.IsRecordingEvents() {
|
||||
span.AddAttributes(
|
||||
trace.StringAttribute("peer", p.Pretty()),
|
||||
)
|
||||
}
|
||||
|
||||
gsproto := string(gsnet.ProtocolGraphsync)
|
||||
supp, err := bs.host.Peerstore().SupportsProtocols(p, BlockSyncProtocolID, gsproto)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get protocols for peer: %w", err)
|
||||
}
|
||||
|
||||
if len(supp) == 0 {
|
||||
return nil, xerrors.Errorf("peer %s supports no known sync protocols", p)
|
||||
}
|
||||
|
||||
switch supp[0] {
|
||||
case BlockSyncProtocolID:
|
||||
res, err := bs.fetchBlocksBlockSync(ctx, p, req)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("blocksync req failed: %w", err)
|
||||
}
|
||||
return res, nil
|
||||
case gsproto:
|
||||
res, err := bs.fetchBlocksGraphSync(ctx, p, req)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("graphsync req failed: %w", err)
|
||||
}
|
||||
return res, nil
|
||||
default:
|
||||
return nil, xerrors.Errorf("peerstore somehow returned unexpected protocols: %v", supp)
|
||||
}
|
||||
|
||||
}
|
||||
func (bs *BlockSync) fetchBlocksBlockSync(ctx context.Context, p peer.ID, req *BlockSyncRequest) (*BlockSyncResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockSyncFetch")
|
||||
defer span.End()
|
||||
|
||||
start := time.Now()
|
||||
s, err := bs.host.NewStream(inet.WithNoDial(ctx, "should already have connection"), p, BlockSyncProtocolID)
|
||||
if err != nil {
|
||||
bs.RemovePeer(p)
|
||||
return nil, xerrors.Errorf("failed to open stream to peer: %w", err)
|
||||
}
|
||||
s.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
defer s.SetDeadline(time.Time{})
|
||||
_ = s.SetWriteDeadline(time.Now().Add(5 * time.Second))
|
||||
|
||||
if err := cborutil.WriteCborRPC(s, req); err != nil {
|
||||
_ = s.SetWriteDeadline(time.Time{})
|
||||
bs.syncPeers.logFailure(p, time.Since(start))
|
||||
return nil, err
|
||||
}
|
||||
_ = s.SetWriteDeadline(time.Time{})
|
||||
|
||||
var res BlockSyncResponse
|
||||
if err := cborutil.ReadCborRPC(bufio.NewReader(s), &res); err != nil {
|
||||
r := incrt.New(s, 50<<10, 5*time.Second)
|
||||
if err := cborutil.ReadCborRPC(bufio.NewReader(r), &res); err != nil {
|
||||
bs.syncPeers.logFailure(p, time.Since(start))
|
||||
return nil, err
|
||||
}
|
||||
@@ -264,7 +315,6 @@ func (bs *BlockSync) sendRequestToPeer(ctx context.Context, p peer.ID, req *Bloc
|
||||
}
|
||||
|
||||
bs.syncPeers.logSuccess(p, time.Since(start))
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
@@ -313,6 +363,7 @@ func (bs *BlockSync) RemovePeer(p peer.ID) {
|
||||
bs.syncPeers.removePeer(p)
|
||||
}
|
||||
|
||||
// getPeers returns a preference-sorted set of peers to query.
|
||||
func (bs *BlockSync) getPeers() []peer.ID {
|
||||
return bs.syncPeers.prefSortedPeers()
|
||||
}
|
||||
@@ -455,7 +506,7 @@ func (bpt *bsPeerTracker) prefSortedPeers() []peer.ID {
|
||||
getPeerInitLat := func(p peer.ID) float64 {
|
||||
var res float64
|
||||
if bpt.pmgr != nil {
|
||||
if lat, ok := bpt.pmgr.GetPeerLatency(out[i]); ok {
|
||||
if lat, ok := bpt.pmgr.GetPeerLatency(p); ok {
|
||||
res = float64(lat)
|
||||
}
|
||||
}
|
||||
@@ -476,7 +527,7 @@ func (bpt *bsPeerTracker) prefSortedPeers() []peer.ID {
|
||||
failRateJ := float64(pj.failures) / float64(pj.failures+pj.successes)
|
||||
costJ = float64(pj.averageTime) + failRateJ*float64(bpt.avgGlobalTime)
|
||||
} else {
|
||||
costI = getPeerInitLat(out[i])
|
||||
costJ = getPeerInitLat(out[j])
|
||||
}
|
||||
|
||||
return costI < costJ
|
||||
@@ -518,26 +569,30 @@ func (bpt *bsPeerTracker) logSuccess(p peer.ID, dur time.Duration) {
|
||||
bpt.lk.Lock()
|
||||
defer bpt.lk.Unlock()
|
||||
|
||||
if pi, ok := bpt.peers[p]; !ok {
|
||||
var pi *peerStats
|
||||
var ok bool
|
||||
if pi, ok = bpt.peers[p]; !ok {
|
||||
log.Warnw("log success called on peer not in tracker", "peerid", p.String())
|
||||
return
|
||||
} else {
|
||||
pi.successes++
|
||||
|
||||
logTime(pi, dur)
|
||||
}
|
||||
|
||||
pi.successes++
|
||||
logTime(pi, dur)
|
||||
}
|
||||
|
||||
func (bpt *bsPeerTracker) logFailure(p peer.ID, dur time.Duration) {
|
||||
bpt.lk.Lock()
|
||||
defer bpt.lk.Unlock()
|
||||
if pi, ok := bpt.peers[p]; !ok {
|
||||
|
||||
var pi *peerStats
|
||||
var ok bool
|
||||
if pi, ok = bpt.peers[p]; !ok {
|
||||
log.Warn("log failure called on peer not in tracker", "peerid", p.String())
|
||||
return
|
||||
} else {
|
||||
pi.failures++
|
||||
logTime(pi, dur)
|
||||
}
|
||||
|
||||
pi.failures++
|
||||
logTime(pi, dur)
|
||||
}
|
||||
|
||||
func (bpt *bsPeerTracker) removePeer(p peer.ID) {
|
||||
|
||||
+110
-61
@@ -1,3 +1,5 @@
|
||||
// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT.
|
||||
|
||||
package blocksync
|
||||
|
||||
import (
|
||||
@@ -5,54 +7,60 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
cid "github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-cid"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
xerrors "golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT.
|
||||
|
||||
var _ = xerrors.Errorf
|
||||
|
||||
var lengthBufBlockSyncRequest = []byte{131}
|
||||
|
||||
func (t *BlockSyncRequest) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{131}); err != nil {
|
||||
if _, err := w.Write(lengthBufBlockSyncRequest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scratch := make([]byte, 9)
|
||||
|
||||
// t.Start ([]cid.Cid) (slice)
|
||||
if len(t.Start) > cbg.MaxLength {
|
||||
return xerrors.Errorf("Slice value in field t.Start was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajArray, uint64(len(t.Start)))); err != nil {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Start))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range t.Start {
|
||||
if err := cbg.WriteCid(w, v); err != nil {
|
||||
if err := cbg.WriteCidBuf(scratch, w, v); err != nil {
|
||||
return xerrors.Errorf("failed writing cid field t.Start: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// t.RequestLength (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.RequestLength))); err != nil {
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.RequestLength)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Options (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.Options))); err != nil {
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Options)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *BlockSyncRequest) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
scratch := make([]byte, 8)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
maj, extra, err := cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -66,7 +74,7 @@ func (t *BlockSyncRequest) UnmarshalCBOR(r io.Reader) error {
|
||||
|
||||
// t.Start ([]cid.Cid) (slice)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -78,9 +86,11 @@ func (t *BlockSyncRequest) UnmarshalCBOR(r io.Reader) error {
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
|
||||
if extra > 0 {
|
||||
t.Start = make([]cid.Cid, extra)
|
||||
}
|
||||
|
||||
for i := 0; i < int(extra); i++ {
|
||||
|
||||
c, err := cbg.ReadCid(br)
|
||||
@@ -92,42 +102,54 @@ func (t *BlockSyncRequest) UnmarshalCBOR(r io.Reader) error {
|
||||
|
||||
// t.RequestLength (uint64) (uint64)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
{
|
||||
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.RequestLength = uint64(extra)
|
||||
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.RequestLength = uint64(extra)
|
||||
// t.Options (uint64) (uint64)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
{
|
||||
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.Options = uint64(extra)
|
||||
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.Options = uint64(extra)
|
||||
return nil
|
||||
}
|
||||
|
||||
var lengthBufBlockSyncResponse = []byte{131}
|
||||
|
||||
func (t *BlockSyncResponse) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{131}); err != nil {
|
||||
if _, err := w.Write(lengthBufBlockSyncResponse); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scratch := make([]byte, 9)
|
||||
|
||||
// t.Chain ([]*blocksync.BSTipSet) (slice)
|
||||
if len(t.Chain) > cbg.MaxLength {
|
||||
return xerrors.Errorf("Slice value in field t.Chain was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajArray, uint64(len(t.Chain)))); err != nil {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Chain))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range t.Chain {
|
||||
@@ -137,7 +159,8 @@ func (t *BlockSyncResponse) MarshalCBOR(w io.Writer) error {
|
||||
}
|
||||
|
||||
// t.Status (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.Status))); err != nil {
|
||||
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Status)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -146,10 +169,10 @@ func (t *BlockSyncResponse) MarshalCBOR(w io.Writer) error {
|
||||
return xerrors.Errorf("Value in field t.Message was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len(t.Message)))); err != nil {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Message))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte(t.Message)); err != nil {
|
||||
if _, err := io.WriteString(w, t.Message); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -157,8 +180,9 @@ func (t *BlockSyncResponse) MarshalCBOR(w io.Writer) error {
|
||||
|
||||
func (t *BlockSyncResponse) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
scratch := make([]byte, 8)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
maj, extra, err := cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -172,7 +196,7 @@ func (t *BlockSyncResponse) UnmarshalCBOR(r io.Reader) error {
|
||||
|
||||
// t.Chain ([]*blocksync.BSTipSet) (slice)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -184,9 +208,11 @@ func (t *BlockSyncResponse) UnmarshalCBOR(r io.Reader) error {
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
|
||||
if extra > 0 {
|
||||
t.Chain = make([]*BSTipSet, extra)
|
||||
}
|
||||
|
||||
for i := 0; i < int(extra); i++ {
|
||||
|
||||
var v BSTipSet
|
||||
@@ -199,18 +225,22 @@ func (t *BlockSyncResponse) UnmarshalCBOR(r io.Reader) error {
|
||||
|
||||
// t.Status (uint64) (uint64)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
{
|
||||
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.Status = uint64(extra)
|
||||
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.Status = uint64(extra)
|
||||
// t.Message (string) (string)
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadString(br)
|
||||
sval, err := cbg.ReadStringBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -220,21 +250,25 @@ func (t *BlockSyncResponse) UnmarshalCBOR(r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var lengthBufBSTipSet = []byte{133}
|
||||
|
||||
func (t *BSTipSet) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{133}); err != nil {
|
||||
if _, err := w.Write(lengthBufBSTipSet); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scratch := make([]byte, 9)
|
||||
|
||||
// t.Blocks ([]*types.BlockHeader) (slice)
|
||||
if len(t.Blocks) > cbg.MaxLength {
|
||||
return xerrors.Errorf("Slice value in field t.Blocks was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajArray, uint64(len(t.Blocks)))); err != nil {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Blocks))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range t.Blocks {
|
||||
@@ -248,7 +282,7 @@ func (t *BSTipSet) MarshalCBOR(w io.Writer) error {
|
||||
return xerrors.Errorf("Slice value in field t.BlsMessages was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajArray, uint64(len(t.BlsMessages)))); err != nil {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.BlsMessages))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range t.BlsMessages {
|
||||
@@ -262,7 +296,7 @@ func (t *BSTipSet) MarshalCBOR(w io.Writer) error {
|
||||
return xerrors.Errorf("Slice value in field t.BlsMsgIncludes was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajArray, uint64(len(t.BlsMsgIncludes)))); err != nil {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.BlsMsgIncludes))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range t.BlsMsgIncludes {
|
||||
@@ -270,11 +304,11 @@ func (t *BSTipSet) MarshalCBOR(w io.Writer) error {
|
||||
return xerrors.Errorf("Slice value in field v was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajArray, uint64(len(v)))); err != nil {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(v))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range v {
|
||||
if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, v); err != nil {
|
||||
if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -285,7 +319,7 @@ func (t *BSTipSet) MarshalCBOR(w io.Writer) error {
|
||||
return xerrors.Errorf("Slice value in field t.SecpkMessages was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajArray, uint64(len(t.SecpkMessages)))); err != nil {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.SecpkMessages))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range t.SecpkMessages {
|
||||
@@ -299,7 +333,7 @@ func (t *BSTipSet) MarshalCBOR(w io.Writer) error {
|
||||
return xerrors.Errorf("Slice value in field t.SecpkMsgIncludes was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajArray, uint64(len(t.SecpkMsgIncludes)))); err != nil {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.SecpkMsgIncludes))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range t.SecpkMsgIncludes {
|
||||
@@ -307,11 +341,11 @@ func (t *BSTipSet) MarshalCBOR(w io.Writer) error {
|
||||
return xerrors.Errorf("Slice value in field v was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajArray, uint64(len(v)))); err != nil {
|
||||
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(v))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range v {
|
||||
if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, v); err != nil {
|
||||
if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -321,8 +355,9 @@ func (t *BSTipSet) MarshalCBOR(w io.Writer) error {
|
||||
|
||||
func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
scratch := make([]byte, 8)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
maj, extra, err := cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -336,7 +371,7 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
|
||||
// t.Blocks ([]*types.BlockHeader) (slice)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -348,9 +383,11 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
|
||||
if extra > 0 {
|
||||
t.Blocks = make([]*types.BlockHeader, extra)
|
||||
}
|
||||
|
||||
for i := 0; i < int(extra); i++ {
|
||||
|
||||
var v types.BlockHeader
|
||||
@@ -363,7 +400,7 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
|
||||
// t.BlsMessages ([]*types.Message) (slice)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -375,9 +412,11 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
|
||||
if extra > 0 {
|
||||
t.BlsMessages = make([]*types.Message, extra)
|
||||
}
|
||||
|
||||
for i := 0; i < int(extra); i++ {
|
||||
|
||||
var v types.Message
|
||||
@@ -390,7 +429,7 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
|
||||
// t.BlsMsgIncludes ([][]uint64) (slice)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -402,16 +441,18 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
|
||||
if extra > 0 {
|
||||
t.BlsMsgIncludes = make([][]uint64, extra)
|
||||
}
|
||||
|
||||
for i := 0; i < int(extra); i++ {
|
||||
{
|
||||
var maj byte
|
||||
var extra uint64
|
||||
var err error
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -423,12 +464,14 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
|
||||
if extra > 0 {
|
||||
t.BlsMsgIncludes[i] = make([]uint64, extra)
|
||||
}
|
||||
|
||||
for j := 0; j < int(extra); j++ {
|
||||
|
||||
maj, val, err := cbg.CborReadHeader(br)
|
||||
maj, val, err := cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to read uint64 for t.BlsMsgIncludes[i] slice: %w", err)
|
||||
}
|
||||
@@ -437,7 +480,7 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
return xerrors.Errorf("value read for array t.BlsMsgIncludes[i] was not a uint, instead got %d", maj)
|
||||
}
|
||||
|
||||
t.BlsMsgIncludes[i][j] = val
|
||||
t.BlsMsgIncludes[i][j] = uint64(val)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -445,7 +488,7 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
|
||||
// t.SecpkMessages ([]*types.SignedMessage) (slice)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -457,9 +500,11 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
|
||||
if extra > 0 {
|
||||
t.SecpkMessages = make([]*types.SignedMessage, extra)
|
||||
}
|
||||
|
||||
for i := 0; i < int(extra); i++ {
|
||||
|
||||
var v types.SignedMessage
|
||||
@@ -472,7 +517,7 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
|
||||
// t.SecpkMsgIncludes ([][]uint64) (slice)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -484,16 +529,18 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
|
||||
if extra > 0 {
|
||||
t.SecpkMsgIncludes = make([][]uint64, extra)
|
||||
}
|
||||
|
||||
for i := 0; i < int(extra); i++ {
|
||||
{
|
||||
var maj byte
|
||||
var extra uint64
|
||||
var err error
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -505,12 +552,14 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
|
||||
if extra > 0 {
|
||||
t.SecpkMsgIncludes[i] = make([]uint64, extra)
|
||||
}
|
||||
|
||||
for j := 0; j < int(extra); j++ {
|
||||
|
||||
maj, val, err := cbg.CborReadHeader(br)
|
||||
maj, val, err := cbg.CborReadHeaderBuf(br, scratch)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to read uint64 for t.SecpkMsgIncludes[i] slice: %w", err)
|
||||
}
|
||||
@@ -519,7 +568,7 @@ func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error {
|
||||
return xerrors.Errorf("value read for array t.SecpkMsgIncludes[i] was not a uint, instead got %d", maj)
|
||||
}
|
||||
|
||||
t.SecpkMsgIncludes[i][j] = val
|
||||
t.SecpkMsgIncludes[i][j] = uint64(val)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package blocksync
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-datastore"
|
||||
"github.com/ipfs/go-graphsync"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
store "github.com/filecoin-project/lotus/chain/store"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
|
||||
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
|
||||
basicnode "github.com/ipld/go-ipld-prime/node/basic"
|
||||
ipldselector "github.com/ipld/go-ipld-prime/traversal/selector"
|
||||
selectorbuilder "github.com/ipld/go-ipld-prime/traversal/selector/builder"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// AMT selector recursion. An AMT has arity of 8 so this gives allows
|
||||
// us to retrieve trees with 8^10 (1,073,741,824) elements.
|
||||
amtRecursionDepth = uint32(10)
|
||||
|
||||
// some constants for looking up tuple encoded struct fields
|
||||
// field index of Parents field in a block header
|
||||
blockIndexParentsField = 5
|
||||
|
||||
// field index of Messages field in a block header
|
||||
blockIndexMessagesField = 10
|
||||
|
||||
// field index of AMT node in AMT head
|
||||
amtHeadNodeFieldIndex = 2
|
||||
|
||||
// field index of links array AMT node
|
||||
amtNodeLinksFieldIndex = 1
|
||||
|
||||
// field index of values array AMT node
|
||||
amtNodeValuesFieldIndex = 2
|
||||
|
||||
// maximum depth per traversal
|
||||
maxRequestLength = 50
|
||||
)
|
||||
|
||||
var amtSelector selectorbuilder.SelectorSpec
|
||||
|
||||
func init() {
|
||||
// builer for selectors
|
||||
ssb := selectorbuilder.NewSelectorSpecBuilder(basicnode.Style.Any)
|
||||
// amt selector -- needed to selector through a messages AMT
|
||||
amtSelector = ssb.ExploreIndex(amtHeadNodeFieldIndex,
|
||||
ssb.ExploreRecursive(ipldselector.RecursionLimitDepth(int(amtRecursionDepth)),
|
||||
ssb.ExploreUnion(
|
||||
ssb.ExploreIndex(amtNodeLinksFieldIndex,
|
||||
ssb.ExploreAll(ssb.ExploreRecursiveEdge())),
|
||||
ssb.ExploreIndex(amtNodeValuesFieldIndex,
|
||||
ssb.ExploreAll(ssb.Matcher())))))
|
||||
}
|
||||
|
||||
func selectorForRequest(req *BlockSyncRequest) ipld.Node {
|
||||
// builer for selectors
|
||||
ssb := selectorbuilder.NewSelectorSpecBuilder(basicnode.Style.Any)
|
||||
|
||||
bso := ParseBSOptions(req.Options)
|
||||
if bso.IncludeMessages {
|
||||
return ssb.ExploreRecursive(ipldselector.RecursionLimitDepth(int(req.RequestLength)),
|
||||
ssb.ExploreIndex(blockIndexParentsField,
|
||||
ssb.ExploreUnion(
|
||||
ssb.ExploreAll(
|
||||
ssb.ExploreIndex(blockIndexMessagesField,
|
||||
ssb.ExploreRange(0, 2, amtSelector),
|
||||
)),
|
||||
ssb.ExploreIndex(0, ssb.ExploreRecursiveEdge()),
|
||||
))).Node()
|
||||
}
|
||||
return ssb.ExploreRecursive(ipldselector.RecursionLimitDepth(int(req.RequestLength)), ssb.ExploreIndex(blockIndexParentsField,
|
||||
ssb.ExploreUnion(
|
||||
ssb.ExploreAll(
|
||||
ssb.Matcher(),
|
||||
),
|
||||
ssb.ExploreIndex(0, ssb.ExploreRecursiveEdge()),
|
||||
))).Node()
|
||||
}
|
||||
|
||||
func firstTipsetSelector(req *BlockSyncRequest) ipld.Node {
|
||||
// builer for selectors
|
||||
ssb := selectorbuilder.NewSelectorSpecBuilder(basicnode.Style.Any)
|
||||
|
||||
bso := ParseBSOptions(req.Options)
|
||||
if bso.IncludeMessages {
|
||||
return ssb.ExploreIndex(blockIndexMessagesField,
|
||||
ssb.ExploreRange(0, 2, amtSelector),
|
||||
).Node()
|
||||
}
|
||||
return ssb.Matcher().Node()
|
||||
|
||||
}
|
||||
|
||||
func (bs *BlockSync) executeGsyncSelector(ctx context.Context, p peer.ID, root cid.Cid, sel ipld.Node) error {
|
||||
extension := graphsync.ExtensionData{
|
||||
Name: "chainsync",
|
||||
Data: nil,
|
||||
}
|
||||
_, errs := bs.gsync.Request(ctx, p, cidlink.Link{Cid: root}, sel, extension)
|
||||
|
||||
for err := range errs {
|
||||
return xerrors.Errorf("failed to complete graphsync request: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fallback for interacting with other non-lotus nodes
|
||||
func (bs *BlockSync) fetchBlocksGraphSync(ctx context.Context, p peer.ID, req *BlockSyncRequest) (*BlockSyncResponse, error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
immediateTsSelector := firstTipsetSelector(req)
|
||||
|
||||
// Do this because we can only request one root at a time
|
||||
for _, r := range req.Start {
|
||||
if err := bs.executeGsyncSelector(ctx, p, r, immediateTsSelector); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if req.RequestLength > maxRequestLength {
|
||||
req.RequestLength = maxRequestLength
|
||||
}
|
||||
|
||||
sel := selectorForRequest(req)
|
||||
|
||||
// execute the selector forreal
|
||||
if err := bs.executeGsyncSelector(ctx, p, req.Start[0], sel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now pull the data we fetched out of the chainstore (where it should now be persisted)
|
||||
tempcs := store.NewChainStore(bs.bserv.Blockstore(), datastore.NewMapDatastore(), nil)
|
||||
|
||||
opts := ParseBSOptions(req.Options)
|
||||
tsk := types.NewTipSetKey(req.Start...)
|
||||
chain, err := collectChainSegment(tempcs, tsk, req.RequestLength, opts)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to load chain data from chainstore after successful graphsync response (start = %v): %w", req.Start, err)
|
||||
}
|
||||
|
||||
return &BlockSyncResponse{Chain: chain}, nil
|
||||
}
|
||||
@@ -1,883 +0,0 @@
|
||||
package deals
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
xerrors "golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT.
|
||||
|
||||
var _ = xerrors.Errorf
|
||||
|
||||
func (t *AskRequest) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{129}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Miner (address.Address) (struct)
|
||||
if err := t.Miner.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *AskRequest) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("cbor input should be of type array")
|
||||
}
|
||||
|
||||
if extra != 1 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
}
|
||||
|
||||
// t.Miner (address.Address) (struct)
|
||||
|
||||
{
|
||||
|
||||
if err := t.Miner.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *AskResponse) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{129}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Ask (types.SignedStorageAsk) (struct)
|
||||
if err := t.Ask.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *AskResponse) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("cbor input should be of type array")
|
||||
}
|
||||
|
||||
if extra != 1 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
}
|
||||
|
||||
// t.Ask (types.SignedStorageAsk) (struct)
|
||||
|
||||
{
|
||||
|
||||
pb, err := br.PeekByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pb == cbg.CborNull[0] {
|
||||
var nbuf [1]byte
|
||||
if _, err := br.Read(nbuf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
t.Ask = new(types.SignedStorageAsk)
|
||||
if err := t.Ask.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Proposal) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{130}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.DealProposal (actors.StorageDealProposal) (struct)
|
||||
if err := t.DealProposal.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Piece (cid.Cid) (struct)
|
||||
|
||||
if err := cbg.WriteCid(w, t.Piece); err != nil {
|
||||
return xerrors.Errorf("failed to write cid field t.Piece: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Proposal) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("cbor input should be of type array")
|
||||
}
|
||||
|
||||
if extra != 2 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
}
|
||||
|
||||
// t.DealProposal (actors.StorageDealProposal) (struct)
|
||||
|
||||
{
|
||||
|
||||
pb, err := br.PeekByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pb == cbg.CborNull[0] {
|
||||
var nbuf [1]byte
|
||||
if _, err := br.Read(nbuf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
t.DealProposal = new(actors.StorageDealProposal)
|
||||
if err := t.DealProposal.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// t.Piece (cid.Cid) (struct)
|
||||
|
||||
{
|
||||
|
||||
c, err := cbg.ReadCid(br)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to read cid field t.Piece: %w", err)
|
||||
}
|
||||
|
||||
t.Piece = c
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Response) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{132}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.State (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.State))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Message (string) (string)
|
||||
if len(t.Message) > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field t.Message was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len(t.Message)))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte(t.Message)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Proposal (cid.Cid) (struct)
|
||||
|
||||
if err := cbg.WriteCid(w, t.Proposal); err != nil {
|
||||
return xerrors.Errorf("failed to write cid field t.Proposal: %w", err)
|
||||
}
|
||||
|
||||
// t.StorageDealSubmission (types.SignedMessage) (struct)
|
||||
if err := t.StorageDealSubmission.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Response) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("cbor input should be of type array")
|
||||
}
|
||||
|
||||
if extra != 4 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
}
|
||||
|
||||
// t.State (uint64) (uint64)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.State = uint64(extra)
|
||||
// t.Message (string) (string)
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadString(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Message = string(sval)
|
||||
}
|
||||
// t.Proposal (cid.Cid) (struct)
|
||||
|
||||
{
|
||||
|
||||
c, err := cbg.ReadCid(br)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to read cid field t.Proposal: %w", err)
|
||||
}
|
||||
|
||||
t.Proposal = c
|
||||
|
||||
}
|
||||
// t.StorageDealSubmission (types.SignedMessage) (struct)
|
||||
|
||||
{
|
||||
|
||||
pb, err := br.PeekByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pb == cbg.CborNull[0] {
|
||||
var nbuf [1]byte
|
||||
if _, err := br.Read(nbuf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
t.StorageDealSubmission = new(types.SignedMessage)
|
||||
if err := t.StorageDealSubmission.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *SignedResponse) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{130}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Response (deals.Response) (struct)
|
||||
if err := t.Response.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Signature (types.Signature) (struct)
|
||||
if err := t.Signature.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *SignedResponse) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("cbor input should be of type array")
|
||||
}
|
||||
|
||||
if extra != 2 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
}
|
||||
|
||||
// t.Response (deals.Response) (struct)
|
||||
|
||||
{
|
||||
|
||||
if err := t.Response.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
// t.Signature (types.Signature) (struct)
|
||||
|
||||
{
|
||||
|
||||
pb, err := br.PeekByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pb == cbg.CborNull[0] {
|
||||
var nbuf [1]byte
|
||||
if _, err := br.Read(nbuf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
t.Signature = new(types.Signature)
|
||||
if err := t.Signature.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ClientDealProposal) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{136}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Data (cid.Cid) (struct)
|
||||
|
||||
if err := cbg.WriteCid(w, t.Data); err != nil {
|
||||
return xerrors.Errorf("failed to write cid field t.Data: %w", err)
|
||||
}
|
||||
|
||||
// t.PricePerEpoch (types.BigInt) (struct)
|
||||
if err := t.PricePerEpoch.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.ProposalExpiration (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.ProposalExpiration))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Duration (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.Duration))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.ProviderAddress (address.Address) (struct)
|
||||
if err := t.ProviderAddress.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Client (address.Address) (struct)
|
||||
if err := t.Client.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.MinerWorker (address.Address) (struct)
|
||||
if err := t.MinerWorker.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.MinerID (peer.ID) (string)
|
||||
if len(t.MinerID) > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field t.MinerID was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len(t.MinerID)))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte(t.MinerID)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ClientDealProposal) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("cbor input should be of type array")
|
||||
}
|
||||
|
||||
if extra != 8 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
}
|
||||
|
||||
// t.Data (cid.Cid) (struct)
|
||||
|
||||
{
|
||||
|
||||
c, err := cbg.ReadCid(br)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to read cid field t.Data: %w", err)
|
||||
}
|
||||
|
||||
t.Data = c
|
||||
|
||||
}
|
||||
// t.PricePerEpoch (types.BigInt) (struct)
|
||||
|
||||
{
|
||||
|
||||
if err := t.PricePerEpoch.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
// t.ProposalExpiration (uint64) (uint64)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.ProposalExpiration = uint64(extra)
|
||||
// t.Duration (uint64) (uint64)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.Duration = uint64(extra)
|
||||
// t.ProviderAddress (address.Address) (struct)
|
||||
|
||||
{
|
||||
|
||||
if err := t.ProviderAddress.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
// t.Client (address.Address) (struct)
|
||||
|
||||
{
|
||||
|
||||
if err := t.Client.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
// t.MinerWorker (address.Address) (struct)
|
||||
|
||||
{
|
||||
|
||||
if err := t.MinerWorker.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
// t.MinerID (peer.ID) (string)
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadString(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.MinerID = peer.ID(sval)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ClientDeal) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{135}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.ProposalCid (cid.Cid) (struct)
|
||||
|
||||
if err := cbg.WriteCid(w, t.ProposalCid); err != nil {
|
||||
return xerrors.Errorf("failed to write cid field t.ProposalCid: %w", err)
|
||||
}
|
||||
|
||||
// t.Proposal (actors.StorageDealProposal) (struct)
|
||||
if err := t.Proposal.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.State (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.State))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Miner (peer.ID) (string)
|
||||
if len(t.Miner) > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field t.Miner was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len(t.Miner)))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte(t.Miner)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.MinerWorker (address.Address) (struct)
|
||||
if err := t.MinerWorker.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.DealID (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.DealID))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.PublishMessage (types.SignedMessage) (struct)
|
||||
if err := t.PublishMessage.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ClientDeal) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("cbor input should be of type array")
|
||||
}
|
||||
|
||||
if extra != 7 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
}
|
||||
|
||||
// t.ProposalCid (cid.Cid) (struct)
|
||||
|
||||
{
|
||||
|
||||
c, err := cbg.ReadCid(br)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to read cid field t.ProposalCid: %w", err)
|
||||
}
|
||||
|
||||
t.ProposalCid = c
|
||||
|
||||
}
|
||||
// t.Proposal (actors.StorageDealProposal) (struct)
|
||||
|
||||
{
|
||||
|
||||
if err := t.Proposal.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
// t.State (uint64) (uint64)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.State = uint64(extra)
|
||||
// t.Miner (peer.ID) (string)
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadString(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Miner = peer.ID(sval)
|
||||
}
|
||||
// t.MinerWorker (address.Address) (struct)
|
||||
|
||||
{
|
||||
|
||||
if err := t.MinerWorker.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
// t.DealID (uint64) (uint64)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.DealID = uint64(extra)
|
||||
// t.PublishMessage (types.SignedMessage) (struct)
|
||||
|
||||
{
|
||||
|
||||
pb, err := br.PeekByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pb == cbg.CborNull[0] {
|
||||
var nbuf [1]byte
|
||||
if _, err := br.Read(nbuf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
t.PublishMessage = new(types.SignedMessage)
|
||||
if err := t.PublishMessage.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *MinerDeal) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{135}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Client (peer.ID) (string)
|
||||
if len(t.Client) > cbg.MaxLength {
|
||||
return xerrors.Errorf("Value in field t.Client was too long")
|
||||
}
|
||||
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajTextString, uint64(len(t.Client)))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte(t.Client)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Proposal (actors.StorageDealProposal) (struct)
|
||||
if err := t.Proposal.MarshalCBOR(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.ProposalCid (cid.Cid) (struct)
|
||||
|
||||
if err := cbg.WriteCid(w, t.ProposalCid); err != nil {
|
||||
return xerrors.Errorf("failed to write cid field t.ProposalCid: %w", err)
|
||||
}
|
||||
|
||||
// t.State (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.State))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Ref (cid.Cid) (struct)
|
||||
|
||||
if err := cbg.WriteCid(w, t.Ref); err != nil {
|
||||
return xerrors.Errorf("failed to write cid field t.Ref: %w", err)
|
||||
}
|
||||
|
||||
// t.DealID (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.DealID))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.SectorID (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.SectorID))); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *MinerDeal) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("cbor input should be of type array")
|
||||
}
|
||||
|
||||
if extra != 7 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
}
|
||||
|
||||
// t.Client (peer.ID) (string)
|
||||
|
||||
{
|
||||
sval, err := cbg.ReadString(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Client = peer.ID(sval)
|
||||
}
|
||||
// t.Proposal (actors.StorageDealProposal) (struct)
|
||||
|
||||
{
|
||||
|
||||
if err := t.Proposal.UnmarshalCBOR(br); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
// t.ProposalCid (cid.Cid) (struct)
|
||||
|
||||
{
|
||||
|
||||
c, err := cbg.ReadCid(br)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to read cid field t.ProposalCid: %w", err)
|
||||
}
|
||||
|
||||
t.ProposalCid = c
|
||||
|
||||
}
|
||||
// t.State (uint64) (uint64)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.State = uint64(extra)
|
||||
// t.Ref (cid.Cid) (struct)
|
||||
|
||||
{
|
||||
|
||||
c, err := cbg.ReadCid(br)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to read cid field t.Ref: %w", err)
|
||||
}
|
||||
|
||||
t.Ref = c
|
||||
|
||||
}
|
||||
// t.DealID (uint64) (uint64)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.DealID = uint64(extra)
|
||||
// t.SectorID (uint64) (uint64)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.SectorID = uint64(extra)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *StorageDataTransferVoucher) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write([]byte{130}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Proposal (cid.Cid) (struct)
|
||||
|
||||
if err := cbg.WriteCid(w, t.Proposal); err != nil {
|
||||
return xerrors.Errorf("failed to write cid field t.Proposal: %w", err)
|
||||
}
|
||||
|
||||
// t.DealID (uint64) (uint64)
|
||||
if _, err := w.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, uint64(t.DealID))); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *StorageDataTransferVoucher) UnmarshalCBOR(r io.Reader) error {
|
||||
br := cbg.GetPeeker(r)
|
||||
|
||||
maj, extra, err := cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("cbor input should be of type array")
|
||||
}
|
||||
|
||||
if extra != 2 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
}
|
||||
|
||||
// t.Proposal (cid.Cid) (struct)
|
||||
|
||||
{
|
||||
|
||||
c, err := cbg.ReadCid(br)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to read cid field t.Proposal: %w", err)
|
||||
}
|
||||
|
||||
t.Proposal = c
|
||||
|
||||
}
|
||||
// t.DealID (uint64) (uint64)
|
||||
|
||||
maj, extra, err = cbg.CborReadHeader(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.DealID = uint64(extra)
|
||||
return nil
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
package deals
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
logging "github.com/ipfs/go-log"
|
||||
"github.com/libp2p/go-libp2p-core/host"
|
||||
inet "github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/events"
|
||||
"github.com/filecoin-project/lotus/chain/market"
|
||||
"github.com/filecoin-project/lotus/chain/stmgr"
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/wallet"
|
||||
"github.com/filecoin-project/lotus/lib/cborutil"
|
||||
"github.com/filecoin-project/lotus/lib/statestore"
|
||||
"github.com/filecoin-project/lotus/node/impl/full"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
"github.com/filecoin-project/lotus/retrieval/discovery"
|
||||
)
|
||||
|
||||
var log = logging.Logger("deals")
|
||||
|
||||
type ClientDeal struct {
|
||||
ProposalCid cid.Cid
|
||||
Proposal actors.StorageDealProposal
|
||||
State api.DealState
|
||||
Miner peer.ID
|
||||
MinerWorker address.Address
|
||||
DealID uint64
|
||||
|
||||
PublishMessage *types.SignedMessage
|
||||
|
||||
s inet.Stream
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
sm *stmgr.StateManager
|
||||
chain *store.ChainStore
|
||||
h host.Host
|
||||
w *wallet.Wallet
|
||||
// dataTransfer
|
||||
// TODO: once the data transfer module is complete, the
|
||||
// client will listen to events on the data transfer module
|
||||
// Because we are using only a fake DAGService
|
||||
// implementation, there's no validation or events on the client side
|
||||
dataTransfer dtypes.ClientDataTransfer
|
||||
dag dtypes.ClientDAG
|
||||
discovery *discovery.Local
|
||||
events *events.Events
|
||||
fm *market.FundMgr
|
||||
|
||||
deals *statestore.StateStore
|
||||
conns map[cid.Cid]inet.Stream
|
||||
|
||||
incoming chan *ClientDeal
|
||||
updated chan clientDealUpdate
|
||||
|
||||
stop chan struct{}
|
||||
stopped chan struct{}
|
||||
}
|
||||
|
||||
type clientDealUpdate struct {
|
||||
newState api.DealState
|
||||
id cid.Cid
|
||||
err error
|
||||
mut func(*ClientDeal)
|
||||
}
|
||||
|
||||
type clientApi struct {
|
||||
full.ChainAPI
|
||||
full.StateAPI
|
||||
}
|
||||
|
||||
func NewClient(sm *stmgr.StateManager, chain *store.ChainStore, h host.Host, w *wallet.Wallet, dag dtypes.ClientDAG, dataTransfer dtypes.ClientDataTransfer, discovery *discovery.Local, fm *market.FundMgr, deals dtypes.ClientDealStore, chainapi full.ChainAPI, stateapi full.StateAPI) *Client {
|
||||
c := &Client{
|
||||
sm: sm,
|
||||
chain: chain,
|
||||
h: h,
|
||||
w: w,
|
||||
dataTransfer: dataTransfer,
|
||||
dag: dag,
|
||||
discovery: discovery,
|
||||
fm: fm,
|
||||
events: events.NewEvents(context.TODO(), &clientApi{chainapi, stateapi}),
|
||||
|
||||
deals: deals,
|
||||
conns: map[cid.Cid]inet.Stream{},
|
||||
|
||||
incoming: make(chan *ClientDeal, 16),
|
||||
updated: make(chan clientDealUpdate, 16),
|
||||
|
||||
stop: make(chan struct{}),
|
||||
stopped: make(chan struct{}),
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) Run(ctx context.Context) {
|
||||
go func() {
|
||||
defer close(c.stopped)
|
||||
|
||||
for {
|
||||
select {
|
||||
case deal := <-c.incoming:
|
||||
c.onIncoming(deal)
|
||||
case update := <-c.updated:
|
||||
c.onUpdated(ctx, update)
|
||||
case <-c.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *Client) onIncoming(deal *ClientDeal) {
|
||||
log.Info("incoming deal")
|
||||
|
||||
if _, ok := c.conns[deal.ProposalCid]; ok {
|
||||
log.Errorf("tracking deal connection: already tracking connection for deal %s", deal.ProposalCid)
|
||||
return
|
||||
}
|
||||
c.conns[deal.ProposalCid] = deal.s
|
||||
|
||||
if err := c.deals.Begin(deal.ProposalCid, deal); err != nil {
|
||||
// We may have re-sent the proposal
|
||||
log.Errorf("deal tracking failed: %s", err)
|
||||
c.failDeal(deal.ProposalCid, err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
c.updated <- clientDealUpdate{
|
||||
newState: api.DealUnknown,
|
||||
id: deal.ProposalCid,
|
||||
err: nil,
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *Client) onUpdated(ctx context.Context, update clientDealUpdate) {
|
||||
log.Infof("Client deal %s updated state to %s", update.id, api.DealStates[update.newState])
|
||||
var deal ClientDeal
|
||||
err := c.deals.Mutate(update.id, func(d *ClientDeal) error {
|
||||
d.State = update.newState
|
||||
if update.mut != nil {
|
||||
update.mut(d)
|
||||
}
|
||||
deal = *d
|
||||
return nil
|
||||
})
|
||||
if update.err != nil {
|
||||
log.Errorf("deal %s failed: %s", update.id, update.err)
|
||||
c.failDeal(update.id, update.err)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.failDeal(update.id, err)
|
||||
return
|
||||
}
|
||||
|
||||
switch update.newState {
|
||||
case api.DealUnknown: // new
|
||||
c.handle(ctx, deal, c.new, api.DealAccepted)
|
||||
case api.DealAccepted:
|
||||
c.handle(ctx, deal, c.accepted, api.DealStaged)
|
||||
case api.DealStaged:
|
||||
c.handle(ctx, deal, c.staged, api.DealSealing)
|
||||
case api.DealSealing:
|
||||
c.handle(ctx, deal, c.sealing, api.DealNoUpdate)
|
||||
// TODO: DealComplete -> watch for faults, expiration, etc.
|
||||
}
|
||||
}
|
||||
|
||||
type ClientDealProposal struct {
|
||||
Data cid.Cid
|
||||
|
||||
PricePerEpoch types.BigInt
|
||||
ProposalExpiration uint64
|
||||
Duration uint64
|
||||
|
||||
ProviderAddress address.Address
|
||||
Client address.Address
|
||||
MinerWorker address.Address
|
||||
MinerID peer.ID
|
||||
}
|
||||
|
||||
func (c *Client) Start(ctx context.Context, p ClientDealProposal) (cid.Cid, error) {
|
||||
if err := c.fm.EnsureAvailable(ctx, p.Client, types.BigMul(p.PricePerEpoch, types.NewInt(p.Duration))); err != nil {
|
||||
return cid.Undef, xerrors.Errorf("adding market funds failed: %w", err)
|
||||
}
|
||||
|
||||
commP, pieceSize, err := c.commP(ctx, p.Data)
|
||||
if err != nil {
|
||||
return cid.Undef, xerrors.Errorf("computing commP failed: %w", err)
|
||||
}
|
||||
|
||||
dealProposal := &actors.StorageDealProposal{
|
||||
PieceRef: commP,
|
||||
PieceSize: uint64(pieceSize),
|
||||
Client: p.Client,
|
||||
Provider: p.ProviderAddress,
|
||||
ProposalExpiration: p.ProposalExpiration,
|
||||
Duration: p.Duration,
|
||||
StoragePricePerEpoch: p.PricePerEpoch,
|
||||
StorageCollateral: types.NewInt(uint64(pieceSize)), // TODO: real calc
|
||||
}
|
||||
|
||||
if err := api.SignWith(ctx, c.w.Sign, p.Client, dealProposal); err != nil {
|
||||
return cid.Undef, xerrors.Errorf("signing deal proposal failed: %w", err)
|
||||
}
|
||||
|
||||
proposalNd, err := cborutil.AsIpld(dealProposal)
|
||||
if err != nil {
|
||||
return cid.Undef, xerrors.Errorf("getting proposal node failed: %w", err)
|
||||
}
|
||||
|
||||
s, err := c.h.NewStream(ctx, p.MinerID, DealProtocolID)
|
||||
if err != nil {
|
||||
return cid.Undef, xerrors.Errorf("connecting to storage provider failed: %w", err)
|
||||
}
|
||||
|
||||
proposal := &Proposal{
|
||||
DealProposal: dealProposal,
|
||||
Piece: p.Data,
|
||||
}
|
||||
|
||||
if err := cborutil.WriteCborRPC(s, proposal); err != nil {
|
||||
s.Reset()
|
||||
return cid.Undef, xerrors.Errorf("sending proposal to storage provider failed: %w", err)
|
||||
}
|
||||
|
||||
deal := &ClientDeal{
|
||||
ProposalCid: proposalNd.Cid(),
|
||||
Proposal: *dealProposal,
|
||||
State: api.DealUnknown,
|
||||
Miner: p.MinerID,
|
||||
MinerWorker: p.MinerWorker,
|
||||
|
||||
s: s,
|
||||
}
|
||||
|
||||
c.incoming <- deal
|
||||
|
||||
return deal.ProposalCid, c.discovery.AddPeer(p.Data, discovery.RetrievalPeer{
|
||||
Address: dealProposal.Provider,
|
||||
ID: deal.Miner,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) QueryAsk(ctx context.Context, p peer.ID, a address.Address) (*types.SignedStorageAsk, error) {
|
||||
s, err := c.h.NewStream(ctx, p, AskProtocolID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to open stream to miner: %w", err)
|
||||
}
|
||||
|
||||
req := &AskRequest{
|
||||
Miner: a,
|
||||
}
|
||||
if err := cborutil.WriteCborRPC(s, req); err != nil {
|
||||
return nil, xerrors.Errorf("failed to send ask request: %w", err)
|
||||
}
|
||||
|
||||
var out AskResponse
|
||||
if err := cborutil.ReadCborRPC(s, &out); err != nil {
|
||||
return nil, xerrors.Errorf("failed to read ask response: %w", err)
|
||||
}
|
||||
|
||||
if out.Ask == nil {
|
||||
return nil, xerrors.Errorf("got no ask back")
|
||||
}
|
||||
|
||||
if out.Ask.Ask.Miner != a {
|
||||
return nil, xerrors.Errorf("got back ask for wrong miner")
|
||||
}
|
||||
|
||||
if err := c.checkAskSignature(out.Ask); err != nil {
|
||||
return nil, xerrors.Errorf("ask was not properly signed")
|
||||
}
|
||||
|
||||
return out.Ask, nil
|
||||
}
|
||||
|
||||
func (c *Client) List() ([]ClientDeal, error) {
|
||||
var out []ClientDeal
|
||||
if err := c.deals.List(&out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetDeal(d cid.Cid) (*ClientDeal, error) {
|
||||
var out ClientDeal
|
||||
if err := c.deals.Get(d, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (c *Client) Stop() {
|
||||
close(c.stop)
|
||||
<-c.stopped
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
package deals
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/stmgr"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/cborutil"
|
||||
)
|
||||
|
||||
type clientHandlerFunc func(ctx context.Context, deal ClientDeal) (func(*ClientDeal), error)
|
||||
|
||||
func (c *Client) handle(ctx context.Context, deal ClientDeal, cb clientHandlerFunc, next api.DealState) {
|
||||
go func() {
|
||||
mut, err := cb(ctx, deal)
|
||||
if err != nil {
|
||||
next = api.DealError
|
||||
}
|
||||
|
||||
if err == nil && next == api.DealNoUpdate {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case c.updated <- clientDealUpdate{
|
||||
newState: next,
|
||||
id: deal.ProposalCid,
|
||||
err: err,
|
||||
mut: mut,
|
||||
}:
|
||||
case <-c.stop:
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *Client) new(ctx context.Context, deal ClientDeal) (func(*ClientDeal), error) {
|
||||
resp, err := c.readStorageDealResp(deal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: verify StorageDealSubmission
|
||||
|
||||
if err := c.disconnect(deal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
/* data transfer happens */
|
||||
if resp.State != api.DealAccepted {
|
||||
return nil, xerrors.Errorf("deal wasn't accepted (State=%d)", resp.State)
|
||||
}
|
||||
|
||||
return func(info *ClientDeal) {
|
||||
info.PublishMessage = resp.StorageDealSubmission
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) accepted(ctx context.Context, deal ClientDeal) (func(*ClientDeal), error) {
|
||||
log.Infow("DEAL ACCEPTED!")
|
||||
|
||||
pubmsg := deal.PublishMessage.Message
|
||||
pw, err := stmgr.GetMinerWorker(ctx, c.sm, nil, deal.Proposal.Provider)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("getting miner worker failed: %w", err)
|
||||
}
|
||||
|
||||
if pubmsg.From != pw {
|
||||
return nil, xerrors.Errorf("deal wasn't published by storage provider: from=%s, provider=%s", pubmsg.From, deal.Proposal.Provider)
|
||||
}
|
||||
|
||||
if pubmsg.To != actors.StorageMarketAddress {
|
||||
return nil, xerrors.Errorf("deal publish message wasn't set to StorageMarket actor (to=%s)", pubmsg.To)
|
||||
}
|
||||
|
||||
if pubmsg.Method != actors.SMAMethods.PublishStorageDeals {
|
||||
return nil, xerrors.Errorf("deal publish message called incorrect method (method=%s)", pubmsg.Method)
|
||||
}
|
||||
|
||||
var params actors.PublishStorageDealsParams
|
||||
if err := params.UnmarshalCBOR(bytes.NewReader(pubmsg.Params)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dealIdx := -1
|
||||
for i, storageDeal := range params.Deals {
|
||||
// TODO: make it less hacky
|
||||
sd := storageDeal
|
||||
eq, err := cborutil.Equals(&deal.Proposal, &sd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if eq {
|
||||
dealIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dealIdx == -1 {
|
||||
return nil, xerrors.Errorf("deal publish didn't contain our deal (message cid: %s)", deal.PublishMessage.Cid())
|
||||
}
|
||||
|
||||
// TODO: timeout
|
||||
_, ret, err := c.sm.WaitForMessage(ctx, deal.PublishMessage.Cid())
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("waiting for deal publish message: %w", err)
|
||||
}
|
||||
if ret.ExitCode != 0 {
|
||||
return nil, xerrors.Errorf("deal publish failed: exit=%d", ret.ExitCode)
|
||||
}
|
||||
|
||||
var res actors.PublishStorageDealResponse
|
||||
if err := res.UnmarshalCBOR(bytes.NewReader(ret.Return)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(info *ClientDeal) {
|
||||
info.DealID = res.DealIDs[dealIdx]
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) staged(ctx context.Context, deal ClientDeal) (func(*ClientDeal), error) {
|
||||
// TODO: Maybe wait for pre-commit
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Client) sealing(ctx context.Context, deal ClientDeal) (func(*ClientDeal), error) {
|
||||
checkFunc := func(ts *types.TipSet) (done bool, more bool, err error) {
|
||||
sd, err := stmgr.GetStorageDeal(ctx, c.sm, deal.DealID, ts)
|
||||
if err != nil {
|
||||
// TODO: This may be fine for some errors
|
||||
return false, false, xerrors.Errorf("failed to look up deal on chain: %w", err)
|
||||
}
|
||||
|
||||
if sd.ActivationEpoch > 0 {
|
||||
select {
|
||||
case c.updated <- clientDealUpdate{
|
||||
newState: api.DealComplete,
|
||||
id: deal.ProposalCid,
|
||||
}:
|
||||
case <-c.stop:
|
||||
}
|
||||
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
return false, true, nil
|
||||
}
|
||||
|
||||
called := func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH uint64) (more bool, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
select {
|
||||
case c.updated <- clientDealUpdate{
|
||||
newState: api.DealComplete,
|
||||
id: deal.ProposalCid,
|
||||
err: xerrors.Errorf("handling applied event: %w", err),
|
||||
}:
|
||||
case <-c.stop:
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if msg == nil {
|
||||
log.Error("timed out waiting for deal activation... what now?")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
sd, err := stmgr.GetStorageDeal(ctx, c.sm, deal.DealID, ts)
|
||||
if err != nil {
|
||||
return false, xerrors.Errorf("failed to look up deal on chain: %w", err)
|
||||
}
|
||||
|
||||
if sd.ActivationEpoch == 0 {
|
||||
return false, xerrors.Errorf("deal wasn't active: deal=%d, parentState=%s, h=%d", deal.DealID, ts.ParentState(), ts.Height())
|
||||
}
|
||||
|
||||
log.Infof("Storage deal %d activated at epoch %d", deal.DealID, sd.ActivationEpoch)
|
||||
|
||||
select {
|
||||
case c.updated <- clientDealUpdate{
|
||||
newState: api.DealComplete,
|
||||
id: deal.ProposalCid,
|
||||
}:
|
||||
case <-c.stop:
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
revert := func(ctx context.Context, ts *types.TipSet) error {
|
||||
log.Warn("deal activation reverted; TODO: actually handle this!")
|
||||
// TODO: Just go back to DealSealing?
|
||||
return nil
|
||||
}
|
||||
|
||||
matchEvent := func(msg *types.Message) (bool, error) {
|
||||
if msg.To != deal.Proposal.Provider {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if msg.Method != actors.MAMethods.ProveCommitSector {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var params actors.SectorProveCommitInfo
|
||||
if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var found bool
|
||||
for _, dealID := range params.DealIDs {
|
||||
if dealID == deal.DealID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return found, nil
|
||||
}
|
||||
|
||||
if err := c.events.Called(checkFunc, called, revert, 3, build.SealRandomnessLookbackLimit, matchEvent); err != nil {
|
||||
return nil, xerrors.Errorf("failed to set up called handler")
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package deals
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"runtime"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
files "github.com/ipfs/go-ipfs-files"
|
||||
unixfile "github.com/ipfs/go-unixfs/file"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/lotus/datatransfer"
|
||||
"github.com/filecoin-project/lotus/lib/cborutil"
|
||||
"github.com/filecoin-project/lotus/lib/padreader"
|
||||
"github.com/filecoin-project/lotus/lib/sectorbuilder"
|
||||
"github.com/filecoin-project/lotus/lib/statestore"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
)
|
||||
|
||||
func (c *Client) failDeal(id cid.Cid, cerr error) {
|
||||
if cerr == nil {
|
||||
_, f, l, _ := runtime.Caller(1)
|
||||
cerr = xerrors.Errorf("unknown error (fail called at %s:%d)", f, l)
|
||||
}
|
||||
|
||||
s, ok := c.conns[id]
|
||||
if ok {
|
||||
_ = s.Reset()
|
||||
delete(c.conns, id)
|
||||
}
|
||||
|
||||
// TODO: store in some sort of audit log
|
||||
log.Errorf("deal %s failed: %+v", id, cerr)
|
||||
}
|
||||
|
||||
func (c *Client) commP(ctx context.Context, data cid.Cid) ([]byte, uint64, error) {
|
||||
root, err := c.dag.Get(ctx, data)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get file root for deal: %s", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
n, err := unixfile.NewUnixfsFile(ctx, c.dag, root)
|
||||
if err != nil {
|
||||
log.Errorf("cannot open unixfs file: %s", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
uf, ok := n.(files.File)
|
||||
if !ok {
|
||||
// TODO: we probably got directory, how should we handle this in unixfs mode?
|
||||
return nil, 0, xerrors.New("unsupported unixfs type")
|
||||
}
|
||||
|
||||
s, err := uf.Size()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
pr, psize := padreader.New(uf, uint64(s))
|
||||
|
||||
commp, err := sectorbuilder.GeneratePieceCommitment(pr, psize)
|
||||
if err != nil {
|
||||
return nil, 0, xerrors.Errorf("generating CommP: %w", err)
|
||||
}
|
||||
|
||||
return commp[:], psize, nil
|
||||
}
|
||||
|
||||
func (c *Client) readStorageDealResp(deal ClientDeal) (*Response, error) {
|
||||
s, ok := c.conns[deal.ProposalCid]
|
||||
if !ok {
|
||||
// TODO: Try to re-establish the connection using query protocol
|
||||
return nil, xerrors.Errorf("no connection to miner")
|
||||
}
|
||||
|
||||
var resp SignedResponse
|
||||
if err := cborutil.ReadCborRPC(s, &resp); err != nil {
|
||||
log.Errorw("failed to read Response message", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := resp.Verify(deal.MinerWorker); err != nil {
|
||||
return nil, xerrors.Errorf("verifying response signature failed", err)
|
||||
}
|
||||
|
||||
if resp.Response.Proposal != deal.ProposalCid {
|
||||
return nil, xerrors.Errorf("miner responded to a wrong proposal: %s != %s", resp.Response.Proposal, deal.ProposalCid)
|
||||
}
|
||||
|
||||
return &resp.Response, nil
|
||||
}
|
||||
|
||||
func (c *Client) disconnect(deal ClientDeal) error {
|
||||
s, ok := c.conns[deal.ProposalCid]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := s.Close()
|
||||
delete(c.conns, deal.ProposalCid)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ datatransfer.RequestValidator = &ClientRequestValidator{}
|
||||
|
||||
// ClientRequestValidator validates data transfer requests for the client
|
||||
// in a storage market
|
||||
type ClientRequestValidator struct {
|
||||
deals *statestore.StateStore
|
||||
}
|
||||
|
||||
// NewClientRequestValidator returns a new client request validator for the
|
||||
// given datastore
|
||||
func NewClientRequestValidator(deals dtypes.ClientDealStore) *ClientRequestValidator {
|
||||
crv := &ClientRequestValidator{
|
||||
deals: deals,
|
||||
}
|
||||
return crv
|
||||
}
|
||||
|
||||
// ValidatePush validates a push request received from the peer that will send data
|
||||
// Will always error because clients should not accept push requests from a provider
|
||||
// in a storage deal (i.e. send data to client).
|
||||
func (c *ClientRequestValidator) ValidatePush(
|
||||
sender peer.ID,
|
||||
voucher datatransfer.Voucher,
|
||||
baseCid cid.Cid,
|
||||
Selector ipld.Node) error {
|
||||
return ErrNoPushAccepted
|
||||
}
|
||||
|
||||
// ValidatePull validates a pull request received from the peer that will receive data
|
||||
// Will succeed only if:
|
||||
// - voucher has correct type
|
||||
// - voucher references an active deal
|
||||
// - referenced deal matches the receiver (miner)
|
||||
// - referenced deal matches the given base CID
|
||||
// - referenced deal is in an acceptable state
|
||||
func (c *ClientRequestValidator) ValidatePull(
|
||||
receiver peer.ID,
|
||||
voucher datatransfer.Voucher,
|
||||
baseCid cid.Cid,
|
||||
Selector ipld.Node) error {
|
||||
dealVoucher, ok := voucher.(*StorageDataTransferVoucher)
|
||||
if !ok {
|
||||
return xerrors.Errorf("voucher type %s: %w", voucher.Identifier(), ErrWrongVoucherType)
|
||||
}
|
||||
|
||||
var deal ClientDeal
|
||||
err := c.deals.Get(dealVoucher.Proposal, &deal)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Proposal CID %s: %w", dealVoucher.Proposal.String(), ErrNoDeal)
|
||||
}
|
||||
if deal.Miner != receiver {
|
||||
return xerrors.Errorf("Deal Peer %s, Data Transfer Peer %s: %w", deal.Miner.String(), receiver.String(), ErrWrongPeer)
|
||||
}
|
||||
if !bytes.Equal(deal.Proposal.PieceRef, baseCid.Bytes()) {
|
||||
return xerrors.Errorf("Deal Payload CID %s, Data Transfer CID %s: %w", string(deal.Proposal.PieceRef), baseCid.String(), ErrWrongPiece)
|
||||
}
|
||||
for _, state := range DataTransferStates {
|
||||
if deal.State == state {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return xerrors.Errorf("Deal State %s: %w", deal.State, ErrInacceptableDealState)
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
package deals
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
cid "github.com/ipfs/go-cid"
|
||||
datastore "github.com/ipfs/go-datastore"
|
||||
"github.com/ipfs/go-datastore/namespace"
|
||||
inet "github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/datatransfer"
|
||||
"github.com/filecoin-project/lotus/lib/cborutil"
|
||||
"github.com/filecoin-project/lotus/lib/statestore"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
"github.com/filecoin-project/lotus/storage"
|
||||
"github.com/filecoin-project/lotus/storage/sectorblocks"
|
||||
)
|
||||
|
||||
var ProviderDsPrefix = "/deals/provider"
|
||||
|
||||
type MinerDeal struct {
|
||||
Client peer.ID
|
||||
Proposal actors.StorageDealProposal
|
||||
ProposalCid cid.Cid
|
||||
State api.DealState
|
||||
|
||||
Ref cid.Cid
|
||||
|
||||
DealID uint64
|
||||
SectorID uint64 // Set when State >= DealStaged
|
||||
|
||||
s inet.Stream
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
pricePerByteBlock types.BigInt // how much we want for storing one byte for one block
|
||||
minPieceSize uint64
|
||||
|
||||
ask *types.SignedStorageAsk
|
||||
askLk sync.Mutex
|
||||
|
||||
secb *sectorblocks.SectorBlocks
|
||||
sminer *storage.Miner
|
||||
full api.FullNode
|
||||
|
||||
// TODO: This will go away once storage market module + CAR
|
||||
// is implemented
|
||||
dag dtypes.StagingDAG
|
||||
|
||||
// dataTransfer is the manager of data transfers used by this storage provider
|
||||
dataTransfer dtypes.ProviderDataTransfer
|
||||
|
||||
deals *statestore.StateStore
|
||||
ds dtypes.MetadataDS
|
||||
|
||||
conns map[cid.Cid]inet.Stream
|
||||
|
||||
actor address.Address
|
||||
|
||||
incoming chan MinerDeal
|
||||
updated chan minerDealUpdate
|
||||
stop chan struct{}
|
||||
stopped chan struct{}
|
||||
}
|
||||
|
||||
type minerDealUpdate struct {
|
||||
newState api.DealState
|
||||
id cid.Cid
|
||||
err error
|
||||
mut func(*MinerDeal)
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrDataTransferFailed means a data transfer for a deal failed
|
||||
ErrDataTransferFailed = errors.New("deal data transfer failed")
|
||||
)
|
||||
|
||||
func NewProvider(ds dtypes.MetadataDS, sminer *storage.Miner, secb *sectorblocks.SectorBlocks, dag dtypes.StagingDAG, dataTransfer dtypes.ProviderDataTransfer, fullNode api.FullNode) (*Provider, error) {
|
||||
addr, err := ds.Get(datastore.NewKey("miner-address"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
minerAddress, err := address.NewFromBytes(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := &Provider{
|
||||
sminer: sminer,
|
||||
dag: dag,
|
||||
dataTransfer: dataTransfer,
|
||||
full: fullNode,
|
||||
secb: secb,
|
||||
|
||||
pricePerByteBlock: types.NewInt(3), // TODO: allow setting
|
||||
minPieceSize: 256, // TODO: allow setting (BUT KEEP MIN 256! (because of how we fill sectors up))
|
||||
|
||||
conns: map[cid.Cid]inet.Stream{},
|
||||
|
||||
incoming: make(chan MinerDeal),
|
||||
updated: make(chan minerDealUpdate),
|
||||
stop: make(chan struct{}),
|
||||
stopped: make(chan struct{}),
|
||||
|
||||
actor: minerAddress,
|
||||
|
||||
deals: statestore.New(namespace.Wrap(ds, datastore.NewKey(ProviderDsPrefix))),
|
||||
ds: ds,
|
||||
}
|
||||
|
||||
if err := h.tryLoadAsk(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if h.ask == nil {
|
||||
// TODO: we should be fine with this state, and just say it means 'not actively accepting deals'
|
||||
// for now... lets just set a price
|
||||
if err := h.SetPrice(types.NewInt(500_000_000), 1000000); err != nil {
|
||||
return nil, xerrors.Errorf("failed setting a default price: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// register a data transfer event handler -- this will move deals from
|
||||
// accepted to staged
|
||||
h.dataTransfer.SubscribeToEvents(h.onDataTransferEvent)
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (p *Provider) Run(ctx context.Context) {
|
||||
// TODO: restore state
|
||||
|
||||
go func() {
|
||||
defer log.Warn("quitting deal provider loop")
|
||||
defer close(p.stopped)
|
||||
|
||||
for {
|
||||
select {
|
||||
case deal := <-p.incoming: // DealAccepted
|
||||
p.onIncoming(deal)
|
||||
case update := <-p.updated: // DealStaged
|
||||
p.onUpdated(ctx, update)
|
||||
case <-p.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *Provider) onIncoming(deal MinerDeal) {
|
||||
log.Info("incoming deal")
|
||||
|
||||
p.conns[deal.ProposalCid] = deal.s
|
||||
|
||||
if err := p.deals.Begin(deal.ProposalCid, &deal); err != nil {
|
||||
// This can happen when client re-sends proposal
|
||||
p.failDeal(deal.ProposalCid, err)
|
||||
log.Errorf("deal tracking failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
p.updated <- minerDealUpdate{
|
||||
newState: api.DealAccepted,
|
||||
id: deal.ProposalCid,
|
||||
err: nil,
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *Provider) onUpdated(ctx context.Context, update minerDealUpdate) {
|
||||
log.Infof("Deal %s updated state to %s", update.id, api.DealStates[update.newState])
|
||||
if update.err != nil {
|
||||
log.Errorf("deal %s (newSt: %d) failed: %+v", update.id, update.newState, update.err)
|
||||
p.failDeal(update.id, update.err)
|
||||
return
|
||||
}
|
||||
var deal MinerDeal
|
||||
err := p.deals.Mutate(update.id, func(d *MinerDeal) error {
|
||||
d.State = update.newState
|
||||
if update.mut != nil {
|
||||
update.mut(d)
|
||||
}
|
||||
deal = *d
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
p.failDeal(update.id, err)
|
||||
return
|
||||
}
|
||||
|
||||
switch update.newState {
|
||||
case api.DealAccepted:
|
||||
p.handle(ctx, deal, p.accept, api.DealNoUpdate)
|
||||
case api.DealStaged:
|
||||
p.handle(ctx, deal, p.staged, api.DealSealing)
|
||||
case api.DealSealing:
|
||||
p.handle(ctx, deal, p.sealing, api.DealComplete)
|
||||
case api.DealComplete:
|
||||
p.handle(ctx, deal, p.complete, api.DealNoUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
// onDataTransferEvent is the function called when an event occurs in a data
|
||||
// transfer -- it reads the voucher to verify this even occurred in a storage
|
||||
// market deal, then, based on the data transfer event that occurred, it generates
|
||||
// and update message for the deal -- either moving to staged for a completion
|
||||
// event or moving to error if a data transfer error occurs
|
||||
func (p *Provider) onDataTransferEvent(event datatransfer.Event, channelState datatransfer.ChannelState) {
|
||||
voucher, ok := channelState.Voucher().(*StorageDataTransferVoucher)
|
||||
// if this event is for a transfer not related to storage, ignore
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// data transfer events for opening and progress do not affect deal state
|
||||
var next api.DealState
|
||||
var err error
|
||||
var mut func(*MinerDeal)
|
||||
switch event {
|
||||
case datatransfer.Complete:
|
||||
next = api.DealStaged
|
||||
mut = func(deal *MinerDeal) {
|
||||
deal.DealID = voucher.DealID
|
||||
}
|
||||
case datatransfer.Error:
|
||||
next = api.DealFailed
|
||||
err = ErrDataTransferFailed
|
||||
default:
|
||||
// the only events we care about are complete and error
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case p.updated <- minerDealUpdate{
|
||||
newState: next,
|
||||
id: voucher.Proposal,
|
||||
err: err,
|
||||
mut: mut,
|
||||
}:
|
||||
case <-p.stop:
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) newDeal(s inet.Stream, proposal Proposal) (MinerDeal, error) {
|
||||
proposalNd, err := cborutil.AsIpld(proposal.DealProposal)
|
||||
if err != nil {
|
||||
return MinerDeal{}, err
|
||||
}
|
||||
|
||||
return MinerDeal{
|
||||
Client: s.Conn().RemotePeer(),
|
||||
Proposal: *proposal.DealProposal,
|
||||
ProposalCid: proposalNd.Cid(),
|
||||
State: api.DealUnknown,
|
||||
|
||||
Ref: proposal.Piece,
|
||||
|
||||
s: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) HandleStream(s inet.Stream) {
|
||||
log.Info("Handling storage deal proposal!")
|
||||
|
||||
proposal, err := p.readProposal(s)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
s.Close()
|
||||
return
|
||||
}
|
||||
|
||||
deal, err := p.newDeal(s, proposal)
|
||||
if err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
s.Close()
|
||||
return
|
||||
}
|
||||
|
||||
p.incoming <- deal
|
||||
}
|
||||
|
||||
func (p *Provider) Stop() {
|
||||
close(p.stop)
|
||||
<-p.stopped
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package deals
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/stmgr"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/cborutil"
|
||||
datastore "github.com/ipfs/go-datastore"
|
||||
inet "github.com/libp2p/go-libp2p-core/network"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func (p *Provider) SetPrice(price types.BigInt, ttlsecs int64) error {
|
||||
p.askLk.Lock()
|
||||
defer p.askLk.Unlock()
|
||||
|
||||
var seqno uint64
|
||||
if p.ask != nil {
|
||||
seqno = p.ask.Ask.SeqNo + 1
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
ask := &types.StorageAsk{
|
||||
Price: price,
|
||||
Timestamp: uint64(now),
|
||||
Expiry: uint64(now + ttlsecs),
|
||||
Miner: p.actor,
|
||||
SeqNo: seqno,
|
||||
MinPieceSize: p.minPieceSize,
|
||||
}
|
||||
|
||||
ssa, err := p.signAsk(ask)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.saveAsk(ssa)
|
||||
}
|
||||
|
||||
func (p *Provider) getAsk(m address.Address) *types.SignedStorageAsk {
|
||||
p.askLk.Lock()
|
||||
defer p.askLk.Unlock()
|
||||
if m != p.actor {
|
||||
return nil
|
||||
}
|
||||
|
||||
return p.ask
|
||||
}
|
||||
|
||||
func (p *Provider) HandleAskStream(s inet.Stream) {
|
||||
defer s.Close()
|
||||
var ar AskRequest
|
||||
if err := cborutil.ReadCborRPC(s, &ar); err != nil {
|
||||
log.Errorf("failed to read AskRequest from incoming stream: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := p.processAskRequest(&ar)
|
||||
|
||||
if err := cborutil.WriteCborRPC(s, resp); err != nil {
|
||||
log.Errorf("failed to write ask response: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) processAskRequest(ar *AskRequest) *AskResponse {
|
||||
return &AskResponse{
|
||||
Ask: p.getAsk(ar.Miner),
|
||||
}
|
||||
}
|
||||
|
||||
var bestAskKey = datastore.NewKey("latest-ask")
|
||||
|
||||
func (p *Provider) tryLoadAsk() error {
|
||||
p.askLk.Lock()
|
||||
defer p.askLk.Unlock()
|
||||
|
||||
err := p.loadAsk()
|
||||
if err != nil {
|
||||
if xerrors.Is(err, datastore.ErrNotFound) {
|
||||
log.Warn("no previous ask found, miner will not accept deals until a price is set")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) loadAsk() error {
|
||||
askb, err := p.ds.Get(datastore.NewKey("latest-ask"))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to load most recent ask from disk: %w", err)
|
||||
}
|
||||
|
||||
var ssa types.SignedStorageAsk
|
||||
if err := cborutil.ReadCborRPC(bytes.NewReader(askb), &ssa); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.ask = &ssa
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) signAsk(a *types.StorageAsk) (*types.SignedStorageAsk, error) {
|
||||
b, err := cborutil.Dump(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
worker, err := p.getWorker(p.actor)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get worker to sign ask: %w", err)
|
||||
}
|
||||
|
||||
sig, err := p.full.WalletSign(context.TODO(), worker, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.SignedStorageAsk{
|
||||
Ask: a,
|
||||
Signature: sig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) saveAsk(a *types.SignedStorageAsk) error {
|
||||
b, err := cborutil.Dump(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.ds.Put(bestAskKey, b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.ask = a
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) checkAskSignature(ask *types.SignedStorageAsk) error {
|
||||
tss := c.sm.ChainStore().GetHeaviestTipSet().ParentState()
|
||||
|
||||
w, err := stmgr.GetMinerWorkerRaw(context.TODO(), c.sm, tss, ask.Ask.Miner)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to get worker for miner in ask", err)
|
||||
}
|
||||
|
||||
sigb, err := cborutil.Dump(ask.Ask)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to re-serialize ask")
|
||||
}
|
||||
|
||||
return ask.Signature.Verify(w, sigb)
|
||||
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
package deals
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
ipldfree "github.com/ipld/go-ipld-prime/impl/free"
|
||||
"github.com/ipld/go-ipld-prime/traversal/selector"
|
||||
"github.com/ipld/go-ipld-prime/traversal/selector/builder"
|
||||
|
||||
unixfile "github.com/ipfs/go-unixfs/file"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/padreader"
|
||||
"github.com/filecoin-project/lotus/storage/sectorblocks"
|
||||
)
|
||||
|
||||
type providerHandlerFunc func(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error)
|
||||
|
||||
func (p *Provider) handle(ctx context.Context, deal MinerDeal, cb providerHandlerFunc, next api.DealState) {
|
||||
go func() {
|
||||
mut, err := cb(ctx, deal)
|
||||
|
||||
if err == nil && next == api.DealNoUpdate {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case p.updated <- minerDealUpdate{
|
||||
newState: next,
|
||||
id: deal.ProposalCid,
|
||||
err: err,
|
||||
mut: mut,
|
||||
}:
|
||||
case <-p.stop:
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// ACCEPTED
|
||||
func (p *Provider) accept(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
|
||||
|
||||
head, err := p.full.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if head.Height() >= deal.Proposal.ProposalExpiration {
|
||||
return nil, xerrors.Errorf("deal proposal already expired")
|
||||
}
|
||||
|
||||
// TODO: check StorageCollateral
|
||||
|
||||
minPrice := types.BigDiv(types.BigMul(p.ask.Ask.Price, types.NewInt(deal.Proposal.PieceSize)), types.NewInt(1<<30))
|
||||
if deal.Proposal.StoragePricePerEpoch.LessThan(minPrice) {
|
||||
return nil, xerrors.Errorf("storage price per epoch less than asking price: %s < %s", deal.Proposal.StoragePricePerEpoch, minPrice)
|
||||
}
|
||||
|
||||
if deal.Proposal.PieceSize < p.ask.Ask.MinPieceSize {
|
||||
return nil, xerrors.Errorf("piece size less than minimum required size: %d < %d", deal.Proposal.PieceSize, p.ask.Ask.MinPieceSize)
|
||||
}
|
||||
|
||||
// check market funds
|
||||
clientMarketBalance, err := p.full.StateMarketBalance(ctx, deal.Proposal.Client, nil)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("getting client market balance failed: %w", err)
|
||||
}
|
||||
|
||||
// This doesn't guarantee that the client won't withdraw / lock those funds
|
||||
// but it's a decent first filter
|
||||
if clientMarketBalance.Available.LessThan(deal.Proposal.TotalStoragePrice()) {
|
||||
return nil, xerrors.New("clientMarketBalance.Available too small")
|
||||
}
|
||||
|
||||
waddr, err := p.full.StateMinerWorker(ctx, deal.Proposal.Provider, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: check StorageCollateral (may be too large (or too small))
|
||||
if err := p.full.MarketEnsureAvailable(ctx, waddr, deal.Proposal.StorageCollateral); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Info("publishing deal")
|
||||
|
||||
params, err := actors.SerializeParams(&actors.PublishStorageDealsParams{
|
||||
Deals: []actors.StorageDealProposal{deal.Proposal},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("serializing PublishStorageDeals params failed: ", err)
|
||||
}
|
||||
|
||||
// TODO: We may want this to happen after fetching data
|
||||
smsg, err := p.full.MpoolPushMessage(ctx, &types.Message{
|
||||
To: actors.StorageMarketAddress,
|
||||
From: waddr,
|
||||
Value: types.NewInt(0),
|
||||
GasPrice: types.NewInt(0),
|
||||
GasLimit: types.NewInt(1000000),
|
||||
Method: actors.SMAMethods.PublishStorageDeals,
|
||||
Params: params,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := p.full.StateWaitMsg(ctx, smsg.Cid())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.Receipt.ExitCode != 0 {
|
||||
return nil, xerrors.Errorf("publishing deal failed: exit %d", r.Receipt.ExitCode)
|
||||
}
|
||||
var resp actors.PublishStorageDealResponse
|
||||
if err := resp.UnmarshalCBOR(bytes.NewReader(r.Receipt.Return)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.DealIDs) != 1 {
|
||||
return nil, xerrors.Errorf("got unexpected number of DealIDs from SMA")
|
||||
}
|
||||
|
||||
log.Infof("fetching data for a deal %d", resp.DealIDs[0])
|
||||
err = p.sendSignedResponse(&Response{
|
||||
State: api.DealAccepted,
|
||||
|
||||
Proposal: deal.ProposalCid,
|
||||
StorageDealSubmission: smsg,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := p.disconnect(deal); err != nil {
|
||||
log.Warnf("closing client connection: %+v", err)
|
||||
}
|
||||
|
||||
ssb := builder.NewSelectorSpecBuilder(ipldfree.NodeBuilder())
|
||||
|
||||
// this is the selector for "get the whole DAG"
|
||||
// TODO: support storage deals with custom payload selectors
|
||||
allSelector := ssb.ExploreRecursive(selector.RecursionLimitNone(),
|
||||
ssb.ExploreAll(ssb.ExploreRecursiveEdge())).Node()
|
||||
|
||||
// initiate a pull data transfer. This will complete asynchronously and the
|
||||
// completion of the data transfer will trigger a change in deal state
|
||||
// (see onDataTransferEvent)
|
||||
_, err = p.dataTransfer.OpenPullDataChannel(ctx,
|
||||
deal.Client,
|
||||
&StorageDataTransferVoucher{Proposal: deal.ProposalCid, DealID: resp.DealIDs[0]},
|
||||
deal.Ref,
|
||||
allSelector,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to open pull data channel: %w", err)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// STAGED
|
||||
|
||||
func (p *Provider) staged(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
|
||||
root, err := p.dag.Get(ctx, deal.Ref)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get file root for deal: %s", err)
|
||||
}
|
||||
|
||||
// TODO: abstract this away into ReadSizeCloser + implement different modes
|
||||
n, err := unixfile.NewUnixfsFile(ctx, p.dag, root)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("cannot open unixfs file: %s", err)
|
||||
}
|
||||
|
||||
uf, ok := n.(sectorblocks.UnixfsReader)
|
||||
if !ok {
|
||||
// we probably got directory, unsupported for now
|
||||
return nil, xerrors.Errorf("unsupported unixfs file type")
|
||||
}
|
||||
|
||||
// TODO: uf.Size() is user input, not trusted
|
||||
// This won't be useful / here after we migrate to putting CARs into sectors
|
||||
size, err := uf.Size()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("getting unixfs file size: %w", err)
|
||||
}
|
||||
if padreader.PaddedSize(uint64(size)) != deal.Proposal.PieceSize {
|
||||
return nil, xerrors.Errorf("deal.Proposal.PieceSize didn't match padded unixfs file size")
|
||||
}
|
||||
|
||||
sectorID, err := p.secb.AddUnixfsPiece(ctx, uf, deal.DealID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("AddPiece failed: %s", err)
|
||||
}
|
||||
log.Warnf("New Sector: %d (deal %d)", sectorID, deal.DealID)
|
||||
|
||||
return func(deal *MinerDeal) {
|
||||
deal.SectorID = sectorID
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SEALING
|
||||
|
||||
func (p *Provider) sealing(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
|
||||
// TODO: consider waiting for seal to happen
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *Provider) complete(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
|
||||
// TODO: observe sector lifecycle, status, expiration..
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
package deals
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"runtime"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/datatransfer"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/cborutil"
|
||||
"github.com/filecoin-project/lotus/lib/statestore"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
inet "github.com/libp2p/go-libp2p-core/network"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func (p *Provider) failDeal(id cid.Cid, cerr error) {
|
||||
if err := p.deals.End(id); err != nil {
|
||||
log.Warnf("deals.End: %s", err)
|
||||
}
|
||||
|
||||
if cerr == nil {
|
||||
_, f, l, _ := runtime.Caller(1)
|
||||
cerr = xerrors.Errorf("unknown error (fail called at %s:%d)", f, l)
|
||||
}
|
||||
|
||||
log.Warnf("deal %s failed: %s", id, cerr)
|
||||
|
||||
err := p.sendSignedResponse(&Response{
|
||||
State: api.DealFailed,
|
||||
Message: cerr.Error(),
|
||||
Proposal: id,
|
||||
})
|
||||
|
||||
s, ok := p.conns[id]
|
||||
if ok {
|
||||
_ = s.Reset()
|
||||
delete(p.conns, id)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("notifying client about deal failure: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) readProposal(s inet.Stream) (proposal Proposal, err error) {
|
||||
if err := cborutil.ReadCborRPC(s, &proposal); err != nil {
|
||||
log.Errorw("failed to read proposal message", "error", err)
|
||||
return proposal, err
|
||||
}
|
||||
|
||||
if err := proposal.DealProposal.Verify(); err != nil {
|
||||
return proposal, xerrors.Errorf("verifying StorageDealProposal: %w", err)
|
||||
}
|
||||
|
||||
if proposal.DealProposal.Provider != p.actor {
|
||||
log.Errorf("proposal with wrong ProviderAddress: %s", proposal.DealProposal.Provider)
|
||||
return proposal, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Provider) sendSignedResponse(resp *Response) error {
|
||||
s, ok := p.conns[resp.Proposal]
|
||||
if !ok {
|
||||
return xerrors.New("couldn't send response: not connected")
|
||||
}
|
||||
|
||||
msg, err := cborutil.Dump(resp)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("serializing response: %w", err)
|
||||
}
|
||||
|
||||
worker, err := p.getWorker(p.actor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sig, err := p.full.WalletSign(context.TODO(), worker, msg)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to sign response message: %w", err)
|
||||
}
|
||||
|
||||
signedResponse := &SignedResponse{
|
||||
Response: *resp,
|
||||
Signature: sig,
|
||||
}
|
||||
|
||||
err = cborutil.WriteCborRPC(s, signedResponse)
|
||||
if err != nil {
|
||||
// Assume client disconnected
|
||||
s.Close()
|
||||
delete(p.conns, resp.Proposal)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Provider) disconnect(deal MinerDeal) error {
|
||||
s, ok := p.conns[deal.ProposalCid]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := s.Close()
|
||||
delete(p.conns, deal.ProposalCid)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Provider) getWorker(miner address.Address) (address.Address, error) {
|
||||
getworker := &types.Message{
|
||||
To: miner,
|
||||
From: miner,
|
||||
Method: actors.MAMethods.GetWorkerAddr,
|
||||
}
|
||||
r, err := p.full.StateCall(context.TODO(), getworker, nil)
|
||||
if err != nil {
|
||||
return address.Undef, xerrors.Errorf("getting worker address: %w", err)
|
||||
}
|
||||
|
||||
if r.ExitCode != 0 {
|
||||
return address.Undef, xerrors.Errorf("getWorker call failed: %d", r.ExitCode)
|
||||
}
|
||||
|
||||
return address.NewFromBytes(r.Return)
|
||||
}
|
||||
|
||||
var _ datatransfer.RequestValidator = &ProviderRequestValidator{}
|
||||
|
||||
// ProviderRequestValidator validates data transfer requests for the provider
|
||||
// in a storage market
|
||||
type ProviderRequestValidator struct {
|
||||
deals *statestore.StateStore
|
||||
}
|
||||
|
||||
// NewProviderRequestValidator returns a new client request validator for the
|
||||
// given datastore
|
||||
func NewProviderRequestValidator(deals dtypes.ProviderDealStore) *ProviderRequestValidator {
|
||||
return &ProviderRequestValidator{
|
||||
deals: deals,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidatePush validates a push request received from the peer that will send data
|
||||
// Will succeed only if:
|
||||
// - voucher has correct type
|
||||
// - voucher references an active deal
|
||||
// - referenced deal matches the client
|
||||
// - referenced deal matches the given base CID
|
||||
// - referenced deal is in an acceptable state
|
||||
func (m *ProviderRequestValidator) ValidatePush(
|
||||
sender peer.ID,
|
||||
voucher datatransfer.Voucher,
|
||||
baseCid cid.Cid,
|
||||
Selector ipld.Node) error {
|
||||
dealVoucher, ok := voucher.(*StorageDataTransferVoucher)
|
||||
if !ok {
|
||||
return xerrors.Errorf("voucher type %s: %w", voucher.Identifier(), ErrWrongVoucherType)
|
||||
}
|
||||
|
||||
var deal MinerDeal
|
||||
err := m.deals.Get(dealVoucher.Proposal, &deal)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Proposal CID %s: %w", dealVoucher.Proposal.String(), ErrNoDeal)
|
||||
}
|
||||
if deal.Client != sender {
|
||||
return xerrors.Errorf("Deal Peer %s, Data Transfer Peer %s: %w", deal.Client.String(), sender.String(), ErrWrongPeer)
|
||||
}
|
||||
|
||||
if !bytes.Equal(deal.Proposal.PieceRef, baseCid.Bytes()) {
|
||||
return xerrors.Errorf("Deal Payload CID %s, Data Transfer CID %s: %w", string(deal.Proposal.PieceRef), baseCid.String(), ErrWrongPiece)
|
||||
}
|
||||
for _, state := range DataTransferStates {
|
||||
if deal.State == state {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return xerrors.Errorf("Deal State %s: %w", deal.State, ErrInacceptableDealState)
|
||||
}
|
||||
|
||||
// ValidatePull validates a pull request received from the peer that will receive data.
|
||||
// Will always error because providers should not accept pull requests from a client
|
||||
// in a storage deal (i.e. send data to client).
|
||||
func (m *ProviderRequestValidator) ValidatePull(
|
||||
receiver peer.ID,
|
||||
voucher datatransfer.Voucher,
|
||||
baseCid cid.Cid,
|
||||
Selector ipld.Node) error {
|
||||
return ErrNoPullAccepted
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
package deals_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-datastore"
|
||||
"github.com/ipfs/go-datastore/namespace"
|
||||
dss "github.com/ipfs/go-datastore/sync"
|
||||
blocksutil "github.com/ipfs/go-ipfs-blocksutil"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
xerrors "golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/deals"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/cborutil"
|
||||
"github.com/filecoin-project/lotus/lib/statestore"
|
||||
)
|
||||
|
||||
var blockGenerator = blocksutil.NewBlockGenerator()
|
||||
|
||||
type wrongDTType struct {
|
||||
}
|
||||
|
||||
func (wrongDTType) ToBytes() ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
func (wrongDTType) FromBytes([]byte) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (wrongDTType) Identifier() string {
|
||||
return "WrongDTTYPE"
|
||||
}
|
||||
|
||||
func uniqueStorageDealProposal() (actors.StorageDealProposal, error) {
|
||||
clientAddr, err := address.NewIDAddress(uint64(rand.Int()))
|
||||
if err != nil {
|
||||
return actors.StorageDealProposal{}, err
|
||||
}
|
||||
providerAddr, err := address.NewIDAddress(uint64(rand.Int()))
|
||||
if err != nil {
|
||||
return actors.StorageDealProposal{}, err
|
||||
}
|
||||
return actors.StorageDealProposal{
|
||||
PieceRef: blockGenerator.Next().Cid().Bytes(),
|
||||
Client: clientAddr,
|
||||
Provider: providerAddr,
|
||||
ProposerSignature: &types.Signature{
|
||||
Data: []byte("foo bar cat dog"),
|
||||
Type: types.KTBLS,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newClientDeal(minerID peer.ID, state api.DealState) (deals.ClientDeal, error) {
|
||||
newProposal, err := uniqueStorageDealProposal()
|
||||
if err != nil {
|
||||
return deals.ClientDeal{}, err
|
||||
}
|
||||
proposalNd, err := cborutil.AsIpld(&newProposal)
|
||||
if err != nil {
|
||||
return deals.ClientDeal{}, err
|
||||
}
|
||||
minerAddr, err := address.NewIDAddress(uint64(rand.Int()))
|
||||
if err != nil {
|
||||
return deals.ClientDeal{}, err
|
||||
}
|
||||
|
||||
return deals.ClientDeal{
|
||||
Proposal: newProposal,
|
||||
ProposalCid: proposalNd.Cid(),
|
||||
Miner: minerID,
|
||||
MinerWorker: minerAddr,
|
||||
State: state,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newMinerDeal(clientID peer.ID, state api.DealState) (deals.MinerDeal, error) {
|
||||
newProposal, err := uniqueStorageDealProposal()
|
||||
if err != nil {
|
||||
return deals.MinerDeal{}, err
|
||||
}
|
||||
proposalNd, err := cborutil.AsIpld(&newProposal)
|
||||
if err != nil {
|
||||
return deals.MinerDeal{}, err
|
||||
}
|
||||
ref, err := cid.Cast(newProposal.PieceRef)
|
||||
if err != nil {
|
||||
return deals.MinerDeal{}, err
|
||||
}
|
||||
|
||||
return deals.MinerDeal{
|
||||
Proposal: newProposal,
|
||||
ProposalCid: proposalNd.Cid(),
|
||||
Client: clientID,
|
||||
State: state,
|
||||
Ref: ref,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestClientRequestValidation(t *testing.T) {
|
||||
ds := dss.MutexWrap(datastore.NewMapDatastore())
|
||||
state := statestore.New(namespace.Wrap(ds, datastore.NewKey("/deals/client")))
|
||||
|
||||
crv := deals.NewClientRequestValidator(state)
|
||||
minerID := peer.ID("fakepeerid")
|
||||
block := blockGenerator.Next()
|
||||
t.Run("ValidatePush fails", func(t *testing.T) {
|
||||
if !xerrors.Is(crv.ValidatePush(minerID, wrongDTType{}, block.Cid(), nil), deals.ErrNoPushAccepted) {
|
||||
t.Fatal("Push should fail for the client request validator for storage deals")
|
||||
}
|
||||
})
|
||||
t.Run("ValidatePull fails deal not found", func(t *testing.T) {
|
||||
proposal, err := uniqueStorageDealProposal()
|
||||
if err != nil {
|
||||
t.Fatal("error creating proposal")
|
||||
}
|
||||
proposalNd, err := cborutil.AsIpld(&proposal)
|
||||
if err != nil {
|
||||
t.Fatal("error serializing proposal")
|
||||
}
|
||||
pieceRef, err := cid.Cast(proposal.PieceRef)
|
||||
if err != nil {
|
||||
t.Fatal("unable to construct piece cid")
|
||||
}
|
||||
if !xerrors.Is(crv.ValidatePull(minerID, &deals.StorageDataTransferVoucher{proposalNd.Cid(), 1}, pieceRef, nil), deals.ErrNoDeal) {
|
||||
t.Fatal("Pull should fail if there is no deal stored")
|
||||
}
|
||||
})
|
||||
t.Run("ValidatePull fails wrong client", func(t *testing.T) {
|
||||
otherMiner := peer.ID("otherminer")
|
||||
clientDeal, err := newClientDeal(otherMiner, api.DealAccepted)
|
||||
if err != nil {
|
||||
t.Fatal("error creating client deal")
|
||||
}
|
||||
if err := state.Begin(clientDeal.ProposalCid, &clientDeal); err != nil {
|
||||
t.Fatal("deal tracking failed")
|
||||
}
|
||||
pieceRef, err := cid.Cast(clientDeal.Proposal.PieceRef)
|
||||
if err != nil {
|
||||
t.Fatal("unable to construct piece cid")
|
||||
}
|
||||
if !xerrors.Is(crv.ValidatePull(minerID, &deals.StorageDataTransferVoucher{clientDeal.ProposalCid, 1}, pieceRef, nil), deals.ErrWrongPeer) {
|
||||
t.Fatal("Pull should fail if miner address is incorrect")
|
||||
}
|
||||
})
|
||||
t.Run("ValidatePull fails wrong piece ref", func(t *testing.T) {
|
||||
clientDeal, err := newClientDeal(minerID, api.DealAccepted)
|
||||
if err != nil {
|
||||
t.Fatal("error creating client deal")
|
||||
}
|
||||
if err := state.Begin(clientDeal.ProposalCid, &clientDeal); err != nil {
|
||||
t.Fatal("deal tracking failed")
|
||||
}
|
||||
if !xerrors.Is(crv.ValidatePull(minerID, &deals.StorageDataTransferVoucher{clientDeal.ProposalCid, 1}, blockGenerator.Next().Cid(), nil), deals.ErrWrongPiece) {
|
||||
t.Fatal("Pull should fail if piece ref is incorrect")
|
||||
}
|
||||
})
|
||||
t.Run("ValidatePull fails wrong deal state", func(t *testing.T) {
|
||||
clientDeal, err := newClientDeal(minerID, api.DealComplete)
|
||||
if err != nil {
|
||||
t.Fatal("error creating client deal")
|
||||
}
|
||||
if err := state.Begin(clientDeal.ProposalCid, &clientDeal); err != nil {
|
||||
t.Fatal("deal tracking failed")
|
||||
}
|
||||
pieceRef, err := cid.Cast(clientDeal.Proposal.PieceRef)
|
||||
if err != nil {
|
||||
t.Fatal("unable to construct piece cid")
|
||||
}
|
||||
if !xerrors.Is(crv.ValidatePull(minerID, &deals.StorageDataTransferVoucher{clientDeal.ProposalCid, 1}, pieceRef, nil), deals.ErrInacceptableDealState) {
|
||||
t.Fatal("Pull should fail if deal is in a state that cannot be data transferred")
|
||||
}
|
||||
})
|
||||
t.Run("ValidatePull succeeds", func(t *testing.T) {
|
||||
clientDeal, err := newClientDeal(minerID, api.DealAccepted)
|
||||
if err != nil {
|
||||
t.Fatal("error creating client deal")
|
||||
}
|
||||
if err := state.Begin(clientDeal.ProposalCid, &clientDeal); err != nil {
|
||||
t.Fatal("deal tracking failed")
|
||||
}
|
||||
pieceRef, err := cid.Cast(clientDeal.Proposal.PieceRef)
|
||||
if err != nil {
|
||||
t.Fatal("unable to construct piece cid")
|
||||
}
|
||||
if crv.ValidatePull(minerID, &deals.StorageDataTransferVoucher{clientDeal.ProposalCid, 1}, pieceRef, nil) != nil {
|
||||
t.Fatal("Pull should should succeed when all parameters are correct")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestProviderRequestValidation(t *testing.T) {
|
||||
ds := dss.MutexWrap(datastore.NewMapDatastore())
|
||||
state := statestore.New(namespace.Wrap(ds, datastore.NewKey("/deals/client")))
|
||||
|
||||
mrv := deals.NewProviderRequestValidator(state)
|
||||
clientID := peer.ID("fakepeerid")
|
||||
block := blockGenerator.Next()
|
||||
t.Run("ValidatePull fails", func(t *testing.T) {
|
||||
if !xerrors.Is(mrv.ValidatePull(clientID, wrongDTType{}, block.Cid(), nil), deals.ErrNoPullAccepted) {
|
||||
t.Fatal("Pull should fail for the provider request validator for storage deals")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ValidatePush fails deal not found", func(t *testing.T) {
|
||||
proposal, err := uniqueStorageDealProposal()
|
||||
if err != nil {
|
||||
t.Fatal("error creating proposal")
|
||||
}
|
||||
proposalNd, err := cborutil.AsIpld(&proposal)
|
||||
if err != nil {
|
||||
t.Fatal("error serializing proposal")
|
||||
}
|
||||
pieceRef, err := cid.Cast(proposal.PieceRef)
|
||||
if err != nil {
|
||||
t.Fatal("unable to construct piece cid")
|
||||
}
|
||||
if !xerrors.Is(mrv.ValidatePush(clientID, &deals.StorageDataTransferVoucher{proposalNd.Cid(), 1}, pieceRef, nil), deals.ErrNoDeal) {
|
||||
t.Fatal("Push should fail if there is no deal stored")
|
||||
}
|
||||
})
|
||||
t.Run("ValidatePush fails wrong miner", func(t *testing.T) {
|
||||
otherClient := peer.ID("otherclient")
|
||||
minerDeal, err := newMinerDeal(otherClient, api.DealAccepted)
|
||||
if err != nil {
|
||||
t.Fatal("error creating client deal")
|
||||
}
|
||||
if err := state.Begin(minerDeal.ProposalCid, &minerDeal); err != nil {
|
||||
t.Fatal("deal tracking failed")
|
||||
}
|
||||
pieceRef, err := cid.Cast(minerDeal.Proposal.PieceRef)
|
||||
if err != nil {
|
||||
t.Fatal("unable to construct piece cid")
|
||||
}
|
||||
if !xerrors.Is(mrv.ValidatePush(clientID, &deals.StorageDataTransferVoucher{minerDeal.ProposalCid, 1}, pieceRef, nil), deals.ErrWrongPeer) {
|
||||
t.Fatal("Push should fail if miner address is incorrect")
|
||||
}
|
||||
})
|
||||
t.Run("ValidatePush fails wrong piece ref", func(t *testing.T) {
|
||||
minerDeal, err := newMinerDeal(clientID, api.DealAccepted)
|
||||
if err != nil {
|
||||
t.Fatal("error creating client deal")
|
||||
}
|
||||
if err := state.Begin(minerDeal.ProposalCid, &minerDeal); err != nil {
|
||||
t.Fatal("deal tracking failed")
|
||||
}
|
||||
if !xerrors.Is(mrv.ValidatePush(clientID, &deals.StorageDataTransferVoucher{minerDeal.ProposalCid, 1}, blockGenerator.Next().Cid(), nil), deals.ErrWrongPiece) {
|
||||
t.Fatal("Push should fail if piece ref is incorrect")
|
||||
}
|
||||
})
|
||||
t.Run("ValidatePush fails wrong deal state", func(t *testing.T) {
|
||||
minerDeal, err := newMinerDeal(clientID, api.DealComplete)
|
||||
if err != nil {
|
||||
t.Fatal("error creating client deal")
|
||||
}
|
||||
if err := state.Begin(minerDeal.ProposalCid, &minerDeal); err != nil {
|
||||
t.Fatal("deal tracking failed")
|
||||
}
|
||||
pieceRef, err := cid.Cast(minerDeal.Proposal.PieceRef)
|
||||
if err != nil {
|
||||
t.Fatal("unable to construct piece cid")
|
||||
}
|
||||
if !xerrors.Is(mrv.ValidatePush(clientID, &deals.StorageDataTransferVoucher{minerDeal.ProposalCid, 1}, pieceRef, nil), deals.ErrInacceptableDealState) {
|
||||
t.Fatal("Push should fail if deal is in a state that cannot be data transferred")
|
||||
}
|
||||
})
|
||||
t.Run("ValidatePush succeeds", func(t *testing.T) {
|
||||
minerDeal, err := newMinerDeal(clientID, api.DealAccepted)
|
||||
if err != nil {
|
||||
t.Fatal("error creating client deal")
|
||||
}
|
||||
if err := state.Begin(minerDeal.ProposalCid, &minerDeal); err != nil {
|
||||
t.Fatal("deal tracking failed")
|
||||
}
|
||||
pieceRef, err := cid.Cast(minerDeal.Proposal.PieceRef)
|
||||
if err != nil {
|
||||
t.Fatal("unable to construct piece cid")
|
||||
}
|
||||
if mrv.ValidatePush(clientID, &deals.StorageDataTransferVoucher{minerDeal.ProposalCid, 1}, pieceRef, nil) != nil {
|
||||
t.Fatal("Push should should succeed when all parameters are correct")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package deals
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/cborutil"
|
||||
"github.com/ipfs/go-cid"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrWrongVoucherType means the voucher was not the correct type can validate against
|
||||
ErrWrongVoucherType = errors.New("cannot validate voucher type.")
|
||||
|
||||
// ErrNoPushAccepted just means clients do not accept pushes for storage deals
|
||||
ErrNoPushAccepted = errors.New("client should not receive data for a storage deal.")
|
||||
|
||||
// ErrNoPullAccepted just means providers do not accept pulls for storage deals
|
||||
ErrNoPullAccepted = errors.New("provider should not send data for a storage deal.")
|
||||
|
||||
// ErrNoDeal means no active deal was found for this vouchers proposal cid
|
||||
ErrNoDeal = errors.New("no deal found for this proposal.")
|
||||
|
||||
// ErrWrongPeer means that the other peer for this data transfer request does not match
|
||||
// the other peer for the deal
|
||||
ErrWrongPeer = errors.New("data Transfer peer id and Deal peer id do not match.")
|
||||
|
||||
// ErrWrongPiece means that the pieceref for this data transfer request does not match
|
||||
// the one specified in the deal
|
||||
ErrWrongPiece = errors.New("base CID for deal does not match CID for piece.")
|
||||
|
||||
// ErrInacceptableDealState means the deal for this transfer is not in a deal state
|
||||
// where transfer can be performed
|
||||
ErrInacceptableDealState = errors.New("deal is not a in a state where deals are accepted.")
|
||||
|
||||
// DataTransferStates are the states in which it would make sense to actually start a data transfer
|
||||
DataTransferStates = []api.DealState{api.DealAccepted, api.DealUnknown}
|
||||
)
|
||||
|
||||
const DealProtocolID = "/fil/storage/mk/1.0.1"
|
||||
const AskProtocolID = "/fil/storage/ask/1.0.1"
|
||||
|
||||
type Proposal struct {
|
||||
DealProposal *actors.StorageDealProposal
|
||||
|
||||
Piece cid.Cid // Used for retrieving from the client
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
State api.DealState
|
||||
|
||||
// DealProposalRejected
|
||||
Message string
|
||||
Proposal cid.Cid
|
||||
|
||||
// DealAccepted
|
||||
StorageDealSubmission *types.SignedMessage
|
||||
}
|
||||
|
||||
// TODO: Do we actually need this to be signed?
|
||||
type SignedResponse struct {
|
||||
Response Response
|
||||
|
||||
Signature *types.Signature
|
||||
}
|
||||
|
||||
func (r *SignedResponse) Verify(addr address.Address) error {
|
||||
b, err := cborutil.Dump(&r.Response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.Signature.Verify(addr, b)
|
||||
}
|
||||
|
||||
type AskRequest struct {
|
||||
Miner address.Address
|
||||
}
|
||||
|
||||
type AskResponse struct {
|
||||
Ask *types.SignedStorageAsk
|
||||
}
|
||||
|
||||
// StorageDataTransferVoucher is the voucher type for data transfers
|
||||
// used by the storage market
|
||||
type StorageDataTransferVoucher struct {
|
||||
Proposal cid.Cid
|
||||
DealID uint64
|
||||
}
|
||||
|
||||
// ToBytes converts the StorageDataTransferVoucher to raw bytes
|
||||
func (dv *StorageDataTransferVoucher) ToBytes() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
err := dv.MarshalCBOR(&buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// FromBytes converts the StorageDataTransferVoucher to raw bytes
|
||||
func (dv *StorageDataTransferVoucher) FromBytes(raw []byte) error {
|
||||
r := bytes.NewReader(raw)
|
||||
return dv.UnmarshalCBOR(r)
|
||||
}
|
||||
|
||||
// Identifier is the unique string identifier for a StorageDataTransferVoucher
|
||||
func (dv *StorageDataTransferVoucher) Identifier() string {
|
||||
return "StorageDataTransferVoucher"
|
||||
}
|
||||
+26
-28
@@ -5,21 +5,22 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/ipfs/go-cid"
|
||||
logging "github.com/ipfs/go-log"
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
var log = logging.Logger("events")
|
||||
|
||||
// `curH`-`ts.Height` = `confidence`
|
||||
type HeightHandler func(ctx context.Context, ts *types.TipSet, curH uint64) error
|
||||
// HeightHandler `curH`-`ts.Height` = `confidence`
|
||||
type HeightHandler func(ctx context.Context, ts *types.TipSet, curH abi.ChainEpoch) error
|
||||
type RevertHandler func(ctx context.Context, ts *types.TipSet) error
|
||||
|
||||
type heightHandler struct {
|
||||
@@ -30,17 +31,18 @@ type heightHandler struct {
|
||||
revert RevertHandler
|
||||
}
|
||||
|
||||
type eventApi interface {
|
||||
ChainNotify(context.Context) (<-chan []*store.HeadChange, error)
|
||||
type eventAPI interface {
|
||||
ChainNotify(context.Context) (<-chan []*api.HeadChange, error)
|
||||
ChainGetBlockMessages(context.Context, cid.Cid) (*api.BlockMessages, error)
|
||||
ChainGetTipSetByHeight(context.Context, uint64, *types.TipSet) (*types.TipSet, error)
|
||||
StateGetReceipt(context.Context, cid.Cid, *types.TipSet) (*types.MessageReceipt, error)
|
||||
ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error)
|
||||
StateGetReceipt(context.Context, cid.Cid, types.TipSetKey) (*types.MessageReceipt, error)
|
||||
ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error)
|
||||
|
||||
StateGetActor(ctx context.Context, actor address.Address, ts *types.TipSet) (*types.Actor, error) // optional / for CalledMsg
|
||||
StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) // optional / for CalledMsg
|
||||
}
|
||||
|
||||
type Events struct {
|
||||
api eventApi
|
||||
api eventAPI
|
||||
|
||||
tsc *tipSetCache
|
||||
lk sync.Mutex
|
||||
@@ -49,10 +51,10 @@ type Events struct {
|
||||
readyOnce sync.Once
|
||||
|
||||
heightEvents
|
||||
calledEvents
|
||||
*hcEvents
|
||||
}
|
||||
|
||||
func NewEvents(ctx context.Context, api eventApi) *Events {
|
||||
func NewEvents(ctx context.Context, api eventAPI) *Events {
|
||||
gcConfidence := 2 * build.ForkLengthThreshold
|
||||
|
||||
tsc := newTSCache(gcConfidence, api.ChainGetTipSetByHeight)
|
||||
@@ -65,25 +67,14 @@ func NewEvents(ctx context.Context, api eventApi) *Events {
|
||||
heightEvents: heightEvents{
|
||||
tsc: tsc,
|
||||
ctx: ctx,
|
||||
gcConfidence: uint64(gcConfidence),
|
||||
gcConfidence: gcConfidence,
|
||||
|
||||
heightTriggers: map[uint64]*heightHandler{},
|
||||
htTriggerHeights: map[uint64][]uint64{},
|
||||
htHeights: map[uint64][]uint64{},
|
||||
htTriggerHeights: map[abi.ChainEpoch][]uint64{},
|
||||
htHeights: map[abi.ChainEpoch][]uint64{},
|
||||
},
|
||||
|
||||
calledEvents: calledEvents{
|
||||
cs: api,
|
||||
tsc: tsc,
|
||||
ctx: ctx,
|
||||
gcConfidence: uint64(gcConfidence),
|
||||
|
||||
confQueue: map[triggerH]map[msgH][]*queuedEvent{},
|
||||
revertQueue: map[msgH][]triggerH{},
|
||||
triggers: map[triggerId]*callHandler{},
|
||||
matchers: map[triggerId][]MatchFunc{},
|
||||
timeouts: map[uint64]map[triggerId]int{},
|
||||
},
|
||||
hcEvents: newHCEvents(ctx, api, tsc, uint64(gcConfidence)),
|
||||
}
|
||||
|
||||
e.ready.Add(1)
|
||||
@@ -141,6 +132,8 @@ func (e *Events) listenHeadChangesOnce(ctx context.Context) error {
|
||||
}
|
||||
|
||||
e.readyOnce.Do(func() {
|
||||
e.lastTs = cur[0].Val
|
||||
|
||||
e.ready.Done()
|
||||
})
|
||||
|
||||
@@ -160,6 +153,11 @@ func (e *Events) listenHeadChangesOnce(ctx context.Context) error {
|
||||
if err := e.headChange(rev, app); err != nil {
|
||||
log.Warnf("headChange failed: %s", err)
|
||||
}
|
||||
|
||||
// sync with fake chainstore (for tests)
|
||||
if fcs, ok := e.api.(interface{ notifDone() }); ok {
|
||||
fcs.notifDone()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -177,5 +175,5 @@ func (e *Events) headChange(rev, app []*types.TipSet) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.headChangeCalled(rev, app)
|
||||
return e.processHeadChangeEvent(rev, app)
|
||||
}
|
||||
|
||||
+387
-123
@@ -5,69 +5,79 @@ import (
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/ipfs/go-cid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
const NoTimeout = math.MaxUint64
|
||||
const NoTimeout = math.MaxInt64
|
||||
const NoHeight = abi.ChainEpoch(-1)
|
||||
|
||||
type triggerId = uint64
|
||||
type triggerID = uint64
|
||||
|
||||
// msgH is the block height at which a message was present / event has happened
|
||||
type msgH = uint64
|
||||
type msgH = abi.ChainEpoch
|
||||
|
||||
// triggerH is the block height at which the listener will be notified about the
|
||||
// message (msgH+confidence)
|
||||
type triggerH = uint64
|
||||
type triggerH = abi.ChainEpoch
|
||||
|
||||
// `ts` is the tipset, in which the `msg` is included.
|
||||
type eventData interface{}
|
||||
|
||||
// EventHandler arguments:
|
||||
// `prevTs` is the previous tipset, eg the "from" tipset for a state change.
|
||||
// `ts` is the event tipset, eg the tipset in which the `msg` is included.
|
||||
// `curH`-`ts.Height` = `confidence`
|
||||
type CalledHandler func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH uint64) (more bool, err error)
|
||||
type EventHandler func(data eventData, prevTs, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error)
|
||||
|
||||
// CheckFunc is used for atomicity guarantees. If the condition the callbacks
|
||||
// wait for has already happened in tipset `ts`
|
||||
//
|
||||
// If `done` is true, timeout won't be triggered
|
||||
// If `more` is false, no messages will be sent to CalledHandler (RevertHandler
|
||||
// If `more` is false, no messages will be sent to EventHandler (RevertHandler
|
||||
// may still be called)
|
||||
type CheckFunc func(ts *types.TipSet) (done bool, more bool, err error)
|
||||
|
||||
type MatchFunc func(msg *types.Message) (bool, error)
|
||||
|
||||
type callHandler struct {
|
||||
// Keep track of information for an event handler
|
||||
type handlerInfo struct {
|
||||
confidence int
|
||||
timeout uint64
|
||||
timeout abi.ChainEpoch
|
||||
|
||||
disabled bool // TODO: GC after gcConfidence reached
|
||||
|
||||
handle CalledHandler
|
||||
handle EventHandler
|
||||
revert RevertHandler
|
||||
}
|
||||
|
||||
// When a change occurs, a queuedEvent is created and put into a queue
|
||||
// until the required confidence is reached
|
||||
type queuedEvent struct {
|
||||
trigger triggerId
|
||||
trigger triggerID
|
||||
|
||||
h uint64
|
||||
msg *types.Message
|
||||
prevH abi.ChainEpoch
|
||||
h abi.ChainEpoch
|
||||
data eventData
|
||||
|
||||
called bool
|
||||
}
|
||||
|
||||
type calledEvents struct {
|
||||
cs eventApi
|
||||
// Manages chain head change events, which may be forward (new tipset added to
|
||||
// chain) or backward (chain branch discarded in favour of heavier branch)
|
||||
type hcEvents struct {
|
||||
cs eventAPI
|
||||
tsc *tipSetCache
|
||||
ctx context.Context
|
||||
gcConfidence uint64
|
||||
|
||||
lastTs *types.TipSet
|
||||
|
||||
lk sync.Mutex
|
||||
|
||||
ctr triggerId
|
||||
ctr triggerID
|
||||
|
||||
triggers map[triggerId]*callHandler
|
||||
matchers map[triggerId][]MatchFunc
|
||||
triggers map[triggerID]*handlerInfo
|
||||
|
||||
// maps block heights to events
|
||||
// [triggerH][msgH][event]
|
||||
@@ -76,27 +86,81 @@ type calledEvents struct {
|
||||
// [msgH][triggerH]
|
||||
revertQueue map[msgH][]triggerH
|
||||
|
||||
// [timeoutH+confidence][triggerId]{calls}
|
||||
timeouts map[uint64]map[triggerId]int
|
||||
// [timeoutH+confidence][triggerID]{calls}
|
||||
timeouts map[abi.ChainEpoch]map[triggerID]int
|
||||
|
||||
messageEvents
|
||||
watcherEvents
|
||||
}
|
||||
|
||||
func (e *calledEvents) headChangeCalled(rev, app []*types.TipSet) error {
|
||||
func newHCEvents(ctx context.Context, cs eventAPI, tsc *tipSetCache, gcConfidence uint64) *hcEvents {
|
||||
e := hcEvents{
|
||||
ctx: ctx,
|
||||
cs: cs,
|
||||
tsc: tsc,
|
||||
gcConfidence: gcConfidence,
|
||||
|
||||
confQueue: map[triggerH]map[msgH][]*queuedEvent{},
|
||||
revertQueue: map[msgH][]triggerH{},
|
||||
triggers: map[triggerID]*handlerInfo{},
|
||||
timeouts: map[abi.ChainEpoch]map[triggerID]int{},
|
||||
}
|
||||
|
||||
e.messageEvents = newMessageEvents(ctx, &e, cs)
|
||||
e.watcherEvents = newWatcherEvents(ctx, &e, cs)
|
||||
|
||||
return &e
|
||||
}
|
||||
|
||||
// Called when there is a change to the head with tipsets to be
|
||||
// reverted / applied
|
||||
func (e *hcEvents) processHeadChangeEvent(rev, app []*types.TipSet) error {
|
||||
e.lk.Lock()
|
||||
defer e.lk.Unlock()
|
||||
|
||||
for _, ts := range rev {
|
||||
e.handleReverts(ts)
|
||||
e.lastTs = ts
|
||||
}
|
||||
|
||||
for _, ts := range app {
|
||||
// called triggers
|
||||
// Check if the head change caused any state changes that we were
|
||||
// waiting for
|
||||
stateChanges := e.watcherEvents.checkStateChanges(e.lastTs, ts)
|
||||
|
||||
e.checkNewCalls(ts)
|
||||
e.applyWithConfidence(ts)
|
||||
e.applyTimeouts(ts)
|
||||
// Queue up calls until there have been enough blocks to reach
|
||||
// confidence on the state changes
|
||||
for tid, data := range stateChanges {
|
||||
e.queueForConfidence(tid, data, e.lastTs, ts)
|
||||
}
|
||||
|
||||
// Check if the head change included any new message calls
|
||||
newCalls, err := e.messageEvents.checkNewCalls(ts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Queue up calls until there have been enough blocks to reach
|
||||
// confidence on the message calls
|
||||
for tid, data := range newCalls {
|
||||
e.queueForConfidence(tid, data, nil, ts)
|
||||
}
|
||||
|
||||
for at := e.lastTs.Height(); at <= ts.Height(); at++ {
|
||||
// Apply any queued events and timeouts that were targeted at the
|
||||
// current chain height
|
||||
e.applyWithConfidence(ts, at)
|
||||
e.applyTimeouts(ts)
|
||||
}
|
||||
|
||||
// Update the latest known tipset
|
||||
e.lastTs = ts
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *calledEvents) handleReverts(ts *types.TipSet) {
|
||||
func (e *hcEvents) handleReverts(ts *types.TipSet) {
|
||||
reverts, ok := e.revertQueue[ts.Height()]
|
||||
if !ok {
|
||||
return // nothing to do
|
||||
@@ -112,7 +176,7 @@ func (e *calledEvents) handleReverts(ts *types.TipSet) {
|
||||
trigger := e.triggers[event.trigger]
|
||||
|
||||
if err := trigger.revert(e.ctx, ts); err != nil {
|
||||
log.Errorf("reverting chain trigger (call %s.%d() @H %d, called @ %d) failed: %s", event.msg.To, event.msg.Method, ts.Height(), triggerH, err)
|
||||
log.Errorf("reverting chain trigger (@H %d, triggered @ %d) failed: %s", ts.Height(), triggerH, err)
|
||||
}
|
||||
}
|
||||
delete(e.confQueue[triggerH], ts.Height())
|
||||
@@ -120,66 +184,46 @@ func (e *calledEvents) handleReverts(ts *types.TipSet) {
|
||||
delete(e.revertQueue, ts.Height())
|
||||
}
|
||||
|
||||
func (e *calledEvents) checkNewCalls(ts *types.TipSet) {
|
||||
e.messagesForTs(ts, func(msg *types.Message) {
|
||||
// TODO: provide receipts
|
||||
// Queue up events until the chain has reached a height that reflects the
|
||||
// desired confidence
|
||||
func (e *hcEvents) queueForConfidence(trigID uint64, data eventData, prevTs, ts *types.TipSet) {
|
||||
trigger := e.triggers[trigID]
|
||||
|
||||
for tid, matchFns := range e.matchers {
|
||||
var matched bool
|
||||
for _, matchFn := range matchFns {
|
||||
ok, err := matchFn(msg)
|
||||
if err != nil {
|
||||
log.Warnf("event matcher failed: %s")
|
||||
continue
|
||||
}
|
||||
matched = ok
|
||||
prevH := NoHeight
|
||||
if prevTs != nil {
|
||||
prevH = prevTs.Height()
|
||||
}
|
||||
appliedH := ts.Height()
|
||||
|
||||
if matched {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
e.queueForConfidence(tid, msg, ts)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *calledEvents) queueForConfidence(triggerId uint64, msg *types.Message, ts *types.TipSet) {
|
||||
trigger := e.triggers[triggerId]
|
||||
|
||||
// messages are not applied in the tipset they are included in
|
||||
appliedH := ts.Height() + 1
|
||||
|
||||
triggerH := appliedH + uint64(trigger.confidence)
|
||||
triggerH := appliedH + abi.ChainEpoch(trigger.confidence)
|
||||
|
||||
byOrigH, ok := e.confQueue[triggerH]
|
||||
if !ok {
|
||||
byOrigH = map[uint64][]*queuedEvent{}
|
||||
byOrigH = map[abi.ChainEpoch][]*queuedEvent{}
|
||||
e.confQueue[triggerH] = byOrigH
|
||||
}
|
||||
|
||||
byOrigH[appliedH] = append(byOrigH[appliedH], &queuedEvent{
|
||||
trigger: triggerId,
|
||||
trigger: trigID,
|
||||
prevH: prevH,
|
||||
h: appliedH,
|
||||
msg: msg,
|
||||
data: data,
|
||||
})
|
||||
|
||||
e.revertQueue[appliedH] = append(e.revertQueue[appliedH], triggerH)
|
||||
}
|
||||
|
||||
func (e *calledEvents) applyWithConfidence(ts *types.TipSet) {
|
||||
byOrigH, ok := e.confQueue[ts.Height()]
|
||||
// Apply any events that were waiting for this chain height for confidence
|
||||
func (e *hcEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpoch) {
|
||||
byOrigH, ok := e.confQueue[height]
|
||||
if !ok {
|
||||
return // no triggers at thin height
|
||||
return // no triggers at this height
|
||||
}
|
||||
|
||||
for origH, events := range byOrigH {
|
||||
triggerTs, err := e.tsc.get(origH)
|
||||
if err != nil {
|
||||
log.Errorf("events: applyWithConfidence didn't find tipset for event; wanted %d; current %d", origH, ts.Height())
|
||||
log.Errorf("events: applyWithConfidence didn't find tipset for event; wanted %d; current %d", origH, height)
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
@@ -192,15 +236,20 @@ func (e *calledEvents) applyWithConfidence(ts *types.TipSet) {
|
||||
continue
|
||||
}
|
||||
|
||||
rec, err := e.cs.StateGetReceipt(e.ctx, event.msg.Cid(), ts)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
// Previous tipset - this is relevant for example in a state change
|
||||
// from one tipset to another
|
||||
var prevTs *types.TipSet
|
||||
if event.prevH != NoHeight {
|
||||
prevTs, err = e.tsc.get(event.prevH)
|
||||
if err != nil {
|
||||
log.Errorf("events: applyWithConfidence didn't find tipset for previous event; wanted %d; current %d", event.prevH, height)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
more, err := trigger.handle(event.msg, rec, triggerTs, ts.Height())
|
||||
more, err := trigger.handle(event.data, prevTs, triggerTs, height)
|
||||
if err != nil {
|
||||
log.Errorf("chain trigger (call %s.%d() @H %d, called @ %d) failed: %s", event.msg.To, event.msg.Method, origH, ts.Height(), err)
|
||||
log.Errorf("chain trigger (@H %d, triggered @ %d) failed: %s", origH, height, err)
|
||||
continue // don't revert failed calls
|
||||
}
|
||||
|
||||
@@ -216,24 +265,25 @@ func (e *calledEvents) applyWithConfidence(ts *types.TipSet) {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *calledEvents) applyTimeouts(ts *types.TipSet) {
|
||||
// Apply any timeouts that expire at this height
|
||||
func (e *hcEvents) applyTimeouts(ts *types.TipSet) {
|
||||
triggers, ok := e.timeouts[ts.Height()]
|
||||
if !ok {
|
||||
return // nothing to do
|
||||
}
|
||||
|
||||
for triggerId, calls := range triggers {
|
||||
for triggerID, calls := range triggers {
|
||||
if calls > 0 {
|
||||
continue // don't timeout if the method was called
|
||||
}
|
||||
trigger := e.triggers[triggerId]
|
||||
trigger := e.triggers[triggerID]
|
||||
if trigger.disabled {
|
||||
continue
|
||||
}
|
||||
|
||||
timeoutTs, err := e.tsc.get(ts.Height() - uint64(trigger.confidence))
|
||||
timeoutTs, err := e.tsc.get(ts.Height() - abi.ChainEpoch(trigger.confidence))
|
||||
if err != nil {
|
||||
log.Errorf("events: applyTimeouts didn't find tipset for event; wanted %d; current %d", ts.Height()-uint64(trigger.confidence), ts.Height())
|
||||
log.Errorf("events: applyTimeouts didn't find tipset for event; wanted %d; current %d", ts.Height()-abi.ChainEpoch(trigger.confidence), ts.Height())
|
||||
}
|
||||
|
||||
more, err := trigger.handle(nil, nil, timeoutTs, ts.Height())
|
||||
@@ -246,12 +296,225 @@ func (e *calledEvents) applyTimeouts(ts *types.TipSet) {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *calledEvents) messagesForTs(ts *types.TipSet, consume func(*types.Message)) {
|
||||
// Listen for an event
|
||||
// - CheckFunc: immediately checks if the event already occurred
|
||||
// - EventHandler: called when the event has occurred, after confidence tipsets
|
||||
// - RevertHandler: called if the chain head changes causing the event to revert
|
||||
// - confidence: wait this many tipsets before calling EventHandler
|
||||
// - timeout: at this chain height, timeout on waiting for this event
|
||||
func (e *hcEvents) onHeadChanged(check CheckFunc, hnd EventHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch) (triggerID, error) {
|
||||
e.lk.Lock()
|
||||
defer e.lk.Unlock()
|
||||
|
||||
// Check if the event has already occurred
|
||||
ts := e.tsc.best()
|
||||
done, more, err := check(ts)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("called check error (h: %d): %w", ts.Height(), err)
|
||||
}
|
||||
if done {
|
||||
timeout = NoTimeout
|
||||
}
|
||||
|
||||
// Create a trigger for the event
|
||||
id := e.ctr
|
||||
e.ctr++
|
||||
|
||||
e.triggers[id] = &handlerInfo{
|
||||
confidence: confidence,
|
||||
timeout: timeout + abi.ChainEpoch(confidence),
|
||||
|
||||
disabled: !more,
|
||||
|
||||
handle: hnd,
|
||||
revert: rev,
|
||||
}
|
||||
|
||||
// If there's a timeout, set up a timeout check at that height
|
||||
if timeout != NoTimeout {
|
||||
if e.timeouts[timeout+abi.ChainEpoch(confidence)] == nil {
|
||||
e.timeouts[timeout+abi.ChainEpoch(confidence)] = map[uint64]int{}
|
||||
}
|
||||
e.timeouts[timeout+abi.ChainEpoch(confidence)][id] = 0
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// headChangeAPI is used to allow the composed event APIs to call back to hcEvents
|
||||
// to listen for changes
|
||||
type headChangeAPI interface {
|
||||
onHeadChanged(check CheckFunc, hnd EventHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch) (triggerID, error)
|
||||
}
|
||||
|
||||
// watcherEvents watches for a state change
|
||||
type watcherEvents struct {
|
||||
ctx context.Context
|
||||
cs eventAPI
|
||||
hcAPI headChangeAPI
|
||||
|
||||
lk sync.RWMutex
|
||||
matchers map[triggerID]StateMatchFunc
|
||||
}
|
||||
|
||||
func newWatcherEvents(ctx context.Context, hcAPI headChangeAPI, cs eventAPI) watcherEvents {
|
||||
return watcherEvents{
|
||||
ctx: ctx,
|
||||
cs: cs,
|
||||
hcAPI: hcAPI,
|
||||
matchers: make(map[triggerID]StateMatchFunc),
|
||||
}
|
||||
}
|
||||
|
||||
// Run each of the matchers against the previous and current state to see if
|
||||
// there's a change
|
||||
func (we *watcherEvents) checkStateChanges(oldState, newState *types.TipSet) map[triggerID]eventData {
|
||||
we.lk.RLock()
|
||||
defer we.lk.RUnlock()
|
||||
|
||||
res := make(map[triggerID]eventData)
|
||||
for tid, matchFn := range we.matchers {
|
||||
ok, data, err := matchFn(oldState, newState)
|
||||
if err != nil {
|
||||
log.Errorf("event diff fn failed: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if ok {
|
||||
res[tid] = data
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// StateChange represents a change in state
|
||||
type StateChange interface{}
|
||||
|
||||
// StateChangeHandler arguments:
|
||||
// `oldTs` is the state "from" tipset
|
||||
// `newTs` is the state "to" tipset
|
||||
// `states` is the change in state
|
||||
// `curH`-`ts.Height` = `confidence`
|
||||
type StateChangeHandler func(oldTs, newTs *types.TipSet, states StateChange, curH abi.ChainEpoch) (more bool, err error)
|
||||
|
||||
type StateMatchFunc func(oldTs, newTs *types.TipSet) (bool, StateChange, error)
|
||||
|
||||
// StateChanged registers a callback which is triggered when a specified state
|
||||
// change occurs or a timeout is reached.
|
||||
//
|
||||
// * `CheckFunc` callback is invoked immediately with a recent tipset, it
|
||||
// returns two booleans - `done`, and `more`.
|
||||
//
|
||||
// * `done` should be true when some on-chain state change we are waiting
|
||||
// for has happened. When `done` is set to true, timeout trigger is disabled.
|
||||
//
|
||||
// * `more` should be false when we don't want to receive new notifications
|
||||
// through StateChangeHandler. Note that notifications may still be delivered to
|
||||
// RevertHandler
|
||||
//
|
||||
// * `StateChangeHandler` is called when the specified state change was observed
|
||||
// on-chain, and a confidence threshold was reached, or the specified `timeout`
|
||||
// height was reached with no state change observed. When this callback is
|
||||
// invoked on a timeout, `oldState` and `newState` are set to nil.
|
||||
// This callback returns a boolean specifying whether further notifications
|
||||
// should be sent, like `more` return param from `CheckFunc` above.
|
||||
//
|
||||
// * `RevertHandler` is called after apply handler, when we drop the tipset
|
||||
// containing the message. The tipset passed as the argument is the tipset
|
||||
// that is being dropped. Note that the event dropped may be re-applied
|
||||
// in a different tipset in small amount of time.
|
||||
//
|
||||
// * `StateMatchFunc` is called against each tipset state. If there is a match,
|
||||
// the state change is queued up until the confidence interval has elapsed (and
|
||||
// `StateChangeHandler` is called)
|
||||
func (we *watcherEvents) StateChanged(check CheckFunc, scHnd StateChangeHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, mf StateMatchFunc) error {
|
||||
hnd := func(data eventData, prevTs, ts *types.TipSet, height abi.ChainEpoch) (bool, error) {
|
||||
states, ok := data.(StateChange)
|
||||
if data != nil && !ok {
|
||||
panic("expected StateChange")
|
||||
}
|
||||
|
||||
return scHnd(prevTs, ts, states, height)
|
||||
}
|
||||
|
||||
id, err := we.hcAPI.onHeadChanged(check, hnd, rev, confidence, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
we.lk.Lock()
|
||||
defer we.lk.Unlock()
|
||||
we.matchers[id] = mf
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// messageEvents watches for message calls to actors
|
||||
type messageEvents struct {
|
||||
ctx context.Context
|
||||
cs eventAPI
|
||||
hcAPI headChangeAPI
|
||||
|
||||
lk sync.RWMutex
|
||||
matchers map[triggerID][]MsgMatchFunc
|
||||
}
|
||||
|
||||
func newMessageEvents(ctx context.Context, hcAPI headChangeAPI, cs eventAPI) messageEvents {
|
||||
return messageEvents{
|
||||
ctx: ctx,
|
||||
cs: cs,
|
||||
hcAPI: hcAPI,
|
||||
matchers: map[triggerID][]MsgMatchFunc{},
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there are any new actor calls
|
||||
func (me *messageEvents) checkNewCalls(ts *types.TipSet) (map[triggerID]eventData, error) {
|
||||
pts, err := me.cs.ChainGetTipSet(me.ctx, ts.Parents()) // we actually care about messages in the parent tipset here
|
||||
if err != nil {
|
||||
log.Errorf("getting parent tipset in checkNewCalls: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
me.lk.RLock()
|
||||
defer me.lk.RUnlock()
|
||||
|
||||
res := make(map[triggerID]eventData)
|
||||
me.messagesForTs(pts, func(msg *types.Message) {
|
||||
// TODO: provide receipts
|
||||
|
||||
for tid, matchFns := range me.matchers {
|
||||
var matched bool
|
||||
for _, matchFn := range matchFns {
|
||||
ok, err := matchFn(msg)
|
||||
if err != nil {
|
||||
log.Errorf("event matcher failed: %s", err)
|
||||
continue
|
||||
}
|
||||
matched = ok
|
||||
|
||||
if matched {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
res[tid] = msg
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Get the messages in a tipset
|
||||
func (me *messageEvents) messagesForTs(ts *types.TipSet, consume func(*types.Message)) {
|
||||
seen := map[cid.Cid]struct{}{}
|
||||
|
||||
for _, tsb := range ts.Blocks() {
|
||||
|
||||
msgs, err := e.cs.ChainGetBlockMessages(context.TODO(), tsb.Cid())
|
||||
msgs, err := me.cs.ChainGetBlockMessages(context.TODO(), tsb.Cid())
|
||||
if err != nil {
|
||||
log.Errorf("messagesForTs MessagesForBlock failed (ts.H=%d, Bcid:%s, B.Mcid:%s): %s", ts.Height(), tsb.Cid(), tsb.Messages, err)
|
||||
// this is quite bad, but probably better than missing all the other updates
|
||||
@@ -280,20 +543,27 @@ func (e *calledEvents) messagesForTs(ts *types.TipSet, consume func(*types.Messa
|
||||
}
|
||||
}
|
||||
|
||||
// Called registers a callbacks which are triggered when a specified method is
|
||||
// MsgHandler arguments:
|
||||
// `ts` is the tipset, in which the `msg` is included.
|
||||
// `curH`-`ts.Height` = `confidence`
|
||||
type MsgHandler func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error)
|
||||
|
||||
type MsgMatchFunc func(msg *types.Message) (bool, error)
|
||||
|
||||
// Called registers a callback which is triggered when a specified method is
|
||||
// called on an actor, or a timeout is reached.
|
||||
//
|
||||
// * `CheckFunc` callback is invoked immediately with a recent tipset, it
|
||||
// returns two booleans - `done`, and `more`.
|
||||
//
|
||||
// * `done` should be true when some on-chain action we are waiting for has
|
||||
// happened. When `done` is set to true, timeout trigger is disabled.
|
||||
// * `done` should be true when some on-chain action we are waiting for has
|
||||
// happened. When `done` is set to true, timeout trigger is disabled.
|
||||
//
|
||||
// * `more` should be false when we don't want to receive new notifications
|
||||
// through CalledHandler. Note that notifications may still be delivered to
|
||||
// RevertHandler
|
||||
// * `more` should be false when we don't want to receive new notifications
|
||||
// through MsgHandler. Note that notifications may still be delivered to
|
||||
// RevertHandler
|
||||
//
|
||||
// * `CalledHandler` is called when the specified event was observed on-chain,
|
||||
// * `MsgHandler` is called when the specified event was observed on-chain,
|
||||
// and a confidence threshold was reached, or the specified `timeout` height
|
||||
// was reached with no events observed. When this callback is invoked on a
|
||||
// timeout, `msg` is set to nil. This callback returns a boolean specifying
|
||||
@@ -304,44 +574,38 @@ func (e *calledEvents) messagesForTs(ts *types.TipSet, consume func(*types.Messa
|
||||
// containing the message. The tipset passed as the argument is the tipset
|
||||
// that is being dropped. Note that the message dropped may be re-applied
|
||||
// in a different tipset in small amount of time.
|
||||
func (e *calledEvents) Called(check CheckFunc, hnd CalledHandler, rev RevertHandler, confidence int, timeout uint64, mf MatchFunc) error {
|
||||
e.lk.Lock()
|
||||
defer e.lk.Unlock()
|
||||
|
||||
ts := e.tsc.best()
|
||||
done, more, err := check(ts)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("called check error (h: %d): %w", ts.Height(), err)
|
||||
}
|
||||
if done {
|
||||
timeout = NoTimeout
|
||||
}
|
||||
|
||||
id := e.ctr
|
||||
e.ctr++
|
||||
|
||||
e.triggers[id] = &callHandler{
|
||||
confidence: confidence,
|
||||
timeout: timeout + uint64(confidence),
|
||||
|
||||
disabled: !more,
|
||||
|
||||
handle: hnd,
|
||||
revert: rev,
|
||||
}
|
||||
|
||||
e.matchers[id] = append(e.matchers[id], mf)
|
||||
|
||||
if timeout != NoTimeout {
|
||||
if e.timeouts[timeout+uint64(confidence)] == nil {
|
||||
e.timeouts[timeout+uint64(confidence)] = map[uint64]int{}
|
||||
//
|
||||
// * `MsgMatchFunc` is called against each message. If there is a match, the
|
||||
// message is queued up until the confidence interval has elapsed (and
|
||||
// `MsgHandler` is called)
|
||||
func (me *messageEvents) Called(check CheckFunc, msgHnd MsgHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, mf MsgMatchFunc) error {
|
||||
hnd := func(data eventData, prevTs, ts *types.TipSet, height abi.ChainEpoch) (bool, error) {
|
||||
msg, ok := data.(*types.Message)
|
||||
if data != nil && !ok {
|
||||
panic("expected msg")
|
||||
}
|
||||
e.timeouts[timeout+uint64(confidence)][id] = 0
|
||||
|
||||
rec, err := me.cs.StateGetReceipt(me.ctx, msg.Cid(), ts.Key())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return msgHnd(msg, rec, ts, height)
|
||||
}
|
||||
|
||||
id, err := me.hcAPI.onHeadChanged(check, hnd, rev, confidence, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
me.lk.Lock()
|
||||
defer me.lk.Unlock()
|
||||
me.matchers[id] = append(me.matchers[id], mf)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *calledEvents) CalledMsg(ctx context.Context, hnd CalledHandler, rev RevertHandler, confidence int, timeout uint64, msg store.ChainMsg) error {
|
||||
return e.Called(e.CheckMsg(ctx, msg, hnd), hnd, rev, confidence, timeout, e.MatchMsg(msg.VMMessage()))
|
||||
// Convenience function for checking and matching messages
|
||||
func (me *messageEvents) CalledMsg(ctx context.Context, hnd MsgHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, msg types.ChainMsg) error {
|
||||
return me.Called(me.CheckMsg(ctx, msg, hnd), hnd, rev, confidence, timeout, me.MatchMsg(msg.VMMessage()))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"go.opencensus.io/trace"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
@@ -12,34 +13,40 @@ import (
|
||||
type heightEvents struct {
|
||||
lk sync.Mutex
|
||||
tsc *tipSetCache
|
||||
gcConfidence uint64
|
||||
gcConfidence abi.ChainEpoch
|
||||
|
||||
ctr triggerId
|
||||
ctr triggerID
|
||||
|
||||
heightTriggers map[triggerId]*heightHandler
|
||||
heightTriggers map[triggerID]*heightHandler
|
||||
|
||||
htTriggerHeights map[triggerH][]triggerId
|
||||
htHeights map[msgH][]triggerId
|
||||
htTriggerHeights map[triggerH][]triggerID
|
||||
htHeights map[msgH][]triggerID
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error {
|
||||
|
||||
ctx, span := trace.StartSpan(e.ctx, "events.HeightHeadChange")
|
||||
defer span.End()
|
||||
span.AddAttributes(trace.Int64Attribute("endHeight", int64(app[0].Height())))
|
||||
span.AddAttributes(trace.Int64Attribute("reverts", int64(len(rev))))
|
||||
span.AddAttributes(trace.Int64Attribute("applies", int64(len(app))))
|
||||
|
||||
e.lk.Lock()
|
||||
defer e.lk.Unlock()
|
||||
for _, ts := range rev {
|
||||
// TODO: log error if h below gcconfidence
|
||||
// revert height-based triggers
|
||||
|
||||
revert := func(h uint64, ts *types.TipSet) {
|
||||
revert := func(h abi.ChainEpoch, ts *types.TipSet) {
|
||||
for _, tid := range e.htHeights[h] {
|
||||
ctx, span := trace.StartSpan(ctx, "events.HeightRevert")
|
||||
|
||||
err := e.heightTriggers[tid].revert(ctx, ts)
|
||||
rev := e.heightTriggers[tid].revert
|
||||
e.lk.Unlock()
|
||||
err := rev(ctx, ts)
|
||||
e.lk.Lock()
|
||||
e.heightTriggers[tid].called = false
|
||||
|
||||
span.End()
|
||||
@@ -80,15 +87,14 @@ func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error {
|
||||
|
||||
// height triggers
|
||||
|
||||
apply := func(h uint64, ts *types.TipSet) error {
|
||||
apply := func(h abi.ChainEpoch, ts *types.TipSet) error {
|
||||
for _, tid := range e.htTriggerHeights[h] {
|
||||
hnd := e.heightTriggers[tid]
|
||||
if hnd.called {
|
||||
return nil
|
||||
}
|
||||
hnd.called = true
|
||||
|
||||
triggerH := h - uint64(hnd.confidence)
|
||||
triggerH := h - abi.ChainEpoch(hnd.confidence)
|
||||
|
||||
incTs, err := e.tsc.getNonNull(triggerH)
|
||||
if err != nil {
|
||||
@@ -97,8 +103,11 @@ func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error {
|
||||
|
||||
ctx, span := trace.StartSpan(ctx, "events.HeightApply")
|
||||
span.AddAttributes(trace.BoolAttribute("immediate", false))
|
||||
|
||||
err = hnd.handle(ctx, incTs, h)
|
||||
handle := hnd.handle
|
||||
e.lk.Unlock()
|
||||
err = handle(ctx, incTs, h)
|
||||
e.lk.Lock()
|
||||
hnd.called = true
|
||||
span.End()
|
||||
|
||||
if err != nil {
|
||||
@@ -139,13 +148,13 @@ func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error {
|
||||
// specified height, `RevertHandler` will be called.
|
||||
//
|
||||
// ts passed to handlers is the tipset at the specified, or above, if lower tipsets were null
|
||||
func (e *heightEvents) ChainAt(hnd HeightHandler, rev RevertHandler, confidence int, h uint64) error {
|
||||
func (e *heightEvents) ChainAt(hnd HeightHandler, rev RevertHandler, confidence int, h abi.ChainEpoch) error {
|
||||
|
||||
e.lk.Lock() // Tricky locking, check your locks if you modify this function!
|
||||
|
||||
bestH := e.tsc.best().Height()
|
||||
|
||||
if bestH >= h+uint64(confidence) {
|
||||
if bestH >= h+abi.ChainEpoch(confidence) {
|
||||
ts, err := e.tsc.getNonNull(h)
|
||||
if err != nil {
|
||||
log.Warnf("events.ChainAt: calling HandleFunc with nil tipset, not found in cache: %s", err)
|
||||
@@ -168,11 +177,11 @@ func (e *heightEvents) ChainAt(hnd HeightHandler, rev RevertHandler, confidence
|
||||
|
||||
defer e.lk.Unlock()
|
||||
|
||||
if bestH >= h+uint64(confidence)+e.gcConfidence {
|
||||
if bestH >= h+abi.ChainEpoch(confidence)+e.gcConfidence {
|
||||
return nil
|
||||
}
|
||||
|
||||
triggerAt := h + uint64(confidence)
|
||||
triggerAt := h + abi.ChainEpoch(confidence)
|
||||
|
||||
id := e.ctr
|
||||
e.ctr++
|
||||
|
||||
+506
-75
@@ -3,18 +3,20 @@ package events
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/multiformats/go-multihash"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/crypto"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/address"
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
@@ -31,28 +33,36 @@ type fakeMsg struct {
|
||||
|
||||
type fakeCS struct {
|
||||
t *testing.T
|
||||
h uint64
|
||||
h abi.ChainEpoch
|
||||
tsc *tipSetCache
|
||||
|
||||
msgs map[cid.Cid]fakeMsg
|
||||
blkMsgs map[cid.Cid]cid.Cid
|
||||
|
||||
sync sync.Mutex
|
||||
|
||||
tipsets map[types.TipSetKey]*types.TipSet
|
||||
|
||||
sub func(rev, app []*types.TipSet)
|
||||
}
|
||||
|
||||
func (fcs *fakeCS) StateGetReceipt(context.Context, cid.Cid, *types.TipSet) (*types.MessageReceipt, error) {
|
||||
func (fcs *fakeCS) ChainGetTipSet(ctx context.Context, key types.TipSetKey) (*types.TipSet, error) {
|
||||
return fcs.tipsets[key], nil
|
||||
}
|
||||
|
||||
func (fcs *fakeCS) StateGetReceipt(context.Context, cid.Cid, types.TipSetKey) (*types.MessageReceipt, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (fcs *fakeCS) StateGetActor(ctx context.Context, actor address.Address, ts *types.TipSet) (*types.Actor, error) {
|
||||
func (fcs *fakeCS) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) {
|
||||
panic("Not Implemented")
|
||||
}
|
||||
|
||||
func (fcs *fakeCS) ChainGetTipSetByHeight(context.Context, uint64, *types.TipSet) (*types.TipSet, error) {
|
||||
func (fcs *fakeCS) ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) {
|
||||
panic("Not Implemented")
|
||||
}
|
||||
|
||||
func makeTs(t *testing.T, h uint64, msgcid cid.Cid) *types.TipSet {
|
||||
func (fcs *fakeCS) makeTs(t *testing.T, parents []cid.Cid, h abi.ChainEpoch, msgcid cid.Cid) *types.TipSet {
|
||||
a, _ := address.NewFromString("t00")
|
||||
b, _ := address.NewFromString("t02")
|
||||
var ts, err = types.NewTipSet([]*types.BlockHeader{
|
||||
@@ -60,50 +70,59 @@ func makeTs(t *testing.T, h uint64, msgcid cid.Cid) *types.TipSet {
|
||||
Height: h,
|
||||
Miner: a,
|
||||
|
||||
Parents: parents,
|
||||
|
||||
Ticket: &types.Ticket{VRFProof: []byte{byte(h % 2)}},
|
||||
|
||||
ParentStateRoot: dummyCid,
|
||||
Messages: msgcid,
|
||||
ParentMessageReceipts: dummyCid,
|
||||
|
||||
BlockSig: &types.Signature{Type: types.KTBLS},
|
||||
BLSAggregate: types.Signature{Type: types.KTBLS},
|
||||
BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS},
|
||||
BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS},
|
||||
},
|
||||
{
|
||||
Height: h,
|
||||
Miner: b,
|
||||
|
||||
Parents: parents,
|
||||
|
||||
Ticket: &types.Ticket{VRFProof: []byte{byte((h + 1) % 2)}},
|
||||
|
||||
ParentStateRoot: dummyCid,
|
||||
Messages: msgcid,
|
||||
ParentMessageReceipts: dummyCid,
|
||||
|
||||
BlockSig: &types.Signature{Type: types.KTBLS},
|
||||
BLSAggregate: types.Signature{Type: types.KTBLS},
|
||||
BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS},
|
||||
BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS},
|
||||
},
|
||||
})
|
||||
|
||||
if fcs.tipsets == nil {
|
||||
fcs.tipsets = map[types.TipSetKey]*types.TipSet{}
|
||||
}
|
||||
fcs.tipsets[ts.Key()] = ts
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
return ts
|
||||
}
|
||||
|
||||
func (fcs *fakeCS) ChainNotify(context.Context) (<-chan []*store.HeadChange, error) {
|
||||
out := make(chan []*store.HeadChange, 1)
|
||||
out <- []*store.HeadChange{{Type: store.HCCurrent, Val: fcs.tsc.best()}}
|
||||
func (fcs *fakeCS) ChainNotify(context.Context) (<-chan []*api.HeadChange, error) {
|
||||
out := make(chan []*api.HeadChange, 1)
|
||||
out <- []*api.HeadChange{{Type: store.HCCurrent, Val: fcs.tsc.best()}}
|
||||
|
||||
fcs.sub = func(rev, app []*types.TipSet) {
|
||||
notif := make([]*store.HeadChange, len(rev)+len(app))
|
||||
notif := make([]*api.HeadChange, len(rev)+len(app))
|
||||
|
||||
for i, r := range rev {
|
||||
notif[i] = &store.HeadChange{
|
||||
notif[i] = &api.HeadChange{
|
||||
Type: store.HCRevert,
|
||||
Val: r,
|
||||
}
|
||||
}
|
||||
for i, r := range app {
|
||||
notif[i+len(rev)] = &store.HeadChange{
|
||||
notif[i+len(rev)] = &api.HeadChange{
|
||||
Type: store.HCApply,
|
||||
Val: r,
|
||||
}
|
||||
@@ -177,7 +196,7 @@ func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid, nulls ...int) { /
|
||||
continue
|
||||
}
|
||||
|
||||
ts := makeTs(fcs.t, fcs.h, mc)
|
||||
ts := fcs.makeTs(fcs.t, fcs.tsc.best().Key().Cids(), fcs.h, mc)
|
||||
require.NoError(fcs.t, fcs.tsc.add(ts))
|
||||
|
||||
if hasMsgs {
|
||||
@@ -187,11 +206,19 @@ func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid, nulls ...int) { /
|
||||
apps = append(apps, ts)
|
||||
}
|
||||
|
||||
fcs.sync.Lock()
|
||||
|
||||
fcs.sub(revs, apps)
|
||||
time.Sleep(100 * time.Millisecond) // TODO: :c
|
||||
|
||||
fcs.sync.Lock()
|
||||
fcs.sync.Unlock() //nolint:staticcheck
|
||||
}
|
||||
|
||||
var _ eventApi = &fakeCS{}
|
||||
func (fcs *fakeCS) notifDone() {
|
||||
fcs.sync.Unlock()
|
||||
}
|
||||
|
||||
var _ eventAPI = &fakeCS{}
|
||||
|
||||
func TestAt(t *testing.T) {
|
||||
fcs := &fakeCS{
|
||||
@@ -199,14 +226,14 @@ func TestAt(t *testing.T) {
|
||||
h: 1,
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
var applied bool
|
||||
var reverted bool
|
||||
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH uint64) error {
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error {
|
||||
require.Equal(t, 5, int(ts.Height()))
|
||||
require.Equal(t, 8, int(curH))
|
||||
applied = true
|
||||
@@ -264,14 +291,14 @@ func TestAtDoubleTrigger(t *testing.T) {
|
||||
h: 1,
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
var applied bool
|
||||
var reverted bool
|
||||
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH uint64) error {
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error {
|
||||
require.Equal(t, 5, int(ts.Height()))
|
||||
require.Equal(t, 8, int(curH))
|
||||
applied = true
|
||||
@@ -306,15 +333,15 @@ func TestAtNullTrigger(t *testing.T) {
|
||||
h: 1,
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
var applied bool
|
||||
var reverted bool
|
||||
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH uint64) error {
|
||||
require.Equal(t, uint64(6), ts.Height())
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error {
|
||||
require.Equal(t, abi.ChainEpoch(6), ts.Height())
|
||||
require.Equal(t, 8, int(curH))
|
||||
applied = true
|
||||
return nil
|
||||
@@ -340,14 +367,14 @@ func TestAtNullConf(t *testing.T) {
|
||||
h: 1,
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
var applied bool
|
||||
var reverted bool
|
||||
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH uint64) error {
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error {
|
||||
require.Equal(t, 5, int(ts.Height()))
|
||||
require.Equal(t, 8, int(curH))
|
||||
applied = true
|
||||
@@ -379,7 +406,7 @@ func TestAtStart(t *testing.T) {
|
||||
h: 1,
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
@@ -388,7 +415,7 @@ func TestAtStart(t *testing.T) {
|
||||
var applied bool
|
||||
var reverted bool
|
||||
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH uint64) error {
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error {
|
||||
require.Equal(t, 5, int(ts.Height()))
|
||||
require.Equal(t, 8, int(curH))
|
||||
applied = true
|
||||
@@ -413,7 +440,7 @@ func TestAtStartConfidence(t *testing.T) {
|
||||
h: 1,
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
@@ -422,7 +449,7 @@ func TestAtStartConfidence(t *testing.T) {
|
||||
var applied bool
|
||||
var reverted bool
|
||||
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH uint64) error {
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error {
|
||||
require.Equal(t, 5, int(ts.Height()))
|
||||
require.Equal(t, 11, int(curH))
|
||||
applied = true
|
||||
@@ -443,15 +470,15 @@ func TestAtChained(t *testing.T) {
|
||||
h: 1,
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
var applied bool
|
||||
var reverted bool
|
||||
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH uint64) error {
|
||||
return events.ChainAt(func(_ context.Context, ts *types.TipSet, curH uint64) error {
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error {
|
||||
return events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error {
|
||||
require.Equal(t, 10, int(ts.Height()))
|
||||
applied = true
|
||||
return nil
|
||||
@@ -477,7 +504,7 @@ func TestAtChainedConfidence(t *testing.T) {
|
||||
h: 1,
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
@@ -486,8 +513,8 @@ func TestAtChainedConfidence(t *testing.T) {
|
||||
var applied bool
|
||||
var reverted bool
|
||||
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH uint64) error {
|
||||
return events.ChainAt(func(_ context.Context, ts *types.TipSet, curH uint64) error {
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error {
|
||||
return events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error {
|
||||
require.Equal(t, 10, int(ts.Height()))
|
||||
applied = true
|
||||
return nil
|
||||
@@ -511,7 +538,7 @@ func TestAtChainedConfidenceNull(t *testing.T) {
|
||||
h: 1,
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
@@ -520,7 +547,7 @@ func TestAtChainedConfidenceNull(t *testing.T) {
|
||||
var applied bool
|
||||
var reverted bool
|
||||
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH uint64) error {
|
||||
err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error {
|
||||
applied = true
|
||||
require.Equal(t, 6, int(ts.Height()))
|
||||
return nil
|
||||
@@ -534,7 +561,7 @@ func TestAtChainedConfidenceNull(t *testing.T) {
|
||||
require.Equal(t, false, reverted)
|
||||
}
|
||||
|
||||
func matchAddrMethod(to address.Address, m uint64) func(msg *types.Message) (bool, error) {
|
||||
func matchAddrMethod(to address.Address, m abi.MethodNum) func(msg *types.Message) (bool, error) {
|
||||
return func(msg *types.Message) (bool, error) {
|
||||
return to == msg.To && m == msg.Method, nil
|
||||
}
|
||||
@@ -549,7 +576,7 @@ func TestCalled(t *testing.T) {
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
@@ -560,11 +587,11 @@ func TestCalled(t *testing.T) {
|
||||
var applied, reverted bool
|
||||
var appliedMsg *types.Message
|
||||
var appliedTs *types.TipSet
|
||||
var appliedH uint64
|
||||
var appliedH abi.ChainEpoch
|
||||
|
||||
err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return false, true, nil
|
||||
}, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH uint64) (bool, error) {
|
||||
}, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) {
|
||||
require.Equal(t, false, applied)
|
||||
applied = true
|
||||
appliedMsg = msg
|
||||
@@ -610,12 +637,12 @@ func TestCalled(t *testing.T) {
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
require.Equal(t, uint64(7), appliedTs.Height())
|
||||
require.Equal(t, abi.ChainEpoch(7), appliedTs.Height())
|
||||
require.Equal(t, "bafkqaaa", appliedTs.Blocks()[0].Messages.String())
|
||||
require.Equal(t, uint64(10), appliedH)
|
||||
require.Equal(t, abi.ChainEpoch(10), appliedH)
|
||||
require.Equal(t, t0123, appliedMsg.To)
|
||||
require.Equal(t, uint64(1), appliedMsg.Nonce)
|
||||
require.Equal(t, uint64(5), appliedMsg.Method)
|
||||
require.Equal(t, abi.MethodNum(5), appliedMsg.Method)
|
||||
|
||||
// revert some blocks, keep the message
|
||||
|
||||
@@ -625,7 +652,7 @@ func TestCalled(t *testing.T) {
|
||||
|
||||
// revert the message
|
||||
|
||||
fcs.advance(2, 1, nil) // H=7, we reverted ts with the msg
|
||||
fcs.advance(2, 1, nil) // H=7, we reverted ts with the msg execution, but not the msg itself
|
||||
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, true, reverted)
|
||||
@@ -639,24 +666,31 @@ func TestCalled(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
fcs.advance(0, 5, map[int]cid.Cid{ // (confidence=3)
|
||||
fcs.advance(0, 3, map[int]cid.Cid{ // (n2msg confidence=1)
|
||||
0: n2msg,
|
||||
})
|
||||
|
||||
require.Equal(t, true, applied) // msg from H=7, which had reverted execution
|
||||
require.Equal(t, false, reverted)
|
||||
require.Equal(t, abi.ChainEpoch(10), appliedH)
|
||||
applied = false
|
||||
|
||||
fcs.advance(0, 2, nil) // (confidence=3)
|
||||
|
||||
require.Equal(t, true, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
applied = false
|
||||
|
||||
require.Equal(t, uint64(9), appliedTs.Height())
|
||||
require.Equal(t, abi.ChainEpoch(9), appliedTs.Height())
|
||||
require.Equal(t, "bafkqaaa", appliedTs.Blocks()[0].Messages.String())
|
||||
require.Equal(t, uint64(12), appliedH)
|
||||
require.Equal(t, abi.ChainEpoch(12), appliedH)
|
||||
require.Equal(t, t0123, appliedMsg.To)
|
||||
require.Equal(t, uint64(2), appliedMsg.Nonce)
|
||||
require.Equal(t, uint64(5), appliedMsg.Method)
|
||||
require.Equal(t, abi.MethodNum(5), appliedMsg.Method)
|
||||
|
||||
// revert and apply at different height
|
||||
|
||||
fcs.advance(4, 6, map[int]cid.Cid{ // (confidence=3)
|
||||
fcs.advance(8, 6, map[int]cid.Cid{ // (confidence=3)
|
||||
1: n2msg,
|
||||
})
|
||||
|
||||
@@ -668,12 +702,12 @@ func TestCalled(t *testing.T) {
|
||||
reverted = false
|
||||
applied = false
|
||||
|
||||
require.Equal(t, uint64(11), appliedTs.Height())
|
||||
require.Equal(t, abi.ChainEpoch(7), appliedTs.Height())
|
||||
require.Equal(t, "bafkqaaa", appliedTs.Blocks()[0].Messages.String())
|
||||
require.Equal(t, uint64(14), appliedH)
|
||||
require.Equal(t, abi.ChainEpoch(10), appliedH)
|
||||
require.Equal(t, t0123, appliedMsg.To)
|
||||
require.Equal(t, uint64(2), appliedMsg.Nonce)
|
||||
require.Equal(t, uint64(5), appliedMsg.Method)
|
||||
require.Equal(t, abi.MethodNum(5), appliedMsg.Method)
|
||||
|
||||
// call method again
|
||||
|
||||
@@ -694,7 +728,7 @@ func TestCalled(t *testing.T) {
|
||||
}),
|
||||
})
|
||||
|
||||
fcs.advance(1, 4, nil) // H=19, but message reverted
|
||||
fcs.advance(2, 5, nil) // H=19, but message reverted
|
||||
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
@@ -754,7 +788,7 @@ func TestCalledTimeout(t *testing.T) {
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
@@ -765,11 +799,11 @@ func TestCalledTimeout(t *testing.T) {
|
||||
|
||||
err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return false, true, nil
|
||||
}, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH uint64) (bool, error) {
|
||||
}, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) {
|
||||
called = true
|
||||
require.Nil(t, msg)
|
||||
require.Equal(t, uint64(20), ts.Height())
|
||||
require.Equal(t, uint64(23), curH)
|
||||
require.Equal(t, abi.ChainEpoch(20), ts.Height())
|
||||
require.Equal(t, abi.ChainEpoch(23), curH)
|
||||
return false, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
t.Fatal("revert on timeout")
|
||||
@@ -794,17 +828,17 @@ func TestCalledTimeout(t *testing.T) {
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events = NewEvents(context.Background(), fcs)
|
||||
|
||||
err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return true, true, nil
|
||||
}, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH uint64) (bool, error) {
|
||||
}, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) {
|
||||
called = true
|
||||
require.Nil(t, msg)
|
||||
require.Equal(t, uint64(20), ts.Height())
|
||||
require.Equal(t, uint64(23), curH)
|
||||
require.Equal(t, abi.ChainEpoch(20), ts.Height())
|
||||
require.Equal(t, abi.ChainEpoch(23), curH)
|
||||
return false, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
t.Fatal("revert on timeout")
|
||||
@@ -828,7 +862,7 @@ func TestCalledOrder(t *testing.T) {
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(makeTs(t, 1, dummyCid)))
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
@@ -839,14 +873,14 @@ func TestCalledOrder(t *testing.T) {
|
||||
|
||||
err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return false, true, nil
|
||||
}, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH uint64) (bool, error) {
|
||||
}, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) {
|
||||
switch at {
|
||||
case 0:
|
||||
require.Equal(t, uint64(1), msg.Nonce)
|
||||
require.Equal(t, uint64(4), ts.Height())
|
||||
require.Equal(t, abi.ChainEpoch(4), ts.Height())
|
||||
case 1:
|
||||
require.Equal(t, uint64(2), msg.Nonce)
|
||||
require.Equal(t, uint64(5), ts.Height())
|
||||
require.Equal(t, abi.ChainEpoch(5), ts.Height())
|
||||
default:
|
||||
t.Fatal("apply should only get called twice, at: ", at)
|
||||
}
|
||||
@@ -855,9 +889,9 @@ func TestCalledOrder(t *testing.T) {
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
switch at {
|
||||
case 2:
|
||||
require.Equal(t, uint64(5), ts.Height())
|
||||
require.Equal(t, abi.ChainEpoch(5), ts.Height())
|
||||
case 3:
|
||||
require.Equal(t, uint64(4), ts.Height())
|
||||
require.Equal(t, abi.ChainEpoch(4), ts.Height())
|
||||
default:
|
||||
t.Fatal("revert should only get called twice, at: ", at)
|
||||
}
|
||||
@@ -881,3 +915,400 @@ func TestCalledOrder(t *testing.T) {
|
||||
|
||||
fcs.advance(9, 1, nil)
|
||||
}
|
||||
|
||||
func TestCalledNull(t *testing.T) {
|
||||
fcs := &fakeCS{
|
||||
t: t,
|
||||
h: 1,
|
||||
|
||||
msgs: map[cid.Cid]fakeMsg{},
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
t0123, err := address.NewFromString("t0123")
|
||||
require.NoError(t, err)
|
||||
|
||||
more := true
|
||||
var applied, reverted bool
|
||||
|
||||
err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return false, true, nil
|
||||
}, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) {
|
||||
require.Equal(t, false, applied)
|
||||
applied = true
|
||||
return more, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
reverted = true
|
||||
return nil
|
||||
}, 3, 20, matchAddrMethod(t0123, 5))
|
||||
require.NoError(t, err)
|
||||
|
||||
// create few blocks to make sure nothing get's randomly called
|
||||
|
||||
fcs.advance(0, 4, nil) // H=5
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// create blocks with message (but below confidence threshold)
|
||||
|
||||
fcs.advance(0, 3, map[int]cid.Cid{ // msg at H=6; H=8 (confidence=2)
|
||||
0: fcs.fakeMsgs(fakeMsg{
|
||||
bmsgs: []*types.Message{
|
||||
{To: t0123, From: t0123, Method: 5, Nonce: 1},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// create additional blocks so we are above confidence threshold, but with null tipset at the height
|
||||
// of application
|
||||
|
||||
fcs.advance(0, 3, nil, 10) // H=11 (confidence=3, apply)
|
||||
|
||||
require.Equal(t, true, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
applied = false
|
||||
|
||||
fcs.advance(5, 1, nil, 10)
|
||||
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, true, reverted)
|
||||
}
|
||||
|
||||
func TestRemoveTriggersOnMessage(t *testing.T) {
|
||||
fcs := &fakeCS{
|
||||
t: t,
|
||||
h: 1,
|
||||
|
||||
msgs: map[cid.Cid]fakeMsg{},
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
t0123, err := address.NewFromString("t0123")
|
||||
require.NoError(t, err)
|
||||
|
||||
more := true
|
||||
var applied, reverted bool
|
||||
|
||||
err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return false, true, nil
|
||||
}, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) {
|
||||
require.Equal(t, false, applied)
|
||||
applied = true
|
||||
return more, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
reverted = true
|
||||
return nil
|
||||
}, 3, 20, matchAddrMethod(t0123, 5))
|
||||
require.NoError(t, err)
|
||||
|
||||
// create few blocks to make sure nothing get's randomly called
|
||||
|
||||
fcs.advance(0, 4, nil) // H=5
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// create blocks with message (but below confidence threshold)
|
||||
|
||||
fcs.advance(0, 3, map[int]cid.Cid{ // msg occurs at H=5, applied at H=6; H=8 (confidence=2)
|
||||
0: fcs.fakeMsgs(fakeMsg{
|
||||
bmsgs: []*types.Message{
|
||||
{To: t0123, From: t0123, Method: 5, Nonce: 1},
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// revert applied TS & message TS
|
||||
fcs.advance(3, 1, nil) // H=6 (tipset message applied in reverted, AND message reverted)
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// create additional blocks so we are above confidence threshold, but message not applied
|
||||
// as it was reverted
|
||||
fcs.advance(0, 5, nil) // H=11 (confidence=3, apply)
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// create blocks with message again (but below confidence threshold)
|
||||
|
||||
fcs.advance(0, 3, map[int]cid.Cid{ // msg occurs at H=12, applied at H=13; H=15 (confidence=2)
|
||||
0: fcs.fakeMsgs(fakeMsg{
|
||||
bmsgs: []*types.Message{
|
||||
{To: t0123, From: t0123, Method: 5, Nonce: 2},
|
||||
},
|
||||
}),
|
||||
})
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// revert applied height TS, but don't remove message trigger
|
||||
fcs.advance(2, 1, nil) // H=13 (tipset message applied in reverted, by tipset with message not reverted)
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// create additional blocks so we are above confidence threshold
|
||||
fcs.advance(0, 4, nil) // H=18 (confidence=3, apply)
|
||||
|
||||
require.Equal(t, true, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
}
|
||||
|
||||
type testStateChange struct {
|
||||
from string
|
||||
to string
|
||||
}
|
||||
|
||||
func TestStateChanged(t *testing.T) {
|
||||
fcs := &fakeCS{
|
||||
t: t,
|
||||
h: 1,
|
||||
|
||||
msgs: map[cid.Cid]fakeMsg{},
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
more := true
|
||||
var applied, reverted bool
|
||||
var appliedData StateChange
|
||||
var appliedOldTs *types.TipSet
|
||||
var appliedNewTs *types.TipSet
|
||||
var appliedH abi.ChainEpoch
|
||||
var matchData StateChange
|
||||
|
||||
confidence := 3
|
||||
timeout := abi.ChainEpoch(20)
|
||||
|
||||
err := events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return false, true, nil
|
||||
}, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) {
|
||||
require.Equal(t, false, applied)
|
||||
applied = true
|
||||
appliedData = data
|
||||
appliedOldTs = oldTs
|
||||
appliedNewTs = newTs
|
||||
appliedH = curH
|
||||
return more, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
reverted = true
|
||||
return nil
|
||||
}, confidence, timeout, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) {
|
||||
if matchData == nil {
|
||||
return false, matchData, nil
|
||||
}
|
||||
|
||||
d := matchData
|
||||
matchData = nil
|
||||
return true, d, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// create few blocks to make sure nothing get's randomly called
|
||||
|
||||
fcs.advance(0, 4, nil) // H=5
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// create state change (but below confidence threshold)
|
||||
matchData = testStateChange{from: "a", to: "b"}
|
||||
fcs.advance(0, 3, nil)
|
||||
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// create additional block so we are above confidence threshold
|
||||
|
||||
fcs.advance(0, 2, nil) // H=10 (confidence=3, apply)
|
||||
|
||||
require.Equal(t, true, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
applied = false
|
||||
|
||||
// dip below confidence (should not apply again)
|
||||
fcs.advance(2, 2, nil) // H=10 (confidence=3, apply)
|
||||
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// Change happens from 5 -> 6
|
||||
require.Equal(t, abi.ChainEpoch(5), appliedOldTs.Height())
|
||||
require.Equal(t, abi.ChainEpoch(6), appliedNewTs.Height())
|
||||
|
||||
// Actually applied (with confidence) at 9
|
||||
require.Equal(t, abi.ChainEpoch(9), appliedH)
|
||||
|
||||
// Make sure the state change was correctly passed through
|
||||
rcvd := appliedData.(testStateChange)
|
||||
require.Equal(t, "a", rcvd.from)
|
||||
require.Equal(t, "b", rcvd.to)
|
||||
}
|
||||
|
||||
func TestStateChangedRevert(t *testing.T) {
|
||||
fcs := &fakeCS{
|
||||
t: t,
|
||||
h: 1,
|
||||
|
||||
msgs: map[cid.Cid]fakeMsg{},
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
more := true
|
||||
var applied, reverted bool
|
||||
var matchData StateChange
|
||||
|
||||
confidence := 1
|
||||
timeout := abi.ChainEpoch(20)
|
||||
|
||||
err := events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return false, true, nil
|
||||
}, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) {
|
||||
require.Equal(t, false, applied)
|
||||
applied = true
|
||||
return more, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
reverted = true
|
||||
return nil
|
||||
}, confidence, timeout, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) {
|
||||
if matchData == nil {
|
||||
return false, matchData, nil
|
||||
}
|
||||
|
||||
d := matchData
|
||||
matchData = nil
|
||||
return true, d, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
fcs.advance(0, 2, nil) // H=3
|
||||
|
||||
// Make a state change from TS at height 3 to TS at height 4
|
||||
matchData = testStateChange{from: "a", to: "b"}
|
||||
fcs.advance(0, 1, nil) // H=4
|
||||
|
||||
// Haven't yet reached confidence
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// Advance to reach confidence level
|
||||
fcs.advance(0, 1, nil) // H=5
|
||||
|
||||
// Should now have called the handler
|
||||
require.Equal(t, true, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
applied = false
|
||||
|
||||
// Advance 3 more TS
|
||||
fcs.advance(0, 3, nil) // H=8
|
||||
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// Regress but not so far as to cause a revert
|
||||
fcs.advance(3, 1, nil) // H=6
|
||||
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, false, reverted)
|
||||
|
||||
// Regress back to state where change happened
|
||||
fcs.advance(3, 1, nil) // H=4
|
||||
|
||||
// Expect revert to have happened
|
||||
require.Equal(t, false, applied)
|
||||
require.Equal(t, true, reverted)
|
||||
}
|
||||
|
||||
func TestStateChangedTimeout(t *testing.T) {
|
||||
fcs := &fakeCS{
|
||||
t: t,
|
||||
h: 1,
|
||||
|
||||
msgs: map[cid.Cid]fakeMsg{},
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events := NewEvents(context.Background(), fcs)
|
||||
|
||||
called := false
|
||||
|
||||
err := events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return false, true, nil
|
||||
}, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) {
|
||||
called = true
|
||||
require.Nil(t, data)
|
||||
require.Equal(t, abi.ChainEpoch(20), newTs.Height())
|
||||
require.Equal(t, abi.ChainEpoch(23), curH)
|
||||
return false, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
t.Fatal("revert on timeout")
|
||||
return nil
|
||||
}, 3, 20, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) {
|
||||
return false, nil, nil
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
fcs.advance(0, 21, nil)
|
||||
require.False(t, called)
|
||||
|
||||
fcs.advance(0, 5, nil)
|
||||
require.True(t, called)
|
||||
called = false
|
||||
|
||||
// with check func reporting done
|
||||
|
||||
fcs = &fakeCS{
|
||||
t: t,
|
||||
h: 1,
|
||||
|
||||
msgs: map[cid.Cid]fakeMsg{},
|
||||
blkMsgs: map[cid.Cid]cid.Cid{},
|
||||
tsc: newTSCache(2*build.ForkLengthThreshold, nil),
|
||||
}
|
||||
require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid)))
|
||||
|
||||
events = NewEvents(context.Background(), fcs)
|
||||
|
||||
err = events.StateChanged(func(ts *types.TipSet) (d bool, m bool, e error) {
|
||||
return true, true, nil
|
||||
}, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) {
|
||||
called = true
|
||||
require.Nil(t, data)
|
||||
require.Equal(t, abi.ChainEpoch(20), newTs.Height())
|
||||
require.Equal(t, abi.ChainEpoch(23), curH)
|
||||
return false, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
t.Fatal("revert on timeout")
|
||||
return nil
|
||||
}, 3, 20, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) {
|
||||
return false, nil, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
fcs.advance(0, 21, nil)
|
||||
require.False(t, called)
|
||||
|
||||
fcs.advance(0, 5, nil)
|
||||
require.False(t, called)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
)
|
||||
|
||||
type contextStore struct {
|
||||
ctx context.Context
|
||||
cst *cbor.BasicIpldStore
|
||||
}
|
||||
|
||||
func (cs *contextStore) Context() context.Context {
|
||||
return cs.ctx
|
||||
}
|
||||
|
||||
func (cs *contextStore) Get(ctx context.Context, c cid.Cid, out interface{}) error {
|
||||
return cs.cst.Get(ctx, c, out)
|
||||
}
|
||||
|
||||
func (cs *contextStore) Put(ctx context.Context, v interface{}) (cid.Cid, error) {
|
||||
return cs.cst.Put(ctx, v)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user