Compare commits
435
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5de7b62dbc | ||
|
|
5fb76ddad2 | ||
|
|
cc02092aae | ||
|
|
3e4b80e7f6 | ||
|
|
14ae8c7272 | ||
|
|
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 | ||
|
|
a5935486ef | ||
|
|
ffd012906c | ||
|
|
f8a1fb2321 | ||
|
|
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 | ||
|
|
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 | ||
|
|
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 | ||
|
|
ce30162907 | ||
|
|
c0cdcbb9e0 | ||
|
|
21415a9847 | ||
|
|
8c05251cce | ||
|
|
7c15d1e8c3 |
@@ -45,7 +45,7 @@ commands:
|
||||
- 'v25-2k-lotus-params'
|
||||
paths:
|
||||
- /var/tmp/filecoin-proof-parameters/
|
||||
- run: ./lotus fetch-params --proving-params 2048
|
||||
- run: ./lotus fetch-params 2048
|
||||
- save_cache:
|
||||
name: Save parameters cache
|
||||
key: 'v25-2k-lotus-params'
|
||||
@@ -314,7 +314,7 @@ workflows:
|
||||
ci:
|
||||
jobs:
|
||||
- lint-changes:
|
||||
args: "--new-from-rev origin/next"
|
||||
args: "--new-from-rev origin/master"
|
||||
- mod-tidy-check
|
||||
- gofmt
|
||||
- test:
|
||||
|
||||
@@ -35,3 +35,4 @@ build/paramfetch.sh
|
||||
bin/ipget
|
||||
bin/tmp/*
|
||||
.idea
|
||||
scratchpad
|
||||
|
||||
@@ -6,7 +6,7 @@ all: build
|
||||
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
|
||||
@@ -105,15 +105,17 @@ install:
|
||||
|
||||
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 and lotus-miner services installed. Don't forget to 'systemctl enable lotus|lotus-miner' for it to be enabled on startup."
|
||||
@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
|
||||
@@ -160,6 +162,13 @@ 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
|
||||
@@ -182,6 +191,12 @@ health:
|
||||
.PHONY: health
|
||||
BINS+=health
|
||||
|
||||
testground:
|
||||
go build -tags testground -o /dev/null ./cmd/lotus
|
||||
|
||||
.PHONY: testground
|
||||
BINS+=testground
|
||||
|
||||
# MISC
|
||||
|
||||
buildall: $(BINS)
|
||||
|
||||
@@ -4,14 +4,23 @@
|
||||
|
||||
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).
|
||||
|
||||
## 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).
|
||||
|
||||
## 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.
|
||||
* [`interopnet`](https://github.com/filecoin-project/lotus/tree/interopnet): 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: |
|
||||
+8
-2
@@ -13,11 +13,13 @@ import (
|
||||
)
|
||||
|
||||
type Common interface {
|
||||
// Auth
|
||||
|
||||
// MethodGroup: Auth
|
||||
|
||||
AuthVerify(ctx context.Context, token string) ([]auth.Permission, error)
|
||||
AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error)
|
||||
|
||||
// network
|
||||
// MethodGroup: Net
|
||||
|
||||
NetConnectedness(context.Context, peer.ID) (network.Connectedness, error)
|
||||
NetPeers(context.Context) ([]peer.AddrInfo, error)
|
||||
@@ -27,6 +29,8 @@ type Common interface {
|
||||
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)
|
||||
|
||||
@@ -38,6 +42,8 @@ type Common interface {
|
||||
|
||||
// trigger graceful shutdown
|
||||
Shutdown(context.Context) error
|
||||
|
||||
Closing(context.Context) (<-chan struct{}, error)
|
||||
}
|
||||
|
||||
// Version provides various build-time information
|
||||
|
||||
+81
-12
@@ -35,26 +35,71 @@ type FullNode interface {
|
||||
// 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 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(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)
|
||||
|
||||
// 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)
|
||||
|
||||
// ChainGetParentReceipts 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 higher 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
|
||||
@@ -63,23 +108,45 @@ type FullNode interface {
|
||||
|
||||
// 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)
|
||||
|
||||
// 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(context.Context, *types.Message) (*types.SignedMessage, error) // get nonce, sign, push
|
||||
|
||||
// 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)
|
||||
MpoolEstimateGasPrice(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error)
|
||||
|
||||
// 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)
|
||||
|
||||
// MethodGroup: Miner
|
||||
|
||||
@@ -101,6 +168,7 @@ type FullNode interface {
|
||||
WalletSetDefault(context.Context, address.Address) error
|
||||
WalletExport(context.Context, address.Address) (*types.KeyInfo, error)
|
||||
WalletImport(context.Context, *types.KeyInfo) (address.Address, error)
|
||||
WalletDelete(context.Context, address.Address) error
|
||||
|
||||
// Other
|
||||
|
||||
@@ -117,6 +185,7 @@ type FullNode interface {
|
||||
ClientListDeals(ctx context.Context) ([]DealInfo, error)
|
||||
ClientHasLocal(ctx context.Context, root cid.Cid) (bool, error)
|
||||
ClientFindData(ctx context.Context, root cid.Cid) ([]QueryOffer, error)
|
||||
ClientMinerQueryOffer(ctx context.Context, root cid.Cid, miner address.Address) (QueryOffer, error)
|
||||
ClientRetrieve(ctx context.Context, order RetrievalOrder, ref *FileRef) error
|
||||
ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.SignedStorageAsk, error)
|
||||
ClientCalcCommP(ctx context.Context, inpath string, miner address.Address) (*CommPRet, error)
|
||||
@@ -137,7 +206,7 @@ type FullNode interface {
|
||||
StateCall(context.Context, *types.Message, types.TipSetKey) (*InvocResult, error)
|
||||
StateReplay(context.Context, types.TipSetKey, cid.Cid) (*InvocResult, error)
|
||||
StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error)
|
||||
StateReadState(ctx context.Context, act *types.Actor, tsk types.TipSetKey) (*ActorState, error)
|
||||
StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*ActorState, error)
|
||||
StateListMessages(ctx context.Context, match *types.Message, tsk types.TipSetKey, toht abi.ChainEpoch) ([]cid.Cid, error)
|
||||
|
||||
StateNetworkName(context.Context) (dtypes.NetworkName, error)
|
||||
@@ -145,7 +214,7 @@ type FullNode interface {
|
||||
StateMinerProvingSet(context.Context, address.Address, types.TipSetKey) ([]*ChainSectorInfo, error)
|
||||
StateMinerProvingDeadline(context.Context, address.Address, types.TipSetKey) (*miner.DeadlineInfo, error)
|
||||
StateMinerPower(context.Context, address.Address, types.TipSetKey) (*MinerPower, error)
|
||||
StateMinerInfo(context.Context, address.Address, types.TipSetKey) (miner.MinerInfo, error)
|
||||
StateMinerInfo(context.Context, address.Address, types.TipSetKey) (MinerInfo, error)
|
||||
StateMinerDeadlines(context.Context, address.Address, types.TipSetKey) (*miner.Deadlines, error)
|
||||
StateMinerFaults(context.Context, address.Address, types.TipSetKey) (*abi.BitField, error)
|
||||
// Returns all non-expired Faults that occur within lookback epochs of the given tipset
|
||||
@@ -349,11 +418,11 @@ type RetrievalOrder struct {
|
||||
}
|
||||
|
||||
type InvocResult struct {
|
||||
Msg *types.Message
|
||||
MsgRct *types.MessageReceipt
|
||||
InternalExecutions []*types.ExecutionResult
|
||||
Error string
|
||||
Duration time.Duration
|
||||
Msg *types.Message
|
||||
MsgRct *types.MessageReceipt
|
||||
ExecutionTrace types.ExecutionTrace
|
||||
Error string
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
type MethodCall struct {
|
||||
|
||||
+13
-1
@@ -36,6 +36,7 @@ type StorageMiner interface {
|
||||
SectorsRefs(context.Context) (map[string][]SealedRef, error)
|
||||
|
||||
SectorsUpdate(context.Context, abi.SectorNumber, SectorState) error
|
||||
SectorRemove(context.Context, abi.SectorNumber) error
|
||||
|
||||
StorageList(ctx context.Context) (map[stores.ID][]stores.Decl, error)
|
||||
StorageLocal(ctx context.Context) (map[stores.ID]string, error)
|
||||
@@ -50,10 +51,21 @@ type StorageMiner interface {
|
||||
MarketImportDealData(ctx context.Context, propcid cid.Cid, path string) error
|
||||
MarketListDeals(ctx context.Context) ([]storagemarket.StorageDeal, error)
|
||||
MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error)
|
||||
MarketSetPrice(context.Context, types.BigInt) 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
|
||||
}
|
||||
|
||||
+135
-45
@@ -50,7 +50,8 @@ type CommonStruct struct {
|
||||
LogList func(context.Context) ([]string, error) `perm:"write"`
|
||||
LogSetLevel func(context.Context, string, string) error `perm:"write"`
|
||||
|
||||
Shutdown func(context.Context) error `perm:"admin"`
|
||||
Shutdown func(context.Context) error `perm:"admin"`
|
||||
Closing func(context.Context) (<-chan struct{}, error) `perm:"read"`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,25 +107,27 @@ type FullNodeStruct struct {
|
||||
WalletSetDefault func(context.Context, address.Address) error `perm:"admin"`
|
||||
WalletExport func(context.Context, address.Address) (*types.KeyInfo, error) `perm:"admin"`
|
||||
WalletImport func(context.Context, *types.KeyInfo) (address.Address, error) `perm:"admin"`
|
||||
WalletDelete func(context.Context, address.Address) error `perm:"write"`
|
||||
|
||||
ClientImport func(ctx context.Context, ref api.FileRef) (cid.Cid, error) `perm:"admin"`
|
||||
ClientListImports func(ctx context.Context) ([]api.Import, error) `perm:"write"`
|
||||
ClientHasLocal func(ctx context.Context, root cid.Cid) (bool, error) `perm:"write"`
|
||||
ClientFindData func(ctx context.Context, root cid.Cid) ([]api.QueryOffer, error) `perm:"read"`
|
||||
ClientStartDeal func(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) `perm:"admin"`
|
||||
ClientGetDealInfo func(context.Context, cid.Cid) (*api.DealInfo, error) `perm:"read"`
|
||||
ClientListDeals func(ctx context.Context) ([]api.DealInfo, error) `perm:"write"`
|
||||
ClientRetrieve func(ctx context.Context, order api.RetrievalOrder, ref *api.FileRef) error `perm:"admin"`
|
||||
ClientQueryAsk func(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.SignedStorageAsk, error) `perm:"read"`
|
||||
ClientCalcCommP func(ctx context.Context, inpath string, miner address.Address) (*api.CommPRet, error) `perm:"read"`
|
||||
ClientGenCar func(ctx context.Context, ref api.FileRef, outpath string) error `perm:"write"`
|
||||
ClientImport func(ctx context.Context, ref api.FileRef) (cid.Cid, error) `perm:"admin"`
|
||||
ClientListImports func(ctx context.Context) ([]api.Import, error) `perm:"write"`
|
||||
ClientHasLocal func(ctx context.Context, root cid.Cid) (bool, error) `perm:"write"`
|
||||
ClientFindData func(ctx context.Context, root cid.Cid) ([]api.QueryOffer, error) `perm:"read"`
|
||||
ClientMinerQueryOffer func(ctx context.Context, root cid.Cid, miner address.Address) (api.QueryOffer, error) `perm:"read"`
|
||||
ClientStartDeal func(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) `perm:"admin"`
|
||||
ClientGetDealInfo func(context.Context, cid.Cid) (*api.DealInfo, error) `perm:"read"`
|
||||
ClientListDeals func(ctx context.Context) ([]api.DealInfo, error) `perm:"write"`
|
||||
ClientRetrieve func(ctx context.Context, order api.RetrievalOrder, ref *api.FileRef) error `perm:"admin"`
|
||||
ClientQueryAsk func(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.SignedStorageAsk, error) `perm:"read"`
|
||||
ClientCalcCommP func(ctx context.Context, inpath string, miner address.Address) (*api.CommPRet, error) `perm:"read"`
|
||||
ClientGenCar func(ctx context.Context, ref api.FileRef, outpath string) error `perm:"write"`
|
||||
|
||||
StateNetworkName func(context.Context) (dtypes.NetworkName, error) `perm:"read"`
|
||||
StateMinerSectors func(context.Context, address.Address, *abi.BitField, bool, types.TipSetKey) ([]*api.ChainSectorInfo, error) `perm:"read"`
|
||||
StateMinerProvingSet func(context.Context, address.Address, types.TipSetKey) ([]*api.ChainSectorInfo, error) `perm:"read"`
|
||||
StateMinerProvingDeadline func(context.Context, address.Address, types.TipSetKey) (*miner.DeadlineInfo, error) `perm:"read"`
|
||||
StateMinerPower func(context.Context, address.Address, types.TipSetKey) (*api.MinerPower, error) `perm:"read"`
|
||||
StateMinerInfo func(context.Context, address.Address, types.TipSetKey) (miner.MinerInfo, error) `perm:"read"`
|
||||
StateMinerInfo func(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) `perm:"read"`
|
||||
StateMinerDeadlines func(context.Context, address.Address, types.TipSetKey) (*miner.Deadlines, error) `perm:"read"`
|
||||
StateMinerFaults func(context.Context, address.Address, types.TipSetKey) (*abi.BitField, error) `perm:"read"`
|
||||
StateAllMinerFaults func(context.Context, abi.ChainEpoch, types.TipSetKey) ([]*api.Fault, error) `perm:"read"`
|
||||
@@ -136,7 +139,7 @@ type FullNodeStruct struct {
|
||||
StateCall func(context.Context, *types.Message, types.TipSetKey) (*api.InvocResult, error) `perm:"read"`
|
||||
StateReplay func(context.Context, types.TipSetKey, cid.Cid) (*api.InvocResult, error) `perm:"read"`
|
||||
StateGetActor func(context.Context, address.Address, types.TipSetKey) (*types.Actor, error) `perm:"read"`
|
||||
StateReadState func(context.Context, *types.Actor, types.TipSetKey) (*api.ActorState, error) `perm:"read"`
|
||||
StateReadState func(context.Context, address.Address, types.TipSetKey) (*api.ActorState, error) `perm:"read"`
|
||||
StatePledgeCollateral func(context.Context, types.TipSetKey) (types.BigInt, error) `perm:"read"`
|
||||
StateWaitMsg func(ctx context.Context, cid cid.Cid, confidence uint64) (*api.MsgLookup, error) `perm:"read"`
|
||||
StateSearchMsg func(context.Context, cid.Cid) (*api.MsgLookup, error) `perm:"read"`
|
||||
@@ -191,10 +194,11 @@ type StorageMinerStruct struct {
|
||||
|
||||
MiningBase func(context.Context) (*types.TipSet, error) `perm:"read"`
|
||||
|
||||
MarketImportDealData func(context.Context, cid.Cid, string) error `perm:"write"`
|
||||
MarketListDeals func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"`
|
||||
MarketListIncompleteDeals func(ctx context.Context) ([]storagemarket.MinerDeal, error) `perm:"read"`
|
||||
MarketSetPrice func(context.Context, types.BigInt) error `perm:"admin"`
|
||||
MarketImportDealData func(context.Context, cid.Cid, string) error `perm:"write"`
|
||||
MarketListDeals func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"`
|
||||
MarketListIncompleteDeals func(ctx context.Context) ([]storagemarket.MinerDeal, error) `perm:"read"`
|
||||
MarketSetAsk func(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error `perm:"admin"`
|
||||
MarketGetAsk func(ctx context.Context) (*storagemarket.SignedStorageAsk, error) `perm:"read"`
|
||||
|
||||
PledgeSector func(context.Context) error `perm:"write"`
|
||||
|
||||
@@ -202,24 +206,36 @@ type StorageMinerStruct struct {
|
||||
SectorsList func(context.Context) ([]abi.SectorNumber, error) `perm:"read"`
|
||||
SectorsRefs func(context.Context) (map[string][]api.SealedRef, error) `perm:"read"`
|
||||
SectorsUpdate func(context.Context, abi.SectorNumber, api.SectorState) error `perm:"write"`
|
||||
SectorRemove func(context.Context, abi.SectorNumber) error `perm:"admin"`
|
||||
|
||||
WorkerConnect func(context.Context, string) error `perm:"admin"` // TODO: worker perm
|
||||
WorkerStats func(context.Context) (map[uint64]storiface.WorkerStats, error) `perm:"admin"`
|
||||
|
||||
StorageList func(context.Context) (map[stores.ID][]stores.Decl, error) `perm:"admin"`
|
||||
StorageLocal func(context.Context) (map[stores.ID]string, error) `perm:"admin"`
|
||||
StorageStat func(context.Context, stores.ID) (stores.FsStat, error) `perm:"admin"`
|
||||
StorageAttach func(context.Context, stores.StorageInfo, stores.FsStat) error `perm:"admin"`
|
||||
StorageDeclareSector func(context.Context, stores.ID, abi.SectorID, stores.SectorFileType, bool) error `perm:"admin"`
|
||||
StorageDropSector func(context.Context, stores.ID, abi.SectorID, stores.SectorFileType) error `perm:"admin"`
|
||||
StorageFindSector func(context.Context, abi.SectorID, stores.SectorFileType, bool) ([]stores.SectorStorageInfo, error) `perm:"admin"`
|
||||
StorageInfo func(context.Context, stores.ID) (stores.StorageInfo, error) `perm:"admin"`
|
||||
StorageBestAlloc func(ctx context.Context, allocate stores.SectorFileType, spt abi.RegisteredProof, sealing stores.PathType) ([]stores.StorageInfo, error) `perm:"admin"`
|
||||
StorageReportHealth func(ctx context.Context, id stores.ID, report stores.HealthReport) error `perm:"admin"`
|
||||
StorageLock func(ctx context.Context, sector abi.SectorID, read stores.SectorFileType, write stores.SectorFileType) error `perm:"admin"`
|
||||
StorageList func(context.Context) (map[stores.ID][]stores.Decl, error) `perm:"admin"`
|
||||
StorageLocal func(context.Context) (map[stores.ID]string, error) `perm:"admin"`
|
||||
StorageStat func(context.Context, stores.ID) (stores.FsStat, error) `perm:"admin"`
|
||||
StorageAttach func(context.Context, stores.StorageInfo, stores.FsStat) error `perm:"admin"`
|
||||
StorageDeclareSector func(context.Context, stores.ID, abi.SectorID, stores.SectorFileType, bool) error `perm:"admin"`
|
||||
StorageDropSector func(context.Context, stores.ID, abi.SectorID, stores.SectorFileType) error `perm:"admin"`
|
||||
StorageFindSector func(context.Context, abi.SectorID, stores.SectorFileType, bool) ([]stores.SectorStorageInfo, error) `perm:"admin"`
|
||||
StorageInfo func(context.Context, stores.ID) (stores.StorageInfo, error) `perm:"admin"`
|
||||
StorageBestAlloc func(ctx context.Context, allocate stores.SectorFileType, spt abi.RegisteredSealProof, sealing stores.PathType) ([]stores.StorageInfo, error) `perm:"admin"`
|
||||
StorageReportHealth func(ctx context.Context, id stores.ID, report stores.HealthReport) error `perm:"admin"`
|
||||
StorageLock func(ctx context.Context, sector abi.SectorID, read stores.SectorFileType, write stores.SectorFileType) error `perm:"admin"`
|
||||
StorageTryLock func(ctx context.Context, sector abi.SectorID, read stores.SectorFileType, write stores.SectorFileType) (bool, error) `perm:"admin"`
|
||||
|
||||
DealsImportData func(ctx context.Context, dealPropCid cid.Cid, file string) error `perm:"write"`
|
||||
DealsList func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"`
|
||||
DealsImportData func(ctx context.Context, dealPropCid cid.Cid, file string) error `perm:"write"`
|
||||
DealsList func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"`
|
||||
DealsConsiderOnlineStorageDeals func(context.Context) (bool, error) `perm:"read"`
|
||||
DealsSetConsiderOnlineStorageDeals func(context.Context, bool) error `perm:"admin"`
|
||||
DealsConsiderOnlineRetrievalDeals func(context.Context) (bool, error) `perm:"read"`
|
||||
DealsSetConsiderOnlineRetrievalDeals func(context.Context, bool) error `perm:"admin"`
|
||||
DealsConsiderOfflineStorageDeals func(context.Context) (bool, error) `perm:"read"`
|
||||
DealsSetConsiderOfflineStorageDeals func(context.Context, bool) error `perm:"admin"`
|
||||
DealsConsiderOfflineRetrievalDeals func(context.Context) (bool, error) `perm:"read"`
|
||||
DealsSetConsiderOfflineRetrievalDeals func(context.Context, bool) error `perm:"admin"`
|
||||
DealsPieceCidBlocklist func(context.Context) ([]cid.Cid, error) `perm:"read"`
|
||||
DealsSetPieceCidBlocklist func(context.Context, []cid.Cid) error `perm:"admin"`
|
||||
|
||||
StorageAddLocal func(ctx context.Context, path string) error `perm:"admin"`
|
||||
}
|
||||
@@ -235,12 +251,14 @@ type WorkerStruct struct {
|
||||
Paths func(context.Context) ([]stores.StoragePath, error) `perm:"admin"`
|
||||
Info func(context.Context) (storiface.WorkerInfo, error) `perm:"admin"`
|
||||
|
||||
SealPreCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, pieces []abi.PieceInfo) (storage.PreCommit1Out, error) `perm:"admin"`
|
||||
SealPreCommit2 func(context.Context, abi.SectorID, storage.PreCommit1Out) (cids storage.SectorCids, err error) `perm:"admin"`
|
||||
SealCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, seed abi.InteractiveSealRandomness, pieces []abi.PieceInfo, cids storage.SectorCids) (storage.Commit1Out, error) `perm:"admin"`
|
||||
SealCommit2 func(context.Context, abi.SectorID, storage.Commit1Out) (storage.Proof, error) `perm:"admin"`
|
||||
FinalizeSector func(context.Context, abi.SectorID) error `perm:"admin"`
|
||||
MoveStorage func(ctx context.Context, sector abi.SectorID) error `perm:"admin"`
|
||||
SealPreCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, pieces []abi.PieceInfo) (storage.PreCommit1Out, error) `perm:"admin"`
|
||||
SealPreCommit2 func(context.Context, abi.SectorID, storage.PreCommit1Out) (cids storage.SectorCids, err error) `perm:"admin"`
|
||||
SealCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, seed abi.InteractiveSealRandomness, pieces []abi.PieceInfo, cids storage.SectorCids) (storage.Commit1Out, error) `perm:"admin"`
|
||||
SealCommit2 func(context.Context, abi.SectorID, storage.Commit1Out) (storage.Proof, error) `perm:"admin"`
|
||||
FinalizeSector func(context.Context, abi.SectorID, []storage.Range) error `perm:"admin"`
|
||||
ReleaseUnsealed func(ctx context.Context, sector abi.SectorID, safeToFree []storage.Range) error `perm:"admin"`
|
||||
Remove func(ctx context.Context, sector abi.SectorID) error `perm:"admin"`
|
||||
MoveStorage func(ctx context.Context, sector abi.SectorID) error `perm:"admin"`
|
||||
|
||||
UnsealPiece func(context.Context, abi.SectorID, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize, abi.SealRandomness, cid.Cid) error `perm:"admin"`
|
||||
ReadPiece func(context.Context, io.Writer, abi.SectorID, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize) error `perm:"admin"`
|
||||
@@ -310,6 +328,10 @@ func (c *CommonStruct) Shutdown(ctx context.Context) error {
|
||||
return c.Internal.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func (c *CommonStruct) Closing(ctx context.Context) (<-chan struct{}, error) {
|
||||
return c.Internal.Closing(ctx)
|
||||
}
|
||||
|
||||
// FullNodeStruct
|
||||
|
||||
func (c *FullNodeStruct) ClientListImports(ctx context.Context) ([]api.Import, error) {
|
||||
@@ -328,6 +350,10 @@ func (c *FullNodeStruct) ClientFindData(ctx context.Context, root cid.Cid) ([]ap
|
||||
return c.Internal.ClientFindData(ctx, root)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) ClientMinerQueryOffer(ctx context.Context, root cid.Cid, miner address.Address) (api.QueryOffer, error) {
|
||||
return c.Internal.ClientMinerQueryOffer(ctx, root, miner)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) ClientStartDeal(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) {
|
||||
return c.Internal.ClientStartDeal(ctx, params)
|
||||
}
|
||||
@@ -438,6 +464,10 @@ func (c *FullNodeStruct) WalletImport(ctx context.Context, ki *types.KeyInfo) (a
|
||||
return c.Internal.WalletImport(ctx, ki)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) WalletDelete(ctx context.Context, addr address.Address) error {
|
||||
return c.Internal.WalletDelete(ctx, addr)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) MpoolGetNonce(ctx context.Context, addr address.Address) (uint64, error) {
|
||||
return c.Internal.MpoolGetNonce(ctx, addr)
|
||||
}
|
||||
@@ -546,7 +576,7 @@ func (c *FullNodeStruct) StateMinerPower(ctx context.Context, a address.Address,
|
||||
return c.Internal.StateMinerPower(ctx, a, tsk)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) StateMinerInfo(ctx context.Context, actor address.Address, tsk types.TipSetKey) (miner.MinerInfo, error) {
|
||||
func (c *FullNodeStruct) StateMinerInfo(ctx context.Context, actor address.Address, tsk types.TipSetKey) (api.MinerInfo, error) {
|
||||
return c.Internal.StateMinerInfo(ctx, actor, tsk)
|
||||
}
|
||||
|
||||
@@ -594,8 +624,8 @@ func (c *FullNodeStruct) StateGetActor(ctx context.Context, actor address.Addres
|
||||
return c.Internal.StateGetActor(ctx, actor, tsk)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) StateReadState(ctx context.Context, act *types.Actor, tsk types.TipSetKey) (*api.ActorState, error) {
|
||||
return c.Internal.StateReadState(ctx, act, tsk)
|
||||
func (c *FullNodeStruct) StateReadState(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*api.ActorState, error) {
|
||||
return c.Internal.StateReadState(ctx, addr, tsk)
|
||||
}
|
||||
|
||||
func (c *FullNodeStruct) StatePledgeCollateral(ctx context.Context, tsk types.TipSetKey) (types.BigInt, error) {
|
||||
@@ -766,6 +796,10 @@ func (c *StorageMinerStruct) SectorsUpdate(ctx context.Context, id abi.SectorNum
|
||||
return c.Internal.SectorsUpdate(ctx, id, state)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) SectorRemove(ctx context.Context, number abi.SectorNumber) error {
|
||||
return c.Internal.SectorRemove(ctx, number)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) WorkerConnect(ctx context.Context, url string) error {
|
||||
return c.Internal.WorkerConnect(ctx, url)
|
||||
}
|
||||
@@ -806,7 +840,7 @@ func (c *StorageMinerStruct) StorageInfo(ctx context.Context, id stores.ID) (sto
|
||||
return c.Internal.StorageInfo(ctx, id)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) StorageBestAlloc(ctx context.Context, allocate stores.SectorFileType, spt abi.RegisteredProof, pt stores.PathType) ([]stores.StorageInfo, error) {
|
||||
func (c *StorageMinerStruct) StorageBestAlloc(ctx context.Context, allocate stores.SectorFileType, spt abi.RegisteredSealProof, pt stores.PathType) ([]stores.StorageInfo, error) {
|
||||
return c.Internal.StorageBestAlloc(ctx, allocate, spt, pt)
|
||||
}
|
||||
|
||||
@@ -818,6 +852,10 @@ func (c *StorageMinerStruct) StorageLock(ctx context.Context, sector abi.SectorI
|
||||
return c.Internal.StorageLock(ctx, sector, read, write)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) StorageTryLock(ctx context.Context, sector abi.SectorID, read stores.SectorFileType, write stores.SectorFileType) (bool, error) {
|
||||
return c.Internal.StorageTryLock(ctx, sector, read, write)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) MarketImportDealData(ctx context.Context, propcid cid.Cid, path string) error {
|
||||
return c.Internal.MarketImportDealData(ctx, propcid, path)
|
||||
}
|
||||
@@ -830,8 +868,12 @@ func (c *StorageMinerStruct) MarketListIncompleteDeals(ctx context.Context) ([]s
|
||||
return c.Internal.MarketListIncompleteDeals(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) MarketSetPrice(ctx context.Context, p types.BigInt) error {
|
||||
return c.Internal.MarketSetPrice(ctx, p)
|
||||
func (c *StorageMinerStruct) MarketSetAsk(ctx context.Context, price types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error {
|
||||
return c.Internal.MarketSetAsk(ctx, price, duration, minPieceSize, maxPieceSize)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error) {
|
||||
return c.Internal.MarketGetAsk(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error {
|
||||
@@ -842,6 +884,46 @@ func (c *StorageMinerStruct) DealsList(ctx context.Context) ([]storagemarket.Sto
|
||||
return c.Internal.DealsList(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsConsiderOnlineStorageDeals(ctx context.Context) (bool, error) {
|
||||
return c.Internal.DealsConsiderOnlineStorageDeals(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsSetConsiderOnlineStorageDeals(ctx context.Context, b bool) error {
|
||||
return c.Internal.DealsSetConsiderOnlineStorageDeals(ctx, b)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsConsiderOnlineRetrievalDeals(ctx context.Context) (bool, error) {
|
||||
return c.Internal.DealsConsiderOnlineRetrievalDeals(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsSetConsiderOnlineRetrievalDeals(ctx context.Context, b bool) error {
|
||||
return c.Internal.DealsSetConsiderOnlineRetrievalDeals(ctx, b)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsPieceCidBlocklist(ctx context.Context) ([]cid.Cid, error) {
|
||||
return c.Internal.DealsPieceCidBlocklist(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsSetPieceCidBlocklist(ctx context.Context, cids []cid.Cid) error {
|
||||
return c.Internal.DealsSetPieceCidBlocklist(ctx, cids)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsConsiderOfflineStorageDeals(ctx context.Context) (bool, error) {
|
||||
return c.Internal.DealsConsiderOfflineStorageDeals(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsSetConsiderOfflineStorageDeals(ctx context.Context, b bool) error {
|
||||
return c.Internal.DealsSetConsiderOfflineStorageDeals(ctx, b)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsConsiderOfflineRetrievalDeals(ctx context.Context) (bool, error) {
|
||||
return c.Internal.DealsConsiderOfflineRetrievalDeals(ctx)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) DealsSetConsiderOfflineRetrievalDeals(ctx context.Context, b bool) error {
|
||||
return c.Internal.DealsSetConsiderOfflineRetrievalDeals(ctx, b)
|
||||
}
|
||||
|
||||
func (c *StorageMinerStruct) StorageAddLocal(ctx context.Context, path string) error {
|
||||
return c.Internal.StorageAddLocal(ctx, path)
|
||||
}
|
||||
@@ -880,8 +962,16 @@ func (w *WorkerStruct) SealCommit2(ctx context.Context, sector abi.SectorID, c1o
|
||||
return w.Internal.SealCommit2(ctx, sector, c1o)
|
||||
}
|
||||
|
||||
func (w *WorkerStruct) FinalizeSector(ctx context.Context, sector abi.SectorID) error {
|
||||
return w.Internal.FinalizeSector(ctx, sector)
|
||||
func (w *WorkerStruct) FinalizeSector(ctx context.Context, sector abi.SectorID, keepUnsealed []storage.Range) error {
|
||||
return w.Internal.FinalizeSector(ctx, sector, keepUnsealed)
|
||||
}
|
||||
|
||||
func (w *WorkerStruct) ReleaseUnsealed(ctx context.Context, sector abi.SectorID, safeToFree []storage.Range) error {
|
||||
return w.Internal.ReleaseUnsealed(ctx, sector, safeToFree)
|
||||
}
|
||||
|
||||
func (w *WorkerStruct) Remove(ctx context.Context, sector abi.SectorID) error {
|
||||
return w.Internal.Remove(ctx, sector)
|
||||
}
|
||||
|
||||
func (w *WorkerStruct) MoveStorage(ctx context.Context, sector abi.SectorID) error {
|
||||
|
||||
+62
-21
@@ -16,6 +16,7 @@ import (
|
||||
"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"
|
||||
@@ -72,10 +73,14 @@ func init() {
|
||||
addExample(pid)
|
||||
|
||||
addExample(bitfield.NewFromSet([]uint64{5}))
|
||||
addExample(abi.RegisteredProof_StackedDRG32GiBPoSt)
|
||||
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)
|
||||
@@ -93,18 +98,18 @@ func init() {
|
||||
addExample(build.APIVersion)
|
||||
addExample(api.PCHInbound)
|
||||
addExample(time.Minute)
|
||||
addExample(&types.ExecutionResult{
|
||||
Msg: exampleValue(reflect.TypeOf(&types.Message{})).(*types.Message),
|
||||
MsgRct: exampleValue(reflect.TypeOf(&types.MessageReceipt{})).(*types.MessageReceipt),
|
||||
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{})).(types.Actor),
|
||||
"t01236": exampleValue(reflect.TypeOf(types.Actor{}), nil).(types.Actor),
|
||||
})
|
||||
addExample(map[string]api.MarketDeal{
|
||||
"t026363": exampleValue(reflect.TypeOf(api.MarketDeal{})).(api.MarketDeal),
|
||||
"t026363": exampleValue(reflect.TypeOf(api.MarketDeal{}), nil).(api.MarketDeal),
|
||||
})
|
||||
addExample(map[string]api.MarketBalance{
|
||||
"t026363": exampleValue(reflect.TypeOf(api.MarketBalance{})).(api.MarketBalance),
|
||||
"t026363": exampleValue(reflect.TypeOf(api.MarketBalance{}), nil).(api.MarketBalance),
|
||||
})
|
||||
|
||||
maddr, err := multiaddr.NewMultiaddr("/ip4/52.36.61.156/tcp/1347/p2p/12D3KooWFETiESTf1v4PGUvtnxMAcEFMzLZbJGg4tjWfGEimYior")
|
||||
@@ -117,7 +122,7 @@ func init() {
|
||||
|
||||
}
|
||||
|
||||
func exampleValue(t reflect.Type) interface{} {
|
||||
func exampleValue(t, parent reflect.Type) interface{} {
|
||||
v, ok := ExampleValues[t]
|
||||
if ok {
|
||||
return v
|
||||
@@ -126,25 +131,25 @@ func exampleValue(t reflect.Type) interface{} {
|
||||
switch t.Kind() {
|
||||
case reflect.Slice:
|
||||
out := reflect.New(t).Elem()
|
||||
reflect.Append(out, reflect.ValueOf(exampleValue(t.Elem())))
|
||||
reflect.Append(out, reflect.ValueOf(exampleValue(t.Elem(), t)))
|
||||
return out.Interface()
|
||||
case reflect.Chan:
|
||||
return exampleValue(t.Elem())
|
||||
return exampleValue(t.Elem(), nil)
|
||||
case reflect.Struct:
|
||||
es := exampleStruct(t)
|
||||
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())))
|
||||
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())
|
||||
es := exampleStruct(t.Elem(), t)
|
||||
//ExampleValues[t] = es
|
||||
return es
|
||||
}
|
||||
@@ -155,12 +160,15 @@ func exampleValue(t reflect.Type) interface{} {
|
||||
panic(fmt.Sprintf("No example value for type: %s", t))
|
||||
}
|
||||
|
||||
func exampleStruct(t reflect.Type) interface{} {
|
||||
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)))
|
||||
ns.Elem().Field(i).Set(reflect.ValueOf(exampleValue(f.Type, t)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,17 +294,17 @@ func main() {
|
||||
ft := m.Func.Type()
|
||||
for j := 2; j < ft.NumIn(); j++ {
|
||||
inp := ft.In(j)
|
||||
args = append(args, exampleValue(inp))
|
||||
args = append(args, exampleValue(inp, nil))
|
||||
}
|
||||
|
||||
v, err := json.Marshal(args)
|
||||
v, err := json.MarshalIndent(args, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
outv := exampleValue(ft.Out(0))
|
||||
outv := exampleValue(ft.Out(0), nil)
|
||||
|
||||
ov, err := json.Marshal(outv)
|
||||
ov, err := json.MarshalIndent(outv, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -318,6 +326,18 @@ func main() {
|
||||
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)
|
||||
@@ -331,8 +351,29 @@ func main() {
|
||||
fmt.Printf("### %s\n", m.Name)
|
||||
fmt.Printf("%s\n\n", m.Comment)
|
||||
|
||||
fmt.Printf("Inputs: `%s`\n\n", m.InputExample)
|
||||
fmt.Printf("Response: `%s`\n\n", m.ResponseExample)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-6
@@ -8,6 +8,7 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -52,11 +53,11 @@ func TestDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, carExport
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
mine := true
|
||||
mine := int64(1)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
for mine {
|
||||
for atomic.LoadInt64(&mine) == 1 {
|
||||
time.Sleep(blocktime)
|
||||
if err := sn[0].MineOne(ctx, func(bool) {}); err != nil {
|
||||
t.Error(err)
|
||||
@@ -66,7 +67,7 @@ func TestDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, carExport
|
||||
|
||||
makeDeal(t, ctx, 6, client, miner, carExport)
|
||||
|
||||
mine = false
|
||||
atomic.AddInt64(&mine, -1)
|
||||
fmt.Println("shutting down mining")
|
||||
<-done
|
||||
}
|
||||
@@ -89,12 +90,12 @@ func TestDoubleDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration) {
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
mine := true
|
||||
mine := int64(1)
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer close(done)
|
||||
for mine {
|
||||
for atomic.LoadInt64(&mine) == 1 {
|
||||
time.Sleep(blocktime)
|
||||
if err := sn[0].MineOne(ctx, func(bool) {}); err != nil {
|
||||
t.Error(err)
|
||||
@@ -105,7 +106,7 @@ func TestDoubleDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration) {
|
||||
makeDeal(t, ctx, 6, client, miner, false)
|
||||
makeDeal(t, ctx, 7, client, miner, false)
|
||||
|
||||
mine = false
|
||||
atomic.AddInt64(&mine, -1)
|
||||
fmt.Println("shutting down mining")
|
||||
<-done
|
||||
}
|
||||
|
||||
+3
-2
@@ -126,6 +126,7 @@ func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExpo
|
||||
minedTwo := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
doneMinedTwo := false
|
||||
defer close(done)
|
||||
|
||||
prevExpect := 0
|
||||
@@ -175,9 +176,9 @@ func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExpo
|
||||
time.Sleep(blocktime)
|
||||
}
|
||||
|
||||
if prevExpect == 2 && expect == 2 && minedTwo != nil {
|
||||
if prevExpect == 2 && expect == 2 && !doneMinedTwo {
|
||||
close(minedTwo)
|
||||
minedTwo = nil
|
||||
doneMinedTwo = true
|
||||
}
|
||||
|
||||
prevExpect = expect
|
||||
|
||||
@@ -2,6 +2,9 @@ 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"
|
||||
@@ -39,3 +42,36 @@ 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
|
||||
}
|
||||
|
||||
@@ -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/t01000.miner.interopnet.kittyhawk.wtf/tcp/1347/p2p/12D3KooWALjK9ai1gzssE4H9kwmS7CVqkENedeJJzmgVS9YMtLii
|
||||
/ip4/34.217.110.132/tcp/1347/p2p/12D3KooWALjK9ai1gzssE4H9kwmS7CVqkENedeJJzmgVS9YMtLii
|
||||
/dns4/peer0.interopnet.kittyhawk.wtf/tcp/1347/p2p/12D3KooWSJYjQMi2MGaeyRAWvDnEAAC7GdFSRXk5Lns6G1ajSMA7
|
||||
/ip4/54.187.182.170/tcp/1347/p2p/12D3KooWSJYjQMi2MGaeyRAWvDnEAAC7GdFSRXk5Lns6G1ajSMA7
|
||||
/dns4/peer1.interopnet.kittyhawk.wtf/tcp/1347/p2p/12D3KooWPdeoNGcaGTxWdENfzEYJ9WqGVkJAxi2aHpX4TjkbeGkj
|
||||
/ip4/52.24.84.39/tcp/1347/p2p/12D3KooWPdeoNGcaGTxWdENfzEYJ9WqGVkJAxi2aHpX4TjkbeGkj
|
||||
/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
|
||||
Binary file not shown.
+4
-5
@@ -12,18 +12,17 @@ import (
|
||||
|
||||
func init() {
|
||||
power.ConsensusMinerMinPower = big.NewInt(2048)
|
||||
miner.SupportedProofTypes = map[abi.RegisteredProof]struct{}{
|
||||
abi.RegisteredProof_StackedDRG2KiBSeal: {},
|
||||
miner.SupportedProofTypes = map[abi.RegisteredSealProof]struct{}{
|
||||
abi.RegisteredSealProof_StackedDrg2KiBV1: {},
|
||||
}
|
||||
verifreg.MinVerifiedDealSize = big.NewInt(256)
|
||||
|
||||
BuildType |= Build2k
|
||||
}
|
||||
|
||||
// Seconds
|
||||
const BlockDelay = 2
|
||||
const BlockDelaySecs = uint64(2)
|
||||
|
||||
const PropagationDelay = 3
|
||||
const PropagationDelaySecs = uint64(3)
|
||||
|
||||
// SlashablePowerDelay is the number of epochs after ElectionPeriodStart, after
|
||||
// which the miner is slashed
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
// +build !testground
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"sort"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/protocol"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
@@ -13,32 +12,6 @@ import (
|
||||
"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))
|
||||
}
|
||||
|
||||
// /////
|
||||
// Storage
|
||||
|
||||
@@ -48,8 +21,7 @@ const UnixfsLinksPerLevel = 1024
|
||||
// /////
|
||||
// Consensus / Network
|
||||
|
||||
// Seconds
|
||||
const AllowableClockDrift = 1
|
||||
const AllowableClockDriftSecs = uint64(1)
|
||||
|
||||
// Epochs
|
||||
const ForkLengthThreshold = Finality
|
||||
@@ -59,12 +31,12 @@ var BlocksPerEpoch = uint64(builtin.ExpectedLeadersPerEpoch)
|
||||
|
||||
// Epochs
|
||||
const Finality = miner.ChainFinalityish
|
||||
const MessageConfidence = 5
|
||||
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 = 2
|
||||
const WRatioDen = uint64(2)
|
||||
|
||||
// /////
|
||||
// Proofs
|
||||
@@ -82,25 +54,25 @@ const MaxSealLookback = SealRandomnessLookbackLimit + 2000 // TODO: Get from spe
|
||||
// Mining
|
||||
|
||||
// Epochs
|
||||
const TicketRandomnessLookback = 1
|
||||
const TicketRandomnessLookback = abi.ChainEpoch(1)
|
||||
|
||||
const WinningPoStSectorSetLookback = 10
|
||||
const WinningPoStSectorSetLookback = abi.ChainEpoch(10)
|
||||
|
||||
// /////
|
||||
// Devnet settings
|
||||
|
||||
const TotalFilecoin = 2_000_000_000
|
||||
const MiningRewardTotal = 1_400_000_000
|
||||
const TotalFilecoin = uint64(2_000_000_000)
|
||||
const MiningRewardTotal = uint64(1_400_000_000)
|
||||
|
||||
const FilecoinPrecision = 1_000_000_000_000_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(MiningRewardTotal)
|
||||
InitialRewardBalance = InitialRewardBalance.Mul(InitialRewardBalance, big.NewInt(FilecoinPrecision))
|
||||
InitialRewardBalance = big.NewInt(int64(MiningRewardTotal))
|
||||
InitialRewardBalance = InitialRewardBalance.Mul(InitialRewardBalance, big.NewInt(int64(FilecoinPrecision)))
|
||||
}
|
||||
|
||||
// Sync
|
||||
@@ -121,4 +93,11 @@ const VerifSigCacheSize = 32000
|
||||
const BlockMessageLimit = 512
|
||||
const BlockGasLimit = 100_000_000_000
|
||||
|
||||
var DrandChain = `{"public_key":"922a2e93828ff83345bae533f5172669a26c02dc76d6bf59c80892e12ab1455c229211886f35bb56af6d5bea981024df","period":25,"genesis_time":1590445175,"hash":"138a324aa6540f93d0dad002aa89454b1bec2b6e948682cde6bd4db40f4b7c9b"}`
|
||||
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"}`,
|
||||
}
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
// +build !debug
|
||||
// +build !2k
|
||||
// +build !testground
|
||||
|
||||
package build
|
||||
|
||||
@@ -12,15 +13,13 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
power.ConsensusMinerMinPower = big.NewInt(1024 << 20)
|
||||
miner.SupportedProofTypes = map[abi.RegisteredProof]struct{}{
|
||||
abi.RegisteredProof_StackedDRG512MiBSeal: {},
|
||||
abi.RegisteredProof_StackedDRG32GiBSeal: {},
|
||||
abi.RegisteredProof_StackedDRG64GiBSeal: {},
|
||||
power.ConsensusMinerMinPower = big.NewInt(1024 << 30)
|
||||
miner.SupportedProofTypes = map[abi.RegisteredSealProof]struct{}{
|
||||
abi.RegisteredSealProof_StackedDrg32GiBV1: {},
|
||||
abi.RegisteredSealProof_StackedDrg64GiBV1: {},
|
||||
}
|
||||
}
|
||||
|
||||
// Seconds
|
||||
const BlockDelay = builtin.EpochDurationSeconds
|
||||
const BlockDelaySecs = uint64(builtin.EpochDurationSeconds)
|
||||
|
||||
const PropagationDelay = 6
|
||||
const PropagationDelaySecs = uint64(6)
|
||||
|
||||
@@ -1,152 +1,152 @@
|
||||
{
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0170db1f394b35d995252228ee359194b13199d259380541dc529fb0099096b0.params": {
|
||||
"cid": "QmYkygifkXnrnsN4MJsjBFHTQJHx294CyikDgDK8nYxdGh",
|
||||
"digest": "df3f30442a6d6b4192f5071fb17e820c",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0170db1f394b35d995252228ee359194b13199d259380541dc529fb0099096b0.params": {
|
||||
"cid": "QmeDRyxek34F1H6xJY6AkFdWvPsy5F6dKTrebV3ZtWT4ky",
|
||||
"digest": "f5827f2d8801c62c831e0f972f6dc8bb",
|
||||
"sector_size": 2048
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0170db1f394b35d995252228ee359194b13199d259380541dc529fb0099096b0.vk": {
|
||||
"cid": "QmdXyqbmy2bkJA9Kyhh6z25GrTCq48LwX6c1mxPsm54wi7",
|
||||
"digest": "0bea3951abf9557a3569f68e52a30c6c",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0170db1f394b35d995252228ee359194b13199d259380541dc529fb0099096b0.vk": {
|
||||
"cid": "QmUw1ZmG4BBbX19MsbH3zAEGKUc42iFJc5ZAyomDHeJTsA",
|
||||
"digest": "398fecdb4b2de445125852bc3c080b35",
|
||||
"sector_size": 2048
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0cfb4f178bbb71cf2ecfcd42accce558b27199ab4fb59cb78f2483fe21ef36d9.params": {
|
||||
"cid": "Qmf5XZZtP5VcYTf65MbKjLVabcS6cYMbr2rFShmfJzh5e5",
|
||||
"digest": "655e6277638edc8c658094f6f0b33d54",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0cfb4f178bbb71cf2ecfcd42accce558b27199ab4fb59cb78f2483fe21ef36d9.params": {
|
||||
"cid": "QmUeNKp9YZpiAFm81RV5KuxH1FDGJx2DuwcbU2XNSZLLSv",
|
||||
"digest": "2b6d2972ac9e862e8134d98fb695b0c5",
|
||||
"sector_size": 536870912
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0cfb4f178bbb71cf2ecfcd42accce558b27199ab4fb59cb78f2483fe21ef36d9.vk": {
|
||||
"cid": "QmPuhdWnAXBks43emnkqi9FQzyU1gASKyz23zrD27BPGs8",
|
||||
"digest": "57690e3a6a94c3f704802a674b34f36b",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0cfb4f178bbb71cf2ecfcd42accce558b27199ab4fb59cb78f2483fe21ef36d9.vk": {
|
||||
"cid": "QmQaQmTXX995Akd66ggtJY5bNx6Gkxk8P34JTdMMq8393G",
|
||||
"digest": "3688c9eb256b7b17f411dad78d5ef74a",
|
||||
"sector_size": 536870912
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-3ea05428c9d11689f23529cde32fd30aabd50f7d2c93657c1d3650bca3e8ea9e.params": {
|
||||
"cid": "QmPNVgTN7N5vDtD5u7ERMTLcvUtrKRBfYVUDr6uW3pKhX7",
|
||||
"digest": "3d390654f58e603b896ac70c653f5676",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-3ea05428c9d11689f23529cde32fd30aabd50f7d2c93657c1d3650bca3e8ea9e.params": {
|
||||
"cid": "QmfEYTMSkwGJTumQx26iKXGNKiYh3mmAC4SkdybZpJCj5p",
|
||||
"digest": "09bff16aed893349d94485cfae366a9c",
|
||||
"sector_size": 2048
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-3ea05428c9d11689f23529cde32fd30aabd50f7d2c93657c1d3650bca3e8ea9e.vk": {
|
||||
"cid": "Qmbj61Zez7v5xA7nSCnmWbyLYznWJDWeusz7Yg8EcgVdoN",
|
||||
"digest": "8c170a164743c39576a7f47a1b51e6f3",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-3ea05428c9d11689f23529cde32fd30aabd50f7d2c93657c1d3650bca3e8ea9e.vk": {
|
||||
"cid": "QmP4ThPieSUJyRanjibWpT5R5cCMzMAU4j8Y7kBn7CSW1Q",
|
||||
"digest": "142f2f7e8f1b1779290315cabfd2c803",
|
||||
"sector_size": 2048
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-50c7368dea9593ed0989e70974d28024efa9d156d585b7eea1be22b2e753f331.params": {
|
||||
"cid": "QmRApb8RZoBK3cqicT7V3ydXg8yVvqPFMPrQNXP33aBihp",
|
||||
"digest": "b1b58ff9a297b82885e8a7dfb035f83c",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-50c7368dea9593ed0989e70974d28024efa9d156d585b7eea1be22b2e753f331.params": {
|
||||
"cid": "QmcAixrHsz29DgvtZiMc2kQjvPRvWxYUp36QYmRDZbmREm",
|
||||
"digest": "8f987f64d434365562180b96ec12e299",
|
||||
"sector_size": 8388608
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-50c7368dea9593ed0989e70974d28024efa9d156d585b7eea1be22b2e753f331.vk": {
|
||||
"cid": "QmcytF1dTdqMFoyXi931j1RgmGtLfR9LLLaBznRt1tPQyD",
|
||||
"digest": "1a09e00c641f192f55af3433a028f050",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-50c7368dea9593ed0989e70974d28024efa9d156d585b7eea1be22b2e753f331.vk": {
|
||||
"cid": "QmT4iFnbL6r4txS5PXsiV7NTzbhCxHy54PvdkJJGV2VFXb",
|
||||
"digest": "94b6c24ac01924f4feeecedd16b5d77d",
|
||||
"sector_size": 8388608
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-5294475db5237a2e83c3e52fd6c2b03859a1831d45ed08c4f35dbf9a803165a9.params": {
|
||||
"cid": "QmPvr54tWaVeP4WnekivzUAJitTqsQfvikBvAHNEaDNQSw",
|
||||
"digest": "9380e41368ed4083dbc922b290d3b786",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-5294475db5237a2e83c3e52fd6c2b03859a1831d45ed08c4f35dbf9a803165a9.params": {
|
||||
"cid": "QmbjFst6SFCK1KsTQrfwPdxf3VTNa1raed574tEZZ9PoyQ",
|
||||
"digest": "2c245fe8179839dd6c6cdea207c67ae8",
|
||||
"sector_size": 8388608
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-5294475db5237a2e83c3e52fd6c2b03859a1831d45ed08c4f35dbf9a803165a9.vk": {
|
||||
"cid": "QmXyVLVDRCcxA9SjT7PeK8HFtyxZ2ZH3SHa8KoGLw8VGJt",
|
||||
"digest": "f0731a7e20f90704bd38fc5d27882f6d",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-5294475db5237a2e83c3e52fd6c2b03859a1831d45ed08c4f35dbf9a803165a9.vk": {
|
||||
"cid": "QmQJKmvZN1a5cQ1Nw6CDyXs3nuRPzvyU5NvCFMUL2BfcZC",
|
||||
"digest": "56ae47bfda53bb8d22981ed8d8d27d72",
|
||||
"sector_size": 8388608
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-7d739b8cf60f1b0709eeebee7730e297683552e4b69cab6984ec0285663c5781.params": {
|
||||
"cid": "Qmf5f6ko3dqj7qauzXpZqxM9B2x2sL977K6gE2ppNwuJPv",
|
||||
"digest": "273ebb8c896326b7c292bee8b775fd38",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-7d739b8cf60f1b0709eeebee7730e297683552e4b69cab6984ec0285663c5781.params": {
|
||||
"cid": "QmQCABxeTpdvXTyjDyk7nPBxkQzCh7MXfGztWnSXEPKMLW",
|
||||
"digest": "7e6b2eb5ecbb11ac651ad66ebbb2075a",
|
||||
"sector_size": 536870912
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-7d739b8cf60f1b0709eeebee7730e297683552e4b69cab6984ec0285663c5781.vk": {
|
||||
"cid": "QmfP3MQe8koW63n5MkDENENVHxib78MJYYyZvbneCsuze8",
|
||||
"digest": "3dd94da9da64e51b3445bc528d84e76d",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-7d739b8cf60f1b0709eeebee7730e297683552e4b69cab6984ec0285663c5781.vk": {
|
||||
"cid": "QmPBweyugh5Sx4umk8ULhgEGbjY8xmWLfU6M7EMpc8Mad6",
|
||||
"digest": "94a8d9e25a9ab9674d339833664eba25",
|
||||
"sector_size": 536870912
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-0377ded656c6f524f1618760bffe4e0a1c51d5a70c4509eedae8a27555733edc.params": {
|
||||
"cid": "QmYEeeCE8uT2bsVkxcqqUYeMmMEbe6rfmo8wQCv7jFHqqm",
|
||||
"digest": "c947f2021304ed43b7216f7a8436e294",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-0377ded656c6f524f1618760bffe4e0a1c51d5a70c4509eedae8a27555733edc.params": {
|
||||
"cid": "QmY5yax1E9KymBnCeHksE9Zi8NieZbmwcpoDGoabkeeb9h",
|
||||
"digest": "c909ea9e3fe25ab9b391a64593afdbba",
|
||||
"sector_size": 34359738368
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-0377ded656c6f524f1618760bffe4e0a1c51d5a70c4509eedae8a27555733edc.vk": {
|
||||
"cid": "QmXB63ExriFjB4ywWnXTnFwCcLFfCeEP3h15qtL5i7F4aX",
|
||||
"digest": "ab20d7b253e7e9a0d2ccdf7599ec8ec3",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-0377ded656c6f524f1618760bffe4e0a1c51d5a70c4509eedae8a27555733edc.vk": {
|
||||
"cid": "QmXnPo4yH5mwMguwrvqgRfduSttbmPrXtbBfbwU21wQWHt",
|
||||
"digest": "caf900461e988bbf86dbcaca087b7864",
|
||||
"sector_size": 34359738368
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-559e581f022bb4e4ec6e719e563bf0e026ad6de42e56c18714a2c692b1b88d7e.params": {
|
||||
"cid": "QmW5Yxg3L1NSzuQVcRMHMbG3uvVoi4dTLzVaDpnEUPQpnA",
|
||||
"digest": "079ba19645828ae42b22b0e3f4866e8d",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-559e581f022bb4e4ec6e719e563bf0e026ad6de42e56c18714a2c692b1b88d7e.params": {
|
||||
"cid": "QmZtzzPWwmZEgR7MSMvXRbt9KVK8k4XZ5RLWHybHJW9SdE",
|
||||
"digest": "a2844f0703f186d143a06146a04577d8",
|
||||
"sector_size": 34359738368
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-559e581f022bb4e4ec6e719e563bf0e026ad6de42e56c18714a2c692b1b88d7e.vk": {
|
||||
"cid": "QmQzZ5dJ11tcSBees38WX41tZLXS9BqpEti253m5QcnTNs",
|
||||
"digest": "c76125a50a7de315165de359b5174ae4",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-559e581f022bb4e4ec6e719e563bf0e026ad6de42e56c18714a2c692b1b88d7e.vk": {
|
||||
"cid": "QmWxEA7EdQCUJTzjNpxg5XTF45D2uVyYnN1QRUb5TRYU8M",
|
||||
"digest": "2306247a1e616dbe07f01b88196c2044",
|
||||
"sector_size": 34359738368
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-2627e4006b67f99cef990c0a47d5426cb7ab0a0ad58fc1061547bf2d28b09def.params": {
|
||||
"cid": "QmNk3wga1tS53FUu1QnkK8ehWA2cqpCnSEAPv3KLxdJxNa",
|
||||
"digest": "421e4790c0b80e0107a7ff67acf14084",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-2627e4006b67f99cef990c0a47d5426cb7ab0a0ad58fc1061547bf2d28b09def.params": {
|
||||
"cid": "QmP676KwuvyF9Y64uJnXvLtvD1xcuWQ6wD23RzYtQ6dd4f",
|
||||
"digest": "215b1c667a4f46a1d0178338df568615",
|
||||
"sector_size": 68719476736
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-2627e4006b67f99cef990c0a47d5426cb7ab0a0ad58fc1061547bf2d28b09def.vk": {
|
||||
"cid": "QmVQCHGsrUtbn9RjHs1e6GXfeXDW5m9w4ge48PSX3Z2as2",
|
||||
"digest": "8b60e9cc1470a6729c687d6cf0a1f79c",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-2627e4006b67f99cef990c0a47d5426cb7ab0a0ad58fc1061547bf2d28b09def.vk": {
|
||||
"cid": "QmPvPwbJtcSGyqB1rQJhSF5yvFbX9ZBSsHVej5F8JUyHUJ",
|
||||
"digest": "0c9c423b28b1455fcbc329a1045fd4dd",
|
||||
"sector_size": 68719476736
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-b62098629d07946e9028127e70295ed996fe3ed25b0f9f88eb610a0ab4385a3c.params": {
|
||||
"cid": "QmTL3VvydaMFWKvE5VzxjgKsJYgL9JMM4JVYNtQxdj9JK1",
|
||||
"digest": "2685f31124b22ea6b2857e5a5e87ffa3",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-b62098629d07946e9028127e70295ed996fe3ed25b0f9f88eb610a0ab4385a3c.params": {
|
||||
"cid": "QmUxPQfvckzm1t6MFRdDZ1fDK5UJzAjK7pTZ97cwyachdr",
|
||||
"digest": "965132f51ae445b0e6d32692b7561995",
|
||||
"sector_size": 68719476736
|
||||
},
|
||||
"v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-b62098629d07946e9028127e70295ed996fe3ed25b0f9f88eb610a0ab4385a3c.vk": {
|
||||
"cid": "QmSVWbLqQYbUbbJyfsRMzEib2rfSqMtnPks1Nw22omcBQm",
|
||||
"digest": "efe703cd2839597c7ca5c2a906b74296",
|
||||
"v27-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-b62098629d07946e9028127e70295ed996fe3ed25b0f9f88eb610a0ab4385a3c.vk": {
|
||||
"cid": "QmTxq2EBnQWb5R8tS4MHdchj4vNfLYGoSXxwJFvs5xgW4K",
|
||||
"digest": "fc8c3d26e0e56373ad96cb41520d55a6",
|
||||
"sector_size": 68719476736
|
||||
},
|
||||
"v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-032d3138d22506ec0082ed72b2dcba18df18477904e35bafee82b3793b06832f.params": {
|
||||
"cid": "QmU9dH31nZZUJnsogR4Ld4ySUcH6wm2RgmGiujwnqtbU6k",
|
||||
"digest": "fcef8e87ae2afd7a28aae44347b804cf",
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-032d3138d22506ec0082ed72b2dcba18df18477904e35bafee82b3793b06832f.params": {
|
||||
"cid": "QmRjgZHERgqGoRagR788Kh6ybi26csVYa8mqbqhmZm57Jx",
|
||||
"digest": "cfc7b0897d1eee48c586f7beb89e67f7",
|
||||
"sector_size": 2048
|
||||
},
|
||||
"v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-032d3138d22506ec0082ed72b2dcba18df18477904e35bafee82b3793b06832f.vk": {
|
||||
"cid": "QmdJ15DMGPooye5NaPcRfXUdHUDibcN7hKjbmTGuu1K4AQ",
|
||||
"digest": "2ee2b3518229680db15161d4f582af37",
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-032d3138d22506ec0082ed72b2dcba18df18477904e35bafee82b3793b06832f.vk": {
|
||||
"cid": "QmNjvnvFP7KgovHUddULoB19fBHT81iz7NcUbzEHZUUPsm",
|
||||
"digest": "fb59bd061c987eac7068008c44de346b",
|
||||
"sector_size": 2048
|
||||
},
|
||||
"v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-6babf46ce344ae495d558e7770a585b2382d54f225af8ed0397b8be7c3fcd472.params": {
|
||||
"cid": "QmZgtxcY3tMXXQxZTA7ZTUDXLVUnfxNcerXgeW4gG2NnfP",
|
||||
"digest": "3273c7135cb75684248b475781b738ee",
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-6babf46ce344ae495d558e7770a585b2382d54f225af8ed0397b8be7c3fcd472.params": {
|
||||
"cid": "QmTpRPBA4dt8fgGpcVzi4L1KA1U2eBHCE8WVmS2GUygMvT",
|
||||
"digest": "36d465915b0afbf96bd08e7915e00952",
|
||||
"sector_size": 536870912
|
||||
},
|
||||
"v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-6babf46ce344ae495d558e7770a585b2382d54f225af8ed0397b8be7c3fcd472.vk": {
|
||||
"cid": "QmSS6ZkAV2aGZcgKgdPpEEgihXF1ryZX8PSAZDWSoeL1d4",
|
||||
"digest": "1519b5f61d9044a59f2bdc57537c094b",
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-6babf46ce344ae495d558e7770a585b2382d54f225af8ed0397b8be7c3fcd472.vk": {
|
||||
"cid": "QmRzDyVfQCLsxspoVsed5bcQRsG6KiktngJfcNBL3TJPZe",
|
||||
"digest": "99d16df0eb6a7e227a4f4570c4f6b6f1",
|
||||
"sector_size": 536870912
|
||||
},
|
||||
"v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-ecd683648512ab1765faa2a5f14bab48f676e633467f0aa8aad4b55dcb0652bb.params": {
|
||||
"cid": "QmQBGXeiNn6hVwbR6qFarQqiNGDdKk4h9ucfyvcXyfYz2N",
|
||||
"digest": "7d5f896f435c38e93bcda6dd168d860b",
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-ecd683648512ab1765faa2a5f14bab48f676e633467f0aa8aad4b55dcb0652bb.params": {
|
||||
"cid": "QmV8ZjTSGzDUWmFvsq9NSyPBR7eDDUcvCPNgj2yE7HMAFu",
|
||||
"digest": "34f3ddf1d1c9f41c0cd73b91e8b4bc27",
|
||||
"sector_size": 8388608
|
||||
},
|
||||
"v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-ecd683648512ab1765faa2a5f14bab48f676e633467f0aa8aad4b55dcb0652bb.vk": {
|
||||
"cid": "QmPrZgBVGMckEAeu5eSJnLmiAwcPQjKjZe5ir6VaQ5AxKs",
|
||||
"digest": "fe6d2de44580a0db5a4934688899b92f",
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-ecd683648512ab1765faa2a5f14bab48f676e633467f0aa8aad4b55dcb0652bb.vk": {
|
||||
"cid": "QmTa3VbjTiqJWU6r4WKayaQrUaaBsrpp5UDqYvPDd2C5hs",
|
||||
"digest": "ec62d59651daa5631d3d1e9c782dd940",
|
||||
"sector_size": 8388608
|
||||
},
|
||||
"v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-0-sha256_hasher-82a357d2f2ca81dc61bb45f4a762807aedee1b0a53fd6c4e77b46a01bfef7820.params": {
|
||||
"cid": "QmZL2cq45XJn5BFzagAZwgFmLrcM1W6CXoiEF9C5j5tjEF",
|
||||
"digest": "acdfed9f0512bc85a01a9fb871d475d5",
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-0-sha256_hasher-82a357d2f2ca81dc61bb45f4a762807aedee1b0a53fd6c4e77b46a01bfef7820.params": {
|
||||
"cid": "Qmf8ngfArxrv9tFWDqBcNegdBMymvuakwyHKd1pbW3pbsb",
|
||||
"digest": "a16d6f4c6424fb280236739f84b24f97",
|
||||
"sector_size": 34359738368
|
||||
},
|
||||
"v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-0-sha256_hasher-82a357d2f2ca81dc61bb45f4a762807aedee1b0a53fd6c4e77b46a01bfef7820.vk": {
|
||||
"cid": "QmQ4zB7nNa1tDYNifBkExRnZtwtxZw775iaqvVsZyRi6Q2",
|
||||
"digest": "524a2f3e9d6826593caebc41bb545c40",
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-0-sha256_hasher-82a357d2f2ca81dc61bb45f4a762807aedee1b0a53fd6c4e77b46a01bfef7820.vk": {
|
||||
"cid": "QmfQgVFerArJ6Jupwyc9tKjLD9n1J9ajLHBdpY465tRM7M",
|
||||
"digest": "7a139d82b8a02e35279d657e197f5c1f",
|
||||
"sector_size": 34359738368
|
||||
},
|
||||
"v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-2-sha256_hasher-96f1b4a04c5c51e4759bbf224bbc2ef5a42c7100f16ec0637123f16a845ddfb2.params": {
|
||||
"cid": "QmY7DitNKXFeLQt9QoVQkfjM1EvRnprqUVxjmkTXkHDNka",
|
||||
"digest": "f27271c0537ba65ade2ec045f8fbd069",
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-2-sha256_hasher-96f1b4a04c5c51e4759bbf224bbc2ef5a42c7100f16ec0637123f16a845ddfb2.params": {
|
||||
"cid": "QmfDha8271nXJn14Aq3qQeghjMBWbs6HNSGa6VuzCVk4TW",
|
||||
"digest": "5d3cd3f107a3bea8a96d1189efd2965c",
|
||||
"sector_size": 68719476736
|
||||
},
|
||||
"v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-2-sha256_hasher-96f1b4a04c5c51e4759bbf224bbc2ef5a42c7100f16ec0637123f16a845ddfb2.vk": {
|
||||
"cid": "QmUJsvoCuQ4LszPmeRVAkMYb5qY95ctz3UXKhu8xLzyFKo",
|
||||
"digest": "576b292938c6c9d0a0e721bd867a543b",
|
||||
"v27-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-2-sha256_hasher-96f1b4a04c5c51e4759bbf224bbc2ef5a42c7100f16ec0637123f16a845ddfb2.vk": {
|
||||
"cid": "QmRVtTtiFzHJTHurYzaCvetGAchux9cktixT4aGHthN6Zt",
|
||||
"digest": "62c366405404e60f171e661492740b1c",
|
||||
"sector_size": 68719476736
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -25,7 +25,7 @@ func buildType() string {
|
||||
}
|
||||
|
||||
// BuildVersion is the local build version, set by build system
|
||||
const BuildVersion = "0.4.0"
|
||||
const BuildVersion = "0.4.1"
|
||||
|
||||
func UserVersion() string {
|
||||
return BuildVersion + buildType() + CurrentCommit
|
||||
@@ -53,7 +53,7 @@ func (ve Version) EqMajorMinor(v2 Version) bool {
|
||||
}
|
||||
|
||||
// APIVersion is a semver version of the rpc api exposed
|
||||
var APIVersion Version = newVer(0, 3, 0)
|
||||
var APIVersion Version = newVer(0, 5, 0)
|
||||
|
||||
//nolint:varcheck,deadcode
|
||||
const (
|
||||
|
||||
@@ -17,6 +17,10 @@ type Response struct {
|
||||
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
|
||||
|
||||
+31
-31
@@ -8,8 +8,9 @@ import (
|
||||
|
||||
dchain "github.com/drand/drand/chain"
|
||||
dclient "github.com/drand/drand/client"
|
||||
gclient "github.com/drand/drand/cmd/relay-gossip/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"
|
||||
@@ -18,31 +19,15 @@ import (
|
||||
logging "github.com/ipfs/go-log"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"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/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
)
|
||||
|
||||
var log = logging.Logger("drand")
|
||||
|
||||
var drandServers = []string{
|
||||
"https://pl-eu.testnet.drand.sh",
|
||||
"https://pl-us.testnet.drand.sh",
|
||||
"https://pl-sin.testnet.drand.sh",
|
||||
}
|
||||
|
||||
var drandChain *dchain.Info
|
||||
|
||||
func init() {
|
||||
|
||||
var err error
|
||||
drandChain, err = dchain.InfoFromJSON(bytes.NewReader([]byte(build.DrandChain)))
|
||||
if err != nil {
|
||||
panic("could not unmarshal chain info: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
type drandPeer struct {
|
||||
addr string
|
||||
tls bool
|
||||
@@ -56,6 +41,13 @@ 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
|
||||
|
||||
@@ -72,18 +64,34 @@ type DrandBeacon struct {
|
||||
localCache map[uint64]types.BeaconEntry
|
||||
}
|
||||
|
||||
func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub) (*DrandBeacon, error) {
|
||||
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.WithHTTPEndpoints(drandServers),
|
||||
dclient.WithCacheSize(1024),
|
||||
dclient.WithLogger(dlogger),
|
||||
dclient.WithAutoWatch(),
|
||||
}
|
||||
|
||||
if ps != nil {
|
||||
@@ -92,19 +100,11 @@ func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub) (*DrandBeacon
|
||||
log.Info("drand beacon without pubsub")
|
||||
}
|
||||
|
||||
client, err := dclient.New(opts...)
|
||||
client, err := dclient.Wrap(clients, opts...)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("creating drand client")
|
||||
}
|
||||
|
||||
go func() {
|
||||
// Explicitly Watch until that is fixed in drand
|
||||
ch := client.Watch(context.Background())
|
||||
for range ch {
|
||||
}
|
||||
log.Error("dranch Watch bork")
|
||||
}()
|
||||
|
||||
db := &DrandBeacon{
|
||||
client: client,
|
||||
localCache: make(map[uint64]types.BeaconEntry),
|
||||
|
||||
@@ -5,12 +5,15 @@ import (
|
||||
"testing"
|
||||
|
||||
dchain "github.com/drand/drand/chain"
|
||||
dclient "github.com/drand/drand/client"
|
||||
hclient "github.com/drand/drand/client/http"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
)
|
||||
|
||||
func TestPrintGroupInfo(t *testing.T) {
|
||||
c, err := dclient.NewHTTPClient(drandServers[0], nil, nil)
|
||||
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)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"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"
|
||||
|
||||
@@ -27,6 +28,24 @@ const BlockSyncProtocolID = "/fil/sync/blk/0.0.1"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -64,6 +64,11 @@ func (bs *BlockSync) processStatus(req *BlockSyncRequest, res *BlockSyncResponse
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
@@ -80,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)
|
||||
|
||||
@@ -356,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()
|
||||
}
|
||||
|
||||
+4
-15
@@ -51,7 +51,7 @@ type Events struct {
|
||||
readyOnce sync.Once
|
||||
|
||||
heightEvents
|
||||
calledEvents
|
||||
*hcEvents
|
||||
}
|
||||
|
||||
func NewEvents(ctx context.Context, api eventAPI) *Events {
|
||||
@@ -74,18 +74,7 @@ func NewEvents(ctx context.Context, api eventAPI) *Events {
|
||||
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[abi.ChainEpoch]map[triggerID]int{},
|
||||
},
|
||||
hcEvents: newHCEvents(ctx, api, tsc, uint64(gcConfidence)),
|
||||
}
|
||||
|
||||
e.ready.Add(1)
|
||||
@@ -143,7 +132,7 @@ func (e *Events) listenHeadChangesOnce(ctx context.Context) error {
|
||||
}
|
||||
|
||||
e.readyOnce.Do(func() {
|
||||
e.at = cur[0].Val.Height()
|
||||
e.lastTs = cur[0].Val
|
||||
|
||||
e.ready.Done()
|
||||
})
|
||||
@@ -186,5 +175,5 @@ func (e *Events) headChange(rev, app []*types.TipSet) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.headChangeCalled(rev, app)
|
||||
return e.processHeadChangeEvent(rev, app)
|
||||
}
|
||||
|
||||
+363
-109
@@ -13,6 +13,7 @@ import (
|
||||
)
|
||||
|
||||
const NoTimeout = math.MaxInt64
|
||||
const NoHeight = abi.ChainEpoch(-1)
|
||||
|
||||
type triggerID = uint64
|
||||
|
||||
@@ -23,54 +24,60 @@ type msgH = abi.ChainEpoch
|
||||
// message (msgH+confidence)
|
||||
type triggerH = abi.ChainEpoch
|
||||
|
||||
// CalledHandler arguments:
|
||||
// `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 abi.ChainEpoch) (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 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
|
||||
|
||||
h abi.ChainEpoch
|
||||
msg *types.Message
|
||||
prevH abi.ChainEpoch
|
||||
h abi.ChainEpoch
|
||||
data eventData
|
||||
|
||||
called bool
|
||||
}
|
||||
|
||||
type calledEvents struct {
|
||||
// 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
|
||||
|
||||
at abi.ChainEpoch
|
||||
lastTs *types.TipSet
|
||||
|
||||
lk sync.Mutex
|
||||
|
||||
ctr triggerID
|
||||
|
||||
triggers map[triggerID]*callHandler
|
||||
matchers map[triggerID][]MatchFunc
|
||||
triggers map[triggerID]*handlerInfo
|
||||
|
||||
// maps block heights to events
|
||||
// [triggerH][msgH][event]
|
||||
@@ -81,27 +88,79 @@ type calledEvents struct {
|
||||
|
||||
// [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.at = ts.Height()
|
||||
e.lastTs = ts
|
||||
}
|
||||
|
||||
for _, ts := range app {
|
||||
// called triggers
|
||||
e.checkNewCalls(ts)
|
||||
for ; e.at <= ts.Height(); e.at++ {
|
||||
e.applyWithConfidence(ts, e.at)
|
||||
// Check if the head change caused any state changes that we were
|
||||
// waiting for
|
||||
stateChanges := e.watcherEvents.checkStateChanges(e.lastTs, 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
|
||||
@@ -117,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())
|
||||
@@ -125,42 +184,15 @@ func (e *calledEvents) handleReverts(ts *types.TipSet) {
|
||||
delete(e.revertQueue, ts.Height())
|
||||
}
|
||||
|
||||
func (e *calledEvents) checkNewCalls(ts *types.TipSet) {
|
||||
pts, err := e.cs.ChainGetTipSet(e.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
|
||||
}
|
||||
|
||||
e.messagesForTs(pts, func(msg *types.Message) {
|
||||
// TODO: provide receipts
|
||||
|
||||
for tid, matchFns := range e.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 {
|
||||
e.queueForConfidence(tid, msg, ts)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *calledEvents) queueForConfidence(trigID uint64, msg *types.Message, ts *types.TipSet) {
|
||||
// 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]
|
||||
|
||||
prevH := NoHeight
|
||||
if prevTs != nil {
|
||||
prevH = prevTs.Height()
|
||||
}
|
||||
appliedH := ts.Height()
|
||||
|
||||
triggerH := appliedH + abi.ChainEpoch(trigger.confidence)
|
||||
@@ -173,17 +205,19 @@ func (e *calledEvents) queueForConfidence(trigID uint64, msg *types.Message, ts
|
||||
|
||||
byOrigH[appliedH] = append(byOrigH[appliedH], &queuedEvent{
|
||||
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, height abi.ChainEpoch) {
|
||||
// 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 {
|
||||
@@ -202,15 +236,20 @@ func (e *calledEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpo
|
||||
continue
|
||||
}
|
||||
|
||||
rec, err := e.cs.StateGetReceipt(e.ctx, event.msg.Cid(), ts.Key())
|
||||
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, 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, height, err)
|
||||
log.Errorf("chain trigger (@H %d, triggered @ %d) failed: %s", origH, height, err)
|
||||
continue // don't revert failed calls
|
||||
}
|
||||
|
||||
@@ -226,7 +265,8 @@ func (e *calledEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpo
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -256,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
|
||||
@@ -290,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
|
||||
@@ -314,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 abi.ChainEpoch, 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 + abi.ChainEpoch(confidence),
|
||||
|
||||
disabled: !more,
|
||||
|
||||
handle: hnd,
|
||||
revert: rev,
|
||||
}
|
||||
|
||||
e.matchers[id] = append(e.matchers[id], mf)
|
||||
|
||||
if timeout != NoTimeout {
|
||||
if e.timeouts[timeout+abi.ChainEpoch(confidence)] == nil {
|
||||
e.timeouts[timeout+abi.ChainEpoch(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+abi.ChainEpoch(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 abi.ChainEpoch, msg types.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()))
|
||||
}
|
||||
|
||||
@@ -26,12 +26,15 @@ type heightEvents struct {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -40,7 +43,10 @@ func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error {
|
||||
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()
|
||||
@@ -98,8 +104,10 @@ 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()
|
||||
span.End()
|
||||
|
||||
if err != nil {
|
||||
|
||||
+247
-2
@@ -1004,8 +1004,6 @@ func TestRemoveTriggersOnMessage(t *testing.T) {
|
||||
return false, true, nil
|
||||
}, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) {
|
||||
require.Equal(t, false, applied)
|
||||
fmt.Println(msg == nil)
|
||||
fmt.Println(curH)
|
||||
applied = true
|
||||
return more, nil
|
||||
}, func(_ context.Context, ts *types.TipSet) error {
|
||||
@@ -1067,3 +1065,250 @@ func TestRemoveTriggersOnMessage(t *testing.T) {
|
||||
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,137 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-amt-ipld/v2"
|
||||
"github.com/filecoin-project/lotus/api/apibstore"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/market"
|
||||
"github.com/ipfs/go-cid"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
)
|
||||
|
||||
// UserData is the data returned from the DiffFunc
|
||||
type UserData interface{}
|
||||
|
||||
// ChainAPI abstracts out calls made by this class to external APIs
|
||||
type ChainAPI interface {
|
||||
apibstore.ChainIO
|
||||
StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error)
|
||||
}
|
||||
|
||||
// StatePredicates has common predicates for responding to state changes
|
||||
type StatePredicates struct {
|
||||
api ChainAPI
|
||||
cst *cbor.BasicIpldStore
|
||||
}
|
||||
|
||||
func NewStatePredicates(api ChainAPI) *StatePredicates {
|
||||
return &StatePredicates{
|
||||
api: api,
|
||||
cst: cbor.NewCborStore(apibstore.NewAPIBlockstore(api)),
|
||||
}
|
||||
}
|
||||
|
||||
// DiffFunc check if there's a change form oldState to newState, and returns
|
||||
// - changed: was there a change
|
||||
// - user: user-defined data representing the state change
|
||||
// - err
|
||||
type DiffFunc func(ctx context.Context, oldState, newState *types.TipSet) (changed bool, user UserData, err error)
|
||||
|
||||
type DiffStateFunc func(ctx context.Context, oldActorStateHead, newActorStateHead cid.Cid) (changed bool, user UserData, err error)
|
||||
|
||||
// OnActorStateChanged calls diffStateFunc when the state changes for the given actor
|
||||
func (sp *StatePredicates) OnActorStateChanged(addr address.Address, diffStateFunc DiffStateFunc) DiffFunc {
|
||||
return func(ctx context.Context, oldState, newState *types.TipSet) (changed bool, user UserData, err error) {
|
||||
oldActor, err := sp.api.StateGetActor(ctx, addr, oldState.Key())
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
newActor, err := sp.api.StateGetActor(ctx, addr, newState.Key())
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if oldActor.Head.Equals(newActor.Head) {
|
||||
return false, nil, nil
|
||||
}
|
||||
return diffStateFunc(ctx, oldActor.Head, newActor.Head)
|
||||
}
|
||||
}
|
||||
|
||||
type DiffStorageMarketStateFunc func(ctx context.Context, oldState *market.State, newState *market.State) (changed bool, user UserData, err error)
|
||||
|
||||
// OnStorageMarketActorChanged calls diffStorageMarketState when the state changes for the market actor
|
||||
func (sp *StatePredicates) OnStorageMarketActorChanged(diffStorageMarketState DiffStorageMarketStateFunc) DiffFunc {
|
||||
return sp.OnActorStateChanged(builtin.StorageMarketActorAddr, func(ctx context.Context, oldActorStateHead, newActorStateHead cid.Cid) (changed bool, user UserData, err error) {
|
||||
var oldState market.State
|
||||
if err := sp.cst.Get(ctx, oldActorStateHead, &oldState); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
var newState market.State
|
||||
if err := sp.cst.Get(ctx, newActorStateHead, &newState); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return diffStorageMarketState(ctx, &oldState, &newState)
|
||||
})
|
||||
}
|
||||
|
||||
type DiffDealStatesFunc func(ctx context.Context, oldDealStateRoot *amt.Root, newDealStateRoot *amt.Root) (changed bool, user UserData, err error)
|
||||
|
||||
// OnDealStateChanged calls diffDealStates when the market state changes
|
||||
func (sp *StatePredicates) OnDealStateChanged(diffDealStates DiffDealStatesFunc) DiffStorageMarketStateFunc {
|
||||
return func(ctx context.Context, oldState *market.State, newState *market.State) (changed bool, user UserData, err error) {
|
||||
if oldState.States.Equals(newState.States) {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
oldRoot, err := amt.LoadAMT(ctx, sp.cst, oldState.States)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
newRoot, err := amt.LoadAMT(ctx, sp.cst, newState.States)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
return diffDealStates(ctx, oldRoot, newRoot)
|
||||
}
|
||||
}
|
||||
|
||||
// ChangedDeals is a set of changes to deal state
|
||||
type ChangedDeals map[abi.DealID]DealStateChange
|
||||
|
||||
// DealStateChange is a change in deal state from -> to
|
||||
type DealStateChange struct {
|
||||
From market.DealState
|
||||
To market.DealState
|
||||
}
|
||||
|
||||
// DealStateChangedForIDs detects changes in the deal state AMT for the given deal IDs
|
||||
func (sp *StatePredicates) DealStateChangedForIDs(dealIds []abi.DealID) DiffDealStatesFunc {
|
||||
return func(ctx context.Context, oldDealStateRoot *amt.Root, newDealStateRoot *amt.Root) (changed bool, user UserData, err error) {
|
||||
changedDeals := make(ChangedDeals)
|
||||
for _, dealID := range dealIds {
|
||||
var oldDeal, newDeal market.DealState
|
||||
err := oldDealStateRoot.Get(ctx, uint64(dealID), &oldDeal)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
err = newDealStateRoot.Get(ctx, uint64(dealID), &newDeal)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if oldDeal != newDeal {
|
||||
changedDeals[dealID] = DealStateChange{oldDeal, newDeal}
|
||||
}
|
||||
}
|
||||
if len(changedDeals) > 0 {
|
||||
return true, changedDeals, nil
|
||||
}
|
||||
return false, nil, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/crypto"
|
||||
"github.com/ipfs/go-hamt-ipld"
|
||||
|
||||
"github.com/filecoin-project/go-amt-ipld/v2"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/market"
|
||||
ds "github.com/ipfs/go-datastore"
|
||||
ds_sync "github.com/ipfs/go-datastore/sync"
|
||||
bstore "github.com/ipfs/go-ipfs-blockstore"
|
||||
cbornode "github.com/ipfs/go-ipld-cbor"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
)
|
||||
|
||||
var dummyCid cid.Cid
|
||||
|
||||
func init() {
|
||||
dummyCid, _ = cid.Parse("bafkqaaa")
|
||||
}
|
||||
|
||||
type mockAPI struct {
|
||||
ts map[types.TipSetKey]*types.Actor
|
||||
bs bstore.Blockstore
|
||||
}
|
||||
|
||||
func newMockAPI(bs bstore.Blockstore) *mockAPI {
|
||||
return &mockAPI{
|
||||
bs: bs,
|
||||
ts: make(map[types.TipSetKey]*types.Actor),
|
||||
}
|
||||
}
|
||||
|
||||
func (m mockAPI) ChainHasObj(ctx context.Context, c cid.Cid) (bool, error) {
|
||||
return m.bs.Has(c)
|
||||
}
|
||||
|
||||
func (m mockAPI) ChainReadObj(ctx context.Context, c cid.Cid) ([]byte, error) {
|
||||
blk, err := m.bs.Get(c)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("blockstore get: %w", err)
|
||||
}
|
||||
|
||||
return blk.RawData(), nil
|
||||
}
|
||||
|
||||
func (m mockAPI) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) {
|
||||
return m.ts[tsk], nil
|
||||
}
|
||||
|
||||
func (m mockAPI) setActor(tsk types.TipSetKey, act *types.Actor) {
|
||||
m.ts[tsk] = act
|
||||
}
|
||||
|
||||
func TestPredicates(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
bs := bstore.NewBlockstore(ds_sync.MutexWrap(ds.NewMapDatastore()))
|
||||
store := cbornode.NewCborStore(bs)
|
||||
|
||||
oldDeals := map[abi.DealID]*market.DealState{
|
||||
abi.DealID(1): {
|
||||
SectorStartEpoch: 1,
|
||||
LastUpdatedEpoch: 2,
|
||||
SlashEpoch: 0,
|
||||
},
|
||||
abi.DealID(2): {
|
||||
SectorStartEpoch: 4,
|
||||
LastUpdatedEpoch: 5,
|
||||
SlashEpoch: 0,
|
||||
},
|
||||
}
|
||||
oldStateC := createMarketState(ctx, t, store, oldDeals)
|
||||
|
||||
newDeals := map[abi.DealID]*market.DealState{
|
||||
abi.DealID(1): {
|
||||
SectorStartEpoch: 1,
|
||||
LastUpdatedEpoch: 3,
|
||||
SlashEpoch: 0,
|
||||
},
|
||||
abi.DealID(2): {
|
||||
SectorStartEpoch: 4,
|
||||
LastUpdatedEpoch: 6,
|
||||
SlashEpoch: 6,
|
||||
},
|
||||
}
|
||||
newStateC := createMarketState(ctx, t, store, newDeals)
|
||||
|
||||
miner, err := address.NewFromString("t00")
|
||||
require.NoError(t, err)
|
||||
oldState, err := mockTipset(miner, 1)
|
||||
require.NoError(t, err)
|
||||
newState, err := mockTipset(miner, 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
api := newMockAPI(bs)
|
||||
api.setActor(oldState.Key(), &types.Actor{Head: oldStateC})
|
||||
api.setActor(newState.Key(), &types.Actor{Head: newStateC})
|
||||
|
||||
preds := NewStatePredicates(api)
|
||||
|
||||
dealIds := []abi.DealID{abi.DealID(1), abi.DealID(2)}
|
||||
diffFn := preds.OnStorageMarketActorChanged(preds.OnDealStateChanged(preds.DealStateChangedForIDs(dealIds)))
|
||||
|
||||
// Diff a state against itself: expect no change
|
||||
changed, _, err := diffFn(ctx, oldState, oldState)
|
||||
require.NoError(t, err)
|
||||
require.False(t, changed)
|
||||
|
||||
// Diff old state against new state
|
||||
changed, val, err := diffFn(ctx, oldState, newState)
|
||||
require.NoError(t, err)
|
||||
require.True(t, changed)
|
||||
|
||||
changedDeals, ok := val.(ChangedDeals)
|
||||
require.True(t, ok)
|
||||
require.Len(t, changedDeals, 2)
|
||||
require.Contains(t, changedDeals, abi.DealID(1))
|
||||
require.Contains(t, changedDeals, abi.DealID(2))
|
||||
deal1 := changedDeals[abi.DealID(1)]
|
||||
if deal1.From.LastUpdatedEpoch != 2 || deal1.To.LastUpdatedEpoch != 3 {
|
||||
t.Fatal("Unexpected change to LastUpdatedEpoch")
|
||||
}
|
||||
deal2 := changedDeals[abi.DealID(2)]
|
||||
if deal2.From.SlashEpoch != 0 || deal2.To.SlashEpoch != 6 {
|
||||
t.Fatal("Unexpected change to SlashEpoch")
|
||||
}
|
||||
|
||||
// Test that OnActorStateChanged does not call the callback if the state has not changed
|
||||
mockAddr, err := address.NewFromString("t01")
|
||||
require.NoError(t, err)
|
||||
actorDiffFn := preds.OnActorStateChanged(mockAddr, func(context.Context, cid.Cid, cid.Cid) (bool, UserData, error) {
|
||||
t.Fatal("No state change so this should not be called")
|
||||
return false, nil, nil
|
||||
})
|
||||
changed, _, err = actorDiffFn(ctx, oldState, oldState)
|
||||
require.NoError(t, err)
|
||||
require.False(t, changed)
|
||||
|
||||
// Test that OnDealStateChanged does not call the callback if the state has not changed
|
||||
diffDealStateFn := preds.OnDealStateChanged(func(context.Context, *amt.Root, *amt.Root) (bool, UserData, error) {
|
||||
t.Fatal("No state change so this should not be called")
|
||||
return false, nil, nil
|
||||
})
|
||||
marketState := createEmptyMarketState(t, store)
|
||||
changed, _, err = diffDealStateFn(ctx, marketState, marketState)
|
||||
require.NoError(t, err)
|
||||
require.False(t, changed)
|
||||
}
|
||||
|
||||
func mockTipset(miner address.Address, timestamp uint64) (*types.TipSet, error) {
|
||||
return types.NewTipSet([]*types.BlockHeader{{
|
||||
Miner: miner,
|
||||
Height: 5,
|
||||
ParentStateRoot: dummyCid,
|
||||
Messages: dummyCid,
|
||||
ParentMessageReceipts: dummyCid,
|
||||
BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS},
|
||||
BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS},
|
||||
Timestamp: timestamp,
|
||||
}})
|
||||
}
|
||||
|
||||
func createMarketState(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, deals map[abi.DealID]*market.DealState) cid.Cid {
|
||||
rootCid := createAMT(ctx, t, store, deals)
|
||||
|
||||
state := createEmptyMarketState(t, store)
|
||||
state.States = rootCid
|
||||
|
||||
stateC, err := store.Put(ctx, state)
|
||||
require.NoError(t, err)
|
||||
return stateC
|
||||
}
|
||||
|
||||
func createEmptyMarketState(t *testing.T, store *cbornode.BasicIpldStore) *market.State {
|
||||
emptyArrayCid, err := amt.NewAMT(store).Flush(context.TODO())
|
||||
require.NoError(t, err)
|
||||
emptyMap, err := store.Put(context.TODO(), hamt.NewNode(store, hamt.UseTreeBitWidth(5)))
|
||||
require.NoError(t, err)
|
||||
return market.ConstructState(emptyArrayCid, emptyMap, emptyMap)
|
||||
}
|
||||
|
||||
func createAMT(ctx context.Context, t *testing.T, store *cbornode.BasicIpldStore, deals map[abi.DealID]*market.DealState) cid.Cid {
|
||||
root := amt.NewAMT(store)
|
||||
for dealID, dealState := range deals {
|
||||
err := root.Set(ctx, uint64(dealID), dealState)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
rootCid, err := root.Flush(ctx)
|
||||
require.NoError(t, err)
|
||||
return rootCid
|
||||
}
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
func (e *calledEvents) CheckMsg(ctx context.Context, smsg types.ChainMsg, hnd CalledHandler) CheckFunc {
|
||||
func (me *messageEvents) CheckMsg(ctx context.Context, smsg types.ChainMsg, hnd MsgHandler) CheckFunc {
|
||||
msg := smsg.VMMessage()
|
||||
|
||||
return func(ts *types.TipSet) (done bool, more bool, err error) {
|
||||
fa, err := e.cs.StateGetActor(ctx, msg.From, ts.Key())
|
||||
fa, err := me.cs.StateGetActor(ctx, msg.From, ts.Key())
|
||||
if err != nil {
|
||||
return false, true, err
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func (e *calledEvents) CheckMsg(ctx context.Context, smsg types.ChainMsg, hnd Ca
|
||||
return false, true, nil
|
||||
}
|
||||
|
||||
rec, err := e.cs.StateGetReceipt(ctx, smsg.VMMessage().Cid(), ts.Key())
|
||||
rec, err := me.cs.StateGetReceipt(ctx, smsg.VMMessage().Cid(), ts.Key())
|
||||
if err != nil {
|
||||
return false, true, xerrors.Errorf("getting receipt in CheckMsg: %w", err)
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func (e *calledEvents) CheckMsg(ctx context.Context, smsg types.ChainMsg, hnd Ca
|
||||
}
|
||||
}
|
||||
|
||||
func (e *calledEvents) MatchMsg(inmsg *types.Message) MatchFunc {
|
||||
func (me *messageEvents) MatchMsg(inmsg *types.Message) MsgMatchFunc {
|
||||
return func(msg *types.Message) (bool, error) {
|
||||
if msg.From == inmsg.From && msg.Nonce == inmsg.Nonce && !inmsg.Equals(msg) {
|
||||
return false, xerrors.Errorf("matching msg %s from %s, nonce %d: got duplicate origin/nonce msg %d", inmsg.Cid(), inmsg.From, inmsg.Nonce, msg.Nonce)
|
||||
|
||||
+13
-11
@@ -45,6 +45,10 @@ var log = logging.Logger("gen")
|
||||
|
||||
const msgsPerBlock = 20
|
||||
|
||||
var ValidWpostForTesting = []abi.PoStProof{{
|
||||
ProofBytes: []byte("valid proof"),
|
||||
}}
|
||||
|
||||
type ChainGen struct {
|
||||
msgsPerBlock int
|
||||
|
||||
@@ -89,8 +93,8 @@ func (m mybs) Get(c cid.Cid) (block.Block, error) {
|
||||
}
|
||||
|
||||
func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) {
|
||||
saminer.SupportedProofTypes = map[abi.RegisteredProof]struct{}{
|
||||
abi.RegisteredProof_StackedDRG2KiBSeal: {},
|
||||
saminer.SupportedProofTypes = map[abi.RegisteredSealProof]struct{}{
|
||||
abi.RegisteredSealProof_StackedDrg2KiBV1: {},
|
||||
}
|
||||
|
||||
mr := repo.NewMemory(nil)
|
||||
@@ -141,7 +145,7 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
genm1, k1, err := seed.PreSeal(maddr1, abi.RegisteredProof_StackedDRG2KiBPoSt, 0, numSectors, m1temp, []byte("some randomness"), nil)
|
||||
genm1, k1, err := seed.PreSeal(maddr1, abi.RegisteredSealProof_StackedDrg2KiBV1, 0, numSectors, m1temp, []byte("some randomness"), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -153,7 +157,7 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
genm2, k2, err := seed.PreSeal(maddr2, abi.RegisteredProof_StackedDRG2KiBPoSt, 0, numSectors, m2temp, []byte("some randomness"), nil)
|
||||
genm2, k2, err := seed.PreSeal(maddr2, abi.RegisteredSealProof_StackedDrg2KiBV1, 0, numSectors, m2temp, []byte("some randomness"), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -192,7 +196,7 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) {
|
||||
*genm2,
|
||||
},
|
||||
NetworkName: "",
|
||||
Timestamp: uint64(time.Now().Add(-500 * build.BlockDelay * time.Second).Unix()),
|
||||
Timestamp: uint64(time.Now().Add(-500 * time.Duration(build.BlockDelaySecs) * time.Second).Unix()),
|
||||
}
|
||||
|
||||
genb, err := genesis2.MakeGenesisBlock(context.TODO(), bs, sys, tpl)
|
||||
@@ -219,7 +223,7 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) {
|
||||
miners := []address.Address{maddr1, maddr2}
|
||||
|
||||
beac := beacon.NewMockBeacon(time.Second)
|
||||
//beac, err := drand.NewDrandBeacon(tpl.Timestamp, build.BlockDelay)
|
||||
//beac, err := drand.NewDrandBeacon(tpl.Timestamp, build.BlockDelaySecs)
|
||||
//if err != nil {
|
||||
//return nil, xerrors.Errorf("creating drand beacon: %w", err)
|
||||
//}
|
||||
@@ -410,7 +414,7 @@ func (cg *ChainGen) makeBlock(parents *types.TipSet, m address.Address, vrfticke
|
||||
if cg.Timestamper != nil {
|
||||
ts = cg.Timestamper(parents, height-parents.Height())
|
||||
} else {
|
||||
ts = parents.MinTimestamp() + uint64((height-parents.Height())*build.BlockDelay)
|
||||
ts = parents.MinTimestamp() + uint64(height-parents.Height())*build.BlockDelaySecs
|
||||
}
|
||||
|
||||
fblk, err := MinerCreateBlock(context.TODO(), cg.sm, cg.w, &api.BlockTemplate{
|
||||
@@ -533,9 +537,7 @@ func (wpp *wppProvider) GenerateCandidates(ctx context.Context, _ abi.PoStRandom
|
||||
}
|
||||
|
||||
func (wpp *wppProvider) ComputeProof(context.Context, []abi.SectorInfo, abi.PoStRandomness) ([]abi.PoStProof, error) {
|
||||
return []abi.PoStProof{{
|
||||
ProofBytes: []byte("valid proof"),
|
||||
}}, nil
|
||||
return ValidWpostForTesting, nil
|
||||
}
|
||||
|
||||
func IsRoundWinner(ctx context.Context, ts *types.TipSet, round abi.ChainEpoch,
|
||||
@@ -611,6 +613,6 @@ func (m genFakeVerifier) VerifyWindowPoSt(ctx context.Context, info abi.WindowPo
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
func (m genFakeVerifier) GenerateWinningPoStSectorChallenge(ctx context.Context, proof abi.RegisteredProof, id abi.ActorID, randomness abi.PoStRandomness, u uint64) ([]uint64, error) {
|
||||
func (m genFakeVerifier) GenerateWinningPoStSectorChallenge(ctx context.Context, proof abi.RegisteredPoStProof, id abi.ActorID, randomness abi.PoStRandomness, u uint64) ([]uint64, error) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
miner.SupportedProofTypes = map[abi.RegisteredProof]struct{}{
|
||||
abi.RegisteredProof_StackedDRG2KiBSeal: {},
|
||||
miner.SupportedProofTypes = map[abi.RegisteredSealProof]struct{}{
|
||||
abi.RegisteredSealProof_StackedDrg2KiBV1: {},
|
||||
}
|
||||
power.ConsensusMinerMinPower = big.NewInt(2048)
|
||||
verifreg.MinVerifiedDealSize = big.NewInt(256)
|
||||
|
||||
@@ -204,12 +204,12 @@ func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid
|
||||
{
|
||||
newSectorInfo := &miner.SectorOnChainInfo{
|
||||
Info: miner.SectorPreCommitInfo{
|
||||
RegisteredProof: preseal.ProofType,
|
||||
SectorNumber: preseal.SectorID,
|
||||
SealedCID: preseal.CommR,
|
||||
SealRandEpoch: 0,
|
||||
DealIDs: []abi.DealID{dealIDs[pi]},
|
||||
Expiration: preseal.Deal.EndEpoch,
|
||||
SealProof: preseal.ProofType,
|
||||
SectorNumber: preseal.SectorID,
|
||||
SealedCID: preseal.CommR,
|
||||
SealRandEpoch: 0,
|
||||
DealIDs: []abi.DealID{dealIDs[pi]},
|
||||
Expiration: preseal.Deal.EndEpoch,
|
||||
},
|
||||
ActivationEpoch: 0,
|
||||
DealWeight: dealWeight.DealWeight,
|
||||
@@ -242,6 +242,9 @@ func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid
|
||||
st.TotalQualityAdjPower = big.Sub(st.TotalQualityAdjPower, big.NewInt(1))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return cid.Undef, xerrors.Errorf("mutating state: %w", err)
|
||||
}
|
||||
|
||||
c, err := vm.Flush(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -150,6 +150,17 @@ func aggregateSignatures(sigs []crypto.Signature) (*crypto.Signature, error) {
|
||||
}
|
||||
|
||||
aggSig := bls.Aggregate(blsSigs)
|
||||
if aggSig == nil {
|
||||
if len(sigs) > 0 {
|
||||
return nil, xerrors.Errorf("bls.Aggregate returned nil with %d signatures", len(sigs))
|
||||
}
|
||||
|
||||
return &crypto.Signature{
|
||||
Type: crypto.SigTypeBLS,
|
||||
Data: new(bls.Signature)[:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &crypto.Signature{
|
||||
Type: crypto.SigTypeBLS,
|
||||
Data: aggSig[:],
|
||||
|
||||
@@ -187,7 +187,7 @@ func New(api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName) (*Messa
|
||||
|
||||
mp := &MessagePool{
|
||||
closer: make(chan struct{}),
|
||||
repubTk: time.NewTicker(build.BlockDelay * 10 * time.Second),
|
||||
repubTk: time.NewTicker(time.Duration(build.BlockDelaySecs) * 10 * time.Second),
|
||||
localAddrs: make(map[address.Address]struct{}),
|
||||
pending: make(map[address.Address]*msgSet),
|
||||
minGasPrice: types.NewInt(0),
|
||||
|
||||
+5
-5
@@ -62,11 +62,11 @@ func (sm *StateManager) CallRaw(ctx context.Context, msg *types.Message, bstate
|
||||
}
|
||||
|
||||
return &api.InvocResult{
|
||||
Msg: msg,
|
||||
MsgRct: &ret.MessageReceipt,
|
||||
InternalExecutions: ret.InternalExecutions,
|
||||
Error: errs,
|
||||
Duration: ret.Duration,
|
||||
Msg: msg,
|
||||
MsgRct: &ret.MessageReceipt,
|
||||
ExecutionTrace: ret.ExecutionTrace,
|
||||
Error: errs,
|
||||
Duration: ret.Duration,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
miner.SupportedProofTypes = map[abi.RegisteredProof]struct{}{
|
||||
abi.RegisteredProof_StackedDRG2KiBSeal: {},
|
||||
miner.SupportedProofTypes = map[abi.RegisteredSealProof]struct{}{
|
||||
abi.RegisteredSealProof_StackedDrg2KiBV1: {},
|
||||
}
|
||||
power.ConsensusMinerMinPower = big.NewInt(2048)
|
||||
verifreg.MinVerifiedDealSize = big.NewInt(256)
|
||||
|
||||
@@ -121,10 +121,10 @@ func (sm *StateManager) ExecutionTrace(ctx context.Context, ts *types.TipSet) (c
|
||||
var trace []*api.InvocResult
|
||||
st, _, err := sm.computeTipSetState(ctx, ts.Blocks(), func(mcid cid.Cid, msg *types.Message, ret *vm.ApplyRet) error {
|
||||
ir := &api.InvocResult{
|
||||
Msg: msg,
|
||||
MsgRct: &ret.MessageReceipt,
|
||||
InternalExecutions: ret.InternalExecutions,
|
||||
Duration: ret.Duration,
|
||||
Msg: msg,
|
||||
MsgRct: &ret.MessageReceipt,
|
||||
ExecutionTrace: ret.ExecutionTrace,
|
||||
Duration: ret.Duration,
|
||||
}
|
||||
if ret.ActorErr != nil {
|
||||
ir.Error = ret.ActorErr.Error()
|
||||
@@ -184,10 +184,9 @@ func (sm *StateManager) ApplyBlocks(ctx context.Context, pstate cid.Cid, bms []B
|
||||
|
||||
var err error
|
||||
params, err := actors.SerializeParams(&reward.AwardBlockRewardParams{
|
||||
Miner: b.Miner,
|
||||
Penalty: penalty,
|
||||
GasReward: gasReward,
|
||||
TicketCount: 1, // TODO: no longer need ticket count here.
|
||||
Miner: b.Miner,
|
||||
Penalty: penalty,
|
||||
GasReward: gasReward,
|
||||
})
|
||||
if err != nil {
|
||||
return cid.Undef, cid.Undef, xerrors.Errorf("failed to serialize award params: %w", err)
|
||||
|
||||
+48
-9
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
amt "github.com/filecoin-project/go-amt-ipld/v2"
|
||||
"github.com/filecoin-project/go-bitfield"
|
||||
"github.com/filecoin-project/sector-storage/ffiwrapper"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
@@ -173,13 +174,34 @@ func GetSectorsForWinningPoSt(ctx context.Context, pv ffiwrapper.Verifier, sm *S
|
||||
return nil, xerrors.Errorf("(get sectors) failed to load miner actor state: %w", err)
|
||||
}
|
||||
|
||||
// TODO: Optimization: we could avoid loaditg the whole proving set here if we had AMT.GetNth with bitfield filtering
|
||||
sectorSet, err := GetProvingSetRaw(ctx, sm, mas)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("getting proving set: %w", err)
|
||||
cst := cbor.NewCborStore(sm.cs.Blockstore())
|
||||
var deadlines miner.Deadlines
|
||||
if err := cst.Get(ctx, mas.Deadlines, &deadlines); err != nil {
|
||||
return nil, xerrors.Errorf("failed to load deadlines: %w", err)
|
||||
}
|
||||
|
||||
if len(sectorSet) == 0 {
|
||||
notProving, err := abi.BitFieldUnion(mas.Faults, mas.Recoveries)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to union faults and recoveries: %w", err)
|
||||
}
|
||||
|
||||
allSectors, err := bitfield.MultiMerge(append(deadlines.Due[:], mas.NewSectors)...)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("merging deadline bitfields failed: %w", err)
|
||||
}
|
||||
|
||||
provingSectors, err := bitfield.SubtractBitField(allSectors, notProving)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to subtract non-proving sectors from set: %w", err)
|
||||
}
|
||||
|
||||
numProvSect, err := provingSectors.Count()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to count bits: %w", err)
|
||||
}
|
||||
|
||||
// TODO(review): is this right? feels fishy to me
|
||||
if numProvSect == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -198,17 +220,34 @@ func GetSectorsForWinningPoSt(ctx context.Context, pv ffiwrapper.Verifier, sm *S
|
||||
return nil, xerrors.Errorf("getting miner ID: %w", err)
|
||||
}
|
||||
|
||||
ids, err := pv.GenerateWinningPoStSectorChallenge(ctx, wpt, abi.ActorID(mid), rand, uint64(len(sectorSet)))
|
||||
ids, err := pv.GenerateWinningPoStSectorChallenge(ctx, wpt, abi.ActorID(mid), rand, numProvSect)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("generating winning post challenges: %w", err)
|
||||
}
|
||||
|
||||
sectors, err := provingSectors.All(miner.SectorsMax)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to enumerate all sector IDs: %w", err)
|
||||
}
|
||||
|
||||
sectorAmt, err := amt.LoadAMT(ctx, cst, mas.Sectors)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to load sectors amt: %w", err)
|
||||
}
|
||||
|
||||
out := make([]abi.SectorInfo, len(ids))
|
||||
for i, n := range ids {
|
||||
sid := sectors[n]
|
||||
|
||||
var sinfo miner.SectorOnChainInfo
|
||||
if err := sectorAmt.Get(ctx, sid, &sinfo); err != nil {
|
||||
return nil, xerrors.Errorf("failed to get sector %d: %w", sid, err)
|
||||
}
|
||||
|
||||
out[i] = abi.SectorInfo{
|
||||
RegisteredProof: wpt,
|
||||
SectorNumber: sectorSet[n].ID,
|
||||
SealedCID: sectorSet[n].Info.Info.SealedCID,
|
||||
SealProof: spt,
|
||||
SectorNumber: sinfo.Info.SectorNumber,
|
||||
SealedCID: sinfo.Info.SealedCID,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,11 @@ func (fts *FullTipSet) Cids() []cid.Cid {
|
||||
return cids
|
||||
}
|
||||
|
||||
// TipSet returns a narrower view of this FullTipSet elliding the block
|
||||
// messages.
|
||||
func (fts *FullTipSet) TipSet() *types.TipSet {
|
||||
if fts.tipset != nil {
|
||||
// FIXME: fts.tipset is actually never set. Should it memoize?
|
||||
return fts.tipset
|
||||
}
|
||||
|
||||
|
||||
+13
-9
@@ -34,7 +34,7 @@ type lbEntry struct {
|
||||
target types.TipSetKey
|
||||
}
|
||||
|
||||
func (ci *ChainIndex) GetTipsetByHeight(ctx context.Context, from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) {
|
||||
func (ci *ChainIndex) GetTipsetByHeight(_ context.Context, from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) {
|
||||
if from.Height()-to <= ci.skipLength {
|
||||
return ci.walkBack(from, to)
|
||||
}
|
||||
@@ -91,15 +91,16 @@ func (ci *ChainIndex) fillCache(tsk types.TipSetKey) (*lbEntry, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if parent.Height() > rheight {
|
||||
return nil, xerrors.Errorf("cache is inconsistent")
|
||||
}
|
||||
|
||||
rheight -= ci.skipLength
|
||||
|
||||
skipTarget, err := ci.walkBack(parent, rheight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var skipTarget *types.TipSet
|
||||
if parent.Height() < rheight {
|
||||
skipTarget = parent
|
||||
} else {
|
||||
skipTarget, err = ci.walkBack(parent, rheight)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("fillCache walkback: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
lbe := &lbEntry{
|
||||
@@ -113,8 +114,9 @@ func (ci *ChainIndex) fillCache(tsk types.TipSetKey) (*lbEntry, error) {
|
||||
return lbe, nil
|
||||
}
|
||||
|
||||
// floors to nearest skipLength multiple
|
||||
func (ci *ChainIndex) roundHeight(h abi.ChainEpoch) abi.ChainEpoch {
|
||||
return abi.ChainEpoch(h/ci.skipLength) * ci.skipLength
|
||||
return (h / ci.skipLength) * ci.skipLength
|
||||
}
|
||||
|
||||
func (ci *ChainIndex) roundDown(ts *types.TipSet) (*types.TipSet, error) {
|
||||
@@ -146,6 +148,8 @@ func (ci *ChainIndex) walkBack(from *types.TipSet, to abi.ChainEpoch) (*types.Ti
|
||||
}
|
||||
|
||||
if to > pts.Height() {
|
||||
// in case pts is lower than the epoch we're looking for (null blocks)
|
||||
// return a tipset above that height
|
||||
return ts, nil
|
||||
}
|
||||
if to == pts.Height() {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/gen"
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
"github.com/filecoin-project/lotus/chain/types/mock"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
datastore "github.com/ipfs/go-datastore"
|
||||
syncds "github.com/ipfs/go-datastore/sync"
|
||||
blockstore "github.com/ipfs/go-ipfs-blockstore"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIndexSeeks(t *testing.T) {
|
||||
cg, err := gen.NewGenerator()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gencar, err := cg.GenesisCar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gen := cg.Genesis()
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
nbs := blockstore.NewBlockstore(syncds.MutexWrap(datastore.NewMapDatastore()))
|
||||
cs := store.NewChainStore(nbs, syncds.MutexWrap(datastore.NewMapDatastore()), nil)
|
||||
|
||||
_, err = cs.Import(bytes.NewReader(gencar))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cur := mock.TipSet(gen)
|
||||
if err := cs.PutTipSet(ctx, mock.TipSet(gen)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cs.SetGenesis(gen)
|
||||
|
||||
// Put 113 blocks from genesis
|
||||
for i := 0; i < 113; i++ {
|
||||
nextts := mock.TipSet(mock.MkBlock(cur, 1, 1))
|
||||
|
||||
if err := cs.PutTipSet(ctx, nextts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cur = nextts
|
||||
}
|
||||
|
||||
// Put 50 null epochs + 1 block
|
||||
skip := mock.MkBlock(cur, 1, 1)
|
||||
skip.Height += 50
|
||||
|
||||
skipts := mock.TipSet(skip)
|
||||
|
||||
if err := cs.PutTipSet(ctx, skipts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ts, err := cs.GetTipsetByHeight(ctx, skip.Height-10, skipts, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, abi.ChainEpoch(164), ts.Height())
|
||||
|
||||
for i := 0; i <= 113; i++ {
|
||||
ts3, err := cs.GetTipsetByHeight(ctx, abi.ChainEpoch(i), skipts, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, abi.ChainEpoch(i), ts3.Height())
|
||||
}
|
||||
}
|
||||
+53
-14
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/state"
|
||||
"github.com/filecoin-project/lotus/chain/vm"
|
||||
"github.com/filecoin-project/lotus/metrics"
|
||||
@@ -48,7 +47,20 @@ import (
|
||||
var log = logging.Logger("chainstore")
|
||||
|
||||
var chainHeadKey = dstore.NewKey("head")
|
||||
var blockValidationCacheKeyPrefix = dstore.NewKey("blockValidation")
|
||||
|
||||
// ReorgNotifee represents a callback that gets called upon reorgs.
|
||||
type ReorgNotifee func(rev, app []*types.TipSet) error
|
||||
|
||||
// ChainStore is the main point of access to chain data.
|
||||
//
|
||||
// Raw chain data is stored in the Blockstore, with relevant markers (genesis,
|
||||
// latest head tipset references) being tracked in the Datastore (key-value
|
||||
// store).
|
||||
//
|
||||
// To alleviate disk access, the ChainStore has two ARC caches:
|
||||
// 1. a tipset cache
|
||||
// 2. a block => messages references cache.
|
||||
type ChainStore struct {
|
||||
bs bstore.Blockstore
|
||||
ds dstore.Datastore
|
||||
@@ -64,8 +76,8 @@ type ChainStore struct {
|
||||
|
||||
cindex *ChainIndex
|
||||
|
||||
reorgCh chan<- reorg
|
||||
headChangeNotifs []func(rev, app []*types.TipSet) error
|
||||
reorgCh chan<- reorg
|
||||
reorgNotifeeCh chan ReorgNotifee
|
||||
|
||||
mmCache *lru.ARCCache
|
||||
tsCache *lru.ARCCache
|
||||
@@ -90,8 +102,6 @@ func NewChainStore(bs bstore.Blockstore, ds dstore.Batching, vmcalls runtime.Sys
|
||||
|
||||
cs.cindex = ci
|
||||
|
||||
cs.reorgCh = cs.reorgWorker(context.TODO())
|
||||
|
||||
hcnf := func(rev, app []*types.TipSet) error {
|
||||
cs.pubLk.Lock()
|
||||
defer cs.pubLk.Unlock()
|
||||
@@ -123,7 +133,8 @@ func NewChainStore(bs bstore.Blockstore, ds dstore.Batching, vmcalls runtime.Sys
|
||||
return nil
|
||||
}
|
||||
|
||||
cs.headChangeNotifs = append(cs.headChangeNotifs, hcnf, hcmetric)
|
||||
cs.reorgNotifeeCh = make(chan ReorgNotifee)
|
||||
cs.reorgCh = cs.reorgWorker(context.TODO(), []ReorgNotifee{hcnf, hcmetric})
|
||||
|
||||
return cs
|
||||
}
|
||||
@@ -212,8 +223,24 @@ func (cs *ChainStore) SubHeadChanges(ctx context.Context) chan []*api.HeadChange
|
||||
return out
|
||||
}
|
||||
|
||||
func (cs *ChainStore) SubscribeHeadChanges(f func(rev, app []*types.TipSet) error) {
|
||||
cs.headChangeNotifs = append(cs.headChangeNotifs, f)
|
||||
func (cs *ChainStore) SubscribeHeadChanges(f ReorgNotifee) {
|
||||
cs.reorgNotifeeCh <- f
|
||||
}
|
||||
|
||||
func (cs *ChainStore) IsBlockValidated(ctx context.Context, blkid cid.Cid) (bool, error) {
|
||||
key := blockValidationCacheKeyPrefix.Instance(blkid.String())
|
||||
|
||||
return cs.ds.Has(key)
|
||||
}
|
||||
|
||||
func (cs *ChainStore) MarkBlockAsValidated(ctx context.Context, blkid cid.Cid) error {
|
||||
key := blockValidationCacheKeyPrefix.Instance(blkid.String())
|
||||
|
||||
if err := cs.ds.Put(key, []byte{0}); err != nil {
|
||||
return xerrors.Errorf("cache block validation: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *ChainStore) SetGenesis(b *types.BlockHeader) error {
|
||||
@@ -248,6 +275,9 @@ func (cs *ChainStore) PutTipSet(ctx context.Context, ts *types.TipSet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaybeTakeHeavierTipSet evaluates the incoming tipset and locks it in our
|
||||
// internal state as our new head, if and only if it is heavier than the current
|
||||
// head.
|
||||
func (cs *ChainStore) MaybeTakeHeavierTipSet(ctx context.Context, ts *types.TipSet) error {
|
||||
cs.heaviestLk.Lock()
|
||||
defer cs.heaviestLk.Unlock()
|
||||
@@ -274,13 +304,19 @@ type reorg struct {
|
||||
new *types.TipSet
|
||||
}
|
||||
|
||||
func (cs *ChainStore) reorgWorker(ctx context.Context) chan<- reorg {
|
||||
func (cs *ChainStore) reorgWorker(ctx context.Context, initialNotifees []ReorgNotifee) chan<- reorg {
|
||||
out := make(chan reorg, 32)
|
||||
notifees := make([]ReorgNotifee, len(initialNotifees))
|
||||
copy(notifees, initialNotifees)
|
||||
|
||||
go func() {
|
||||
defer log.Warn("reorgWorker quit")
|
||||
|
||||
for {
|
||||
select {
|
||||
case n := <-cs.reorgNotifeeCh:
|
||||
notifees = append(notifees, n)
|
||||
|
||||
case r := <-out:
|
||||
revert, apply, err := cs.ReorgOps(r.old, r.new)
|
||||
if err != nil {
|
||||
@@ -294,7 +330,7 @@ func (cs *ChainStore) reorgWorker(ctx context.Context) chan<- reorg {
|
||||
apply[i], apply[opp] = apply[opp], apply[i]
|
||||
}
|
||||
|
||||
for _, hcf := range cs.headChangeNotifs {
|
||||
for _, hcf := range notifees {
|
||||
if err := hcf(revert, apply); err != nil {
|
||||
log.Error("head change func errored (BAD): ", err)
|
||||
}
|
||||
@@ -307,6 +343,9 @@ func (cs *ChainStore) reorgWorker(ctx context.Context) chan<- reorg {
|
||||
return out
|
||||
}
|
||||
|
||||
// takeHeaviestTipSet actually sets the incoming tipset as our head both in
|
||||
// memory and in the ChainStore. It also sends a notification to deliver to
|
||||
// ReorgNotifees.
|
||||
func (cs *ChainStore) takeHeaviestTipSet(ctx context.Context, ts *types.TipSet) error {
|
||||
_, span := trace.StartSpan(ctx, "takeHeaviestTipSet")
|
||||
defer span.End()
|
||||
@@ -344,6 +383,7 @@ func (cs *ChainStore) SetHead(ts *types.TipSet) error {
|
||||
return cs.takeHeaviestTipSet(context.TODO(), ts)
|
||||
}
|
||||
|
||||
// Contains returns whether our BlockStore has all blocks in the supplied TipSet.
|
||||
func (cs *ChainStore) Contains(ts *types.TipSet) (bool, error) {
|
||||
for _, c := range ts.Cids() {
|
||||
has, err := cs.bs.Has(c)
|
||||
@@ -358,6 +398,8 @@ func (cs *ChainStore) Contains(ts *types.TipSet) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetBlock fetches a BlockHeader with the supplied CID. It returns
|
||||
// blockstore.ErrNotFound if the block was not found in the BlockStore.
|
||||
func (cs *ChainStore) GetBlock(c cid.Cid) (*types.BlockHeader, error) {
|
||||
sb, err := cs.bs.Get(c)
|
||||
if err != nil {
|
||||
@@ -450,6 +492,7 @@ func (cs *ChainStore) ReorgOps(a, b *types.TipSet) ([]*types.TipSet, []*types.Ti
|
||||
return leftChain, rightChain, nil
|
||||
}
|
||||
|
||||
// GetHeaviestTipSet returns the current heaviest tipset known (i.e. our head).
|
||||
func (cs *ChainStore) GetHeaviestTipSet() *types.TipSet {
|
||||
cs.heaviestLk.Lock()
|
||||
defer cs.heaviestLk.Unlock()
|
||||
@@ -964,10 +1007,6 @@ func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, t
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
if ts.Height()-h > build.ForkLengthThreshold {
|
||||
log.Warnf("expensive call to GetTipsetByHeight, seeking %d levels", ts.Height()-h)
|
||||
}
|
||||
|
||||
lbts, err := cs.cindex.GetTipsetByHeight(ctx, ts, h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -22,8 +22,8 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
miner.SupportedProofTypes = map[abi.RegisteredProof]struct{}{
|
||||
abi.RegisteredProof_StackedDRG2KiBSeal: {},
|
||||
miner.SupportedProofTypes = map[abi.RegisteredSealProof]struct{}{
|
||||
abi.RegisteredSealProof_StackedDrg2KiBV1: {},
|
||||
}
|
||||
power.ConsensusMinerMinPower = big.NewInt(2048)
|
||||
verifreg.MinVerifiedDealSize = big.NewInt(256)
|
||||
|
||||
@@ -60,8 +60,6 @@ func HandleIncomingBlocks(ctx context.Context, bsub *pubsub.Subscription, s *cha
|
||||
src := msg.GetFrom()
|
||||
|
||||
go func() {
|
||||
log.Infof("New block over pubsub: %s", blk.Cid())
|
||||
|
||||
start := time.Now()
|
||||
log.Debug("about to fetch messages for block from pubsub")
|
||||
bmsgs, err := s.Bsync.FetchMessagesByCids(context.TODO(), blk.BlsMessages)
|
||||
@@ -145,8 +143,7 @@ func (bv *BlockValidator) Validate(ctx context.Context, pid peer.ID, msg *pubsub
|
||||
// track validation time
|
||||
begin := time.Now()
|
||||
defer func() {
|
||||
end := time.Now()
|
||||
log.Infof("block validation time: %s", end.Sub(begin))
|
||||
log.Debugf("block validation time: %s", time.Since(begin))
|
||||
}()
|
||||
|
||||
stats.Record(ctx, metrics.BlockReceived.M(1))
|
||||
|
||||
+207
-40
@@ -8,7 +8,6 @@ import (
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Gurpartap/async"
|
||||
@@ -50,10 +49,37 @@ import (
|
||||
"github.com/filecoin-project/lotus/metrics"
|
||||
)
|
||||
|
||||
// Blocks that are more than MaxHeightDrift epochs above
|
||||
//the theoretical max height based on systime are quickly rejected
|
||||
const MaxHeightDrift = 5
|
||||
|
||||
var log = logging.Logger("chain")
|
||||
|
||||
var LocalIncoming = "incoming"
|
||||
|
||||
// Syncer is in charge of running the chain synchronization logic. As such, it
|
||||
// is tasked with these functions, amongst others:
|
||||
//
|
||||
// * Fast-forwards the chain as it learns of new TipSets from the network via
|
||||
// the SyncManager.
|
||||
// * Applies the fork choice rule to select the correct side when confronted
|
||||
// with a fork in the network.
|
||||
// * Requests block headers and messages from other peers when not available
|
||||
// in our BlockStore.
|
||||
// * Tracks blocks marked as bad in a cache.
|
||||
// * Keeps the BlockStore and ChainStore consistent with our view of the world,
|
||||
// the latter of which in turn informs other components when a reorg has been
|
||||
// committed.
|
||||
//
|
||||
// The Syncer does not run workers itself. It's mainly concerned with
|
||||
// ensuring a consistent state of chain consensus. The reactive and network-
|
||||
// interfacing processes are part of other components, such as the SyncManager
|
||||
// (which owns the sync scheduler and sync workers), BlockSync, the HELLO
|
||||
// protocol, and the gossipsub block propagation layer.
|
||||
//
|
||||
// {hint/concept} The fork-choice rule as it currently stands is: "pick the
|
||||
// chain with the heaviest weight, so long as it hasn’t deviated one finality
|
||||
// threshold from our head (900 epochs, parameter determined by spec-actors)".
|
||||
type Syncer struct {
|
||||
// The interface for accessing and putting tipsets into local storage
|
||||
store *store.ChainStore
|
||||
@@ -86,6 +112,7 @@ type Syncer struct {
|
||||
verifier ffiwrapper.Verifier
|
||||
}
|
||||
|
||||
// NewSyncer creates a new Syncer object.
|
||||
func NewSyncer(sm *stmgr.StateManager, bsync *blocksync.BlockSync, connmgr connmgr.ConnManager, self peer.ID, beacon beacon.RandomBeacon, verifier ffiwrapper.Verifier) (*Syncer, error) {
|
||||
gen, err := sm.ChainStore().GetGenesis()
|
||||
if err != nil {
|
||||
@@ -134,6 +161,11 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if syncer.IsEpochBeyondCurrMax(fts.TipSet().Height()) {
|
||||
log.Errorf("Received block with impossibly large height %d", fts.TipSet().Height())
|
||||
return false
|
||||
}
|
||||
|
||||
for _, b := range fts.Blocks {
|
||||
if reason, ok := syncer.bad.Has(b.Cid()); ok {
|
||||
log.Warnf("InformNewHead called on block marked as bad: %s (reason: %s)", b.Cid(), reason)
|
||||
@@ -183,6 +215,11 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IncomingBlocks spawns a goroutine that subscribes to the local eventbus to
|
||||
// receive new block headers as they arrive from the network, and sends them to
|
||||
// the returned channel.
|
||||
//
|
||||
// These blocks have not necessarily been incorporated to our view of the chain.
|
||||
func (syncer *Syncer) IncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error) {
|
||||
sub := syncer.incoming.Sub(LocalIncoming)
|
||||
out := make(chan *types.BlockHeader, 10)
|
||||
@@ -210,11 +247,15 @@ func (syncer *Syncer) IncomingBlocks(ctx context.Context) (<-chan *types.BlockHe
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ValidateMsgMeta performs structural and content hash validation of the
|
||||
// messages within this block. If validation passes, it stores the messages in
|
||||
// the underlying IPLD block store.
|
||||
func (syncer *Syncer) ValidateMsgMeta(fblk *types.FullBlock) error {
|
||||
if msgc := len(fblk.BlsMessages) + len(fblk.SecpkMessages); msgc > build.BlockMessageLimit {
|
||||
return xerrors.Errorf("block %s has too many messages (%d)", fblk.Header.Cid(), msgc)
|
||||
}
|
||||
|
||||
// Collect the CIDs of both types of messages separately: BLS and Secpk.
|
||||
var bcids, scids []cbg.CBORMarshaler
|
||||
for _, m := range fblk.BlsMessages {
|
||||
c := cbg.CborCid(m.Cid())
|
||||
@@ -232,11 +273,14 @@ func (syncer *Syncer) ValidateMsgMeta(fblk *types.FullBlock) error {
|
||||
blockstore := syncer.store.Blockstore()
|
||||
|
||||
bs := cbor.NewCborStore(blockstore)
|
||||
|
||||
// Compute the root CID of the combined message trie.
|
||||
smroot, err := computeMsgMeta(bs, bcids, scids)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("validating msgmeta, compute failed: %w", err)
|
||||
}
|
||||
|
||||
// Check that the message trie root matches with what's in the block.
|
||||
if fblk.Header.Messages != smroot {
|
||||
return xerrors.Errorf("messages in full block did not match msgmeta root in header (%s != %s)", fblk.Header.Messages, smroot)
|
||||
}
|
||||
@@ -346,6 +390,8 @@ func zipTipSetAndMessages(bs cbor.IpldStore, ts *types.TipSet, allbmsgs []*types
|
||||
return fts, nil
|
||||
}
|
||||
|
||||
// computeMsgMeta computes the root CID of the combined arrays of message CIDs
|
||||
// of both types (BLS and Secpk).
|
||||
func computeMsgMeta(bs cbor.IpldStore, bmsgCids, smsgCids []cbg.CBORMarshaler) (cid.Cid, error) {
|
||||
ctx := context.TODO()
|
||||
bmroot, err := amt.FromArray(ctx, bs, bmsgCids)
|
||||
@@ -369,14 +415,24 @@ func computeMsgMeta(bs cbor.IpldStore, bmsgCids, smsgCids []cbg.CBORMarshaler) (
|
||||
return mrcid, nil
|
||||
}
|
||||
|
||||
// FetchTipSet tries to load the provided tipset from the store, and falls back
|
||||
// to the network (BlockSync) by querying the supplied peer if not found
|
||||
// locally.
|
||||
//
|
||||
// {hint/usage} This is used from the HELLO protocol, to fetch the greeting
|
||||
// peer's heaviest tipset if we don't have it.
|
||||
func (syncer *Syncer) FetchTipSet(ctx context.Context, p peer.ID, tsk types.TipSetKey) (*store.FullTipSet, error) {
|
||||
if fts, err := syncer.tryLoadFullTipSet(tsk); err == nil {
|
||||
return fts, nil
|
||||
}
|
||||
|
||||
// fall back to the network.
|
||||
return syncer.Bsync.GetFullTipSet(ctx, p, tsk)
|
||||
}
|
||||
|
||||
// tryLoadFullTipSet queries the tipset in the ChainStore, and returns a full
|
||||
// representation of it containing FullBlocks. If ALL blocks are not found
|
||||
// locally, it errors entirely with blockstore.ErrNotFound.
|
||||
func (syncer *Syncer) tryLoadFullTipSet(tsk types.TipSetKey) (*store.FullTipSet, error) {
|
||||
ts, err := syncer.store.LoadTipSet(tsk)
|
||||
if err != nil {
|
||||
@@ -401,6 +457,12 @@ func (syncer *Syncer) tryLoadFullTipSet(tsk types.TipSetKey) (*store.FullTipSet,
|
||||
return fts, nil
|
||||
}
|
||||
|
||||
// Sync tries to advance our view of the chain to `maybeHead`. It does nothing
|
||||
// if our current head is heavier than the requested tipset, or if we're already
|
||||
// at the requested head, or if the head is the genesis.
|
||||
//
|
||||
// Most of the heavy-lifting logic happens in syncer#collectChain. Refer to the
|
||||
// godocs on that method for a more detailed view.
|
||||
func (syncer *Syncer) Sync(ctx context.Context, maybeHead *types.TipSet) error {
|
||||
ctx, span := trace.StartSpan(ctx, "chain.Sync")
|
||||
defer span.End()
|
||||
@@ -429,6 +491,8 @@ func (syncer *Syncer) Sync(ctx context.Context, maybeHead *types.TipSet) error {
|
||||
return xerrors.Errorf("collectChain failed: %w", err)
|
||||
}
|
||||
|
||||
// At this point we have accepted and synced to the new `maybeHead`
|
||||
// (`StageSyncComplete`).
|
||||
if err := syncer.store.PutTipSet(ctx, maybeHead); err != nil {
|
||||
span.AddAttributes(trace.StringAttribute("put_error", err.Error()))
|
||||
span.SetStatus(trace.Status{
|
||||
@@ -465,16 +529,27 @@ func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet)
|
||||
return nil
|
||||
}
|
||||
|
||||
var futures []async.ErrorFuture
|
||||
for _, b := range fts.Blocks {
|
||||
if err := syncer.ValidateBlock(ctx, b); err != nil {
|
||||
if isPermanent(err) {
|
||||
syncer.bad.Add(b.Cid(), err.Error())
|
||||
}
|
||||
return xerrors.Errorf("validating block %s: %w", b.Cid(), err)
|
||||
}
|
||||
b := b // rebind to a scoped variable
|
||||
|
||||
if err := syncer.sm.ChainStore().AddToTipSetTracker(b.Header); err != nil {
|
||||
return xerrors.Errorf("failed to add validated header to tipset tracker: %w", err)
|
||||
futures = append(futures, async.Err(func() error {
|
||||
if err := syncer.ValidateBlock(ctx, b); err != nil {
|
||||
if isPermanent(err) {
|
||||
syncer.bad.Add(b.Cid(), err.Error())
|
||||
}
|
||||
return xerrors.Errorf("validating block %s: %w", b.Cid(), err)
|
||||
}
|
||||
|
||||
if err := syncer.sm.ChainStore().AddToTipSetTracker(b.Header); err != nil {
|
||||
return xerrors.Errorf("failed to add validated header to tipset tracker: %w", err)
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
for _, f := range futures {
|
||||
if err := f.AwaitContext(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -527,9 +602,35 @@ func blockSanityChecks(h *types.BlockHeader) error {
|
||||
}
|
||||
|
||||
// ValidateBlock should match up with 'Semantical Validation' in validation.md in the spec
|
||||
func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) error {
|
||||
func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) (err error) {
|
||||
defer func() {
|
||||
// b.Cid() could panic for empty blocks that are used in tests.
|
||||
if rerr := recover(); rerr != nil {
|
||||
err = xerrors.Errorf("validate block panic: %w", rerr)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
isValidated, err := syncer.store.IsBlockValidated(ctx, b.Cid())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("check block validation cache %s: %w", b.Cid(), err)
|
||||
}
|
||||
|
||||
if isValidated {
|
||||
return nil
|
||||
}
|
||||
|
||||
validationStart := time.Now()
|
||||
defer func() {
|
||||
dur := time.Since(validationStart)
|
||||
durMilli := dur.Seconds() * float64(1000)
|
||||
stats.Record(ctx, metrics.BlockValidationDurationMilliseconds.M(durMilli))
|
||||
log.Infow("block validation", "took", dur, "height", b.Header.Height)
|
||||
}()
|
||||
|
||||
ctx, span := trace.StartSpan(ctx, "validateBlock")
|
||||
defer span.End()
|
||||
|
||||
if build.InsecurePoStValidation {
|
||||
log.Warn("insecure test validation is enabled, if you see this outside of a test, it is a severe bug!")
|
||||
}
|
||||
@@ -565,18 +666,18 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err
|
||||
// fast checks first
|
||||
|
||||
now := uint64(time.Now().Unix())
|
||||
if h.Timestamp > now+build.AllowableClockDrift {
|
||||
if h.Timestamp > now+build.AllowableClockDriftSecs {
|
||||
return xerrors.Errorf("block was from the future (now=%d, blk=%d): %w", now, h.Timestamp, ErrTemporal)
|
||||
}
|
||||
if h.Timestamp > now {
|
||||
log.Warn("Got block from the future, but within threshold", h.Timestamp, time.Now().Unix())
|
||||
}
|
||||
|
||||
if h.Timestamp < baseTs.MinTimestamp()+(build.BlockDelay*uint64(h.Height-baseTs.Height())) {
|
||||
if h.Timestamp < baseTs.MinTimestamp()+(build.BlockDelaySecs*uint64(h.Height-baseTs.Height())) {
|
||||
log.Warn("timestamp funtimes: ", h.Timestamp, baseTs.MinTimestamp(), h.Height, baseTs.Height())
|
||||
diff := (baseTs.MinTimestamp() + (build.BlockDelay * uint64(h.Height-baseTs.Height()))) - h.Timestamp
|
||||
diff := (baseTs.MinTimestamp() + (build.BlockDelaySecs * uint64(h.Height-baseTs.Height()))) - h.Timestamp
|
||||
|
||||
return xerrors.Errorf("block was generated too soon (h.ts:%d < base.mints:%d + BLOCK_DELAY:%d * deltaH:%d; diff %d)", h.Timestamp, baseTs.MinTimestamp(), build.BlockDelay, h.Height-baseTs.Height(), diff)
|
||||
return xerrors.Errorf("block was generated too soon (h.ts:%d < base.mints:%d + BLOCK_DELAY:%d * deltaH:%d; diff %d)", h.Timestamp, baseTs.MinTimestamp(), build.BlockDelaySecs, h.Height-baseTs.Height(), diff)
|
||||
}
|
||||
|
||||
msgsCheck := async.Err(func() error {
|
||||
@@ -639,7 +740,7 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err
|
||||
return xerrors.Errorf("could not draw randomness: %w", err)
|
||||
}
|
||||
|
||||
if err := gen.VerifyVRF(ctx, waddr, vrfBase, h.ElectionProof.VRFProof); err != nil {
|
||||
if err := VerifyElectionPoStVRF(ctx, waddr, vrfBase, h.ElectionProof.VRFProof); err != nil {
|
||||
return xerrors.Errorf("validating block election proof failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -700,7 +801,7 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err
|
||||
return xerrors.Errorf("failed to compute vrf base for ticket: %w", err)
|
||||
}
|
||||
|
||||
err = gen.VerifyVRF(ctx, waddr, vrfBase, h.Ticket.VRFProof)
|
||||
err = VerifyElectionPoStVRF(ctx, waddr, vrfBase, h.Ticket.VRFProof)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("validating block tickets failed: %w", err)
|
||||
}
|
||||
@@ -748,7 +849,11 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err
|
||||
}
|
||||
}
|
||||
|
||||
return merr
|
||||
if err := syncer.store.MarkBlockAsValidated(ctx, b.Cid()); err != nil {
|
||||
return xerrors.Errorf("caching block validation %s: %w", b.Cid(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (syncer *Syncer) VerifyWinningPoStProof(ctx context.Context, h *types.BlockHeader, prevBeacon types.BeaconEntry, lbst cid.Cid, waddr address.Address) error {
|
||||
@@ -846,7 +951,7 @@ func (syncer *Syncer) checkBlockMessages(ctx context.Context, b *types.FullBlock
|
||||
|
||||
// Phase 1: syntactic validation, as defined in the spec
|
||||
minGas := vm.PricelistByEpoch(baseTs.Height()).OnChainMessage(msg.ChainLength())
|
||||
if err := m.ValidForBlockInclusion(minGas); err != nil {
|
||||
if err := m.ValidForBlockInclusion(minGas.Total()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -938,23 +1043,14 @@ func (syncer *Syncer) verifyBlsAggregate(ctx context.Context, sig *crypto.Signat
|
||||
trace.Int64Attribute("msgCount", int64(len(msgs))),
|
||||
)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
digests := make([]bls.Digest, len(msgs))
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(w int) {
|
||||
defer wg.Done()
|
||||
for j := 0; (j*10)+w < len(msgs); j++ {
|
||||
digests[j*10+w] = bls.Hash(bls.Message(msgs[j*10+w].Bytes()))
|
||||
}
|
||||
}(i)
|
||||
bmsgs := make([]bls.Message, len(msgs))
|
||||
for i, m := range msgs {
|
||||
bmsgs[i] = m.Bytes()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
var bsig bls.Signature
|
||||
copy(bsig[:], sig.Data)
|
||||
if !bls.Verify(&bsig, digests, pubks) {
|
||||
if !bls.HashVerify(&bsig, bmsgs, pubks) {
|
||||
return xerrors.New("bls aggregate signature failed to verify")
|
||||
}
|
||||
|
||||
@@ -971,6 +1067,39 @@ func extractSyncState(ctx context.Context) *SyncerState {
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectHeaders collects the headers from the blocks between any two tipsets.
|
||||
//
|
||||
// `from` is the heaviest/projected/target tipset we have learned about, and
|
||||
// `to` is usually an anchor tipset we already have in our view of the chain
|
||||
// (which could be the genesis).
|
||||
//
|
||||
// collectHeaders checks if portions of the chain are in our ChainStore; falling
|
||||
// down to the network to retrieve the missing parts. If during the process, any
|
||||
// portion we receive is in our denylist (bad list), we short-circuit.
|
||||
//
|
||||
// {hint/naming}: `from` and `to` is in inverse order. `from` is the highest,
|
||||
// and `to` is the lowest. This method traverses the chain backwards.
|
||||
//
|
||||
// {hint/usage}: This is used by collectChain, which is in turn called from the
|
||||
// main Sync method (Syncer#Sync), so it's a pretty central method.
|
||||
//
|
||||
// {hint/logic}: The logic of this method is as follows:
|
||||
//
|
||||
// 1. Check that the from tipset is not linked to a parent block known to be
|
||||
// bad.
|
||||
// 2. Check the consistency of beacon entries in the from tipset. We check
|
||||
// total equality of the BeaconEntries in each block.
|
||||
// 3. Travers the chain backwards, for each tipset:
|
||||
// 3a. Load it from the chainstore; if found, it move on to its parent.
|
||||
// 3b. Query our peers via BlockSync in batches, requesting up to a
|
||||
// maximum of 500 tipsets every time.
|
||||
//
|
||||
// Once we've concluded, if we find a mismatching tipset at the height where the
|
||||
// anchor tipset should be, we are facing a fork, and we invoke Syncer#syncFork
|
||||
// to resolve it. Refer to the godocs there.
|
||||
//
|
||||
// All throughout the process, we keep checking if the received blocks are in
|
||||
// the deny list, and short-circuit the process if so.
|
||||
func (syncer *Syncer) collectHeaders(ctx context.Context, from *types.TipSet, to *types.TipSet) ([]*types.TipSet, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "collectHeaders")
|
||||
defer span.End()
|
||||
@@ -987,6 +1116,8 @@ func (syncer *Syncer) collectHeaders(ctx context.Context, from *types.TipSet, to
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the parents of the from block are in the denylist.
|
||||
// i.e. if a fork of the chain has been requested that we know to be bad.
|
||||
for _, pcid := range from.Parents().Cids() {
|
||||
if reason, ok := syncer.bad.Has(pcid); ok {
|
||||
markBad("linked to %s", pcid)
|
||||
@@ -1057,8 +1188,8 @@ loop:
|
||||
}
|
||||
|
||||
// NB: GetBlocks validates that the blocks are in-fact the ones we
|
||||
// requested, and that they are correctly linked to eachother. It does
|
||||
// not validate any state transitions
|
||||
// requested, and that they are correctly linked to one another. It does
|
||||
// not validate any state transitions.
|
||||
window := 500
|
||||
if gap := int(blockSet[len(blockSet)-1].Height() - untilHeight); gap < window {
|
||||
window = gap
|
||||
@@ -1099,7 +1230,6 @@ loop:
|
||||
at = blks[len(blks)-1].Parents()
|
||||
}
|
||||
|
||||
// We have now ascertained that this is *not* a 'fast forward'
|
||||
if !types.CidArrsEqual(blockSet[len(blockSet)-1].Parents().Cids(), to.Cids()) {
|
||||
last := blockSet[len(blockSet)-1]
|
||||
if last.Parents() == to.Parents() {
|
||||
@@ -1107,6 +1237,8 @@ loop:
|
||||
return blockSet, nil
|
||||
}
|
||||
|
||||
// We have now ascertained that this is *not* a 'fast forward'
|
||||
|
||||
log.Warnf("(fork detected) synced header chain (%s - %d) does not link to our best block (%s - %d)", from.Cids(), from.Height(), to.Cids(), to.Height())
|
||||
fork, err := syncer.syncFork(ctx, last, to)
|
||||
if err != nil {
|
||||
@@ -1128,6 +1260,12 @@ loop:
|
||||
|
||||
var ErrForkTooLong = fmt.Errorf("fork longer than threshold")
|
||||
|
||||
// syncFork tries to obtain the chain fragment that links a fork into a common
|
||||
// ancestor in our view of the chain.
|
||||
//
|
||||
// If the fork is too long (build.ForkLengthThreshold), we add the entire subchain to the
|
||||
// denylist. Else, we find the common ancestor, and add the missing chain
|
||||
// fragment until the fork point to the returned []TipSet.
|
||||
func (syncer *Syncer) syncFork(ctx context.Context, from *types.TipSet, to *types.TipSet) ([]*types.TipSet, error) {
|
||||
tips, err := syncer.Bsync.GetBlocks(ctx, from.Parents(), int(build.ForkLengthThreshold))
|
||||
if err != nil {
|
||||
@@ -1279,6 +1417,25 @@ func persistMessages(bs bstore.Blockstore, bst *blocksync.BSTipSet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectChain tries to advance our view of the chain to the purported head.
|
||||
//
|
||||
// It goes through various stages:
|
||||
//
|
||||
// 1. StageHeaders: we proceed in the sync process by requesting block headers
|
||||
// from our peers, moving back from their heads, until we reach a tipset
|
||||
// that we have in common (such a common tipset must exist, thought it may
|
||||
// simply be the genesis block).
|
||||
//
|
||||
// If the common tipset is our head, we treat the sync as a "fast-forward",
|
||||
// else we must drop part of our chain to connect to the peer's head
|
||||
// (referred to as "forking").
|
||||
//
|
||||
// 2. StagePersistHeaders: now that we've collected the missing headers,
|
||||
// augmented by those on the other side of a fork, we persist them to the
|
||||
// BlockStore.
|
||||
//
|
||||
// 3. StageMessages: having acquired the headers and found a common tipset,
|
||||
// we then move forward, requesting the full blocks, including the messages.
|
||||
func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet) error {
|
||||
ctx, span := trace.StartSpan(ctx, "collectChain")
|
||||
defer span.End()
|
||||
@@ -1325,12 +1482,11 @@ func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func VerifyElectionPoStVRF(ctx context.Context, evrf []byte, rand []byte, worker address.Address) error {
|
||||
if err := gen.VerifyVRF(ctx, worker, rand, evrf); err != nil {
|
||||
return xerrors.Errorf("failed to verify post_randomness vrf: %w", err)
|
||||
func VerifyElectionPoStVRF(ctx context.Context, worker address.Address, rand []byte, evrf []byte) error {
|
||||
if build.InsecurePoStValidation {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
return gen.VerifyVRF(ctx, worker, rand, evrf)
|
||||
}
|
||||
|
||||
func (syncer *Syncer) State() []SyncerState {
|
||||
@@ -1341,6 +1497,7 @@ func (syncer *Syncer) State() []SyncerState {
|
||||
return out
|
||||
}
|
||||
|
||||
// MarkBad manually adds a block to the "bad blocks" cache.
|
||||
func (syncer *Syncer) MarkBad(blk cid.Cid) {
|
||||
syncer.bad.Add(blk, "manually marked bad")
|
||||
}
|
||||
@@ -1348,7 +1505,7 @@ func (syncer *Syncer) MarkBad(blk cid.Cid) {
|
||||
func (syncer *Syncer) CheckBadBlockCache(blk cid.Cid) (string, bool) {
|
||||
return syncer.bad.Has(blk)
|
||||
}
|
||||
func (syncer *Syncer) getLatestBeaconEntry(ctx context.Context, ts *types.TipSet) (*types.BeaconEntry, error) {
|
||||
func (syncer *Syncer) getLatestBeaconEntry(_ context.Context, ts *types.TipSet) (*types.BeaconEntry, error) {
|
||||
cur := ts
|
||||
for i := 0; i < 20; i++ {
|
||||
cbe := cur.Blocks()[0].BeaconEntries
|
||||
@@ -1369,3 +1526,13 @@ func (syncer *Syncer) getLatestBeaconEntry(ctx context.Context, ts *types.TipSet
|
||||
|
||||
return nil, xerrors.Errorf("found NO beacon entries in the 20 blocks prior to given tipset")
|
||||
}
|
||||
|
||||
func (syncer *Syncer) IsEpochBeyondCurrMax(epoch abi.ChainEpoch) bool {
|
||||
g, err := syncer.store.GetGenesis()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
now := uint64(time.Now().Unix())
|
||||
return epoch > (abi.ChainEpoch((now-g.Timestamp)/build.BlockDelaySecs) + MaxHeightDrift)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ type SyncManager struct {
|
||||
|
||||
syncStates []*SyncerState
|
||||
|
||||
// Normally this handler is set to `(*Syncer).Sync()`.
|
||||
doSync func(context.Context, *types.TipSet) error
|
||||
|
||||
stop chan struct{}
|
||||
@@ -280,7 +281,7 @@ func (sm *SyncManager) syncScheduler() {
|
||||
}
|
||||
|
||||
func (sm *SyncManager) scheduleIncoming(ts *types.TipSet) {
|
||||
log.Info("scheduling incoming tipset sync: ", ts.Cids())
|
||||
log.Debug("scheduling incoming tipset sync: ", ts.Cids())
|
||||
if sm.getBootstrapState() == BSStateSelected {
|
||||
sm.setBootstrapState(BSStateScheduled)
|
||||
sm.syncTargets <- ts
|
||||
|
||||
+3
-3
@@ -34,8 +34,8 @@ import (
|
||||
func init() {
|
||||
build.InsecurePoStValidation = true
|
||||
os.Setenv("TRUST_PARAMS", "1")
|
||||
miner.SupportedProofTypes = map[abi.RegisteredProof]struct{}{
|
||||
abi.RegisteredProof_StackedDRG2KiBSeal: {},
|
||||
miner.SupportedProofTypes = map[abi.RegisteredSealProof]struct{}{
|
||||
abi.RegisteredSealProof_StackedDrg2KiBV1: {},
|
||||
}
|
||||
power.ConsensusMinerMinPower = big.NewInt(2048)
|
||||
verifreg.MinVerifiedDealSize = big.NewInt(256)
|
||||
@@ -408,7 +408,7 @@ func TestSyncBadTimestamp(t *testing.T) {
|
||||
|
||||
base := tu.g.CurTipset
|
||||
tu.g.Timestamper = func(pts *types.TipSet, tl abi.ChainEpoch) uint64 {
|
||||
return pts.MinTimestamp() + (build.BlockDelay / 2)
|
||||
return pts.MinTimestamp() + (build.BlockDelaySecs / 2)
|
||||
}
|
||||
|
||||
fmt.Println("BASE: ", base.Cids())
|
||||
|
||||
@@ -76,7 +76,7 @@ func SizeStr(bi BigInt) string {
|
||||
}
|
||||
|
||||
f, _ := r.Float64()
|
||||
return fmt.Sprintf("%.3g %s", f, byteSizeUnits[i])
|
||||
return fmt.Sprintf("%.4g %s", f, byteSizeUnits[i])
|
||||
}
|
||||
|
||||
var deciUnits = []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"}
|
||||
|
||||
@@ -3,7 +3,12 @@ package types
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -60,8 +65,10 @@ func TestSizeStr(t *testing.T) {
|
||||
}{
|
||||
{0, "0 B"},
|
||||
{1, "1 B"},
|
||||
{1016, "1016 B"},
|
||||
{1024, "1 KiB"},
|
||||
{2000, "1.95 KiB"},
|
||||
{1000 * 1024, "1000 KiB"},
|
||||
{2000, "1.953 KiB"},
|
||||
{5 << 20, "5 MiB"},
|
||||
{11 << 60, "11 EiB"},
|
||||
}
|
||||
@@ -71,6 +78,22 @@ func TestSizeStr(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSizeStrUnitsSymmetry(t *testing.T) {
|
||||
s := rand.NewSource(time.Now().UnixNano())
|
||||
r := rand.New(s)
|
||||
|
||||
for i := 0; i < 1000000; i++ {
|
||||
n := r.Uint64()
|
||||
l := strings.ReplaceAll(units.BytesSize(float64(n)), " ", "")
|
||||
r := strings.ReplaceAll(SizeStr(NewInt(n)), " ", "")
|
||||
|
||||
assert.NotContains(t, l, "e+")
|
||||
assert.NotContains(t, r, "e+")
|
||||
|
||||
assert.Equal(t, l, r, "wrong formatting for %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSizeStrBig(t *testing.T) {
|
||||
ZiB := big.NewInt(50000)
|
||||
ZiB = ZiB.Lsh(ZiB, 70)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -79,7 +80,7 @@ func TestInteropBH(t *testing.T) {
|
||||
}
|
||||
|
||||
posts := []abi.PoStProof{
|
||||
{abi.RegisteredProof_StackedDRG2KiBWinningPoSt, []byte{0x07}},
|
||||
{abi.RegisteredPoStProof_StackedDrgWinning2KiBV1, []byte{0x07}},
|
||||
}
|
||||
|
||||
bh := &BlockHeader{
|
||||
@@ -116,10 +117,8 @@ func TestInteropBH(t *testing.T) {
|
||||
}
|
||||
|
||||
// acquired from go-filecoin
|
||||
gfc := "8f5501d04cb15021bf6bd003073d79e2238d4e61f1ad22814301020381420a0b818205410c818209410781d82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619cc430003e802d82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619ccd82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619ccd82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619cc410001f603"
|
||||
if gfc != hex.EncodeToString(bhsb) {
|
||||
t.Fatal("not equal!")
|
||||
}
|
||||
gfc := "8f5501d04cb15021bf6bd003073d79e2238d4e61f1ad22814301020381420a0b818205410c818200410781d82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619cc430003e802d82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619ccd82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619ccd82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619cc410001f603"
|
||||
require.Equal(t, gfc, hex.EncodeToString(bhsb))
|
||||
}
|
||||
|
||||
func BenchmarkBlockHeaderMarshal(b *testing.B) {
|
||||
|
||||
@@ -1,12 +1,103 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ExecutionResult struct {
|
||||
Msg *Message
|
||||
MsgRct *MessageReceipt
|
||||
Error string
|
||||
Duration time.Duration
|
||||
type ExecutionTrace struct {
|
||||
Msg *Message
|
||||
MsgRct *MessageReceipt
|
||||
Error string
|
||||
Duration time.Duration
|
||||
GasCharges []*GasTrace
|
||||
|
||||
Subcalls []*ExecutionResult
|
||||
Subcalls []ExecutionTrace
|
||||
}
|
||||
|
||||
type GasTrace struct {
|
||||
Name string
|
||||
|
||||
Location []Loc `json:"loc"`
|
||||
TotalGas int64 `json:"tg"`
|
||||
ComputeGas int64 `json:"cg"`
|
||||
StorageGas int64 `json:"sg"`
|
||||
TotalVirtualGas int64 `json:"vtg"`
|
||||
VirtualComputeGas int64 `json:"vcg"`
|
||||
VirtualStorageGas int64 `json:"vsg"`
|
||||
|
||||
TimeTaken time.Duration `json:"tt"`
|
||||
Extra interface{} `json:"ex,omitempty"`
|
||||
|
||||
Callers []uintptr `json:"-"`
|
||||
}
|
||||
|
||||
type Loc struct {
|
||||
File string
|
||||
Line int
|
||||
Function string
|
||||
}
|
||||
|
||||
func (l Loc) Show() bool {
|
||||
ignorePrefix := []string{
|
||||
"reflect.",
|
||||
"github.com/filecoin-project/lotus/chain/vm.(*Invoker).transform",
|
||||
"github.com/filecoin-project/go-amt-ipld/",
|
||||
}
|
||||
for _, pre := range ignorePrefix {
|
||||
if strings.HasPrefix(l.Function, pre) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
func (l Loc) String() string {
|
||||
file := strings.Split(l.File, "/")
|
||||
|
||||
fn := strings.Split(l.Function, "/")
|
||||
var fnpkg string
|
||||
if len(fn) > 2 {
|
||||
fnpkg = strings.Join(fn[len(fn)-2:], "/")
|
||||
} else {
|
||||
fnpkg = l.Function
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s@%s:%d", fnpkg, file[len(file)-1], l.Line)
|
||||
}
|
||||
|
||||
func (l Loc) Important() bool {
|
||||
if strings.HasPrefix(l.Function, "github.com/filecoin-project/specs-actors/actors/builtin") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (gt *GasTrace) MarshalJSON() ([]byte, error) {
|
||||
type GasTraceCopy GasTrace
|
||||
if len(gt.Location) == 0 {
|
||||
if len(gt.Callers) != 0 {
|
||||
frames := runtime.CallersFrames(gt.Callers)
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
if frame.Function == "github.com/filecoin-project/lotus/chain/vm.(*VM).ApplyMessage" {
|
||||
break
|
||||
}
|
||||
l := Loc{
|
||||
File: frame.File,
|
||||
Line: frame.Line,
|
||||
Function: frame.Function,
|
||||
}
|
||||
gt.Location = append(gt.Location, l)
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cpy := (*GasTraceCopy)(gt)
|
||||
return json.Marshal(cpy)
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ import (
|
||||
type FIL BigInt
|
||||
|
||||
func (f FIL) String() string {
|
||||
r := new(big.Rat).SetFrac(f.Int, big.NewInt(build.FilecoinPrecision))
|
||||
r := new(big.Rat).SetFrac(f.Int, big.NewInt(int64(build.FilecoinPrecision)))
|
||||
if r.Sign() == 0 {
|
||||
return "0"
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func ParseFIL(s string) (FIL, error) {
|
||||
return FIL{}, fmt.Errorf("failed to parse %q as a decimal number", s)
|
||||
}
|
||||
|
||||
r = r.Mul(r, big.NewRat(build.FilecoinPrecision, 1))
|
||||
r = r.Mul(r, big.NewRat(int64(build.FilecoinPrecision), 1))
|
||||
if !r.IsInt() {
|
||||
return FIL{}, fmt.Errorf("invalid FIL value: %q", s)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ type ChainMsg interface {
|
||||
Cid() cid.Cid
|
||||
VMMessage() *Message
|
||||
ToStorageBlock() (block.Block, error)
|
||||
// FIXME: This is the *message* length, this name is misleading.
|
||||
ChainLength() int
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,11 @@ func MkBlock(parents *types.TipSet, weightInc uint64, ticketNonce uint64) *types
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pstateRoot := c
|
||||
if parents != nil {
|
||||
pstateRoot = parents.Blocks()[0].ParentStateRoot
|
||||
}
|
||||
|
||||
var pcids []cid.Cid
|
||||
var height abi.ChainEpoch
|
||||
weight := types.NewInt(weightInc)
|
||||
@@ -72,7 +77,7 @@ func MkBlock(parents *types.TipSet, weightInc uint64, ticketNonce uint64) *types
|
||||
ParentWeight: weight,
|
||||
Messages: c,
|
||||
Height: height,
|
||||
ParentStateRoot: c,
|
||||
ParentStateRoot: pstateRoot,
|
||||
BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS, Data: []byte("boo! im a signature")},
|
||||
}
|
||||
}
|
||||
|
||||
+77
-16
@@ -12,34 +12,74 @@ import (
|
||||
"github.com/ipfs/go-cid"
|
||||
)
|
||||
|
||||
const (
|
||||
GasStorageMulti = 1
|
||||
GasComputeMulti = 1
|
||||
)
|
||||
|
||||
type GasCharge struct {
|
||||
Name string
|
||||
Extra interface{}
|
||||
|
||||
ComputeGas int64
|
||||
StorageGas int64
|
||||
|
||||
VirtualCompute int64
|
||||
VirtualStorage int64
|
||||
}
|
||||
|
||||
func (g GasCharge) Total() int64 {
|
||||
return g.ComputeGas*GasComputeMulti + g.StorageGas*GasStorageMulti
|
||||
}
|
||||
func (g GasCharge) WithVirtual(compute, storage int64) GasCharge {
|
||||
out := g
|
||||
out.VirtualCompute = compute
|
||||
out.VirtualStorage = storage
|
||||
return out
|
||||
}
|
||||
|
||||
func (g GasCharge) WithExtra(extra interface{}) GasCharge {
|
||||
out := g
|
||||
out.Extra = extra
|
||||
return out
|
||||
}
|
||||
|
||||
func newGasCharge(name string, computeGas int64, storageGas int64) GasCharge {
|
||||
return GasCharge{
|
||||
Name: name,
|
||||
ComputeGas: computeGas,
|
||||
StorageGas: storageGas,
|
||||
}
|
||||
}
|
||||
|
||||
// Pricelist provides prices for operations in the VM.
|
||||
//
|
||||
// Note: this interface should be APPEND ONLY since last chain checkpoint
|
||||
type Pricelist interface {
|
||||
// OnChainMessage returns the gas used for storing a message of a given size in the chain.
|
||||
OnChainMessage(msgSize int) int64
|
||||
OnChainMessage(msgSize int) GasCharge
|
||||
// OnChainReturnValue returns the gas used for storing the response of a message in the chain.
|
||||
OnChainReturnValue(dataSize int) int64
|
||||
OnChainReturnValue(dataSize int) GasCharge
|
||||
|
||||
// OnMethodInvocation returns the gas used when invoking a method.
|
||||
OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) int64
|
||||
OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) GasCharge
|
||||
|
||||
// OnIpldGet returns the gas used for storing an object
|
||||
OnIpldGet(dataSize int) int64
|
||||
OnIpldGet(dataSize int) GasCharge
|
||||
// OnIpldPut returns the gas used for storing an object
|
||||
OnIpldPut(dataSize int) int64
|
||||
OnIpldPut(dataSize int) GasCharge
|
||||
|
||||
// OnCreateActor returns the gas used for creating an actor
|
||||
OnCreateActor() int64
|
||||
OnCreateActor() GasCharge
|
||||
// OnDeleteActor returns the gas used for deleting an actor
|
||||
OnDeleteActor() int64
|
||||
OnDeleteActor() GasCharge
|
||||
|
||||
OnVerifySignature(sigType crypto.SigType, planTextSize int) (int64, error)
|
||||
OnHashing(dataSize int) int64
|
||||
OnComputeUnsealedSectorCid(proofType abi.RegisteredProof, pieces []abi.PieceInfo) int64
|
||||
OnVerifySeal(info abi.SealVerifyInfo) int64
|
||||
OnVerifyPost(info abi.WindowPoStVerifyInfo) int64
|
||||
OnVerifyConsensusFault() int64
|
||||
OnVerifySignature(sigType crypto.SigType, planTextSize int) (GasCharge, error)
|
||||
OnHashing(dataSize int) GasCharge
|
||||
OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) GasCharge
|
||||
OnVerifySeal(info abi.SealVerifyInfo) GasCharge
|
||||
OnVerifyPost(info abi.WindowPoStVerifyInfo) GasCharge
|
||||
OnVerifyConsensusFault() GasCharge
|
||||
}
|
||||
|
||||
var prices = map[abi.ChainEpoch]Pricelist{
|
||||
@@ -93,7 +133,7 @@ func PricelistByEpoch(epoch abi.ChainEpoch) Pricelist {
|
||||
type pricedSyscalls struct {
|
||||
under vmr.Syscalls
|
||||
pl Pricelist
|
||||
chargeGas func(int64)
|
||||
chargeGas func(GasCharge)
|
||||
}
|
||||
|
||||
// Verifies that a signature is valid for an address and plaintext.
|
||||
@@ -103,30 +143,40 @@ func (ps pricedSyscalls) VerifySignature(signature crypto.Signature, signer addr
|
||||
return err
|
||||
}
|
||||
ps.chargeGas(c)
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.VerifySignature(signature, signer, plaintext)
|
||||
}
|
||||
|
||||
// Hashes input data using blake2b with 256 bit output.
|
||||
func (ps pricedSyscalls) HashBlake2b(data []byte) [32]byte {
|
||||
ps.chargeGas(ps.pl.OnHashing(len(data)))
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.HashBlake2b(data)
|
||||
}
|
||||
|
||||
// Computes an unsealed sector CID (CommD) from its constituent piece CIDs (CommPs) and sizes.
|
||||
func (ps pricedSyscalls) ComputeUnsealedSectorCID(reg abi.RegisteredProof, pieces []abi.PieceInfo) (cid.Cid, error) {
|
||||
func (ps pricedSyscalls) ComputeUnsealedSectorCID(reg abi.RegisteredSealProof, pieces []abi.PieceInfo) (cid.Cid, error) {
|
||||
ps.chargeGas(ps.pl.OnComputeUnsealedSectorCid(reg, pieces))
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.ComputeUnsealedSectorCID(reg, pieces)
|
||||
}
|
||||
|
||||
// Verifies a sector seal proof.
|
||||
func (ps pricedSyscalls) VerifySeal(vi abi.SealVerifyInfo) error {
|
||||
ps.chargeGas(ps.pl.OnVerifySeal(vi))
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.VerifySeal(vi)
|
||||
}
|
||||
|
||||
// Verifies a proof of spacetime.
|
||||
func (ps pricedSyscalls) VerifyPoSt(vi abi.WindowPoStVerifyInfo) error {
|
||||
ps.chargeGas(ps.pl.OnVerifyPost(vi))
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.VerifyPoSt(vi)
|
||||
}
|
||||
|
||||
@@ -142,10 +192,21 @@ func (ps pricedSyscalls) VerifyPoSt(vi abi.WindowPoStVerifyInfo) error {
|
||||
// Returns nil and an error if the headers don't prove a fault.
|
||||
func (ps pricedSyscalls) VerifyConsensusFault(h1 []byte, h2 []byte, extra []byte) (*runtime.ConsensusFault, error) {
|
||||
ps.chargeGas(ps.pl.OnVerifyConsensusFault())
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.VerifyConsensusFault(h1, h2, extra)
|
||||
}
|
||||
|
||||
func (ps pricedSyscalls) BatchVerifySeals(inp map[address.Address][]abi.SealVerifyInfo) (map[address.Address][]bool, error) {
|
||||
ps.chargeGas(0) // TODO: this is only called by the cron actor. Should we even charge gas?
|
||||
var gasChargeSum GasCharge
|
||||
gasChargeSum.Name = "BatchVerifySeals"
|
||||
count := int64(0)
|
||||
for _, svis := range inp {
|
||||
count += int64(len(svis))
|
||||
}
|
||||
gasChargeSum = gasChargeSum.WithExtra(count).WithVirtual(129778623*count+716683250, 0)
|
||||
ps.chargeGas(gasChargeSum) // TODO: this is only called by the cron actor. Should we even charge gas?
|
||||
defer ps.chargeGas(gasOnActorExec)
|
||||
|
||||
return ps.under.BatchVerifySeals(inp)
|
||||
}
|
||||
|
||||
+61
-27
@@ -2,7 +2,9 @@ package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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/crypto"
|
||||
)
|
||||
@@ -84,80 +86,112 @@ type pricelistV0 struct {
|
||||
var _ Pricelist = (*pricelistV0)(nil)
|
||||
|
||||
// OnChainMessage returns the gas used for storing a message of a given size in the chain.
|
||||
func (pl *pricelistV0) OnChainMessage(msgSize int) int64 {
|
||||
return pl.onChainMessageBase + pl.onChainMessagePerByte*int64(msgSize)
|
||||
func (pl *pricelistV0) OnChainMessage(msgSize int) GasCharge {
|
||||
return newGasCharge("OnChainMessage", 0, pl.onChainMessageBase+pl.onChainMessagePerByte*int64(msgSize)).WithVirtual(77302, 0)
|
||||
}
|
||||
|
||||
// OnChainReturnValue returns the gas used for storing the response of a message in the chain.
|
||||
func (pl *pricelistV0) OnChainReturnValue(dataSize int) int64 {
|
||||
return int64(dataSize) * pl.onChainReturnValuePerByte
|
||||
func (pl *pricelistV0) OnChainReturnValue(dataSize int) GasCharge {
|
||||
return newGasCharge("OnChainReturnValue", 0, int64(dataSize)*pl.onChainReturnValuePerByte).WithVirtual(107294, 0)
|
||||
}
|
||||
|
||||
// OnMethodInvocation returns the gas used when invoking a method.
|
||||
func (pl *pricelistV0) OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) int64 {
|
||||
func (pl *pricelistV0) OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) GasCharge {
|
||||
ret := pl.sendBase
|
||||
extra := ""
|
||||
virtGas := int64(1072944)
|
||||
|
||||
if value != abi.NewTokenAmount(0) {
|
||||
// TODO: fix this, it is comparing pointers instead of values
|
||||
// see vv
|
||||
ret += pl.sendTransferFunds
|
||||
}
|
||||
if big.Cmp(value, abi.NewTokenAmount(0)) != 0 {
|
||||
virtGas += 497495
|
||||
if methodNum == builtin.MethodSend {
|
||||
// transfer only
|
||||
virtGas += 973940
|
||||
}
|
||||
extra += "t"
|
||||
}
|
||||
if methodNum != builtin.MethodSend {
|
||||
ret += pl.sendInvokeMethod
|
||||
extra += "i"
|
||||
// running actors is cheaper becase we hand over to actors
|
||||
virtGas += -295779
|
||||
}
|
||||
return ret
|
||||
return newGasCharge("OnMethodInvocation", ret, 0).WithVirtual(virtGas, 0).WithExtra(extra)
|
||||
}
|
||||
|
||||
// OnIpldGet returns the gas used for storing an object
|
||||
func (pl *pricelistV0) OnIpldGet(dataSize int) int64 {
|
||||
return pl.ipldGetBase + int64(dataSize)*pl.ipldGetPerByte
|
||||
func (pl *pricelistV0) OnIpldGet(dataSize int) GasCharge {
|
||||
return newGasCharge("OnIpldGet", pl.ipldGetBase+int64(dataSize)*pl.ipldGetPerByte, 0).
|
||||
WithExtra(dataSize).WithVirtual(433685, 0)
|
||||
}
|
||||
|
||||
// OnIpldPut returns the gas used for storing an object
|
||||
func (pl *pricelistV0) OnIpldPut(dataSize int) int64 {
|
||||
return pl.ipldPutBase + int64(dataSize)*pl.ipldPutPerByte
|
||||
func (pl *pricelistV0) OnIpldPut(dataSize int) GasCharge {
|
||||
return newGasCharge("OnIpldPut", pl.ipldPutBase, int64(dataSize)*pl.ipldPutPerByte).
|
||||
WithExtra(dataSize).WithVirtual(88970, 0)
|
||||
}
|
||||
|
||||
// OnCreateActor returns the gas used for creating an actor
|
||||
func (pl *pricelistV0) OnCreateActor() int64 {
|
||||
return pl.createActorBase + pl.createActorExtra
|
||||
func (pl *pricelistV0) OnCreateActor() GasCharge {
|
||||
return newGasCharge("OnCreateActor", pl.createActorBase, pl.createActorExtra).WithVirtual(65636, 0)
|
||||
}
|
||||
|
||||
// OnDeleteActor returns the gas used for deleting an actor
|
||||
func (pl *pricelistV0) OnDeleteActor() int64 {
|
||||
return pl.deleteActor
|
||||
func (pl *pricelistV0) OnDeleteActor() GasCharge {
|
||||
return newGasCharge("OnDeleteActor", 0, pl.deleteActor)
|
||||
}
|
||||
|
||||
// OnVerifySignature
|
||||
func (pl *pricelistV0) OnVerifySignature(sigType crypto.SigType, planTextSize int) (int64, error) {
|
||||
|
||||
func (pl *pricelistV0) OnVerifySignature(sigType crypto.SigType, planTextSize int) (GasCharge, error) {
|
||||
costFn, ok := pl.verifySignature[sigType]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("cost function for signature type %d not supported", sigType)
|
||||
return GasCharge{}, fmt.Errorf("cost function for signature type %d not supported", sigType)
|
||||
}
|
||||
return costFn(int64(planTextSize)), nil
|
||||
sigName, _ := sigType.Name()
|
||||
virtGas := int64(0)
|
||||
switch sigType {
|
||||
case crypto.SigTypeBLS:
|
||||
virtGas = 220138570
|
||||
case crypto.SigTypeSecp256k1:
|
||||
virtGas = 7053730
|
||||
}
|
||||
|
||||
return newGasCharge("OnVerifySignature", costFn(int64(planTextSize)), 0).
|
||||
WithExtra(map[string]interface{}{
|
||||
"type": sigName,
|
||||
"size": planTextSize,
|
||||
}).WithVirtual(virtGas, 0), nil
|
||||
}
|
||||
|
||||
// OnHashing
|
||||
func (pl *pricelistV0) OnHashing(dataSize int) int64 {
|
||||
return pl.hashingBase + int64(dataSize)*pl.hashingPerByte
|
||||
func (pl *pricelistV0) OnHashing(dataSize int) GasCharge {
|
||||
return newGasCharge("OnHashing", pl.hashingBase+int64(dataSize)*pl.hashingPerByte, 0).WithExtra(dataSize).WithVirtual(77300, 0)
|
||||
}
|
||||
|
||||
// OnComputeUnsealedSectorCid
|
||||
func (pl *pricelistV0) OnComputeUnsealedSectorCid(proofType abi.RegisteredProof, pieces []abi.PieceInfo) int64 {
|
||||
func (pl *pricelistV0) OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) GasCharge {
|
||||
// TODO: this needs more cost tunning, check with @lotus
|
||||
return pl.computeUnsealedSectorCidBase
|
||||
return newGasCharge("OnComputeUnsealedSectorCid", pl.computeUnsealedSectorCidBase, 0).WithVirtual(382370, 0)
|
||||
}
|
||||
|
||||
// OnVerifySeal
|
||||
func (pl *pricelistV0) OnVerifySeal(info abi.SealVerifyInfo) int64 {
|
||||
func (pl *pricelistV0) OnVerifySeal(info abi.SealVerifyInfo) GasCharge {
|
||||
// TODO: this needs more cost tunning, check with @lotus
|
||||
return pl.verifySealBase
|
||||
return newGasCharge("OnVerifySeal", pl.verifySealBase, 0).WithVirtual(199954003, 0)
|
||||
}
|
||||
|
||||
// OnVerifyPost
|
||||
func (pl *pricelistV0) OnVerifyPost(info abi.WindowPoStVerifyInfo) int64 {
|
||||
func (pl *pricelistV0) OnVerifyPost(info abi.WindowPoStVerifyInfo) GasCharge {
|
||||
// TODO: this needs more cost tunning, check with @lotus
|
||||
return pl.verifyPostBase
|
||||
return newGasCharge("OnVerifyPost", pl.verifyPostBase, 0).WithVirtual(2629471704, 0).WithExtra(len(info.ChallengedSectors))
|
||||
}
|
||||
|
||||
// OnVerifyConsensusFault
|
||||
func (pl *pricelistV0) OnVerifyConsensusFault() int64 {
|
||||
return pl.verifyConsensusFault
|
||||
func (pl *pricelistV0) OnVerifyConsensusFault() GasCharge {
|
||||
return newGasCharge("OnVerifyConsensusFault", pl.verifyConsensusFault, 0).WithVirtual(551935, 0)
|
||||
}
|
||||
|
||||
+77
-32
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
gruntime "runtime"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
@@ -51,10 +52,12 @@ type Runtime struct {
|
||||
origin address.Address
|
||||
originNonce uint64
|
||||
|
||||
internalExecutions []*types.ExecutionResult
|
||||
numActorsCreated uint64
|
||||
allowInternal bool
|
||||
callerValidated bool
|
||||
executionTrace types.ExecutionTrace
|
||||
numActorsCreated uint64
|
||||
allowInternal bool
|
||||
callerValidated bool
|
||||
lastGasChargeTime time.Time
|
||||
lastGasCharge *types.GasTrace
|
||||
}
|
||||
|
||||
func (rt *Runtime) TotalFilCircSupply() abi.TokenAmount {
|
||||
@@ -139,7 +142,7 @@ func (rt *Runtime) shimCall(f func() interface{}) (rval []byte, aerr aerrors.Act
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if ar, ok := r.(aerrors.ActorError); ok {
|
||||
log.Errorf("VM.Call failure: %+v", ar)
|
||||
log.Warnf("VM.Call failure: %+v", ar)
|
||||
aerr = ar
|
||||
return
|
||||
}
|
||||
@@ -250,7 +253,7 @@ func (rt *Runtime) CreateActor(codeID cid.Cid, address address.Address) {
|
||||
rt.Abortf(exitcode.SysErrorIllegalArgument, "Actor address already exists")
|
||||
}
|
||||
|
||||
rt.ChargeGas(rt.Pricelist().OnCreateActor())
|
||||
rt.chargeGas(rt.Pricelist().OnCreateActor())
|
||||
|
||||
err = rt.state.SetActor(address, &types.Actor{
|
||||
Code: codeID,
|
||||
@@ -261,10 +264,11 @@ func (rt *Runtime) CreateActor(codeID cid.Cid, address address.Address) {
|
||||
if err != nil {
|
||||
panic(aerrors.Fatalf("creating actor entry: %v", err))
|
||||
}
|
||||
_ = rt.chargeGasSafe(gasOnActorExec)
|
||||
}
|
||||
|
||||
func (rt *Runtime) DeleteActor(addr address.Address) {
|
||||
rt.ChargeGas(rt.Pricelist().OnDeleteActor())
|
||||
rt.chargeGas(rt.Pricelist().OnDeleteActor())
|
||||
act, err := rt.state.GetActor(rt.Message().Receiver())
|
||||
if err != nil {
|
||||
if xerrors.Is(err, types.ErrActorNotFound) {
|
||||
@@ -281,10 +285,10 @@ func (rt *Runtime) DeleteActor(addr address.Address) {
|
||||
if err := rt.state.DeleteActor(rt.Message().Receiver()); err != nil {
|
||||
panic(aerrors.Fatalf("failed to delete actor: %s", err))
|
||||
}
|
||||
_ = rt.chargeGasSafe(gasOnActorExec)
|
||||
}
|
||||
|
||||
func (rt *Runtime) Syscalls() vmr.Syscalls {
|
||||
// TODO: Make sure this is wrapped in something that charges gas for each of the calls
|
||||
return rt.sys
|
||||
}
|
||||
|
||||
@@ -309,7 +313,7 @@ func (rt *Runtime) Context() context.Context {
|
||||
}
|
||||
|
||||
func (rt *Runtime) Abortf(code exitcode.ExitCode, msg string, args ...interface{}) {
|
||||
log.Error("Abortf: ", fmt.Sprintf(msg, args...))
|
||||
log.Warnf("Abortf: " + fmt.Sprintf(msg, args...))
|
||||
panic(aerrors.NewfSkip(2, code, msg, args...))
|
||||
}
|
||||
|
||||
@@ -364,10 +368,12 @@ func (rt *Runtime) Send(to address.Address, method abi.MethodNum, m vmr.CBORMars
|
||||
log.Warnf("vmctx send failed: to: %s, method: %d: ret: %d, err: %s", to, method, ret, err)
|
||||
return nil, err.RetCode()
|
||||
}
|
||||
_ = rt.chargeGasSafe(gasOnActorExec)
|
||||
return &dumbWrapperType{ret}, 0
|
||||
}
|
||||
|
||||
func (rt *Runtime) internalSend(from, to address.Address, method abi.MethodNum, value types.BigInt, params []byte) ([]byte, aerrors.ActorError) {
|
||||
|
||||
start := time.Now()
|
||||
ctx, span := trace.StartSpan(rt.ctx, "vmc.Send")
|
||||
defer span.End()
|
||||
@@ -394,34 +400,17 @@ func (rt *Runtime) internalSend(from, to address.Address, method abi.MethodNum,
|
||||
}
|
||||
defer st.ClearSnapshot()
|
||||
|
||||
ret, errSend, subrt := rt.vm.send(ctx, msg, rt, 0)
|
||||
ret, errSend, subrt := rt.vm.send(ctx, msg, rt, nil, start)
|
||||
if errSend != nil {
|
||||
if errRevert := st.Revert(); errRevert != nil {
|
||||
return nil, aerrors.Escalate(errRevert, "failed to revert state tree after failed subcall")
|
||||
}
|
||||
}
|
||||
|
||||
mr := types.MessageReceipt{
|
||||
ExitCode: aerrors.RetCode(errSend),
|
||||
Return: ret,
|
||||
GasUsed: 0,
|
||||
}
|
||||
|
||||
er := types.ExecutionResult{
|
||||
Msg: msg,
|
||||
MsgRct: &mr,
|
||||
Duration: time.Since(start),
|
||||
}
|
||||
|
||||
if errSend != nil {
|
||||
er.Error = errSend.Error()
|
||||
}
|
||||
|
||||
if subrt != nil {
|
||||
er.Subcalls = subrt.internalExecutions
|
||||
rt.numActorsCreated = subrt.numActorsCreated
|
||||
}
|
||||
rt.internalExecutions = append(rt.internalExecutions, &er)
|
||||
rt.executionTrace.Subcalls = append(rt.executionTrace.Subcalls, subrt.executionTrace)
|
||||
return ret, errSend
|
||||
}
|
||||
|
||||
@@ -503,22 +492,78 @@ func (rt *Runtime) stateCommit(oldh, newh cid.Cid) aerrors.ActorError {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rt *Runtime) ChargeGas(toUse int64) {
|
||||
err := rt.chargeGasSafe(toUse)
|
||||
func (rt *Runtime) finilizeGasTracing() {
|
||||
if rt.lastGasCharge != nil {
|
||||
rt.lastGasCharge.TimeTaken = time.Since(rt.lastGasChargeTime)
|
||||
}
|
||||
}
|
||||
|
||||
// ChargeGas is spec actors function
|
||||
func (rt *Runtime) ChargeGas(name string, compute int64, virtual int64) {
|
||||
err := rt.chargeGasInternal(newGasCharge(name, compute, 0).WithVirtual(virtual, 0), 1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *Runtime) chargeGasSafe(toUse int64) aerrors.ActorError {
|
||||
func (rt *Runtime) chargeGas(gas GasCharge) {
|
||||
err := rt.chargeGasInternal(gas, 1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *Runtime) chargeGasFunc(skip int) func(GasCharge) {
|
||||
return func(gas GasCharge) {
|
||||
err := rt.chargeGasInternal(gas, 1+skip)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (rt *Runtime) chargeGasInternal(gas GasCharge, skip int) aerrors.ActorError {
|
||||
toUse := gas.Total()
|
||||
var callers [10]uintptr
|
||||
cout := gruntime.Callers(2+skip, callers[:])
|
||||
|
||||
now := time.Now()
|
||||
if rt.lastGasCharge != nil {
|
||||
rt.lastGasCharge.TimeTaken = now.Sub(rt.lastGasChargeTime)
|
||||
}
|
||||
|
||||
gasTrace := types.GasTrace{
|
||||
Name: gas.Name,
|
||||
Extra: gas.Extra,
|
||||
|
||||
TotalGas: toUse,
|
||||
ComputeGas: gas.ComputeGas,
|
||||
StorageGas: gas.StorageGas,
|
||||
|
||||
TotalVirtualGas: gas.VirtualCompute*GasComputeMulti + gas.VirtualStorage*GasStorageMulti,
|
||||
VirtualComputeGas: gas.VirtualCompute,
|
||||
VirtualStorageGas: gas.VirtualStorage,
|
||||
|
||||
Callers: callers[:cout],
|
||||
}
|
||||
rt.executionTrace.GasCharges = append(rt.executionTrace.GasCharges, &gasTrace)
|
||||
rt.lastGasChargeTime = now
|
||||
rt.lastGasCharge = &gasTrace
|
||||
|
||||
if rt.gasUsed+toUse > rt.gasAvailable {
|
||||
rt.gasUsed = rt.gasAvailable
|
||||
return aerrors.Newf(exitcode.SysErrOutOfGas, "not enough gas: used=%d, available=%d", rt.gasUsed, rt.gasAvailable)
|
||||
return aerrors.Newf(exitcode.SysErrOutOfGas, "not enough gas: used=%d, available=%d",
|
||||
rt.gasUsed, rt.gasAvailable)
|
||||
}
|
||||
rt.gasUsed += toUse
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rt *Runtime) chargeGasSafe(gas GasCharge) aerrors.ActorError {
|
||||
return rt.chargeGasInternal(gas, 1)
|
||||
}
|
||||
|
||||
func (rt *Runtime) Pricelist() Pricelist {
|
||||
return rt.pricelist
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ type syscallShim struct {
|
||||
verifier ffiwrapper.Verifier
|
||||
}
|
||||
|
||||
func (ss *syscallShim) ComputeUnsealedSectorCID(st abi.RegisteredProof, pieces []abi.PieceInfo) (cid.Cid, error) {
|
||||
func (ss *syscallShim) ComputeUnsealedSectorCID(st abi.RegisteredSealProof, pieces []abi.PieceInfo) (cid.Cid, error) {
|
||||
var sum abi.PaddedPieceSize
|
||||
for _, p := range pieces {
|
||||
sum += p.Size
|
||||
@@ -115,7 +115,7 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime.Consen
|
||||
// (b) time-offset mining fault
|
||||
// strictly speaking no need to compare heights based on double fork mining check above,
|
||||
// but at same height this would be a different fault.
|
||||
if !types.CidArrsEqual(blockA.Parents, blockB.Parents) && blockA.Height != blockB.Height {
|
||||
if types.CidArrsEqual(blockA.Parents, blockB.Parents) && blockA.Height != blockB.Height {
|
||||
consensusFault = &runtime.ConsensusFault{
|
||||
Target: blockA.Miner,
|
||||
Epoch: blockB.Height,
|
||||
@@ -159,7 +159,7 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime.Consen
|
||||
}
|
||||
|
||||
if sigErr := ss.VerifyBlockSig(&blockB); sigErr != nil {
|
||||
return nil, xerrors.Errorf("cannot verify first block sig: %w", sigErr)
|
||||
return nil, xerrors.Errorf("cannot verify second block sig: %w", sigErr)
|
||||
}
|
||||
|
||||
return consensusFault, nil
|
||||
@@ -241,10 +241,12 @@ func (ss *syscallShim) VerifySignature(sig crypto.Signature, addr address.Addres
|
||||
return sigs.Verify(&sig, kaddr, input)
|
||||
}
|
||||
|
||||
var BatchSealVerifyParallelism = goruntime.NumCPU()
|
||||
|
||||
func (ss *syscallShim) BatchVerifySeals(inp map[address.Address][]abi.SealVerifyInfo) (map[address.Address][]bool, error) {
|
||||
out := make(map[address.Address][]bool)
|
||||
|
||||
sema := make(chan struct{}, goruntime.NumCPU())
|
||||
sema := make(chan struct{}, BatchSealVerifyParallelism)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for addr, seals := range inp {
|
||||
|
||||
+81
-42
@@ -35,6 +35,7 @@ import (
|
||||
)
|
||||
|
||||
var log = logging.Logger("vm")
|
||||
var gasOnActorExec = newGasCharge("OnActorExec", 0, 0)
|
||||
|
||||
// ResolveToKeyAddr returns the public key type of address (`BLS`/`SECP256K1`) of an account actor identified by `addr`.
|
||||
func ResolveToKeyAddr(state types.StateTree, cst cbor.IpldStore, addr address.Address) (address.Address, aerrors.ActorError) {
|
||||
@@ -62,17 +63,19 @@ func ResolveToKeyAddr(state types.StateTree, cst cbor.IpldStore, addr address.Ad
|
||||
var _ cbor.IpldBlockstore = (*gasChargingBlocks)(nil)
|
||||
|
||||
type gasChargingBlocks struct {
|
||||
chargeGas func(int64)
|
||||
chargeGas func(GasCharge)
|
||||
pricelist Pricelist
|
||||
under cbor.IpldBlockstore
|
||||
}
|
||||
|
||||
func (bs *gasChargingBlocks) Get(c cid.Cid) (block.Block, error) {
|
||||
bs.chargeGas(newGasCharge("OnIpldGetStart", 0, 0))
|
||||
blk, err := bs.under.Get(c)
|
||||
if err != nil {
|
||||
return nil, aerrors.Escalate(err, "failed to get block from blockstore")
|
||||
}
|
||||
bs.chargeGas(bs.pricelist.OnIpldGet(len(blk.RawData())))
|
||||
bs.chargeGas(gasOnActorExec)
|
||||
|
||||
return blk, nil
|
||||
}
|
||||
@@ -83,6 +86,7 @@ func (bs *gasChargingBlocks) Put(blk block.Block) error {
|
||||
if err := bs.under.Put(blk); err != nil {
|
||||
return aerrors.Escalate(err, "failed to write data to disk")
|
||||
}
|
||||
bs.chargeGas(gasOnActorExec)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -102,15 +106,16 @@ func (vm *VM) makeRuntime(ctx context.Context, msg *types.Message, origin addres
|
||||
pricelist: PricelistByEpoch(vm.blockHeight),
|
||||
allowInternal: true,
|
||||
callerValidated: false,
|
||||
executionTrace: types.ExecutionTrace{Msg: msg},
|
||||
}
|
||||
|
||||
rt.cst = &cbor.BasicIpldStore{
|
||||
Blocks: &gasChargingBlocks{rt.ChargeGas, rt.pricelist, vm.cst.Blocks},
|
||||
Blocks: &gasChargingBlocks{rt.chargeGasFunc(2), rt.pricelist, vm.cst.Blocks},
|
||||
Atlas: vm.cst.Atlas,
|
||||
}
|
||||
rt.sys = pricedSyscalls{
|
||||
under: vm.Syscalls,
|
||||
chargeGas: rt.ChargeGas,
|
||||
chargeGas: rt.chargeGasFunc(1),
|
||||
pl: rt.pricelist,
|
||||
}
|
||||
|
||||
@@ -163,63 +168,93 @@ type Rand interface {
|
||||
|
||||
type ApplyRet struct {
|
||||
types.MessageReceipt
|
||||
ActorErr aerrors.ActorError
|
||||
Penalty types.BigInt
|
||||
InternalExecutions []*types.ExecutionResult
|
||||
Duration time.Duration
|
||||
ActorErr aerrors.ActorError
|
||||
Penalty types.BigInt
|
||||
ExecutionTrace types.ExecutionTrace
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
func (vm *VM) send(ctx context.Context, msg *types.Message, parent *Runtime,
|
||||
gasCharge int64) ([]byte, aerrors.ActorError, *Runtime) {
|
||||
gasCharge *GasCharge, start time.Time) ([]byte, aerrors.ActorError, *Runtime) {
|
||||
|
||||
st := vm.cstate
|
||||
gasUsed := gasCharge
|
||||
|
||||
origin := msg.From
|
||||
on := msg.Nonce
|
||||
var nac uint64 = 0
|
||||
var gasUsed int64
|
||||
if parent != nil {
|
||||
gasUsed = parent.gasUsed + gasUsed
|
||||
gasUsed = parent.gasUsed
|
||||
origin = parent.origin
|
||||
on = parent.originNonce
|
||||
nac = parent.numActorsCreated
|
||||
}
|
||||
|
||||
rt := vm.makeRuntime(ctx, msg, origin, on, gasUsed, nac)
|
||||
rt.lastGasChargeTime = start
|
||||
if parent != nil {
|
||||
rt.lastGasChargeTime = parent.lastGasChargeTime
|
||||
rt.lastGasCharge = parent.lastGasCharge
|
||||
defer func() {
|
||||
parent.gasUsed = rt.gasUsed
|
||||
parent.lastGasChargeTime = rt.lastGasChargeTime
|
||||
parent.lastGasCharge = rt.lastGasCharge
|
||||
}()
|
||||
}
|
||||
|
||||
if aerr := rt.chargeGasSafe(rt.Pricelist().OnMethodInvocation(msg.Value, msg.Method)); aerr != nil {
|
||||
return nil, aerrors.Wrap(aerr, "not enough gas for method invocation"), rt
|
||||
if gasCharge != nil {
|
||||
if err := rt.chargeGasSafe(*gasCharge); err != nil {
|
||||
// this should never happen
|
||||
return nil, aerrors.Wrap(err, "not enough gas for initial message charge, this should not happen"), rt
|
||||
}
|
||||
}
|
||||
|
||||
toActor, err := st.GetActor(msg.To)
|
||||
if err != nil {
|
||||
if xerrors.Is(err, init_.ErrAddressNotFound) {
|
||||
a, err := TryCreateAccountActor(rt, msg.To)
|
||||
if err != nil {
|
||||
return nil, aerrors.Wrapf(err, "could not create account"), rt
|
||||
ret, err := func() ([]byte, aerrors.ActorError) {
|
||||
if aerr := rt.chargeGasSafe(rt.Pricelist().OnMethodInvocation(msg.Value, msg.Method)); aerr != nil {
|
||||
return nil, aerrors.Wrap(aerr, "not enough gas for method invocation")
|
||||
}
|
||||
|
||||
toActor, err := st.GetActor(msg.To)
|
||||
if err != nil {
|
||||
if xerrors.Is(err, init_.ErrAddressNotFound) {
|
||||
a, err := TryCreateAccountActor(rt, msg.To)
|
||||
if err != nil {
|
||||
return nil, aerrors.Wrapf(err, "could not create account")
|
||||
}
|
||||
toActor = a
|
||||
} else {
|
||||
return nil, aerrors.Escalate(err, "getting actor")
|
||||
}
|
||||
toActor = a
|
||||
} else {
|
||||
return nil, aerrors.Escalate(err, "getting actor"), rt
|
||||
}
|
||||
}
|
||||
|
||||
if types.BigCmp(msg.Value, types.NewInt(0)) != 0 {
|
||||
if err := vm.transfer(msg.From, msg.To, msg.Value); err != nil {
|
||||
return nil, aerrors.Wrap(err, "failed to transfer funds"), nil
|
||||
if types.BigCmp(msg.Value, types.NewInt(0)) != 0 {
|
||||
if err := vm.transfer(msg.From, msg.To, msg.Value); err != nil {
|
||||
return nil, aerrors.Wrap(err, "failed to transfer funds")
|
||||
}
|
||||
}
|
||||
|
||||
if msg.Method != 0 {
|
||||
var ret []byte
|
||||
_ = rt.chargeGasSafe(gasOnActorExec)
|
||||
ret, err := vm.Invoke(toActor, rt, msg.Method, msg.Params)
|
||||
_ = rt.chargeGasSafe(newGasCharge("OnActorExecDone", 0, 0))
|
||||
return ret, err
|
||||
}
|
||||
return nil, nil
|
||||
}()
|
||||
|
||||
mr := types.MessageReceipt{
|
||||
ExitCode: aerrors.RetCode(err),
|
||||
Return: ret,
|
||||
GasUsed: rt.gasUsed,
|
||||
}
|
||||
rt.executionTrace.MsgRct = &mr
|
||||
rt.executionTrace.Duration = time.Since(start)
|
||||
if err != nil {
|
||||
rt.executionTrace.Error = err.Error()
|
||||
}
|
||||
|
||||
if msg.Method != 0 {
|
||||
ret, err := vm.Invoke(toActor, rt, msg.Method, msg.Params)
|
||||
return ret, err, rt
|
||||
}
|
||||
|
||||
return nil, nil, rt
|
||||
return ret, err, rt
|
||||
}
|
||||
|
||||
func checkMessage(msg *types.Message) error {
|
||||
@@ -243,17 +278,18 @@ func checkMessage(msg *types.Message) error {
|
||||
|
||||
func (vm *VM) ApplyImplicitMessage(ctx context.Context, msg *types.Message) (*ApplyRet, error) {
|
||||
start := time.Now()
|
||||
ret, actorErr, rt := vm.send(ctx, msg, nil, 0)
|
||||
ret, actorErr, rt := vm.send(ctx, msg, nil, nil, start)
|
||||
rt.finilizeGasTracing()
|
||||
return &ApplyRet{
|
||||
MessageReceipt: types.MessageReceipt{
|
||||
ExitCode: aerrors.RetCode(actorErr),
|
||||
Return: ret,
|
||||
GasUsed: 0,
|
||||
},
|
||||
ActorErr: actorErr,
|
||||
InternalExecutions: rt.internalExecutions,
|
||||
Penalty: types.NewInt(0),
|
||||
Duration: time.Since(start),
|
||||
ActorErr: actorErr,
|
||||
ExecutionTrace: rt.executionTrace,
|
||||
Penalty: types.NewInt(0),
|
||||
Duration: time.Since(start),
|
||||
}, actorErr
|
||||
}
|
||||
|
||||
@@ -276,7 +312,8 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
|
||||
|
||||
pl := PricelistByEpoch(vm.blockHeight)
|
||||
|
||||
msgGasCost := pl.OnChainMessage(cmsg.ChainLength())
|
||||
msgGas := pl.OnChainMessage(cmsg.ChainLength())
|
||||
msgGasCost := msgGas.Total()
|
||||
// this should never happen, but is currently still exercised by some tests
|
||||
if msgGasCost > msg.GasLimit {
|
||||
return &ApplyRet{
|
||||
@@ -359,7 +396,7 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
|
||||
}
|
||||
defer st.ClearSnapshot()
|
||||
|
||||
ret, actorErr, rt := vm.send(ctx, msg, nil, msgGasCost)
|
||||
ret, actorErr, rt := vm.send(ctx, msg, nil, &msgGas, start)
|
||||
if aerrors.IsFatal(actorErr) {
|
||||
return nil, xerrors.Errorf("[from=%s,to=%s,n=%d,m=%d,h=%d] fatal error: %w", msg.From, msg.To, msg.Nonce, msg.Method, vm.blockHeight, actorErr)
|
||||
}
|
||||
@@ -413,16 +450,18 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet,
|
||||
return nil, xerrors.Errorf("gas handling math is wrong")
|
||||
}
|
||||
|
||||
rt.finilizeGasTracing()
|
||||
|
||||
return &ApplyRet{
|
||||
MessageReceipt: types.MessageReceipt{
|
||||
ExitCode: errcode,
|
||||
Return: ret,
|
||||
GasUsed: gasUsed,
|
||||
},
|
||||
ActorErr: actorErr,
|
||||
InternalExecutions: rt.internalExecutions,
|
||||
Penalty: types.NewInt(0),
|
||||
Duration: time.Since(start),
|
||||
ActorErr: actorErr,
|
||||
ExecutionTrace: rt.executionTrace,
|
||||
Penalty: types.NewInt(0),
|
||||
Duration: time.Since(start),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
+22
-4
@@ -22,10 +22,11 @@ import (
|
||||
var log = logging.Logger("wallet")
|
||||
|
||||
const (
|
||||
KNamePrefix = "wallet-"
|
||||
KDefault = "default"
|
||||
KTBLS = "bls"
|
||||
KTSecp256k1 = "secp256k1"
|
||||
KNamePrefix = "wallet-"
|
||||
KTrashPrefix = "trash-"
|
||||
KDefault = "default"
|
||||
KTBLS = "bls"
|
||||
KTSecp256k1 = "secp256k1"
|
||||
)
|
||||
|
||||
type Wallet struct {
|
||||
@@ -230,6 +231,23 @@ func (w *Wallet) HasKey(addr address.Address) (bool, error) {
|
||||
return k != nil, nil
|
||||
}
|
||||
|
||||
func (w *Wallet) DeleteKey(addr address.Address) error {
|
||||
k, err := w.findKey(addr)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to delete key %s : %w", addr, err)
|
||||
}
|
||||
|
||||
if err := w.keystore.Put(KTrashPrefix+k.Address.String(), k.KeyInfo); err != nil {
|
||||
return xerrors.Errorf("failed to mark key %s as trashed: %w", addr, err)
|
||||
}
|
||||
|
||||
if err := w.keystore.Delete(KNamePrefix + k.Address.String()); err != nil {
|
||||
return xerrors.Errorf("failed to delete key %s: %w", addr, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Key struct {
|
||||
types.KeyInfo
|
||||
|
||||
|
||||
+35
-5
@@ -12,7 +12,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
"github.com/filecoin-project/go-address"
|
||||
cborutil "github.com/filecoin-project/go-cbor-util"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
@@ -222,6 +221,9 @@ var chainStatObjCmd = &cli.Command{
|
||||
base := cid.Undef
|
||||
if cctx.IsSet("base") {
|
||||
base, err = cid.Decode(cctx.String("base"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
stats, err := api.ChainStatObj(ctx, obj, base)
|
||||
@@ -230,7 +232,7 @@ var chainStatObjCmd = &cli.Command{
|
||||
}
|
||||
|
||||
fmt.Printf("Links: %d\n", stats.Links)
|
||||
fmt.Printf("Size: %s (%d)\n", units.BytesSize(float64(stats.Size)), stats.Size)
|
||||
fmt.Printf("Size: %s (%d)\n", types.SizeStr(types.NewInt(stats.Size)), stats.Size)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -842,6 +844,10 @@ var slashConsensusFault = &cli.Command{
|
||||
Name: "miner",
|
||||
Usage: "Miner address",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "extra",
|
||||
Usage: "Extra block cid",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := GetFullNodeAPI(cctx)
|
||||
@@ -886,10 +892,34 @@ var slashConsensusFault = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
params, err := actors.SerializeParams(&miner.ReportConsensusFaultParams{
|
||||
params := miner.ReportConsensusFaultParams{
|
||||
BlockHeader1: bh1,
|
||||
BlockHeader2: bh2,
|
||||
})
|
||||
}
|
||||
|
||||
if cctx.String("extra") != "" {
|
||||
cExtra, err := cid.Parse(cctx.String("extra"))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parsing cid extra: %w", err)
|
||||
}
|
||||
|
||||
bExtra, err := api.ChainGetBlock(ctx, cExtra)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting block extra: %w", err)
|
||||
}
|
||||
|
||||
be, err := cborutil.Dump(bExtra)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params.BlockHeaderExtra = be
|
||||
}
|
||||
|
||||
enc, err := actors.SerializeParams(¶ms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cctx.String("miner") == "" {
|
||||
return xerrors.Errorf("--miner flag is required")
|
||||
@@ -907,7 +937,7 @@ var slashConsensusFault = &cli.Command{
|
||||
GasPrice: types.NewInt(1),
|
||||
GasLimit: 10000000,
|
||||
Method: builtin.MethodsMiner.ReportConsensusFault,
|
||||
Params: params,
|
||||
Params: enc,
|
||||
}
|
||||
|
||||
smsg, err := api.MpoolPushMessage(ctx, msg)
|
||||
|
||||
+38
-17
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/market"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
lapi "github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
@@ -143,7 +144,7 @@ var clientCommPCmd = &cli.Command{
|
||||
}
|
||||
|
||||
fmt.Println("CID: ", encoder.Encode(ret.Root))
|
||||
fmt.Println("Piece size: ", ret.Size)
|
||||
fmt.Println("Piece size: ", types.SizeStr(types.NewInt(uint64(ret.Size))))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -203,7 +204,7 @@ var clientLocalCmd = &cli.Command{
|
||||
}
|
||||
|
||||
for _, v := range list {
|
||||
fmt.Printf("%s %s %d %s\n", encoder.Encode(v.Key), v.FilePath, v.Size, v.Status)
|
||||
fmt.Printf("%s %s %s %s\n", encoder.Encode(v.Key), v.FilePath, types.SizeStr(types.NewInt(v.Size)), v.Status)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@@ -371,7 +372,7 @@ var clientFindCmd = &cli.Command{
|
||||
fmt.Printf("ERR %s@%s: %s\n", offer.Miner, offer.MinerPeerID, offer.Err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("RETRIEVAL %s@%s-%sfil-%db\n", offer.Miner, offer.MinerPeerID, types.FIL(offer.MinPrice), offer.Size)
|
||||
fmt.Printf("RETRIEVAL %s@%s-%sfil-%s\n", offer.Miner, offer.MinerPeerID, types.FIL(offer.MinPrice), types.SizeStr(types.NewInt(offer.Size)))
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -391,6 +392,10 @@ var clientRetrieveCmd = &cli.Command{
|
||||
Name: "car",
|
||||
Usage: "export to a car file instead of a regular file",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "miner",
|
||||
Usage: "miner address for retrieval, if not present it'll use local discovery",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
if cctx.NArg() != 2 {
|
||||
@@ -398,7 +403,7 @@ var clientRetrieveCmd = &cli.Command{
|
||||
return nil
|
||||
}
|
||||
|
||||
api, closer, err := GetFullNodeAPI(cctx)
|
||||
fapi, closer, err := GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -409,7 +414,7 @@ var clientRetrieveCmd = &cli.Command{
|
||||
if cctx.String("address") != "" {
|
||||
payer, err = address.NewFromString(cctx.String("address"))
|
||||
} else {
|
||||
payer, err = api.WalletDefaultAddress(ctx)
|
||||
payer, err = fapi.WalletDefaultAddress(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -432,23 +437,39 @@ var clientRetrieveCmd = &cli.Command{
|
||||
return nil
|
||||
}*/ // TODO: fix
|
||||
|
||||
offers, err := api.ClientFindData(ctx, file)
|
||||
if err != nil {
|
||||
return err
|
||||
var offer api.QueryOffer
|
||||
minerStrAddr := cctx.String("miner")
|
||||
if minerStrAddr == "" { // Local discovery
|
||||
offers, err := fapi.ClientFindData(ctx, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: parse offer strings from `client find`, make this smarter
|
||||
if len(offers) < 1 {
|
||||
fmt.Println("Failed to find file")
|
||||
return nil
|
||||
}
|
||||
offer = offers[0]
|
||||
} else { // Directed retrieval
|
||||
minerAddr, err := address.NewFromString(minerStrAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offer, err = fapi.ClientMinerQueryOffer(ctx, file, minerAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: parse offer strings from `client find`, make this smarter
|
||||
|
||||
if len(offers) < 1 {
|
||||
fmt.Println("Failed to find file")
|
||||
return nil
|
||||
if offer.Err != "" {
|
||||
return fmt.Errorf("The received offer errored: %s", offer.Err)
|
||||
}
|
||||
|
||||
ref := &lapi.FileRef{
|
||||
Path: cctx.Args().Get(1),
|
||||
IsCAR: cctx.Bool("car"),
|
||||
}
|
||||
if err := api.ClientRetrieve(ctx, offers[0].Order(payer), ref); err != nil {
|
||||
if err := fapi.ClientRetrieve(ctx, offer.Order(payer), ref); err != nil {
|
||||
return xerrors.Errorf("Retrieval Failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -520,7 +541,7 @@ var clientQueryAskCmd = &cli.Command{
|
||||
|
||||
fmt.Printf("Ask: %s\n", maddr)
|
||||
fmt.Printf("Price per GiB: %s\n", types.FIL(ask.Ask.Price))
|
||||
fmt.Printf("Max Piece size: %d\n", ask.Ask.MaxPieceSize)
|
||||
fmt.Printf("Max Piece size: %s\n", types.SizeStr(types.NewInt(uint64(ask.Ask.MaxPieceSize))))
|
||||
|
||||
size := cctx.Int64("size")
|
||||
if size == 0 {
|
||||
@@ -597,7 +618,7 @@ var clientListDeals = &cli.Command{
|
||||
slashed = fmt.Sprintf("Y (epoch %d)", d.OnChainDealState.SlashEpoch)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\t%s\t%d\t%s\t%d\t%s\n", d.LocalDeal.ProposalCid, d.LocalDeal.DealID, d.LocalDeal.Provider, storagemarket.DealStates[d.LocalDeal.State], onChain, slashed, d.LocalDeal.PieceCID, d.LocalDeal.Size, d.LocalDeal.PricePerEpoch, d.LocalDeal.Duration, d.LocalDeal.Message)
|
||||
fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%d\t%s\n", d.LocalDeal.ProposalCid, d.LocalDeal.DealID, d.LocalDeal.Provider, storagemarket.DealStates[d.LocalDeal.State], onChain, slashed, d.LocalDeal.PieceCID, types.SizeStr(types.NewInt(d.LocalDeal.Size)), d.LocalDeal.PricePerEpoch, d.LocalDeal.Duration, d.LocalDeal.Message)
|
||||
}
|
||||
return w.Flush()
|
||||
},
|
||||
|
||||
+8
-10
@@ -10,18 +10,16 @@ import (
|
||||
)
|
||||
|
||||
var fetchParamCmd = &cli.Command{
|
||||
Name: "fetch-params",
|
||||
Usage: "Fetch proving parameters",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "proving-params",
|
||||
Usage: "download params used creating proofs for given size, i.e. 32GiB",
|
||||
},
|
||||
},
|
||||
Name: "fetch-params",
|
||||
Usage: "Fetch proving parameters",
|
||||
ArgsUsage: "[sectorSize]",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
sectorSizeInt, err := units.RAMInBytes(cctx.String("proving-params"))
|
||||
if !cctx.Args().Present() {
|
||||
return xerrors.Errorf("must pass sector size to fetch params for (specify as \"32GiB\", for instance)")
|
||||
}
|
||||
sectorSizeInt, err := units.RAMInBytes(cctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
return xerrors.Errorf("error parsing sector size (specify as \"32GiB\", for instance): %w", err)
|
||||
}
|
||||
sectorSize := uint64(sectorSizeInt)
|
||||
|
||||
|
||||
+256
-144
@@ -5,13 +5,14 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"github.com/multiformats/go-multihash"
|
||||
@@ -32,6 +33,7 @@ import (
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/power"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/reward"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/verifreg"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
@@ -41,7 +43,7 @@ import (
|
||||
)
|
||||
|
||||
type methodMeta struct {
|
||||
name string
|
||||
Name string
|
||||
|
||||
params reflect.Type
|
||||
ret reflect.Type
|
||||
@@ -69,7 +71,7 @@ func init() {
|
||||
nf := rt.NumField()
|
||||
|
||||
methods[c] = append(methods[c], methodMeta{
|
||||
name: "Send",
|
||||
Name: "Send",
|
||||
params: reflect.TypeOf(new(adt.EmptyValue)),
|
||||
ret: reflect.TypeOf(new(adt.EmptyValue)),
|
||||
})
|
||||
@@ -79,7 +81,7 @@ func init() {
|
||||
export := reflect.TypeOf(exports[i+1])
|
||||
|
||||
methods[c] = append(methods[c], methodMeta{
|
||||
name: rt.Field(i).Name,
|
||||
Name: rt.Field(i).Name,
|
||||
params: export.In(1),
|
||||
ret: export.Out(0),
|
||||
})
|
||||
@@ -145,27 +147,15 @@ var stateMinerInfo = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
act, err := api.StateGetActor(ctx, addr, ts.Key())
|
||||
mi, err := api.StateMinerInfo(ctx, addr, ts.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aso, err := api.ChainReadObj(ctx, act.Head)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var mst miner2.State
|
||||
if err := mst.UnmarshalCBOR(bytes.NewReader(aso)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mi := mst.Info
|
||||
|
||||
fmt.Printf("Owner:\t%s\n", mi.Owner)
|
||||
fmt.Printf("Worker:\t%s\n", mi.Worker)
|
||||
fmt.Printf("PeerID:\t%s\n", mi.PeerId)
|
||||
fmt.Printf("SectorSize:\t%s (%d)\n", units.BytesSize(float64(mi.SectorSize)), mi.SectorSize)
|
||||
fmt.Printf("SectorSize:\t%s (%d)\n", types.SizeStr(types.NewInt(uint64(mi.SectorSize))), mi.SectorSize)
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -361,7 +351,7 @@ var stateReplaySetCmd = &cli.Command{
|
||||
return fmt.Errorf("message cid was invalid: %s", err)
|
||||
}
|
||||
|
||||
api, closer, err := GetFullNodeAPI(cctx)
|
||||
fapi, closer, err := GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -383,7 +373,7 @@ var stateReplaySetCmd = &cli.Command{
|
||||
if len(tscids) > 0 {
|
||||
var headers []*types.BlockHeader
|
||||
for _, c := range tscids {
|
||||
h, err := api.ChainGetBlock(ctx, c)
|
||||
h, err := fapi.ChainGetBlock(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -393,12 +383,13 @@ var stateReplaySetCmd = &cli.Command{
|
||||
|
||||
ts, err = types.NewTipSet(headers)
|
||||
} else {
|
||||
r, err := api.StateWaitMsg(ctx, mcid, build.MessageConfidence)
|
||||
var r *api.MsgLookup
|
||||
r, err = fapi.StateWaitMsg(ctx, mcid, build.MessageConfidence)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("finding message in chain: %w", err)
|
||||
}
|
||||
|
||||
ts, err = api.ChainGetTipSet(ctx, r.TipSet.Parents())
|
||||
ts, err = fapi.ChainGetTipSet(ctx, r.TipSet.Parents())
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -406,7 +397,7 @@ var stateReplaySetCmd = &cli.Command{
|
||||
|
||||
}
|
||||
|
||||
res, err := api.StateReplay(ctx, ts.Key(), mcid)
|
||||
res, err := fapi.StateReplay(ctx, ts.Key(), mcid)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("replay call failed: %w", err)
|
||||
}
|
||||
@@ -719,7 +710,7 @@ var stateSectorSizeCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%d\n", mi.SectorSize)
|
||||
fmt.Printf("%s (%d)\n", types.SizeStr(types.NewInt(uint64(mi.SectorSize))), mi.SectorSize)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -751,12 +742,7 @@ var stateReadStateCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
act, err := api.StateGetActor(ctx, addr, ts.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
as, err := api.StateReadState(ctx, act, ts.Key())
|
||||
as, err := api.StateReadState(ctx, addr, ts.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -936,37 +922,29 @@ var stateComputeStateCmd = &cli.Command{
|
||||
return c.Code, nil
|
||||
}
|
||||
|
||||
return computeStateHtml(ts, stout, getCode)
|
||||
return computeStateHTMLTempl(ts, stout, getCode)
|
||||
}
|
||||
|
||||
fmt.Println("computed state cid: ", stout.Root)
|
||||
if cctx.Bool("show-trace") {
|
||||
for _, ir := range stout.Trace {
|
||||
fmt.Printf("%s\t%s\t%s\t%d\t%x\t%d\t%x\n", ir.Msg.From, ir.Msg.To, ir.Msg.Value, ir.Msg.Method, ir.Msg.Params, ir.MsgRct.ExitCode, ir.MsgRct.Return)
|
||||
printInternalExecutions("\t", ir.InternalExecutions)
|
||||
printInternalExecutions("\t", ir.ExecutionTrace.Subcalls)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func printInternalExecutions(prefix string, trace []*types.ExecutionResult) {
|
||||
func printInternalExecutions(prefix string, trace []types.ExecutionTrace) {
|
||||
for _, im := range trace {
|
||||
fmt.Printf("%s%s\t%s\t%s\t%d\t%x\t%d\t%x\n", prefix, im.Msg.From, im.Msg.To, im.Msg.Value, im.Msg.Method, im.Msg.Params, im.MsgRct.ExitCode, im.MsgRct.Return)
|
||||
printInternalExecutions(prefix+"\t", im.Subcalls)
|
||||
}
|
||||
}
|
||||
|
||||
func codeStr(c cid.Cid) string {
|
||||
cmh, err := multihash.Decode(c.Hash())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(cmh.Digest)
|
||||
}
|
||||
|
||||
func computeStateHtml(ts *types.TipSet, o *api.ComputeStateOutput, getCode func(addr address.Address) (cid.Cid, error)) error {
|
||||
fmt.Printf(`<html>
|
||||
var compStateTemplate = `
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
html, body { font-family: monospace; }
|
||||
@@ -988,125 +966,259 @@ func computeStateHtml(ts *types.TipSet, o *api.ComputeStateOutput, getCode func(
|
||||
}
|
||||
.slow-true-false { color: #660; }
|
||||
.slow-true-true { color: #f80; }
|
||||
.deemp { color: #444; }
|
||||
table {
|
||||
font-size: 12px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
tr {
|
||||
border-top: 1px solid black;
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
tr.sum { border-top: 2px solid black; }
|
||||
tr:first-child { border-top: none; }
|
||||
tr:last-child { border-bottom: none; }
|
||||
|
||||
|
||||
.ellipsis-content,
|
||||
.ellipsis-toggle input {
|
||||
display: none;
|
||||
}
|
||||
.ellipsis-toggle {
|
||||
cursor: pointer;
|
||||
}
|
||||
/**
|
||||
Checked State
|
||||
**/
|
||||
|
||||
.ellipsis-toggle input:checked + .ellipsis {
|
||||
display: none;
|
||||
}
|
||||
.ellipsis-toggle input:checked ~ .ellipsis-content {
|
||||
display: inline;
|
||||
background-color: #ddd;
|
||||
}
|
||||
hr {
|
||||
border: none;
|
||||
height: 1px;
|
||||
background-color: black;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>Tipset: <b>%s</b></div>
|
||||
<div>Height: %d</div>
|
||||
<div>State CID: <b>%s</b></div>
|
||||
<div>Calls</div>`, ts.Key(), ts.Height(), o.Root)
|
||||
<div>Tipset: <b>{{.TipSet.Key}}</b></div>
|
||||
<div>Epoch: {{.TipSet.Height}}</div>
|
||||
<div>State CID: <b>{{.Comp.Root}}</b></div>
|
||||
<div>Calls</div>
|
||||
{{range .Comp.Trace}}
|
||||
{{template "message" (Call .ExecutionTrace false .Msg.Cid.String)}}
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
for _, ir := range o.Trace {
|
||||
toCode, err := getCode(ir.Msg.To)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting code for %s: %w", toCode, err)
|
||||
}
|
||||
var compStateMsg = `
|
||||
<div class="exec" id="{{.Hash}}">
|
||||
{{$code := GetCode .Msg.To}}
|
||||
<div>
|
||||
<a href="#{{.Hash}}">
|
||||
{{if not .Subcall}}
|
||||
<h2 class="call">
|
||||
{{else}}
|
||||
<h4 class="call">
|
||||
{{end}}
|
||||
{{- CodeStr $code}}:{{GetMethod ($code) (.Msg.Method)}}
|
||||
{{if not .Subcall}}
|
||||
</h2>
|
||||
{{else}}
|
||||
</h4>
|
||||
{{end}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
params, err := jsonParams(toCode, ir.Msg.Method, ir.Msg.Params)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("decoding params: %w", err)
|
||||
}
|
||||
<div><b>{{.Msg.From}}</b> -> <b>{{.Msg.To}}</b> ({{ToFil .Msg.Value}} FIL), M{{.Msg.Method}}</div>
|
||||
{{if not .Subcall}}<div><small>Msg CID: {{.Msg.Cid}}</small></div>{{end}}
|
||||
{{if gt (len .Msg.Params) 0}}
|
||||
<div><pre class="params">{{JsonParams ($code) (.Msg.Method) (.Msg.Params) | html}}</pre></div>
|
||||
{{end}}
|
||||
<div><span class="slow-{{IsSlow .Duration}}-{{IsVerySlow .Duration}}">Took {{.Duration}}</span>, <span class="exit{{IntExit .MsgRct.ExitCode}}">Exit: <b>{{.MsgRct.ExitCode}}</b></span>{{if gt (len .MsgRct.Return) 0}}, Return{{end}}</div>
|
||||
|
||||
{{if gt (len .MsgRct.Return) 0}}
|
||||
<div><pre class="ret">{{JsonReturn ($code) (.Msg.Method) (.MsgRct.Return) | html}}</pre></div>
|
||||
{{end}}
|
||||
|
||||
if len(ir.Msg.Params) != 0 {
|
||||
params = `<div><pre class="params">` + params + `</pre></div>`
|
||||
} else {
|
||||
params = ""
|
||||
}
|
||||
{{if ne .MsgRct.ExitCode 0}}
|
||||
<div class="error">Error: <pre>{{.Error}}</pre></div>
|
||||
{{end}}
|
||||
|
||||
ret, err := jsonReturn(toCode, ir.Msg.Method, ir.MsgRct.Return)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("decoding return value: %w", err)
|
||||
}
|
||||
<details>
|
||||
<summary>Gas Trace</summary>
|
||||
<table>
|
||||
<tr><th>Name</th><th>Total/Compute/Storage</th><th>Time Taken</th><th>Location</th></tr>
|
||||
{{define "virt" -}}
|
||||
{{- if . -}}
|
||||
<span class="deemp">+({{.}})</span>
|
||||
{{- end -}}
|
||||
{{- end}}
|
||||
|
||||
if len(ir.MsgRct.Return) == 0 {
|
||||
ret = "</div>"
|
||||
} else {
|
||||
ret = `, Return</div><div><pre class="ret">` + ret + `</pre></div>`
|
||||
}
|
||||
{{define "gasC" -}}
|
||||
<td>{{.TotalGas}}{{template "virt" .TotalVirtualGas }}/{{.ComputeGas}}{{template "virt" .VirtualComputeGas}}/{{.StorageGas}}{{template "virt" .VirtualStorageGas}}</td>
|
||||
{{- end}}
|
||||
|
||||
slow := ir.Duration > 10*time.Millisecond
|
||||
veryslow := ir.Duration > 50*time.Millisecond
|
||||
{{range .GasCharges}}
|
||||
<tr><td>{{.Name}}{{if .Extra}}:{{.Extra}}{{end}}</td>
|
||||
{{template "gasC" .}}
|
||||
<td>{{.TimeTaken}}</td>
|
||||
<td>
|
||||
{{ $fImp := FirstImportant .Location }}
|
||||
{{ if $fImp }}
|
||||
<details>
|
||||
<summary>{{ $fImp }}</summary><hr />
|
||||
{{ $elipOn := false }}
|
||||
{{ range $index, $ele := .Location -}}
|
||||
{{- if $index }}<br />{{end -}}
|
||||
{{- if .Show -}}
|
||||
{{ if $elipOn }}
|
||||
{{ $elipOn = false }}
|
||||
</span></label>
|
||||
{{end}}
|
||||
|
||||
cid := ir.Msg.Cid()
|
||||
{{- if .Important }}<b>{{end -}}
|
||||
{{- . -}}
|
||||
{{if .Important }}</b>{{end}}
|
||||
{{else}}
|
||||
{{ if not $elipOn }}
|
||||
{{ $elipOn = true }}
|
||||
<label class="ellipsis-toggle"><input type="checkbox" /><span class="ellipsis">[…]<br /></span>
|
||||
<span class="ellipsis-content">
|
||||
{{end}}
|
||||
{{- "" -}}
|
||||
{{- . -}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{ if $elipOn }}
|
||||
{{ $elipOn = false }}
|
||||
</span></label>
|
||||
{{end}}
|
||||
</details>
|
||||
{{end}}
|
||||
</td></tr>
|
||||
{{end}}
|
||||
{{with SumGas .GasCharges}}
|
||||
<tr class="sum"><td><b>Sum</b></td>
|
||||
{{template "gasC" .}}
|
||||
<td>{{.TimeTaken}}</td>
|
||||
<td></td></tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</details>
|
||||
|
||||
fmt.Printf(`<div class="exec" id="%s">
|
||||
<div><a href="#%s"><h2 class="call">%s:%s</h2></a></div>
|
||||
<div><b>%s</b> -> <b>%s</b> (%s FIL), M%d</div>
|
||||
<div><small>Msg CID: %s</small></div>
|
||||
%s
|
||||
<div><span class="slow-%t-%t">Took %s</span>, <span class="exit%d">Exit: <b>%d</b></span>%s
|
||||
`, cid, cid, codeStr(toCode), methods[toCode][ir.Msg.Method].name, ir.Msg.From, ir.Msg.To, types.FIL(ir.Msg.Value), ir.Msg.Method, cid, params, slow, veryslow, ir.Duration, ir.MsgRct.ExitCode, ir.MsgRct.ExitCode, ret)
|
||||
if ir.MsgRct.ExitCode != 0 {
|
||||
fmt.Printf(`<div class="error">Error: <pre>%s</pre></div>`, ir.Error)
|
||||
}
|
||||
|
||||
if len(ir.InternalExecutions) > 0 {
|
||||
fmt.Println("<div>Internal executions:</div>")
|
||||
if err := printInternalExecutionsHtml(cid.String(), ir.InternalExecutions, getCode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Println("</div>")
|
||||
}
|
||||
{{if gt (len .Subcalls) 0}}
|
||||
<div>Subcalls:</div>
|
||||
{{$hash := .Hash}}
|
||||
{{range .Subcalls}}
|
||||
{{template "message" (Call . true (printf "%s-%s" $hash .Msg.Cid.String))}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>`
|
||||
|
||||
fmt.Printf(`</body>
|
||||
</html>`)
|
||||
return nil
|
||||
type compStateHTMLIn struct {
|
||||
TipSet *types.TipSet
|
||||
Comp *api.ComputeStateOutput
|
||||
}
|
||||
|
||||
func printInternalExecutionsHtml(hashName string, trace []*types.ExecutionResult, getCode func(addr address.Address) (cid.Cid, error)) error {
|
||||
for i, im := range trace {
|
||||
hashName := fmt.Sprintf("%s-r%d", hashName, i)
|
||||
|
||||
toCode, err := getCode(im.Msg.To)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting code for %s: %w", toCode, err)
|
||||
}
|
||||
|
||||
params, err := jsonParams(toCode, im.Msg.Method, im.Msg.Params)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("decoding params: %w", err)
|
||||
}
|
||||
|
||||
if len(im.Msg.Params) != 0 {
|
||||
params = `<div><pre class="params">` + params + `</pre></div>`
|
||||
} else {
|
||||
params = ""
|
||||
}
|
||||
|
||||
ret, err := jsonReturn(toCode, im.Msg.Method, im.MsgRct.Return)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("decoding return value: %w", err)
|
||||
}
|
||||
|
||||
if len(im.MsgRct.Return) == 0 {
|
||||
ret = "</div>"
|
||||
} else {
|
||||
ret = `, Return</div><div><pre class="ret">` + ret + `</pre></div>`
|
||||
}
|
||||
|
||||
slow := im.Duration > 10*time.Millisecond
|
||||
veryslow := im.Duration > 50*time.Millisecond
|
||||
|
||||
fmt.Printf(`<div class="exec" id="%s">
|
||||
<div><a href="#%s"><h4 class="call">%s:%s</h4></a></div>
|
||||
<div><b>%s</b> -> <b>%s</b> (%s FIL), M%d</div>
|
||||
%s
|
||||
<div><span class="slow-%t-%t">Took %s</span>, <span class="exit%d">Exit: <b>%d</b></span>%s
|
||||
`, hashName, hashName, codeStr(toCode), methods[toCode][im.Msg.Method].name, im.Msg.From, im.Msg.To, types.FIL(im.Msg.Value), im.Msg.Method, params, slow, veryslow, im.Duration, im.MsgRct.ExitCode, im.MsgRct.ExitCode, ret)
|
||||
if im.MsgRct.ExitCode != 0 {
|
||||
fmt.Printf(`<div class="error">Error: <pre>%s</pre></div>`, im.Error)
|
||||
}
|
||||
if len(im.Subcalls) > 0 {
|
||||
fmt.Println("<div>Subcalls:</div>")
|
||||
if err := printInternalExecutionsHtml(hashName, im.Subcalls, getCode); err != nil {
|
||||
return err
|
||||
func computeStateHTMLTempl(ts *types.TipSet, o *api.ComputeStateOutput, getCode func(addr address.Address) (cid.Cid, error)) error {
|
||||
t, err := template.New("compute_state").Funcs(map[string]interface{}{
|
||||
"GetCode": getCode,
|
||||
"GetMethod": getMethod,
|
||||
"ToFil": toFil,
|
||||
"JsonParams": jsonParams,
|
||||
"JsonReturn": jsonReturn,
|
||||
"IsSlow": isSlow,
|
||||
"IsVerySlow": isVerySlow,
|
||||
"IntExit": func(i exitcode.ExitCode) int64 { return int64(i) },
|
||||
"SumGas": sumGas,
|
||||
"CodeStr": codeStr,
|
||||
"Call": call,
|
||||
"FirstImportant": func(locs []types.Loc) *types.Loc {
|
||||
if len(locs) != 0 {
|
||||
for _, l := range locs {
|
||||
if l.Important() {
|
||||
return &l
|
||||
}
|
||||
}
|
||||
return &locs[0]
|
||||
}
|
||||
}
|
||||
fmt.Println("</div>")
|
||||
return nil
|
||||
},
|
||||
}).Parse(compStateTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err = t.New("message").Parse(compStateMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return t.ExecuteTemplate(os.Stdout, "compute_state", &compStateHTMLIn{
|
||||
TipSet: ts,
|
||||
Comp: o,
|
||||
})
|
||||
}
|
||||
|
||||
type callMeta struct {
|
||||
types.ExecutionTrace
|
||||
Subcall bool
|
||||
Hash string
|
||||
}
|
||||
|
||||
func call(e types.ExecutionTrace, subcall bool, hash string) callMeta {
|
||||
return callMeta{
|
||||
ExecutionTrace: e,
|
||||
Subcall: subcall,
|
||||
Hash: hash,
|
||||
}
|
||||
}
|
||||
|
||||
func codeStr(c cid.Cid) string {
|
||||
cmh, err := multihash.Decode(c.Hash())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(cmh.Digest)
|
||||
}
|
||||
|
||||
func getMethod(code cid.Cid, method abi.MethodNum) string {
|
||||
return methods[code][method].Name
|
||||
}
|
||||
|
||||
func toFil(f types.BigInt) types.FIL {
|
||||
return types.FIL(f)
|
||||
}
|
||||
|
||||
func isSlow(t time.Duration) bool {
|
||||
return t > 10*time.Millisecond
|
||||
}
|
||||
|
||||
func isVerySlow(t time.Duration) bool {
|
||||
return t > 50*time.Millisecond
|
||||
}
|
||||
|
||||
func sumGas(changes []*types.GasTrace) types.GasTrace {
|
||||
var out types.GasTrace
|
||||
for _, gc := range changes {
|
||||
out.TotalGas += gc.TotalGas
|
||||
out.ComputeGas += gc.ComputeGas
|
||||
out.StorageGas += gc.StorageGas
|
||||
|
||||
out.TotalVirtualGas += gc.TotalVirtualGas
|
||||
out.VirtualComputeGas += gc.VirtualComputeGas
|
||||
out.VirtualStorageGas += gc.VirtualStorageGas
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func jsonParams(code cid.Cid, method abi.MethodNum, params []byte) (string, error) {
|
||||
|
||||
+1
-1
@@ -186,7 +186,7 @@ func SyncWait(ctx context.Context, napi api.FullNode) error {
|
||||
|
||||
fmt.Printf("\r\x1b[2KWorker %d: Target: %s\tState: %s\tHeight: %d", working, target, chain.SyncStageString(ss.Stage), ss.Height)
|
||||
|
||||
if time.Now().Unix()-int64(head.MinTimestamp()) < build.BlockDelay {
|
||||
if time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs) {
|
||||
fmt.Println("\nDone!")
|
||||
return nil
|
||||
}
|
||||
|
||||
+32
-1
@@ -31,6 +31,7 @@ var walletCmd = &cli.Command{
|
||||
walletSetDefault,
|
||||
walletSign,
|
||||
walletVerify,
|
||||
walletDelete,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -112,7 +113,12 @@ var walletBalance = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", types.FIL(balance))
|
||||
if balance.Equals(types.NewInt(0)) {
|
||||
fmt.Printf("%s (warning: may display 0 if chain sync in progress)\n", types.FIL(balance))
|
||||
} else {
|
||||
fmt.Printf("%s\n", types.FIL(balance))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -375,3 +381,28 @@ var walletVerify = &cli.Command{
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var walletDelete = &cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete an account from the wallet",
|
||||
ArgsUsage: "<address> ",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
ctx := ReqContext(cctx)
|
||||
|
||||
if !cctx.Args().Present() || cctx.NArg() != 1 {
|
||||
return fmt.Errorf("must specify address to delete")
|
||||
}
|
||||
|
||||
addr, err := address.NewFromString(cctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return api.WalletDelete(ctx, addr)
|
||||
},
|
||||
}
|
||||
|
||||
+437
-26
@@ -1,11 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"sort"
|
||||
"time"
|
||||
@@ -44,8 +50,14 @@ var importBenchCmd = &cli.Command{
|
||||
Name: "height",
|
||||
Usage: "halt validation after given height",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "batch-seal-verify-threads",
|
||||
Usage: "set the parallelism factor for batch seal verification",
|
||||
Value: runtime.NumCPU(),
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
vm.BatchSealVerifyParallelism = cctx.Int("batch-seal-verify-threads")
|
||||
if !cctx.Args().Present() {
|
||||
fmt.Println("must pass car file of chain to benchmark importing")
|
||||
return nil
|
||||
@@ -111,14 +123,22 @@ var importBenchCmd = &cli.Command{
|
||||
ts = next
|
||||
}
|
||||
|
||||
out := make([]TipSetExec, 0, len(tschain))
|
||||
ibj, err := os.Create("import-bench.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ibj.Close() //nolint:errcheck
|
||||
|
||||
enc := json.NewEncoder(ibj)
|
||||
|
||||
var lastTse *TipSetExec
|
||||
|
||||
lastState := tschain[len(tschain)-1].ParentState()
|
||||
for i := len(tschain) - 2; i >= 0; i-- {
|
||||
cur := tschain[i]
|
||||
log.Infof("computing state (height: %d, ts=%s)", cur.Height(), cur.Cids())
|
||||
if cur.ParentState() != lastState {
|
||||
lastTrace := out[len(out)-1].Trace
|
||||
lastTrace := lastTse.Trace
|
||||
d, err := json.MarshalIndent(lastTrace, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -132,36 +152,301 @@ var importBenchCmd = &cli.Command{
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out = append(out, TipSetExec{
|
||||
stripCallers(trace)
|
||||
|
||||
lastTse = &TipSetExec{
|
||||
TipSet: cur.Key(),
|
||||
Trace: trace,
|
||||
Duration: time.Since(start),
|
||||
})
|
||||
}
|
||||
lastState = st
|
||||
if err := enc.Encode(lastTse); err != nil {
|
||||
return xerrors.Errorf("failed to write out tipsetexec: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
pprof.StopCPUProfile()
|
||||
|
||||
ibj, err := os.Create("import-bench.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ibj.Close() //nolint:errcheck
|
||||
|
||||
if err := json.NewEncoder(ibj).Encode(out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func walkExecutionTrace(et *types.ExecutionTrace) {
|
||||
for _, gc := range et.GasCharges {
|
||||
gc.Callers = nil
|
||||
}
|
||||
for _, sub := range et.Subcalls {
|
||||
walkExecutionTrace(&sub) //nolint:scopelint,gosec
|
||||
}
|
||||
}
|
||||
|
||||
func stripCallers(trace []*api.InvocResult) {
|
||||
for _, t := range trace {
|
||||
walkExecutionTrace(&t.ExecutionTrace)
|
||||
}
|
||||
}
|
||||
|
||||
type Invocation struct {
|
||||
TipSet types.TipSetKey
|
||||
Invoc *api.InvocResult
|
||||
}
|
||||
|
||||
const GasPerNs = 10
|
||||
|
||||
func countGasCosts(et *types.ExecutionTrace) (int64, int64) {
|
||||
var cgas, vgas int64
|
||||
|
||||
for _, gc := range et.GasCharges {
|
||||
cgas += gc.ComputeGas
|
||||
vgas += gc.VirtualComputeGas
|
||||
}
|
||||
|
||||
for _, sub := range et.Subcalls {
|
||||
c, v := countGasCosts(&sub)
|
||||
cgas += c
|
||||
vgas += v
|
||||
}
|
||||
|
||||
return cgas, vgas
|
||||
}
|
||||
|
||||
func compStats(vals []float64) (float64, float64) {
|
||||
var sum float64
|
||||
|
||||
for _, v := range vals {
|
||||
sum += v
|
||||
}
|
||||
|
||||
av := sum / float64(len(vals))
|
||||
|
||||
var varsum float64
|
||||
for _, v := range vals {
|
||||
delta := av - v
|
||||
varsum += delta * delta
|
||||
}
|
||||
|
||||
return av, math.Sqrt(varsum / float64(len(vals)))
|
||||
}
|
||||
|
||||
type stats struct {
|
||||
timeTaken meanVar
|
||||
gasRatio meanVar
|
||||
|
||||
extraCovar *covar
|
||||
}
|
||||
|
||||
type covar struct {
|
||||
meanX float64
|
||||
meanY float64
|
||||
c float64
|
||||
n float64
|
||||
m2x float64
|
||||
m2y float64
|
||||
}
|
||||
|
||||
func (cov1 *covar) Covariance() float64 {
|
||||
return cov1.c / (cov1.n - 1)
|
||||
}
|
||||
|
||||
func (cov1 *covar) VarianceX() float64 {
|
||||
return cov1.m2x / (cov1.n - 1)
|
||||
}
|
||||
|
||||
func (v1 *covar) StddevX() float64 {
|
||||
return math.Sqrt(v1.VarianceX())
|
||||
}
|
||||
|
||||
func (cov1 *covar) VarianceY() float64 {
|
||||
return cov1.m2y / (cov1.n - 1)
|
||||
}
|
||||
|
||||
func (v1 *covar) StddevY() float64 {
|
||||
return math.Sqrt(v1.VarianceY())
|
||||
}
|
||||
|
||||
func (cov1 *covar) AddPoint(x, y float64) {
|
||||
cov1.n += 1
|
||||
|
||||
dx := x - cov1.meanX
|
||||
cov1.meanX += dx / cov1.n
|
||||
dx2 := x - cov1.meanX
|
||||
cov1.m2x += dx * dx2
|
||||
|
||||
dy := y - cov1.meanY
|
||||
cov1.meanY += dy / cov1.n
|
||||
dy2 := y - cov1.meanY
|
||||
cov1.m2y += dy * dy2
|
||||
|
||||
cov1.c += dx * dy
|
||||
}
|
||||
|
||||
func (cov1 *covar) Combine(cov2 *covar) {
|
||||
if cov1.n == 0 {
|
||||
*cov1 = *cov2
|
||||
return
|
||||
}
|
||||
if cov2.n == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if cov1.n == 1 {
|
||||
cpy := *cov2
|
||||
cpy.AddPoint(cov2.meanX, cov2.meanY)
|
||||
*cov1 = cpy
|
||||
return
|
||||
}
|
||||
if cov2.n == 1 {
|
||||
cov1.AddPoint(cov2.meanX, cov2.meanY)
|
||||
}
|
||||
|
||||
out := covar{}
|
||||
out.n = cov1.n + cov2.n
|
||||
|
||||
dx := cov1.meanX - cov2.meanX
|
||||
out.meanX = cov1.meanX - dx*cov2.n/out.n
|
||||
out.m2x = cov1.m2x + cov2.m2x + dx*dx*cov1.n*cov2.n/out.n
|
||||
|
||||
dy := cov1.meanY - cov2.meanY
|
||||
out.meanY = cov1.meanY - dy*cov2.n/out.n
|
||||
out.m2y = cov1.m2y + cov2.m2y + dy*dy*cov1.n*cov2.n/out.n
|
||||
|
||||
out.c = cov1.c + cov2.c + dx*dy*cov1.n*cov2.n/out.n
|
||||
*cov1 = out
|
||||
}
|
||||
|
||||
func (cov1 *covar) A() float64 {
|
||||
return cov1.Covariance() / cov1.VarianceX()
|
||||
}
|
||||
func (cov1 *covar) B() float64 {
|
||||
return cov1.meanY - cov1.meanX*cov1.A()
|
||||
}
|
||||
func (cov1 *covar) Correl() float64 {
|
||||
return cov1.Covariance() / cov1.StddevX() / cov1.StddevY()
|
||||
}
|
||||
|
||||
type meanVar struct {
|
||||
n float64
|
||||
mean float64
|
||||
m2 float64
|
||||
}
|
||||
|
||||
func (v1 *meanVar) AddPoint(value float64) {
|
||||
// based on https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm
|
||||
v1.n += 1
|
||||
delta := value - v1.mean
|
||||
v1.mean += delta / v1.n
|
||||
delta2 := value - v1.mean
|
||||
v1.m2 += delta * delta2
|
||||
}
|
||||
|
||||
func (v1 *meanVar) Variance() float64 {
|
||||
return v1.m2 / (v1.n - 1)
|
||||
}
|
||||
func (v1 *meanVar) Mean() float64 {
|
||||
return v1.mean
|
||||
}
|
||||
func (v1 *meanVar) Stddev() float64 {
|
||||
return math.Sqrt(v1.Variance())
|
||||
}
|
||||
|
||||
func (v1 *meanVar) Combine(v2 *meanVar) {
|
||||
if v1.n == 0 {
|
||||
*v1 = *v2
|
||||
return
|
||||
}
|
||||
if v2.n == 0 {
|
||||
return
|
||||
}
|
||||
if v1.n == 1 {
|
||||
cpy := *v2
|
||||
cpy.AddPoint(v1.mean)
|
||||
*v1 = cpy
|
||||
return
|
||||
}
|
||||
if v2.n == 1 {
|
||||
v1.AddPoint(v2.mean)
|
||||
return
|
||||
}
|
||||
|
||||
newCount := v1.n + v2.n
|
||||
delta := v2.mean - v1.mean
|
||||
meanDelta := delta * v2.n / newCount
|
||||
m2 := v1.m2 + v2.m2 + delta*meanDelta*v1.n
|
||||
v1.n = newCount
|
||||
v1.mean += meanDelta
|
||||
v1.m2 = m2
|
||||
}
|
||||
|
||||
func getExtras(ex interface{}) (*string, *float64) {
|
||||
if t, ok := ex.(string); ok {
|
||||
return &t, nil
|
||||
}
|
||||
if size, ok := ex.(float64); ok {
|
||||
return nil, &size
|
||||
}
|
||||
if exMap, ok := ex.(map[string]interface{}); ok {
|
||||
t, tok := exMap["type"].(string)
|
||||
size, sok := exMap["size"].(float64)
|
||||
if tok && sok {
|
||||
return &t, &size
|
||||
}
|
||||
if tok {
|
||||
return &t, nil
|
||||
}
|
||||
if sok {
|
||||
return nil, &size
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func tallyGasCharges(charges map[string]*stats, et types.ExecutionTrace) {
|
||||
for i, gc := range et.GasCharges {
|
||||
name := gc.Name
|
||||
if name == "OnIpldGetStart" {
|
||||
continue
|
||||
}
|
||||
tt := float64(gc.TimeTaken.Nanoseconds())
|
||||
if name == "OnIpldGet" {
|
||||
prev := et.GasCharges[i-1]
|
||||
if prev.Name != "OnIpldGetStart" {
|
||||
log.Warn("OnIpldGet without OnIpldGetStart")
|
||||
}
|
||||
tt += float64(prev.TimeTaken.Nanoseconds())
|
||||
}
|
||||
eType, eSize := getExtras(gc.Extra)
|
||||
if eType != nil {
|
||||
name += "-" + *eType
|
||||
}
|
||||
compGas := gc.VirtualComputeGas
|
||||
if compGas == 0 {
|
||||
compGas = 1
|
||||
}
|
||||
s := charges[name]
|
||||
if s == nil {
|
||||
s = new(stats)
|
||||
charges[name] = s
|
||||
}
|
||||
|
||||
if eSize != nil {
|
||||
if s.extraCovar == nil {
|
||||
s.extraCovar = &covar{}
|
||||
}
|
||||
s.extraCovar.AddPoint(*eSize, tt)
|
||||
}
|
||||
|
||||
s.timeTaken.AddPoint(tt)
|
||||
|
||||
ratio := tt / float64(compGas) * GasPerNs
|
||||
s.gasRatio.AddPoint(ratio)
|
||||
}
|
||||
for _, sub := range et.Subcalls {
|
||||
tallyGasCharges(charges, sub)
|
||||
}
|
||||
}
|
||||
|
||||
var importAnalyzeCmd = &cli.Command{
|
||||
Name: "analyze",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
@@ -170,27 +455,142 @@ var importAnalyzeCmd = &cli.Command{
|
||||
return nil
|
||||
}
|
||||
|
||||
go func() {
|
||||
http.ListenAndServe("localhost:6060", nil)
|
||||
}()
|
||||
|
||||
fi, err := os.Open(cctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var results []TipSetExec
|
||||
if err := json.NewDecoder(fi).Decode(&results); err != nil {
|
||||
return err
|
||||
const nWorkers = 16
|
||||
jsonIn := make(chan []byte, 2*nWorkers)
|
||||
type result struct {
|
||||
totalTime time.Duration
|
||||
chargeStats map[string]*stats
|
||||
expensiveInvocs []Invocation
|
||||
}
|
||||
|
||||
results := make(chan result, nWorkers)
|
||||
|
||||
for i := 0; i < nWorkers; i++ {
|
||||
go func() {
|
||||
chargeStats := make(map[string]*stats)
|
||||
var totalTime time.Duration
|
||||
const invocsKeep = 32
|
||||
var expensiveInvocs = make([]Invocation, 0, 8*invocsKeep)
|
||||
var leastExpensiveInvoc = time.Duration(0)
|
||||
|
||||
for {
|
||||
b, ok := <-jsonIn
|
||||
if !ok {
|
||||
results <- result{
|
||||
totalTime: totalTime,
|
||||
chargeStats: chargeStats,
|
||||
expensiveInvocs: expensiveInvocs,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var tse TipSetExec
|
||||
err := json.Unmarshal(b, &tse)
|
||||
if err != nil {
|
||||
log.Warnf("error unmarshaling tipset: %+v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
totalTime += tse.Duration
|
||||
for _, inv := range tse.Trace {
|
||||
if inv.Duration > leastExpensiveInvoc {
|
||||
expensiveInvocs = append(expensiveInvocs, Invocation{
|
||||
TipSet: tse.TipSet,
|
||||
Invoc: inv,
|
||||
})
|
||||
}
|
||||
|
||||
tallyGasCharges(chargeStats, inv.ExecutionTrace)
|
||||
}
|
||||
if len(expensiveInvocs) > 4*invocsKeep {
|
||||
sort.Slice(expensiveInvocs, func(i, j int) bool {
|
||||
return expensiveInvocs[i].Invoc.Duration > expensiveInvocs[j].Invoc.Duration
|
||||
})
|
||||
leastExpensiveInvoc = expensiveInvocs[len(expensiveInvocs)-1].Invoc.Duration
|
||||
n := 30
|
||||
if len(expensiveInvocs) < n {
|
||||
n = len(expensiveInvocs)
|
||||
}
|
||||
expensiveInvocs = expensiveInvocs[:n]
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var totalTipsets int64
|
||||
reader := bufio.NewReader(fi)
|
||||
for {
|
||||
b, err := reader.ReadBytes('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
if e, ok := err.(*json.SyntaxError); ok {
|
||||
log.Warnf("syntax error at byte offset %d", e.Offset)
|
||||
}
|
||||
return err
|
||||
}
|
||||
totalTipsets++
|
||||
jsonIn <- b
|
||||
fmt.Fprintf(os.Stderr, "\rProcessed %d tipsets", totalTipsets)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
close(jsonIn)
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, "Collecting results\n")
|
||||
|
||||
var invocs []Invocation
|
||||
var totalTime time.Duration
|
||||
for i, r := range results {
|
||||
_ = i
|
||||
totalTime += r.Duration
|
||||
var keys []string
|
||||
var charges = make(map[string]*stats)
|
||||
for i := 0; i < nWorkers; i++ {
|
||||
fmt.Fprintf(os.Stderr, "\rProcessing results from worker %d/%d", i+1, nWorkers)
|
||||
res := <-results
|
||||
invocs = append(invocs, res.expensiveInvocs...)
|
||||
for k, v := range res.chargeStats {
|
||||
s := charges[k]
|
||||
if s == nil {
|
||||
s = new(stats)
|
||||
charges[k] = s
|
||||
}
|
||||
s.timeTaken.Combine(&v.timeTaken)
|
||||
s.gasRatio.Combine(&v.gasRatio)
|
||||
|
||||
for _, inv := range r.Trace {
|
||||
invocs = append(invocs, Invocation{
|
||||
TipSet: r.TipSet,
|
||||
Invoc: inv,
|
||||
})
|
||||
if v.extraCovar != nil {
|
||||
if s.extraCovar == nil {
|
||||
s.extraCovar = &covar{}
|
||||
}
|
||||
s.extraCovar.Combine(v.extraCovar)
|
||||
}
|
||||
}
|
||||
totalTime += res.totalTime
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "\nCollecting gas keys\n")
|
||||
for k := range charges {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
fmt.Println("Gas Price Deltas")
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
s := charges[k]
|
||||
fmt.Printf("%s: incr by %.4f~%.4f; tt %.4f~%.4f\n", k, s.gasRatio.Mean(), s.gasRatio.Stddev(),
|
||||
s.timeTaken.Mean(), s.timeTaken.Stddev())
|
||||
if s.extraCovar != nil {
|
||||
fmt.Printf("\t correll: %.2f, tt = %.2f * extra + %.2f\n", s.extraCovar.Correl(),
|
||||
s.extraCovar.A(), s.extraCovar.B())
|
||||
fmt.Printf("\t covar: %.2f, extra: %.2f~%.2f, tt2: %.2f~%.2f, count %.0f\n",
|
||||
s.extraCovar.Covariance(), s.extraCovar.meanX, s.extraCovar.StddevX(),
|
||||
s.extraCovar.meanY, s.extraCovar.StddevY(), s.extraCovar.n)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,9 +599,20 @@ var importAnalyzeCmd = &cli.Command{
|
||||
})
|
||||
|
||||
fmt.Println("Total time: ", totalTime)
|
||||
fmt.Println("Average time per epoch: ", totalTime/time.Duration(len(results)))
|
||||
fmt.Println("Average time per epoch: ", totalTime/time.Duration(totalTipsets))
|
||||
if actorExec, ok := charges["OnActorExec"]; ok {
|
||||
timeInActors := actorExec.timeTaken.Mean() * actorExec.timeTaken.n
|
||||
fmt.Printf("Avarage time per epoch in actors: %s (%.1f%%)\n", time.Duration(timeInActors)/time.Duration(totalTipsets), timeInActors/float64(totalTime)*100)
|
||||
}
|
||||
if actorExecDone, ok := charges["OnActorExecDone"]; ok {
|
||||
timeInActors := actorExecDone.timeTaken.Mean() * actorExecDone.timeTaken.n
|
||||
fmt.Printf("Avarage time per epoch in OnActorExecDone %s (%.1f%%)\n", time.Duration(timeInActors)/time.Duration(totalTipsets), timeInActors/float64(totalTime)*100)
|
||||
}
|
||||
|
||||
n := 30
|
||||
if len(invocs) < n {
|
||||
n = len(invocs)
|
||||
}
|
||||
fmt.Printf("Top %d most expensive calls:\n", n)
|
||||
for i := 0; i < n; i++ {
|
||||
inv := invocs[i].Invoc
|
||||
|
||||
+208
-136
@@ -74,7 +74,7 @@ func main() {
|
||||
|
||||
log.Info("Starting lotus-bench")
|
||||
|
||||
miner.SupportedProofTypes[abi.RegisteredProof_StackedDRG2KiBSeal] = struct{}{}
|
||||
miner.SupportedProofTypes[abi.RegisteredSealProof_StackedDrg2KiBV1] = struct{}{}
|
||||
|
||||
app := &cli.App{
|
||||
Name: "lotus-bench",
|
||||
@@ -139,6 +139,10 @@ var sealBenchCmd = &cli.Command{
|
||||
Name: "num-sectors",
|
||||
Value: 1,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "parallel",
|
||||
Value: 1,
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.Bool("no-gpu") {
|
||||
@@ -235,7 +239,12 @@ var sealBenchCmd = &cli.Command{
|
||||
|
||||
if robench == "" {
|
||||
var err error
|
||||
sealTimings, sealedSectors, err = runSeals(sb, sbfs, c.Int("num-sectors"), mid, sectorSize, []byte(c.String("ticket-preimage")), c.String("save-commit2-input"), c.Bool("skip-commit2"), c.Bool("skip-unseal"))
|
||||
parCfg := ParCfg{
|
||||
PreCommit1: c.Int("parallel"),
|
||||
PreCommit2: 1,
|
||||
Commit: 1,
|
||||
}
|
||||
sealTimings, sealedSectors, err = runSeals(sb, sbfs, c.Int("num-sectors"), parCfg, mid, sectorSize, []byte(c.String("ticket-preimage")), c.String("save-commit2-input"), c.Bool("skip-commit2"), c.Bool("skip-unseal"))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to run seals: %w", err)
|
||||
}
|
||||
@@ -270,9 +279,9 @@ var sealBenchCmd = &cli.Command{
|
||||
|
||||
for _, s := range genm.Sectors {
|
||||
sealedSectors = append(sealedSectors, abi.SectorInfo{
|
||||
SealedCID: s.CommR,
|
||||
SectorNumber: s.SectorID,
|
||||
RegisteredProof: s.ProofType,
|
||||
SealedCID: s.CommR,
|
||||
SectorNumber: s.SectorID,
|
||||
SealProof: s.ProofType,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -284,7 +293,12 @@ var sealBenchCmd = &cli.Command{
|
||||
|
||||
if !c.Bool("skip-commit2") {
|
||||
log.Info("generating winning post candidates")
|
||||
fcandidates, err := ffiwrapper.ProofVerifier.GenerateWinningPoStSectorChallenge(context.TODO(), spt, mid, challenge[:], uint64(len(sealedSectors)))
|
||||
wipt, err := spt.RegisteredWinningPoStProof()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fcandidates, err := ffiwrapper.ProofVerifier.GenerateWinningPoStSectorChallenge(context.TODO(), wipt, mid, challenge[:], uint64(len(sealedSectors)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -302,7 +316,7 @@ var sealBenchCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
winnnigpost1 := time.Now()
|
||||
winningpost1 := time.Now()
|
||||
|
||||
log.Info("computing winning post snark (hot)")
|
||||
proof2, err := sb.GenerateWinningPoSt(context.TODO(), mid, candidates, challenge[:])
|
||||
@@ -326,7 +340,7 @@ var sealBenchCmd = &cli.Command{
|
||||
log.Error("post verification failed")
|
||||
}
|
||||
|
||||
verifyWinnnigPost1 := time.Now()
|
||||
verifyWinningPost1 := time.Now()
|
||||
|
||||
pvi2 := abi.WinningPoStVerifyInfo{
|
||||
Randomness: abi.PoStRandomness(challenge[:]),
|
||||
@@ -345,7 +359,7 @@ var sealBenchCmd = &cli.Command{
|
||||
verifyWinningPost2 := time.Now()
|
||||
|
||||
log.Info("computing window post snark (cold)")
|
||||
wproof1, err := sb.GenerateWindowPoSt(context.TODO(), mid, sealedSectors, challenge[:])
|
||||
wproof1, _, err := sb.GenerateWindowPoSt(context.TODO(), mid, sealedSectors, challenge[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -353,7 +367,7 @@ var sealBenchCmd = &cli.Command{
|
||||
windowpost1 := time.Now()
|
||||
|
||||
log.Info("computing window post snark (hot)")
|
||||
wproof2, err := sb.GenerateWindowPoSt(context.TODO(), mid, sealedSectors, challenge[:])
|
||||
wproof2, _, err := sb.GenerateWindowPoSt(context.TODO(), mid, sealedSectors, challenge[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -393,10 +407,10 @@ var sealBenchCmd = &cli.Command{
|
||||
verifyWindowpost2 := time.Now()
|
||||
|
||||
bo.PostGenerateCandidates = gencandidates.Sub(beforePost)
|
||||
bo.PostWinningProofCold = winnnigpost1.Sub(gencandidates)
|
||||
bo.PostWinningProofHot = winnningpost2.Sub(winnnigpost1)
|
||||
bo.VerifyWinningPostCold = verifyWinnnigPost1.Sub(winnningpost2)
|
||||
bo.VerifyWinningPostHot = verifyWinningPost2.Sub(verifyWinnnigPost1)
|
||||
bo.PostWinningProofCold = winningpost1.Sub(gencandidates)
|
||||
bo.PostWinningProofHot = winnningpost2.Sub(winningpost1)
|
||||
bo.VerifyWinningPostCold = verifyWinningPost1.Sub(winnningpost2)
|
||||
bo.VerifyWinningPostHot = verifyWinningPost2.Sub(verifyWinningPost1)
|
||||
|
||||
bo.PostWindowProofCold = windowpost1.Sub(verifyWinningPost2)
|
||||
bo.PostWindowProofHot = windowpost2.Sub(windowpost1)
|
||||
@@ -412,7 +426,7 @@ var sealBenchCmd = &cli.Command{
|
||||
|
||||
fmt.Println(string(data))
|
||||
} else {
|
||||
fmt.Printf("----\nresults (v26) (%d)\n", sectorSize)
|
||||
fmt.Printf("----\nresults (v27) (%d)\n", sectorSize)
|
||||
if robench == "" {
|
||||
fmt.Printf("seal: addPiece: %s (%s)\n", bo.SealingResults[0].AddPiece, bps(bo.SectorSize, bo.SealingResults[0].AddPiece)) // TODO: average across multiple sealings
|
||||
fmt.Printf("seal: preCommit phase 1: %s (%s)\n", bo.SealingResults[0].PreCommit1, bps(bo.SectorSize, bo.SealingResults[0].PreCommit1))
|
||||
@@ -427,10 +441,10 @@ var sealBenchCmd = &cli.Command{
|
||||
}
|
||||
if !c.Bool("skip-commit2") {
|
||||
fmt.Printf("generate candidates: %s (%s)\n", bo.PostGenerateCandidates, bps(bo.SectorSize*abi.SectorSize(len(bo.SealingResults)), bo.PostGenerateCandidates))
|
||||
fmt.Printf("compute winnnig post proof (cold): %s\n", bo.PostWinningProofCold)
|
||||
fmt.Printf("compute winnnig post proof (hot): %s\n", bo.PostWinningProofHot)
|
||||
fmt.Printf("verify winnnig post proof (cold): %s\n", bo.VerifyWinningPostCold)
|
||||
fmt.Printf("verify winnnig post proof (hot): %s\n\n", bo.VerifyWinningPostHot)
|
||||
fmt.Printf("compute winning post proof (cold): %s\n", bo.PostWinningProofCold)
|
||||
fmt.Printf("compute winning post proof (hot): %s\n", bo.PostWinningProofHot)
|
||||
fmt.Printf("verify winning post proof (cold): %s\n", bo.VerifyWinningPostCold)
|
||||
fmt.Printf("verify winning post proof (hot): %s\n\n", bo.VerifyWinningPostHot)
|
||||
|
||||
fmt.Printf("compute window post proof (cold): %s\n", bo.PostWindowProofCold)
|
||||
fmt.Printf("compute window post proof (hot): %s\n", bo.PostWindowProofHot)
|
||||
@@ -442,9 +456,23 @@ var sealBenchCmd = &cli.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors int, mid abi.ActorID, sectorSize abi.SectorSize, ticketPreimage []byte, saveC2inp string, skipc2, skipunseal bool) ([]SealingResult, []abi.SectorInfo, error) {
|
||||
var sealTimings []SealingResult
|
||||
var sealedSectors []abi.SectorInfo
|
||||
type ParCfg struct {
|
||||
PreCommit1 int
|
||||
PreCommit2 int
|
||||
Commit int
|
||||
}
|
||||
|
||||
func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors int, par ParCfg, mid abi.ActorID, sectorSize abi.SectorSize, ticketPreimage []byte, saveC2inp string, skipc2, skipunseal bool) ([]SealingResult, []abi.SectorInfo, error) {
|
||||
var pieces []abi.PieceInfo
|
||||
sealTimings := make([]SealingResult, numSectors)
|
||||
sealedSectors := make([]abi.SectorInfo, numSectors)
|
||||
|
||||
preCommit2Sema := make(chan struct{}, par.PreCommit2)
|
||||
commitSema := make(chan struct{}, par.Commit)
|
||||
|
||||
if numSectors%par.PreCommit1 != 0 {
|
||||
return nil, nil, fmt.Errorf("parallelism factor must cleanly divide numSectors")
|
||||
}
|
||||
|
||||
for i := abi.SectorNumber(1); i <= abi.SectorNumber(numSectors); i++ {
|
||||
sid := abi.SectorID{
|
||||
@@ -453,7 +481,7 @@ func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors int, mid
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
log.Info("Writing piece into sector...")
|
||||
log.Infof("[%d] Writing piece into sector...", i)
|
||||
|
||||
r := rand.New(rand.NewSource(100 + int64(i)))
|
||||
|
||||
@@ -462,129 +490,168 @@ func runSeals(sb *ffiwrapper.Sealer, sbfs *basicfs.Provider, numSectors int, mid
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
addpiece := time.Now()
|
||||
pieces = append(pieces, pi)
|
||||
|
||||
trand := blake2b.Sum256(ticketPreimage)
|
||||
ticket := abi.SealRandomness(trand[:])
|
||||
sealTimings[i-1].AddPiece = time.Since(start)
|
||||
}
|
||||
|
||||
log.Info("Running replication(1)...")
|
||||
pieces := []abi.PieceInfo{pi}
|
||||
pc1o, err := sb.SealPreCommit1(context.TODO(), sid, ticket, pieces)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("commit: %w", err)
|
||||
}
|
||||
sectorsPerWorker := numSectors / par.PreCommit1
|
||||
|
||||
precommit1 := time.Now()
|
||||
errs := make(chan error, par.PreCommit1)
|
||||
for wid := 0; wid < par.PreCommit1; wid++ {
|
||||
go func(worker int) {
|
||||
sealerr := func() error {
|
||||
start := 1 + (worker * sectorsPerWorker)
|
||||
end := start + sectorsPerWorker
|
||||
for i := abi.SectorNumber(start); i < abi.SectorNumber(end); i++ {
|
||||
ix := int(i - 1)
|
||||
sid := abi.SectorID{
|
||||
Miner: mid,
|
||||
Number: i,
|
||||
}
|
||||
|
||||
log.Info("Running replication(2)...")
|
||||
cids, err := sb.SealPreCommit2(context.TODO(), sid, pc1o)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("commit: %w", err)
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
precommit2 := time.Now()
|
||||
trand := blake2b.Sum256(ticketPreimage)
|
||||
ticket := abi.SealRandomness(trand[:])
|
||||
|
||||
sealedSectors = append(sealedSectors, abi.SectorInfo{
|
||||
RegisteredProof: sb.SealProofType(),
|
||||
SectorNumber: i,
|
||||
SealedCID: cids.Sealed,
|
||||
})
|
||||
log.Infof("[%d] Running replication(1)...", i)
|
||||
pieces := []abi.PieceInfo{pieces[ix]}
|
||||
pc1o, err := sb.SealPreCommit1(context.TODO(), sid, ticket, pieces)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("commit: %w", err)
|
||||
}
|
||||
|
||||
seed := lapi.SealSeed{
|
||||
Epoch: 101,
|
||||
Value: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 255},
|
||||
}
|
||||
precommit1 := time.Now()
|
||||
|
||||
log.Info("Generating PoRep for sector (1)")
|
||||
c1o, err := sb.SealCommit1(context.TODO(), sid, ticket, seed.Value, pieces, cids)
|
||||
preCommit2Sema <- struct{}{}
|
||||
pc2Start := time.Now()
|
||||
log.Infof("[%d] Running replication(2)...", i)
|
||||
cids, err := sb.SealPreCommit2(context.TODO(), sid, pc1o)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("commit: %w", err)
|
||||
}
|
||||
|
||||
precommit2 := time.Now()
|
||||
<-preCommit2Sema
|
||||
|
||||
sealedSectors[ix] = abi.SectorInfo{
|
||||
SealProof: sb.SealProofType(),
|
||||
SectorNumber: i,
|
||||
SealedCID: cids.Sealed,
|
||||
}
|
||||
|
||||
seed := lapi.SealSeed{
|
||||
Epoch: 101,
|
||||
Value: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 255},
|
||||
}
|
||||
|
||||
commitSema <- struct{}{}
|
||||
commitStart := time.Now()
|
||||
log.Infof("[%d] Generating PoRep for sector (1)", i)
|
||||
c1o, err := sb.SealCommit1(context.TODO(), sid, ticket, seed.Value, pieces, cids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sealcommit1 := time.Now()
|
||||
|
||||
log.Infof("[%d] Generating PoRep for sector (2)", i)
|
||||
|
||||
if saveC2inp != "" {
|
||||
c2in := Commit2In{
|
||||
SectorNum: int64(i),
|
||||
Phase1Out: c1o,
|
||||
SectorSize: uint64(sectorSize),
|
||||
}
|
||||
|
||||
b, err := json.Marshal(&c2in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(saveC2inp, b, 0664); err != nil {
|
||||
log.Warnf("%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var proof storage.Proof
|
||||
if !skipc2 {
|
||||
proof, err = sb.SealCommit2(context.TODO(), sid, c1o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sealcommit2 := time.Now()
|
||||
<-commitSema
|
||||
|
||||
if !skipc2 {
|
||||
svi := abi.SealVerifyInfo{
|
||||
SectorID: abi.SectorID{Miner: mid, Number: i},
|
||||
SealedCID: cids.Sealed,
|
||||
SealProof: sb.SealProofType(),
|
||||
Proof: proof,
|
||||
DealIDs: nil,
|
||||
Randomness: ticket,
|
||||
InteractiveRandomness: seed.Value,
|
||||
UnsealedCID: cids.Unsealed,
|
||||
}
|
||||
|
||||
ok, err := ffiwrapper.ProofVerifier.VerifySeal(svi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return xerrors.Errorf("porep proof for sector %d was invalid", i)
|
||||
}
|
||||
}
|
||||
|
||||
verifySeal := time.Now()
|
||||
|
||||
if !skipunseal {
|
||||
log.Infof("[%d] Unsealing sector", i)
|
||||
{
|
||||
p, done, err := sbfs.AcquireSector(context.TODO(), abi.SectorID{Miner: mid, Number: 1}, stores.FTUnsealed, stores.FTNone, true)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("acquire unsealed sector for removing: %w", err)
|
||||
}
|
||||
done()
|
||||
|
||||
if err := os.Remove(p.Unsealed); err != nil {
|
||||
return xerrors.Errorf("removing unsealed sector: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err := sb.UnsealPiece(context.TODO(), abi.SectorID{Miner: mid, Number: 1}, 0, abi.PaddedPieceSize(sectorSize).Unpadded(), ticket, cids.Unsealed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
unseal := time.Now()
|
||||
|
||||
sealTimings[ix].PreCommit1 = precommit1.Sub(start)
|
||||
sealTimings[ix].PreCommit2 = precommit2.Sub(pc2Start)
|
||||
sealTimings[ix].Commit1 = sealcommit1.Sub(commitStart)
|
||||
sealTimings[ix].Commit2 = sealcommit2.Sub(sealcommit1)
|
||||
sealTimings[ix].Verify = verifySeal.Sub(sealcommit2)
|
||||
sealTimings[ix].Unseal = unseal.Sub(verifySeal)
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if sealerr != nil {
|
||||
errs <- sealerr
|
||||
return
|
||||
}
|
||||
errs <- nil
|
||||
}(wid)
|
||||
}
|
||||
|
||||
for i := 0; i < par.PreCommit1; i++ {
|
||||
err := <-errs
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sealcommit1 := time.Now()
|
||||
|
||||
log.Info("Generating PoRep for sector (2)")
|
||||
|
||||
if saveC2inp != "" {
|
||||
c2in := Commit2In{
|
||||
SectorNum: int64(i),
|
||||
Phase1Out: c1o,
|
||||
SectorSize: uint64(sectorSize),
|
||||
}
|
||||
|
||||
b, err := json.Marshal(&c2in)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(saveC2inp, b, 0664); err != nil {
|
||||
log.Warnf("%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var proof storage.Proof
|
||||
if !skipc2 {
|
||||
proof, err = sb.SealCommit2(context.TODO(), sid, c1o)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
sealcommit2 := time.Now()
|
||||
|
||||
if !skipc2 {
|
||||
svi := abi.SealVerifyInfo{
|
||||
SectorID: abi.SectorID{Miner: mid, Number: i},
|
||||
SealedCID: cids.Sealed,
|
||||
RegisteredProof: sb.SealProofType(),
|
||||
Proof: proof,
|
||||
DealIDs: nil,
|
||||
Randomness: ticket,
|
||||
InteractiveRandomness: seed.Value,
|
||||
UnsealedCID: cids.Unsealed,
|
||||
}
|
||||
|
||||
ok, err := ffiwrapper.ProofVerifier.VerifySeal(svi)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, nil, xerrors.Errorf("porep proof for sector %d was invalid", i)
|
||||
}
|
||||
}
|
||||
|
||||
verifySeal := time.Now()
|
||||
|
||||
if !skipunseal {
|
||||
log.Info("Unsealing sector")
|
||||
{
|
||||
p, done, err := sbfs.AcquireSector(context.TODO(), abi.SectorID{Miner: mid, Number: 1}, stores.FTUnsealed, stores.FTNone, true)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("acquire unsealed sector for removing: %w", err)
|
||||
}
|
||||
done()
|
||||
|
||||
if err := os.Remove(p.Unsealed); err != nil {
|
||||
return nil, nil, xerrors.Errorf("removing unsealed sector: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err := sb.UnsealPiece(context.TODO(), abi.SectorID{Miner: mid, Number: 1}, 0, abi.UnpaddedPieceSize(sectorSize), ticket, cids.Unsealed)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
unseal := time.Now()
|
||||
|
||||
sealTimings = append(sealTimings, SealingResult{
|
||||
AddPiece: addpiece.Sub(start),
|
||||
PreCommit1: precommit1.Sub(addpiece),
|
||||
PreCommit2: precommit2.Sub(precommit1),
|
||||
Commit1: sealcommit1.Sub(precommit2),
|
||||
Commit2: sealcommit2.Sub(sealcommit1),
|
||||
Verify: verifySeal.Sub(sealcommit2),
|
||||
Unseal: unseal.Sub(verifySeal),
|
||||
})
|
||||
}
|
||||
|
||||
return sealTimings, sealedSectors, nil
|
||||
@@ -598,6 +665,11 @@ var proveCmd = &cli.Command{
|
||||
Name: "no-gpu",
|
||||
Usage: "disable gpu usage for the benchmark run",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "miner-addr",
|
||||
Usage: "pass miner address (only necessary if using existing sectorbuilder)",
|
||||
Value: "t01000",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.Bool("no-gpu") {
|
||||
@@ -659,7 +731,7 @@ var proveCmd = &cli.Command{
|
||||
|
||||
fmt.Printf("proof: %x\n", proof)
|
||||
|
||||
fmt.Printf("----\nresults (v23) (%d)\n", c2in.SectorSize)
|
||||
fmt.Printf("----\nresults (v27) (%d)\n", c2in.SectorSize)
|
||||
dur := sealCommit2.Sub(start)
|
||||
|
||||
fmt.Printf("seal: commit phase 2: %s (%s)\n", dur, bps(abi.SectorSize(c2in.SectorSize), dur))
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMeanVar(t *testing.T) {
|
||||
N := 16
|
||||
ss := make([]*meanVar, N)
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
for i := 0; i < N; i++ {
|
||||
ss[i] = &meanVar{}
|
||||
maxJ := rng.Intn(1000)
|
||||
for j := 0; j < maxJ; j++ {
|
||||
ss[i].AddPoint(rng.NormFloat64()*5 + 500)
|
||||
}
|
||||
t.Logf("mean: %f, stddev: %f, count %f", ss[i].mean, ss[i].Stddev(), ss[i].n)
|
||||
}
|
||||
out := &meanVar{}
|
||||
for i := 0; i < N; i++ {
|
||||
out.Combine(ss[i])
|
||||
t.Logf("combine: mean: %f, stddev: %f", out.mean, out.Stddev())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCovar(t *testing.T) {
|
||||
N := 16
|
||||
ss := make([]*covar, N)
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
for i := 0; i < N; i++ {
|
||||
ss[i] = &covar{}
|
||||
maxJ := rng.Intn(1000) + 500
|
||||
for j := 0; j < maxJ; j++ {
|
||||
x := rng.NormFloat64()*5 + 500
|
||||
ss[i].AddPoint(x, x*2-1000)
|
||||
}
|
||||
t.Logf("corell: %f, y = %f*x+%f @%.0f", ss[i].Correl(), ss[i].A(), ss[i].B(), ss[i].n)
|
||||
t.Logf("\txVar: %f yVar: %f covar: %f", ss[i].StddevX(), ss[i].StddevY(), ss[i].Covariance())
|
||||
}
|
||||
out := &covar{}
|
||||
for i := 0; i < N; i++ {
|
||||
out.Combine(ss[i])
|
||||
t.Logf("combine: corell: %f, y = %f*x+%f", out.Correl(), out.A(), out.B())
|
||||
t.Logf("\txVar: %f yVar: %f covar: %f", out.StddevX(), out.StddevY(), out.Covariance())
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,9 @@ var log = logging.Logger("chainwatch")
|
||||
|
||||
func main() {
|
||||
_ = logging.SetLogLevel("*", "INFO")
|
||||
if err := logging.SetLogLevel("rpc", "error"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Info("Starting chainwatch")
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ func subMpool(ctx context.Context, api aapi.FullNode, st *storage) {
|
||||
msgs[v.Message.Message.Cid()] = &v.Message.Message
|
||||
}
|
||||
|
||||
log.Infof("Processing %d mpool updates", len(msgs))
|
||||
log.Debugf("Processing %d mpool updates", len(msgs))
|
||||
|
||||
err := st.storeMessages(msgs)
|
||||
if err != nil {
|
||||
|
||||
+327
-59
@@ -1,11 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
miner_spec "github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
"github.com/ipfs/go-cid"
|
||||
_ "github.com/lib/pq"
|
||||
"golang.org/x/xerrors"
|
||||
@@ -18,6 +24,9 @@ type storage struct {
|
||||
db *sql.DB
|
||||
|
||||
headerLk sync.Mutex
|
||||
|
||||
// stateful miner data
|
||||
minerSectors map[cid.Cid]struct{}
|
||||
}
|
||||
|
||||
func openStorage(dbSource string) (*storage, error) {
|
||||
@@ -28,7 +37,10 @@ func openStorage(dbSource string) (*storage, error) {
|
||||
|
||||
db.SetMaxOpenConns(1350)
|
||||
|
||||
st := &storage{db: db}
|
||||
ms := make(map[cid.Cid]struct{})
|
||||
ms[cid.Undef] = struct{}{}
|
||||
|
||||
st := &storage{db: db, minerSectors: ms}
|
||||
|
||||
return st, st.setup()
|
||||
}
|
||||
@@ -118,7 +130,7 @@ create unique index if not exists block_cid_uindex
|
||||
create materialized view if not exists state_heights
|
||||
as select distinct height, parentstateroot from blocks;
|
||||
|
||||
create unique index if not exists state_heights_uindex
|
||||
create index if not exists state_heights_index
|
||||
on state_heights (height);
|
||||
|
||||
create index if not exists state_heights_height_index
|
||||
@@ -252,31 +264,56 @@ create table if not exists receipts
|
||||
|
||||
create index if not exists receipts_msg_state_index
|
||||
on receipts (msg, state);
|
||||
/*
|
||||
create table if not exists miner_heads
|
||||
|
||||
create table if not exists miner_sectors
|
||||
(
|
||||
head text not null,
|
||||
addr text not null,
|
||||
stateroot text not null,
|
||||
sectorset text not null,
|
||||
setsize decimal not null,
|
||||
provingset text not null,
|
||||
provingsize decimal not null,
|
||||
owner text not null,
|
||||
worker text not null,
|
||||
peerid text not null,
|
||||
sectorsize bigint not null,
|
||||
power decimal not null,
|
||||
active bool,
|
||||
ppe bigint not null,
|
||||
slashed_at bigint not null,
|
||||
constraint miner_heads_pk
|
||||
primary key (head, addr)
|
||||
miner_id text not null,
|
||||
sector_id bigint not null,
|
||||
|
||||
activation_epoch bigint not null,
|
||||
expiration_epoch bigint not null,
|
||||
termination_epoch bigint,
|
||||
|
||||
deal_weight text not null,
|
||||
verified_deal_weight text not null,
|
||||
seal_cid text not null,
|
||||
seal_rand_epoch bigint not null,
|
||||
constraint miner_sectors_pk
|
||||
primary key (miner_id, sector_id)
|
||||
);
|
||||
|
||||
create index if not exists miner_heads_stateroot_index
|
||||
on miner_heads (stateroot);
|
||||
create index if not exists miner_sectors_miner_sectorid_index
|
||||
on miner_sectors (miner_id, sector_id);
|
||||
|
||||
create table if not exists miner_info
|
||||
(
|
||||
miner_id text not null,
|
||||
owner_addr text not null,
|
||||
worker_addr text not null,
|
||||
peer_id text,
|
||||
sector_size text not null,
|
||||
|
||||
precommit_deposits text not null,
|
||||
locked_funds text not null,
|
||||
next_deadline_process_faults bigint not null,
|
||||
constraint miner_info_pk
|
||||
primary key (miner_id)
|
||||
);
|
||||
|
||||
/* used to tell when a miners sectors (proven-not-yet-expired) changed if the miner_sectors_cid's are different a new sector was added or removed (terminated/expired) */
|
||||
create table if not exists miner_sectors_heads
|
||||
(
|
||||
miner_id text not null,
|
||||
miner_sectors_cid text not null,
|
||||
|
||||
state_root text not null,
|
||||
|
||||
constraint miner_sectors_heads_pk
|
||||
primary key (miner_id,miner_sectors_cid)
|
||||
|
||||
);
|
||||
|
||||
/*
|
||||
create or replace function miner_tips(epoch bigint)
|
||||
returns table (head text,
|
||||
addr text,
|
||||
@@ -456,54 +493,261 @@ func (st *storage) storeActors(actors map[address.Address]map[types.Actor]actorI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *storage) storeMiners(miners map[minerKey]*minerInfo) error {
|
||||
/*tx, err := st.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
type storeSectorsAPI interface {
|
||||
StateMinerSectors(context.Context, address.Address, *abi.BitField, bool, types.TipSetKey) ([]*api.ChainSectorInfo, error)
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`
|
||||
func (st *storage) storeSectors(minerTips map[types.TipSetKey][]*minerStateInfo, sectorApi storeSectorsAPI) error {
|
||||
tx, err := st.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
create temp table mh (like miner_heads excluding constraints) on commit drop;
|
||||
if _, err := tx.Exec(`create temp table ms (like miner_sectors excluding constraints) on commit drop;`); err != nil {
|
||||
return xerrors.Errorf("prep temp: %w", err)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(`copy ms (miner_id, sector_id, activation_epoch, expiration_epoch, deal_weight, verified_deal_weight, seal_cid, seal_rand_epoch) from STDIN`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
`); err != nil {
|
||||
return xerrors.Errorf("prep temp: %w", err)
|
||||
}
|
||||
for tipset, miners := range minerTips {
|
||||
for _, miner := range miners {
|
||||
sectors, err := sectorApi.StateMinerSectors(context.TODO(), miner.addr, nil, true, tipset)
|
||||
if err != nil {
|
||||
log.Debugw("Failed to load sectors", "tipset", tipset.String(), "miner", miner.addr.String(), "error", err)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(`copy mh (head, addr, stateroot, sectorset, setsize, provingset, provingsize, owner, worker, peerid, sectorsize, power, ppe) from STDIN`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, i := range miners {
|
||||
if _, err := stmt.Exec(
|
||||
k.act.Head.String(),
|
||||
k.addr.String(),
|
||||
k.stateroot.String(),
|
||||
i.state.Sectors.String(),
|
||||
fmt.Sprint(i.ssize),
|
||||
i.state.ProvingSet.String(),
|
||||
fmt.Sprint(i.psize),
|
||||
i.info.Owner.String(),
|
||||
i.info.Worker.String(),
|
||||
i.info.PeerId.String(),
|
||||
i.info.SectorSize,
|
||||
i.power.String(), // TODO: SPA
|
||||
i.state.PoStState.ProvingPeriodStart,
|
||||
); err != nil {
|
||||
return err
|
||||
for _, sector := range sectors {
|
||||
if _, err := stmt.Exec(
|
||||
miner.addr.String(),
|
||||
uint64(sector.ID),
|
||||
int64(sector.Info.ActivationEpoch),
|
||||
int64(sector.Info.Info.Expiration),
|
||||
sector.Info.DealWeight.String(),
|
||||
sector.Info.VerifiedDealWeight.String(),
|
||||
sector.Info.Info.SealedCID.String(),
|
||||
int64(sector.Info.Info.SealRandEpoch),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := stmt.Close(); err != nil {
|
||||
}
|
||||
|
||||
if err := stmt.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`insert into miner_sectors select * from ms on conflict do nothing `); err != nil {
|
||||
return xerrors.Errorf("actor put: %w", err)
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (st *storage) storeMiners(minerTips map[types.TipSetKey][]*minerStateInfo) error {
|
||||
tx, err := st.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`create temp table mi (like miner_info excluding constraints) on commit drop;`); err != nil {
|
||||
return xerrors.Errorf("prep temp: %w", err)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(`copy mi (miner_id, owner_addr, worker_addr, peer_id, sector_size, precommit_deposits, locked_funds, next_deadline_process_faults) from STDIN`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for ts, miners := range minerTips {
|
||||
for _, miner := range miners {
|
||||
var pid string
|
||||
if len(miner.info.PeerId) != 0 {
|
||||
peerid, err := peer.IDFromBytes(miner.info.PeerId)
|
||||
if err != nil {
|
||||
// this should "never happen", but if it does we should still store info about the miner.
|
||||
log.Warnw("failed to decode peerID", "peerID (bytes)", miner.info.PeerId, "miner", miner.addr, "tipset", ts.String())
|
||||
} else {
|
||||
pid = peerid.String()
|
||||
}
|
||||
}
|
||||
if _, err := stmt.Exec(
|
||||
miner.addr.String(),
|
||||
miner.info.Owner.String(),
|
||||
miner.info.Worker.String(),
|
||||
pid,
|
||||
miner.info.SectorSize.ShortString(),
|
||||
miner.state.PreCommitDeposits.String(),
|
||||
miner.state.LockedFunds.String(),
|
||||
miner.state.NextDeadlineToProcessFaults,
|
||||
); err != nil {
|
||||
log.Errorw("failed to store miner state", "state", miner.state, "info", miner.info, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if err := stmt.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`insert into miner_info select * from mi on conflict do nothing `); err != nil {
|
||||
return xerrors.Errorf("actor put: %w", err)
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
type minerSectorUpdate struct {
|
||||
minerState *minerStateInfo
|
||||
tskey types.TipSetKey
|
||||
oldSector cid.Cid
|
||||
}
|
||||
|
||||
func (st *storage) storeMinerSectorsHeads(minerTips map[types.TipSetKey][]*minerStateInfo, api api.FullNode) error {
|
||||
tx, err := st.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`create temp table msh (like miner_sectors_heads excluding constraints) on commit drop;`); err != nil {
|
||||
return xerrors.Errorf("prep temp: %w", err)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(`copy msh (miner_id, miner_sectors_cid, state_root) from STDIN`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var updateMiners []*minerSectorUpdate
|
||||
for tsk, miners := range minerTips {
|
||||
for _, miner := range miners {
|
||||
sectorCID, err := st.getLatestMinerSectorCID(context.TODO(), miner.addr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if sectorCID == cid.Undef {
|
||||
continue
|
||||
}
|
||||
if _, found := st.minerSectors[sectorCID]; !found {
|
||||
// schedule miner table update
|
||||
updateMiners = append(updateMiners, &minerSectorUpdate{
|
||||
minerState: miner,
|
||||
tskey: tsk,
|
||||
oldSector: sectorCID,
|
||||
})
|
||||
}
|
||||
st.minerSectors[sectorCID] = struct{}{}
|
||||
log.Debugw("got sector CID", "miner", miner.addr, "cid", sectorCID.String())
|
||||
if _, err := stmt.Exec(
|
||||
miner.addr.String(),
|
||||
miner.state.Sectors.String(),
|
||||
miner.stateroot.String(),
|
||||
); err != nil {
|
||||
log.Errorw("failed to store miners sectors head", "state", miner.state, "info", miner.info, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if err := stmt.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`insert into miner_sectors_heads select * from msh on conflict do nothing `); err != nil {
|
||||
return xerrors.Errorf("actor put: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
return st.updateMinerSectors(updateMiners, api)
|
||||
}
|
||||
|
||||
type deletedSector struct {
|
||||
deletedSector miner_spec.SectorOnChainInfo
|
||||
miner address.Address
|
||||
tskey types.TipSetKey
|
||||
}
|
||||
|
||||
func (st *storage) updateMinerSectors(miners []*minerSectorUpdate, api api.FullNode) error {
|
||||
log.Info("updating miners constant sector table")
|
||||
var deletedSectors []*deletedSector
|
||||
for _, miner := range miners {
|
||||
s := &apiIpldStore{context.TODO(), api}
|
||||
newSectors, err := adt.AsArray(s, miner.minerState.state.Sectors)
|
||||
if err != nil {
|
||||
log.Warnw("new sectors as array", "error", err, "cid", miner.minerState.state.Sectors)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`insert into miner_heads select * from mh on conflict do nothing `); err != nil {
|
||||
return xerrors.Errorf("actor put: %w", err)
|
||||
oldSectors, err := adt.AsArray(s, miner.oldSector)
|
||||
if err != nil {
|
||||
log.Warnw("old sectors as array", "error", err, "cid", miner.oldSector.String())
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()*/
|
||||
return nil
|
||||
var oldSecInfo miner_spec.SectorOnChainInfo
|
||||
var newSecInfo miner_spec.SectorOnChainInfo
|
||||
// if we cannot find an old sector in the new list then it was removed.
|
||||
if err := oldSectors.ForEach(&oldSecInfo, func(i int64) error {
|
||||
found, err := newSectors.Get(uint64(oldSecInfo.Info.SectorNumber), &newSecInfo)
|
||||
if err != nil {
|
||||
log.Warnw("new sectors get", "error", err)
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
log.Infow("MINER DELETED SECTOR", "miner", miner.minerState.addr.String(), "sector", oldSecInfo.Info.SectorNumber, "tipset", miner.tskey.String())
|
||||
deletedSectors = append(deletedSectors, &deletedSector{
|
||||
deletedSector: oldSecInfo,
|
||||
miner: miner.minerState.addr,
|
||||
tskey: miner.tskey,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Warnw("old sectors foreach", "error", err)
|
||||
return err
|
||||
}
|
||||
if len(deletedSectors) > 0 {
|
||||
log.Infow("Calculated updates", "miner", miner.minerState.addr, "deleted sectors", len(deletedSectors))
|
||||
}
|
||||
}
|
||||
// now we have all the sectors that were removed, update the database
|
||||
tx, err := st.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stmt, err := tx.Prepare(`UPDATE miner_sectors SET termination_epoch=$1 WHERE miner_id=$2 AND sector_id=$3`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, ds := range deletedSectors {
|
||||
ts, err := api.ChainGetTipSet(context.TODO(), ds.tskey)
|
||||
if err != nil {
|
||||
log.Warnw("get tipset", "error", err)
|
||||
return err
|
||||
}
|
||||
// TODO validate this shits right
|
||||
if ts.Height() >= ds.deletedSector.Info.Expiration {
|
||||
// means it expired, do nothing
|
||||
log.Infow("expired sector", "miner", ds.miner.String(), "sector", ds.deletedSector.Info.SectorNumber)
|
||||
continue
|
||||
}
|
||||
log.Infow("terminated sector", "miner", ds.miner.String(), "sector", ds.deletedSector.Info.SectorNumber)
|
||||
// means it was terminated.
|
||||
if _, err := stmt.Exec(int64(ts.Height()), ds.miner.String(), int64(ds.deletedSector.Info.SectorNumber)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := stmt.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer log.Info("update miner sectors complete")
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (st *storage) storeHeaders(bhs map[cid.Cid]*types.BlockHeader, sync bool) error {
|
||||
@@ -1008,3 +1252,27 @@ func (st *storage) refreshViews() error {
|
||||
func (st *storage) close() error {
|
||||
return st.db.Close()
|
||||
}
|
||||
|
||||
func (st *storage) getLatestMinerSectorCID(ctx context.Context, miner address.Address) (cid.Cid, error) {
|
||||
queryStr := fmt.Sprintf(`
|
||||
SELECT miner_sectors_cid
|
||||
FROM miner_sectors_heads
|
||||
LEFT JOIN blocks ON miner_sectors_heads.state_root = blocks.parentstateroot
|
||||
WHERE miner_id = '%s'
|
||||
ORDER BY blocks.height DESC
|
||||
LIMIT 1;
|
||||
`,
|
||||
miner.String())
|
||||
|
||||
var cidstr string
|
||||
err := st.db.QueryRowContext(ctx, queryStr).Scan(&cidstr)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
log.Warnf("no miner with miner_id: %s in table", miner)
|
||||
return cid.Undef, nil
|
||||
case err != nil:
|
||||
return cid.Undef, err
|
||||
default:
|
||||
return cid.Decode(cidstr)
|
||||
}
|
||||
}
|
||||
|
||||
+203
-60
@@ -5,17 +5,21 @@ import (
|
||||
"container/list"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
"time"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/ipfs/go-cid"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"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"
|
||||
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
@@ -49,27 +53,30 @@ func runSyncer(ctx context.Context, api api.FullNode, st *storage, maxBatch int)
|
||||
}()
|
||||
}
|
||||
|
||||
type minerKey struct {
|
||||
type minerStateInfo struct {
|
||||
// common
|
||||
addr address.Address
|
||||
act types.Actor
|
||||
stateroot cid.Cid
|
||||
}
|
||||
|
||||
type minerInfo struct {
|
||||
// miner specific
|
||||
state miner.State
|
||||
info miner.MinerInfo
|
||||
|
||||
power big.Int
|
||||
ssize uint64
|
||||
psize uint64
|
||||
// tracked by power actor
|
||||
rawPower big.Int
|
||||
qalPower big.Int
|
||||
ssize uint64
|
||||
psize uint64
|
||||
}
|
||||
|
||||
type actorInfo struct {
|
||||
stateroot cid.Cid
|
||||
tsKey types.TipSetKey
|
||||
state string
|
||||
}
|
||||
|
||||
func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipSet, maxBatch int) {
|
||||
func syncHead(ctx context.Context, api api.FullNode, st *storage, headTs *types.TipSet, maxBatch int) {
|
||||
var alk sync.Mutex
|
||||
|
||||
log.Infof("Getting synced block list")
|
||||
@@ -78,25 +85,28 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
|
||||
log.Infof("Getting headers / actors")
|
||||
|
||||
// global list of all blocks that need to be synced
|
||||
allToSync := map[cid.Cid]*types.BlockHeader{}
|
||||
// a stack
|
||||
toVisit := list.New()
|
||||
|
||||
for _, header := range ts.Blocks() {
|
||||
for _, header := range headTs.Blocks() {
|
||||
toVisit.PushBack(header)
|
||||
}
|
||||
|
||||
// TODO consider making a db query to check where syncing left off at in the case of a restart and avoid reprocessing
|
||||
// those entries, or write value to file on shutdown
|
||||
// walk the entire chain starting from headTS
|
||||
for toVisit.Len() > 0 {
|
||||
bh := toVisit.Remove(toVisit.Back()).(*types.BlockHeader)
|
||||
|
||||
_, has := hazlist[bh.Cid()]
|
||||
if _, seen := allToSync[bh.Cid()]; seen || has {
|
||||
continue
|
||||
}
|
||||
|
||||
allToSync[bh.Cid()] = bh
|
||||
|
||||
if len(allToSync)%500 == 10 {
|
||||
log.Infof("todo: (%d) %s @%d", len(allToSync), bh.Cid(), bh.Height)
|
||||
log.Debugf("to visit: (%d) %s @%d", len(allToSync), bh.Cid(), bh.Height)
|
||||
}
|
||||
|
||||
if len(bh.Parents) == 0 {
|
||||
@@ -114,30 +124,42 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
}
|
||||
}
|
||||
|
||||
// Main worker loop, this loop runs until all tipse from headTS to genesis have been processed.
|
||||
for len(allToSync) > 0 {
|
||||
// first map is addresses -> common actors states (head, code, balance, nonce)
|
||||
// second map common actor states -> chain state (tipset, stateroot) & unique actor state (deserialization of their head CID) represented as json.
|
||||
actors := map[address.Address]map[types.Actor]actorInfo{}
|
||||
addresses := map[address.Address]address.Address{}
|
||||
|
||||
// map of actor public key address to ID address
|
||||
addressToID := map[address.Address]address.Address{}
|
||||
minH := abi.ChainEpoch(math.MaxInt64)
|
||||
|
||||
// find the blockheader with the lowest height
|
||||
for _, header := range allToSync {
|
||||
if header.Height < minH {
|
||||
minH = header.Height
|
||||
}
|
||||
}
|
||||
|
||||
// toSync maps block cids to their headers and contains all block headers that will be synced in this batch
|
||||
// `maxBatch` is a tunable parameter to control how many blocks we sync per iteration.
|
||||
toSync := map[cid.Cid]*types.BlockHeader{}
|
||||
for c, header := range allToSync {
|
||||
if header.Height < minH+abi.ChainEpoch(maxBatch) {
|
||||
toSync[c] = header
|
||||
addresses[header.Miner] = address.Undef
|
||||
addressToID[header.Miner] = address.Undef
|
||||
}
|
||||
}
|
||||
// remove everything we are syncing this round from the global list of blocks to sync
|
||||
for c := range toSync {
|
||||
delete(allToSync, c)
|
||||
}
|
||||
|
||||
log.Infof("Syncing %d blocks", len(toSync))
|
||||
log.Infow("Starting Sync", "height", minH, "numBlocks", len(toSync), "maxBatch", maxBatch)
|
||||
|
||||
// map of addresses to changed actors
|
||||
var changes map[string]types.Actor
|
||||
// collect all actor state that has changes between block headers
|
||||
paDone := 0
|
||||
parmap.Par(50, parmap.MapArr(toSync), func(bh *types.BlockHeader) {
|
||||
paDone++
|
||||
@@ -146,20 +168,23 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
}
|
||||
|
||||
if len(bh.Parents) == 0 { // genesis case
|
||||
ts, _ := types.NewTipSet([]*types.BlockHeader{bh})
|
||||
aadrs, err := api.StateListActors(ctx, ts.Key())
|
||||
genesisTs, _ := types.NewTipSet([]*types.BlockHeader{bh})
|
||||
aadrs, err := api.StateListActors(ctx, genesisTs.Key())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO suspicious there is not a lot to be gained by doing this in parallel since the genesis state
|
||||
// is unlikely to contain a lot of actors, why not for loop here?
|
||||
parmap.Par(50, aadrs, func(addr address.Address) {
|
||||
act, err := api.StateGetActor(ctx, addr, ts.Key())
|
||||
act, err := api.StateGetActor(ctx, addr, genesisTs.Key())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
ast, err := api.StateReadState(ctx, act, ts.Key())
|
||||
|
||||
ast, err := api.StateReadState(ctx, addr, genesisTs.Key())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
@@ -177,9 +202,10 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
}
|
||||
actors[addr][*act] = actorInfo{
|
||||
stateroot: bh.ParentStateRoot,
|
||||
tsKey: genesisTs.Key(),
|
||||
state: string(state),
|
||||
}
|
||||
addresses[addr] = address.Undef
|
||||
addressToID[addr] = address.Undef
|
||||
alk.Unlock()
|
||||
})
|
||||
|
||||
@@ -192,12 +218,15 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
return
|
||||
}
|
||||
|
||||
changes, err := api.StateChangedActors(ctx, pts.ParentState(), bh.ParentStateRoot)
|
||||
// TODO Does this return actors that have been deleted between states?
|
||||
// collect all actors that had state changes between the blockheader parent-state and its grandparent-state.
|
||||
changes, err = api.StateChangedActors(ctx, pts.ParentState(), bh.ParentStateRoot)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// record the state of all actors that have changed
|
||||
for a, act := range changes {
|
||||
act := act
|
||||
|
||||
@@ -206,11 +235,14 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
ast, err := api.StateReadState(ctx, &act, pts.Key())
|
||||
|
||||
ast, err := api.StateReadState(ctx, addr, pts.Key())
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
state, err := json.Marshal(ast.State)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
@@ -222,15 +254,22 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
if !ok {
|
||||
actors[addr] = map[types.Actor]actorInfo{}
|
||||
}
|
||||
// a change occurred for the actor with address `addr` and state `act` at tipset `pts`.
|
||||
actors[addr][act] = actorInfo{
|
||||
stateroot: bh.ParentStateRoot,
|
||||
state: string(state),
|
||||
tsKey: pts.Key(),
|
||||
}
|
||||
addresses[addr] = address.Undef
|
||||
addressToID[addr] = address.Undef
|
||||
alk.Unlock()
|
||||
}
|
||||
})
|
||||
|
||||
// map of tipset to all miners that had a head-change at that tipset.
|
||||
minerTips := make(map[types.TipSetKey][]*minerStateInfo, len(changes))
|
||||
// heads we've seen, im being paranoid
|
||||
headsSeen := make(map[cid.Cid]struct{}, len(actors))
|
||||
|
||||
log.Infof("Getting messages")
|
||||
|
||||
msgs, incls := fetchMessages(ctx, api, toSync)
|
||||
@@ -238,70 +277,109 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
log.Infof("Resolving addresses")
|
||||
|
||||
for _, message := range msgs {
|
||||
addresses[message.To] = address.Undef
|
||||
addresses[message.From] = address.Undef
|
||||
addressToID[message.To] = address.Undef
|
||||
addressToID[message.From] = address.Undef
|
||||
}
|
||||
|
||||
parmap.Par(50, parmap.KMapArr(addresses), func(addr address.Address) {
|
||||
parmap.Par(50, parmap.KMapArr(addressToID), func(addr address.Address) {
|
||||
// FIXME: cannot use EmptyTSK here since actorID's can change during reorgs, need to use the corresponding tipset.
|
||||
// TODO: figure out a way to get the corresponding tipset...
|
||||
raddr, err := api.StateLookupID(ctx, addr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
return
|
||||
}
|
||||
alk.Lock()
|
||||
addresses[addr] = raddr
|
||||
addressToID[addr] = raddr
|
||||
alk.Unlock()
|
||||
})
|
||||
|
||||
log.Infof("Getting miner info")
|
||||
|
||||
miners := map[minerKey]*minerInfo{}
|
||||
|
||||
minerChanges := 0
|
||||
for addr, m := range actors {
|
||||
for actor, c := range m {
|
||||
if actor.Code != builtin.StorageMinerActorCodeID {
|
||||
continue
|
||||
}
|
||||
|
||||
miners[minerKey{
|
||||
// only want miner actors with head change events
|
||||
if _, found := headsSeen[actor.Head]; found {
|
||||
continue
|
||||
}
|
||||
minerChanges++
|
||||
|
||||
minerTips[c.tsKey] = append(minerTips[c.tsKey], &minerStateInfo{
|
||||
addr: addr,
|
||||
act: actor,
|
||||
stateroot: c.stateroot,
|
||||
}] = &minerInfo{}
|
||||
|
||||
state: miner.State{},
|
||||
info: miner.MinerInfo{},
|
||||
|
||||
rawPower: big.Zero(),
|
||||
qalPower: big.Zero(),
|
||||
})
|
||||
|
||||
headsSeen[actor.Head] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
parmap.Par(50, parmap.KVMapArr(miners), func(it func() (minerKey, *minerInfo)) {
|
||||
k, info := it()
|
||||
minerProcessingStartedAt := time.Now()
|
||||
log.Infow("Processing miners", "numTips", len(minerTips), "numMinerChanges", minerChanges)
|
||||
// extract the power actor state at each tipset, loop over all miners that changed at said tipset and extract their
|
||||
// claims from the power actor state. This ensures we only fetch the power actors state once for each tipset.
|
||||
parmap.Par(50, parmap.KVMapArr(minerTips), func(it func() (types.TipSetKey, []*minerStateInfo)) {
|
||||
tsKey, minerInfo := it()
|
||||
|
||||
pow, err := api.StateMinerPower(ctx, k.addr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
// Not sure why this would fail, but its probably worth continuing
|
||||
}
|
||||
info.power = pow.MinerPower.QualityAdjPower
|
||||
|
||||
sszs, err := api.StateMinerSectorCount(ctx, k.addr, types.EmptyTSK)
|
||||
// get the power actors claims map
|
||||
mp, err := getPowerActorClaimsMap(ctx, api, tsKey)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
info.psize = sszs.Pset
|
||||
info.ssize = sszs.Sset
|
||||
// Get miner raw and quality power
|
||||
for _, mi := range minerInfo {
|
||||
var claim power.Claim
|
||||
// get miner claim from power actors claim map and store if found, else the miner had no claim at
|
||||
// this tipset
|
||||
found, err := mp.Get(adt.AddrKey(mi.addr), &claim)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if found {
|
||||
mi.qalPower = claim.QualityAdjPower
|
||||
mi.rawPower = claim.RawBytePower
|
||||
}
|
||||
|
||||
astb, err := api.ChainReadObj(ctx, k.act.Head)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
// Get the miner state info
|
||||
astb, err := api.ChainReadObj(ctx, mi.act.Head)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
if err := mi.state.UnmarshalCBOR(bytes.NewReader(astb)); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
mi.info = mi.state.Info
|
||||
}
|
||||
|
||||
if err := info.state.UnmarshalCBOR(bytes.NewReader(astb)); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
info.info = info.state.Info
|
||||
// TODO Get the Sector Count
|
||||
// FIXME this is returning a lot of "address not found" errors, which is strange given that StateChangedActors
|
||||
// retruns all actors that had a state change at tipset `k.tsKey`, maybe its returning deleted miners too??
|
||||
/*
|
||||
sszs, err := api.StateMinerSectorCount(ctx, k.addr, k.tsKey)
|
||||
if err != nil {
|
||||
info.psize = 0
|
||||
info.ssize = 0
|
||||
} else {
|
||||
info.psize = sszs.Pset
|
||||
info.ssize = sszs.Sset
|
||||
}
|
||||
*/
|
||||
})
|
||||
log.Infow("Completed Miner Processing", "duration", time.Since(minerProcessingStartedAt).String(), "processed", minerChanges)
|
||||
|
||||
log.Info("Getting receipts")
|
||||
|
||||
@@ -316,7 +394,7 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
|
||||
log.Info("Storing address mapping")
|
||||
|
||||
if err := st.storeAddressMap(addresses); err != nil {
|
||||
if err := st.storeAddressMap(addressToID); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
@@ -329,8 +407,21 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
}
|
||||
|
||||
log.Info("Storing miners")
|
||||
if err := st.storeMiners(minerTips); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := st.storeMiners(miners); err != nil {
|
||||
log.Info("Storing miner sectors")
|
||||
sectorStart := time.Now()
|
||||
if err := st.storeSectors(minerTips, api); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
log.Infow("Finished storing miner sectors", "duration", time.Since(sectorStart).String())
|
||||
|
||||
log.Info("Storing miner sectors heads")
|
||||
if err := st.storeMinerSectorsHeads(minerTips, api); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
@@ -361,7 +452,7 @@ func syncHead(ctx context.Context, api api.FullNode, st *storage, ts *types.TipS
|
||||
log.Infof("Get deals")
|
||||
|
||||
// TODO: incremental, gather expired
|
||||
deals, err := api.StateMarketDeals(ctx, ts.Key())
|
||||
deals, err := api.StateMarketDeals(ctx, headTs.Key())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
@@ -451,3 +542,55 @@ func fetchParentReceipts(ctx context.Context, api api.FullNode, toSync map[cid.C
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// load the power actor state clam as an adt.Map at the tipset `ts`.
|
||||
func getPowerActorClaimsMap(ctx context.Context, api api.FullNode, ts types.TipSetKey) (*adt.Map, error) {
|
||||
powerActor, err := api.StateGetActor(ctx, builtin.StoragePowerActorAddr, ts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
powerRaw, err := api.ChainReadObj(ctx, powerActor.Head)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var powerActorState power.State
|
||||
if err := powerActorState.UnmarshalCBOR(bytes.NewReader(powerRaw)); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal power actor state: %w", err)
|
||||
}
|
||||
|
||||
s := &apiIpldStore{ctx, api}
|
||||
return adt.AsMap(s, powerActorState.Claims)
|
||||
}
|
||||
|
||||
// require for AMT and HAMT access
|
||||
// TODO extract this to a common location in lotus and reuse the code
|
||||
type apiIpldStore struct {
|
||||
ctx context.Context
|
||||
api api.FullNode
|
||||
}
|
||||
|
||||
func (ht *apiIpldStore) Context() context.Context {
|
||||
return ht.ctx
|
||||
}
|
||||
|
||||
func (ht *apiIpldStore) Get(ctx context.Context, c cid.Cid, out interface{}) error {
|
||||
raw, err := ht.api.ChainReadObj(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cu, ok := out.(cbg.CBORUnmarshaler)
|
||||
if ok {
|
||||
if err := cu.UnmarshalCBOR(bytes.NewReader(raw)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Object does not implement CBORUnmarshaler: %T", out)
|
||||
}
|
||||
|
||||
func (ht *apiIpldStore) Put(ctx context.Context, v interface{}) (cid.Cid, error) {
|
||||
return cid.Undef, fmt.Errorf("Put is not implemented on apiIpldStore")
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ var watchHeadCmd = &cli.Command{
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "interval",
|
||||
Value: build.BlockDelay,
|
||||
Value: int(build.BlockDelaySecs),
|
||||
Usage: "interval in seconds between chain head checks",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
@@ -72,8 +72,9 @@ var watchHeadCmd = &cli.Command{
|
||||
Usage: "systemd unit name to restart on health check failure",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "api-timeout",
|
||||
Value: build.BlockDelay,
|
||||
Name: "api-timeout",
|
||||
// TODO: this default value seems spurious.
|
||||
Value: int(build.BlockDelaySecs),
|
||||
Usage: "timeout between API retries",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
@@ -236,7 +237,7 @@ func waitForSyncComplete(ctx context.Context, a api.FullNode, r int, t time.Dura
|
||||
return err
|
||||
}
|
||||
|
||||
if time.Now().Unix()-int64(head.MinTimestamp()) < build.BlockDelay {
|
||||
if time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,14 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
@@ -117,11 +120,19 @@ var runCmd = &cli.Command{
|
||||
}
|
||||
|
||||
// Connect to storage-miner
|
||||
|
||||
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting miner api: %w", err)
|
||||
var nodeApi api.StorageMiner
|
||||
var closer func()
|
||||
var err error
|
||||
for {
|
||||
nodeApi, closer, err = lcli.GetStorageMinerAPI(cctx)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
fmt.Printf("\r\x1b[0KConnecting to miner API... (%s)", err)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
defer closer()
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
@@ -136,6 +147,8 @@ var runCmd = &cli.Command{
|
||||
}
|
||||
log.Infof("Remote version %s", v)
|
||||
|
||||
watchMinerConn(ctx, cctx, nodeApi)
|
||||
|
||||
// Check params
|
||||
|
||||
act, err := nodeApi.ActorAddress(ctx)
|
||||
@@ -317,3 +330,42 @@ var runCmd = &cli.Command{
|
||||
return srv.Serve(nl)
|
||||
},
|
||||
}
|
||||
|
||||
func watchMinerConn(ctx context.Context, cctx *cli.Context, nodeApi api.StorageMiner) {
|
||||
go func() {
|
||||
closing, err := nodeApi.Closing(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get remote closing channel: %+v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-closing:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return // graceful shutdown
|
||||
}
|
||||
|
||||
log.Warnf("Connection with miner node lost, restarting")
|
||||
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
log.Errorf("getting executable for auto-restart: %+v", err)
|
||||
}
|
||||
|
||||
log.Sync()
|
||||
|
||||
// TODO: there are probably cleaner/more graceful ways to restart,
|
||||
// but this is good enough for now (FSM can recover from the mess this creates)
|
||||
if err := syscall.Exec(exe, []string{exe, "run",
|
||||
fmt.Sprintf("--address=%s", cctx.String("address")),
|
||||
fmt.Sprintf("--no-local-storage=%t", cctx.Bool("no-local-storage")),
|
||||
fmt.Sprintf("--precommit1=%t", cctx.Bool("precommit1")),
|
||||
fmt.Sprintf("--precommit2=%t", cctx.Bool("precommit2")),
|
||||
fmt.Sprintf("--commit=%t", cctx.Bool("commit")),
|
||||
}, os.Environ()); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ var genesisAddMinerCmd = &cli.Command{
|
||||
log.Infof("Giving %s some initial balance", miner.Owner)
|
||||
template.Accounts = append(template.Accounts, genesis.Actor{
|
||||
Type: genesis.TAccount,
|
||||
Balance: big.Mul(big.NewInt(50_000_000), big.NewInt(build.FilecoinPrecision)),
|
||||
Balance: big.Mul(big.NewInt(50_000_000), big.NewInt(int64(build.FilecoinPrecision))),
|
||||
Meta: (&genesis.AccountMeta{Owner: miner.Owner}).ActorMeta(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,12 +33,7 @@ import (
|
||||
|
||||
var log = logging.Logger("preseal")
|
||||
|
||||
func PreSeal(maddr address.Address, pt abi.RegisteredProof, offset abi.SectorNumber, sectors int, sbroot string, preimage []byte, key *types.KeyInfo) (*genesis.Miner, *types.KeyInfo, error) {
|
||||
spt, err := pt.RegisteredSealProof()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
func PreSeal(maddr address.Address, spt abi.RegisteredSealProof, offset abi.SectorNumber, sectors int, sbroot string, preimage []byte, key *types.KeyInfo) (*genesis.Miner, *types.KeyInfo, error) {
|
||||
mid, err := address.IDFromAddress(maddr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -63,7 +58,7 @@ func PreSeal(maddr address.Address, pt abi.RegisteredProof, offset abi.SectorNum
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ssize, err := pt.SectorSize()
|
||||
ssize, err := spt.SectorSize()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -93,16 +88,20 @@ func PreSeal(maddr address.Address, pt abi.RegisteredProof, offset abi.SectorNum
|
||||
return nil, nil, xerrors.Errorf("commit: %w", err)
|
||||
}
|
||||
|
||||
if err := sb.FinalizeSector(context.TODO(), sid); err != nil {
|
||||
if err := sb.FinalizeSector(context.TODO(), sid, nil); err != nil {
|
||||
return nil, nil, xerrors.Errorf("trim cache: %w", err)
|
||||
}
|
||||
|
||||
if err := cleanupUnsealed(sbfs, sid); err != nil {
|
||||
return nil, nil, xerrors.Errorf("remove unsealed file: %w", err)
|
||||
}
|
||||
|
||||
log.Warn("PreCommitOutput: ", sid, cids.Sealed, cids.Unsealed)
|
||||
sealedSectors = append(sealedSectors, &genesis.PreSeal{
|
||||
CommR: cids.Sealed,
|
||||
CommD: cids.Unsealed,
|
||||
SectorID: sid.Number,
|
||||
ProofType: pt,
|
||||
ProofType: spt,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -166,6 +165,16 @@ func PreSeal(maddr address.Address, pt abi.RegisteredProof, offset abi.SectorNum
|
||||
return miner, &minerAddr.KeyInfo, nil
|
||||
}
|
||||
|
||||
func cleanupUnsealed(sbfs *basicfs.Provider, sid abi.SectorID) error {
|
||||
paths, done, err := sbfs.AcquireSector(context.TODO(), sid, stores.FTUnsealed, stores.FTNone, stores.PathSealing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer done()
|
||||
|
||||
return os.Remove(paths.Unsealed)
|
||||
}
|
||||
|
||||
func WriteGenesisMiner(maddr address.Address, sbroot string, gm *genesis.Miner, key *types.KeyInfo) error {
|
||||
output := map[string]genesis.Miner{
|
||||
maddr.String(): *gm,
|
||||
|
||||
+315
-34
@@ -1,8 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -11,77 +13,356 @@ import (
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
_ "github.com/filecoin-project/lotus/lib/sigs/bls"
|
||||
_ "github.com/filecoin-project/lotus/lib/sigs/secp"
|
||||
"github.com/libp2p/go-libp2p-core/crypto"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/wallet"
|
||||
"github.com/filecoin-project/lotus/node/modules/lp2p"
|
||||
"github.com/filecoin-project/lotus/node/repo"
|
||||
|
||||
_ "github.com/filecoin-project/lotus/lib/sigs/bls"
|
||||
_ "github.com/filecoin-project/lotus/lib/sigs/secp"
|
||||
)
|
||||
|
||||
type walletInfo struct {
|
||||
var validTypes = []string{wallet.KTBLS, wallet.KTSecp256k1, lp2p.KTLibp2pHost}
|
||||
|
||||
type keyInfoOutput struct {
|
||||
Type string
|
||||
Address string
|
||||
PublicKey string
|
||||
}
|
||||
|
||||
func (wi walletInfo) String() string {
|
||||
bs, _ := json.Marshal(wi)
|
||||
return string(bs)
|
||||
var keyinfoCmd = &cli.Command{
|
||||
Name: "keyinfo",
|
||||
Usage: "work with lotus keyinfo files (wallets and libp2p host keys)",
|
||||
Description: `The subcommands of keyinfo provide helpful tools for working with keyinfo files without
|
||||
having to run the lotus daemon.`,
|
||||
Subcommands: []*cli.Command{
|
||||
keyinfoNewCmd,
|
||||
keyinfoInfoCmd,
|
||||
keyinfoImportCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var keyinfoCmd = &cli.Command{
|
||||
Name: "keyinfo",
|
||||
Description: "decode a keyinfo",
|
||||
var keyinfoImportCmd = &cli.Command{
|
||||
Name: "import",
|
||||
Usage: "import a keyinfo file into a lotus repository",
|
||||
Description: `The import command provides a way to import keyfiles into a lotus repository
|
||||
without running the daemon.
|
||||
|
||||
Note: The LOTUS_PATH directory must be created. This command will not create this directory for you.
|
||||
|
||||
Examples
|
||||
|
||||
env LOTUS_PATH=/var/lib/lotus lotus-shed keyinfo import libp2p-host.keyinfo`,
|
||||
Action: func(cctx *cli.Context) error {
|
||||
flagRepo := cctx.String("repo")
|
||||
|
||||
var input io.Reader
|
||||
if cctx.Args().Len() == 0 {
|
||||
input = os.Stdin
|
||||
} else {
|
||||
var err error
|
||||
input, err = os.Open(cctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
encoded, err := ioutil.ReadAll(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decoded, err := hex.DecodeString(strings.TrimSpace(string(encoded)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var keyInfo types.KeyInfo
|
||||
if err := json.Unmarshal(decoded, &keyInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fsrepo, err := repo.NewFS(flagRepo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lkrepo, err := fsrepo.Lock(repo.FullNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer lkrepo.Close()
|
||||
|
||||
keystore, err := lkrepo.KeyStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch keyInfo.Type {
|
||||
case lp2p.KTLibp2pHost:
|
||||
if err := keystore.Put(lp2p.KLibp2pHost, keyInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sk, err := crypto.UnmarshalPrivateKey(keyInfo.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerid, err := peer.IDFromPrivateKey(sk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", peerid.String())
|
||||
|
||||
break
|
||||
case wallet.KTSecp256k1:
|
||||
case wallet.KTBLS:
|
||||
w, err := wallet.NewWallet(keystore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addr, err := w.Import(&keyInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", addr.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var keyinfoInfoCmd = &cli.Command{
|
||||
Name: "info",
|
||||
Usage: "print information about a keyinfo file",
|
||||
Description: `The info command prints additional information about a key which can't easily
|
||||
be retrieved by inspecting the file itself.
|
||||
|
||||
The 'format' flag takes a golang text/template template as its value.
|
||||
|
||||
The following fields can be retrived through this command
|
||||
Type
|
||||
Address
|
||||
PublicKey
|
||||
|
||||
The PublicKey value will be printed base64 encoded using golangs StdEncoding
|
||||
|
||||
Examples
|
||||
|
||||
Retreive the address of a lotus wallet
|
||||
lotus-shed keyinfo info --format '{{ .Address }}' wallet.keyinfo
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "format",
|
||||
Value: "{{.Address}}",
|
||||
Usage: "Format to output",
|
||||
Value: "{{ .Type }} {{ .Address }}",
|
||||
Usage: "specify which output columns to print",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
format := cctx.String("format")
|
||||
|
||||
var input io.Reader
|
||||
|
||||
if cctx.Args().Len() == 0 {
|
||||
input = os.Stdin
|
||||
} else {
|
||||
input = strings.NewReader(cctx.Args().First())
|
||||
var err error
|
||||
input, err = os.Open(cctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadAll(input)
|
||||
|
||||
data, err := hex.DecodeString(strings.TrimSpace(string(bytes)))
|
||||
encoded, err := ioutil.ReadAll(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ki types.KeyInfo
|
||||
if err := json.Unmarshal(data, &ki); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := wallet.NewKey(ki)
|
||||
decoded, err := hex.DecodeString(strings.TrimSpace(string(encoded)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bs, err := json.Marshal(key)
|
||||
var keyInfo types.KeyInfo
|
||||
if err := json.Unmarshal(decoded, &keyInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var kio keyInfoOutput
|
||||
|
||||
switch keyInfo.Type {
|
||||
case lp2p.KTLibp2pHost:
|
||||
kio.Type = keyInfo.Type
|
||||
|
||||
sk, err := crypto.UnmarshalPrivateKey(keyInfo.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pk := sk.GetPublic()
|
||||
|
||||
peerid, err := peer.IDFromPrivateKey(sk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkBytes, err := pk.Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kio.Address = peerid.String()
|
||||
kio.PublicKey = base64.StdEncoding.EncodeToString(pkBytes)
|
||||
|
||||
break
|
||||
case wallet.KTSecp256k1:
|
||||
case wallet.KTBLS:
|
||||
kio.Type = keyInfo.Type
|
||||
|
||||
key, err := wallet.NewKey(keyInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kio.Address = key.Address.String()
|
||||
kio.PublicKey = base64.StdEncoding.EncodeToString(key.PublicKey)
|
||||
}
|
||||
|
||||
tmpl, err := template.New("output").Parse(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var wi walletInfo
|
||||
if err := json.Unmarshal(bs, &wi); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("").Parse(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tmpl.Execute(os.Stdout, wi)
|
||||
return tmpl.Execute(os.Stdout, kio)
|
||||
},
|
||||
}
|
||||
|
||||
var keyinfoNewCmd = &cli.Command{
|
||||
Name: "new",
|
||||
Usage: "create a new keyinfo file of the provided type",
|
||||
ArgsUsage: "[bls|secp256k1|libp2p-host]",
|
||||
Description: `Keyinfo files are base16 encoded json structures containing a type
|
||||
string value, and a base64 encoded private key.
|
||||
|
||||
Both the bls and secp256k1 keyfiles can be imported into a running lotus daemon using
|
||||
the 'lotus wallet import' command. Or imported to a non-running / unitialized repo using
|
||||
the 'lotus-shed keyinfo import' command. Libp2p host keys can only be imported using lotus-shed
|
||||
as lotus itself does not provide this functionality at the moment.`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "output",
|
||||
Value: "<type>-<addr>.keyinfo",
|
||||
Usage: "output file formt",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "silent",
|
||||
Value: false,
|
||||
Usage: "do not print the address to stdout",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
if !cctx.Args().Present() {
|
||||
return fmt.Errorf("please specify a type to generate")
|
||||
}
|
||||
|
||||
keyType := cctx.Args().First()
|
||||
flagOutput := cctx.String("output")
|
||||
|
||||
if i := SliceIndex(len(validTypes), func(i int) bool {
|
||||
if keyType == validTypes[i] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}); i == -1 {
|
||||
return fmt.Errorf("invalid key type argument provided '%s'", keyType)
|
||||
}
|
||||
|
||||
keystore := wallet.NewMemKeyStore()
|
||||
|
||||
var keyAddr string
|
||||
var keyInfo types.KeyInfo
|
||||
|
||||
switch keyType {
|
||||
case lp2p.KTLibp2pHost:
|
||||
sk, err := lp2p.PrivKey(keystore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ki, err := keystore.Get(lp2p.KLibp2pHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerid, err := peer.IDFromPrivateKey(sk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyAddr = peerid.String()
|
||||
keyInfo = ki
|
||||
|
||||
break
|
||||
case wallet.KTSecp256k1:
|
||||
case wallet.KTBLS:
|
||||
key, err := wallet.GenerateKey(wallet.ActSigType(keyType))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyAddr = key.Address.String()
|
||||
keyInfo = key.KeyInfo
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
filename := flagOutput
|
||||
filename = strings.ReplaceAll(filename, "<addr>", keyAddr)
|
||||
filename = strings.ReplaceAll(filename, "<type>", keyType)
|
||||
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := file.Close(); err != nil {
|
||||
log.Warnf("failed to close output file: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
bytes, err := json.Marshal(keyInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encoded := hex.EncodeToString(bytes)
|
||||
if _, err := file.Write([]byte(encoded)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cctx.Bool("silent") {
|
||||
fmt.Println(keyAddr)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func SliceIndex(length int, fn func(i int) bool) int {
|
||||
for i := 0; i < length; i++ {
|
||||
if fn(i) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ func main() {
|
||||
base16Cmd,
|
||||
bitFieldCmd,
|
||||
keyinfoCmd,
|
||||
peerkeyCmd,
|
||||
noncefix,
|
||||
bigIntParseCmd,
|
||||
staterootStatsCmd,
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/node/modules/lp2p"
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
)
|
||||
|
||||
type keystore struct {
|
||||
set bool
|
||||
info types.KeyInfo
|
||||
}
|
||||
|
||||
func (ks *keystore) Put(name string, info types.KeyInfo) error {
|
||||
ks.info = info
|
||||
ks.set = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ks *keystore) Get(name string) (types.KeyInfo, error) {
|
||||
if !ks.set {
|
||||
return types.KeyInfo{}, types.ErrKeyInfoNotFound
|
||||
}
|
||||
|
||||
return ks.info, nil
|
||||
}
|
||||
|
||||
func (ks *keystore) Delete(name string) error {
|
||||
panic("Implement me")
|
||||
}
|
||||
|
||||
func (ks *keystore) List() ([]string, error) {
|
||||
panic("Implement me")
|
||||
}
|
||||
|
||||
var peerkeyCmd = &cli.Command{
|
||||
Name: "peerkey",
|
||||
Description: "create libp2p host key",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "output",
|
||||
Value: "<peerid>.peerkey",
|
||||
Usage: "Output file format",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "silent",
|
||||
Value: false,
|
||||
Usage: "Do not print peerid at end",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
output := cctx.String("output")
|
||||
ks := keystore{}
|
||||
|
||||
sk, err := lp2p.PrivKey(&ks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bs, err := json.Marshal(ks.info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerid, err := peer.IDFromPrivateKey(sk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output = strings.ReplaceAll(output, "<peerid>", peerid.String())
|
||||
|
||||
f, err := os.Create(output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
log.Warnf("failed to close output file: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := f.Write(bs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cctx.Bool("silent") {
|
||||
fmt.Println(peerid.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -88,7 +88,7 @@ var verifySealProofCmd = &cli.Command{
|
||||
Number: snum,
|
||||
},
|
||||
SealedCID: commr,
|
||||
RegisteredProof: abi.RegisteredProof(cctx.Int64("proof-type")),
|
||||
SealProof: abi.RegisteredSealProof(cctx.Int64("proof-type")),
|
||||
Proof: proof,
|
||||
DealIDs: nil,
|
||||
Randomness: abi.SealRandomness(ticket),
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
)
|
||||
|
||||
var actorCmd = &cli.Command{
|
||||
Name: "actor",
|
||||
Usage: "manipulate the miner actor",
|
||||
Subcommands: []*cli.Command{
|
||||
actorSetAddrsCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var actorSetAddrsCmd = &cli.Command{
|
||||
Name: "set-addrs",
|
||||
Usage: "set addresses that your miner can be publically dialed on",
|
||||
Flags: []cli.Flag{
|
||||
&cli.Int64Flag{
|
||||
Name: "gas-limit",
|
||||
Usage: "set gas limit",
|
||||
Value: 100000,
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
nodeAPI, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
api, acloser, err := lcli.GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer acloser()
|
||||
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
var addrs []abi.Multiaddrs
|
||||
for _, a := range cctx.Args().Slice() {
|
||||
maddr, err := ma.NewMultiaddr(a)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse %q as a multiaddr: %w", a, err)
|
||||
}
|
||||
|
||||
addrs = append(addrs, maddr.Bytes())
|
||||
}
|
||||
|
||||
maddr, err := nodeAPI.ActorAddress(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
minfo, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params, err := actors.SerializeParams(&miner.ChangeMultiaddrsParams{NewMultiaddrs: addrs})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gasLimit := cctx.Int64("gas-limit")
|
||||
|
||||
smsg, err := api.MpoolPushMessage(ctx, &types.Message{
|
||||
To: maddr,
|
||||
From: minfo.Worker,
|
||||
Value: types.NewInt(0),
|
||||
GasPrice: types.NewInt(1),
|
||||
GasLimit: gasLimit,
|
||||
Method: 18,
|
||||
Params: params,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Requested multiaddrs change in message %s\n", smsg.Cid())
|
||||
return nil
|
||||
|
||||
},
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/power"
|
||||
sealing "github.com/filecoin-project/storage-fsm"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
@@ -119,16 +120,20 @@ var infoCmd = &cli.Command{
|
||||
faultyPercentage)
|
||||
}
|
||||
|
||||
expWinChance := float64(types.BigMul(qpercI, types.NewInt(build.BlocksPerEpoch)).Int64()) / 1000000
|
||||
if expWinChance > 0 {
|
||||
if expWinChance > 1 {
|
||||
expWinChance = 1
|
||||
}
|
||||
winRate := time.Duration(float64(time.Second*build.BlockDelay) / expWinChance)
|
||||
winPerDay := float64(time.Hour*24) / float64(winRate)
|
||||
if pow.MinerPower.RawBytePower.LessThan(power.ConsensusMinerMinPower) {
|
||||
fmt.Print("Below minimum power threshold, no blocks will be won")
|
||||
} else {
|
||||
expWinChance := float64(types.BigMul(qpercI, types.NewInt(build.BlocksPerEpoch)).Int64()) / 1000000
|
||||
if expWinChance > 0 {
|
||||
if expWinChance > 1 {
|
||||
expWinChance = 1
|
||||
}
|
||||
winRate := time.Duration(float64(time.Second*time.Duration(build.BlockDelaySecs)) / expWinChance)
|
||||
winPerDay := float64(time.Hour*24) / float64(winRate)
|
||||
|
||||
fmt.Print("Expected block win rate: ")
|
||||
color.Blue("%.4f/day (every %s)", winPerDay, winRate.Truncate(time.Second))
|
||||
fmt.Print("Expected block win rate: ")
|
||||
color.Blue("%.4f/day (every %s)", winPerDay, winRate.Truncate(time.Second))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
@@ -460,7 +460,7 @@ func storageMinerInit(ctx context.Context, cctx *cli.Context, api lapi.FullNode,
|
||||
}
|
||||
|
||||
if cerr != nil {
|
||||
return xerrors.Errorf("failed to configure storage miner: %w", err)
|
||||
return xerrors.Errorf("failed to configure storage miner: %w", cerr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@ func main() {
|
||||
lotuslog.SetupLogLevels()
|
||||
|
||||
local := []*cli.Command{
|
||||
dealsCmd,
|
||||
actorCmd,
|
||||
storageDealsCmd,
|
||||
retrievalDealsCmd,
|
||||
infoCmd,
|
||||
initCmd,
|
||||
rewardsCmd,
|
||||
@@ -30,7 +32,6 @@ func main() {
|
||||
stopCmd,
|
||||
sectorsCmd,
|
||||
storageCmd,
|
||||
setPriceCmd,
|
||||
workersCmd,
|
||||
provingCmd,
|
||||
}
|
||||
|
||||
@@ -1,47 +1,301 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipfs/go-cidutil/cidenc"
|
||||
"github.com/multiformats/go-multibase"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-fil-markets/storagemarket"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var setPriceCmd = &cli.Command{
|
||||
Name: "set-price",
|
||||
Usage: "Set price that miner will accept storage deals at (FIL / GiB / Epoch)",
|
||||
Flags: []cli.Flag{},
|
||||
var CidBaseFlag = cli.StringFlag{
|
||||
Name: "cid-base",
|
||||
Hidden: true,
|
||||
Value: "base32",
|
||||
Usage: "Multibase encoding used for version 1 CIDs in output.",
|
||||
DefaultText: "base32",
|
||||
}
|
||||
|
||||
// GetCidEncoder returns an encoder using the `cid-base` flag if provided, or
|
||||
// the default (Base32) encoder if not.
|
||||
func GetCidEncoder(cctx *cli.Context) (cidenc.Encoder, error) {
|
||||
val := cctx.String("cid-base")
|
||||
|
||||
e := cidenc.Encoder{Base: multibase.MustNewEncoder(multibase.Base32)}
|
||||
|
||||
if val != "" {
|
||||
var err error
|
||||
e.Base, err = multibase.EncoderByName(val)
|
||||
if err != nil {
|
||||
return e, err
|
||||
}
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
var storageDealSelectionCmd = &cli.Command{
|
||||
Name: "selection",
|
||||
Usage: "Configure acceptance criteria for storage deal proposals",
|
||||
Subcommands: []*cli.Command{
|
||||
storageDealSelectionShowCmd,
|
||||
storageDealSelectionResetCmd,
|
||||
storageDealSelectionRejectCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var storageDealSelectionShowCmd = &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List storage deal proposal selection criteria",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
smapi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
onlineOk, err := smapi.DealsConsiderOnlineStorageDeals(lcli.DaemonContext(cctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
offlineOk, err := smapi.DealsConsiderOfflineStorageDeals(lcli.DaemonContext(cctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("considering online storage deals: %t\n", onlineOk)
|
||||
fmt.Printf("considering offline storage deals: %t\n", offlineOk)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var storageDealSelectionResetCmd = &cli.Command{
|
||||
Name: "reset",
|
||||
Usage: "Reset storage deal proposal selection criteria to default values",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
smapi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
err = smapi.DealsSetConsiderOnlineStorageDeals(lcli.DaemonContext(cctx), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = smapi.DealsSetConsiderOfflineStorageDeals(lcli.DaemonContext(cctx), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var storageDealSelectionRejectCmd = &cli.Command{
|
||||
Name: "reject",
|
||||
Usage: "Configure criteria which necessitate automatic rejection",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "online",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "offline",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
smapi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
if cctx.Bool("online") {
|
||||
err = smapi.DealsSetConsiderOnlineStorageDeals(lcli.DaemonContext(cctx), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if cctx.Bool("offline") {
|
||||
err = smapi.DealsSetConsiderOfflineStorageDeals(lcli.DaemonContext(cctx), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var setAskCmd = &cli.Command{
|
||||
Name: "set-ask",
|
||||
Usage: "Configure the miner's ask",
|
||||
Flags: []cli.Flag{
|
||||
&cli.Uint64Flag{
|
||||
Name: "price",
|
||||
Usage: "Set the price of the ask (specified as FIL / GiB / Epoch) to `PRICE`",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "duration",
|
||||
Usage: "Set duration of ask (a quantity of time after which the ask expires) `DURATION`",
|
||||
DefaultText: "720h0m0s",
|
||||
Value: "720h0m0s",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "min-piece-size",
|
||||
Usage: "Set minimum piece size (w/bit-padding, in bytes) in ask to `SIZE`",
|
||||
DefaultText: "256B",
|
||||
Value: "256B",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "max-piece-size",
|
||||
Usage: "Set maximum piece size (w/bit-padding, in bytes) in ask to `SIZE`",
|
||||
DefaultText: "miner sector size",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
ctx := lcli.DaemonContext(cctx)
|
||||
|
||||
api, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
ctx := lcli.DaemonContext(cctx)
|
||||
pri := types.NewInt(cctx.Uint64("price"))
|
||||
|
||||
if !cctx.Args().Present() {
|
||||
return fmt.Errorf("must specify price to set")
|
||||
dur, err := time.ParseDuration(cctx.String("duration"))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("cannot parse duration: %w", err)
|
||||
}
|
||||
|
||||
fp, err := types.ParseFIL(cctx.Args().First())
|
||||
qty := dur.Seconds() / float64(build.BlockDelaySecs)
|
||||
|
||||
min, err := units.RAMInBytes(cctx.String("min-piece-size"))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("cannot parse min-piece-size to quantity of bytes: %w", err)
|
||||
}
|
||||
|
||||
if min < 256 {
|
||||
return xerrors.New("minimum piece size (w/bit-padding) is 256B")
|
||||
}
|
||||
|
||||
max, err := units.RAMInBytes(cctx.String("max-piece-size"))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("cannot parse max-piece-size to quantity of bytes: %w", err)
|
||||
}
|
||||
|
||||
maddr, err := api.ActorAddress(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return api.MarketSetPrice(ctx, types.BigInt(fp))
|
||||
ssize, err := api.ActorSectorSize(ctx, maddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
smax := int64(ssize)
|
||||
|
||||
if max == 0 {
|
||||
max = smax
|
||||
}
|
||||
|
||||
if max > smax {
|
||||
return xerrors.Errorf("max piece size (w/bit-padding) %s cannot exceed miner sector size %s", types.SizeStr(types.NewInt(uint64(max))), types.SizeStr(types.NewInt(uint64(smax))))
|
||||
}
|
||||
|
||||
return api.MarketSetAsk(ctx, pri, abi.ChainEpoch(qty), abi.PaddedPieceSize(min), abi.PaddedPieceSize(max))
|
||||
},
|
||||
}
|
||||
|
||||
var dealsCmd = &cli.Command{
|
||||
Name: "deals",
|
||||
Usage: "interact with your deals",
|
||||
var getAskCmd = &cli.Command{
|
||||
Name: "get-ask",
|
||||
Usage: "Print the miner's ask",
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
ctx := lcli.DaemonContext(cctx)
|
||||
|
||||
fnapi, closer, err := lcli.GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
smapi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
sask, err := smapi.MarketGetAsk(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ask *storagemarket.StorageAsk
|
||||
if sask != nil && sask.Ask != nil {
|
||||
ask = sask.Ask
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0)
|
||||
fmt.Fprintf(w, "Price per GiB / Epoch\tMin. Piece Size (w/bit-padding)\tMax. Piece Size (w/bit-padding)\tExpiry (Epoch)\tExpiry (Appx. Rem. Time)\tSeq. No.\n")
|
||||
if ask == nil {
|
||||
fmt.Fprintf(w, "<miner does not have an ask>\n")
|
||||
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
head, err := fnapi.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dlt := ask.Expiry - head.Height()
|
||||
rem := "<expired>"
|
||||
if dlt > 0 {
|
||||
rem = (time.Second * time.Duration(int64(dlt)*int64(build.BlockDelaySecs))).String()
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\t%d\n", ask.Price, types.SizeStr(types.NewInt(uint64(ask.MinPieceSize))), types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize))), ask.Expiry, rem, ask.SeqNo)
|
||||
|
||||
return w.Flush()
|
||||
},
|
||||
}
|
||||
|
||||
var storageDealsCmd = &cli.Command{
|
||||
Name: "storage-deals",
|
||||
Usage: "Manage storage deals and related configuration",
|
||||
Subcommands: []*cli.Command{
|
||||
dealsImportDataCmd,
|
||||
dealsListCmd,
|
||||
storageDealSelectionCmd,
|
||||
setAskCmd,
|
||||
getAskCmd,
|
||||
setBlocklistCmd,
|
||||
getBlocklistCmd,
|
||||
resetBlocklistCmd,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -100,3 +354,96 @@ var dealsListCmd = &cli.Command{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var getBlocklistCmd = &cli.Command{
|
||||
Name: "get-blocklist",
|
||||
Usage: "List the contents of the storage miner's piece CID blocklist",
|
||||
Flags: []cli.Flag{
|
||||
&CidBaseFlag,
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
blocklist, err := api.DealsPieceCidBlocklist(lcli.DaemonContext(cctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encoder, err := GetCidEncoder(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for idx := range blocklist {
|
||||
fmt.Println(encoder.Encode(blocklist[idx]))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var setBlocklistCmd = &cli.Command{
|
||||
Name: "set-blocklist",
|
||||
Usage: "Set the storage miner's list of blocklisted piece CIDs",
|
||||
ArgsUsage: "[<path-of-file-containing-newline-delimited-piece-CIDs> (optional, will read from stdin if omitted)]",
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
if cctx.Args().Present() && cctx.Args().First() != "-" {
|
||||
absPath, err := filepath.Abs(cctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Open(absPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer file.Close() //nolint:errcheck
|
||||
|
||||
scanner = bufio.NewScanner(file)
|
||||
}
|
||||
|
||||
var blocklist []cid.Cid
|
||||
for scanner.Scan() {
|
||||
decoded, err := cid.Decode(scanner.Text())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blocklist = append(blocklist, decoded)
|
||||
}
|
||||
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return api.DealsSetPieceCidBlocklist(lcli.DaemonContext(cctx), blocklist)
|
||||
},
|
||||
}
|
||||
|
||||
var resetBlocklistCmd = &cli.Command{
|
||||
Name: "reset-blocklist",
|
||||
Usage: "Remove all entries from the storage miner's piece CID blocklist",
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
api, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
return api.DealsSetPieceCidBlocklist(lcli.DaemonContext(cctx), []cid.Cid{})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -21,15 +21,86 @@ import (
|
||||
)
|
||||
|
||||
var provingCmd = &cli.Command{
|
||||
Name: "proving",
|
||||
Name: "proving",
|
||||
Usage: "View proving information",
|
||||
Subcommands: []*cli.Command{
|
||||
provingInfoCmd,
|
||||
provingDeadlinesCmd,
|
||||
provingFaultsCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var provingFaultsCmd = &cli.Command{
|
||||
Name: "faults",
|
||||
Usage: "View the currently known proving faulty sectors information",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
api, acloser, err := lcli.GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer acloser()
|
||||
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
maddr, err := nodeApi.ActorAddress(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting actor address: %w", err)
|
||||
}
|
||||
|
||||
var mas miner.State
|
||||
{
|
||||
mact, err := api.StateGetActor(ctx, maddr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rmas, err := api.ChainReadObj(ctx, mact.Head)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mas.UnmarshalCBOR(bytes.NewReader(rmas)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
faults, err := mas.Faults.All(100000000000)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(faults) == 0 {
|
||||
fmt.Println("no faulty sectors")
|
||||
}
|
||||
head, err := api.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting chain head: %w", err)
|
||||
}
|
||||
deadlines, err := api.StateMinerDeadlines(ctx, maddr, head.Key())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting miner deadlines: %w", err)
|
||||
}
|
||||
tw := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0)
|
||||
_, _ = fmt.Fprintln(tw, "deadline\tsectors")
|
||||
for deadline, sectors := range deadlines.Due {
|
||||
intersectSectors, _ := bitfield.IntersectBitField(sectors, mas.Faults)
|
||||
if intersectSectors != nil {
|
||||
allSectors, _ := intersectSectors.All(100000000000)
|
||||
for _, num := range allSectors {
|
||||
_, _ = fmt.Fprintf(tw, "%d\t%d\n", deadline, num)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return tw.Flush()
|
||||
},
|
||||
}
|
||||
|
||||
var provingInfoCmd = &cli.Command{
|
||||
Name: "info",
|
||||
Name: "info",
|
||||
Usage: "View current state information",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
@@ -140,18 +211,19 @@ var provingInfoCmd = &cli.Command{
|
||||
func epochTime(curr, e abi.ChainEpoch) string {
|
||||
switch {
|
||||
case curr > e:
|
||||
return fmt.Sprintf("%d (%s ago)", e, time.Second*time.Duration(build.BlockDelay*(curr-e)))
|
||||
return fmt.Sprintf("%d (%s ago)", e, time.Second*time.Duration(int64(build.BlockDelaySecs)*int64(curr-e)))
|
||||
case curr == e:
|
||||
return fmt.Sprintf("%d (now)", e)
|
||||
case curr < e:
|
||||
return fmt.Sprintf("%d (in %s)", e, time.Second*time.Duration(build.BlockDelay*(e-curr)))
|
||||
return fmt.Sprintf("%d (in %s)", e, time.Second*time.Duration(int64(build.BlockDelaySecs)*int64(e-curr)))
|
||||
}
|
||||
|
||||
panic("math broke")
|
||||
}
|
||||
|
||||
var provingDeadlinesCmd = &cli.Command{
|
||||
Name: "deadlines",
|
||||
Name: "deadlines",
|
||||
Usage: "View the current proving period deadlines information",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var retrievalDealsCmd = &cli.Command{
|
||||
Name: "retrieval-deals",
|
||||
Usage: "Manage retrieval deals and related configuration",
|
||||
Subcommands: []*cli.Command{
|
||||
retrievalDealSelectionCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var retrievalDealSelectionCmd = &cli.Command{
|
||||
Name: "selection",
|
||||
Usage: "Configure acceptance criteria for retrieval deal proposals",
|
||||
Subcommands: []*cli.Command{
|
||||
retrievalDealSelectionShowCmd,
|
||||
retrievalDealSelectionResetCmd,
|
||||
retrievalDealSelectionRejectCmd,
|
||||
},
|
||||
}
|
||||
|
||||
var retrievalDealSelectionShowCmd = &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List retrieval deal proposal selection criteria",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
smapi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
onlineOk, err := smapi.DealsConsiderOnlineRetrievalDeals(lcli.DaemonContext(cctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
offlineOk, err := smapi.DealsConsiderOfflineRetrievalDeals(lcli.DaemonContext(cctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("considering online retrieval deals: %t\n", onlineOk)
|
||||
fmt.Printf("considering offline retrieval deals: %t\n", offlineOk)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var retrievalDealSelectionResetCmd = &cli.Command{
|
||||
Name: "reset",
|
||||
Usage: "Reset retrieval deal proposal selection criteria to default values",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
smapi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
err = smapi.DealsSetConsiderOnlineRetrievalDeals(lcli.DaemonContext(cctx), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = smapi.DealsSetConsiderOfflineRetrievalDeals(lcli.DaemonContext(cctx), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var retrievalDealSelectionRejectCmd = &cli.Command{
|
||||
Name: "reject",
|
||||
Usage: "Configure criteria which necessitate automatic rejection",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "online",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "offline",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
smapi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
if cctx.Bool("online") {
|
||||
err = smapi.DealsSetConsiderOnlineRetrievalDeals(lcli.DaemonContext(cctx), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if cctx.Bool("offline") {
|
||||
err = smapi.DealsSetConsiderOfflineRetrievalDeals(lcli.DaemonContext(cctx), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -27,6 +27,7 @@ var sectorsCmd = &cli.Command{
|
||||
sectorsRefsCmd,
|
||||
sectorsUpdateCmd,
|
||||
sectorsPledgeCmd,
|
||||
sectorsRemoveCmd,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -46,8 +47,9 @@ var sectorsPledgeCmd = &cli.Command{
|
||||
}
|
||||
|
||||
var sectorsStatusCmd = &cli.Command{
|
||||
Name: "status",
|
||||
Usage: "Get the seal status of a sector by its ID",
|
||||
Name: "status",
|
||||
Usage: "Get the seal status of a sector by its number",
|
||||
ArgsUsage: "<sectorNum>",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "log",
|
||||
@@ -63,7 +65,7 @@ var sectorsStatusCmd = &cli.Command{
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
if !cctx.Args().Present() {
|
||||
return fmt.Errorf("must specify sector ID to get status of")
|
||||
return fmt.Errorf("must specify sector number to get status of")
|
||||
}
|
||||
|
||||
id, err := strconv.ParseUint(cctx.Args().First(), 10, 64)
|
||||
@@ -208,6 +210,39 @@ var sectorsRefsCmd = &cli.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var sectorsRemoveCmd = &cli.Command{
|
||||
Name: "remove",
|
||||
Usage: "Forcefully remove a sector (WARNING: This means losing power and collateral for the removed sector)",
|
||||
ArgsUsage: "<sectorNum>",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "really-do-it",
|
||||
Usage: "pass this flag if you know what you are doing",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
if !cctx.Bool("really-do-it") {
|
||||
return xerrors.Errorf("this is a command for advanced users, only use it if you are sure of what you are doing")
|
||||
}
|
||||
nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
if cctx.Args().Len() != 1 {
|
||||
return xerrors.Errorf("must pass sector number")
|
||||
}
|
||||
|
||||
id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("could not parse sector number: %w", err)
|
||||
}
|
||||
|
||||
return nodeApi.SectorRemove(ctx, abi.SectorNumber(id))
|
||||
},
|
||||
}
|
||||
|
||||
var sectorsUpdateCmd = &cli.Command{
|
||||
Name: "update-state",
|
||||
Usage: "ADVANCED: manually update the state of a sector, this may aid in error recovery",
|
||||
@@ -228,12 +263,12 @@ var sectorsUpdateCmd = &cli.Command{
|
||||
defer closer()
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
if cctx.Args().Len() < 2 {
|
||||
return xerrors.Errorf("must pass sector ID and new state")
|
||||
return xerrors.Errorf("must pass sector number and new state")
|
||||
}
|
||||
|
||||
id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("could not parse sector ID: %w", err)
|
||||
return xerrors.Errorf("could not parse sector number: %w", err)
|
||||
}
|
||||
|
||||
return nodeApi.SectorsUpdate(ctx, abi.SectorNumber(id), api.SectorState(cctx.Args().Get(1)))
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.opencensus.io/plugin/runmetrics"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/tag"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
"github.com/filecoin-project/lotus/chain/vm"
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
"github.com/filecoin-project/lotus/lib/peermgr"
|
||||
"github.com/filecoin-project/lotus/lib/ulimit"
|
||||
"github.com/filecoin-project/lotus/metrics"
|
||||
"github.com/filecoin-project/lotus/node"
|
||||
"github.com/filecoin-project/lotus/node/modules"
|
||||
@@ -112,8 +114,27 @@ var DaemonCmd = &cli.Command{
|
||||
Name: "profile",
|
||||
Usage: "specify type of node",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "manage-fdlimit",
|
||||
Usage: "manage open file limit",
|
||||
Value: true,
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
err := runmetrics.Enable(runmetrics.RunMetricOptions{
|
||||
EnableCPU: true,
|
||||
EnableMemory: true,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("enabling runtime metrics: %w", err)
|
||||
}
|
||||
|
||||
if cctx.Bool("manage-fdlimit") {
|
||||
if _, _, err := ulimit.ManageFdLimit(); err != nil {
|
||||
log.Errorf("setting file descriptor limit: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if prof := cctx.String("pprof"); prof != "" {
|
||||
profile, err := os.Create(prof)
|
||||
if err != nil {
|
||||
|
||||
@@ -69,10 +69,10 @@ func init() {
|
||||
}
|
||||
// TODO: beacon
|
||||
|
||||
uts := head.MinTimestamp() + uint64(build.BlockDelay)
|
||||
uts := head.MinTimestamp() + uint64(build.BlockDelaySecs)
|
||||
nheight := head.Height() + 1
|
||||
blk, err := api.MinerCreateBlock(ctx, &lapi.BlockTemplate{
|
||||
addr, head.Key(), ticket, nil, nil, msgs, nheight, uts, nil,
|
||||
addr, head.Key(), ticket, &types.ElectionProof{}, nil, msgs, nheight, uts, gen.ValidWpostForTesting,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("creating block: %w", err)
|
||||
|
||||
@@ -63,10 +63,6 @@
|
||||
"title": "Proof-of-Spacetime(s)",
|
||||
"value": "Filecoin is a protocol token whose blockchain runs on a novel proof, called Proof-of-Spacetime, where blocks are created by miners that are storing data."
|
||||
},
|
||||
"lotus-testnet": {
|
||||
"title": "Filecoin Testnet",
|
||||
"value": "Until we launch, we are making lots of changes to Lotus. The Testnet is expected to bring a few significant fixes/improvements. During Testnet, you can retrieve test filecoin from our network faucet to use as collateral to start mining. Test filecoin do not have any value – the official filecoin tokens will not be released until Mainnet launch."
|
||||
},
|
||||
"filecoin-testnet": {
|
||||
"title": "Filecoin Testnet",
|
||||
"value": "Until we launch, we are making lots of changes to Lotus. The Testnet is expected to bring a few significant fixes/improvements. During Testnet, you can retrieve test filecoin from our network faucet to use as collateral to start mining. Test filecoin do not have any value – the official filecoin tokens will not be released until Mainnet launch."
|
||||
|
||||
@@ -58,8 +58,8 @@
|
||||
},
|
||||
{
|
||||
"title": "Use Lotus with systemd",
|
||||
"slug": "en+install-system-services",
|
||||
"github": "en/install-system-services.md",
|
||||
"slug": "en+install-systemd-services",
|
||||
"github": "en/install-systemd-services.md",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
@@ -71,6 +71,12 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Architecture",
|
||||
"slug": "en+arch",
|
||||
"github": "en/architecture.md",
|
||||
"value": null,
|
||||
"posts": []
|
||||
}, {
|
||||
"title": "Storage Mining",
|
||||
"slug": "en+mining",
|
||||
"github": "en/mining.md",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user