Compare commits
343
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87b8e0a8fd | ||
|
|
82f754f31e | ||
|
|
670b72ff1f | ||
|
|
a17b6af00c | ||
|
|
a8829a049e | ||
|
|
14a7ae339f | ||
|
|
0374c50728 | ||
|
|
864eef3e4c | ||
|
|
6187f6b7ac | ||
|
|
d981baa362 | ||
|
|
40582335e9 | ||
|
|
5e76b05b17 | ||
|
|
9fc3c3351a | ||
|
|
2b367225e4 | ||
|
|
861134d923 | ||
|
|
00aad34194 | ||
|
|
7778fc2473 | ||
|
|
0787ff3bc3 | ||
|
|
46a3150784 | ||
|
|
1927b26abf | ||
|
|
444e75ef77 | ||
|
|
0e26a8d99b | ||
|
|
3fd57ff7d9 | ||
|
|
a791a795e9 | ||
|
|
5b394f0f98 | ||
|
|
e0f90274e4 | ||
|
|
1d893d6630 | ||
|
|
d64bc2c9be | ||
|
|
f1c70720df | ||
|
|
3f2d2eb659 | ||
|
|
121cd97f65 | ||
|
|
a8d693a2c0 | ||
|
|
e8570bd69c | ||
|
|
ac85a2e23b | ||
|
|
2acbdb43d7 | ||
|
|
444da43228 | ||
|
|
99990504b5 | ||
|
|
2fc2875106 | ||
|
|
c11a5bce59 | ||
|
|
49044b4c69 | ||
|
|
70cb445a12 | ||
|
|
30e4259eff | ||
|
|
5b9914790c | ||
|
|
bdffac300a | ||
|
|
10a9eb2155 | ||
|
|
768b2b3812 | ||
|
|
113b8b5002 | ||
|
|
f7c254b3de | ||
|
|
00335441ce | ||
|
|
26e9703548 | ||
|
|
8b77839d38 | ||
|
|
d23b96fc32 | ||
|
|
e5f32f288d | ||
|
|
7e40f8636b | ||
|
|
8aaa8de975 | ||
|
|
35ceb02c50 | ||
|
|
89a0b25377 | ||
|
|
2d7a9b0551 | ||
|
|
26b35b7f8e | ||
|
|
087d799547 | ||
|
|
6892c35aec | ||
|
|
861731ee7c | ||
|
|
131f87afb1 | ||
|
|
d9755dfb1c | ||
|
|
e06604d342 | ||
|
|
2a8ea957fa | ||
|
|
89dede32ac | ||
|
|
cfaeb2718f | ||
|
|
f06d67c5a2 | ||
|
|
7ef93448fb | ||
|
|
d70674c369 | ||
|
|
45eb890917 | ||
|
|
0dd7976ffb | ||
|
|
d771aa353e | ||
|
|
7c61e890c5 | ||
|
|
8ab6eaa6e3 | ||
|
|
4ea712c011 | ||
|
|
5e5a81bf23 | ||
|
|
ef9a9175a1 | ||
|
|
20266857e6 | ||
|
|
14fba589bc | ||
|
|
82b13afed6 | ||
|
|
9567dee00c | ||
|
|
70895afa3e | ||
|
|
37b8afd197 | ||
|
|
4ded4cf49e | ||
|
|
b73c016f6d | ||
|
|
d590c53400 | ||
|
|
114a3bbf3f | ||
|
|
b68ced7b19 | ||
|
|
43d1d62fc6 | ||
|
|
668d615be6 | ||
|
|
91d2c02765 | ||
|
|
5fb41d7098 | ||
|
|
b92cff74f2 | ||
|
|
f3b3ff0dd2 | ||
|
|
7e6da3d76f | ||
|
|
0d5c99cb3c | ||
|
|
077f3f0c33 | ||
|
|
8ea44e82d1 | ||
|
|
e74eb6af09 | ||
|
|
ad6b0575c1 | ||
|
|
a965fc660e | ||
|
|
4b78c4ea1e | ||
|
|
6167bcc8ce | ||
|
|
fa0f117edd | ||
|
|
bcc2d7fd10 | ||
|
|
b0b39424e4 | ||
|
|
41dd8954aa | ||
|
|
d71d647aaf | ||
|
|
f3da600b28 | ||
|
|
20fe979857 | ||
|
|
cf0ff92008 | ||
|
|
9d9de747a9 | ||
|
|
1e8e63e39e | ||
|
|
0096d521c3 | ||
|
|
144bbdfd3b | ||
|
|
13e1b4b3df | ||
|
|
b6fe914748 | ||
|
|
930e9b957c | ||
|
|
57e825901e | ||
|
|
aef0ecf2d2 | ||
|
|
ed407689e6 | ||
|
|
029a4a72b8 | ||
|
|
10a5480895 | ||
|
|
ee3cdf0e97 | ||
|
|
cf255127a4 | ||
|
|
cb5e6e0cd1 | ||
|
|
8d8891a431 | ||
|
|
ef7bcfec06 | ||
|
|
a1b890c8c7 | ||
|
|
4068e0710d | ||
|
|
ebb54bc381 | ||
|
|
ba1ee60d1b | ||
|
|
7f99d15071 | ||
|
|
fd69f8bbd8 | ||
|
|
abeb842d93 | ||
|
|
392ef1beb7 | ||
|
|
2c902db0e1 | ||
|
|
c1eaa2f864 | ||
|
|
1b0f54a61b | ||
|
|
c4214e23bf | ||
|
|
a79f96a531 | ||
|
|
f8faa85cba | ||
|
|
d83956aa6d | ||
|
|
4223e411b6 | ||
|
|
bce5535679 | ||
|
|
1c9344d805 | ||
|
|
ae2ae3b9ed | ||
|
|
13ade62e27 | ||
|
|
50cd87e953 | ||
|
|
366329b085 | ||
|
|
03826aac87 | ||
|
|
7774ca0179 | ||
|
|
ca23a4406c | ||
|
|
5bfea36a17 | ||
|
|
882f00fbf3 | ||
|
|
413008a98f | ||
|
|
0ff655a0a0 | ||
|
|
d581f58896 | ||
|
|
62f52c672c | ||
|
|
eae61009b0 | ||
|
|
7491956197 | ||
|
|
2f113e58ca | ||
|
|
7e6ed09628 | ||
|
|
c90faf0754 | ||
|
|
5281a6d307 | ||
|
|
5f5c6a08a4 | ||
|
|
825064669d | ||
|
|
034f6cf832 | ||
|
|
91ee13b461 | ||
|
|
e5fbba7958 | ||
|
|
98a82d3dfe | ||
|
|
05ac9453cd | ||
|
|
cb7231cb8a | ||
|
|
9028df409c | ||
|
|
1fe7493897 | ||
|
|
e1ed24ddd0 | ||
|
|
92f31ee0be | ||
|
|
2ee879d1ec | ||
|
|
5edc7dcdb3 | ||
|
|
c42e57b51c | ||
|
|
93f95d2ef9 | ||
|
|
c1fd48a14b | ||
|
|
d50cf4eb64 | ||
|
|
bca0ec4f9c | ||
|
|
539a524271 | ||
|
|
51be40291a | ||
|
|
c1231125e9 | ||
|
|
925498f938 | ||
|
|
bcc8b9d02f | ||
|
|
c48bf864a4 | ||
|
|
cbbb76b2f0 | ||
|
|
e8020e8f4e | ||
|
|
23cd5832c7 | ||
|
|
76bca6c392 | ||
|
|
9e69576410 | ||
|
|
38350b8d8a | ||
|
|
80928586d1 | ||
|
|
22f490132e | ||
|
|
c46aea6a36 | ||
|
|
b226a7dbff | ||
|
|
a750e63a7b | ||
|
|
33182967eb | ||
|
|
b11fba3561 | ||
|
|
2f0fbe26f8 | ||
|
|
12e076e9ea | ||
|
|
7b43f90a3b | ||
|
|
3566e96b55 | ||
|
|
20e831b9ea | ||
|
|
2c7c4ee845 | ||
|
|
49010a9b9a | ||
|
|
69f3580ca2 | ||
|
|
1da18e8058 | ||
|
|
2cbdedc209 | ||
|
|
0ff334912b | ||
|
|
52657c5cb0 | ||
|
|
62032e7c2a | ||
|
|
93b0ea2108 | ||
|
|
32f21258a1 | ||
|
|
bcde3bd60e | ||
|
|
3cec54f774 | ||
|
|
a626b3e62c | ||
|
|
18ae6bd18b | ||
|
|
9310cd6036 | ||
|
|
0c34deb884 | ||
|
|
aa7220d9e8 | ||
|
|
87458af858 | ||
|
|
c6d31c0d6d | ||
|
|
98aff9dcc6 | ||
|
|
40cfe626b8 | ||
|
|
41fac326b1 | ||
|
|
a4d22f585f | ||
|
|
bb2ac5c22d | ||
|
|
94e48ae860 | ||
|
|
1c73ac69b4 | ||
|
|
c4798a604a | ||
|
|
ef2be0b546 | ||
|
|
b6ba3b23bb | ||
|
|
826ef40626 | ||
|
|
884246677b | ||
|
|
f620310bfe | ||
|
|
683432e6e0 | ||
|
|
d082940d54 | ||
|
|
fe7cf0c39a | ||
|
|
6636da1299 | ||
|
|
9b4992b834 | ||
|
|
9459c612fb | ||
|
|
8ce69d1397 | ||
|
|
d900acc506 | ||
|
|
b88e4abe05 | ||
|
|
8e56357450 | ||
|
|
cd958e4f6e | ||
|
|
482c90412d | ||
|
|
6a07def34a | ||
|
|
bf36ca93bb | ||
|
|
c3f421a9cd | ||
|
|
3f93b86ebe | ||
|
|
719215122b | ||
|
|
8a5950b3e9 | ||
|
|
df17f709a4 | ||
|
|
977390e3c0 | ||
|
|
990b5a0bad | ||
|
|
8c4e705df3 | ||
|
|
2776e29042 | ||
|
|
15faab8412 | ||
|
|
4d8f8d2600 | ||
|
|
9624dc53f8 | ||
|
|
0a83896589 | ||
|
|
d127208b85 | ||
|
|
2ddb52e103 | ||
|
|
d40946fb8c | ||
|
|
9e5e77b778 | ||
|
|
e17af657da | ||
|
|
1fc47eb7d8 | ||
|
|
3af9fde0b4 | ||
|
|
09b566dde1 | ||
|
|
a1ded51024 | ||
|
|
e197d27f20 | ||
|
|
23d705e33b | ||
|
|
871d1ba547 | ||
|
|
03078cdb56 | ||
|
|
38b21a713a | ||
|
|
6eadc0ee57 | ||
|
|
eaf0200fe1 | ||
|
|
5aeb27ed94 | ||
|
|
807a5dbaa8 | ||
|
|
25c591fd6c | ||
|
|
0d7621be56 | ||
|
|
e9f57ae77f | ||
|
|
9bd0e9ed58 | ||
|
|
3f4429a5ef | ||
|
|
921bdb85ab | ||
|
|
921e5520ea | ||
|
|
cff7716707 | ||
|
|
d463abae2f | ||
|
|
3f71298c4b | ||
|
|
7a93a70e6e | ||
|
|
5d072409f8 | ||
|
|
cbbd7dea0c | ||
|
|
4bc032430b | ||
|
|
e58b1dfda2 | ||
|
|
7180b52d2c | ||
|
|
802a9f0a78 | ||
|
|
4cfdf8e83c | ||
|
|
07965467ee | ||
|
|
e54b32208c | ||
|
|
1b61d13733 | ||
|
|
60ee56a23e | ||
|
|
1358d70128 | ||
|
|
537d31416e | ||
|
|
e30f8af2d1 | ||
|
|
402895fdf0 | ||
|
|
a5f66b5605 | ||
|
|
a03746c5df | ||
|
|
bc5637c60a | ||
|
|
4240990c6c | ||
|
|
1a2a2314b3 | ||
|
|
8ad89188fe | ||
|
|
13a65fdf56 | ||
|
|
126869ed98 | ||
|
|
d5ae0905f9 | ||
|
|
9b52b06ea9 | ||
|
|
2061c32a2e | ||
|
|
dedfa143ce | ||
|
|
13224e3e0b | ||
|
|
8220e7f82f | ||
|
|
7b21420831 | ||
|
|
5201744199 | ||
|
|
3f0ddcc553 | ||
|
|
ab72f2e230 | ||
|
|
be3281bdcf | ||
|
|
d2b2fba799 | ||
|
|
3cacbdfa18 | ||
|
|
016661b657 | ||
|
|
3897bf14d1 | ||
|
|
0cfdc9b5b6 | ||
|
|
77ea7ef90d | ||
|
|
4aa977f119 | ||
|
|
8f3123d75b | ||
|
|
36a88f45fc | ||
|
|
d599833b64 | ||
|
|
5f6977af1a |
@@ -7,12 +7,12 @@ executors:
|
||||
golang:
|
||||
docker:
|
||||
# Must match GO_VERSION_MIN in project root
|
||||
- image: cimg/go:1.19.12
|
||||
- image: cimg/go:1.20.7
|
||||
resource_class: medium+
|
||||
golang-2xl:
|
||||
docker:
|
||||
# Must match GO_VERSION_MIN in project root
|
||||
- image: cimg/go:1.19.12
|
||||
- image: cimg/go:1.20.7
|
||||
resource_class: 2xlarge
|
||||
ubuntu:
|
||||
docker:
|
||||
@@ -997,6 +997,7 @@ workflows:
|
||||
suite: utest-unit-cli
|
||||
target: "./cli/... ./cmd/... ./api/..."
|
||||
get-params: true
|
||||
executor: golang-2xl
|
||||
- test:
|
||||
name: test-unit-node
|
||||
requires:
|
||||
@@ -1004,12 +1005,14 @@ workflows:
|
||||
suite: utest-unit-node
|
||||
target: "./node/..."
|
||||
|
||||
|
||||
- test:
|
||||
name: test-unit-rest
|
||||
requires:
|
||||
- build
|
||||
suite: utest-unit-rest
|
||||
target: "./api/... ./blockstore/... ./build/... ./chain/... ./cli/... ./cmd/... ./conformance/... ./extern/... ./gateway/... ./journal/... ./lib/... ./markets/... ./node/... ./paychmgr/... ./storage/... ./tools/..."
|
||||
target: "./blockstore/... ./build/... ./chain/... ./conformance/... ./gateway/... ./journal/... ./lib/... ./markets/... ./paychmgr/... ./tools/..."
|
||||
|
||||
executor: golang-2xl
|
||||
- test:
|
||||
name: test-unit-storage
|
||||
@@ -1018,6 +1021,7 @@ workflows:
|
||||
suite: utest-unit-storage
|
||||
target: "./storage/... ./extern/..."
|
||||
|
||||
|
||||
- test:
|
||||
go-test-flags: "-run=TestMulticoreSDR"
|
||||
requires:
|
||||
|
||||
@@ -65,6 +65,8 @@ func main() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Redundantly flag both absolute and relative paths as excluded
|
||||
excluded[filepath.Join(repo, s)] = struct{}{}
|
||||
excluded[e] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ executors:
|
||||
golang:
|
||||
docker:
|
||||
# Must match GO_VERSION_MIN in project root
|
||||
- image: cimg/go:1.19.12
|
||||
- image: cimg/go:1.20.7
|
||||
resource_class: medium+
|
||||
golang-2xl:
|
||||
docker:
|
||||
# Must match GO_VERSION_MIN in project root
|
||||
- image: cimg/go:1.19.12
|
||||
- image: cimg/go:1.20.7
|
||||
resource_class: 2xlarge
|
||||
ubuntu:
|
||||
docker:
|
||||
@@ -558,6 +558,7 @@ workflows:
|
||||
suite: utest-[[ $suite ]]
|
||||
target: "[[ $pkgs ]]"
|
||||
[[if eq $suite "unit-cli"]]get-params: true[[end]]
|
||||
[[if eq $suite "unit-cli"]]executor: golang-2xl[[end]]
|
||||
[[- if eq $suite "unit-rest"]]executor: golang-2xl[[end]]
|
||||
[[- end]]
|
||||
- test:
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: New Task
|
||||
about: A larger yet well-scoped task
|
||||
title: '<title>'
|
||||
labels: Needs Triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## User Story
|
||||
<!-- Why? -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- What? -->
|
||||
<!-- add description-->
|
||||
|
||||
```[tasklist]
|
||||
### Deliverables
|
||||
|
||||
```
|
||||
|
||||
## Technical Breakdown
|
||||
```[tasklist]
|
||||
### Development
|
||||
|
||||
```
|
||||
|
||||
```[tasklist]
|
||||
### Testing
|
||||
|
||||
```
|
||||
@@ -16,6 +16,7 @@ Before you mark the PR ready for review, please make sure that:
|
||||
- example: ` fix: mempool: Introduce a cache for valid signatures`
|
||||
- `PR type`: fix, feat, build, chore, ci, docs, perf, refactor, revert, style, test
|
||||
- `area`, e.g. api, chain, state, market, mempool, multisig, networking, paych, proving, sealing, wallet, deps
|
||||
- [ ] If the PR affects users (e.g., new feature, bug fix, system requirements change), update the CHANGELOG.md and add details to the UNRELEASED section.
|
||||
- [ ] New features have usage guidelines and / or documentation updates in
|
||||
- [ ] [Lotus Documentation](https://lotus.filecoin.io)
|
||||
- [ ] [Discussion Tutorials](https://github.com/filecoin-project/lotus/discussions/categories/tutorials)
|
||||
|
||||
@@ -41,6 +41,7 @@ build/paramfetch.sh
|
||||
bin/ipget
|
||||
bin/tmp/*
|
||||
.idea
|
||||
.vscode
|
||||
scratchpad
|
||||
|
||||
build/builtin-actors/v*
|
||||
|
||||
@@ -14,6 +14,7 @@ linters:
|
||||
- varcheck
|
||||
- deadcode
|
||||
- scopelint
|
||||
- unused
|
||||
|
||||
# We don't want to skip builtin/
|
||||
skip-dirs-use-default: false
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
# Lotus changelog
|
||||
|
||||
# UNRELEASED
|
||||
- chore: Auto remove local chain data when importing chain file or snapshot ([filecoin-project/lotus#11277](https://github.com/filecoin-project/lotus/pull/11277))
|
||||
|
||||
## New features
|
||||
- feat: Added new tracing API (**HIGHLY EXPERIMENTAL**) supporting two RPC methods: `trace_block` and `trace_replayBlockTransactions` ([filecoin-project/lotus#11100](https://github.com/filecoin-project/lotus/pull/11100))
|
||||
|
||||
## Improvements
|
||||
- fix: Add time slicing to splitstore purging step during compaction to reduce lock congestion [filecoin-project/lotus#11269](https://github.com/filecoin-project/lotus/pull/11269)
|
||||
- feat: Added instructions on how to setup Prometheus/Grafana for monitoring a local Lotus node [filecoin-project/lotus#11276](https://github.com/filecoin-project/lotus/pull/11276)
|
||||
- feat: Add a `FilterThresholdEpoch` configuration option [filecoin-project/lotus#11346](https://github.com/filecoin-project/lotus/pull/11346)
|
||||
|
||||
## New features
|
||||
- feat: Add move-partition command ([filecoin-project/lotus#11290](https://github.com/filecoin-project/lotus/pull/11290))
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#####################################
|
||||
FROM golang:1.19.12-bullseye AS lotus-builder
|
||||
FROM golang:1.20.7-bullseye AS lotus-builder
|
||||
MAINTAINER Lotus Development Team
|
||||
|
||||
RUN apt-get update && apt-get install -y ca-certificates build-essential clang ocl-icd-opencl-dev ocl-icd-libopencl1 jq libhwloc-dev
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.19.12
|
||||
1.20.7
|
||||
|
||||
@@ -73,7 +73,7 @@ All releases under an odd minor version number indicate **feature releases**. Th
|
||||
|
||||
Feature releases include new development and bug fixes. They are not mandatory, but still highly recommended, **as they may contain critical security fixes**. Note that some of these releases may be very small patch releases that include critical hotfixes. There is no way to distinguish between a bug fix release and a feature release on the "feature" version. Both cases will use the "patch" version number.
|
||||
|
||||
We aim to ship a new feature release of the Lotus software from our development (master) branch every 3 weeks, so users can expect a regular cadence of Lotus feature releases. Note that mandatory releases for network upgrades may disrupt this schedule. For more, see the Release Cycle section (TODO: Link).
|
||||
We aim to ship a new feature release of the Lotus software from our development (master) branch every 3 weeks, so users can expect a regular cadence of Lotus feature releases. Note that mandatory releases for network upgrades may disrupt this schedule. For more, see the [Release Cycle section](#release-cycle).
|
||||
|
||||
### Examples Scenarios
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ lotus-health:
|
||||
.PHONY: lotus-health
|
||||
BINS+=lotus-health
|
||||
|
||||
lotus-wallet:
|
||||
lotus-wallet: $(BUILD_DEPS)
|
||||
rm -f lotus-wallet
|
||||
$(GOCC) build $(GOFLAGS) -o lotus-wallet ./cmd/lotus-wallet
|
||||
.PHONY: lotus-wallet
|
||||
|
||||
@@ -133,6 +133,8 @@ Note: The default branch `master` is the dev branch where the latest new feature
|
||||
|
||||
6. You should now have Lotus installed. You can now [start the Lotus daemon and sync the chain](https://lotus.filecoin.io/lotus/install/linux/#start-the-lotus-daemon-and-sync-the-chain).
|
||||
|
||||
7. (Optional) Follow the [Setting Up Prometheus and Grafana](https://github.com/filecoin-project/lotus/tree/master/metrics/README.md) guide for detailed instructions on setting up a working monitoring system running against a local running lotus node.
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -867,6 +867,13 @@ type FullNode interface {
|
||||
// Returns the client version
|
||||
Web3ClientVersion(ctx context.Context) (string, error) //perm:read
|
||||
|
||||
// TraceAPI related methods
|
||||
//
|
||||
// Returns traces created at given block
|
||||
EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error) //perm:read
|
||||
// Replays all transactions in a block returning the requested traces for each transaction
|
||||
EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) //perm:read
|
||||
|
||||
// CreateBackup creates node backup onder the specified file name. The
|
||||
// method requires that the lotus daemon is running with the
|
||||
// LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-jsonrpc"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg"
|
||||
"github.com/filecoin-project/go-state-types/dline"
|
||||
|
||||
apitypes "github.com/filecoin-project/lotus/api/types"
|
||||
@@ -65,6 +66,11 @@ type Gateway interface {
|
||||
StateDealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, verified bool, tsk types.TipSetKey) (DealCollateralBounds, error)
|
||||
StateDecodeParams(ctx context.Context, toAddr address.Address, method abi.MethodNum, params []byte, tsk types.TipSetKey) (interface{}, error)
|
||||
StateGetActor(ctx context.Context, actor address.Address, ts types.TipSetKey) (*types.Actor, error)
|
||||
StateGetAllocationForPendingDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*verifregtypes.Allocation, error)
|
||||
StateGetAllocation(ctx context.Context, clientAddr address.Address, allocationId verifregtypes.AllocationId, tsk types.TipSetKey) (*verifregtypes.Allocation, error)
|
||||
StateGetAllocations(ctx context.Context, clientAddr address.Address, tsk types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error)
|
||||
StateGetClaim(ctx context.Context, providerAddr address.Address, claimId verifregtypes.ClaimId, tsk types.TipSetKey) (*verifregtypes.Claim, error)
|
||||
StateGetClaims(ctx context.Context, providerAddr address.Address, tsk types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error)
|
||||
StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*ActorState, error)
|
||||
StateListMiners(ctx context.Context, tsk types.TipSetKey) ([]address.Address, error)
|
||||
StateLookupID(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error)
|
||||
@@ -121,4 +127,6 @@ type Gateway interface {
|
||||
EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error)
|
||||
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
|
||||
Web3ClientVersion(ctx context.Context) (string, error)
|
||||
EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error)
|
||||
EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error)
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ func CreateEthRPCAliases(as apitypes.Aliaser) {
|
||||
as.AliasMethod("eth_subscribe", "Filecoin.EthSubscribe")
|
||||
as.AliasMethod("eth_unsubscribe", "Filecoin.EthUnsubscribe")
|
||||
|
||||
as.AliasMethod("trace_block", "Filecoin.EthTraceBlock")
|
||||
as.AliasMethod("trace_replayBlockTransactions", "Filecoin.EthTraceReplayBlockTransactions")
|
||||
|
||||
as.AliasMethod("net_version", "Filecoin.NetVersion")
|
||||
as.AliasMethod("net_listening", "Filecoin.NetListening")
|
||||
|
||||
|
||||
@@ -1491,6 +1491,36 @@ func (mr *MockFullNodeMockRecorder) EthSyncing(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthSyncing", reflect.TypeOf((*MockFullNode)(nil).EthSyncing), arg0)
|
||||
}
|
||||
|
||||
// EthTraceBlock mocks base method.
|
||||
func (m *MockFullNode) EthTraceBlock(arg0 context.Context, arg1 string) ([]*ethtypes.EthTraceBlock, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EthTraceBlock", arg0, arg1)
|
||||
ret0, _ := ret[0].([]*ethtypes.EthTraceBlock)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EthTraceBlock indicates an expected call of EthTraceBlock.
|
||||
func (mr *MockFullNodeMockRecorder) EthTraceBlock(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthTraceBlock", reflect.TypeOf((*MockFullNode)(nil).EthTraceBlock), arg0, arg1)
|
||||
}
|
||||
|
||||
// EthTraceReplayBlockTransactions mocks base method.
|
||||
func (m *MockFullNode) EthTraceReplayBlockTransactions(arg0 context.Context, arg1 string, arg2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EthTraceReplayBlockTransactions", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].([]*ethtypes.EthTraceReplayBlockTransaction)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EthTraceReplayBlockTransactions indicates an expected call of EthTraceReplayBlockTransactions.
|
||||
func (mr *MockFullNodeMockRecorder) EthTraceReplayBlockTransactions(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthTraceReplayBlockTransactions", reflect.TypeOf((*MockFullNode)(nil).EthTraceReplayBlockTransactions), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// EthUninstallFilter mocks base method.
|
||||
func (m *MockFullNode) EthUninstallFilter(arg0 context.Context, arg1 ethtypes.EthFilterID) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -315,6 +315,10 @@ type FullNodeMethods struct {
|
||||
|
||||
EthSyncing func(p0 context.Context) (ethtypes.EthSyncingResult, error) `perm:"read"`
|
||||
|
||||
EthTraceBlock func(p0 context.Context, p1 string) ([]*ethtypes.EthTraceBlock, error) `perm:"read"`
|
||||
|
||||
EthTraceReplayBlockTransactions func(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) `perm:"read"`
|
||||
|
||||
EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) `perm:"read"`
|
||||
|
||||
EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) `perm:"read"`
|
||||
@@ -731,6 +735,10 @@ type GatewayMethods struct {
|
||||
|
||||
EthSyncing func(p0 context.Context) (ethtypes.EthSyncingResult, error) ``
|
||||
|
||||
EthTraceBlock func(p0 context.Context, p1 string) ([]*ethtypes.EthTraceBlock, error) ``
|
||||
|
||||
EthTraceReplayBlockTransactions func(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) ``
|
||||
|
||||
EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) ``
|
||||
|
||||
EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) ``
|
||||
@@ -769,6 +777,16 @@ type GatewayMethods struct {
|
||||
|
||||
StateGetActor func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) ``
|
||||
|
||||
StateGetAllocation func(p0 context.Context, p1 address.Address, p2 verifregtypes.AllocationId, p3 types.TipSetKey) (*verifregtypes.Allocation, error) ``
|
||||
|
||||
StateGetAllocationForPendingDeal func(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*verifregtypes.Allocation, error) ``
|
||||
|
||||
StateGetAllocations func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) ``
|
||||
|
||||
StateGetClaim func(p0 context.Context, p1 address.Address, p2 verifregtypes.ClaimId, p3 types.TipSetKey) (*verifregtypes.Claim, error) ``
|
||||
|
||||
StateGetClaims func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) ``
|
||||
|
||||
StateListMiners func(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) ``
|
||||
|
||||
StateLookupID func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) ``
|
||||
@@ -2446,6 +2464,28 @@ func (s *FullNodeStub) EthSyncing(p0 context.Context) (ethtypes.EthSyncingResult
|
||||
return *new(ethtypes.EthSyncingResult), ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *FullNodeStruct) EthTraceBlock(p0 context.Context, p1 string) ([]*ethtypes.EthTraceBlock, error) {
|
||||
if s.Internal.EthTraceBlock == nil {
|
||||
return *new([]*ethtypes.EthTraceBlock), ErrNotSupported
|
||||
}
|
||||
return s.Internal.EthTraceBlock(p0, p1)
|
||||
}
|
||||
|
||||
func (s *FullNodeStub) EthTraceBlock(p0 context.Context, p1 string) ([]*ethtypes.EthTraceBlock, error) {
|
||||
return *new([]*ethtypes.EthTraceBlock), ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *FullNodeStruct) EthTraceReplayBlockTransactions(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
|
||||
if s.Internal.EthTraceReplayBlockTransactions == nil {
|
||||
return *new([]*ethtypes.EthTraceReplayBlockTransaction), ErrNotSupported
|
||||
}
|
||||
return s.Internal.EthTraceReplayBlockTransactions(p0, p1, p2)
|
||||
}
|
||||
|
||||
func (s *FullNodeStub) EthTraceReplayBlockTransactions(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
|
||||
return *new([]*ethtypes.EthTraceReplayBlockTransaction), ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *FullNodeStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) {
|
||||
if s.Internal.EthUninstallFilter == nil {
|
||||
return false, ErrNotSupported
|
||||
@@ -4668,6 +4708,28 @@ func (s *GatewayStub) EthSyncing(p0 context.Context) (ethtypes.EthSyncingResult,
|
||||
return *new(ethtypes.EthSyncingResult), ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) EthTraceBlock(p0 context.Context, p1 string) ([]*ethtypes.EthTraceBlock, error) {
|
||||
if s.Internal.EthTraceBlock == nil {
|
||||
return *new([]*ethtypes.EthTraceBlock), ErrNotSupported
|
||||
}
|
||||
return s.Internal.EthTraceBlock(p0, p1)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) EthTraceBlock(p0 context.Context, p1 string) ([]*ethtypes.EthTraceBlock, error) {
|
||||
return *new([]*ethtypes.EthTraceBlock), ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) EthTraceReplayBlockTransactions(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
|
||||
if s.Internal.EthTraceReplayBlockTransactions == nil {
|
||||
return *new([]*ethtypes.EthTraceReplayBlockTransaction), ErrNotSupported
|
||||
}
|
||||
return s.Internal.EthTraceReplayBlockTransactions(p0, p1, p2)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) EthTraceReplayBlockTransactions(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
|
||||
return *new([]*ethtypes.EthTraceReplayBlockTransaction), ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) {
|
||||
if s.Internal.EthUninstallFilter == nil {
|
||||
return false, ErrNotSupported
|
||||
@@ -4877,6 +4939,61 @@ func (s *GatewayStub) StateGetActor(p0 context.Context, p1 address.Address, p2 t
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) StateGetAllocation(p0 context.Context, p1 address.Address, p2 verifregtypes.AllocationId, p3 types.TipSetKey) (*verifregtypes.Allocation, error) {
|
||||
if s.Internal.StateGetAllocation == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return s.Internal.StateGetAllocation(p0, p1, p2, p3)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) StateGetAllocation(p0 context.Context, p1 address.Address, p2 verifregtypes.AllocationId, p3 types.TipSetKey) (*verifregtypes.Allocation, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) StateGetAllocationForPendingDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*verifregtypes.Allocation, error) {
|
||||
if s.Internal.StateGetAllocationForPendingDeal == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return s.Internal.StateGetAllocationForPendingDeal(p0, p1, p2)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) StateGetAllocationForPendingDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*verifregtypes.Allocation, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) StateGetAllocations(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) {
|
||||
if s.Internal.StateGetAllocations == nil {
|
||||
return *new(map[verifregtypes.AllocationId]verifregtypes.Allocation), ErrNotSupported
|
||||
}
|
||||
return s.Internal.StateGetAllocations(p0, p1, p2)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) StateGetAllocations(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) {
|
||||
return *new(map[verifregtypes.AllocationId]verifregtypes.Allocation), ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) StateGetClaim(p0 context.Context, p1 address.Address, p2 verifregtypes.ClaimId, p3 types.TipSetKey) (*verifregtypes.Claim, error) {
|
||||
if s.Internal.StateGetClaim == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return s.Internal.StateGetClaim(p0, p1, p2, p3)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) StateGetClaim(p0 context.Context, p1 address.Address, p2 verifregtypes.ClaimId, p3 types.TipSetKey) (*verifregtypes.Claim, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) StateGetClaims(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) {
|
||||
if s.Internal.StateGetClaims == nil {
|
||||
return *new(map[verifregtypes.ClaimId]verifregtypes.Claim), ErrNotSupported
|
||||
}
|
||||
return s.Internal.StateGetClaims(p0, p1, p2)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) StateGetClaims(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) {
|
||||
return *new(map[verifregtypes.ClaimId]verifregtypes.Claim), ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) StateListMiners(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) {
|
||||
if s.Internal.StateListMiners == nil {
|
||||
return *new([]address.Address), ErrNotSupported
|
||||
|
||||
+10
-1
@@ -56,9 +56,17 @@ type PubsubScore struct {
|
||||
Score *pubsub.PeerScoreSnapshot
|
||||
}
|
||||
|
||||
// MessageSendSpec contains optional fields which modify message sending behavior
|
||||
type MessageSendSpec struct {
|
||||
MaxFee abi.TokenAmount
|
||||
// MaxFee specifies a cap on network fees related to this message
|
||||
MaxFee abi.TokenAmount
|
||||
|
||||
// MsgUuid specifies a unique message identifier which can be used on node (or node cluster)
|
||||
// level to prevent double-sends of messages even when nonce generation is not handled by sender
|
||||
MsgUuid uuid.UUID
|
||||
|
||||
// MaximizeFeeCap makes message FeeCap be based entirely on MaxFee
|
||||
MaximizeFeeCap bool
|
||||
}
|
||||
|
||||
type MpoolMessageWhole struct {
|
||||
@@ -312,6 +320,7 @@ type NetworkParams struct {
|
||||
SupportedProofTypes []abi.RegisteredSealProof
|
||||
PreCommitChallengeDelay abi.ChainEpoch
|
||||
ForkUpgradeParams ForkUpgradeParams
|
||||
Eip155ChainID int
|
||||
}
|
||||
|
||||
type ForkUpgradeParams struct {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg"
|
||||
"github.com/filecoin-project/go-state-types/dline"
|
||||
abinetwork "github.com/filecoin-project/go-state-types/network"
|
||||
|
||||
@@ -61,6 +62,11 @@ type Gateway interface {
|
||||
StateDealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, verified bool, tsk types.TipSetKey) (api.DealCollateralBounds, error)
|
||||
StateDecodeParams(ctx context.Context, toAddr address.Address, method abi.MethodNum, params []byte, tsk types.TipSetKey) (interface{}, error)
|
||||
StateGetActor(ctx context.Context, actor address.Address, ts types.TipSetKey) (*types.Actor, error)
|
||||
StateGetAllocationForPendingDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*verifregtypes.Allocation, error)
|
||||
StateGetAllocation(ctx context.Context, clientAddr address.Address, allocationId verifregtypes.AllocationId, tsk types.TipSetKey) (*verifregtypes.Allocation, error)
|
||||
StateGetAllocations(ctx context.Context, clientAddr address.Address, tsk types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error)
|
||||
StateGetClaim(ctx context.Context, providerAddr address.Address, claimId verifregtypes.ClaimId, tsk types.TipSetKey) (*verifregtypes.Claim, error)
|
||||
StateGetClaims(ctx context.Context, providerAddr address.Address, tsk types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error)
|
||||
StateGetReceipt(context.Context, cid.Cid, types.TipSetKey) (*types.MessageReceipt, error)
|
||||
StateListMiners(ctx context.Context, tsk types.TipSetKey) ([]address.Address, error)
|
||||
StateLookupID(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error)
|
||||
|
||||
@@ -478,6 +478,16 @@ type GatewayMethods struct {
|
||||
|
||||
StateGetActor func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) ``
|
||||
|
||||
StateGetAllocation func(p0 context.Context, p1 address.Address, p2 verifregtypes.AllocationId, p3 types.TipSetKey) (*verifregtypes.Allocation, error) ``
|
||||
|
||||
StateGetAllocationForPendingDeal func(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*verifregtypes.Allocation, error) ``
|
||||
|
||||
StateGetAllocations func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) ``
|
||||
|
||||
StateGetClaim func(p0 context.Context, p1 address.Address, p2 verifregtypes.ClaimId, p3 types.TipSetKey) (*verifregtypes.Claim, error) ``
|
||||
|
||||
StateGetClaims func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) ``
|
||||
|
||||
StateGetReceipt func(p0 context.Context, p1 cid.Cid, p2 types.TipSetKey) (*types.MessageReceipt, error) ``
|
||||
|
||||
StateListMiners func(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) ``
|
||||
@@ -2850,6 +2860,61 @@ func (s *GatewayStub) StateGetActor(p0 context.Context, p1 address.Address, p2 t
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) StateGetAllocation(p0 context.Context, p1 address.Address, p2 verifregtypes.AllocationId, p3 types.TipSetKey) (*verifregtypes.Allocation, error) {
|
||||
if s.Internal.StateGetAllocation == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return s.Internal.StateGetAllocation(p0, p1, p2, p3)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) StateGetAllocation(p0 context.Context, p1 address.Address, p2 verifregtypes.AllocationId, p3 types.TipSetKey) (*verifregtypes.Allocation, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) StateGetAllocationForPendingDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*verifregtypes.Allocation, error) {
|
||||
if s.Internal.StateGetAllocationForPendingDeal == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return s.Internal.StateGetAllocationForPendingDeal(p0, p1, p2)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) StateGetAllocationForPendingDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*verifregtypes.Allocation, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) StateGetAllocations(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) {
|
||||
if s.Internal.StateGetAllocations == nil {
|
||||
return *new(map[verifregtypes.AllocationId]verifregtypes.Allocation), ErrNotSupported
|
||||
}
|
||||
return s.Internal.StateGetAllocations(p0, p1, p2)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) StateGetAllocations(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) {
|
||||
return *new(map[verifregtypes.AllocationId]verifregtypes.Allocation), ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) StateGetClaim(p0 context.Context, p1 address.Address, p2 verifregtypes.ClaimId, p3 types.TipSetKey) (*verifregtypes.Claim, error) {
|
||||
if s.Internal.StateGetClaim == nil {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
return s.Internal.StateGetClaim(p0, p1, p2, p3)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) StateGetClaim(p0 context.Context, p1 address.Address, p2 verifregtypes.ClaimId, p3 types.TipSetKey) (*verifregtypes.Claim, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) StateGetClaims(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) {
|
||||
if s.Internal.StateGetClaims == nil {
|
||||
return *new(map[verifregtypes.ClaimId]verifregtypes.Claim), ErrNotSupported
|
||||
}
|
||||
return s.Internal.StateGetClaims(p0, p1, p2)
|
||||
}
|
||||
|
||||
func (s *GatewayStub) StateGetClaims(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) {
|
||||
return *new(map[verifregtypes.ClaimId]verifregtypes.Claim), ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *GatewayStruct) StateGetReceipt(p0 context.Context, p1 cid.Cid, p2 types.TipSetKey) (*types.MessageReceipt, error) {
|
||||
if s.Internal.StateGetReceipt == nil {
|
||||
return nil, ErrNotSupported
|
||||
|
||||
@@ -182,7 +182,6 @@ type SplitStore struct {
|
||||
|
||||
compactionIndex int64
|
||||
pruneIndex int64
|
||||
onlineGCCnt int64
|
||||
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
|
||||
@@ -66,8 +66,9 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
batchSize = 16384
|
||||
cidKeySize = 128
|
||||
batchSize = 16384
|
||||
cidKeySize = 128
|
||||
purgeWorkSliceDuration = time.Second
|
||||
)
|
||||
|
||||
func (s *SplitStore) HeadChange(_, apply []*types.TipSet) error {
|
||||
@@ -1372,9 +1373,21 @@ func (s *SplitStore) purge(coldr *ColdSetReader, checkpoint *Checkpoint, markSet
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
err := coldr.ForEach(func(c cid.Cid) error {
|
||||
batch = append(batch, c)
|
||||
if len(batch) == batchSize {
|
||||
// add some time slicing to the purge as this a very disk I/O heavy operation that
|
||||
// requires write access to txnLk that may starve other operations that require
|
||||
// access to the blockstore.
|
||||
elapsed := time.Since(now)
|
||||
if elapsed > purgeWorkSliceDuration {
|
||||
// work 1 slice, sleep 4 slices, or 20% utilization
|
||||
time.Sleep(4 * elapsed)
|
||||
now = time.Now()
|
||||
}
|
||||
|
||||
return deleteBatch()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
/dns4/bootstrap-0.mainnet.filops.net/tcp/1347/p2p/12D3KooWCVe8MmsEMes2FzgTpt9fXtmCY7wrq91GRiaC8PHSCCBj
|
||||
/dns4/bootstrap-1.mainnet.filops.net/tcp/1347/p2p/12D3KooWCwevHg1yLCvktf2nvLu7L9894mcrJR4MsBCcm4syShVc
|
||||
/dns4/bootstrap-2.mainnet.filops.net/tcp/1347/p2p/12D3KooWEWVwHGn2yR36gKLozmb4YjDJGerotAPGxmdWZx2nxMC4
|
||||
/dns4/bootstrap-3.mainnet.filops.net/tcp/1347/p2p/12D3KooWKhgq8c7NQ9iGjbyK7v7phXvG6492HQfiDaGHLHLQjk7R
|
||||
/dns4/bootstrap-4.mainnet.filops.net/tcp/1347/p2p/12D3KooWL6PsFNPhYftrJzGgF5U18hFoaVhfGk7xwzD8yVrHJ3Uc
|
||||
/dns4/bootstrap-5.mainnet.filops.net/tcp/1347/p2p/12D3KooWLFynvDQiUpXoHroV1YxKHhPJgysQGH2k3ZGwtWzR4dFH
|
||||
/dns4/bootstrap-6.mainnet.filops.net/tcp/1347/p2p/12D3KooWP5MwCiqdMETF9ub1P3MbCvQCcfconnYHbWg6sUJcDRQQ
|
||||
/dns4/bootstrap-7.mainnet.filops.net/tcp/1347/p2p/12D3KooWRs3aY1p3juFjPy8gPN95PEQChm2QKGUCAdcDCC4EBMKf
|
||||
/dns4/bootstrap-8.mainnet.filops.net/tcp/1347/p2p/12D3KooWScFR7385LTyR4zU1bYdzSiiAb5rnNABfVahPvVSzyTkR
|
||||
@@ -13,4 +10,5 @@
|
||||
/dns4/node.glif.io/tcp/1235/p2p/12D3KooWBF8cpp65hp2u9LK5mh19x67ftAam84z9LsfaquTDSBpt
|
||||
/dns4/bootstrap-0.ipfsmain.cn/tcp/34721/p2p/12D3KooWQnwEGNqcM2nAcPtRR9rAX8Hrg4k9kJLCHoTR5chJfz6d
|
||||
/dns4/bootstrap-1.ipfsmain.cn/tcp/34723/p2p/12D3KooWMKxMkD5DMpSWsW7dBddKxKT7L2GgbNuckz9otxvkvByP
|
||||
/dns4/bootstarp-0.1475.io/tcp/61256/p2p/12D3KooWRzCVDwHUkgdK7eRgnoXbjDAELhxPErjHzbRLguSV1aRt
|
||||
/dns4/bootstarp-0.1475.io/tcp/61256/p2p/12D3KooWRzCVDwHUkgdK7eRgnoXbjDAELhxPErjHzbRLguSV1aRt
|
||||
/dns4/bootstrap-venus.mainnet.filincubator.com/tcp/8888/p2p/QmQu8C6deXwKvJP2D8B6QGyhngc3ZiDnFzEHBDx8yeBXST
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -132,6 +132,8 @@ const BlockDelaySecs = uint64(4)
|
||||
|
||||
const PropagationDelaySecs = uint64(1)
|
||||
|
||||
var EquivocationDelaySecs = uint64(0)
|
||||
|
||||
// SlashablePowerDelay is the number of epochs after ElectionPeriodStart, after
|
||||
// which the miner is slashed
|
||||
//
|
||||
|
||||
@@ -83,6 +83,8 @@ const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds)
|
||||
|
||||
const PropagationDelaySecs = uint64(6)
|
||||
|
||||
var EquivocationDelaySecs = uint64(2)
|
||||
|
||||
// BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start
|
||||
const BootstrapPeerThreshold = 2
|
||||
|
||||
|
||||
@@ -123,6 +123,8 @@ const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds)
|
||||
|
||||
var PropagationDelaySecs = uint64(10)
|
||||
|
||||
var EquivocationDelaySecs = uint64(2)
|
||||
|
||||
// BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start
|
||||
const BootstrapPeerThreshold = 4
|
||||
|
||||
|
||||
@@ -121,6 +121,8 @@ const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds)
|
||||
|
||||
const PropagationDelaySecs = uint64(6)
|
||||
|
||||
var EquivocationDelaySecs = uint64(2)
|
||||
|
||||
// BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start
|
||||
const BootstrapPeerThreshold = 2
|
||||
|
||||
|
||||
@@ -106,6 +106,8 @@ var ConsensusMinerMinPower = abi.NewStoragePower(10 << 40)
|
||||
var PreCommitChallengeDelay = abi.ChainEpoch(150)
|
||||
var PropagationDelaySecs = uint64(10)
|
||||
|
||||
var EquivocationDelaySecs = uint64(2)
|
||||
|
||||
func init() {
|
||||
if os.Getenv("LOTUS_USE_TEST_ADDRESSES") != "1" {
|
||||
SetAddressNetwork(address.Mainnet)
|
||||
|
||||
@@ -9,7 +9,6 @@ package build
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
@@ -34,6 +33,7 @@ var (
|
||||
MinimumBaseFee = int64(100)
|
||||
BlockDelaySecs = uint64(builtin2.EpochDurationSeconds)
|
||||
PropagationDelaySecs = uint64(6)
|
||||
EquivocationDelaySecs = uint64(2)
|
||||
SupportedProofTypes = []abi.RegisteredSealProof{
|
||||
abi.RegisteredSealProof_StackedDrg32GiBV1,
|
||||
abi.RegisteredSealProof_StackedDrg64GiBV1,
|
||||
@@ -139,7 +139,3 @@ const BootstrapPeerThreshold = 1
|
||||
// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint.
|
||||
// As per https://github.com/ethereum-lists/chains
|
||||
const Eip155ChainId = 31415926
|
||||
|
||||
// Reducing the delivery delay for equivocation of
|
||||
// consistent broadcast to just half a second.
|
||||
var CBDeliveryDelay = 500 * time.Millisecond
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ func BuildTypeString() string {
|
||||
}
|
||||
|
||||
// BuildVersion is the local build version
|
||||
const BuildVersion = "1.23.3"
|
||||
const BuildVersion = "1.23.5-dev"
|
||||
|
||||
func UserVersion() string {
|
||||
if os.Getenv("LOTUS_VERSION_IGNORE_COMMIT") == "1" {
|
||||
|
||||
+3
-3
@@ -3,14 +3,14 @@ package chain
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"github.com/hashicorp/golang-lru/arc/v2"
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
)
|
||||
|
||||
type BadBlockCache struct {
|
||||
badBlocks *lru.ARCCache[cid.Cid, BadBlockReason]
|
||||
badBlocks *arc.ARCCache[cid.Cid, BadBlockReason]
|
||||
}
|
||||
|
||||
type BadBlockReason struct {
|
||||
@@ -43,7 +43,7 @@ func (bbr BadBlockReason) String() string {
|
||||
}
|
||||
|
||||
func NewBadBlockCache() *BadBlockCache {
|
||||
cache, err := lru.NewARC[cid.Cid, BadBlockReason](build.BadBlockCacheSize)
|
||||
cache, err := arc.NewARC[cid.Cid, BadBlockReason](build.BadBlockCacheSize)
|
||||
if err != nil {
|
||||
panic(err) // ok
|
||||
}
|
||||
|
||||
+13
-13
@@ -29,19 +29,6 @@ import (
|
||||
|
||||
var log = logging.Logger("drand")
|
||||
|
||||
type drandPeer struct {
|
||||
addr string
|
||||
tls bool
|
||||
}
|
||||
|
||||
func (dp *drandPeer) Address() string {
|
||||
return dp.addr
|
||||
}
|
||||
|
||||
func (dp *drandPeer) IsTLS() bool {
|
||||
return dp.tls
|
||||
}
|
||||
|
||||
// DrandBeacon connects Lotus with a drand network in order to provide
|
||||
// randomness to the system in a way that's aligned with Filecoin rounds/epochs.
|
||||
//
|
||||
@@ -235,3 +222,16 @@ func (db *DrandBeacon) maxBeaconRoundV2(latestTs uint64) uint64 {
|
||||
}
|
||||
|
||||
var _ beacon.RandomBeacon = (*DrandBeacon)(nil)
|
||||
|
||||
func BeaconScheduleFromDrandSchedule(dcs dtypes.DrandSchedule, genesisTime uint64, ps *pubsub.PubSub) (beacon.Schedule, error) {
|
||||
shd := beacon.Schedule{}
|
||||
for _, dc := range dcs {
|
||||
bc, err := NewDrandBeacon(genesisTime, build.BlockDelaySecs, ps, dc.Config)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("creating drand beacon: %w", err)
|
||||
}
|
||||
shd = append(shd, beacon.BeaconPoint{Start: dc.Start, Beacon: bc})
|
||||
}
|
||||
|
||||
return shd, nil
|
||||
}
|
||||
|
||||
@@ -147,9 +147,6 @@ func (t *TipSetExecutor) ApplyBlocks(ctx context.Context,
|
||||
return xerrors.Errorf("callback failed on cron message: %w", err)
|
||||
}
|
||||
}
|
||||
if ret.ExitCode != 0 {
|
||||
return xerrors.Errorf("cron exit was non-zero: %d", ret.ExitCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -91,9 +91,6 @@ var RewardFunc = func(ctx context.Context, vmi vm.Interface, em stmgr.ExecMonito
|
||||
}
|
||||
}
|
||||
|
||||
if ret.ExitCode != 0 {
|
||||
return xerrors.Errorf("reward application message failed (exit %d): %s", ret.ExitCode, ret.ActorErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"github.com/hashicorp/golang-lru/arc/v2"
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
@@ -14,11 +14,11 @@ type messageCache struct {
|
||||
api EventAPI
|
||||
|
||||
blockMsgLk sync.Mutex
|
||||
blockMsgCache *lru.ARCCache[cid.Cid, *api.BlockMessages]
|
||||
blockMsgCache *arc.ARCCache[cid.Cid, *api.BlockMessages]
|
||||
}
|
||||
|
||||
func newMessageCache(a EventAPI) *messageCache {
|
||||
blsMsgCache, _ := lru.NewARC[cid.Cid, *api.BlockMessages](500)
|
||||
blsMsgCache, _ := arc.NewARC[cid.Cid, *api.BlockMessages](500)
|
||||
|
||||
return &messageCache{
|
||||
api: a,
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
)
|
||||
|
||||
type contextStore struct {
|
||||
ctx context.Context
|
||||
cst *cbor.BasicIpldStore
|
||||
}
|
||||
|
||||
func (cs *contextStore) Context() context.Context {
|
||||
return cs.ctx
|
||||
}
|
||||
|
||||
func (cs *contextStore) Get(ctx context.Context, c cid.Cid, out interface{}) error {
|
||||
return cs.cst.Get(ctx, c, out)
|
||||
}
|
||||
|
||||
func (cs *contextStore) Put(ctx context.Context, v interface{}) (cid.Cid, error) {
|
||||
return cs.cst.Put(ctx, v)
|
||||
}
|
||||
@@ -381,7 +381,7 @@ func (c *client) sendRequestToPeer(ctx context.Context, peer peer.ID, req *Reque
|
||||
defer span.End()
|
||||
if span.IsRecordingEvents() {
|
||||
span.AddAttributes(
|
||||
trace.StringAttribute("peer", peer.Pretty()),
|
||||
trace.StringAttribute("peer", peer.String()),
|
||||
)
|
||||
}
|
||||
defer func() {
|
||||
|
||||
+17
-6
@@ -362,7 +362,7 @@ func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m add
|
||||
rbase = entries[len(entries)-1]
|
||||
}
|
||||
|
||||
eproof, err := IsRoundWinner(ctx, pts, round, m, rbase, mbi, mc)
|
||||
eproof, err := IsRoundWinner(ctx, round, m, rbase, mbi, mc)
|
||||
if err != nil {
|
||||
return nil, nil, nil, xerrors.Errorf("checking round winner failed: %w", err)
|
||||
}
|
||||
@@ -449,18 +449,19 @@ func (cg *ChainGen) NextTipSetFromMiners(base *types.TipSet, miners []address.Ad
|
||||
}
|
||||
|
||||
func (cg *ChainGen) NextTipSetFromMinersWithMessagesAndNulls(base *types.TipSet, miners []address.Address, msgs [][]*types.SignedMessage, nulls abi.ChainEpoch) (*store.FullTipSet, error) {
|
||||
ctx := context.TODO()
|
||||
var blks []*types.FullBlock
|
||||
|
||||
for round := base.Height() + nulls + 1; len(blks) == 0; round++ {
|
||||
for mi, m := range miners {
|
||||
bvals, et, ticket, err := cg.nextBlockProof(context.TODO(), base, m, round)
|
||||
bvals, et, ticket, err := cg.nextBlockProof(ctx, base, m, round)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("next block proof: %w", err)
|
||||
}
|
||||
|
||||
if et != nil {
|
||||
// TODO: maybe think about passing in more real parameters to this?
|
||||
wpost, err := cg.eppProvs[m].ComputeProof(context.TODO(), nil, nil, round, network.Version0)
|
||||
wpost, err := cg.eppProvs[m].ComputeProof(ctx, nil, nil, round, network.Version0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -476,8 +477,18 @@ func (cg *ChainGen) NextTipSetFromMinersWithMessagesAndNulls(base *types.TipSet,
|
||||
}
|
||||
|
||||
fts := store.NewFullTipSet(blks)
|
||||
if err := cg.cs.PutTipSet(context.TODO(), fts.TipSet()); err != nil {
|
||||
return nil, err
|
||||
if err := cg.cs.PersistTipsets(ctx, []*types.TipSet{fts.TipSet()}); err != nil {
|
||||
return nil, xerrors.Errorf("failed to persist tipset: %w", err)
|
||||
}
|
||||
|
||||
for _, blk := range blks {
|
||||
if err := cg.cs.AddToTipSetTracker(ctx, blk.Header); err != nil {
|
||||
return nil, xerrors.Errorf("failed to add to tipset tracker: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := cg.cs.RefreshHeaviestTipSet(ctx, fts.TipSet().Height()); err != nil {
|
||||
return nil, xerrors.Errorf("failed to put tipset: %w", err)
|
||||
}
|
||||
|
||||
cg.CurTipset = fts
|
||||
@@ -628,7 +639,7 @@ func (wpp *wppProvider) ComputeProof(context.Context, []proof7.ExtendedSectorInf
|
||||
return ValidWpostForTesting, nil
|
||||
}
|
||||
|
||||
func IsRoundWinner(ctx context.Context, ts *types.TipSet, round abi.ChainEpoch,
|
||||
func IsRoundWinner(ctx context.Context, round abi.ChainEpoch,
|
||||
miner address.Address, brand types.BeaconEntry, mbi *api.MiningBaseInfo, a MiningCheckAPI) (*types.ElectionProof, error) {
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
@@ -39,23 +39,6 @@ func (ps *Store) save(ctx context.Context, state *FundedAddressState) error {
|
||||
return ps.ds.Put(ctx, k, b)
|
||||
}
|
||||
|
||||
// get the state for the given address
|
||||
func (ps *Store) get(ctx context.Context, addr address.Address) (*FundedAddressState, error) {
|
||||
k := dskeyForAddr(addr)
|
||||
|
||||
data, err := ps.ds.Get(ctx, k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var state FundedAddressState
|
||||
err = cborrpc.ReadCborRPC(bytes.NewReader(data), &state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &state, nil
|
||||
}
|
||||
|
||||
// forEach calls iter with each address in the datastore
|
||||
func (ps *Store) forEach(ctx context.Context, iter func(*FundedAddressState)) error {
|
||||
res, err := ps.ds.Query(ctx, dsq.Query{Prefix: dsKeyAddr})
|
||||
|
||||
@@ -63,6 +63,9 @@ var MaxNonceGap = uint64(4)
|
||||
|
||||
const MaxMessageSize = 64 << 10 // 64KiB
|
||||
|
||||
// NOTE: When adding a new error type, please make sure to add the new error type in
|
||||
// func (mv *MessageValidator) Validate(ctx context.Context, pid peer.ID, msg *pubsub.Message)
|
||||
// in /chain/sub/incoming.go
|
||||
var (
|
||||
ErrMessageTooBig = errors.New("message too big")
|
||||
|
||||
@@ -207,8 +210,10 @@ func ComputeRBF(curPrem abi.TokenAmount, replaceByFeeRatio types.Percent) abi.To
|
||||
|
||||
func CapGasFee(mff dtypes.DefaultMaxFeeFunc, msg *types.Message, sendSpec *api.MessageSendSpec) {
|
||||
var maxFee abi.TokenAmount
|
||||
var maximizeFeeCap bool
|
||||
if sendSpec != nil {
|
||||
maxFee = sendSpec.MaxFee
|
||||
maximizeFeeCap = sendSpec.MaximizeFeeCap
|
||||
}
|
||||
if maxFee.Int == nil || maxFee.Equals(big.Zero()) {
|
||||
mf, err := mff()
|
||||
@@ -219,15 +224,12 @@ func CapGasFee(mff dtypes.DefaultMaxFeeFunc, msg *types.Message, sendSpec *api.M
|
||||
maxFee = mf
|
||||
}
|
||||
|
||||
gl := types.NewInt(uint64(msg.GasLimit))
|
||||
totalFee := types.BigMul(msg.GasFeeCap, gl)
|
||||
|
||||
if totalFee.LessThanEqual(maxFee) {
|
||||
msg.GasPremium = big.Min(msg.GasFeeCap, msg.GasPremium) // cap premium at FeeCap
|
||||
return
|
||||
gaslimit := types.NewInt(uint64(msg.GasLimit))
|
||||
totalFee := types.BigMul(msg.GasFeeCap, gaslimit)
|
||||
if maximizeFeeCap || totalFee.GreaterThan(maxFee) {
|
||||
msg.GasFeeCap = big.Div(maxFee, gaslimit)
|
||||
}
|
||||
|
||||
msg.GasFeeCap = big.Div(maxFee, gl)
|
||||
msg.GasPremium = big.Min(msg.GasFeeCap, msg.GasPremium) // cap premium at FeeCap
|
||||
}
|
||||
|
||||
|
||||
@@ -232,11 +232,6 @@ func (sm *StateManager) hasExpensiveForkBetween(parent, height abi.ChainEpoch) b
|
||||
return false
|
||||
}
|
||||
|
||||
func (sm *StateManager) hasExpensiveFork(height abi.ChainEpoch) bool {
|
||||
_, ok := sm.expensiveUpgrades[height]
|
||||
return ok
|
||||
}
|
||||
|
||||
func runPreMigration(ctx context.Context, sm *StateManager, fn PreMigrationFunc, cache *nv16.MemMigrationCache, ts *types.TipSet) {
|
||||
height := ts.Height()
|
||||
parent := ts.ParentState()
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"github.com/hashicorp/golang-lru/arc/v2"
|
||||
"github.com/ipfs/go-cid"
|
||||
dstore "github.com/ipfs/go-datastore"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
@@ -156,7 +156,7 @@ type StateManager struct {
|
||||
|
||||
// We keep a small cache for calls to ExecutionTrace which helps improve
|
||||
// performance for node operators like exchanges and block explorers
|
||||
execTraceCache *lru.ARCCache[types.TipSetKey, tipSetCacheEntry]
|
||||
execTraceCache *arc.ARCCache[types.TipSetKey, tipSetCacheEntry]
|
||||
// We need a lock while making the copy as to prevent other callers
|
||||
// overwrite the cache while making the copy
|
||||
execTraceCacheLock sync.Mutex
|
||||
@@ -213,10 +213,10 @@ func NewStateManager(cs *store.ChainStore, exec Executor, sys vm.SyscallBuilder,
|
||||
}
|
||||
|
||||
log.Debugf("execTraceCache size: %d", execTraceCacheSize)
|
||||
var execTraceCache *lru.ARCCache[types.TipSetKey, tipSetCacheEntry]
|
||||
var execTraceCache *arc.ARCCache[types.TipSetKey, tipSetCacheEntry]
|
||||
var err error
|
||||
if execTraceCacheSize > 0 {
|
||||
execTraceCache, err = lru.NewARC[types.TipSetKey, tipSetCacheEntry](execTraceCacheSize)
|
||||
execTraceCache, err = arc.NewARC[types.TipSetKey, tipSetCacheEntry](execTraceCacheSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -388,6 +388,14 @@ func (sm *StateManager) GetCirculatingSupply(ctx context.Context, height abi.Cha
|
||||
circ := big.Zero()
|
||||
unCirc := big.Zero()
|
||||
err := st.ForEach(func(a address.Address, actor *types.Actor) error {
|
||||
// this can be a lengthy operation, we need to cancel early when
|
||||
// the context is cancelled to avoid resource exhaustion
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// this will cause ForEach to return
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
switch {
|
||||
case actor.Balance.IsZero():
|
||||
// Do nothing for zero-balance actors
|
||||
|
||||
@@ -70,7 +70,7 @@ func TestChainCheckpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
// See if the chain will take the fork, it shouldn't.
|
||||
err = cs.MaybeTakeHeavierTipSet(context.Background(), last)
|
||||
err = cs.RefreshHeaviestTipSet(context.Background(), last.Height())
|
||||
require.NoError(t, err)
|
||||
head = cs.GetHeaviestTipSet()
|
||||
require.True(t, head.Equals(checkpoint))
|
||||
@@ -80,7 +80,7 @@ func TestChainCheckpoint(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now switch to the other fork.
|
||||
err = cs.MaybeTakeHeavierTipSet(context.Background(), last)
|
||||
err = cs.RefreshHeaviestTipSet(context.Background(), last.Height())
|
||||
require.NoError(t, err)
|
||||
head = cs.GetHeaviestTipSet()
|
||||
require.True(t, head.Equals(last))
|
||||
|
||||
+12
-10
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/filecoin-project/lotus/chain/consensus/filcns"
|
||||
"github.com/filecoin-project/lotus/chain/gen"
|
||||
"github.com/filecoin-project/lotus/chain/store"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/types/mock"
|
||||
)
|
||||
|
||||
@@ -47,28 +48,29 @@ func TestIndexSeeks(t *testing.T) {
|
||||
}
|
||||
|
||||
cur := mock.TipSet(gen)
|
||||
if err := cs.PutTipSet(ctx, mock.TipSet(gen)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.NoError(t, cs.SetGenesis(ctx, 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)
|
||||
}
|
||||
nextBlk := mock.MkBlock(cur, 1, 1)
|
||||
nextts := mock.TipSet(nextBlk)
|
||||
assert.NoError(t, cs.PersistTipsets(ctx, []*types.TipSet{nextts}))
|
||||
assert.NoError(t, cs.AddToTipSetTracker(ctx, nextBlk))
|
||||
cur = nextts
|
||||
}
|
||||
|
||||
assert.NoError(t, cs.RefreshHeaviestTipSet(ctx, cur.Height()))
|
||||
|
||||
// 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 {
|
||||
assert.NoError(t, cs.PersistTipsets(ctx, []*types.TipSet{skipts}))
|
||||
assert.NoError(t, cs.AddToTipSetTracker(ctx, skip))
|
||||
|
||||
if err := cs.RefreshHeaviestTipSet(ctx, skip.Height); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
||||
+14
-12
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/ipld/go-car"
|
||||
carutil "github.com/ipld/go-car/util"
|
||||
carv2 "github.com/ipld/go-car/v2"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
"github.com/multiformats/go-multicodec"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
"go.uber.org/atomic"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -369,14 +369,16 @@ func (s *walkScheduler) Wait() error {
|
||||
}
|
||||
|
||||
func (s *walkScheduler) enqueueIfNew(task walkTask) {
|
||||
if task.c.Prefix().MhType == mh.IDENTITY {
|
||||
if multicodec.Code(task.c.Prefix().MhType) == multicodec.Identity {
|
||||
//log.Infow("ignored", "cid", todo.c.String())
|
||||
return
|
||||
}
|
||||
|
||||
// This lets through RAW and CBOR blocks, the only two types that we
|
||||
// end up writing to the exported CAR.
|
||||
if task.c.Prefix().Codec != cid.Raw && task.c.Prefix().Codec != cid.DagCBOR {
|
||||
// This lets through RAW, CBOR, and DagCBOR blocks, the only types that we end up writing to
|
||||
// the exported CAR.
|
||||
switch multicodec.Code(task.c.Prefix().Codec) {
|
||||
case multicodec.Cbor, multicodec.DagCbor, multicodec.Raw:
|
||||
default:
|
||||
//log.Infow("ignored", "cid", todo.c.String())
|
||||
return
|
||||
}
|
||||
@@ -450,7 +452,8 @@ func (s *walkScheduler) processTask(t walkTask, workerN int) error {
|
||||
// We exported the ipld block. If it wasn't a CBOR block, there's nothing
|
||||
// else to do and we can bail out early as it won't have any links
|
||||
// etc.
|
||||
if t.c.Prefix().Codec != cid.DagCBOR || t.c.Prefix().MhType == mh.IDENTITY {
|
||||
if multicodec.Code(t.c.Prefix().Codec) != multicodec.DagCbor ||
|
||||
multicodec.Code(t.c.Prefix().MhType) == multicodec.Identity {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -683,14 +686,13 @@ func (cs *ChainStore) WalkSnapshot(ctx context.Context, ts *types.TipSet, inclRe
|
||||
prefix := c.Prefix()
|
||||
|
||||
// Don't include identity CIDs.
|
||||
if prefix.MhType == mh.IDENTITY {
|
||||
if multicodec.Code(prefix.MhType) == multicodec.Identity {
|
||||
continue
|
||||
}
|
||||
|
||||
// We only include raw and dagcbor, for now.
|
||||
// Raw for "code" CIDs.
|
||||
switch prefix.Codec {
|
||||
case cid.Raw, cid.DagCBOR:
|
||||
// We only include raw, cbor, and dagcbor, for now.
|
||||
switch multicodec.Code(prefix.Codec) {
|
||||
case multicodec.Cbor, multicodec.DagCbor, multicodec.Raw:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
@@ -722,7 +724,7 @@ func (cs *ChainStore) WalkSnapshot(ctx context.Context, ts *types.TipSet, inclRe
|
||||
}
|
||||
|
||||
func recurseLinks(ctx context.Context, bs bstore.Blockstore, walked *cid.Set, root cid.Cid, in []cid.Cid) ([]cid.Cid, error) {
|
||||
if root.Prefix().Codec != cid.DagCBOR {
|
||||
if multicodec.Code(root.Prefix().Codec) != multicodec.DagCbor {
|
||||
return in, nil
|
||||
}
|
||||
|
||||
|
||||
+171
-90
@@ -11,7 +11,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"github.com/hashicorp/golang-lru/arc/v2"
|
||||
block "github.com/ipfs/go-block-format"
|
||||
"github.com/ipfs/go-cid"
|
||||
dstore "github.com/ipfs/go-datastore"
|
||||
@@ -120,8 +120,8 @@ type ChainStore struct {
|
||||
reorgCh chan<- reorg
|
||||
reorgNotifeeCh chan ReorgNotifee
|
||||
|
||||
mmCache *lru.ARCCache[cid.Cid, mmCids]
|
||||
tsCache *lru.ARCCache[types.TipSetKey, *types.TipSet]
|
||||
mmCache *arc.ARCCache[cid.Cid, mmCids]
|
||||
tsCache *arc.ARCCache[types.TipSetKey, *types.TipSet]
|
||||
|
||||
evtTypes [1]journal.EventType
|
||||
journal journal.Journal
|
||||
@@ -133,8 +133,8 @@ type ChainStore struct {
|
||||
}
|
||||
|
||||
func NewChainStore(chainBs bstore.Blockstore, stateBs bstore.Blockstore, ds dstore.Batching, weight WeightFunc, j journal.Journal) *ChainStore {
|
||||
c, _ := lru.NewARC[cid.Cid, mmCids](DefaultMsgMetaCacheSize)
|
||||
tsc, _ := lru.NewARC[types.TipSetKey, *types.TipSet](DefaultTipSetCacheSize)
|
||||
c, _ := arc.NewARC[cid.Cid, mmCids](DefaultMsgMetaCacheSize)
|
||||
tsc, _ := arc.NewARC[types.TipSetKey, *types.TipSet](DefaultTipSetCacheSize)
|
||||
if j == nil {
|
||||
j = journal.NilJournal()
|
||||
}
|
||||
@@ -367,49 +367,32 @@ func (cs *ChainStore) UnmarkBlockAsValidated(ctx context.Context, blkid cid.Cid)
|
||||
func (cs *ChainStore) SetGenesis(ctx context.Context, b *types.BlockHeader) error {
|
||||
ts, err := types.NewTipSet([]*types.BlockHeader{b})
|
||||
if err != nil {
|
||||
return err
|
||||
return xerrors.Errorf("failed to construct genesis tipset: %w", err)
|
||||
}
|
||||
|
||||
if err := cs.PutTipSet(ctx, ts); err != nil {
|
||||
return err
|
||||
if err := cs.PersistTipsets(ctx, []*types.TipSet{ts}); err != nil {
|
||||
return xerrors.Errorf("failed to persist genesis tipset: %w", err)
|
||||
}
|
||||
|
||||
if err := cs.AddToTipSetTracker(ctx, b); err != nil {
|
||||
return xerrors.Errorf("failed to add genesis tipset to tracker: %w", err)
|
||||
}
|
||||
|
||||
if err := cs.RefreshHeaviestTipSet(ctx, ts.Height()); err != nil {
|
||||
return xerrors.Errorf("failed to put genesis tipset: %w", err)
|
||||
}
|
||||
|
||||
return cs.metadataDs.Put(ctx, dstore.NewKey("0"), b.Cid().Bytes())
|
||||
}
|
||||
|
||||
func (cs *ChainStore) PutTipSet(ctx context.Context, ts *types.TipSet) error {
|
||||
if err := cs.PersistTipsets(ctx, []*types.TipSet{ts}); err != nil {
|
||||
return xerrors.Errorf("failed to persist tipset: %w", err)
|
||||
}
|
||||
|
||||
expanded, err := cs.expandTipset(ctx, ts.Blocks()[0])
|
||||
if err != nil {
|
||||
return xerrors.Errorf("errored while expanding tipset: %w", err)
|
||||
}
|
||||
|
||||
if expanded.Key() != ts.Key() {
|
||||
log.Debugf("expanded %s into %s\n", ts.Cids(), expanded.Cids())
|
||||
|
||||
tsBlk, err := expanded.Key().ToStorageBlock()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to get tipset key block: %w", err)
|
||||
}
|
||||
|
||||
if err = cs.chainLocalBlockstore.Put(ctx, tsBlk); err != nil {
|
||||
return xerrors.Errorf("failed to put tipset key block: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := cs.MaybeTakeHeavierTipSet(ctx, expanded); err != nil {
|
||||
return xerrors.Errorf("MaybeTakeHeavierTipSet failed in PutTipSet: %w", err)
|
||||
}
|
||||
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 and does not exceed the maximum fork length.
|
||||
func (cs *ChainStore) MaybeTakeHeavierTipSet(ctx context.Context, ts *types.TipSet) error {
|
||||
// RefreshHeaviestTipSet receives a newTsHeight at which a new tipset might exist. It then:
|
||||
// - "refreshes" the heaviest tipset that can be formed at its current heaviest height
|
||||
// - if equivocation is detected among the miners of the current heaviest tipset, the head is immediately updated to the heaviest tipset that can be formed in a range of 5 epochs
|
||||
//
|
||||
// - forms the best tipset that can be formed at the _input_ height
|
||||
// - compares the three tipset weights: "current" heaviest tipset, "refreshed" tipset, and best tipset at newTsHeight
|
||||
// - updates "current" heaviest to the heaviest of those 3 tipsets (if an update is needed), assuming it doesn't violate the maximum fork rule
|
||||
func (cs *ChainStore) RefreshHeaviestTipSet(ctx context.Context, newTsHeight abi.ChainEpoch) error {
|
||||
for {
|
||||
cs.heaviestLk.Lock()
|
||||
if len(cs.reorgCh) < reorgChBuf/2 {
|
||||
@@ -426,39 +409,90 @@ func (cs *ChainStore) MaybeTakeHeavierTipSet(ctx context.Context, ts *types.TipS
|
||||
|
||||
defer cs.heaviestLk.Unlock()
|
||||
|
||||
if ts.Equals(cs.heaviest) {
|
||||
heaviestWeight, err := cs.weight(ctx, cs.StateBlockstore(), cs.heaviest)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to calculate currentHeaviest's weight: %w", err)
|
||||
}
|
||||
|
||||
heaviestHeight := abi.ChainEpoch(0)
|
||||
if cs.heaviest != nil {
|
||||
heaviestHeight = cs.heaviest.Height()
|
||||
}
|
||||
|
||||
// Before we look at newTs, let's refresh best tipset at current head's height -- this is done to detect equivocation
|
||||
newHeaviest, newHeaviestWeight, err := cs.FormHeaviestTipSetForHeight(ctx, heaviestHeight)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to reform head at same height: %w", err)
|
||||
}
|
||||
|
||||
// Equivocation has occurred! We need a new head NOW!
|
||||
if newHeaviest == nil || newHeaviestWeight.LessThan(heaviestWeight) {
|
||||
log.Warnf("chainstore heaviest tipset's weight SHRANK from %d (%s) to %d (%s) due to equivocation", heaviestWeight, cs.heaviest, newHeaviestWeight, newHeaviest)
|
||||
// Unfortunately, we don't know what the right height to form a new heaviest tipset is.
|
||||
// It is _probably_, but not _necessarily_, heaviestHeight.
|
||||
// So, we need to explore a range of epochs, finding the heaviest tipset in that range.
|
||||
// We thus try to form the heaviest tipset for 5 epochs above heaviestHeight (most of which will likely not exist),
|
||||
// as well as for 5 below.
|
||||
// This is slow, but we expect to almost-never be here (only if miners are equivocating, which carries a hefty penalty).
|
||||
for i := heaviestHeight + 5; i > heaviestHeight-5; i-- {
|
||||
possibleHeaviestTs, possibleHeaviestWeight, err := cs.FormHeaviestTipSetForHeight(ctx, i)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to produce head at height %d: %w", i, err)
|
||||
}
|
||||
|
||||
if possibleHeaviestWeight.GreaterThan(newHeaviestWeight) {
|
||||
newHeaviestWeight = possibleHeaviestWeight
|
||||
newHeaviest = possibleHeaviestTs
|
||||
}
|
||||
}
|
||||
|
||||
// if we've found something, we know it's the heaviest equivocation-free head, take it IMMEDIATELY
|
||||
if newHeaviest != nil {
|
||||
errTake := cs.takeHeaviestTipSet(ctx, newHeaviest)
|
||||
if errTake != nil {
|
||||
return xerrors.Errorf("failed to take newHeaviest tipset as head: %w", err)
|
||||
}
|
||||
} else {
|
||||
// if we haven't found something, just stay with our equivocation-y head
|
||||
newHeaviest = cs.heaviest
|
||||
}
|
||||
}
|
||||
|
||||
// if the new height we were notified about isn't what we just refreshed at, see if we have a heavier tipset there
|
||||
if newTsHeight != newHeaviest.Height() {
|
||||
bestTs, bestTsWeight, err := cs.FormHeaviestTipSetForHeight(ctx, newTsHeight)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to form new heaviest tipset at height %d: %w", newTsHeight, err)
|
||||
}
|
||||
|
||||
heavier := bestTsWeight.GreaterThan(newHeaviestWeight)
|
||||
if bestTsWeight.Equals(newHeaviestWeight) {
|
||||
heavier = breakWeightTie(bestTs, newHeaviest)
|
||||
}
|
||||
|
||||
if heavier {
|
||||
newHeaviest = bestTs
|
||||
}
|
||||
}
|
||||
|
||||
// Everything's the same as before, exit early
|
||||
if newHeaviest.Equals(cs.heaviest) {
|
||||
return nil
|
||||
}
|
||||
|
||||
w, err := cs.weight(ctx, cs.StateBlockstore(), ts)
|
||||
// At this point, it MUST be true that newHeaviest is heavier than cs.heaviest -- update if fork allows
|
||||
exceeds, err := cs.exceedsForkLength(ctx, cs.heaviest, newHeaviest)
|
||||
if err != nil {
|
||||
return err
|
||||
return xerrors.Errorf("failed to check fork length: %w", err)
|
||||
}
|
||||
heaviestW, err := cs.weight(ctx, cs.StateBlockstore(), cs.heaviest)
|
||||
|
||||
if exceeds {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = cs.takeHeaviestTipSet(ctx, newHeaviest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
heavier := w.GreaterThan(heaviestW)
|
||||
if w.Equals(heaviestW) && !ts.Equals(cs.heaviest) {
|
||||
log.Errorw("weight draw", "currTs", cs.heaviest, "ts", ts)
|
||||
heavier = breakWeightTie(ts, cs.heaviest)
|
||||
}
|
||||
|
||||
if heavier {
|
||||
// TODO: don't do this for initial sync. Now that we don't have a
|
||||
// difference between 'bootstrap sync' and 'caught up' sync, we need
|
||||
// some other heuristic.
|
||||
|
||||
exceeds, err := cs.exceedsForkLength(ctx, cs.heaviest, ts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exceeds {
|
||||
return nil
|
||||
}
|
||||
|
||||
return cs.takeHeaviestTipSet(ctx, ts)
|
||||
return xerrors.Errorf("failed to take heaviest tipset: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -655,6 +689,16 @@ func (cs *ChainStore) takeHeaviestTipSet(ctx context.Context, ts *types.TipSet)
|
||||
return err
|
||||
}
|
||||
|
||||
// write the tipsetkey block to the blockstore for EthAPI queries
|
||||
tsBlk, err := ts.Key().ToStorageBlock()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to get tipset key block: %w", err)
|
||||
}
|
||||
|
||||
if err = cs.chainLocalBlockstore.Put(ctx, tsBlk); err != nil {
|
||||
return xerrors.Errorf("failed to put tipset key block: %w", err)
|
||||
}
|
||||
|
||||
if prevHeaviest != nil { // buf
|
||||
if len(cs.reorgCh) > 0 {
|
||||
log.Warnf("Reorg channel running behind, %d reorgs buffered", len(cs.reorgCh))
|
||||
@@ -904,6 +948,14 @@ func ReorgOps(ctx context.Context, lts func(ctx context.Context, _ types.TipSetK
|
||||
|
||||
var leftChain, rightChain []*types.TipSet
|
||||
for !left.Equals(right) {
|
||||
// this can take a long time and lot of memory if the tipsets are far apart
|
||||
// since it can be reached through remote calls, we need to
|
||||
// cancel early when possible to prevent resource exhaustion.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
if left.Height() > right.Height() {
|
||||
leftChain = append(leftChain, left)
|
||||
par, err := lts(ctx, left.Parents())
|
||||
@@ -960,7 +1012,7 @@ func (cs *ChainStore) AddToTipSetTracker(ctx context.Context, b *types.BlockHead
|
||||
// This means that we ideally want to keep only most recent 900 epochs in here
|
||||
// Golang's map iteration starts at a random point in a map.
|
||||
// With 5 tries per epoch, and 900 entries to keep, on average we will have
|
||||
// ~136 garbage entires in the `cs.tipsets` map. (solve for 1-(1-x/(900+x))^5 == 0.5)
|
||||
// ~136 garbage entries in the `cs.tipsets` map. (solve for 1-(1-x/(900+x))^5 == 0.5)
|
||||
// Seems good enough to me
|
||||
|
||||
for height := range cs.tipsets {
|
||||
@@ -975,6 +1027,7 @@ func (cs *ChainStore) AddToTipSetTracker(ctx context.Context, b *types.BlockHead
|
||||
return nil
|
||||
}
|
||||
|
||||
// PersistTipsets writes the provided blocks and the TipSetKey objects to the blockstore
|
||||
func (cs *ChainStore) PersistTipsets(ctx context.Context, tipsets []*types.TipSet) error {
|
||||
toPersist := make([]*types.BlockHeader, 0, len(tipsets)*int(build.BlocksPerEpoch))
|
||||
tsBlks := make([]block.Block, 0, len(tipsets))
|
||||
@@ -1027,44 +1080,72 @@ func (cs *ChainStore) persistBlockHeaders(ctx context.Context, b ...*types.Block
|
||||
return err
|
||||
}
|
||||
|
||||
func (cs *ChainStore) expandTipset(ctx context.Context, b *types.BlockHeader) (*types.TipSet, error) {
|
||||
// Hold lock for the whole function for now, if it becomes a problem we can
|
||||
// fix pretty easily
|
||||
// FormHeaviestTipSetForHeight looks up all valid blocks at a given height, and returns the heaviest tipset that can be made at that height
|
||||
// It does not consider ANY blocks from miners that have "equivocated" (produced 2 blocks at the same height)
|
||||
func (cs *ChainStore) FormHeaviestTipSetForHeight(ctx context.Context, height abi.ChainEpoch) (*types.TipSet, types.BigInt, error) {
|
||||
cs.tstLk.Lock()
|
||||
defer cs.tstLk.Unlock()
|
||||
|
||||
all := []*types.BlockHeader{b}
|
||||
|
||||
tsets, ok := cs.tipsets[b.Height]
|
||||
blockCids, ok := cs.tipsets[height]
|
||||
if !ok {
|
||||
return types.NewTipSet(all)
|
||||
return nil, types.NewInt(0), nil
|
||||
}
|
||||
|
||||
inclMiners := map[address.Address]cid.Cid{b.Miner: b.Cid()}
|
||||
for _, bhc := range tsets {
|
||||
if bhc == b.Cid() {
|
||||
continue
|
||||
}
|
||||
// First, identify "bad" miners for the height
|
||||
|
||||
seenMiners := map[address.Address]struct{}{}
|
||||
badMiners := map[address.Address]struct{}{}
|
||||
blocks := make([]*types.BlockHeader, 0, len(blockCids))
|
||||
for _, bhc := range blockCids {
|
||||
h, err := cs.GetBlock(ctx, bhc)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to load block (%s) for tipset expansion: %w", bhc, err)
|
||||
return nil, types.NewInt(0), xerrors.Errorf("failed to load block (%s) for tipset expansion: %w", bhc, err)
|
||||
}
|
||||
|
||||
if cid, found := inclMiners[h.Miner]; found {
|
||||
log.Warnf("Have multiple blocks from miner %s at height %d in our tipset cache %s-%s", h.Miner, h.Height, h.Cid(), cid)
|
||||
if _, seen := seenMiners[h.Miner]; seen {
|
||||
badMiners[h.Miner] = struct{}{}
|
||||
continue
|
||||
}
|
||||
seenMiners[h.Miner] = struct{}{}
|
||||
blocks = append(blocks, h)
|
||||
}
|
||||
|
||||
if types.CidArrsEqual(h.Parents, b.Parents) {
|
||||
all = append(all, h)
|
||||
inclMiners[h.Miner] = bhc
|
||||
// Next, group by parent tipset
|
||||
|
||||
formableTipsets := make(map[types.TipSetKey][]*types.BlockHeader, 0)
|
||||
for _, h := range blocks {
|
||||
if _, bad := badMiners[h.Miner]; bad {
|
||||
continue
|
||||
}
|
||||
ptsk := types.NewTipSetKey(h.Parents...)
|
||||
formableTipsets[ptsk] = append(formableTipsets[ptsk], h)
|
||||
}
|
||||
|
||||
maxWeight := types.NewInt(0)
|
||||
var maxTs *types.TipSet
|
||||
for _, headers := range formableTipsets {
|
||||
ts, err := types.NewTipSet(headers)
|
||||
if err != nil {
|
||||
return nil, types.NewInt(0), xerrors.Errorf("unexpected error forming tipset: %w", err)
|
||||
}
|
||||
|
||||
weight, err := cs.Weight(ctx, ts)
|
||||
if err != nil {
|
||||
return nil, types.NewInt(0), xerrors.Errorf("failed to calculate weight: %w", err)
|
||||
}
|
||||
|
||||
heavier := weight.GreaterThan(maxWeight)
|
||||
if weight.Equals(maxWeight) {
|
||||
heavier = breakWeightTie(ts, maxTs)
|
||||
}
|
||||
|
||||
if heavier {
|
||||
maxWeight = weight
|
||||
maxTs = ts
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: other validation...?
|
||||
|
||||
return types.NewTipSet(all)
|
||||
return maxTs, maxWeight, nil
|
||||
}
|
||||
|
||||
func (cs *ChainStore) GetGenesis(ctx context.Context) (*types.BlockHeader, error) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/ipfs/go-datastore"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/crypto"
|
||||
|
||||
@@ -238,3 +239,171 @@ func TestChainExportImportFull(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEquivocations(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cg, err := gen.NewGenerator()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var last *types.TipSet
|
||||
for i := 0; i < 10; i++ {
|
||||
ts, err := cg.NextTipSet()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
last = ts.TipSet.TipSet()
|
||||
}
|
||||
|
||||
mTs, err := cg.NextTipSetFromMiners(last, []address.Address{last.Blocks()[0].Miner}, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(mTs.TipSet.TipSet().Cids()))
|
||||
last = mTs.TipSet.TipSet()
|
||||
|
||||
require.NotEmpty(t, last.Blocks())
|
||||
blk1 := *last.Blocks()[0]
|
||||
|
||||
// quick check: asking to form tipset at latest height just returns head
|
||||
bestHead, bestHeadWeight, err := cg.ChainStore().FormHeaviestTipSetForHeight(ctx, last.Height())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, last.Key(), bestHead.Key())
|
||||
require.Contains(t, last.Cids(), blk1.Cid())
|
||||
expectedWeight, err := cg.ChainStore().Weight(ctx, bestHead)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedWeight, bestHeadWeight)
|
||||
|
||||
// add another block by a different miner -- it should get included in the best tipset
|
||||
blk2 := blk1
|
||||
blk1Miner, err := address.IDFromAddress(blk2.Miner)
|
||||
require.NoError(t, err)
|
||||
blk2.Miner, err = address.NewIDAddress(blk1Miner + 50)
|
||||
require.NoError(t, err)
|
||||
addBlockToTracker(t, cg.ChainStore(), &blk2)
|
||||
|
||||
bestHead, bestHeadWeight, err = cg.ChainStore().FormHeaviestTipSetForHeight(ctx, last.Height())
|
||||
require.NoError(t, err)
|
||||
for _, blkCid := range last.Cids() {
|
||||
require.Contains(t, bestHead.Cids(), blkCid)
|
||||
}
|
||||
require.Contains(t, bestHead.Cids(), blk2.Cid())
|
||||
expectedWeight, err = cg.ChainStore().Weight(ctx, bestHead)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedWeight, bestHeadWeight)
|
||||
|
||||
// add another block by a different miner, but on a different tipset -- it should NOT get included
|
||||
blk3 := blk1
|
||||
blk3.Miner, err = address.NewIDAddress(blk1Miner + 100)
|
||||
require.NoError(t, err)
|
||||
blk1Parent, err := cg.ChainStore().GetBlock(ctx, blk3.Parents[0])
|
||||
require.NoError(t, err)
|
||||
blk3.Parents = blk1Parent.Parents
|
||||
addBlockToTracker(t, cg.ChainStore(), &blk3)
|
||||
|
||||
bestHead, bestHeadWeight, err = cg.ChainStore().FormHeaviestTipSetForHeight(ctx, last.Height())
|
||||
require.NoError(t, err)
|
||||
for _, blkCid := range last.Cids() {
|
||||
require.Contains(t, bestHead.Cids(), blkCid)
|
||||
}
|
||||
require.Contains(t, bestHead.Cids(), blk2.Cid())
|
||||
require.NotContains(t, bestHead.Cids(), blk3.Cid())
|
||||
expectedWeight, err = cg.ChainStore().Weight(ctx, bestHead)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedWeight, bestHeadWeight)
|
||||
|
||||
// add another block by the same miner as blk1 -- it should NOT get included, and blk1 should be excluded too
|
||||
blk4 := blk1
|
||||
blk4.Timestamp = blk1.Timestamp + 1
|
||||
addBlockToTracker(t, cg.ChainStore(), &blk4)
|
||||
|
||||
bestHead, bestHeadWeight, err = cg.ChainStore().FormHeaviestTipSetForHeight(ctx, last.Height())
|
||||
require.NoError(t, err)
|
||||
for _, blkCid := range last.Cids() {
|
||||
if blkCid != blk1.Cid() {
|
||||
require.Contains(t, bestHead.Cids(), blkCid)
|
||||
}
|
||||
}
|
||||
require.NotContains(t, bestHead.Cids(), blk4.Cid())
|
||||
require.NotContains(t, bestHead.Cids(), blk1.Cid())
|
||||
expectedWeight, err = cg.ChainStore().Weight(ctx, bestHead)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedWeight, bestHeadWeight)
|
||||
|
||||
// check that after all of that, the chainstore's head has NOT changed
|
||||
require.Equal(t, last.Key(), cg.ChainStore().GetHeaviestTipSet().Key())
|
||||
|
||||
// NOW, after all that, notify the chainstore to refresh its head
|
||||
require.NoError(t, cg.ChainStore().RefreshHeaviestTipSet(ctx, blk1.Height+1))
|
||||
|
||||
originalHead := *last
|
||||
newHead := cg.ChainStore().GetHeaviestTipSet()
|
||||
// the newHead should be at the same height as the originalHead
|
||||
require.Equal(t, originalHead.Height(), newHead.Height())
|
||||
// the newHead should NOT be the same as the originalHead
|
||||
require.NotEqual(t, originalHead.Key(), newHead.Key())
|
||||
// specifically, it should not contain any blocks by blk1Miner
|
||||
for _, b := range newHead.Blocks() {
|
||||
require.NotEqual(t, blk1.Miner, b.Miner)
|
||||
}
|
||||
|
||||
// now have blk2's Miner equivocate too! this causes us to switch to a tipset with a different parent!
|
||||
blk5 := blk2
|
||||
blk5.Timestamp = blk5.Timestamp + 1
|
||||
addBlockToTracker(t, cg.ChainStore(), &blk5)
|
||||
|
||||
// notify the chainstore to refresh its head
|
||||
require.NoError(t, cg.ChainStore().RefreshHeaviestTipSet(ctx, blk1.Height+1))
|
||||
newHead = cg.ChainStore().GetHeaviestTipSet()
|
||||
// the newHead should still be at the same height as the originalHead
|
||||
require.Equal(t, originalHead.Height(), newHead.Height())
|
||||
// BUT it should no longer have the same parents -- only blk3's miner is good, and they mined on a different tipset
|
||||
require.Equal(t, 1, len(newHead.Blocks()))
|
||||
require.Equal(t, blk3.Cid(), newHead.Cids()[0])
|
||||
require.NotEqual(t, originalHead.Parents(), newHead.Parents())
|
||||
|
||||
// now have blk3's Miner equivocate too! this causes us to switch to a previous epoch entirely :(
|
||||
blk6 := blk3
|
||||
blk6.Timestamp = blk6.Timestamp + 1
|
||||
addBlockToTracker(t, cg.ChainStore(), &blk6)
|
||||
|
||||
// trying to form a tipset at our previous height leads to emptiness
|
||||
tryTs, tryTsWeight, err := cg.ChainStore().FormHeaviestTipSetForHeight(ctx, blk1.Height)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, tryTs)
|
||||
require.True(t, tryTsWeight.IsZero())
|
||||
|
||||
// notify the chainstore to refresh its head
|
||||
require.NoError(t, cg.ChainStore().RefreshHeaviestTipSet(ctx, blk1.Height+1))
|
||||
newHead = cg.ChainStore().GetHeaviestTipSet()
|
||||
// the newHead should now be one epoch behind originalHead
|
||||
require.Greater(t, originalHead.Height(), newHead.Height())
|
||||
|
||||
// next, we create a new tipset with only one block after many null rounds
|
||||
headAfterNulls, err := cg.NextTipSetFromMiners(newHead, []address.Address{newHead.Blocks()[0].Miner}, 15)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(headAfterNulls.TipSet.Blocks))
|
||||
|
||||
// now, we disqualify the block in this tipset because of equivocation
|
||||
blkAfterNulls := headAfterNulls.TipSet.TipSet().Blocks()[0]
|
||||
equivocatedBlkAfterNulls := *blkAfterNulls
|
||||
equivocatedBlkAfterNulls.Timestamp = blkAfterNulls.Timestamp + 1
|
||||
addBlockToTracker(t, cg.ChainStore(), &equivocatedBlkAfterNulls)
|
||||
|
||||
// try to form a tipset at this height -- it should be empty
|
||||
tryTs2, tryTsWeight2, err := cg.ChainStore().FormHeaviestTipSetForHeight(ctx, blkAfterNulls.Height)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, tryTs2)
|
||||
require.True(t, tryTsWeight2.IsZero())
|
||||
|
||||
// now we "notify" at this height -- it should lead to no head change because there's no formable head in near epochs
|
||||
require.NoError(t, cg.ChainStore().RefreshHeaviestTipSet(ctx, blkAfterNulls.Height))
|
||||
require.True(t, headAfterNulls.TipSet.TipSet().Equals(cg.ChainStore().GetHeaviestTipSet()))
|
||||
}
|
||||
|
||||
func addBlockToTracker(t *testing.T, cs *store.ChainStore, blk *types.BlockHeader) {
|
||||
blk2Ts, err := types.NewTipSet([]*types.BlockHeader{blk})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, cs.PersistTipsets(context.TODO(), []*types.TipSet{blk2Ts}))
|
||||
require.NoError(t, cs.AddToTipSetTracker(context.TODO(), blk))
|
||||
}
|
||||
|
||||
+20
-8
@@ -350,6 +350,7 @@ func (mv *MessageValidator) Validate(ctx context.Context, pid peer.ID, msg *pubs
|
||||
)
|
||||
recordFailure(ctx, metrics.MessageValidationFailure, "add")
|
||||
switch {
|
||||
|
||||
case xerrors.Is(err, messagepool.ErrSoftValidationFailure):
|
||||
fallthrough
|
||||
case xerrors.Is(err, messagepool.ErrRBFTooLowPremium):
|
||||
@@ -362,8 +363,17 @@ func (mv *MessageValidator) Validate(ctx context.Context, pid peer.ID, msg *pubs
|
||||
fallthrough
|
||||
case xerrors.Is(err, messagepool.ErrNonceTooLow):
|
||||
fallthrough
|
||||
case xerrors.Is(err, messagepool.ErrNotEnoughFunds):
|
||||
fallthrough
|
||||
case xerrors.Is(err, messagepool.ErrExistingNonce):
|
||||
return pubsub.ValidationIgnore
|
||||
|
||||
case xerrors.Is(err, messagepool.ErrMessageTooBig):
|
||||
fallthrough
|
||||
case xerrors.Is(err, messagepool.ErrMessageValueTooHigh):
|
||||
fallthrough
|
||||
case xerrors.Is(err, messagepool.ErrInvalidToAddr):
|
||||
fallthrough
|
||||
default:
|
||||
return pubsub.ValidationReject
|
||||
}
|
||||
@@ -519,9 +529,8 @@ func (v *IndexerMessageValidator) Validate(ctx context.Context, pid peer.ID, msg
|
||||
|
||||
msgCid := idxrMsg.Cid
|
||||
|
||||
var msgInfo *peerMsgInfo
|
||||
msgInfo, ok := v.peerCache.Get(minerAddr)
|
||||
if !ok {
|
||||
msgInfo, cached := v.peerCache.Get(minerAddr)
|
||||
if !cached {
|
||||
msgInfo = &peerMsgInfo{}
|
||||
}
|
||||
|
||||
@@ -529,17 +538,17 @@ func (v *IndexerMessageValidator) Validate(ctx context.Context, pid peer.ID, msg
|
||||
msgInfo.mutex.Lock()
|
||||
defer msgInfo.mutex.Unlock()
|
||||
|
||||
if ok {
|
||||
var seqno uint64
|
||||
if cached {
|
||||
// Reject replayed messages.
|
||||
seqno := binary.BigEndian.Uint64(msg.Message.GetSeqno())
|
||||
seqno = binary.BigEndian.Uint64(msg.Message.GetSeqno())
|
||||
if seqno <= msgInfo.lastSeqno {
|
||||
log.Debugf("ignoring replayed indexer message")
|
||||
return pubsub.ValidationIgnore
|
||||
}
|
||||
msgInfo.lastSeqno = seqno
|
||||
}
|
||||
|
||||
if !ok || originPeer != msgInfo.peerID {
|
||||
if !cached || originPeer != msgInfo.peerID {
|
||||
// Check that the miner ID maps to the peer that sent the message.
|
||||
err = v.authenticateMessage(ctx, minerAddr, originPeer)
|
||||
if err != nil {
|
||||
@@ -548,7 +557,7 @@ func (v *IndexerMessageValidator) Validate(ctx context.Context, pid peer.ID, msg
|
||||
return pubsub.ValidationReject
|
||||
}
|
||||
msgInfo.peerID = originPeer
|
||||
if !ok {
|
||||
if !cached {
|
||||
// Add msgInfo to cache only after being authenticated. If two
|
||||
// messages from the same peer are handled concurrently, there is a
|
||||
// small chance that one msgInfo could replace the other here when
|
||||
@@ -557,6 +566,9 @@ func (v *IndexerMessageValidator) Validate(ctx context.Context, pid peer.ID, msg
|
||||
}
|
||||
}
|
||||
|
||||
// Update message info cache with the latest message's sequence number.
|
||||
msgInfo.lastSeqno = seqno
|
||||
|
||||
// See if message needs to be ignored due to rate limiting.
|
||||
if v.rateLimitPeer(msgInfo, msgCid) {
|
||||
return pubsub.ValidationIgnore
|
||||
|
||||
@@ -12,10 +12,12 @@ import (
|
||||
"github.com/ipni/go-libipni/announce/message"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
pb "github.com/libp2p/go-libp2p-pubsub/pb"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/api/mocks"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
@@ -134,3 +136,123 @@ func TestIndexerMessageValidator_Validate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdxValidator(t *testing.T) {
|
||||
validCid, err := cid.Decode("QmbpDgg5kRLDgMxS8vPKNFXEcA6D5MC4CkuUdSWDVtHPGK")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
addr, err := address.NewFromString("f01024")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf1, err := addr.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
selfPID := "12D3KooWQiCbqEStCkdqUvr69gQsrp9urYJZUCkzsQXia7mbqbFW"
|
||||
senderPID := "12D3KooWE8yt84RVwW3sFcd6WMjbUdWrZer2YtT4dmtj3dHdahSZ"
|
||||
extraData := buf1
|
||||
|
||||
mc := gomock.NewController(t)
|
||||
node := mocks.NewMockFullNode(mc)
|
||||
node.EXPECT().ChainHead(gomock.Any()).Return(nil, nil).AnyTimes()
|
||||
|
||||
subject := NewIndexerMessageValidator(peer.ID(selfPID), node, node)
|
||||
message := message.Message{
|
||||
Cid: validCid,
|
||||
Addrs: nil,
|
||||
ExtraData: extraData,
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
if err := message.MarshalCBOR(buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
topic := "topic"
|
||||
|
||||
privk, _, err := crypto.GenerateKeyPair(crypto.RSA, 2048)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id, err := peer.IDFromPublicKey(privk.GetPublic())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
node.EXPECT().StateMinerInfo(gomock.Any(), gomock.Any(), gomock.Any()).Return(api.MinerInfo{PeerId: &id}, nil).AnyTimes()
|
||||
|
||||
pbm := &pb.Message{
|
||||
Data: buf.Bytes(),
|
||||
Topic: &topic,
|
||||
From: []byte(id),
|
||||
Seqno: []byte{1, 1, 1, 1, 2, 2, 2, 2},
|
||||
}
|
||||
validate := subject.Validate(context.Background(), peer.ID(senderPID), &pubsub.Message{
|
||||
Message: pbm,
|
||||
ReceivedFrom: peer.ID("f01024"), // peer.ID(senderPID),
|
||||
ValidatorData: nil,
|
||||
})
|
||||
if validate != pubsub.ValidationAccept {
|
||||
t.Error("Expected to receive ValidationAccept")
|
||||
}
|
||||
msgInfo, cached := subject.peerCache.Get(addr)
|
||||
if !cached {
|
||||
t.Fatal("Message info should be in cache")
|
||||
}
|
||||
seqno := msgInfo.lastSeqno
|
||||
msgInfo.rateLimit = nil // prevent interference from rate limiting
|
||||
|
||||
t.Log("Sending DoS msg")
|
||||
privk, _, err = crypto.GenerateKeyPair(crypto.RSA, 2048)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id2, err := peer.IDFromPublicKey(privk.GetPublic())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pbm = &pb.Message{
|
||||
Data: buf.Bytes(),
|
||||
Topic: &topic,
|
||||
From: []byte(id2),
|
||||
Seqno: []byte{255, 255, 255, 255, 255, 255, 255, 255},
|
||||
}
|
||||
validate = subject.Validate(context.Background(), peer.ID(senderPID), &pubsub.Message{
|
||||
Message: pbm,
|
||||
ReceivedFrom: peer.ID(senderPID),
|
||||
ValidatorData: nil,
|
||||
})
|
||||
if validate != pubsub.ValidationReject {
|
||||
t.Error("Expected to get ValidationReject")
|
||||
}
|
||||
msgInfo, cached = subject.peerCache.Get(addr)
|
||||
if !cached {
|
||||
t.Fatal("Message info should be in cache")
|
||||
}
|
||||
msgInfo.rateLimit = nil // prevent interference from rate limiting
|
||||
|
||||
// Check if DoS is possible.
|
||||
if msgInfo.lastSeqno != seqno {
|
||||
t.Fatal("Sequence number should not have been updated")
|
||||
}
|
||||
|
||||
t.Log("Sending another valid message from miner...")
|
||||
pbm = &pb.Message{
|
||||
Data: buf.Bytes(),
|
||||
Topic: &topic,
|
||||
From: []byte(id),
|
||||
Seqno: []byte{1, 1, 1, 1, 2, 2, 2, 3},
|
||||
}
|
||||
validate = subject.Validate(context.Background(), peer.ID(senderPID), &pubsub.Message{
|
||||
Message: pbm,
|
||||
ReceivedFrom: peer.ID("f01024"), // peer.ID(senderPID),
|
||||
ValidatorData: nil,
|
||||
})
|
||||
if validate != pubsub.ValidationAccept {
|
||||
t.Fatal("Did not receive ValidationAccept")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package ratelimit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestQueue(t *testing.T) {
|
||||
const size = 3
|
||||
q := &queue{buf: make([]int64, size)}
|
||||
|
||||
if q.len() != 0 {
|
||||
t.Fatalf("q.len() = %d, expect 0", q.len())
|
||||
}
|
||||
|
||||
if q.cap() != size {
|
||||
t.Fatalf("q.cap() = %d, expect %d", q.cap(), size)
|
||||
}
|
||||
|
||||
for i := int64(0); i < int64(size); i++ {
|
||||
err := q.push(i)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot push element %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
if q.len() != size {
|
||||
t.Fatalf("q.len() = %d, expect %d", q.len(), size)
|
||||
}
|
||||
|
||||
err := q.push(int64(size))
|
||||
if err != ErrRateLimitExceeded {
|
||||
t.Fatalf("pushing element beyond capacity should have failed with err: %s, got %s", ErrRateLimitExceeded, err)
|
||||
}
|
||||
|
||||
if q.front() != 0 {
|
||||
t.Fatalf("q.front() = %d, expect 0", q.front())
|
||||
}
|
||||
|
||||
if q.back() != int64(size-1) {
|
||||
t.Fatalf("q.back() = %d, expect %d", q.back(), size-1)
|
||||
}
|
||||
|
||||
popVal := q.pop()
|
||||
if popVal != 0 {
|
||||
t.Fatalf("q.pop() = %d, expect 0", popVal)
|
||||
}
|
||||
|
||||
if q.len() != size-1 {
|
||||
t.Fatalf("q.len() = %d, expect %d", q.len(), size-1)
|
||||
}
|
||||
|
||||
// Testing truncation.
|
||||
threshold := int64(1)
|
||||
q.truncate(threshold)
|
||||
if q.len() != 1 {
|
||||
t.Fatalf("q.len() after truncate = %d, expect 1", q.len())
|
||||
}
|
||||
if q.front() != 2 {
|
||||
t.Fatalf("q.front() after truncate = %d, expect 2", q.front())
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -536,7 +536,7 @@ func (syncer *Syncer) Sync(ctx context.Context, maybeHead *types.TipSet) error {
|
||||
|
||||
// At this point we have accepted and synced to the new `maybeHead`
|
||||
// (`StageSyncComplete`).
|
||||
if err := syncer.store.PutTipSet(ctx, maybeHead); err != nil {
|
||||
if err := syncer.store.RefreshHeaviestTipSet(ctx, maybeHead.Height()); err != nil {
|
||||
span.AddAttributes(trace.StringAttribute("put_error", err.Error()))
|
||||
span.SetStatus(trace.Status{
|
||||
Code: 13,
|
||||
|
||||
@@ -92,6 +92,7 @@ type syncManager struct {
|
||||
var _ SyncManager = (*syncManager)(nil)
|
||||
|
||||
type peerHead struct {
|
||||
// Note: this doesn't _necessarily_ mean that p's head is ts, just that ts is a tipset that p sent to us
|
||||
p peer.ID
|
||||
ts *types.TipSet
|
||||
}
|
||||
|
||||
+1
-9
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/ipfs/go-cid"
|
||||
ds "github.com/ipfs/go-datastore"
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
mocknet "github.com/libp2p/go-libp2p/p2p/net/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -311,7 +310,7 @@ func (tu *syncTestUtil) addSourceNode(gen int) {
|
||||
for _, lastB := range lastTs.Blocks {
|
||||
require.NoError(tu.t, cs.AddToTipSetTracker(context.Background(), lastB.Header))
|
||||
}
|
||||
err = cs.PutTipSet(tu.ctx, lastTs.TipSet())
|
||||
err = cs.RefreshHeaviestTipSet(tu.ctx, lastTs.TipSet().Height())
|
||||
require.NoError(tu.t, err)
|
||||
|
||||
tu.genesis = genesis
|
||||
@@ -344,13 +343,6 @@ func (tu *syncTestUtil) addClientNode() int {
|
||||
return len(tu.nds) - 1
|
||||
}
|
||||
|
||||
func (tu *syncTestUtil) pid(n int) peer.ID {
|
||||
nal, err := tu.nds[n].NetAddrsListen(tu.ctx)
|
||||
require.NoError(tu.t, err)
|
||||
|
||||
return nal.ID
|
||||
}
|
||||
|
||||
func (tu *syncTestUtil) connect(from, to int) {
|
||||
toPI, err := tu.nds[to].NetAddrsListen(tu.ctx)
|
||||
require.NoError(tu.t, err)
|
||||
|
||||
@@ -7,9 +7,6 @@ import (
|
||||
)
|
||||
|
||||
func TestDecodeBlockMsg(t *testing.T) {
|
||||
type args struct {
|
||||
b []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/big"
|
||||
builtintypes "github.com/filecoin-project/go-state-types/builtin"
|
||||
|
||||
@@ -929,3 +930,57 @@ func (e *EthBlockNumberOrHash) UnmarshalJSON(b []byte) error {
|
||||
|
||||
return errors.New("invalid block param")
|
||||
}
|
||||
|
||||
type EthTrace struct {
|
||||
Action EthTraceAction `json:"action"`
|
||||
Result EthTraceResult `json:"result"`
|
||||
Subtraces int `json:"subtraces"`
|
||||
TraceAddress []int `json:"traceAddress"`
|
||||
Type string `json:"Type"`
|
||||
|
||||
Parent *EthTrace `json:"-"`
|
||||
|
||||
// if a subtrace makes a call to GetBytecode, we store a pointer to that subtrace here
|
||||
// which we then lookup when checking for delegatecall (InvokeContractDelegate)
|
||||
LastByteCode *EthTrace `json:"-"`
|
||||
}
|
||||
|
||||
func (t *EthTrace) SetCallType(callType string) {
|
||||
t.Action.CallType = callType
|
||||
t.Type = callType
|
||||
}
|
||||
|
||||
type EthTraceBlock struct {
|
||||
*EthTrace
|
||||
BlockHash EthHash `json:"blockHash"`
|
||||
BlockNumber int64 `json:"blockNumber"`
|
||||
TransactionHash EthHash `json:"transactionHash"`
|
||||
TransactionPosition int `json:"transactionPosition"`
|
||||
}
|
||||
|
||||
type EthTraceReplayBlockTransaction struct {
|
||||
Output EthBytes `json:"output"`
|
||||
StateDiff *string `json:"stateDiff"`
|
||||
Trace []*EthTrace `json:"trace"`
|
||||
TransactionHash EthHash `json:"transactionHash"`
|
||||
VmTrace *string `json:"vmTrace"`
|
||||
}
|
||||
|
||||
type EthTraceAction struct {
|
||||
CallType string `json:"callType"`
|
||||
From EthAddress `json:"from"`
|
||||
To EthAddress `json:"to"`
|
||||
Gas EthUint64 `json:"gas"`
|
||||
Input EthBytes `json:"input"`
|
||||
Value EthBigInt `json:"value"`
|
||||
|
||||
FilecoinMethod abi.MethodNum `json:"-"`
|
||||
FilecoinCodeCid cid.Cid `json:"-"`
|
||||
FilecoinFrom address.Address `json:"-"`
|
||||
FilecoinTo address.Address `json:"-"`
|
||||
}
|
||||
|
||||
type EthTraceResult struct {
|
||||
GasUsed EthUint64 `json:"gasUsed"`
|
||||
Output EthBytes `json:"output"`
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ func decodeRLP(data []byte) (res interface{}, consumed int, err error) {
|
||||
return nil, 0, err
|
||||
}
|
||||
totalLen := 1 + strLenInBytes + strLen
|
||||
if totalLen > len(data) {
|
||||
if totalLen > len(data) || totalLen < 0 {
|
||||
return nil, 0, xerrors.Errorf("invalid rlp data: out of bound while parsing string")
|
||||
}
|
||||
return data[1+strLenInBytes : totalLen], totalLen, nil
|
||||
@@ -157,7 +157,12 @@ func decodeLength(data []byte, lenInBytes int) (length int, err error) {
|
||||
if err := binary.Read(r, binary.BigEndian, &decodedLength); err != nil {
|
||||
return 0, xerrors.Errorf("invalid rlp data: cannot parse string length: %w", err)
|
||||
}
|
||||
if lenInBytes+int(decodedLength) > len(data) {
|
||||
if decodedLength < 0 {
|
||||
return 0, xerrors.Errorf("invalid rlp data: negative string length")
|
||||
}
|
||||
|
||||
totalLength := lenInBytes + int(decodedLength)
|
||||
if totalLength < 0 || totalLength > len(data) {
|
||||
return 0, xerrors.Errorf("invalid rlp data: out of bound while parsing list")
|
||||
}
|
||||
return int(decodedLength), nil
|
||||
|
||||
@@ -143,6 +143,20 @@ func TestDecodeList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeNegativeLength(t *testing.T) {
|
||||
testcases := [][]byte{
|
||||
mustDecodeHex("0xbfffffffffffffff0041424344"),
|
||||
mustDecodeHex("0xc1bFFF1111111111111111"),
|
||||
mustDecodeHex("0xbFFF11111111111111"),
|
||||
mustDecodeHex("0xbf7fffffffffffffff41424344"),
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
_, err := DecodeRLP(tc)
|
||||
require.ErrorContains(t, err, "invalid rlp data")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeEncodeTx(t *testing.T) {
|
||||
testcases := [][]byte{
|
||||
mustDecodeHex("0xdc82013a0185012a05f2008504a817c8008080872386f26fc1000000c0"),
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
)
|
||||
|
||||
@@ -38,24 +33,3 @@ type EventEntry struct {
|
||||
}
|
||||
|
||||
type FilterID [32]byte // compatible with EthHash
|
||||
|
||||
// DecodeEvents decodes a CBOR list of CBOR-encoded events.
|
||||
func DecodeEvents(input []byte) ([]Event, error) {
|
||||
r := bytes.NewReader(input)
|
||||
typ, len, err := cbg.NewCborReader(r).ReadHeader()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read events: %w", err)
|
||||
}
|
||||
if typ != cbg.MajArray {
|
||||
return nil, fmt.Errorf("expected a CBOR list, was major type %d", typ)
|
||||
}
|
||||
events := make([]Event, 0, len)
|
||||
for i := 0; i < int(len); i++ {
|
||||
var evt Event
|
||||
if err := evt.UnmarshalCBOR(r); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse event: %w", err)
|
||||
}
|
||||
events = append(events, evt)
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
@@ -27,24 +27,3 @@ type StateTree interface {
|
||||
|
||||
Version() StateTreeVersion
|
||||
}
|
||||
|
||||
type storageWrapper struct {
|
||||
s Storage
|
||||
}
|
||||
|
||||
func (sw *storageWrapper) Put(i cbg.CBORMarshaler) (cid.Cid, error) {
|
||||
c, err := sw.s.Put(i)
|
||||
if err != nil {
|
||||
return cid.Undef, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (sw *storageWrapper) Get(c cid.Cid, out cbg.CBORUnmarshaler) error {
|
||||
if err := sw.s.Get(c, out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+2
-2
@@ -459,7 +459,7 @@ func (vm *FVM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet
|
||||
}
|
||||
|
||||
if vm.returnEvents && len(ret.EventsBytes) > 0 {
|
||||
applyRet.Events, err = types.DecodeEvents(ret.EventsBytes)
|
||||
applyRet.Events, err = decodeEvents(ret.EventsBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode events returned by the FVM: %w", err)
|
||||
}
|
||||
@@ -515,7 +515,7 @@ func (vm *FVM) ApplyImplicitMessage(ctx context.Context, cmsg *types.Message) (*
|
||||
}
|
||||
|
||||
if vm.returnEvents && len(ret.EventsBytes) > 0 {
|
||||
applyRet.Events, err = types.DecodeEvents(ret.EventsBytes)
|
||||
applyRet.Events, err = decodeEvents(ret.EventsBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode events returned by the FVM: %w", err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
const MaxEventSliceLength = 6_000_000
|
||||
|
||||
// DecodeEvents decodes a CBOR list of CBOR-encoded events.
|
||||
func decodeEvents(input []byte) ([]types.Event, error) {
|
||||
r := bytes.NewReader(input)
|
||||
typ, length, err := cbg.NewCborReader(r).ReadHeader()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read events: %w", err)
|
||||
}
|
||||
|
||||
if length > MaxEventSliceLength {
|
||||
log.Errorf("extremely long event slice (len %d) returned, not decoding", length)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if typ != cbg.MajArray {
|
||||
return nil, fmt.Errorf("expected a CBOR list, was major type %d", typ)
|
||||
}
|
||||
events := make([]types.Event, 0, length)
|
||||
for i := 0; i < int(length); i++ {
|
||||
var evt types.Event
|
||||
if err := evt.UnmarshalCBOR(r); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse event: %w", err)
|
||||
}
|
||||
events = append(events, evt)
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
@@ -70,11 +70,6 @@ type syscallShim struct {
|
||||
}
|
||||
|
||||
func (ss *syscallShim) ComputeUnsealedSectorCID(st abi.RegisteredSealProof, pieces []abi.PieceInfo) (cid.Cid, error) {
|
||||
var sum abi.PaddedPieceSize
|
||||
for _, p := range pieces {
|
||||
sum += p.Size
|
||||
}
|
||||
|
||||
commd, err := ffiwrapper.GenerateUnsealedCID(st, pieces)
|
||||
if err != nil {
|
||||
log.Errorf("generate data commitment failed: %s", err)
|
||||
|
||||
+13
-11
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/ipfs/go-cid"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
mh "github.com/multiformats/go-multihash"
|
||||
"github.com/multiformats/go-multicodec"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/trace"
|
||||
@@ -38,7 +38,6 @@ import (
|
||||
)
|
||||
|
||||
const MaxCallDepth = 4096
|
||||
const CborCodec = 0x51
|
||||
|
||||
var (
|
||||
log = logging.Logger("vm")
|
||||
@@ -128,7 +127,7 @@ func (bs *gasChargingBlocks) Put(ctx context.Context, blk block.Block) error {
|
||||
func (vm *LegacyVM) makeRuntime(ctx context.Context, msg *types.Message, parent *Runtime) *Runtime {
|
||||
paramsCodec := uint64(0)
|
||||
if len(msg.Params) > 0 {
|
||||
paramsCodec = CborCodec
|
||||
paramsCodec = uint64(multicodec.Cbor)
|
||||
}
|
||||
rt := &Runtime{
|
||||
ctx: ctx,
|
||||
@@ -380,7 +379,7 @@ func (vm *LegacyVM) send(ctx context.Context, msg *types.Message, parent *Runtim
|
||||
|
||||
retCodec := uint64(0)
|
||||
if len(ret) > 0 {
|
||||
retCodec = CborCodec
|
||||
retCodec = uint64(multicodec.Cbor)
|
||||
}
|
||||
rt.executionTrace.MsgRct = types.ReturnTrace{
|
||||
ExitCode: aerrors.RetCode(err),
|
||||
@@ -695,15 +694,15 @@ func (vm *LegacyVM) ActorStore(ctx context.Context) adt.Store {
|
||||
}
|
||||
|
||||
func linksForObj(blk block.Block, cb func(cid.Cid)) error {
|
||||
switch blk.Cid().Prefix().Codec {
|
||||
case cid.DagCBOR:
|
||||
switch multicodec.Code(blk.Cid().Prefix().Codec) {
|
||||
case multicodec.DagCbor:
|
||||
err := cbg.ScanForLinks(bytes.NewReader(blk.RawData()), cb)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("cbg.ScanForLinks: %w", err)
|
||||
}
|
||||
return nil
|
||||
case cid.Raw:
|
||||
// We implicitly have all children of raw blocks.
|
||||
case multicodec.Raw, multicodec.Cbor:
|
||||
// We implicitly have all children of raw/cbor blocks.
|
||||
return nil
|
||||
default:
|
||||
return xerrors.Errorf("vm flush copy method only supports dag cbor")
|
||||
@@ -803,14 +802,17 @@ func copyRec(ctx context.Context, from, to blockstore.Blockstore, root cid.Cid,
|
||||
}
|
||||
|
||||
prefix := link.Prefix()
|
||||
if prefix.Codec == cid.FilCommitmentSealed || prefix.Codec == cid.FilCommitmentUnsealed {
|
||||
codec := multicodec.Code(prefix.Codec)
|
||||
switch codec {
|
||||
case multicodec.FilCommitmentSealed, cid.FilCommitmentUnsealed:
|
||||
return
|
||||
}
|
||||
|
||||
// We always have blocks inlined into CIDs, but we may not have their children.
|
||||
if prefix.MhType == mh.IDENTITY {
|
||||
if multicodec.Code(prefix.MhType) == multicodec.Identity {
|
||||
// Unless the inlined block has no children.
|
||||
if prefix.Codec == cid.Raw {
|
||||
switch codec {
|
||||
case multicodec.Raw, multicodec.Cbor:
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -282,7 +282,7 @@ var NetDisconnect = &cli.Command{
|
||||
fmt.Println("failure")
|
||||
return err
|
||||
}
|
||||
fmt.Printf("disconnect %s: ", pid.Pretty())
|
||||
fmt.Printf("disconnect %s: ", pid)
|
||||
err = api.NetDisconnect(ctx, pid)
|
||||
if err != nil {
|
||||
fmt.Println("failure")
|
||||
@@ -312,7 +312,7 @@ var NetConnect = &cli.Command{
|
||||
}
|
||||
|
||||
for _, pi := range pis {
|
||||
fmt.Printf("connect %s: ", pi.ID.Pretty())
|
||||
fmt.Printf("connect %s: ", pi.ID)
|
||||
err := api.NetConnect(ctx, pi)
|
||||
if err != nil {
|
||||
fmt.Println("failure")
|
||||
|
||||
+20
-7
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/filecoin-project/lotus/api/v0api"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
cliutil "github.com/filecoin-project/lotus/cli/util"
|
||||
)
|
||||
|
||||
var SyncCmd = &cli.Command{
|
||||
@@ -262,6 +263,9 @@ func SyncWait(ctx context.Context, napi v0api.FullNode, watch bool) error {
|
||||
}
|
||||
firstApp = state.VMApplied
|
||||
|
||||
// eta computes the ETA for the sync to complete (with a lookback of 10 processed items)
|
||||
eta := cliutil.NewETA(10)
|
||||
|
||||
for {
|
||||
state, err := napi.SyncState(ctx)
|
||||
if err != nil {
|
||||
@@ -273,11 +277,6 @@ func SyncWait(ctx context.Context, napi v0api.FullNode, watch bool) error {
|
||||
continue
|
||||
}
|
||||
|
||||
head, err := napi.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
working := -1
|
||||
for i, ss := range state.ActiveSyncs {
|
||||
switch ss.Stage {
|
||||
@@ -317,8 +316,10 @@ func SyncWait(ctx context.Context, napi v0api.FullNode, watch bool) error {
|
||||
fmt.Print("\r\x1b[2K\x1b[A")
|
||||
}
|
||||
|
||||
todo := theight - ss.Height
|
||||
|
||||
fmt.Printf("Worker: %d; Base: %d; Target: %d (diff: %d)\n", workerID, baseHeight, theight, heightDiff)
|
||||
fmt.Printf("State: %s; Current Epoch: %d; Todo: %d\n", ss.Stage, ss.Height, theight-ss.Height)
|
||||
fmt.Printf("State: %s; Current Epoch: %d; Todo: %d, ETA: %s\n", ss.Stage, ss.Height, todo, eta.Update(int64(todo)))
|
||||
lastLines = 2
|
||||
|
||||
if i%samples == 0 {
|
||||
@@ -332,7 +333,11 @@ func SyncWait(ctx context.Context, napi v0api.FullNode, watch bool) error {
|
||||
|
||||
_ = target // todo: maybe print? (creates a bunch of line wrapping issues with most tipsets)
|
||||
|
||||
if !watch && time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs) {
|
||||
isDone, err := IsSyncDone(ctx, napi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !watch && isDone {
|
||||
fmt.Println("\nDone!")
|
||||
return nil
|
||||
}
|
||||
@@ -347,3 +352,11 @@ func SyncWait(ctx context.Context, napi v0api.FullNode, watch bool) error {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func IsSyncDone(ctx context.Context, napi v0api.FullNode) (bool, error) {
|
||||
head, err := napi.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs), nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package cliutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ETA implements a very simple eta calculator based on the number of remaining items. It does not
|
||||
// require knowing the work size in advance and is therefore suitable for streaming workloads and
|
||||
// also does not require that consecutive updates have a monotonically decreasing remaining value.
|
||||
type ETA struct {
|
||||
// max number of items to keep in memory
|
||||
maxItems int
|
||||
// a queue of most recently updated items
|
||||
items []item
|
||||
// we store the last calculated ETA which we reuse if there was not change in remaining items
|
||||
lastETA string
|
||||
}
|
||||
|
||||
type item struct {
|
||||
timestamp time.Time
|
||||
remaining int64
|
||||
}
|
||||
|
||||
// NewETA creates a new ETA calculator of the given size
|
||||
func NewETA(maxItems int) *ETA {
|
||||
return &ETA{
|
||||
maxItems: maxItems,
|
||||
items: make([]item, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Update updates the ETA calculator with the remaining number of items and returns the ETA
|
||||
func (e *ETA) Update(remaining int64) string {
|
||||
item := item{
|
||||
timestamp: time.Now(),
|
||||
remaining: remaining,
|
||||
}
|
||||
|
||||
if len(e.items) == 0 {
|
||||
e.items = append(e.items, item)
|
||||
return ""
|
||||
}
|
||||
|
||||
if e.items[len(e.items)-1].remaining == remaining {
|
||||
// we ignore updates with the same remaining value and just return the previous ETA
|
||||
return e.lastETA
|
||||
} else if e.items[len(e.items)-1].remaining < remaining {
|
||||
// remaining went up from previous update, lets estimate how many items were processed using the
|
||||
// average number processed items in the queue.
|
||||
var avgProcessedPerItem int64 = 1
|
||||
if len(e.items) > 1 {
|
||||
diffRemaining := e.items[0].remaining - e.items[len(e.items)-1].remaining
|
||||
avgProcessedPerItem = int64(math.Round(float64(diffRemaining) / float64(len(e.items))))
|
||||
}
|
||||
|
||||
// diff is the difference in increase in remaining since last update plus the average number of processed
|
||||
// items we estimate that were processed this round
|
||||
diff := remaining - e.items[len(e.items)-1].remaining + avgProcessedPerItem
|
||||
|
||||
// we update all items in the queue by shifting their remaining value accordingly. This means that we
|
||||
// always have strictly decreasing remaining values in the queue
|
||||
for i := range e.items {
|
||||
e.items[i].remaining += diff
|
||||
}
|
||||
}
|
||||
|
||||
// append the item to the queue and remove the oldest item if needed
|
||||
if len(e.items) >= e.maxItems {
|
||||
e.items = e.items[1:]
|
||||
}
|
||||
e.items = append(e.items, item)
|
||||
|
||||
// calculate the average processing time per item in the queue
|
||||
diffMs := e.items[len(e.items)-1].timestamp.Sub(e.items[0].timestamp).Milliseconds()
|
||||
nrItemsProcessed := e.items[0].remaining - e.items[len(e.items)-1].remaining
|
||||
avg := diffMs / nrItemsProcessed
|
||||
|
||||
// use that average processing time to estimate how long the remaining items will take
|
||||
// and cache that ETA so we don't have to recalculate it on every update unless the
|
||||
// remaining value changes
|
||||
e.lastETA = msToETA(avg * remaining)
|
||||
|
||||
return e.lastETA
|
||||
}
|
||||
|
||||
func msToETA(ms int64) string {
|
||||
seconds := ms / 1000
|
||||
minutes := seconds / 60
|
||||
hours := minutes / 60
|
||||
|
||||
return fmt.Sprintf("%02dh:%02dm:%02ds", hours, minutes%60, seconds%60)
|
||||
}
|
||||
+26
-2
@@ -7,7 +7,9 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/term"
|
||||
@@ -20,6 +22,7 @@ import (
|
||||
"github.com/filecoin-project/go-state-types/network"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/lib/tablewriter"
|
||||
)
|
||||
@@ -206,7 +209,12 @@ var walletBalance = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
if balance.Equals(types.NewInt(0)) {
|
||||
inSync, err := IsSyncDone(ctx, api)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if balance.Equals(types.NewInt(0)) && !inSync {
|
||||
afmt.Printf("%s (warning: may display 0 if chain sync in progress)\n", types.FIL(balance))
|
||||
} else {
|
||||
afmt.Printf("%s\n", types.FIL(balance))
|
||||
@@ -330,6 +338,17 @@ var walletImport = &cli.Command{
|
||||
if !cctx.Args().Present() || cctx.Args().First() == "-" {
|
||||
if term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
fmt.Print("Enter private key(not display in the terminal): ")
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
// Notify the channel when SIGINT is received
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
<-sigCh
|
||||
fmt.Println("\nInterrupt signal received. Exiting...")
|
||||
os.Exit(1)
|
||||
}()
|
||||
|
||||
inpdata, err = term.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -441,7 +460,12 @@ var walletSign = &cli.Command{
|
||||
sig, err := api.WalletSign(ctx, addr, msg)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
// Check if the address is a multisig address
|
||||
act, actErr := api.StateGetActor(ctx, addr, types.EmptyTSK)
|
||||
if actErr == nil && builtin.IsMultisigActor(act.Code) {
|
||||
return xerrors.Errorf("specified signer address is a multisig actor, it doesn’t have keys to sign transactions. To send a message with a multisig, signers of the multisig need to propose and approve transactions.")
|
||||
}
|
||||
return xerrors.Errorf("failed to sign message: %w", err)
|
||||
}
|
||||
|
||||
sigBytes := append([]byte{byte(sig.Type)}, sig.Data...)
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
apitypes "github.com/filecoin-project/lotus/api/types"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/types/mock"
|
||||
)
|
||||
|
||||
func TestWalletNew(t *testing.T) {
|
||||
@@ -133,6 +134,11 @@ func TestWalletBalance(t *testing.T) {
|
||||
|
||||
balance := big.NewInt(1234)
|
||||
|
||||
// add blocks to the chain
|
||||
first := mock.TipSet(mock.MkBlock(nil, 5, 4))
|
||||
head := mock.TipSet(mock.MkBlock(first, 15, 7))
|
||||
|
||||
mockApi.EXPECT().ChainHead(ctx).Return(head, nil)
|
||||
mockApi.EXPECT().WalletBalance(ctx, addr).Return(balance, nil)
|
||||
|
||||
//stm: @CLI_WALLET_BALANCE_001
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
// Copied from go-amt-ipld https://github.com/filecoin-project/go-amt-ipld/tree/master/internal
|
||||
// which for some reason is a go internal package and therefore cannot be imported
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
cid "github.com/ipfs/go-cid"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
xerrors "golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type AMTRoot struct {
|
||||
BitWidth uint64
|
||||
Height uint64
|
||||
Count uint64
|
||||
AMTNode AMTNode
|
||||
}
|
||||
|
||||
type AMTNode struct {
|
||||
Bmap []byte
|
||||
Links []cid.Cid
|
||||
Values []*cbg.Deferred
|
||||
}
|
||||
|
||||
// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT.
|
||||
|
||||
var _ = xerrors.Errorf
|
||||
var _ = cid.Undef
|
||||
var _ = math.E
|
||||
var _ = sort.Sort
|
||||
|
||||
var lengthBufAMTRoot = []byte{132}
|
||||
|
||||
func (t *AMTRoot) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
|
||||
cw := cbg.NewCborWriter(w)
|
||||
|
||||
if _, err := cw.Write(lengthBufAMTRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.BitWidth (uint64) (uint64)
|
||||
|
||||
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, t.BitWidth); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Height (uint64) (uint64)
|
||||
|
||||
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, t.Height); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Count (uint64) (uint64)
|
||||
|
||||
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, t.Count); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.AMTNode (internal.AMTNode) (struct)
|
||||
if err := t.AMTNode.MarshalCBOR(cw); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *AMTRoot) UnmarshalCBOR(r io.Reader) (err error) {
|
||||
*t = AMTRoot{}
|
||||
|
||||
cr := cbg.NewCborReader(r)
|
||||
|
||||
maj, extra, err := cr.ReadHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
}()
|
||||
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("cbor input should be of type array")
|
||||
}
|
||||
|
||||
if extra != 4 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
}
|
||||
|
||||
// t.BitWidth (uint64) (uint64)
|
||||
|
||||
{
|
||||
|
||||
maj, extra, err = cr.ReadHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.BitWidth = extra
|
||||
|
||||
}
|
||||
// t.Height (uint64) (uint64)
|
||||
|
||||
{
|
||||
|
||||
maj, extra, err = cr.ReadHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.Height = extra
|
||||
|
||||
}
|
||||
// t.Count (uint64) (uint64)
|
||||
|
||||
{
|
||||
|
||||
maj, extra, err = cr.ReadHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maj != cbg.MajUnsignedInt {
|
||||
return fmt.Errorf("wrong type for uint64 field")
|
||||
}
|
||||
t.Count = extra
|
||||
|
||||
}
|
||||
// t.AMTNode (internal.AMTNode) (struct)
|
||||
|
||||
{
|
||||
|
||||
if err := t.AMTNode.UnmarshalCBOR(cr); err != nil {
|
||||
return xerrors.Errorf("unmarshaling t.AMTNode: %w", err)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var lengthBufAMTNode = []byte{131}
|
||||
|
||||
func (t *AMTNode) MarshalCBOR(w io.Writer) error {
|
||||
if t == nil {
|
||||
_, err := w.Write(cbg.CborNull)
|
||||
return err
|
||||
}
|
||||
|
||||
cw := cbg.NewCborWriter(w)
|
||||
|
||||
if _, err := cw.Write(lengthBufAMTNode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Bmap ([]uint8) (slice)
|
||||
if len(t.Bmap) > cbg.ByteArrayMaxLen {
|
||||
return xerrors.Errorf("Byte array in field t.Bmap was too long")
|
||||
}
|
||||
|
||||
if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Bmap))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := cw.Write(t.Bmap[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// t.Links ([]cid.Cid) (slice)
|
||||
if len(t.Links) > cbg.MaxLength {
|
||||
return xerrors.Errorf("Slice value in field t.Links was too long")
|
||||
}
|
||||
|
||||
if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Links))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range t.Links {
|
||||
if err := cbg.WriteCid(w, v); err != nil {
|
||||
return xerrors.Errorf("failed writing cid field t.Links: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// t.Values ([]*typegen.Deferred) (slice)
|
||||
if len(t.Values) > cbg.MaxLength {
|
||||
return xerrors.Errorf("Slice value in field t.Values was too long")
|
||||
}
|
||||
|
||||
if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Values))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range t.Values {
|
||||
if err := v.MarshalCBOR(cw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *AMTNode) UnmarshalCBOR(r io.Reader) (err error) {
|
||||
*t = AMTNode{}
|
||||
|
||||
cr := cbg.NewCborReader(r)
|
||||
|
||||
maj, extra, err := cr.ReadHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
}()
|
||||
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("cbor input should be of type array")
|
||||
}
|
||||
|
||||
if extra != 3 {
|
||||
return fmt.Errorf("cbor input had wrong number of fields")
|
||||
}
|
||||
|
||||
// t.Bmap ([]uint8) (slice)
|
||||
|
||||
maj, extra, err = cr.ReadHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if extra > cbg.ByteArrayMaxLen {
|
||||
return fmt.Errorf("t.Bmap: byte array too large (%d)", extra)
|
||||
}
|
||||
if maj != cbg.MajByteString {
|
||||
return fmt.Errorf("expected byte array")
|
||||
}
|
||||
|
||||
if extra > 0 {
|
||||
t.Bmap = make([]uint8, extra)
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(cr, t.Bmap[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
// t.Links ([]cid.Cid) (slice)
|
||||
|
||||
maj, extra, err = cr.ReadHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if extra > cbg.MaxLength {
|
||||
return fmt.Errorf("t.Links: array too large (%d)", extra)
|
||||
}
|
||||
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
|
||||
if extra > 0 {
|
||||
t.Links = make([]cid.Cid, extra)
|
||||
}
|
||||
|
||||
for i := 0; i < int(extra); i++ {
|
||||
|
||||
c, err := cbg.ReadCid(cr)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("reading cid field t.Links failed: %w", err)
|
||||
}
|
||||
t.Links[i] = c
|
||||
}
|
||||
|
||||
// t.Values ([]*typegen.Deferred) (slice)
|
||||
|
||||
maj, extra, err = cr.ReadHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if extra > cbg.MaxLength {
|
||||
return fmt.Errorf("t.Values: array too large (%d)", extra)
|
||||
}
|
||||
|
||||
if maj != cbg.MajArray {
|
||||
return fmt.Errorf("expected cbor array")
|
||||
}
|
||||
|
||||
if extra > 0 {
|
||||
t.Values = make([]*cbg.Deferred, extra)
|
||||
}
|
||||
|
||||
for i := 0; i < int(extra); i++ {
|
||||
|
||||
var v cbg.Deferred
|
||||
if err := v.UnmarshalCBOR(cr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Values[i] = &v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var cliCmd = &cli.Command{
|
||||
Name: "cli",
|
||||
Usage: "Runs a concurrent stress test on one or more binaries commands and prints the performance metrics including latency distribution and histogram",
|
||||
Description: `This benchmark has the following features:
|
||||
* Can query each command both sequentially and concurrently
|
||||
* Supports rate limiting
|
||||
* Can query multiple different commands at once (supporting different concurrency level and rate limiting for each command)
|
||||
* Gives a nice reporting summary of the stress testing of each command (including latency distribution, histogram and more)
|
||||
* Easy to use
|
||||
|
||||
To use this benchmark you must specify the commands you want to test using the --cmd options, the format of it is:
|
||||
|
||||
--cmd=CMD[:CONCURRENCY][:QPS] where only NAME is required.
|
||||
|
||||
Here are some real examples:
|
||||
lotus-bench cli --cmd='lotus-shed mpool miner-select-messages' // runs the command with default concurrency and qps
|
||||
lotus-bench cli --cmd='lotus-shed mpool miner-select-messages:3' // override concurrency to 3
|
||||
lotus-bench cli --cmd='lotus-shed mpool miner-select-messages::100' // override to 100 qps while using default concurrency
|
||||
lotus-bench cli --cmd='lotus-shed mpool miner-select-messages:3:100' // run using 3 workers but limit to 100 qps
|
||||
lotus-bench cli --cmd='lotus-shed mpool miner-select-messages' --cmd='lotus sync wait' // run two commands at once
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.DurationFlag{
|
||||
Name: "duration",
|
||||
Value: 60 * time.Second,
|
||||
Usage: "Duration of benchmark in seconds",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 10,
|
||||
Usage: "How many workers should be used per command (can be overridden per command)",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "qps",
|
||||
Value: 0,
|
||||
Usage: "How many requests per second should be sent per command (can be overridden per command), a value of 0 means no limit",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "cmd",
|
||||
Usage: `Command to benchmark, you can specify multiple commands by repeating this flag. You can also specify command specific options to set the concurrency and qps for each command (see usage).`,
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Name: "watch",
|
||||
Value: 0 * time.Second,
|
||||
Usage: "If >0 then generates reports every N seconds (only supports linux/unix)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "print-response",
|
||||
Value: false,
|
||||
Usage: "print the response of each request",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
if len(cctx.StringSlice("cmd")) == 0 {
|
||||
return errors.New("you must specify and least one cmd to benchmark")
|
||||
}
|
||||
|
||||
var cmds []*CMD
|
||||
for _, str := range cctx.StringSlice("cmd") {
|
||||
entries := strings.SplitN(str, ":", 3)
|
||||
if len(entries) == 0 {
|
||||
return errors.New("invalid cmd format")
|
||||
}
|
||||
|
||||
// check if concurrency was specified
|
||||
concurrency := cctx.Int("concurrency")
|
||||
if len(entries) > 1 {
|
||||
if len(entries[1]) > 0 {
|
||||
var err error
|
||||
concurrency, err = strconv.Atoi(entries[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse concurrency value from command %s: %v", entries[0], err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if qps was specified
|
||||
qps := cctx.Int("qps")
|
||||
if len(entries) > 2 {
|
||||
if len(entries[2]) > 0 {
|
||||
var err error
|
||||
qps, err = strconv.Atoi(entries[2])
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse qps value from command %s: %v", entries[0], err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmds = append(cmds, &CMD{
|
||||
w: os.Stdout,
|
||||
cmd: entries[0],
|
||||
concurrency: concurrency,
|
||||
qps: qps,
|
||||
printResp: cctx.Bool("print-response"),
|
||||
})
|
||||
}
|
||||
|
||||
// terminate early on ctrl+c
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
<-c
|
||||
fmt.Println("Received interrupt, stopping...")
|
||||
for _, cmd := range cmds {
|
||||
cmd.Stop()
|
||||
}
|
||||
}()
|
||||
|
||||
// stop all threads after duration
|
||||
go func() {
|
||||
time.Sleep(cctx.Duration("duration"))
|
||||
for _, cmd := range cmds {
|
||||
cmd.Stop()
|
||||
}
|
||||
}()
|
||||
|
||||
// start all threads
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(cmds))
|
||||
|
||||
for _, cmd := range cmds {
|
||||
go func(cmd *CMD) {
|
||||
defer wg.Done()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("error running cmd: %v\n", err)
|
||||
}
|
||||
}(cmd)
|
||||
}
|
||||
|
||||
// if watch is set then print a report every N seconds
|
||||
var progressCh chan struct{}
|
||||
if cctx.Duration("watch") > 0 {
|
||||
progressCh = make(chan struct{}, 1)
|
||||
go func(progressCh chan struct{}) {
|
||||
ticker := time.NewTicker(cctx.Duration("watch"))
|
||||
for {
|
||||
clearAndPrintReport := func() {
|
||||
// clear the screen move the cursor to the top left
|
||||
fmt.Print("\033[2J")
|
||||
fmt.Printf("\033[%d;%dH", 1, 1)
|
||||
for i, cmd := range cmds {
|
||||
cmd.Report()
|
||||
if i < len(cmds)-1 {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ticker.C:
|
||||
clearAndPrintReport()
|
||||
case <-progressCh:
|
||||
clearAndPrintReport()
|
||||
return
|
||||
}
|
||||
}
|
||||
}(progressCh)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if progressCh != nil {
|
||||
// wait for the watch go routine to return
|
||||
progressCh <- struct{}{}
|
||||
|
||||
// no need to print the report again
|
||||
return nil
|
||||
}
|
||||
|
||||
// print the report for each command
|
||||
for i, cmd := range cmds {
|
||||
cmd.Report()
|
||||
if i < len(cmds)-1 {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// CMD handles the benchmarking of a single command.
|
||||
type CMD struct {
|
||||
w io.Writer
|
||||
// the cmd we want to benchmark
|
||||
cmd string
|
||||
// the number of concurrent requests to make to this command
|
||||
concurrency int
|
||||
// if >0 then limit to qps is the max number of requests per second to make to this command (0 = no limit)
|
||||
qps int
|
||||
// whether or not to print the response of each request (useful for debugging)
|
||||
printResp bool
|
||||
// instruct the worker go routines to stop
|
||||
stopCh chan struct{}
|
||||
// when the command bencharking started
|
||||
start time.Time
|
||||
// results channel is used by the workers to send results to the reporter
|
||||
results chan *result
|
||||
// reporter handles reading the results from workers and printing the report statistics
|
||||
reporter *Reporter
|
||||
}
|
||||
|
||||
func (c *CMD) Run() error {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(c.concurrency)
|
||||
|
||||
c.results = make(chan *result, c.concurrency*1_000)
|
||||
c.stopCh = make(chan struct{}, c.concurrency)
|
||||
|
||||
go func() {
|
||||
c.reporter = NewReporter(c.results, c.w)
|
||||
c.reporter.Run()
|
||||
}()
|
||||
|
||||
c.start = time.Now()
|
||||
|
||||
// throttle the number of requests per second
|
||||
var qpsTicker *time.Ticker
|
||||
if c.qps > 0 {
|
||||
qpsTicker = time.NewTicker(time.Second / time.Duration(c.qps))
|
||||
}
|
||||
|
||||
for i := 0; i < c.concurrency; i++ {
|
||||
go func() {
|
||||
c.startWorker(qpsTicker)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// close the results channel so reporter will stop
|
||||
close(c.results)
|
||||
|
||||
// wait until the reporter is done
|
||||
<-c.reporter.doneCh
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CMD) startWorker(qpsTicker *time.Ticker) {
|
||||
for {
|
||||
// check if we should stop
|
||||
select {
|
||||
case <-c.stopCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// wait for the next tick if we are rate limiting this command
|
||||
if qpsTicker != nil {
|
||||
<-qpsTicker.C
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
var statusCode int = 0
|
||||
|
||||
arr := strings.Fields(c.cmd)
|
||||
|
||||
data, err := exec.Command(arr[0], arr[1:]...).Output()
|
||||
if err != nil {
|
||||
fmt.Println("1")
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
statusCode = exitError.ExitCode()
|
||||
} else {
|
||||
statusCode = 1
|
||||
}
|
||||
} else {
|
||||
if c.printResp {
|
||||
fmt.Printf("[%s] %s", c.cmd, string(data))
|
||||
}
|
||||
}
|
||||
|
||||
c.results <- &result{
|
||||
statusCode: &statusCode,
|
||||
err: err,
|
||||
duration: time.Since(start),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CMD) Stop() {
|
||||
for i := 0; i < c.concurrency; i++ {
|
||||
c.stopCh <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CMD) Report() {
|
||||
total := time.Since(c.start)
|
||||
fmt.Fprintf(c.w, "[%s]:\n", c.cmd)
|
||||
fmt.Fprintf(c.w, "- Options:\n")
|
||||
fmt.Fprintf(c.w, " - concurrency: %d\n", c.concurrency)
|
||||
fmt.Fprintf(c.w, " - qps: %d\n", c.qps)
|
||||
c.reporter.Print(total, c.w)
|
||||
}
|
||||
@@ -497,21 +497,6 @@ type Invocation struct {
|
||||
|
||||
const GasPerNs = 10
|
||||
|
||||
func countGasCosts(et *types.ExecutionTrace) int64 {
|
||||
var cgas int64
|
||||
|
||||
for _, gc := range et.GasCharges {
|
||||
cgas += gc.ComputeGas
|
||||
}
|
||||
|
||||
for _, sub := range et.Subcalls {
|
||||
c := countGasCosts(&sub) //nolint
|
||||
cgas += c
|
||||
}
|
||||
|
||||
return cgas
|
||||
}
|
||||
|
||||
type stats struct {
|
||||
timeTaken meanVar
|
||||
gasRatio meanVar
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -8,9 +9,16 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
"github.com/ipfs/boxo/blockservice"
|
||||
"github.com/ipfs/boxo/ipld/merkledag"
|
||||
"github.com/ipfs/go-cid"
|
||||
offline "github.com/ipfs/go-ipfs-exchange-offline"
|
||||
cbor "github.com/ipfs/go-ipld-cbor"
|
||||
format "github.com/ipfs/go-ipld-format"
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
"github.com/minio/blake2b-simd"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
@@ -20,10 +28,14 @@ import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-paramfetch"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/builtin/v9/verifreg"
|
||||
prooftypes "github.com/filecoin-project/go-state-types/proof"
|
||||
adt "github.com/filecoin-project/specs-actors/v6/actors/util/adt"
|
||||
|
||||
lapi "github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/blockstore"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin/market"
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
@@ -104,9 +116,11 @@ func main() {
|
||||
DisableSliceFlagSeparator: true,
|
||||
Commands: []*cli.Command{
|
||||
proveCmd,
|
||||
amtBenchCmd,
|
||||
sealBenchCmd,
|
||||
simpleCmd,
|
||||
importBenchCmd,
|
||||
cliCmd,
|
||||
rpcCmd,
|
||||
},
|
||||
}
|
||||
@@ -117,6 +131,211 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
type amtStatCollector struct {
|
||||
ds format.NodeGetter
|
||||
walk func(format.Node) ([]*format.Link, error)
|
||||
|
||||
statsLk sync.Mutex
|
||||
totalAMTLinks int
|
||||
totalAMTValues int
|
||||
totalAMTLinkNodes int
|
||||
totalAMTValueNodes int
|
||||
totalAMTLinkNodeSize int
|
||||
totalAMTValueNodeSize int
|
||||
}
|
||||
|
||||
func (asc *amtStatCollector) String() string {
|
||||
asc.statsLk.Lock()
|
||||
defer asc.statsLk.Unlock()
|
||||
|
||||
str := "\n------------\n"
|
||||
str += fmt.Sprintf("Link Count: %d\n", asc.totalAMTLinks)
|
||||
str += fmt.Sprintf("Value Count: %d\n", asc.totalAMTValues)
|
||||
str += fmt.Sprintf("%d link nodes %d bytes\n", asc.totalAMTLinkNodes, asc.totalAMTLinkNodeSize)
|
||||
str += fmt.Sprintf("%d value nodes %d bytes\n", asc.totalAMTValueNodes, asc.totalAMTValueNodeSize)
|
||||
str += fmt.Sprintf("Total bytes: %d\n------------\n", asc.totalAMTLinkNodeSize+asc.totalAMTValueNodeSize)
|
||||
return str
|
||||
}
|
||||
|
||||
func (asc *amtStatCollector) record(ctx context.Context, nd format.Node) error {
|
||||
size, err := nd.Size()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var node AMTNode
|
||||
if err := node.UnmarshalCBOR(bytes.NewReader(nd.RawData())); err != nil {
|
||||
// try to deserialize root
|
||||
var root AMTRoot
|
||||
if err := root.UnmarshalCBOR(bytes.NewReader(nd.RawData())); err != nil {
|
||||
return err
|
||||
}
|
||||
node = root.AMTNode
|
||||
}
|
||||
|
||||
asc.statsLk.Lock()
|
||||
defer asc.statsLk.Unlock()
|
||||
|
||||
link := len(node.Links) > 0
|
||||
value := len(node.Values) > 0
|
||||
|
||||
if link {
|
||||
asc.totalAMTLinks += len(node.Links)
|
||||
asc.totalAMTLinkNodes++
|
||||
asc.totalAMTLinkNodeSize += int(size)
|
||||
} else if value {
|
||||
asc.totalAMTValues += len(node.Values)
|
||||
asc.totalAMTValueNodes++
|
||||
asc.totalAMTValueNodeSize += int(size)
|
||||
} else {
|
||||
return xerrors.Errorf("unexpected AMT node %x: neither link nor value", nd.RawData())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (asc *amtStatCollector) walkLinks(ctx context.Context, c cid.Cid) ([]*format.Link, error) {
|
||||
nd, err := asc.ds.Get(ctx, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := asc.record(ctx, nd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return asc.walk(nd)
|
||||
}
|
||||
|
||||
func carWalkFunc(nd format.Node) (out []*format.Link, err error) {
|
||||
for _, link := range nd.Links() {
|
||||
if link.Cid.Prefix().Codec == cid.FilCommitmentSealed || link.Cid.Prefix().Codec == cid.FilCommitmentUnsealed {
|
||||
continue
|
||||
}
|
||||
out = append(out, link)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
var amtBenchCmd = &cli.Command{
|
||||
Name: "amt",
|
||||
Usage: "Benchmark AMT churn",
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "rounds",
|
||||
Usage: "rounds of churn to measure",
|
||||
Value: 1,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "interval",
|
||||
Usage: "AMT idx interval for churning values",
|
||||
Value: 2880,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "bitwidth",
|
||||
Usage: "AMT bitwidth",
|
||||
Value: 6,
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
bs := blockstore.NewMemory()
|
||||
ctx := c.Context
|
||||
store := adt.WrapStore(ctx, cbor.NewCborStore(bs))
|
||||
|
||||
// Setup in memory blockstore
|
||||
bitwidth := c.Int("bitwidth")
|
||||
array, err := adt.MakeEmptyArray(store, bitwidth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Using motivating empirical example: market actor states AMT
|
||||
// Create 40,000,000 states for realistic workload
|
||||
fmt.Printf("Populating AMT\n")
|
||||
for i := 0; i < 40000000; i++ {
|
||||
if err := array.Set(uint64(i), &market.DealState{
|
||||
SectorStartEpoch: abi.ChainEpoch(2000000 + i),
|
||||
LastUpdatedEpoch: abi.ChainEpoch(-1),
|
||||
SlashEpoch: -1,
|
||||
VerifiedClaim: verifreg.AllocationId(i),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r, err := array.Root()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Measure ratio of internal / leaf nodes / sizes
|
||||
dag := merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs)))
|
||||
asc := &amtStatCollector{
|
||||
ds: dag,
|
||||
walk: carWalkFunc,
|
||||
}
|
||||
|
||||
fmt.Printf("Measuring AMT\n")
|
||||
seen := cid.NewSet()
|
||||
if err := merkledag.Walk(ctx, asc.walkLinks, r, seen.Visit, merkledag.Concurrent()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", asc)
|
||||
|
||||
// Overwrite ids with idx % interval: one epoch of market cron
|
||||
rounds := c.Int("rounds")
|
||||
interval := c.Int("interval")
|
||||
|
||||
fmt.Printf("Overwrite 1 out of %d values for %d rounds\n", interval, rounds)
|
||||
array, err = adt.AsArray(store, r, bitwidth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
roots := make([]cid.Cid, rounds)
|
||||
for j := 0; j < rounds; j++ {
|
||||
if j%10 == 0 {
|
||||
fmt.Printf("round: %d\n", j)
|
||||
}
|
||||
for i := j; i < 40000000; i += interval {
|
||||
if i%interval == j {
|
||||
if err := array.Set(uint64(i), &market.DealState{
|
||||
SectorStartEpoch: abi.ChainEpoch(2000000 + i),
|
||||
LastUpdatedEpoch: abi.ChainEpoch(1),
|
||||
SlashEpoch: -1,
|
||||
VerifiedClaim: verifreg.AllocationId(i),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
roots[j], err = array.Root()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Measure churn
|
||||
dag = merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs)))
|
||||
asc = &amtStatCollector{
|
||||
ds: dag,
|
||||
walk: carWalkFunc,
|
||||
}
|
||||
|
||||
fmt.Printf("Measuring %d rounds of churn\n", rounds)
|
||||
|
||||
for _, r := range roots {
|
||||
if err := merkledag.Walk(ctx, asc.walkLinks, r, seen.Visit, merkledag.Concurrent()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", asc)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var sealBenchCmd = &cli.Command{
|
||||
Name: "sealing",
|
||||
Usage: "Benchmark seal and winning post and window post",
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
)
|
||||
|
||||
// result is the result of a single rpc method request.
|
||||
type result struct {
|
||||
err error
|
||||
statusCode *int
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
// Reporter reads the results from the workers through the results channel and aggregates the results.
|
||||
type Reporter struct {
|
||||
// write the report to this writer
|
||||
w io.Writer
|
||||
// the reporter read the results from this channel
|
||||
results chan *result
|
||||
// doneCh is used to signal that the reporter has finished reading the results (channel has closed)
|
||||
doneCh chan bool
|
||||
|
||||
// lock protect the following fields during critical sections (if --watch was specified)
|
||||
lock sync.Mutex
|
||||
// the latencies of all requests
|
||||
latencies []int64
|
||||
// the number of requests that returned each status code
|
||||
statusCodes map[int]int
|
||||
// the number of errors that occurred
|
||||
errors map[string]int
|
||||
}
|
||||
|
||||
func NewReporter(results chan *result, w io.Writer) *Reporter {
|
||||
return &Reporter{
|
||||
w: w,
|
||||
results: results,
|
||||
doneCh: make(chan bool, 1),
|
||||
statusCodes: make(map[int]int),
|
||||
errors: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reporter) Run() {
|
||||
for res := range r.results {
|
||||
r.lock.Lock()
|
||||
|
||||
r.latencies = append(r.latencies, res.duration.Milliseconds())
|
||||
|
||||
if res.statusCode != nil {
|
||||
r.statusCodes[*res.statusCode]++
|
||||
}
|
||||
|
||||
if res.err != nil {
|
||||
if len(r.errors) < 1_000_000 {
|
||||
r.errors[res.err.Error()]++
|
||||
} else {
|
||||
// we don't want to store too many errors in memory
|
||||
r.errors["hidden"]++
|
||||
}
|
||||
} else {
|
||||
r.errors["nil"]++
|
||||
}
|
||||
|
||||
r.lock.Unlock()
|
||||
}
|
||||
|
||||
r.doneCh <- true
|
||||
}
|
||||
|
||||
func (r *Reporter) Print(elapsed time.Duration, w io.Writer) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
nrReq := int64(len(r.latencies))
|
||||
if nrReq == 0 {
|
||||
fmt.Println("No requests were made")
|
||||
return
|
||||
}
|
||||
|
||||
// we need to sort the latencies slice to calculate the percentiles
|
||||
sort.Slice(r.latencies, func(i, j int) bool {
|
||||
return r.latencies[i] < r.latencies[j]
|
||||
})
|
||||
|
||||
var totalLatency int64 = 0
|
||||
for _, latency := range r.latencies {
|
||||
totalLatency += latency
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "- Total Requests: %d\n", nrReq)
|
||||
fmt.Fprintf(w, "- Total Duration: %dms\n", elapsed.Milliseconds())
|
||||
fmt.Fprintf(w, "- Requests/sec: %f\n", float64(nrReq)/elapsed.Seconds())
|
||||
fmt.Fprintf(w, "- Avg latency: %dms\n", totalLatency/nrReq)
|
||||
fmt.Fprintf(w, "- Median latency: %dms\n", r.latencies[nrReq/2])
|
||||
fmt.Fprintf(w, "- Latency distribution:\n")
|
||||
percentiles := []float64{0.1, 0.5, 0.9, 0.95, 0.99, 0.999}
|
||||
for _, p := range percentiles {
|
||||
idx := int64(p * float64(nrReq))
|
||||
fmt.Fprintf(w, " %s%% in %dms\n", fmt.Sprintf("%.2f", p*100.0), r.latencies[idx])
|
||||
}
|
||||
|
||||
// create a simple histogram with 10 buckets spanning the range of latency
|
||||
// into equal ranges
|
||||
//
|
||||
nrBucket := 10
|
||||
buckets := make([]Bucket, nrBucket)
|
||||
latencyRange := r.latencies[len(r.latencies)-1]
|
||||
bucketRange := latencyRange / int64(nrBucket)
|
||||
|
||||
// mark the end of each bucket
|
||||
for i := 0; i < nrBucket; i++ {
|
||||
buckets[i].start = int64(i) * bucketRange
|
||||
buckets[i].end = buckets[i].start + bucketRange
|
||||
// extend the last bucked by any remaning range caused by the integer division
|
||||
if i == nrBucket-1 {
|
||||
buckets[i].end = latencyRange
|
||||
}
|
||||
}
|
||||
|
||||
// count the number of requests in each bucket
|
||||
currBucket := 0
|
||||
for i := 0; i < len(r.latencies); {
|
||||
if r.latencies[i] <= buckets[currBucket].end {
|
||||
buckets[currBucket].cnt++
|
||||
i++
|
||||
} else {
|
||||
currBucket++
|
||||
}
|
||||
}
|
||||
|
||||
// print the histogram using a tabwriter which will align the columns nicely
|
||||
fmt.Fprintf(w, "- Histogram:\n")
|
||||
const padding = 2
|
||||
tabWriter := tabwriter.NewWriter(w, 0, 0, padding, ' ', tabwriter.AlignRight|tabwriter.Debug)
|
||||
for i := 0; i < nrBucket; i++ {
|
||||
ratio := float64(buckets[i].cnt) / float64(nrReq)
|
||||
bars := strings.Repeat("#", int(ratio*100))
|
||||
fmt.Fprintf(tabWriter, " %d-%dms\t%d\t%s (%s%%)\n", buckets[i].start, buckets[i].end, buckets[i].cnt, bars, fmt.Sprintf("%.2f", ratio*100))
|
||||
}
|
||||
tabWriter.Flush() //nolint:errcheck
|
||||
|
||||
fmt.Fprintf(w, "- Status codes:\n")
|
||||
for code, cnt := range r.statusCodes {
|
||||
fmt.Fprintf(w, " [%d]: %d\n", code, cnt)
|
||||
}
|
||||
|
||||
// print the 10 most occurring errors (in case error values are not unique)
|
||||
//
|
||||
type kv struct {
|
||||
err string
|
||||
cnt int
|
||||
}
|
||||
var sortedErrors []kv
|
||||
for err, cnt := range r.errors {
|
||||
sortedErrors = append(sortedErrors, kv{err, cnt})
|
||||
}
|
||||
sort.Slice(sortedErrors, func(i, j int) bool {
|
||||
return sortedErrors[i].cnt > sortedErrors[j].cnt
|
||||
})
|
||||
fmt.Fprintf(w, "- Errors (top 10):\n")
|
||||
for i, se := range sortedErrors {
|
||||
if i > 10 {
|
||||
break
|
||||
}
|
||||
fmt.Fprintf(w, " [%s]: %d\n", se.err, se.cnt)
|
||||
}
|
||||
}
|
||||
|
||||
type Bucket struct {
|
||||
start int64
|
||||
// the end value of the bucket
|
||||
end int64
|
||||
// how many entries are in the bucket
|
||||
cnt int
|
||||
}
|
||||
@@ -9,11 +9,9 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -243,13 +241,6 @@ type RPCMethod struct {
|
||||
reporter *Reporter
|
||||
}
|
||||
|
||||
// result is the result of a single rpc method request.
|
||||
type result struct {
|
||||
err error
|
||||
statusCode *int
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
func (rpc *RPCMethod) Run() error {
|
||||
client := &http.Client{
|
||||
Timeout: 0,
|
||||
@@ -411,166 +402,3 @@ func (rpc *RPCMethod) Report() {
|
||||
fmt.Fprintf(rpc.w, " - qps: %d\n", rpc.qps)
|
||||
rpc.reporter.Print(total, rpc.w)
|
||||
}
|
||||
|
||||
// Reporter reads the results from the workers through the results channel and aggregates the results.
|
||||
type Reporter struct {
|
||||
// write the report to this writer
|
||||
w io.Writer
|
||||
// the reporter read the results from this channel
|
||||
results chan *result
|
||||
// doneCh is used to signal that the reporter has finished reading the results (channel has closed)
|
||||
doneCh chan bool
|
||||
|
||||
// lock protect the following fields during critical sections (if --watch was specified)
|
||||
lock sync.Mutex
|
||||
// the latencies of all requests
|
||||
latencies []int64
|
||||
// the number of requests that returned each status code
|
||||
statusCodes map[int]int
|
||||
// the number of errors that occurred
|
||||
errors map[string]int
|
||||
}
|
||||
|
||||
func NewReporter(results chan *result, w io.Writer) *Reporter {
|
||||
return &Reporter{
|
||||
w: w,
|
||||
results: results,
|
||||
doneCh: make(chan bool, 1),
|
||||
statusCodes: make(map[int]int),
|
||||
errors: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reporter) Run() {
|
||||
for res := range r.results {
|
||||
r.lock.Lock()
|
||||
|
||||
r.latencies = append(r.latencies, res.duration.Milliseconds())
|
||||
|
||||
if res.statusCode != nil {
|
||||
r.statusCodes[*res.statusCode]++
|
||||
}
|
||||
|
||||
if res.err != nil {
|
||||
if len(r.errors) < 1_000_000 {
|
||||
r.errors[res.err.Error()]++
|
||||
} else {
|
||||
// we don't want to store too many errors in memory
|
||||
r.errors["hidden"]++
|
||||
}
|
||||
} else {
|
||||
r.errors["nil"]++
|
||||
}
|
||||
|
||||
r.lock.Unlock()
|
||||
}
|
||||
|
||||
r.doneCh <- true
|
||||
}
|
||||
|
||||
func (r *Reporter) Print(elapsed time.Duration, w io.Writer) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
nrReq := int64(len(r.latencies))
|
||||
if nrReq == 0 {
|
||||
fmt.Println("No requests were made")
|
||||
return
|
||||
}
|
||||
|
||||
// we need to sort the latencies slice to calculate the percentiles
|
||||
sort.Slice(r.latencies, func(i, j int) bool {
|
||||
return r.latencies[i] < r.latencies[j]
|
||||
})
|
||||
|
||||
var totalLatency int64 = 0
|
||||
for _, latency := range r.latencies {
|
||||
totalLatency += latency
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "- Total Requests: %d\n", nrReq)
|
||||
fmt.Fprintf(w, "- Total Duration: %dms\n", elapsed.Milliseconds())
|
||||
fmt.Fprintf(w, "- Requests/sec: %f\n", float64(nrReq)/elapsed.Seconds())
|
||||
fmt.Fprintf(w, "- Avg latency: %dms\n", totalLatency/nrReq)
|
||||
fmt.Fprintf(w, "- Median latency: %dms\n", r.latencies[nrReq/2])
|
||||
fmt.Fprintf(w, "- Latency distribution:\n")
|
||||
percentiles := []float64{0.1, 0.5, 0.9, 0.95, 0.99, 0.999}
|
||||
for _, p := range percentiles {
|
||||
idx := int64(p * float64(nrReq))
|
||||
fmt.Fprintf(w, " %s%% in %dms\n", fmt.Sprintf("%.2f", p*100.0), r.latencies[idx])
|
||||
}
|
||||
|
||||
// create a simple histogram with 10 buckets spanning the range of latency
|
||||
// into equal ranges
|
||||
//
|
||||
nrBucket := 10
|
||||
buckets := make([]Bucket, nrBucket)
|
||||
latencyRange := r.latencies[len(r.latencies)-1]
|
||||
bucketRange := latencyRange / int64(nrBucket)
|
||||
|
||||
// mark the end of each bucket
|
||||
for i := 0; i < nrBucket; i++ {
|
||||
buckets[i].start = int64(i) * bucketRange
|
||||
buckets[i].end = buckets[i].start + bucketRange
|
||||
// extend the last bucked by any remaning range caused by the integer division
|
||||
if i == nrBucket-1 {
|
||||
buckets[i].end = latencyRange
|
||||
}
|
||||
}
|
||||
|
||||
// count the number of requests in each bucket
|
||||
currBucket := 0
|
||||
for i := 0; i < len(r.latencies); {
|
||||
if r.latencies[i] <= buckets[currBucket].end {
|
||||
buckets[currBucket].cnt++
|
||||
i++
|
||||
} else {
|
||||
currBucket++
|
||||
}
|
||||
}
|
||||
|
||||
// print the histogram using a tabwriter which will align the columns nicely
|
||||
fmt.Fprintf(w, "- Histogram:\n")
|
||||
const padding = 2
|
||||
tabWriter := tabwriter.NewWriter(w, 0, 0, padding, ' ', tabwriter.AlignRight|tabwriter.Debug)
|
||||
for i := 0; i < nrBucket; i++ {
|
||||
ratio := float64(buckets[i].cnt) / float64(nrReq)
|
||||
bars := strings.Repeat("#", int(ratio*100))
|
||||
fmt.Fprintf(tabWriter, " %d-%dms\t%d\t%s (%s%%)\n", buckets[i].start, buckets[i].end, buckets[i].cnt, bars, fmt.Sprintf("%.2f", ratio*100))
|
||||
}
|
||||
tabWriter.Flush() //nolint:errcheck
|
||||
|
||||
fmt.Fprintf(w, "- Status codes:\n")
|
||||
for code, cnt := range r.statusCodes {
|
||||
fmt.Fprintf(w, " [%d]: %d\n", code, cnt)
|
||||
}
|
||||
|
||||
// print the 10 most occurring errors (in case error values are not unique)
|
||||
//
|
||||
type kv struct {
|
||||
err string
|
||||
cnt int
|
||||
}
|
||||
var sortedErrors []kv
|
||||
for err, cnt := range r.errors {
|
||||
sortedErrors = append(sortedErrors, kv{err, cnt})
|
||||
}
|
||||
sort.Slice(sortedErrors, func(i, j int) bool {
|
||||
return sortedErrors[i].cnt > sortedErrors[j].cnt
|
||||
})
|
||||
fmt.Fprintf(w, "- Errors (top 10):\n")
|
||||
for i, se := range sortedErrors {
|
||||
if i > 10 {
|
||||
break
|
||||
}
|
||||
fmt.Fprintf(w, " [%s]: %d\n", se.err, se.cnt)
|
||||
}
|
||||
}
|
||||
|
||||
type Bucket struct {
|
||||
start int64
|
||||
// the end value of the bucket
|
||||
end int64
|
||||
// how many entries are in the bucket
|
||||
cnt int
|
||||
}
|
||||
|
||||
@@ -120,6 +120,11 @@ p: pvC0JBrEyUqtIIUvB2UUx/2a24c3Cvnu6AZ0D3IMBYAu...
|
||||
|
||||
type benchSectorProvider map[storiface.SectorFileType]string
|
||||
|
||||
func (b benchSectorProvider) AcquireSectorCopy(ctx context.Context, id storiface.SectorRef, existing storiface.SectorFileType, allocate storiface.SectorFileType, ptype storiface.PathType) (storiface.SectorPaths, func(), error) {
|
||||
// there's no copying in this context
|
||||
return b.AcquireSector(ctx, id, existing, allocate, ptype)
|
||||
}
|
||||
|
||||
func (b benchSectorProvider) AcquireSector(ctx context.Context, id storiface.SectorRef, existing storiface.SectorFileType, allocate storiface.SectorFileType, ptype storiface.PathType) (storiface.SectorPaths, func(), error) {
|
||||
out := storiface.SectorPaths{
|
||||
ID: id.ID,
|
||||
|
||||
@@ -2566,6 +2566,8 @@ var sectorsUnsealCmd = &cli.Command{
|
||||
return xerrors.Errorf("could not parse sector number: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Unsealing sector %d\n", sectorNum)
|
||||
|
||||
return minerAPI.SectorUnseal(ctx, abi.SectorNumber(sectorNum))
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
)
|
||||
|
||||
var blockCmd = &cli.Command{
|
||||
Name: "block",
|
||||
Usage: "Output decoded block header in readeble form",
|
||||
ArgsUsage: "[block header hex]",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
if cctx.NArg() != 1 {
|
||||
return lcli.IncorrectNumArgs(cctx)
|
||||
}
|
||||
|
||||
b, err := hex.DecodeString(cctx.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var blk types.BlockHeader
|
||||
if err := blk.UnmarshalCBOR(bytes.NewReader(b)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jb, err := json.MarshalIndent(blk, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(jb))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -1,14 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
ipldcbor "github.com/ipfs/go-ipld-cbor"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-bitfield"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
miner11 "github.com/filecoin-project/go-state-types/builtin/v11/miner"
|
||||
"github.com/filecoin-project/go-state-types/builtin/v11/util/adt"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
)
|
||||
|
||||
@@ -17,6 +27,245 @@ var cronWcCmd = &cli.Command{
|
||||
Description: "cron stats",
|
||||
Subcommands: []*cli.Command{
|
||||
minerDeadlineCronCountCmd,
|
||||
minerDeadlinePartitionMeasurementCmd,
|
||||
},
|
||||
}
|
||||
|
||||
type DeadlineRef struct {
|
||||
To string
|
||||
Height abi.ChainEpoch
|
||||
Gas json.RawMessage
|
||||
}
|
||||
|
||||
type DeadlineSummary struct {
|
||||
Partitions []PartitionSummary
|
||||
PreCommitExpiry PreCommitExpiry
|
||||
VestingDiff VestingDiff
|
||||
}
|
||||
|
||||
type PreCommitExpiry struct {
|
||||
Expired []uint64
|
||||
}
|
||||
|
||||
type VestingDiff struct {
|
||||
PrevTableSize int
|
||||
NewTableSize int
|
||||
}
|
||||
|
||||
type PartitionSummary struct {
|
||||
Live int
|
||||
Dead int
|
||||
Faulty int
|
||||
Diff PartitionDiff
|
||||
}
|
||||
|
||||
type PartitionDiff struct {
|
||||
Faulted int
|
||||
Recovered int
|
||||
Killed int
|
||||
}
|
||||
|
||||
var minerDeadlinePartitionMeasurementCmd = &cli.Command{
|
||||
Name: "deadline-summary",
|
||||
Description: "",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "json",
|
||||
Usage: "read input as json",
|
||||
Value: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "tipset",
|
||||
Usage: "specify tipset state to search on (pass comma separated array of cids)",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
// read in values to process
|
||||
if !c.Bool("json") {
|
||||
return xerrors.Errorf("unsupported non json input format")
|
||||
}
|
||||
var refStream []DeadlineRef
|
||||
if err := json.NewDecoder(os.Stdin).Decode(&refStream); err != nil {
|
||||
return xerrors.Errorf("failed to parse input: %w", err)
|
||||
}
|
||||
|
||||
// go from height and sp addr to deadline partition data
|
||||
n, acloser, err := lcli.GetFullNodeAPI(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer acloser()
|
||||
ctx := lcli.ReqContext(c)
|
||||
|
||||
bs := ReadOnlyAPIBlockstore{n}
|
||||
adtStore := adt.WrapStore(ctx, ipldcbor.NewCborStore(&bs))
|
||||
|
||||
dSummaries := make([]DeadlineSummary, len(refStream))
|
||||
for j, ref := range refStream {
|
||||
// get miner's deadline
|
||||
tsBefore, err := n.ChainGetTipSetByHeight(ctx, ref.Height, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to get tipset at epoch: %d: %w", ref.Height, err)
|
||||
}
|
||||
tsAfter, err := n.ChainGetTipSetByHeight(ctx, ref.Height+1, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to get tipset at epoch %d: %w", ref.Height, err)
|
||||
}
|
||||
addr, err := address.NewFromString(ref.To)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("faield to get address from input string: %w", err)
|
||||
}
|
||||
dline, err := n.StateMinerProvingDeadline(ctx, addr, tsBefore.Key())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to read proving deadline: %w", err)
|
||||
}
|
||||
|
||||
// iterate through all partitions at epoch of processing
|
||||
var pSummaries []PartitionSummary
|
||||
psBefore, err := n.StateMinerPartitions(ctx, addr, dline.Index, tsBefore.Key())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to get partitions: %w", err)
|
||||
}
|
||||
psAfter, err := n.StateMinerPartitions(ctx, addr, dline.Index, tsAfter.Key())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to get partitions: %w", err)
|
||||
}
|
||||
if len(psBefore) != len(psAfter) {
|
||||
return xerrors.Errorf("faield")
|
||||
}
|
||||
|
||||
type partitionCount struct {
|
||||
live int
|
||||
dead int
|
||||
faulty int
|
||||
recovering int
|
||||
}
|
||||
countPartition := func(p api.Partition) (partitionCount, error) {
|
||||
liveSectors, err := p.LiveSectors.All(abi.MaxSectorNumber)
|
||||
if err != nil {
|
||||
return partitionCount{}, xerrors.Errorf("failed to count live sectors in partition: %w", err)
|
||||
}
|
||||
allSectors, err := p.AllSectors.All(abi.MaxSectorNumber)
|
||||
if err != nil {
|
||||
return partitionCount{}, xerrors.Errorf("failed to count all sectors in partition: %w", err)
|
||||
}
|
||||
faultySectors, err := p.FaultySectors.All(abi.MaxSectorNumber)
|
||||
if err != nil {
|
||||
return partitionCount{}, xerrors.Errorf("failed to count faulty sectors in partition: %w", err)
|
||||
}
|
||||
recoveringSectors, err := p.RecoveringSectors.All(abi.MaxSectorNumber)
|
||||
if err != nil {
|
||||
return partitionCount{}, xerrors.Errorf("failed to count recovering sectors in partition: %w", err)
|
||||
}
|
||||
|
||||
return partitionCount{
|
||||
live: len(liveSectors),
|
||||
dead: len(allSectors) - len(liveSectors),
|
||||
faulty: len(faultySectors),
|
||||
recovering: len(recoveringSectors),
|
||||
}, nil
|
||||
}
|
||||
|
||||
countVestingTable := func(table cid.Cid) (int, error) {
|
||||
var vestingTable miner11.VestingFunds
|
||||
if err := adtStore.Get(ctx, table, &vestingTable); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(vestingTable.Funds), nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(psBefore); i++ {
|
||||
cntBefore, err := countPartition(psBefore[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cntAfter, err := countPartition(psAfter[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pSummaries = append(pSummaries, PartitionSummary{
|
||||
Live: cntBefore.live,
|
||||
Dead: cntBefore.dead,
|
||||
Faulty: cntBefore.faulty,
|
||||
Diff: PartitionDiff{
|
||||
Faulted: cntAfter.faulty - cntBefore.faulty,
|
||||
Recovered: cntBefore.recovering - cntAfter.recovering,
|
||||
Killed: cntAfter.dead - cntBefore.dead,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Precommit and vesting table data
|
||||
// Before
|
||||
aBefore, err := n.StateGetActor(ctx, addr, tsBefore.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var st miner11.State
|
||||
err = adtStore.Get(ctx, aBefore.Head, &st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
expiryQArray, err := adt.AsArray(adtStore, st.PreCommittedSectorsCleanUp, miner11.PrecommitCleanUpAmtBitwidth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var sectorsBf bitfield.BitField
|
||||
var accumulator []uint64
|
||||
h := ref.Height
|
||||
if err := expiryQArray.ForEach(§orsBf, func(i int64) error {
|
||||
if abi.ChainEpoch(i) > h {
|
||||
return nil
|
||||
}
|
||||
sns, err := sectorsBf.All(abi.MaxSectorNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accumulator = append(accumulator, sns...)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vestingBefore, err := countVestingTable(st.VestingFunds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// After
|
||||
aAfter, err := n.StateGetActor(ctx, addr, tsAfter.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var stAfter miner11.State
|
||||
err = adtStore.Get(ctx, aAfter.Head, &stAfter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vestingAfter, err := countVestingTable(stAfter.VestingFunds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dSummaries[j] = DeadlineSummary{
|
||||
Partitions: pSummaries,
|
||||
PreCommitExpiry: PreCommitExpiry{
|
||||
Expired: accumulator,
|
||||
},
|
||||
VestingDiff: VestingDiff{
|
||||
PrevTableSize: vestingBefore,
|
||||
NewTableSize: vestingAfter,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// output partition info
|
||||
if err := json.NewEncoder(os.Stdout).Encode(dSummaries); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ func backTestWinner(ctx context.Context, miner address.Address, round abi.ChainE
|
||||
brand = bvals[len(bvals)-1]
|
||||
}
|
||||
|
||||
winner, err := gen.IsRoundWinner(ctx, ts, round, miner, brand, mbi, api)
|
||||
winner, err := gen.IsRoundWinner(ctx, round, miner, brand, mbi, api)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to check if we win next round: %w", err)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/filecoin-project/go-state-types/network"
|
||||
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/beacon"
|
||||
"github.com/filecoin-project/lotus/chain/beacon/drand"
|
||||
"github.com/filecoin-project/lotus/chain/consensus"
|
||||
"github.com/filecoin-project/lotus/chain/consensus/filcns"
|
||||
@@ -100,15 +99,11 @@ var gasTraceCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
dcs := build.DrandConfigSchedule()
|
||||
shd := beacon.Schedule{}
|
||||
for _, dc := range dcs {
|
||||
bc, err := drand.NewDrandBeacon(MAINNET_GENESIS_TIME, build.BlockDelaySecs, nil, dc.Config)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("creating drand beacon: %w", err)
|
||||
}
|
||||
shd = append(shd, beacon.BeaconPoint{Start: dc.Start, Beacon: bc})
|
||||
shd, err := drand.BeaconScheduleFromDrandSchedule(build.DrandConfigSchedule(), MAINNET_GENESIS_TIME, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs := store.NewChainStore(bs, bs, mds, filcns.Weight, nil)
|
||||
defer cs.Close() //nolint:errcheck
|
||||
|
||||
@@ -200,14 +195,9 @@ var replayOfflineCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
dcs := build.DrandConfigSchedule()
|
||||
shd := beacon.Schedule{}
|
||||
for _, dc := range dcs {
|
||||
bc, err := drand.NewDrandBeacon(MAINNET_GENESIS_TIME, build.BlockDelaySecs, nil, dc.Config)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("creating drand beacon: %w", err)
|
||||
}
|
||||
shd = append(shd, beacon.BeaconPoint{Start: dc.Start, Beacon: bc})
|
||||
shd, err := drand.BeaconScheduleFromDrandSchedule(build.DrandConfigSchedule(), MAINNET_GENESIS_TIME, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs := store.NewChainStore(bs, bs, mds, filcns.Weight, nil)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"path"
|
||||
@@ -8,12 +9,18 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/multiformats/go-varint"
|
||||
"github.com/urfave/cli/v2"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
builtintypes "github.com/filecoin-project/go-state-types/builtin"
|
||||
"github.com/filecoin-project/go-state-types/crypto"
|
||||
"github.com/filecoin-project/go-state-types/exitcode"
|
||||
|
||||
lapi "github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
||||
lcli "github.com/filecoin-project/lotus/cli"
|
||||
)
|
||||
@@ -31,6 +38,291 @@ var indexesCmd = &cli.Command{
|
||||
withCategory("msgindex", backfillMsgIndexCmd),
|
||||
withCategory("msgindex", pruneMsgIndexCmd),
|
||||
withCategory("txhash", backfillTxHashCmd),
|
||||
withCategory("events", backfillEventsCmd),
|
||||
},
|
||||
}
|
||||
|
||||
var backfillEventsCmd = &cli.Command{
|
||||
Name: "backfill-events",
|
||||
Usage: "Backfill the events.db for a number of epochs starting from a specified height",
|
||||
Flags: []cli.Flag{
|
||||
&cli.UintFlag{
|
||||
Name: "from",
|
||||
Value: 0,
|
||||
Usage: "the tipset height to start backfilling from (0 is head of chain)",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "epochs",
|
||||
Value: 2000,
|
||||
Usage: "the number of epochs to backfill",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
srv, err := lcli.GetFullNodeServices(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srv.Close() //nolint:errcheck
|
||||
|
||||
api := srv.FullNodeAPI()
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
// currTs will be the tipset where we start backfilling from
|
||||
currTs, err := api.ChainHead(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cctx.IsSet("from") {
|
||||
// we need to fetch the tipset after the epoch being specified since we will need to advance currTs
|
||||
currTs, err = api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(cctx.Int("from")+1), currTs.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// advance currTs by one epoch and maintain prevTs as the previous tipset (this allows us to easily use the ChainGetParentMessages/Receipt API)
|
||||
prevTs := currTs
|
||||
currTs, err = api.ChainGetTipSet(ctx, currTs.Parents())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load tipset %s: %w", prevTs.Parents(), err)
|
||||
}
|
||||
|
||||
epochs := cctx.Int("epochs")
|
||||
|
||||
basePath, err := homedir.Expand(cctx.String("repo"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dbPath := path.Join(basePath, "sqlite", "events.db")
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := db.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: closing db: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
addressLookups := make(map[abi.ActorID]address.Address)
|
||||
|
||||
resolveFn := func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) {
|
||||
// we only want to match using f4 addresses
|
||||
idAddr, err := address.NewIDAddress(uint64(emitter))
|
||||
if err != nil {
|
||||
return address.Undef, false
|
||||
}
|
||||
|
||||
actor, err := api.StateGetActor(ctx, idAddr, ts.Key())
|
||||
if err != nil || actor.Address == nil {
|
||||
return address.Undef, false
|
||||
}
|
||||
|
||||
// if robust address is not f4 then we won't match against it so bail early
|
||||
if actor.Address.Protocol() != address.Delegated {
|
||||
return address.Undef, false
|
||||
}
|
||||
|
||||
// we have an f4 address, make sure it's assigned by the EAM
|
||||
if namespace, _, err := varint.FromUvarint(actor.Address.Payload()); err != nil || namespace != builtintypes.EthereumAddressManagerActorID {
|
||||
return address.Undef, false
|
||||
}
|
||||
return *actor.Address, true
|
||||
}
|
||||
|
||||
isIndexedValue := func(b uint8) bool {
|
||||
// currently we mark the full entry as indexed if either the key
|
||||
// or the value are indexed; in the future we will need finer-grained
|
||||
// management of indices
|
||||
return b&(types.EventFlagIndexedKey|types.EventFlagIndexedValue) > 0
|
||||
}
|
||||
|
||||
var totalEventsAffected int64
|
||||
var totalEntriesAffected int64
|
||||
|
||||
processHeight := func(ctx context.Context, cnt int, msgs []lapi.Message, receipts []*types.MessageReceipt) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback() //nolint:errcheck
|
||||
|
||||
stmtSelectEvent, err := tx.Prepare("SELECT MAX(id) from event WHERE height=? AND tipset_key=? and tipset_key_cid=? and emitter_addr=? and event_index=? and message_cid=? and message_index=? and reverted=false")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stmtEvent, err := tx.Prepare("INSERT INTO event (height, tipset_key, tipset_key_cid, emitter_addr, event_index, message_cid, message_index, reverted) VALUES(?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stmtEntry, err := tx.Prepare("INSERT INTO event_entry(event_id, indexed, flags, key, codec, value) VALUES(?, ?, ?, ?, ?, ?)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var eventsAffected int64
|
||||
var entriesAffected int64
|
||||
|
||||
// loop over each message receipt and backfill the events
|
||||
for idx, receipt := range receipts {
|
||||
msg := msgs[idx]
|
||||
|
||||
if receipt.ExitCode != exitcode.Ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if receipt.EventsRoot == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
events, err := api.ChainGetEvents(ctx, *receipt.EventsRoot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load events for tipset %s: %w", currTs, err)
|
||||
}
|
||||
|
||||
for eventIdx, event := range events {
|
||||
addr, found := addressLookups[event.Emitter]
|
||||
if !found {
|
||||
var ok bool
|
||||
addr, ok = resolveFn(ctx, event.Emitter, currTs)
|
||||
if !ok {
|
||||
// not an address we will be able to match against
|
||||
continue
|
||||
}
|
||||
addressLookups[event.Emitter] = addr
|
||||
}
|
||||
|
||||
tsKeyCid, err := currTs.Key().Cid()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get tipset key cid: %w", err)
|
||||
}
|
||||
|
||||
// select the highest event id that exists in database, or null if none exists
|
||||
var entryID sql.NullInt64
|
||||
err = stmtSelectEvent.QueryRow(
|
||||
currTs.Height(),
|
||||
currTs.Key().Bytes(),
|
||||
tsKeyCid.Bytes(),
|
||||
addr.Bytes(),
|
||||
eventIdx,
|
||||
msg.Cid.Bytes(),
|
||||
idx,
|
||||
).Scan(&entryID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error checking if event exists: %w", err)
|
||||
}
|
||||
|
||||
// we already have this event
|
||||
if entryID.Valid {
|
||||
continue
|
||||
}
|
||||
|
||||
// event does not exist, lets backfill it
|
||||
res, err := tx.Stmt(stmtEvent).Exec(
|
||||
currTs.Height(), // height
|
||||
currTs.Key().Bytes(), // tipset_key
|
||||
tsKeyCid.Bytes(), // tipset_key_cid
|
||||
addr.Bytes(), // emitter_addr
|
||||
eventIdx, // event_index
|
||||
msg.Cid.Bytes(), // message_cid
|
||||
idx, // message_index
|
||||
false, // reverted
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error inserting event: %w", err)
|
||||
}
|
||||
|
||||
entryID.Int64, err = res.LastInsertId()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get last insert id: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get rows affected: %w", err)
|
||||
}
|
||||
eventsAffected += rowsAffected
|
||||
|
||||
// backfill the event entries
|
||||
for _, entry := range event.Entries {
|
||||
_, err := tx.Stmt(stmtEntry).Exec(
|
||||
entryID.Int64, // event_id
|
||||
isIndexedValue(entry.Flags), // indexed
|
||||
[]byte{entry.Flags}, // flags
|
||||
entry.Key, // key
|
||||
entry.Codec, // codec
|
||||
entry.Value, // value
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error inserting entry: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get rows affected: %w", err)
|
||||
}
|
||||
entriesAffected += rowsAffected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("[%d] backfilling actor events epoch:%d, eventsAffected:%d, entriesAffected:%d", cnt, currTs.Height(), eventsAffected, entriesAffected)
|
||||
|
||||
totalEventsAffected += eventsAffected
|
||||
totalEntriesAffected += entriesAffected
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < epochs; i++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
|
||||
blockCid := prevTs.Blocks()[0].Cid()
|
||||
|
||||
// get messages for the parent of the previous tipset (which will be currTs)
|
||||
msgs, err := api.ChainGetParentMessages(ctx, blockCid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get parent messages for block %s: %w", blockCid, err)
|
||||
}
|
||||
|
||||
// get receipts for the parent of the previous tipset (which will be currTs)
|
||||
receipts, err := api.ChainGetParentReceipts(ctx, blockCid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get parent receipts for block %s: %w", blockCid, err)
|
||||
}
|
||||
|
||||
if len(msgs) != len(receipts) {
|
||||
return fmt.Errorf("mismatched in message and receipt count: %d != %d", len(msgs), len(receipts))
|
||||
}
|
||||
|
||||
err = processHeight(ctx, i, msgs, receipts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// advance prevTs and currTs up the chain
|
||||
prevTs = currTs
|
||||
currTs, err = api.ChainGetTipSet(ctx, currTs.Parents())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load tipset %s: %w", currTs, err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("backfilling events complete, totalEventsAffected:%d, totalEntriesAffected:%d", totalEventsAffected, totalEntriesAffected)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ func main() {
|
||||
local := []*cli.Command{
|
||||
addressCmd,
|
||||
statActorCmd,
|
||||
statSnapshotCmd,
|
||||
statObjCmd,
|
||||
base64Cmd,
|
||||
base32Cmd,
|
||||
@@ -90,6 +91,7 @@ func main() {
|
||||
indexesCmd,
|
||||
FevmAnalyticsCmd,
|
||||
mismatchesCmd,
|
||||
blockCmd,
|
||||
}
|
||||
|
||||
app := &cli.App{
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
@@ -43,10 +44,20 @@ var minerSelectMsgsCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the size of the mempool
|
||||
pendingMsgs, err := api.MpoolPending(ctx, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mpoolSize := len(pendingMsgs)
|
||||
|
||||
// Measure the time taken by MpoolSelect
|
||||
startTime := time.Now()
|
||||
msgs, err := api.MpoolSelect(ctx, head.Key(), cctx.Float64("ticket-quality"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
duration := time.Since(startTime)
|
||||
|
||||
var totalGas int64
|
||||
for i, f := range msgs {
|
||||
@@ -64,6 +75,9 @@ var minerSelectMsgsCmd = &cli.Command{
|
||||
totalGas += f.Message.GasLimit
|
||||
}
|
||||
|
||||
// Log the duration, size of the mempool, selected messages and total gas limit of selected messages
|
||||
fmt.Printf("Message selection took %s\n", duration)
|
||||
fmt.Printf("Size of the mempool: %d\n", mpoolSize)
|
||||
fmt.Println("selected messages: ", len(msgs))
|
||||
fmt.Printf("total gas limit of selected messages: %d / %d (%0.2f%%)\n", totalGas, build.BlockGasLimit, 100*float64(totalGas)/float64(build.BlockGasLimit))
|
||||
return nil
|
||||
|
||||
@@ -26,6 +26,12 @@ var msgCmd = &cli.Command{
|
||||
Aliases: []string{"msg"},
|
||||
Usage: "Translate message between various formats",
|
||||
ArgsUsage: "Message in any form",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "exec-trace",
|
||||
Usage: "Print the execution trace",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
if cctx.NArg() != 1 {
|
||||
return lcli.IncorrectNumArgs(cctx)
|
||||
@@ -36,6 +42,48 @@ var msgCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
// Get the CID of the message
|
||||
mcid := msg.Cid()
|
||||
|
||||
// Search for the message on-chain
|
||||
lookup, err := api.StateSearchMsg(ctx, mcid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lookup == nil {
|
||||
fmt.Println("Message not found on-chain. Continuing...")
|
||||
} else {
|
||||
// Replay the message to get the execution trace
|
||||
res, err := api.StateReplay(ctx, types.EmptyTSK, mcid)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("replay call failed: %w", err)
|
||||
}
|
||||
|
||||
if cctx.Bool("exec-trace") {
|
||||
// Print the execution trace
|
||||
color.Green("Execution trace:")
|
||||
trace, err := json.MarshalIndent(res.ExecutionTrace, "", " ")
|
||||
if err != nil {
|
||||
return xerrors.Errorf("marshaling execution trace: %w", err)
|
||||
}
|
||||
fmt.Println(string(trace))
|
||||
fmt.Println()
|
||||
|
||||
color.Green("Receipt:")
|
||||
fmt.Printf("Exit code: %d\n", res.MsgRct.ExitCode)
|
||||
fmt.Printf("Return: %x\n", res.MsgRct.Return)
|
||||
fmt.Printf("Gas Used: %d\n", res.MsgRct.GasUsed)
|
||||
}
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *types.SignedMessage:
|
||||
return printSignedMessage(cctx, msg)
|
||||
|
||||
+503
-79
@@ -1,11 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
@@ -21,8 +24,12 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
gstactors "github.com/filecoin-project/go-state-types/actors"
|
||||
"github.com/filecoin-project/go-state-types/network"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/blockstore"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/actors/builtin"
|
||||
"github.com/filecoin-project/lotus/chain/consensus"
|
||||
"github.com/filecoin-project/lotus/chain/consensus/filcns"
|
||||
@@ -49,6 +56,19 @@ type fieldItem struct {
|
||||
Stats api.ObjStat
|
||||
}
|
||||
|
||||
type job struct {
|
||||
c cid.Cid
|
||||
key string // prefix path for the region being recorded i.e. "/state/mineractor"
|
||||
}
|
||||
type cidCall struct {
|
||||
c cid.Cid
|
||||
resp chan bool
|
||||
}
|
||||
type result struct {
|
||||
key string
|
||||
stats api.ObjStat
|
||||
}
|
||||
|
||||
type cacheNodeGetter struct {
|
||||
ds format.NodeGetter
|
||||
cache *lru.TwoQueueCache[cid.Cid, format.Node]
|
||||
@@ -166,39 +186,13 @@ var statObjCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := repo.NewFS(cctx.String("repo"))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("opening fs repo: %w", err)
|
||||
}
|
||||
|
||||
exists, err := r.Exists()
|
||||
h, err := loadChainStore(ctx, cctx.String("repo"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return xerrors.Errorf("lotus repo doesn't exist")
|
||||
}
|
||||
defer h.closer()
|
||||
|
||||
lr, err := r.Lock(repo.FullNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer lr.Close() //nolint:errcheck
|
||||
|
||||
bs, err := lr.Blockstore(ctx, repo.UniversalBlockstore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open blockstore: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if c, ok := bs.(io.Closer); ok {
|
||||
if err := c.Close(); err != nil {
|
||||
log.Warnf("failed to close blockstore: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
dag := merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs)))
|
||||
dag := merkledag.NewDAGService(blockservice.New(h.bs, offline.Exchange(h.bs)))
|
||||
dsc := &dagStatCollector{
|
||||
ds: dag,
|
||||
walk: carWalkFunc,
|
||||
@@ -212,6 +206,376 @@ var statObjCmd = &cli.Command{
|
||||
},
|
||||
}
|
||||
|
||||
type StoreHandle struct {
|
||||
bs blockstore.Blockstore
|
||||
cs *store.ChainStore
|
||||
sm *stmgr.StateManager
|
||||
closer func()
|
||||
}
|
||||
|
||||
func loadChainStore(ctx context.Context, repoPath string) (*StoreHandle, error) {
|
||||
r, err := repo.NewFS(repoPath)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("opening fs repo: %w", err)
|
||||
}
|
||||
|
||||
exists, err := r.Exists()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, xerrors.Errorf("lotus repo doesn't exist")
|
||||
}
|
||||
|
||||
lr, err := r.Lock(repo.FullNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bs, err := lr.Blockstore(ctx, repo.UniversalBlockstore)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open blockstore: %w", err)
|
||||
}
|
||||
|
||||
closer := func() {
|
||||
if err := lr.Close(); err != nil {
|
||||
log.Warnf("failed to close locked repo: %s", err)
|
||||
}
|
||||
if c, ok := bs.(io.Closer); ok {
|
||||
if err := c.Close(); err != nil {
|
||||
log.Warnf("failed to close blockstore: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mds, err := lr.Datastore(context.Background(), "/metadata")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cs := store.NewChainStore(bs, bs, mds, nil, nil)
|
||||
if err := cs.Load(ctx); err != nil {
|
||||
return nil, fmt.Errorf("failed to load chain store: %w", err)
|
||||
}
|
||||
|
||||
tsExec := consensus.NewTipSetExecutor(filcns.RewardFunc)
|
||||
sm, err := stmgr.NewStateManager(cs, tsExec, vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule(), nil, mds, index.DummyMsgIndex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open state manager: %w", err)
|
||||
}
|
||||
handle := StoreHandle{
|
||||
bs: bs,
|
||||
sm: sm,
|
||||
cs: cs,
|
||||
closer: closer,
|
||||
}
|
||||
|
||||
return &handle, nil
|
||||
}
|
||||
|
||||
func pipeline(ctx context.Context, name string, numWorkers int, createJobs func(ctx context.Context, jobCh chan job, resultCh chan result) error,
|
||||
worker func(ctx context.Context, id int, jobCh chan job, resultCh chan result) error,
|
||||
processResults func(ctx context.Context, resultCh chan result) error) error {
|
||||
|
||||
eg, egctx := errgroup.WithContext(ctx)
|
||||
jobCh := make(chan job, numWorkers)
|
||||
resultCh := make(chan result)
|
||||
var resultWriterWg sync.WaitGroup
|
||||
|
||||
resultWriterWg.Add(1)
|
||||
eg.Go(func() error {
|
||||
defer resultWriterWg.Done()
|
||||
defer func() {
|
||||
close(jobCh)
|
||||
}()
|
||||
return createJobs(ctx, jobCh, resultCh)
|
||||
})
|
||||
|
||||
var id int
|
||||
for w := 0; w < numWorkers; w++ {
|
||||
id = w
|
||||
|
||||
resultWriterWg.Add(1)
|
||||
eg.Go(func() error {
|
||||
defer resultWriterWg.Done()
|
||||
return worker(egctx, id, jobCh, resultCh)
|
||||
})
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
return processResults(ctx, resultCh)
|
||||
})
|
||||
|
||||
// close result channel when workers are done sending to it.
|
||||
eg.Go(func() error {
|
||||
resultWriterWg.Wait()
|
||||
close(resultCh)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
return fmt.Errorf("failed pipeline %s: %w", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var statSnapshotCmd = &cli.Command{
|
||||
Name: "stat-snapshot",
|
||||
Usage: "calculates the space usage of a snapshot taken from the given tipset",
|
||||
Description: `Walk the chain back to lightweight snapshot size and break down space usage into high level
|
||||
categories: headers, messages, receipts, latest state root, and churn from earlier state roots.
|
||||
State root and churn space is further broken down by actor type and immediate top level fields
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "tipset",
|
||||
Usage: "specify tipset to call method on (pass comma separated array of cids)",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "workers",
|
||||
Usage: "number of workers to use when processing",
|
||||
Value: 10,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "dag-cache-size",
|
||||
Usage: "cache size per worker (setting to 0 disables)",
|
||||
Value: 8092,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "pretty",
|
||||
Usage: "print formated output instead of ldjson",
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
h, err := loadChainStore(ctx, cctx.String("repo"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer h.closer()
|
||||
tsr := &ChainStoreTipSetResolver{
|
||||
Chain: h.cs,
|
||||
}
|
||||
|
||||
ts, err := lcli.LoadTipSet(ctx, cctx, tsr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
numWorkers := cctx.Int("workers")
|
||||
dagCacheSize := cctx.Int("dag-cache-size")
|
||||
|
||||
cidCh := make(chan cidCall, numWorkers)
|
||||
summary := make(map[string]api.ObjStat)
|
||||
// snapshot root objects with no additional bytes or links
|
||||
summary["/"] = api.ObjStat{Size: 0, Links: 0}
|
||||
summary["/statetree"] = api.ObjStat{Size: 0, Links: 0}
|
||||
|
||||
combine := func(statsA, statsB api.ObjStat) api.ObjStat {
|
||||
return api.ObjStat{
|
||||
Size: statsA.Size + statsB.Size,
|
||||
Links: statsA.Links + statsB.Links,
|
||||
}
|
||||
}
|
||||
|
||||
// Threadsafe cid set lives across different pipelines so not part of error group
|
||||
go func() {
|
||||
seen := cid.NewSet()
|
||||
for {
|
||||
select {
|
||||
case call := <-cidCh:
|
||||
call.resp <- seen.Visit(call.c)
|
||||
case <-ctx.Done():
|
||||
log.Infof("shutting down cid set goroutine: %s", ctx.Err())
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
visit := func(c cid.Cid) bool {
|
||||
ch := make(chan bool)
|
||||
cidCh <- cidCall{c: c, resp: ch}
|
||||
out := <-ch
|
||||
return out
|
||||
}
|
||||
// Stage 1 walk all actors in latest state root
|
||||
createJobsStage1 := func(ctx context.Context, jobCh chan job, _ chan result) error {
|
||||
st, err := h.sm.StateTree(ts.ParentState())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return st.ForEach(func(_ address.Address, act *types.Actor) error {
|
||||
actType := builtin.ActorNameByCode(act.Code)
|
||||
actType = path.Base(actType) // strip away fil/<nv>
|
||||
if actType == "<unknown>" {
|
||||
actType = act.Code.String()
|
||||
}
|
||||
jobCh <- job{c: act.Head, key: fmt.Sprintf("/statetree/latest/%s", actType)}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
worker := func(ctx context.Context, id int, jobCh chan job, resultCh chan result) error {
|
||||
var dag format.NodeGetter = merkledag.NewDAGService(blockservice.New(h.bs, offline.Exchange(h.bs)))
|
||||
if dagCacheSize != 0 {
|
||||
var err error
|
||||
dag, err = newCacheNodeGetter(merkledag.NewDAGService(blockservice.New(h.bs, offline.Exchange(h.bs))), dagCacheSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for job := range jobCh {
|
||||
stats, err := collectSnapshotJobStats(ctx, job, dag, visit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stat := range stats {
|
||||
select {
|
||||
case resultCh <- stat:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
processResults := func(ctx context.Context, resultCh chan result) error {
|
||||
for result := range resultCh {
|
||||
if stat, ok := summary[result.key]; ok {
|
||||
summary[result.key] = combine(stat, result.stats)
|
||||
|
||||
} else {
|
||||
summary[result.key] = result.stats
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := pipeline(ctx, "Latest State Actors", numWorkers, createJobsStage1, worker, processResults); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Stage 2: walk the top of the latest state root
|
||||
createJobsStage2 := func(ctx context.Context, jobCh chan job, _ chan result) error {
|
||||
jobCh <- job{c: ts.ParentState(), key: "/statetree/latest"}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := pipeline(ctx, "Latest State HAMT", numWorkers, createJobsStage2, worker, processResults); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Stage 3 walk the rest of the chain: headers, messages, churn
|
||||
// ordering:
|
||||
// for each header send jobs for messages, receipts, state tree churn
|
||||
// don't walk header directly as it would just walk everything including parent tipsets
|
||||
|
||||
churnStateRoots := cid.NewSet()
|
||||
createJobsStage3 := func(ctx context.Context, jobCh chan job, resultCh chan result) error {
|
||||
// walk chain
|
||||
var dag format.NodeGetter = merkledag.NewDAGService(blockservice.New(h.bs, offline.Exchange(h.bs)))
|
||||
if dagCacheSize != 0 {
|
||||
var err error
|
||||
dag, err = newCacheNodeGetter(merkledag.NewDAGService(blockservice.New(h.bs, offline.Exchange(h.bs))), dagCacheSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
blocksToWalk := ts.Cids()
|
||||
startHeight := ts.Height()
|
||||
snapshotStateLimit := abi.ChainEpoch(2000)
|
||||
|
||||
churnActorCache := cid.NewSet()
|
||||
blocksTracked := cid.NewSet()
|
||||
for len(blocksToWalk) > 0 {
|
||||
blkCid := blocksToWalk[0]
|
||||
blocksToWalk = blocksToWalk[1:]
|
||||
nd, err := dag.Get(ctx, blkCid)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("getting block: %w", err)
|
||||
}
|
||||
|
||||
var b types.BlockHeader
|
||||
if err := b.UnmarshalCBOR(bytes.NewBuffer(nd.RawData())); err != nil {
|
||||
return xerrors.Errorf("unmarshaling block header (cid=%s): %w", blkCid, err)
|
||||
}
|
||||
|
||||
// header directly to result channel
|
||||
resultCh <- result{key: "/headers", stats: api.ObjStat{Size: uint64(len(nd.RawData())), Links: uint64(len(nd.Links()))}}
|
||||
// message job
|
||||
if b.Height > startHeight-snapshotStateLimit {
|
||||
jobCh <- job{key: "/messages", c: b.Messages}
|
||||
}
|
||||
|
||||
// state churn job
|
||||
if b.Height > startHeight-snapshotStateLimit {
|
||||
if churnStateRoots.Visit(b.ParentStateRoot) {
|
||||
st, err := h.sm.StateTree(b.ParentStateRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = st.ForEach(func(_ address.Address, act *types.Actor) error {
|
||||
if churnActorCache.Visit(act.Head) {
|
||||
actType := builtin.ActorNameByCode(act.Code)
|
||||
actType = path.Base(actType) // strip away fil/<nv>
|
||||
if actType == "<unknown>" {
|
||||
actType = act.Code.String()
|
||||
}
|
||||
jobCh <- job{c: act.Head, key: fmt.Sprintf("/statetree/churn/%s", actType)}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, blkCid := range b.Parents {
|
||||
if blocksTracked.Visit(blkCid) && b.Height != 0 {
|
||||
blocksToWalk = append(blocksToWalk, blkCid)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := pipeline(ctx, "Churn, Headers, Messages", numWorkers, createJobsStage3, worker, processResults); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// step 1 clean things up and get a nice abstraction to reuse
|
||||
// Stage 4 walk all actor HAMTs for churn
|
||||
|
||||
createJobsStage4 := func(ctx context.Context, jobCh chan job, _ chan result) error {
|
||||
return churnStateRoots.ForEach(func(c cid.Cid) error {
|
||||
jobCh <- job{c: c, key: "/statetree/churn"}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := pipeline(ctx, "Churn HAMT", numWorkers, createJobsStage4, worker, processResults); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cctx.Bool("pretty") {
|
||||
DumpSnapshotStats(summary)
|
||||
} else {
|
||||
if err := DumpJSON(summary); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var statActorCmd = &cli.Command{
|
||||
Name: "stat-actor",
|
||||
Usage: "calculates the size of actors and their immeidate structures",
|
||||
@@ -265,57 +629,14 @@ to reduce the number of decode operations performed by caching the decoded objec
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
}
|
||||
|
||||
r, err := repo.NewFS(cctx.String("repo"))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("opening fs repo: %w", err)
|
||||
}
|
||||
|
||||
exists, err := r.Exists()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return xerrors.Errorf("lotus repo doesn't exist")
|
||||
}
|
||||
|
||||
lr, err := r.Lock(repo.FullNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer lr.Close() //nolint:errcheck
|
||||
|
||||
bs, err := lr.Blockstore(ctx, repo.UniversalBlockstore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open blockstore: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if c, ok := bs.(io.Closer); ok {
|
||||
if err := c.Close(); err != nil {
|
||||
log.Warnf("failed to close blockstore: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
mds, err := lr.Datastore(context.Background(), "/metadata")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs := store.NewChainStore(bs, bs, mds, nil, nil)
|
||||
if err := cs.Load(ctx); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
tsExec := consensus.NewTipSetExecutor(filcns.RewardFunc)
|
||||
sm, err := stmgr.NewStateManager(cs, tsExec, vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule(), nil, mds, index.DummyMsgIndex)
|
||||
h, err := loadChainStore(ctx, cctx.String("repo"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer h.closer()
|
||||
|
||||
tsr := &ChainStoreTipSetResolver{
|
||||
Chain: cs,
|
||||
Chain: h.cs,
|
||||
}
|
||||
|
||||
ts, err := lcli.LoadTipSet(ctx, cctx, tsr)
|
||||
@@ -327,7 +648,7 @@ to reduce the number of decode operations performed by caching the decoded objec
|
||||
|
||||
if len(addrs) == 0 && cctx.Bool("all") {
|
||||
var err error
|
||||
addrs, err = sm.ListAllActors(ctx, ts)
|
||||
addrs, err = h.sm.ListAllActors(ctx, ts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -354,15 +675,15 @@ to reduce the number of decode operations performed by caching the decoded objec
|
||||
return nil
|
||||
}
|
||||
|
||||
actor, err := sm.LoadActor(ctx, addr, ts)
|
||||
actor, err := h.sm.LoadActor(ctx, addr, ts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var dag format.NodeGetter = merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs)))
|
||||
var dag format.NodeGetter = merkledag.NewDAGService(blockservice.New(h.bs, offline.Exchange(h.bs)))
|
||||
if dagCacheSize != 0 {
|
||||
var err error
|
||||
dag, err = newCacheNodeGetter(merkledag.NewDAGService(blockservice.New(bs, offline.Exchange(bs))), dagCacheSize)
|
||||
dag, err = newCacheNodeGetter(merkledag.NewDAGService(blockservice.New(h.bs, offline.Exchange(h.bs))), dagCacheSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -427,6 +748,93 @@ to reduce the number of decode operations performed by caching the decoded objec
|
||||
},
|
||||
}
|
||||
|
||||
func collectSnapshotJobStats(ctx context.Context, in job, dag format.NodeGetter, visit func(c cid.Cid) bool) ([]result, error) {
|
||||
// "state" and "churn" attempt further breakdown by actor type
|
||||
if !(path.Dir(in.key) == "/statetree/latest") && !(path.Dir(in.key) == "/statetree/churn") {
|
||||
dsc := &dagStatCollector{
|
||||
ds: dag,
|
||||
walk: carWalkFunc,
|
||||
}
|
||||
|
||||
if err := merkledag.Walk(ctx, dsc.walkLinks, in.c, visit, merkledag.Concurrent()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []result{{key: in.key, stats: dsc.stats}}, nil
|
||||
}
|
||||
|
||||
// in.c is an actor head cid, try to unmarshal and create sub keys for different regions of state
|
||||
nd, err := dag.Get(ctx, in.c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subjobs := make([]job, 0)
|
||||
results := make([]result, 0)
|
||||
|
||||
// reconstruct actor for state parsing from key
|
||||
av, err := gstactors.VersionForNetwork(network.Version20)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get actors version for network: %w", err)
|
||||
}
|
||||
code, ok := actors.GetActorCodeID(av, path.Base(in.key))
|
||||
if !ok { // try parsing key directly
|
||||
code, err = cid.Parse(path.Base(in.key))
|
||||
if err != nil {
|
||||
log.Debugf("failing to parse actor string: %s", path.Base(in.key))
|
||||
}
|
||||
}
|
||||
|
||||
actor := types.ActorV5{Head: in.c, Code: code}
|
||||
oif, err := vm.DumpActorState(consensus.NewTipSetExecutor(filcns.RewardFunc).NewActorRegistry(), &actor, nd.RawData())
|
||||
if err != nil {
|
||||
oif = nil
|
||||
}
|
||||
// Account actors return nil from DumpActorState as they have no state
|
||||
if oif != nil {
|
||||
v := reflect.Indirect(reflect.ValueOf(oif))
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
varName := v.Type().Field(i).Name
|
||||
varType := v.Type().Field(i).Type
|
||||
varValue := v.Field(i).Interface()
|
||||
|
||||
if varType == reflect.TypeOf(cid.Cid{}) {
|
||||
subjobs = append(subjobs, job{
|
||||
key: fmt.Sprintf("%s/%s", in.key, varName),
|
||||
c: varValue.(cid.Cid),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Walk subfields
|
||||
for _, job := range subjobs {
|
||||
dsc := &dagStatCollector{
|
||||
ds: dag,
|
||||
walk: carWalkFunc,
|
||||
}
|
||||
|
||||
if err := merkledag.Walk(ctx, dsc.walkLinks, job.c, visit, merkledag.Concurrent()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res result
|
||||
res.key = job.key
|
||||
res.stats = dsc.stats
|
||||
|
||||
results = append(results, res)
|
||||
}
|
||||
|
||||
// now walk the top level object of actor state
|
||||
dsc := &dagStatCollector{
|
||||
ds: dag,
|
||||
walk: carWalkFunc,
|
||||
}
|
||||
|
||||
if err := merkledag.Walk(ctx, dsc.walkLinks, in.c, visit, merkledag.Concurrent()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, result{key: in.key, stats: dsc.stats})
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func collectStats(ctx context.Context, addr address.Address, actor *types.Actor, dag format.NodeGetter) (actorStats, error) {
|
||||
log.Infow("actor", "addr", addr, "code", actor.Code, "name", builtin.ActorNameByCode(actor.Code))
|
||||
|
||||
@@ -532,3 +940,19 @@ func DumpStats(actStats actorStats) {
|
||||
|
||||
fmt.Println("--------------------------------------------------------------------------")
|
||||
}
|
||||
|
||||
func DumpSnapshotStats(stats map[string]api.ObjStat) {
|
||||
// sort keys so we get subkey locality
|
||||
keys := make([]string, 0, len(stats))
|
||||
for k := range stats {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
fmt.Printf("%-*s%-*s%-*s\n", 32, "Path", 24, "Size", 24, "\"Blocks\"")
|
||||
for _, k := range keys {
|
||||
stat := stats[k]
|
||||
sizeStr := units.BytesSize(float64(stat.Size))
|
||||
fmt.Printf("%-*s%-*s%-*s%-*d\n", 32, k, 10, sizeStr, 14, fmt.Sprintf("(%d)", stat.Size), 24, stat.Links)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ var staterootStatCmd = &cli.Command{
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s\t%s\t%d\n", inf.Addr, string(cmh.Digest), inf.Stat.Size)
|
||||
fmt.Printf("%s\t%x\t%d\n", inf.Addr, cmh.Digest, inf.Stat.Size)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
+79
-14
@@ -8,12 +8,14 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/ipfs/go-datastore/namespace"
|
||||
logging "github.com/ipfs/go-log/v2"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
manet "github.com/multiformats/go-multiaddr/net"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.opencensus.io/stats/view"
|
||||
@@ -320,6 +322,29 @@ var runCmd = &cli.Command{
|
||||
}
|
||||
}
|
||||
|
||||
// Check DC-environment variable
|
||||
sectorSizes := []string{"2KiB", "8MiB", "512MiB", "32GiB", "64GiB"}
|
||||
resourcesType := reflect.TypeOf(storiface.Resources{})
|
||||
|
||||
for _, sectorSize := range sectorSizes {
|
||||
for i := 0; i < resourcesType.NumField(); i++ {
|
||||
field := resourcesType.Field(i)
|
||||
envName := field.Tag.Get("envname")
|
||||
if envName != "" {
|
||||
// Check if DC_[SectorSize]_[ResourceRestriction] is set
|
||||
envVar, ok := os.LookupEnv("DC_" + sectorSize + "_" + envName)
|
||||
if ok {
|
||||
// If it is set, convert it to DC_[ResourceRestriction]
|
||||
err := os.Setenv("DC_"+envName, envVar)
|
||||
if err != nil {
|
||||
log.Fatalf("Error setting environment variable: %v", err)
|
||||
}
|
||||
log.Warnf("Converted DC_%s_%s to DC_%s, because DC is a sector-size independent job", sectorSize, envName, envName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to storage-miner
|
||||
ctx := lcli.ReqContext(cctx)
|
||||
|
||||
@@ -530,9 +555,14 @@ var runCmd = &cli.Command{
|
||||
|
||||
log.Info("Opening local storage; connecting to master")
|
||||
const unspecifiedAddress = "0.0.0.0"
|
||||
|
||||
address := cctx.String("listen")
|
||||
addressSlice := strings.Split(address, ":")
|
||||
if ip := net.ParseIP(addressSlice[0]); ip != nil {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if ip.String() == unspecifiedAddress {
|
||||
timeout, err := time.ParseDuration(cctx.String("timeout"))
|
||||
if err != nil {
|
||||
@@ -542,11 +572,21 @@ var runCmd = &cli.Command{
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
address = rip + ":" + addressSlice[1]
|
||||
host = rip
|
||||
}
|
||||
}
|
||||
|
||||
localStore, err := paths.NewLocal(ctx, lr, nodeApi, []string{"http://" + address + "/remote"})
|
||||
var newAddress string
|
||||
|
||||
// Check if the IP address is IPv6
|
||||
ip := net.ParseIP(host)
|
||||
if ip.To4() == nil && ip.To16() != nil {
|
||||
newAddress = "[" + host + "]:" + port
|
||||
} else {
|
||||
newAddress = host + ":" + port
|
||||
}
|
||||
|
||||
localStore, err := paths.NewLocal(ctx, lr, nodeApi, []string{"http://" + newAddress + "/remote"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -587,7 +627,7 @@ var runCmd = &cli.Command{
|
||||
Storage: lr,
|
||||
}
|
||||
|
||||
log.Info("Setting up control endpoint at " + address)
|
||||
log.Info("Setting up control endpoint at " + newAddress)
|
||||
|
||||
timeout, err := time.ParseDuration(cctx.String("http-server-timeout"))
|
||||
if err != nil {
|
||||
@@ -612,13 +652,13 @@ var runCmd = &cli.Command{
|
||||
log.Warn("Graceful shutdown successful")
|
||||
}()
|
||||
|
||||
nl, err := net.Listen("tcp", address)
|
||||
nl, err := net.Listen("tcp", newAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
{
|
||||
a, err := net.ResolveTCPAddr("tcp", address)
|
||||
a, err := net.ResolveTCPAddr("tcp", newAddress)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parsing address: %w", err)
|
||||
}
|
||||
@@ -699,7 +739,7 @@ var runCmd = &cli.Command{
|
||||
|
||||
select {
|
||||
case <-readyCh:
|
||||
if err := nodeApi.WorkerConnect(ctx, "http://"+address+"/rpc/v0"); err != nil {
|
||||
if err := nodeApi.WorkerConnect(ctx, "http://"+newAddress+"/rpc/v0"); err != nil {
|
||||
log.Errorf("Registering worker failed: %+v", err)
|
||||
cancel()
|
||||
return
|
||||
@@ -740,21 +780,46 @@ func extractRoutableIP(timeout time.Duration) (string, error) {
|
||||
deprecatedMinerMultiAddrKey := "STORAGE_API_INFO"
|
||||
env, ok := os.LookupEnv(minerMultiAddrKey)
|
||||
if !ok {
|
||||
// TODO remove after deprecation period
|
||||
_, ok = os.LookupEnv(deprecatedMinerMultiAddrKey)
|
||||
if ok {
|
||||
log.Warnf("Using a deprecated env(%s) value, please use env(%s) instead.", deprecatedMinerMultiAddrKey, minerMultiAddrKey)
|
||||
}
|
||||
return "", xerrors.New("MINER_API_INFO environment variable required to extract IP")
|
||||
}
|
||||
minerAddr := strings.Split(env, "/")
|
||||
conn, err := net.DialTimeout("tcp", minerAddr[2]+":"+minerAddr[4], timeout)
|
||||
|
||||
// Splitting the env to separate the JWT from the multiaddress
|
||||
splitEnv := strings.SplitN(env, ":", 2)
|
||||
if len(splitEnv) < 2 {
|
||||
return "", xerrors.Errorf("invalid MINER_API_INFO format")
|
||||
}
|
||||
// Only take the multiaddress part
|
||||
maddrStr := splitEnv[1]
|
||||
|
||||
maddr, err := multiaddr.NewMultiaddr(maddrStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer conn.Close() //nolint:errcheck
|
||||
|
||||
minerIP, _ := maddr.ValueForProtocol(multiaddr.P_IP6)
|
||||
if minerIP == "" {
|
||||
minerIP, _ = maddr.ValueForProtocol(multiaddr.P_IP4)
|
||||
}
|
||||
minerPort, _ := maddr.ValueForProtocol(multiaddr.P_TCP)
|
||||
|
||||
// Format the address appropriately
|
||||
addressToDial := net.JoinHostPort(minerIP, minerPort)
|
||||
|
||||
conn, err := net.DialTimeout("tcp", addressToDial, timeout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if cerr := conn.Close(); cerr != nil {
|
||||
log.Errorf("Error closing connection: %v", cerr)
|
||||
}
|
||||
}()
|
||||
|
||||
localAddr := conn.LocalAddr().(*net.TCPAddr)
|
||||
|
||||
return strings.Split(localAddr.IP.String(), ":")[0], nil
|
||||
return localAddr.IP.String(), nil
|
||||
}
|
||||
|
||||
+117
-7
@@ -12,6 +12,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
|
||||
@@ -32,6 +33,7 @@ import (
|
||||
|
||||
lapi "github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/beacon/drand"
|
||||
"github.com/filecoin-project/lotus/chain/consensus"
|
||||
"github.com/filecoin-project/lotus/chain/consensus/filcns"
|
||||
"github.com/filecoin-project/lotus/chain/index"
|
||||
@@ -119,6 +121,10 @@ var DaemonCmd = &cli.Command{
|
||||
Name: "import-snapshot",
|
||||
Usage: "import chain state from a given chain export file or url",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "remove-existing-chain",
|
||||
Usage: "remove existing chain and splitstore data on a snapshot-import",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "halt-after-import",
|
||||
Usage: "halt the process after importing chain from file",
|
||||
@@ -265,10 +271,55 @@ var DaemonCmd = &cli.Command{
|
||||
|
||||
chainfile := cctx.String("import-chain")
|
||||
snapshot := cctx.String("import-snapshot")
|
||||
willImportChain := false
|
||||
if chainfile != "" || snapshot != "" {
|
||||
if chainfile != "" && snapshot != "" {
|
||||
return fmt.Errorf("cannot specify both 'import-snapshot' and 'import-chain'")
|
||||
}
|
||||
willImportChain = true
|
||||
}
|
||||
|
||||
willRemoveChain := cctx.Bool("remove-existing-chain")
|
||||
if willImportChain && !willRemoveChain {
|
||||
// Confirm with the user about the intention to remove chain data.
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("Importing chain or snapshot will by default delete existing local chain data. Do you want to proceed and delete? (yes/no): ")
|
||||
userInput, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return xerrors.Errorf("reading user input: %w", err)
|
||||
}
|
||||
userInput = strings.ToLower(strings.TrimSpace(userInput))
|
||||
|
||||
if userInput == "yes" {
|
||||
willRemoveChain = true
|
||||
} else if userInput == "no" {
|
||||
willRemoveChain = false
|
||||
} else {
|
||||
return fmt.Errorf("invalid input. please answer with 'yes' or 'no'")
|
||||
}
|
||||
}
|
||||
|
||||
if willRemoveChain {
|
||||
lr, err := repo.NewFS(cctx.String("repo"))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error opening fs repo: %w", err)
|
||||
}
|
||||
|
||||
exists, err := lr.Exists()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return xerrors.Errorf("lotus repo doesn't exist")
|
||||
}
|
||||
|
||||
err = removeExistingChain(cctx, lr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if willImportChain {
|
||||
var issnapshot bool
|
||||
if chainfile == "" {
|
||||
chainfile = snapshot
|
||||
@@ -380,7 +431,6 @@ var DaemonCmd = &cli.Command{
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start json-rpc endpoint: %s", err)
|
||||
}
|
||||
|
||||
// Monitor for shutdown.
|
||||
finishCh := node.MonitorShutdown(shutdownChan,
|
||||
node.ShutdownHandler{Component: "rpc server", StopFunc: rpcStopper},
|
||||
@@ -536,13 +586,17 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool)
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: We need to supply the actual beacon after v14
|
||||
stm, err := stmgr.NewStateManager(cst, consensus.NewTipSetExecutor(filcns.RewardFunc), vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule(), nil, mds, index.DummyMsgIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !snapshot {
|
||||
shd, err := drand.BeaconScheduleFromDrandSchedule(build.DrandConfigSchedule(), gb.MinTimestamp(), nil)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to construct beacon schedule: %w", err)
|
||||
}
|
||||
|
||||
stm, err := stmgr.NewStateManager(cst, consensus.NewTipSetExecutor(filcns.RewardFunc), vm.Syscalls(ffiwrapper.ProofVerifier), filcns.DefaultUpgradeSchedule(), shd, mds, index.DummyMsgIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("validating imported chain...")
|
||||
if err := stm.ValidateChain(ctx, ts); err != nil {
|
||||
return xerrors.Errorf("chain validation failed: %w", err)
|
||||
@@ -574,3 +628,59 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeExistingChain(cctx *cli.Context, lr repo.Repo) error {
|
||||
lockedRepo, err := lr.Lock(repo.FullNode)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error locking repo: %w", err)
|
||||
}
|
||||
// Ensure that lockedRepo is closed when this function exits
|
||||
defer func() {
|
||||
if closeErr := lockedRepo.Close(); closeErr != nil {
|
||||
log.Errorf("Error closing the lockedRepo: %v", closeErr)
|
||||
}
|
||||
}()
|
||||
|
||||
cfg, err := lockedRepo.Config()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error getting config: %w", err)
|
||||
}
|
||||
|
||||
fullNodeConfig, ok := cfg.(*config.FullNode)
|
||||
if !ok {
|
||||
return xerrors.Errorf("wrong config type: %T", cfg)
|
||||
}
|
||||
|
||||
if fullNodeConfig.Chainstore.EnableSplitstore {
|
||||
log.Info("removing splitstore directory...")
|
||||
err = deleteSplitstoreDir(lockedRepo)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error removing splitstore directory: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the base repo path
|
||||
repoPath := lockedRepo.Path()
|
||||
|
||||
// Construct the path to the chain directory
|
||||
chainPath := filepath.Join(repoPath, "datastore", "chain")
|
||||
|
||||
log.Info("removing chain directory:", chainPath)
|
||||
|
||||
err = os.RemoveAll(chainPath)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error removing chain directory: %w", err)
|
||||
}
|
||||
|
||||
log.Info("chain and splitstore data have been removed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteSplitstoreDir(lr repo.LockedRepo) error {
|
||||
path, err := lr.SplitstorePath()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error getting splitstore path: %w", err)
|
||||
}
|
||||
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
@@ -2289,7 +2289,8 @@ Inputs:
|
||||
},
|
||||
{
|
||||
"MaxFee": "0",
|
||||
"MsgUuid": "07070707-0707-0707-0707-070707070707"
|
||||
"MsgUuid": "07070707-0707-0707-0707-070707070707",
|
||||
"MaximizeFeeCap": true
|
||||
},
|
||||
[
|
||||
{
|
||||
@@ -2766,7 +2767,8 @@ Inputs:
|
||||
],
|
||||
{
|
||||
"MaxFee": "0",
|
||||
"MsgUuid": "07070707-0707-0707-0707-070707070707"
|
||||
"MsgUuid": "07070707-0707-0707-0707-070707070707",
|
||||
"MaximizeFeeCap": true
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -3025,7 +3027,8 @@ Inputs:
|
||||
},
|
||||
{
|
||||
"MaxFee": "0",
|
||||
"MsgUuid": "07070707-0707-0707-0707-070707070707"
|
||||
"MsgUuid": "07070707-0707-0707-0707-070707070707",
|
||||
"MaximizeFeeCap": true
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -5467,7 +5470,8 @@ Response:
|
||||
"UpgradeLightningHeight": 10101,
|
||||
"UpgradeThunderHeight": 10101,
|
||||
"UpgradeWatermelonHeight": 10101
|
||||
}
|
||||
},
|
||||
"Eip155ChainID": 123
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -104,6 +104,8 @@
|
||||
* [EthSendRawTransaction](#EthSendRawTransaction)
|
||||
* [EthSubscribe](#EthSubscribe)
|
||||
* [EthSyncing](#EthSyncing)
|
||||
* [EthTraceBlock](#EthTraceBlock)
|
||||
* [EthTraceReplayBlockTransactions](#EthTraceReplayBlockTransactions)
|
||||
* [EthUninstallFilter](#EthUninstallFilter)
|
||||
* [EthUnsubscribe](#EthUnsubscribe)
|
||||
* [Filecoin](#Filecoin)
|
||||
@@ -3083,6 +3085,99 @@ Inputs: `null`
|
||||
|
||||
Response: `false`
|
||||
|
||||
### EthTraceBlock
|
||||
TraceAPI related methods
|
||||
|
||||
Returns traces created at given block
|
||||
|
||||
|
||||
Perms: read
|
||||
|
||||
Inputs:
|
||||
```json
|
||||
[
|
||||
"string value"
|
||||
]
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"action": {
|
||||
"callType": "string value",
|
||||
"from": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031",
|
||||
"to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031",
|
||||
"gas": "0x5",
|
||||
"input": "0x07",
|
||||
"value": "0x0"
|
||||
},
|
||||
"result": {
|
||||
"gasUsed": "0x5",
|
||||
"output": "0x07"
|
||||
},
|
||||
"subtraces": 123,
|
||||
"traceAddress": [
|
||||
123
|
||||
],
|
||||
"Type": "string value",
|
||||
"blockHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e",
|
||||
"blockNumber": 9,
|
||||
"transactionHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e",
|
||||
"transactionPosition": 123
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### EthTraceReplayBlockTransactions
|
||||
Replays all transactions in a block returning the requested traces for each transaction
|
||||
|
||||
|
||||
Perms: read
|
||||
|
||||
Inputs:
|
||||
```json
|
||||
[
|
||||
"string value",
|
||||
[
|
||||
"string value"
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"output": "0x07",
|
||||
"stateDiff": "string value",
|
||||
"trace": [
|
||||
{
|
||||
"action": {
|
||||
"callType": "string value",
|
||||
"from": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031",
|
||||
"to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031",
|
||||
"gas": "0x5",
|
||||
"input": "0x07",
|
||||
"value": "0x0"
|
||||
},
|
||||
"result": {
|
||||
"gasUsed": "0x5",
|
||||
"output": "0x07"
|
||||
},
|
||||
"subtraces": 123,
|
||||
"traceAddress": [
|
||||
123
|
||||
],
|
||||
"Type": "string value"
|
||||
}
|
||||
],
|
||||
"transactionHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e",
|
||||
"vmTrace": "string value"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### EthUninstallFilter
|
||||
Uninstalls a filter with given id.
|
||||
|
||||
@@ -3262,7 +3357,8 @@ Inputs:
|
||||
},
|
||||
{
|
||||
"MaxFee": "0",
|
||||
"MsgUuid": "07070707-0707-0707-0707-070707070707"
|
||||
"MsgUuid": "07070707-0707-0707-0707-070707070707",
|
||||
"MaximizeFeeCap": true
|
||||
},
|
||||
[
|
||||
{
|
||||
@@ -3739,7 +3835,8 @@ Inputs:
|
||||
],
|
||||
{
|
||||
"MaxFee": "0",
|
||||
"MsgUuid": "07070707-0707-0707-0707-070707070707"
|
||||
"MsgUuid": "07070707-0707-0707-0707-070707070707",
|
||||
"MaximizeFeeCap": true
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -4131,7 +4228,8 @@ Inputs:
|
||||
},
|
||||
{
|
||||
"MaxFee": "0",
|
||||
"MsgUuid": "07070707-0707-0707-0707-070707070707"
|
||||
"MsgUuid": "07070707-0707-0707-0707-070707070707",
|
||||
"MaximizeFeeCap": true
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -6981,7 +7079,8 @@ Response:
|
||||
"UpgradeLightningHeight": 10101,
|
||||
"UpgradeThunderHeight": 10101,
|
||||
"UpgradeWatermelonHeight": 10101
|
||||
}
|
||||
},
|
||||
"Eip155ChainID": 123
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ USAGE:
|
||||
lotus-miner [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
1.23.3
|
||||
1.23.5-dev
|
||||
|
||||
COMMANDS:
|
||||
init Initialize a lotus miner repo
|
||||
|
||||
@@ -7,7 +7,7 @@ USAGE:
|
||||
lotus-worker [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
1.23.3
|
||||
1.23.5-dev
|
||||
|
||||
COMMANDS:
|
||||
run Start lotus worker
|
||||
|
||||
@@ -7,7 +7,7 @@ USAGE:
|
||||
lotus [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
1.23.3
|
||||
1.23.5-dev
|
||||
|
||||
COMMANDS:
|
||||
daemon Start a lotus daemon process
|
||||
@@ -65,6 +65,7 @@ OPTIONS:
|
||||
--bootstrap (default: true)
|
||||
--import-chain value on first run, load chain from given file or url and validate
|
||||
--import-snapshot value import chain state from a given chain export file or url
|
||||
--remove-existing-chain remove existing chain and splitstore data on a snapshot-import (default: false)
|
||||
--halt-after-import halt the process after importing chain from file (default: false)
|
||||
--lite start lotus in lite mode (default: false)
|
||||
--pprof value specify name of file for writing cpu profile to
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
|
||||
[Chainstore.Splitstore]
|
||||
# ColdStoreType specifies the type of the coldstore.
|
||||
# It can be "messages" (default) to store only messages, "universal" to store all chain state or "discard" for discarding cold blocks.
|
||||
# It can be "discard" (default) for discarding cold blocks, "messages" to store only messages or "universal" to store all chain state..
|
||||
#
|
||||
# type: string
|
||||
# env var: LOTUS_CHAINSTORE_SPLITSTORE_COLDSTORETYPE
|
||||
@@ -380,6 +380,21 @@
|
||||
# env var: LOTUS_FEVM_EVENTS_MAXFILTERHEIGHTRANGE
|
||||
#MaxFilterHeightRange = 2880
|
||||
|
||||
# FilterThresholdSet signals whether a FilterThresholdEpoch is set. If it is it will be used instead of
|
||||
# MaxFilterHeightRange to define the supported range.
|
||||
#
|
||||
# type: bool
|
||||
# env var: LOTUS_FEVM_EVENTS_FILTERTHRESHOLDSET
|
||||
#FilterThresholdSet = false
|
||||
|
||||
# FilterThresholdEpoch specifies an epoch above which the node will support returning filtered logs.
|
||||
# This is useful for setting a static start epoch for support, rather than a static window size with a start epoch
|
||||
# that shifts as the chain progresses.
|
||||
#
|
||||
# type: uint64
|
||||
# env var: LOTUS_FEVM_EVENTS_FILTERTHRESHOLDEPOCH
|
||||
#FilterThresholdEpoch = 2683348
|
||||
|
||||
# DatabasePath is the full path to a sqlite database that will be used to index actor events to
|
||||
# support the historic filter APIs. If the database does not exist it will be created. The directory containing
|
||||
# the database must already exist and be writeable. If a relative path is provided here, sqlite treats it as
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user