diff --git a/.circleci/config.yml b/.circleci/config.yml index d76be4bdc..9f8f28b86 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -112,7 +112,7 @@ jobs: - run: command: make debug - test: &test + test: description: | Run tests with gotestsum. parameters: &test-params @@ -123,26 +123,20 @@ jobs: type: string default: "-timeout 30m" description: Flags passed to go test. - packages: + target: type: string default: "./..." description: Import paths of packages to be tested. - winpost-test: - type: string - default: "0" - deadline-test: - type: string - default: "0" proofs-log-test: type: string default: "0" - test-suite-name: + suite: type: string default: unit description: Test suite name to report to CircleCI. gotestsum-format: type: string - default: pkgname-and-test-fails + default: standard-verbose description: gotestsum format. https://github.com/gotestyourself/gotestsum#format coverage: type: string @@ -150,7 +144,7 @@ jobs: description: Coverage flag. Set to the empty string to disable. codecov-upload: type: boolean - default: false + default: true description: | Upload coverage report to https://codecov.io/. Requires the codecov API token to be set as an environment variable for private projects. @@ -168,26 +162,24 @@ jobs: - run: name: go test environment: - LOTUS_TEST_WINDOW_POST: << parameters.winpost-test >> - LOTUS_TEST_DEADLINE_TOGGLING: << parameters.deadline-test >> TEST_RUSTPROOFS_LOGS: << parameters.proofs-log-test >> SKIP_CONFORMANCE: "1" command: | - mkdir -p /tmp/test-reports/<< parameters.test-suite-name >> + mkdir -p /tmp/test-reports/<< parameters.suite >> mkdir -p /tmp/test-artifacts gotestsum \ --format << parameters.gotestsum-format >> \ - --junitfile /tmp/test-reports/<< parameters.test-suite-name >>/junit.xml \ - --jsonfile /tmp/test-artifacts/<< parameters.test-suite-name >>.json \ + --junitfile /tmp/test-reports/<< parameters.suite >>/junit.xml \ + --jsonfile /tmp/test-artifacts/<< parameters.suite >>.json \ -- \ << parameters.coverage >> \ << parameters.go-test-flags >> \ - << parameters.packages >> + << parameters.target >> no_output_timeout: 30m - store_test_results: path: /tmp/test-reports - store_artifacts: - path: /tmp/test-artifacts/<< parameters.test-suite-name >>.json + path: /tmp/test-artifacts/<< parameters.suite >>.json - when: condition: << parameters.codecov-upload >> steps: @@ -198,26 +190,6 @@ jobs: command: | bash <(curl -s https://codecov.io/bash) - test-chain: - <<: *test - test-node: - <<: *test - test-storage: - <<: *test - test-cli: - <<: *test - test-short: - <<: *test - test-window-post: - <<: *test - test-window-post-dispute: - <<: *test - test-deadline-toggling: - <<: *test - test-terminate: - <<: *test - check-proofs-multicore-sdr: - <<: *test test-conformance: description: | Run tests using a corpus of interoperable test vectors for Filecoin @@ -462,7 +434,7 @@ jobs: name: prepare workspace command: | mkdir appimage - mv Lotus-latest-x86_64.AppImage appimage + mv Lotus-*.AppImage appimage - persist_to_workspace: root: "." paths: @@ -598,7 +570,6 @@ jobs: name: publish snap command: snapcraft push *.snap --release << parameters.channel >> - build-and-push-image: description: build and push docker images to public AWS ECR registry executor: aws-cli/default @@ -776,64 +747,145 @@ workflows: - gen-check - docs-check - test: - codecov-upload: true - test-suite-name: full - - test-chain: - codecov-upload: true - test-suite-name: chain - packages: "./chain/..." - - test-node: - codecov-upload: true - test-suite-name: node - packages: "./node/..." - - test-storage: - codecov-upload: true - test-suite-name: storage - packages: "./storage/... ./extern/..." - - test-cli: - codecov-upload: true - test-suite-name: cli - packages: "./cli/... ./cmd/... ./api/..." - - test-window-post: - codecov-upload: true - go-test-flags: "-run=TestWindowedPost" - winpost-test: "1" - test-suite-name: window-post - - test-window-post-dispute: - codecov-upload: true - go-test-flags: "-run=TestWindowPostDispute" - winpost-test: "1" - test-suite-name: window-post-dispute - - test-terminate: - codecov-upload: true - go-test-flags: "-run=TestTerminate" - winpost-test: "1" - test-suite-name: terminate - - test-deadline-toggling: - codecov-upload: true - go-test-flags: "-run=TestDeadlineToggling" - deadline-test: "1" - test-suite-name: deadline-toggling - - test-short: - go-test-flags: "--timeout 10m --short" - test-suite-name: short - filters: - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - check-proofs-multicore-sdr: - codecov-upload: true + name: test-itest-api + suite: itest-api + target: "./itests/api_test.go" + + - test: + name: test-itest-batch_deal + suite: itest-batch_deal + target: "./itests/batch_deal_test.go" + + - test: + name: test-itest-ccupgrade + suite: itest-ccupgrade + target: "./itests/ccupgrade_test.go" + + - test: + name: test-itest-cli + suite: itest-cli + target: "./itests/cli_test.go" + + - test: + name: test-itest-deadlines + suite: itest-deadlines + target: "./itests/deadlines_test.go" + + - test: + name: test-itest-deals_concurrent + suite: itest-deals_concurrent + target: "./itests/deals_concurrent_test.go" + + - test: + name: test-itest-deals_offline + suite: itest-deals_offline + target: "./itests/deals_offline_test.go" + + - test: + name: test-itest-deals_power + suite: itest-deals_power + target: "./itests/deals_power_test.go" + + - test: + name: test-itest-deals_pricing + suite: itest-deals_pricing + target: "./itests/deals_pricing_test.go" + + - test: + name: test-itest-deals_publish + suite: itest-deals_publish + target: "./itests/deals_publish_test.go" + + - test: + name: test-itest-deals + suite: itest-deals + target: "./itests/deals_test.go" + + - test: + name: test-itest-gateway + suite: itest-gateway + target: "./itests/gateway_test.go" + + - test: + name: test-itest-multisig + suite: itest-multisig + target: "./itests/multisig_test.go" + + - test: + name: test-itest-paych_api + suite: itest-paych_api + target: "./itests/paych_api_test.go" + + - test: + name: test-itest-paych_cli + suite: itest-paych_cli + target: "./itests/paych_cli_test.go" + + - test: + name: test-itest-sdr_upgrade + suite: itest-sdr_upgrade + target: "./itests/sdr_upgrade_test.go" + + - test: + name: test-itest-sector_pledge + suite: itest-sector_pledge + target: "./itests/sector_pledge_test.go" + + - test: + name: test-itest-sector_terminate + suite: itest-sector_terminate + target: "./itests/sector_terminate_test.go" + + - test: + name: test-itest-tape + suite: itest-tape + target: "./itests/tape_test.go" + + - test: + name: test-itest-verifreg + suite: itest-verifreg + target: "./itests/verifreg_test.go" + + - test: + name: test-itest-wdpost_dispute + suite: itest-wdpost_dispute + target: "./itests/wdpost_dispute_test.go" + + - test: + name: test-itest-wdpost + suite: itest-wdpost + target: "./itests/wdpost_test.go" + + - test: + name: test-unit-cli + suite: utest-unit-cli + target: "./cli/... ./cmd/... ./api/..." + - test: + name: test-unit-node + suite: utest-unit-node + target: "./node/..." + - test: + name: test-unit-rest + suite: utest-unit-rest + target: "./api/... ./blockstore/... ./build/... ./chain/... ./cli/... ./cmd/... ./conformance/... ./extern/... ./gateway/... ./journal/... ./lib/... ./markets/... ./node/... ./paychmgr/... ./storage/... ./tools/..." + - test: + name: test-unit-storage + suite: utest-unit-storage + target: "./storage/... ./extern/..." + - test: go-test-flags: "-run=TestMulticoreSDR" - test-suite-name: multicore-sdr-check - packages: "./extern/sector-storage/ffiwrapper" + suite: multicore-sdr-check + target: "./extern/sector-storage/ffiwrapper" proofs-log-test: "1" - test-conformance: - test-suite-name: conformance - packages: "./conformance" + suite: conformance + codecov-upload: false + target: "./conformance" - test-conformance: name: test-conformance-bleeding-edge - test-suite-name: conformance-bleeding-edge - packages: "./conformance" + codecov-upload: false + suite: conformance-bleeding-edge + target: "./conformance" vectors-branch: master - trigger-testplans: filters: @@ -842,37 +894,27 @@ workflows: - master - build-debug - build-all: - requires: - - test-short filters: tags: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - build-ntwk-calibration: - requires: - - test-short filters: tags: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - build-ntwk-butterfly: - requires: - - test-short filters: tags: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - build-ntwk-nerpa: - requires: - - test-short filters: tags: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - build-lotus-soup - build-macos: - requires: - - test-short filters: branches: ignore: @@ -881,8 +923,6 @@ workflows: only: - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - build-appimage: - requires: - - test-short filters: branches: ignore: diff --git a/.circleci/gen.go b/.circleci/gen.go new file mode 100644 index 000000000..844348e29 --- /dev/null +++ b/.circleci/gen.go @@ -0,0 +1,136 @@ +package main + +import ( + "embed" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "text/template" +) + +//go:generate go run ./gen.go .. + +//go:embed template.yml +var templateFile embed.FS + +type ( + dirs = []string + suite = string +) + +// groupedUnitTests maps suite names to top-level directories that should be +// included in that suite. The program adds an implicit group "rest" that +// includes all other top-level directories. +var groupedUnitTests = map[suite]dirs{ + "unit-node": {"node"}, + "unit-storage": {"storage", "extern"}, + "unit-cli": {"cli", "cmd", "api"}, +} + +func main() { + if len(os.Args) != 2 { + panic("expected path to repo as argument") + } + + repo := os.Args[1] + + tmpl := template.New("template.yml") + tmpl.Delims("[[", "]]") + tmpl.Funcs(template.FuncMap{ + "stripSuffix": func(in string) string { + return strings.TrimSuffix(in, "_test.go") + }, + }) + tmpl = template.Must(tmpl.ParseFS(templateFile, "*")) + + // list all itests. + itests, err := filepath.Glob(filepath.Join(repo, "./itests/*_test.go")) + if err != nil { + panic(err) + } + + // strip the dir from all entries. + for i, f := range itests { + itests[i] = filepath.Base(f) + } + + // calculate the exclusion set of unit test directories to exclude because + // they are already included in a grouped suite. + var excluded = map[string]struct{}{} + for _, ss := range groupedUnitTests { + for _, s := range ss { + e, err := filepath.Abs(filepath.Join(repo, s)) + if err != nil { + panic(err) + } + excluded[e] = struct{}{} + } + } + + // all unit tests top-level dirs that are not itests, nor included in other suites. + var rest = map[string]struct{}{} + err = filepath.Walk(repo, func(path string, f os.FileInfo, err error) error { + // include all tests that aren't in the itests directory. + if strings.Contains(path, "itests") { + return filepath.SkipDir + } + // exclude all tests included in other suites + if f.IsDir() { + if _, ok := excluded[path]; ok { + return filepath.SkipDir + } + } + if strings.HasSuffix(path, "_test.go") { + rel, err := filepath.Rel(repo, path) + if err != nil { + panic(err) + } + // take the first directory + rest[strings.Split(rel, string(os.PathSeparator))[0]] = struct{}{} + } + return err + }) + if err != nil { + panic(err) + } + + // add other directories to a 'rest' suite. + for k := range rest { + groupedUnitTests["unit-rest"] = append(groupedUnitTests["unit-rest"], k) + } + + // map iteration guarantees no order, so sort the array in-place. + sort.Strings(groupedUnitTests["unit-rest"]) + + // form the input data. + type data struct { + ItestFiles []string + UnitSuites map[string]string + } + in := data{ + ItestFiles: itests, + UnitSuites: func() map[string]string { + ret := make(map[string]string) + for name, dirs := range groupedUnitTests { + for i, d := range dirs { + dirs[i] = fmt.Sprintf("./%s/...", d) // turn into package + } + ret[name] = strings.Join(dirs, " ") + } + return ret + }(), + } + + out, err := os.Create("./config.yml") + if err != nil { + panic(err) + } + defer out.Close() + + // execute the template. + if err := tmpl.Execute(out, in); err != nil { + panic(err) + } +} diff --git a/.circleci/template.yml b/.circleci/template.yml new file mode 100644 index 000000000..fb59f23ea --- /dev/null +++ b/.circleci/template.yml @@ -0,0 +1,902 @@ +version: 2.1 +orbs: + go: gotest/tools@0.0.13 + aws-cli: circleci/aws-cli@1.3.2 + packer: salaxander/packer@0.0.3 + +executors: + golang: + docker: + - image: circleci/golang:1.16.4 + resource_class: 2xlarge + ubuntu: + docker: + - image: ubuntu:20.04 + +commands: + install-deps: + steps: + - go/install-ssh + - go/install: {package: git} + prepare: + parameters: + linux: + default: true + description: is a linux build environment? + type: boolean + darwin: + default: false + description: is a darwin build environment? + type: boolean + steps: + - checkout + - git_fetch_all_tags + - checkout + - when: + condition: << parameters.linux >> + steps: + - run: sudo apt-get update + - run: sudo apt-get install ocl-icd-opencl-dev libhwloc-dev + - run: git submodule sync + - run: git submodule update --init + download-params: + steps: + - restore_cache: + name: Restore parameters cache + keys: + - 'v25-2k-lotus-params' + paths: + - /var/tmp/filecoin-proof-parameters/ + - run: ./lotus fetch-params 2048 + - save_cache: + name: Save parameters cache + key: 'v25-2k-lotus-params' + paths: + - /var/tmp/filecoin-proof-parameters/ + install_ipfs: + steps: + - run: | + apt update + apt install -y wget + wget https://github.com/ipfs/go-ipfs/releases/download/v0.4.22/go-ipfs_v0.4.22_linux-amd64.tar.gz + wget https://github.com/ipfs/go-ipfs/releases/download/v0.4.22/go-ipfs_v0.4.22_linux-amd64.tar.gz.sha512 + if [ "$(sha512sum go-ipfs_v0.4.22_linux-amd64.tar.gz)" != "$(cat go-ipfs_v0.4.22_linux-amd64.tar.gz.sha512)" ] + then + echo "ipfs failed checksum check" + exit 1 + fi + tar -xf go-ipfs_v0.4.22_linux-amd64.tar.gz + mv go-ipfs/ipfs /usr/local/bin/ipfs + chmod +x /usr/local/bin/ipfs + git_fetch_all_tags: + steps: + - run: + name: fetch all tags + command: | + git fetch --all + +jobs: + mod-tidy-check: + executor: golang + steps: + - install-deps + - prepare + - go/mod-tidy-check + + build-all: + executor: golang + steps: + - install-deps + - prepare + - run: sudo apt-get update + - run: sudo apt-get install npm + - run: + command: make buildall + - store_artifacts: + path: lotus + - store_artifacts: + path: lotus-miner + - store_artifacts: + path: lotus-worker + - run: mkdir linux && mv lotus lotus-miner lotus-worker linux/ + - persist_to_workspace: + root: "." + paths: + - linux + + build-debug: + executor: golang + steps: + - install-deps + - prepare + - run: + command: make debug + + test: + description: | + Run tests with gotestsum. + parameters: &test-params + executor: + type: executor + default: golang + go-test-flags: + type: string + default: "-timeout 30m" + description: Flags passed to go test. + target: + type: string + default: "./..." + description: Import paths of packages to be tested. + proofs-log-test: + type: string + default: "0" + suite: + type: string + default: unit + description: Test suite name to report to CircleCI. + gotestsum-format: + type: string + default: standard-verbose + description: gotestsum format. https://github.com/gotestyourself/gotestsum#format + coverage: + type: string + default: -coverprofile=coverage.txt -coverpkg=github.com/filecoin-project/lotus/... + description: Coverage flag. Set to the empty string to disable. + codecov-upload: + type: boolean + default: true + description: | + Upload coverage report to https://codecov.io/. Requires the codecov API token to be + set as an environment variable for private projects. + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: + command: make deps lotus + no_output_timeout: 30m + - download-params + - go/install-gotestsum: + gobin: $HOME/.local/bin + version: 0.5.2 + - run: + name: go test + environment: + TEST_RUSTPROOFS_LOGS: << parameters.proofs-log-test >> + SKIP_CONFORMANCE: "1" + command: | + mkdir -p /tmp/test-reports/<< parameters.suite >> + mkdir -p /tmp/test-artifacts + gotestsum \ + --format << parameters.gotestsum-format >> \ + --junitfile /tmp/test-reports/<< parameters.suite >>/junit.xml \ + --jsonfile /tmp/test-artifacts/<< parameters.suite >>.json \ + -- \ + << parameters.coverage >> \ + << parameters.go-test-flags >> \ + << parameters.target >> + no_output_timeout: 30m + - store_test_results: + path: /tmp/test-reports + - store_artifacts: + path: /tmp/test-artifacts/<< parameters.suite >>.json + - when: + condition: << parameters.codecov-upload >> + steps: + - go/install: {package: bash} + - go/install: {package: curl} + - run: + shell: /bin/bash -eo pipefail + command: | + bash <(curl -s https://codecov.io/bash) + + test-conformance: + description: | + Run tests using a corpus of interoperable test vectors for Filecoin + implementations to test their correctness and compliance with the Filecoin + specifications. + parameters: + <<: *test-params + vectors-branch: + type: string + default: "" + description: | + Branch on github.com/filecoin-project/test-vectors to checkout and + test with. If empty (the default) the commit defined by the git + submodule is used. + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: + command: make deps lotus + no_output_timeout: 30m + - download-params + - when: + condition: + not: + equal: [ "", << parameters.vectors-branch >> ] + steps: + - run: + name: checkout vectors branch + command: | + cd extern/test-vectors + git fetch + git checkout origin/<< parameters.vectors-branch >> + - go/install-gotestsum: + gobin: $HOME/.local/bin + version: 0.5.2 + - run: + name: install statediff globally + command: | + ## statediff is optional; we succeed even if compilation fails. + mkdir -p /tmp/statediff + git clone https://github.com/filecoin-project/statediff.git /tmp/statediff + cd /tmp/statediff + go install ./cmd/statediff || exit 0 + - run: + name: go test + environment: + SKIP_CONFORMANCE: "0" + command: | + mkdir -p /tmp/test-reports + mkdir -p /tmp/test-artifacts + gotestsum \ + --format pkgname-and-test-fails \ + --junitfile /tmp/test-reports/junit.xml \ + -- \ + -v -coverpkg ./chain/vm/,github.com/filecoin-project/specs-actors/... -coverprofile=/tmp/conformance.out ./conformance/ + go tool cover -html=/tmp/conformance.out -o /tmp/test-artifacts/conformance-coverage.html + no_output_timeout: 30m + - store_test_results: + path: /tmp/test-reports + - store_artifacts: + path: /tmp/test-artifacts/conformance-coverage.html + build-ntwk-calibration: + description: | + Compile lotus binaries for the calibration network + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: make calibnet + - run: mkdir linux-calibrationnet && mv lotus lotus-miner lotus-worker linux-calibrationnet + - persist_to_workspace: + root: "." + paths: + - linux-calibrationnet + build-ntwk-butterfly: + description: | + Compile lotus binaries for the butterfly network + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: make butterflynet + - run: mkdir linux-butterflynet && mv lotus lotus-miner lotus-worker linux-butterflynet + - persist_to_workspace: + root: "." + paths: + - linux-butterflynet + build-ntwk-nerpa: + description: | + Compile lotus binaries for the nerpa network + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: make nerpanet + - run: mkdir linux-nerpanet && mv lotus lotus-miner lotus-worker linux-nerpanet + - persist_to_workspace: + root: "." + paths: + - linux-nerpanet + build-lotus-soup: + description: | + Compile `lotus-soup` Testground test plan + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: cd extern/filecoin-ffi && make + - run: + name: "go get lotus@master" + command: cd testplans/lotus-soup && go mod edit -replace=github.com/filecoin-project/lotus=../.. && go mod tidy + - run: + name: "build lotus-soup testplan" + command: pushd testplans/lotus-soup && go build -tags=testground . + trigger-testplans: + description: | + Trigger `lotus-soup` test cases on TaaS + parameters: + <<: *test-params + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: + name: "download testground" + command: wget https://gist.github.com/nonsense/5fbf3167cac79945f658771aed32fc44/raw/2e17eb0debf7ec6bdf027c1bdafc2c92dd97273b/testground-d3e9603 -O ~/testground-cli && chmod +x ~/testground-cli + - run: + name: "prepare .env.toml" + command: pushd testplans/lotus-soup && mkdir -p $HOME/testground && cp env-ci.toml $HOME/testground/.env.toml && echo 'endpoint="https://ci.testground.ipfs.team"' >> $HOME/testground/.env.toml && echo 'user="circleci"' >> $HOME/testground/.env.toml + - run: + name: "prepare testground home dir and link test plans" + command: mkdir -p $HOME/testground/plans && ln -s $(pwd)/testplans/lotus-soup $HOME/testground/plans/lotus-soup && ln -s $(pwd)/testplans/graphsync $HOME/testground/plans/graphsync + - run: + name: "go get lotus@master" + command: cd testplans/lotus-soup && go get github.com/filecoin-project/lotus@master + - run: + name: "trigger deals baseline testplan on taas" + command: ~/testground-cli run composition -f $HOME/testground/plans/lotus-soup/_compositions/baseline-k8s-3-1.toml --metadata-commit=$CIRCLE_SHA1 --metadata-repo=filecoin-project/lotus --metadata-branch=$CIRCLE_BRANCH + - run: + name: "trigger payment channel stress testplan on taas" + command: ~/testground-cli run composition -f $HOME/testground/plans/lotus-soup/_compositions/paych-stress-k8s.toml --metadata-commit=$CIRCLE_SHA1 --metadata-repo=filecoin-project/lotus --metadata-branch=$CIRCLE_BRANCH + - run: + name: "trigger graphsync testplan on taas" + command: ~/testground-cli run composition -f $HOME/testground/plans/graphsync/_compositions/stress-k8s.toml --metadata-commit=$CIRCLE_SHA1 --metadata-repo=filecoin-project/lotus --metadata-branch=$CIRCLE_BRANCH + + + build-macos: + description: build darwin lotus binary + macos: + xcode: "10.0.0" + working_directory: ~/go/src/github.com/filecoin-project/lotus + steps: + - prepare: + linux: false + darwin: true + - run: + name: Install go + command: | + curl -O https://dl.google.com/go/go1.16.4.darwin-amd64.pkg && \ + sudo installer -pkg go1.16.4.darwin-amd64.pkg -target / + - run: + name: Install pkg-config + command: HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config + - run: go version + - run: + name: Install Rust + command: | + curl https://sh.rustup.rs -sSf | sh -s -- -y + - run: + name: Install jq + command: | + curl --location https://github.com/stedolan/jq/releases/download/jq-1.6/jq-osx-amd64 --output /usr/local/bin/jq + chmod +x /usr/local/bin/jq + - run: + name: Install hwloc + command: | + mkdir ~/hwloc + curl --location https://download.open-mpi.org/release/hwloc/v2.4/hwloc-2.4.1.tar.gz --output ~/hwloc/hwloc-2.4.1.tar.gz + cd ~/hwloc + tar -xvzpf hwloc-2.4.1.tar.gz + cd hwloc-2.4.1 + ./configure && make && sudo make install + - restore_cache: + name: restore cargo cache + key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }} + - install-deps + - run: + command: make build + no_output_timeout: 30m + - store_artifacts: + path: lotus + - store_artifacts: + path: lotus-miner + - store_artifacts: + path: lotus-worker + - run: mkdir darwin && mv lotus lotus-miner lotus-worker darwin/ + - persist_to_workspace: + root: "." + paths: + - darwin + - save_cache: + name: save cargo cache + key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }} + paths: + - "~/.rustup" + - "~/.cargo" + + build-appimage: + machine: + image: ubuntu-2004:202104-01 + steps: + - checkout + - attach_workspace: + at: "." + - run: + name: install appimage-builder + command: | + # docs: https://appimage-builder.readthedocs.io/en/latest/intro/install.html + sudo apt update + sudo apt install -y python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace + sudo curl -Lo /usr/local/bin/appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage + sudo chmod +x /usr/local/bin/appimagetool + sudo pip3 install appimage-builder + - run: + name: install lotus dependencies + command: sudo apt install ocl-icd-opencl-dev libhwloc-dev + - run: + name: build appimage + command: | + sed -i "s/version: latest/version: ${CIRCLE_TAG:-latest}/" AppImageBuilder.yml + make appimage + - run: + name: prepare workspace + command: | + mkdir appimage + mv Lotus-*.AppImage appimage + - persist_to_workspace: + root: "." + paths: + - appimage + + + gofmt: + executor: golang + steps: + - install-deps + - prepare + - run: + command: "! go fmt ./... 2>&1 | read" + + gen-check: + executor: golang + steps: + - install-deps + - prepare + - run: make deps + - run: go install golang.org/x/tools/cmd/goimports + - run: go install github.com/hannahhoward/cbor-gen-for + - run: make gen + - run: git --no-pager diff + - run: git --no-pager diff --quiet + - run: make docsgen-cli + - run: git --no-pager diff + - run: git --no-pager diff --quiet + + docs-check: + executor: golang + steps: + - install-deps + - prepare + - run: go install golang.org/x/tools/cmd/goimports + - run: zcat build/openrpc/full.json.gz | jq > ../pre-openrpc-full + - run: zcat build/openrpc/miner.json.gz | jq > ../pre-openrpc-miner + - run: zcat build/openrpc/worker.json.gz | jq > ../pre-openrpc-worker + - run: make deps + - run: make docsgen + - run: zcat build/openrpc/full.json.gz | jq > ../post-openrpc-full + - run: zcat build/openrpc/miner.json.gz | jq > ../post-openrpc-miner + - run: zcat build/openrpc/worker.json.gz | jq > ../post-openrpc-worker + - run: git --no-pager diff + - run: diff ../pre-openrpc-full ../post-openrpc-full + - run: diff ../pre-openrpc-miner ../post-openrpc-miner + - run: diff ../pre-openrpc-worker ../post-openrpc-worker + - run: git --no-pager diff --quiet + + lint: &lint + description: | + Run golangci-lint. + parameters: + executor: + type: executor + default: golang + golangci-lint-version: + type: string + default: 1.27.0 + concurrency: + type: string + default: '2' + description: | + Concurrency used to run linters. Defaults to 2 because NumCPU is not + aware of container CPU limits. + args: + type: string + default: '' + description: | + Arguments to pass to golangci-lint + executor: << parameters.executor >> + steps: + - install-deps + - prepare + - run: + command: make deps + no_output_timeout: 30m + - go/install-golangci-lint: + gobin: $HOME/.local/bin + version: << parameters.golangci-lint-version >> + - run: + name: Lint + command: | + $HOME/.local/bin/golangci-lint run -v --timeout 2m \ + --concurrency << parameters.concurrency >> << parameters.args >> + lint-all: + <<: *lint + + publish: + description: publish binary artifacts + executor: ubuntu + steps: + - run: + name: Install git jq curl + command: apt update && apt install -y git jq curl + - checkout + - git_fetch_all_tags + - checkout + - install_ipfs + - attach_workspace: + at: "." + - run: + name: Create bundles + command: ./scripts/build-bundle.sh + - run: + name: Publish release + command: ./scripts/publish-release.sh + + publish-snapcraft: + description: build and push snapcraft + machine: + image: ubuntu-2004:202104-01 + resource_class: 2xlarge + parameters: + channel: + type: string + default: "edge" + description: snapcraft channel + steps: + - checkout + - run: + name: install snapcraft + command: sudo snap install snapcraft --classic + - run: + name: create snapcraft config file + command: | + mkdir -p ~/.config/snapcraft + echo "$SNAPCRAFT_LOGIN_FILE" | base64 -d > ~/.config/snapcraft/snapcraft.cfg + - run: + name: build snap + command: snapcraft --use-lxd + - run: + name: publish snap + command: snapcraft push *.snap --release << parameters.channel >> + + build-and-push-image: + description: build and push docker images to public AWS ECR registry + executor: aws-cli/default + parameters: + profile-name: + type: string + default: "default" + description: AWS profile name to be configured. + + aws-access-key-id: + type: env_var_name + default: AWS_ACCESS_KEY_ID + description: > + AWS access key id for IAM role. Set this to the name of + the environment variable you will set to hold this + value, i.e. AWS_ACCESS_KEY. + + aws-secret-access-key: + type: env_var_name + default: AWS_SECRET_ACCESS_KEY + description: > + AWS secret key for IAM role. Set this to the name of + the environment variable you will set to hold this + value, i.e. AWS_SECRET_ACCESS_KEY. + + region: + type: env_var_name + default: AWS_REGION + description: > + Name of env var storing your AWS region information, + defaults to AWS_REGION + + account-url: + type: env_var_name + default: AWS_ECR_ACCOUNT_URL + description: > + Env var storing Amazon ECR account URL that maps to an AWS account, + e.g. {awsAccountNum}.dkr.ecr.us-west-2.amazonaws.com + defaults to AWS_ECR_ACCOUNT_URL + + dockerfile: + type: string + default: Dockerfile + description: Name of dockerfile to use. Defaults to Dockerfile. + + path: + type: string + default: . + description: Path to the directory containing your Dockerfile and build context. Defaults to . (working directory). + + extra-build-args: + type: string + default: "" + description: > + Extra flags to pass to docker build. For examples, see + https://docs.docker.com/engine/reference/commandline/build + + repo: + type: string + description: Name of an Amazon ECR repository + + tag: + type: string + default: "latest" + description: A comma-separated string containing docker image tags to build and push (default = latest) + + steps: + - run: + name: Confirm that environment variables are set + command: | + if [ -z "$AWS_ACCESS_KEY_ID" ]; then + echo "No AWS_ACCESS_KEY_ID is set. Skipping build-and-push job ..." + circleci-agent step halt + fi + + - aws-cli/setup: + profile-name: <> + aws-access-key-id: <> + aws-secret-access-key: <> + aws-region: <> + + - run: + name: Log into Amazon ECR + command: | + aws ecr-public get-login-password --region $<> --profile <> | docker login --username AWS --password-stdin $<> + + - checkout + + - setup_remote_docker: + version: 19.03.13 + docker_layer_caching: false + + - run: + name: Build docker image + command: | + registry_id=$(echo $<> | sed "s;\..*;;g") + + docker_tag_args="" + IFS="," read -ra DOCKER_TAGS \<<< "<< parameters.tag >>" + for tag in "${DOCKER_TAGS[@]}"; do + docker_tag_args="$docker_tag_args -t $<>/<>:$tag" + done + + docker build \ + <<#parameters.extra-build-args>><><> \ + -f <>/<> \ + $docker_tag_args \ + <> + + - run: + name: Push image to Amazon ECR + command: | + IFS="," read -ra DOCKER_TAGS \<<< "<< parameters.tag >>" + for tag in "${DOCKER_TAGS[@]}"; do + docker push $<>/<>:${tag} + done + + publish-packer-mainnet: + description: build and push AWS IAM and DigitalOcean droplet. + executor: + name: packer/default + packer-version: 1.6.6 + steps: + - checkout + - attach_workspace: + at: "." + - packer/build: + template: tools/packer/lotus.pkr.hcl + args: "-var ci_workspace_bins=./linux -var lotus_network=mainnet -var git_tag=$CIRCLE_TAG" + publish-packer-calibrationnet: + description: build and push AWS IAM and DigitalOcean droplet. + executor: + name: packer/default + packer-version: 1.6.6 + steps: + - checkout + - attach_workspace: + at: "." + - packer/build: + template: tools/packer/lotus.pkr.hcl + args: "-var ci_workspace_bins=./linux-calibrationnet -var lotus_network=calibrationnet -var git_tag=$CIRCLE_TAG" + publish-packer-butterflynet: + description: build and push AWS IAM and DigitalOcean droplet. + executor: + name: packer/default + packer-version: 1.6.6 + steps: + - checkout + - attach_workspace: + at: "." + - packer/build: + template: tools/packer/lotus.pkr.hcl + args: "-var ci_workspace_bins=./linux-butterflynet -var lotus_network=butterflynet -var git_tag=$CIRCLE_TAG" + publish-packer-nerpanet: + description: build and push AWS IAM and DigitalOcean droplet. + executor: + name: packer/default + packer-version: 1.6.6 + steps: + - checkout + - attach_workspace: + at: "." + - packer/build: + template: tools/packer/lotus.pkr.hcl + args: "-var ci_workspace_bins=./linux-nerpanet -var lotus_network=nerpanet -var git_tag=$CIRCLE_TAG" + +workflows: + version: 2.1 + ci: + jobs: + - lint-all: + concurrency: "16" # expend all docker 2xlarge CPUs. + - mod-tidy-check + - gofmt + - gen-check + - docs-check + + [[- range $file := .ItestFiles -]] + [[ with $name := $file | stripSuffix ]] + - test: + name: test-itest-[[ $name ]] + suite: itest-[[ $name ]] + target: "./itests/[[ $file ]]" + [[ end ]] + [[- end -]] + + [[range $suite, $pkgs := .UnitSuites]] + - test: + name: test-[[ $suite ]] + suite: utest-[[ $suite ]] + target: "[[ $pkgs ]]" + [[- end]] + - test: + go-test-flags: "-run=TestMulticoreSDR" + suite: multicore-sdr-check + target: "./extern/sector-storage/ffiwrapper" + proofs-log-test: "1" + - test-conformance: + suite: conformance + codecov-upload: false + target: "./conformance" + - test-conformance: + name: test-conformance-bleeding-edge + codecov-upload: false + suite: conformance-bleeding-edge + target: "./conformance" + vectors-branch: master + - trigger-testplans: + filters: + branches: + only: + - master + - build-debug + - build-all: + filters: + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-ntwk-calibration: + filters: + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-ntwk-butterfly: + filters: + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-ntwk-nerpa: + filters: + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-lotus-soup + - build-macos: + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-appimage: + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish: + requires: + - build-all + - build-macos + - build-appimage + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-and-push-image: + dockerfile: Dockerfile.lotus + path: . + repo: lotus-dev + tag: '${CIRCLE_SHA1:0:8}' + - publish-packer-mainnet: + requires: + - build-all + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish-packer-calibrationnet: + requires: + - build-ntwk-calibration + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish-packer-butterflynet: + requires: + - build-ntwk-butterfly + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish-packer-nerpanet: + requires: + - build-ntwk-nerpa + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - publish-snapcraft: + name: publish-snapcraft-stable + channel: stable + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + + nightly: + triggers: + - schedule: + cron: "0 0 * * *" + filters: + branches: + only: + - master + jobs: + - publish-snapcraft: + name: publish-snapcraft-nightly + channel: edge diff --git a/.gitignore b/.gitignore index eddee0590..467f315b8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /lotus-health /lotus-chainwatch /lotus-shed +/lotus-sim /lotus-pond /lotus-townhall /lotus-fountain diff --git a/CHANGELOG.md b/CHANGELOG.md index fa6330907..0e4ded59a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,363 @@ # Lotus changelog +# 1.11.0-rc1 / 2021-06-28 + +This is the first release candidate for the optional Lotus v1.11.0 release that introduces several months of bugfixes and feature development. + +- github.com/filecoin-project/lotus: + - Lotus version 1.11.0 + - gateway: Add support for Version method ([filecoin-project/lotus#6618](https://github.com/filecoin-project/lotus/pull/6618)) + - Miner SimultaneousTransfers config ([filecoin-project/lotus#6612](https://github.com/filecoin-project/lotus/pull/6612)) + - revamped integration test kit (aka. Operation Sparks Joy) ([filecoin-project/lotus#6329](https://github.com/filecoin-project/lotus/pull/6329)) + - downgrade libp2p/go-libp2p-yamux to v0.5.1. ([filecoin-project/lotus#6605](https://github.com/filecoin-project/lotus/pull/6605)) + - Fix wallet error messages ([filecoin-project/lotus#6594](https://github.com/filecoin-project/lotus/pull/6594)) + - Fix CircleCI gen ([filecoin-project/lotus#6589](https://github.com/filecoin-project/lotus/pull/6589)) + - Make query-ask CLI more graceful ([filecoin-project/lotus#6590](https://github.com/filecoin-project/lotus/pull/6590)) + - ([filecoin-project/lotus#6406](https://github.com/filecoin-project/lotus/pull/6406)) + - move with changed name ([filecoin-project/lotus#6587](https://github.com/filecoin-project/lotus/pull/6587)) + - scale up sector expiration to avoid sector expire in batch-pre-commit waitting ([filecoin-project/lotus#6566](https://github.com/filecoin-project/lotus/pull/6566)) + - Merge release branch into master ([filecoin-project/lotus#6583](https://github.com/filecoin-project/lotus/pull/6583)) + - ([filecoin-project/lotus#6582](https://github.com/filecoin-project/lotus/pull/6582)) + - fix circleci being out of sync. ([filecoin-project/lotus#6573](https://github.com/filecoin-project/lotus/pull/6573)) + - dynamic circleci config for streamlining test execution ([filecoin-project/lotus#6561](https://github.com/filecoin-project/lotus/pull/6561)) + - Merge 1.10 branch into master ([filecoin-project/lotus#6571](https://github.com/filecoin-project/lotus/pull/6571)) + - Fix helptext ([filecoin-project/lotus#6560](https://github.com/filecoin-project/lotus/pull/6560)) + - extern/storage: add ability to ignore worker resources when scheduling. ([filecoin-project/lotus#6542](https://github.com/filecoin-project/lotus/pull/6542)) + - Merge 1.10 branch into master ([filecoin-project/lotus#6540](https://github.com/filecoin-project/lotus/pull/6540)) + - Initial draft: basic build instructions on Readme ([filecoin-project/lotus#6498](https://github.com/filecoin-project/lotus/pull/6498)) + - fix commit finalize failed ([filecoin-project/lotus#6521](https://github.com/filecoin-project/lotus/pull/6521)) + - Dynamic Retrieval pricing ([filecoin-project/lotus#6175](https://github.com/filecoin-project/lotus/pull/6175)) + - Fix soup ([filecoin-project/lotus#6501](https://github.com/filecoin-project/lotus/pull/6501)) + - fix: pick the correct partitions-per-post limit ([filecoin-project/lotus#6502](https://github.com/filecoin-project/lotus/pull/6502)) + - Fix the build + - Adjust various CLI display ratios to arbitrary precision ([filecoin-project/lotus#6309](https://github.com/filecoin-project/lotus/pull/6309)) + - Add utils to use multisigs as miner owners ([filecoin-project/lotus#6490](https://github.com/filecoin-project/lotus/pull/6490)) + - Test multicore SDR support ([filecoin-project/lotus#6479](https://github.com/filecoin-project/lotus/pull/6479)) + - sealing: Fix restartSectors race ([filecoin-project/lotus#6495](https://github.com/filecoin-project/lotus/pull/6495)) + - Merge 1.10 into master ([filecoin-project/lotus#6487](https://github.com/filecoin-project/lotus/pull/6487)) + - Unit tests for sector batchers ([filecoin-project/lotus#6432](https://github.com/filecoin-project/lotus/pull/6432)) + - Merge 1.10 changes into master ([filecoin-project/lotus#6466](https://github.com/filecoin-project/lotus/pull/6466)) + - Update chain list with correct help instructions ([filecoin-project/lotus#6465](https://github.com/filecoin-project/lotus/pull/6465)) + - clean failed sectors in batch commit ([filecoin-project/lotus#6451](https://github.com/filecoin-project/lotus/pull/6451)) + - itests/kit: add guard to ensure imports from tests only. ([filecoin-project/lotus#6445](https://github.com/filecoin-project/lotus/pull/6445)) + - consolidate integration tests into `itests` package; create test kit; cleanup ([filecoin-project/lotus#6311](https://github.com/filecoin-project/lotus/pull/6311)) + - Remove rc changelog, compile the new changelog for final release only ([filecoin-project/lotus#6444](https://github.com/filecoin-project/lotus/pull/6444)) + - updated configuration comments for docs ([filecoin-project/lotus#6440](https://github.com/filecoin-project/lotus/pull/6440)) + - Set ntwk v13 HyperDrive Calibration upgrade epoch ([filecoin-project/lotus#6441](https://github.com/filecoin-project/lotus/pull/6441)) + - Merge release/v1.10.10 into master ([filecoin-project/lotus#6439](https://github.com/filecoin-project/lotus/pull/6439)) + - implement a command to export a car ([filecoin-project/lotus#6405](https://github.com/filecoin-project/lotus/pull/6405)) + - Merge v1.10 release branch into master ([filecoin-project/lotus#6435](https://github.com/filecoin-project/lotus/pull/6435)) + - Fee config for sector batching ([filecoin-project/lotus#6420](https://github.com/filecoin-project/lotus/pull/6420)) + - Fix: correct the change of message size limit ([filecoin-project/lotus#6430](https://github.com/filecoin-project/lotus/pull/6430)) + - UX: lotus state power CLI should fail if called with a not-miner ([filecoin-project/lotus#6425](https://github.com/filecoin-project/lotus/pull/6425)) + - network reset friday + - Increase message size limit ([filecoin-project/lotus#6419](https://github.com/filecoin-project/lotus/pull/6419)) + - polish(stmgr): define ExecMonitor for message application callback ([filecoin-project/lotus#6389](https://github.com/filecoin-project/lotus/pull/6389)) + - upgrade testground action version ([filecoin-project/lotus#6403](https://github.com/filecoin-project/lotus/pull/6403)) + - Fix logging of stringified CIDs double-encoded in hex ([filecoin-project/lotus#6413](https://github.com/filecoin-project/lotus/pull/6413)) + - Update libp2p to 0.14.2 ([filecoin-project/lotus#6404](https://github.com/filecoin-project/lotus/pull/6404)) + - Bypass task scheduler for reading unsealed pieces ([filecoin-project/lotus#6280](https://github.com/filecoin-project/lotus/pull/6280)) + - testplans: lotus-soup: use default WPoStChallengeWindow ([filecoin-project/lotus#6400](https://github.com/filecoin-project/lotus/pull/6400)) + - build snapcraft ([filecoin-project/lotus#6388](https://github.com/filecoin-project/lotus/pull/6388)) + - Fix the doc errors of the sealing config funcs ([filecoin-project/lotus#6399](https://github.com/filecoin-project/lotus/pull/6399)) + - Integration tests for offline deals ([filecoin-project/lotus#6081](https://github.com/filecoin-project/lotus/pull/6081)) + - Fix success handling in Retreival ([filecoin-project/lotus#5921](https://github.com/filecoin-project/lotus/pull/5921)) + - Fix some flaky tests ([filecoin-project/lotus#6397](https://github.com/filecoin-project/lotus/pull/6397)) + - build appimage in CI ([filecoin-project/lotus#6384](https://github.com/filecoin-project/lotus/pull/6384)) + - Add doc on gas balancing ([filecoin-project/lotus#6392](https://github.com/filecoin-project/lotus/pull/6392)) + - Add a command to list retrievals ([filecoin-project/lotus#6337](https://github.com/filecoin-project/lotus/pull/6337)) + - Add interop network ([filecoin-project/lotus#6387](https://github.com/filecoin-project/lotus/pull/6387)) + - Network version 13 (v1.11) ([filecoin-project/lotus#6342](https://github.com/filecoin-project/lotus/pull/6342)) + - Generate AppImage ([filecoin-project/lotus#6208](https://github.com/filecoin-project/lotus/pull/6208)) + - lotus-gateway: add check command ([filecoin-project/lotus#6373](https://github.com/filecoin-project/lotus/pull/6373)) + - Add a warning to the release issue template ([filecoin-project/lotus#6374](https://github.com/filecoin-project/lotus/pull/6374)) + - update to markets-v1.4.0 ([filecoin-project/lotus#6369](https://github.com/filecoin-project/lotus/pull/6369)) + - Add test for AddVerifiedClient ([filecoin-project/lotus#6317](https://github.com/filecoin-project/lotus/pull/6317)) + - Typo fix in error message: "pubusb" -> "pubsub" ([filecoin-project/lotus#6365](https://github.com/filecoin-project/lotus/pull/6365)) + - Improve the cli state call command ([filecoin-project/lotus#6226](https://github.com/filecoin-project/lotus/pull/6226)) + - Upscale mineOne message to a WARN on unexpected ineligibility ([filecoin-project/lotus#6358](https://github.com/filecoin-project/lotus/pull/6358)) + - storagefsm: Fix batch deal packing behavior ([filecoin-project/lotus#6041](https://github.com/filecoin-project/lotus/pull/6041)) + - Remove few useless variable assignments ([filecoin-project/lotus#6359](https://github.com/filecoin-project/lotus/pull/6359)) + - lotus-wallet: JWT Support ([filecoin-project/lotus#6360](https://github.com/filecoin-project/lotus/pull/6360)) + - Reduce noise from 'peer has different genesis' messages ([filecoin-project/lotus#6357](https://github.com/filecoin-project/lotus/pull/6357)) + - events: Fix handling of multiple matched events per epoch ([filecoin-project/lotus#6355](https://github.com/filecoin-project/lotus/pull/6355)) + - Update RELEASE_ISSUE_TEMPLATE.md ([filecoin-project/lotus#6236](https://github.com/filecoin-project/lotus/pull/6236)) + - Get current seal proof when necessary ([filecoin-project/lotus#6339](https://github.com/filecoin-project/lotus/pull/6339)) + - Allow starting networks from arbitrary actor versions ([filecoin-project/lotus#6333](https://github.com/filecoin-project/lotus/pull/6333)) + - Remove log line when tracing is not configured ([filecoin-project/lotus#6334](https://github.com/filecoin-project/lotus/pull/6334)) + - Revert "Allow starting networks from arbitrary actor versions" ([filecoin-project/lotus#6330](https://github.com/filecoin-project/lotus/pull/6330)) + - separate tracing environment variables ([filecoin-project/lotus#6323](https://github.com/filecoin-project/lotus/pull/6323)) + - Allow starting networks from arbitrary actor versions ([filecoin-project/lotus#6305](https://github.com/filecoin-project/lotus/pull/6305)) + - feat: log dispute rate ([filecoin-project/lotus#6322](https://github.com/filecoin-project/lotus/pull/6322)) + - Use new actor tags ([filecoin-project/lotus#6291](https://github.com/filecoin-project/lotus/pull/6291)) + - Fix logging around mineOne ([filecoin-project/lotus#6310](https://github.com/filecoin-project/lotus/pull/6310)) + - Fix shell completions ([filecoin-project/lotus#6316](https://github.com/filecoin-project/lotus/pull/6316)) + - Allow 8MB sectors in devnet ([filecoin-project/lotus#6312](https://github.com/filecoin-project/lotus/pull/6312)) + - fix ticket expired ([filecoin-project/lotus#6304](https://github.com/filecoin-project/lotus/pull/6304)) + - oh, snap! ([filecoin-project/lotus#6202](https://github.com/filecoin-project/lotus/pull/6202)) + - Move verifreg shed utils to CLI ([filecoin-project/lotus#6135](https://github.com/filecoin-project/lotus/pull/6135)) + - consider storiface.PathStorage when calculating storage requirements ([filecoin-project/lotus#6233](https://github.com/filecoin-project/lotus/pull/6233)) + - `storage` module: add go docs and minor code quality refactors ([filecoin-project/lotus#6259](https://github.com/filecoin-project/lotus/pull/6259)) + - Revert "chore: update go-libp2p" ([filecoin-project/lotus#6306](https://github.com/filecoin-project/lotus/pull/6306)) + - Increase data transfer timeouts ([filecoin-project/lotus#6300](https://github.com/filecoin-project/lotus/pull/6300)) + - gateway: spin off from cmd to package ([filecoin-project/lotus#6294](https://github.com/filecoin-project/lotus/pull/6294)) + - Update to markets 1.3 ([filecoin-project/lotus#6149](https://github.com/filecoin-project/lotus/pull/6149)) + - Add a shed util to count 64 GiB miner stats ([filecoin-project/lotus#6290](https://github.com/filecoin-project/lotus/pull/6290)) + - Delete CODEOWNERS ([filecoin-project/lotus#6289](https://github.com/filecoin-project/lotus/pull/6289)) + - Merge v1.9.0 to master ([filecoin-project/lotus#6275](https://github.com/filecoin-project/lotus/pull/6275)) + - Backport 6200 to master ([filecoin-project/lotus#6272](https://github.com/filecoin-project/lotus/pull/6272)) + - Introduce stateless offline dealflow, bypassing the FSM/deallists ([filecoin-project/lotus#5961](https://github.com/filecoin-project/lotus/pull/5961)) + - chore: update go-libp2p ([filecoin-project/lotus#6231](https://github.com/filecoin-project/lotus/pull/6231)) + - fix: wait-api should use GetAPI to acquire binary specific API ([filecoin-project/lotus#6246](https://github.com/filecoin-project/lotus/pull/6246)) + - Update RELEASE_ISSUE_TEMPLATE.md + - fix(ci): Updates to lotus CI build process ([filecoin-project/lotus#6256](https://github.com/filecoin-project/lotus/pull/6256)) + - add flags to control gateway lookback parameters ([filecoin-project/lotus#6247](https://github.com/filecoin-project/lotus/pull/6247)) + - Feat/nerpa v4 ([filecoin-project/lotus#6248](https://github.com/filecoin-project/lotus/pull/6248)) + - chore(ci): Enable build on RC tags ([filecoin-project/lotus#6238](https://github.com/filecoin-project/lotus/pull/6238)) + - Transplant some useful commands to lotus-shed actor ([filecoin-project/lotus#5913](https://github.com/filecoin-project/lotus/pull/5913)) + - wip actor wrapper codegen ([filecoin-project/lotus#6108](https://github.com/filecoin-project/lotus/pull/6108)) + - Robust message management ([filecoin-project/lotus#5822](https://github.com/filecoin-project/lotus/pull/5822)) + - Add a shed util to count miners by post type ([filecoin-project/lotus#6169](https://github.com/filecoin-project/lotus/pull/6169)) + - Introduce a release issue template ([filecoin-project/lotus#5826](https://github.com/filecoin-project/lotus/pull/5826)) + - cron-wc ([filecoin-project/lotus#6178](https://github.com/filecoin-project/lotus/pull/6178)) + - This is a 1:1 forward-port of PR#6183 from 1.9.x to master ([filecoin-project/lotus#6196](https://github.com/filecoin-project/lotus/pull/6196)) + - Allow creation of state tree v3s ([filecoin-project/lotus#6167](https://github.com/filecoin-project/lotus/pull/6167)) + - drand: fix beacon cache ([filecoin-project/lotus#6164](https://github.com/filecoin-project/lotus/pull/6164)) + - Update cli gen ([filecoin-project/lotus#6155](https://github.com/filecoin-project/lotus/pull/6155)) + - mpool: Cleanup pre-nv12 selection logic ([filecoin-project/lotus#6148](https://github.com/filecoin-project/lotus/pull/6148)) + - Update ffi to proofs v7 ([filecoin-project/lotus#6150](https://github.com/filecoin-project/lotus/pull/6150)) + - Generate CLI docs ([filecoin-project/lotus#6145](https://github.com/filecoin-project/lotus/pull/6145)) + - feat: allow checkpointing to forks ([filecoin-project/lotus#6107](https://github.com/filecoin-project/lotus/pull/6107)) + - attempt to do better padding on pieces being written into sectors ([filecoin-project/lotus#5988](https://github.com/filecoin-project/lotus/pull/5988)) + - remove duplicate ask and calculate ping before lock ([filecoin-project/lotus#5968](https://github.com/filecoin-project/lotus/pull/5968)) + - Add a command to get the fees of a deal ([filecoin-project/lotus#5307](https://github.com/filecoin-project/lotus/pull/5307)) + - flaky tests improvement: separate TestBatchDealInput from TestAPIDealFlow ([filecoin-project/lotus#6141](https://github.com/filecoin-project/lotus/pull/6141)) + - Testground checks on push ([filecoin-project/lotus#5887](https://github.com/filecoin-project/lotus/pull/5887)) + - Add a CLI tool for miner proving deadline ([filecoin-project/lotus#6132](https://github.com/filecoin-project/lotus/pull/6132)) + - Use EmptyTSK where appropriate ([filecoin-project/lotus#6134](https://github.com/filecoin-project/lotus/pull/6134)) + - fix: use a consistent tipset in commands ([filecoin-project/lotus#6142](https://github.com/filecoin-project/lotus/pull/6142)) + - go mod tidy for lotus-soup testplans ([filecoin-project/lotus#6124](https://github.com/filecoin-project/lotus/pull/6124)) + - fix testground payment channel tests: use 1 miner ([filecoin-project/lotus#6126](https://github.com/filecoin-project/lotus/pull/6126)) + - fix: use the parent state when listing actors ([filecoin-project/lotus#6143](https://github.com/filecoin-project/lotus/pull/6143)) + - Speed up StateListMessages in some cases ([filecoin-project/lotus#6007](https://github.com/filecoin-project/lotus/pull/6007)) + - Return total power when GetPowerRaw doesn't find miner claim ([filecoin-project/lotus#4938](https://github.com/filecoin-project/lotus/pull/4938)) + - fix(splitstore): fix a panic on revert-only head changes ([filecoin-project/lotus#6133](https://github.com/filecoin-project/lotus/pull/6133)) + - shed: command to list duplicate messages in tipsets (steb) ([filecoin-project/lotus#5847](https://github.com/filecoin-project/lotus/pull/5847)) + - upgrade `lotus-soup` testplans and reduce deals concurrency to a single miner ([filecoin-project/lotus#6122](https://github.com/filecoin-project/lotus/pull/6122)) + - Merge releases (1.8.0) into master ([filecoin-project/lotus#6118](https://github.com/filecoin-project/lotus/pull/6118)) +- github.com/filecoin-project/go-commp-utils (v0.1.0 -> v0.1.1-0.20210427191551-70bf140d31c7): + - add a padding helper function ([filecoin-project/go-commp-utils#3](https://github.com/filecoin-project/go-commp-utils/pull/3)) +- github.com/filecoin-project/go-data-transfer (v1.4.3 -> v1.6.0): + - release: v1.6.0 + - fix: option to disable accept and complete timeouts + - fix: disable restart ack timeout + - release: v1.5.0 + - Add isRestart param to validators (#197) ([filecoin-project/go-data-transfer#197](https://github.com/filecoin-project/go-data-transfer/pull/197)) + - fix: flaky TestChannelMonitorAutoRestart (#198) ([filecoin-project/go-data-transfer#198](https://github.com/filecoin-project/go-data-transfer/pull/198)) + - Channel monitor watches for errors instead of measuring data rate (#190) ([filecoin-project/go-data-transfer#190](https://github.com/filecoin-project/go-data-transfer/pull/190)) + - fix: prevent concurrent restarts for same channel (#195) ([filecoin-project/go-data-transfer#195](https://github.com/filecoin-project/go-data-transfer/pull/195)) + - fix: channel state machine event handling (#194) ([filecoin-project/go-data-transfer#194](https://github.com/filecoin-project/go-data-transfer/pull/194)) + - Dont double count data sent (#185) ([filecoin-project/go-data-transfer#185](https://github.com/filecoin-project/go-data-transfer/pull/185)) + - release: v1.4.3 (#189) ([filecoin-project/go-data-transfer#189](https://github.com/filecoin-project/go-data-transfer/pull/189)) +- github.com/filecoin-project/go-fil-markets (v1.2.5 -> v1.5.0): + - release: v1.5.0 + - Dynamic Retrieval Pricing (#542) ([filecoin-project/go-fil-markets#542](https://github.com/filecoin-project/go-fil-markets/pull/542)) + - release: v1.4.0 (#551) ([filecoin-project/go-fil-markets#551](https://github.com/filecoin-project/go-fil-markets/pull/551)) + - Update to go data transfer v1.6.0 (#550) ([filecoin-project/go-fil-markets#550](https://github.com/filecoin-project/go-fil-markets/pull/550)) + - fix first make error (#548) ([filecoin-project/go-fil-markets#548](https://github.com/filecoin-project/go-fil-markets/pull/548)) + - release: v1.3.0 (#544) ([filecoin-project/go-fil-markets#544](https://github.com/filecoin-project/go-fil-markets/pull/544)) + - fix restarts during data transfer for a retrieval deal (#540) ([filecoin-project/go-fil-markets#540](https://github.com/filecoin-project/go-fil-markets/pull/540)) + - Test Retrieval for offline deals (#541) ([filecoin-project/go-fil-markets#541](https://github.com/filecoin-project/go-fil-markets/pull/541)) + - Allow anonymous submodule checkout (#535) ([filecoin-project/go-fil-markets#535](https://github.com/filecoin-project/go-fil-markets/pull/535)) +- github.com/filecoin-project/specs-actors (v0.9.13 -> v0.9.14): + - Set ConsensusMinerMinPower to 10 TiB (#1427) ([filecoin-project/specs-actors#1427](https://github.com/filecoin-project/specs-actors/pull/1427)) +- github.com/filecoin-project/specs-actors/v2 (v2.3.5-0.20210114162132-5b58b773f4fb -> v2.3.5): + - Set ConsensusMinerMinPower to 10 TiB (#1428) ([filecoin-project/specs-actors#1428](https://github.com/filecoin-project/specs-actors/pull/1428)) + - v2 VM satisfies SimVM interface (#1355) ([filecoin-project/specs-actors#1355](https://github.com/filecoin-project/specs-actors/pull/1355)) +- github.com/filecoin-project/specs-actors/v3 (v3.1.0 -> v3.1.1): + - Set ConsensusMinerMinPower to 10 TiB for all PoStProofPolicies (#1429) ([filecoin-project/specs-actors#1429](https://github.com/filecoin-project/specs-actors/pull/1429)) +- github.com/filecoin-project/specs-actors/v4 (v4.0.0 -> v4.0.1): + - Set ConsensusMinerMinPower to 10 TiB for all PoStProofPolicies (#1430) ([filecoin-project/specs-actors#1430](https://github.com/filecoin-project/specs-actors/pull/1430)) + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| Raúl Kripalani | 118 | +11972/-10860 | 472 | +| Łukasz Magiera | 65 | +10824/-4158 | 353 | +| aarshkshah1992 | 59 | +8057/-3355 | 224 | +| Aayush Rajasekaran | 41 | +8786/-1691 | 331 | +| Steven Allen | 106 | +7653/-2718 | 273 | +| dirkmc | 11 | +2580/-1371 | 77 | +| Dirk McCormick | 39 | +1865/-1194 | 79 | +| Jakub Sztandera | 19 | +1973/-485 | 81 | +| vyzo | 4 | +1748/-330 | 50 | +| Aarsh Shah | 5 | +1462/-213 | 27 | +| Cory Schwartz | 35 | +568/-206 | 59 | +| chadwick2143 | 3 | +739/-1 | 4 | +| Peter Rabbitson | 21 | +487/-164 | 36 | +| hannahhoward | 5 | +544/-5 | 19 | +| Jennifer Wang | 8 | +206/-172 | 17 | +| frrist | 1 | +137/-88 | 7 | +| Travis Person | 3 | +175/-6 | 7 | +| Alex Wade | 1 | +48/-129 | 1 | +| whyrusleeping | 8 | +161/-13 | 11 | +| lotus | 1 | +114/-46 | 1 | +| Anton Evangelatov | 8 | +107/-53 | 20 | +| Rjan | 4 | +115/-33 | 4 | +| ZenGround0 | 3 | +114/-1 | 4 | +| Aloxaf | 1 | +43/-61 | 7 | +| yaohcn | 4 | +89/-9 | 5 | +| mitchellsoo | 1 | +51/-0 | 1 | +| Mike Greenberg | 3 | +28/-18 | 4 | +| Jennifer | 6 | +9/-14 | 6 | +| Frank | 2 | +11/-10 | 2 | +| wangchao | 3 | +5/-4 | 4 | +| Steve Loeppky | 1 | +7/-1 | 1 | +| Lion | 1 | +4/-2 | 1 | +| Mimir | 1 | +2/-2 | 1 | +| raulk | 1 | +1/-1 | 1 | +| Jack Yao | 1 | +1/-1 | 1 | +| IPFSUnion | 1 | +1/-1 | 1 | + +# 1.10.0 / 2021-06-23 + +This is a mandatory release of Lotus that introduces Filecoin network v13, codenamed the HyperDrive upgrade. The +Filecoin mainnet will upgrade, which is epoch 892800, on 2021-06-30T22:00:00Z. The network upgrade introduces the +following FIPs: + +- [FIP-0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md): Add miner batched sector pre-commit method +- [FIP-0011](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0011.md): Remove reward auction from reporting consensus faults +- [FIP-0012](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0012.md): DataCap Top up for FIL+ Client Addresses +- [FIP-0013](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0013.md): Add ProveCommitSectorAggregated method to reduce on-chain congestion +- [FIP-0015](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0015.md): Revert FIP-0009(Exempt Window PoSts from BaseFee burn) + +Note that this release is built on top of Lotus v1.9.0. Enterprising users can use the `master` branch of Lotus to get the latest functionality, including all changes in this release candidate. + +## Proof batching and aggregation + +FIPs [0008](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0008.md) and [0013](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0013.md) combine to allow for a significant increase in the rate of onboarding storage on the Filecoin network. This aims to lead to more useful data being stored on the network, reduced network congestion, and lower network base fee. + +**Check out the documentation [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) for details on the new Lotus miner sealing config options, [here](https://docs.filecoin.io/mine/lotus/miner-configuration/#fees-section) for fee config options, and explanations of the new features.** + +Note: + - We recommend to keep `PreCommitSectorsBatch` as 1. + - We recommend miners to set `PreCommitBatchWait` lower than 30 hours. + - We recommend miners to set a longer `CommitBatchSlack` and `PreCommitBatchSlack` to prevent message failures + due to expirations. + +### Projected state tree growth + +In order to validate the Hyperdrive changes, we wrote a simulation to seal as many sectors as quickly as possible, assuming the same number and mix of 32GiB and 64GiB miners as the current network. + +Given these assumptions: + +- We'd expect a network storage growth rate of around 530PiB per day. 😳 🎉 🥳 😅 +- We'd expect network bandwidth dedicated to `SubmitWindowedPoSt` to grow by about 0.02% per day. +- We'd expect the [state-tree](https://spec.filecoin.io/#section-systems.filecoin_vm.state_tree) (and therefore [snapshot](https://docs.filecoin.io/get-started/lotus/chain/#lightweight-snapshot)) size to grow by 1.16GiB per day. + - Nearly all of the state-tree growth is expected to come from new sector metadata. +- We'd expect the daily lotus datastore growth rate to increase by about 10-15% (from current ~21GiB/day). + - Most "growth" of the lotus datastore is due to "churn", historical data that's no longer referenced by the latest state-tree. + +### Future improvements + +Various Lotus improvements are planned moving forward to mitigate the effects of the growing state tree size. The primary improvement is the [Lotus splitstore](https://github.com/filecoin-project/lotus/discussions/5788), which will soon be enabled by default. The feature allows for [online garbage collection](https://github.com/filecoin-project/lotus/issues/6577) for nodes that do not seek to maintain full chain and state history, thus eliminating the need for users to delete their datastores and sync from snapshots. + +Other improvements including better compressed snapshots, faster pre-migrations, and improved chain exports are in the roadmap. + +## WindowPost base fee burn + +Included in the HyperDrive upgrade is [FIP-0015](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0015.md) which eliminates the special-case gas treatment of `SubmitWindowedPoSt` messages that was introduced in [FIP-0009](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0009.md). Although `SubmitWindowedPoSt` messages will be relatively cheap, thanks to the introduction of optimistic acceptance of these proofs in [FIP-0010](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0010.md), storage providers should pay attention to their `MaxWindowPoStGasFee` config option: too low and PoSts may not land on chain; too high and they may cost an exorbitant amount! + +## Changelog + +### New Features + +- Implement FIP-0015 ([filecoin-project/lotus#6361](https://github.com/filecoin-project/lotus/pull/6361)) +- Integrate FIP0013 and FIP0008 ([filecoin-project/lotus#6235](https://github.com/filecoin-project/lotus/pull/6235)) + - [Configuration docs and cli examples](https://docs.filecoin.io/mine/lotus/miner-configuration/#precommitsectorsbatch) + - [cli docs](https://github.com/filecoin-project/lotus/blob/master/documentation/en/cli-lotus-miner.md#lotus-miner-sectors-batching) + - Introduce gas prices for aggregate verifications ([filecoin-project/lotus#6347](https://github.com/filecoin-project/lotus/pull/6347)) +- Introduce v5 actors ([filecoin-project/lotus#6195](https://github.com/filecoin-project/lotus/pull/6195)) +- Robustify commit batcher ([filecoin-project/lotus#6367](https://github.com/filecoin-project/lotus/pull/6367)) +- Always flush when timer goes off ([filecoin-project/lotus#6563](https://github.com/filecoin-project/lotus/pull/6563)) +- Update default fees for aggregates ([filecoin-project/lotus#6548](https://github.com/filecoin-project/lotus/pull/6548)) +- sealing: Early finalization option ([filecoin-project/lotus#6452](https://github.com/filecoin-project/lotus/pull/6452)) + - `./lotus-miner/config.toml/[Sealing.FinalizeEarly]`: default to false. Enable if you want to FinalizeSector before commiting +- Add filplus utils to CLI ([filecoin-project/lotus#6351](https://github.com/filecoin-project/lotus/pull/6351)) + - cli doc can be found [here](https://github.com/filecoin-project/lotus/blob/master/documentation/en/cli-lotus.md#lotus-filplus) +- Add miner-side MaxDealStartDelay config ([filecoin-project/lotus#6576](https://github.com/filecoin-project/lotus/pull/6576)) + + +### Bug Fixes +- chainstore: Don't take heaviestLk with backlogged reorgCh ([filecoin-project/lotus#6526](https://github.com/filecoin-project/lotus/pull/6526)) +- Backport #6041 - storagefsm: Fix batch deal packing behavior ([filecoin-project/lotus#6519](https://github.com/filecoin-project/lotus/pull/6519)) +- backport: pick the correct partitions-per-post limit ([filecoin-project/lotus#6503](https://github.com/filecoin-project/lotus/pull/6503)) +- failed sectors should be added into res correctly ([filecoin-project/lotus#6472](https://github.com/filecoin-project/lotus/pull/6472)) +- sealing: Fix restartSectors race ([filecoin-project/lotus#6491](https://github.com/filecoin-project/lotus/pull/6491)) +- Fund miners with the aggregate fee when ProveCommitting ([filecoin-project/lotus#6428](https://github.com/filecoin-project/lotus/pull/6428)) +- Commit and Precommit batcher cannot share a getSectorDeadline method ([filecoin-project/lotus#6416](https://github.com/filecoin-project/lotus/pull/6416)) +- Fix supported proof type manipulations for v5 actors ([filecoin-project/lotus#6366](https://github.com/filecoin-project/lotus/pull/6366)) +- events: Fix handling of multiple matched events per epoch ([filecoin-project/lotus#6362](https://github.com/filecoin-project/lotus/pull/6362)) +- Fix randomness fetching around null blocks ([filecoin-project/lotus#6240](https://github.com/filecoin-project/lotus/pull/6240)) + +### Improvements +- Appimage v1.10.0 rc3 ([filecoin-project/lotus#6492](https://github.com/filecoin-project/lotus/pull/6492)) +- Expand on Drand change testing ([filecoin-project/lotus#6500](https://github.com/filecoin-project/lotus/pull/6500)) +- Backport Fix logging around mineOne ([filecoin-project/lotus#6499](https://github.com/filecoin-project/lotus/pull/6499)) +- mpool: Add more metrics ([filecoin-project/lotus#6453](https://github.com/filecoin-project/lotus/pull/6453)) +- Merge backported PRs into v1.10 release branch ([filecoin-project/lotus#6436](https://github.com/filecoin-project/lotus/pull/6436)) +- Fix tests ([filecoin-project/lotus#6371](https://github.com/filecoin-project/lotus/pull/6371)) +- Extend the default deal start epoch delay ([filecoin-project/lotus#6350](https://github.com/filecoin-project/lotus/pull/6350)) +- sealing: Wire up context to batchers ([filecoin-project/lotus#6497](https://github.com/filecoin-project/lotus/pull/6497)) +- Improve address resolution for messages ([filecoin-project/lotus#6364](https://github.com/filecoin-project/lotus/pull/6364)) + +### Dependency Updates +- Proofs v8.0.2 ([filecoin-project/lotus#6524](https://github.com/filecoin-project/lotus/pull/6524)) +- Update to fixed Bellperson ([filecoin-project/lotus#6480](https://github.com/filecoin-project/lotus/pull/6480)) +- Update to go-praamfetch with fslocks ([filecoin-project/lotus#6473](https://github.com/filecoin-project/lotus/pull/6473)) +- Update ffi with fixed multicore sdr support ([filecoin-project/lotus#6471](https://github.com/filecoin-project/lotus/pull/6471)) +- github.com/filecoin-project/go-paramfetch (v0.0.2-0.20200701152213-3e0f0afdc261 -> v0.0.2-0.20210614165157-25a6c7769498) +- github.com/filecoin-project/specs-actors/v5 (v5.0.0-20210512015452-4fe3889fff57 -> v5.0.0) +- github.com/filecoin-project/go-hamt-ipld/v3 (v3.0.1 -> v3.1.0) +- github.com/ipfs/go-log/v2 (v2.1.2-0.20200626104915-0016c0b4b3e4 -> v2.1.3) +- github.com/filecoin-project/go-amt-ipld/v3 (v3.0.0 -> v3.1.0) + +### Network Version v13 HyperDrive Upgrade +- Set HyperDrive upgrade epoch ([filecoin-project/lotus#6565](https://github.com/filecoin-project/lotus/pull/6565)) +- version bump to lotus v1.10.0-rc6 ([filecoin-project/lotus#6529](https://github.com/filecoin-project/lotus/pull/6529)) +- Upgrade epochs for calibration reset ([filecoin-project/lotus#6528](https://github.com/filecoin-project/lotus/pull/6528)) +- Lotus version 1.10.0-rc5 ([filecoin-project/lotus#6504](https://github.com/filecoin-project/lotus/pull/6504)) +- Merge releases into v1.10 release ([filecoin-project/lotus#6494](https://github.com/filecoin-project/lotus/pull/6494)) +- update lotus to v1.10.0-rc3 ([filecoin-project/lotus#6481](https://github.com/filecoin-project/lotus/pull/6481)) +- updated configuration comments for docs +- Lotus version 1.10.0-rc2 ([filecoin-project/lotus#6443](https://github.com/filecoin-project/lotus/pull/6443)) +- Set ntwk v13 HyperDrive Calibration upgrade epoch ([filecoin-project/lotus#6442](https://github.com/filecoin-project/lotus/pull/6442)) + + +## Contributors + +💙Thank you to all the contributors! + +| Contributor | Commits | Lines ± | Files Changed | +|--------------------|---------|-------------|---------------| +| @magik6k | 81 | +9606/-1536 | 361 | +| @arajasek | 41 | +6543/-679 | 189 | +| @ZenGround0 | 11 | +4074/-727 | 110 | +| @anorth | 10 | +2035/-1177 | 55 | +| @iand | 1 | +779/-12 | 5 | +| @frrist | 2 | +722/-6 | 6 | +| @Stebalien | 6 | +368/-24 | 15 | +| @jennijuju | 11 | +204/-111 | 19 | +| @vyzo | 6 | +155/-66 | 13 | +| @coryschwartz | 10 | +171/-27 | 14 | +| @Kubuxu | 4 | +177/-13 | 7 | +| @ribasushi | 4 | +65/-42 | 5 | +| @travisperson | 2 | +11/-11 | 4 | +| @kirk-baird | 1 | +1/-5 | 1 | +| @wangchao | 2 | +3/-2 | 2 | + + # 1.9.0 / 2021-05-17 This is an optional Lotus release that introduces various improvements to the sealing, mining, and deal-making processes. diff --git a/Makefile b/Makefile index d20343f55..4e03d1b6a 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ BUILD_DEPS+=build/.filecoin-install CLEAN+=build/.filecoin-install ffi-version-check: - @[[ "$$(awk '/const Version/{print $$5}' extern/filecoin-ffi/version.go)" -eq 2 ]] || (echo "FFI version mismatch, update submodules"; exit 1) + @[[ "$$(awk '/const Version/{print $$5}' extern/filecoin-ffi/version.go)" -eq 3 ]] || (echo "FFI version mismatch, update submodules"; exit 1) BUILD_DEPS+=ffi-version-check .PHONY: ffi-version-check @@ -234,6 +234,12 @@ BINS+=tvx install-chainwatch: lotus-chainwatch install -C ./lotus-chainwatch /usr/local/bin/lotus-chainwatch +lotus-sim: $(BUILD_DEPS) + rm -f lotus-sim + go build $(GOFLAGS) -o lotus-sim ./cmd/lotus-sim +.PHONY: lotus-sim +BINS+=lotus-sim + # SYSTEMD install-daemon-service: install-daemon @@ -364,7 +370,7 @@ docsgen-openrpc-worker: docsgen-openrpc-bin .PHONY: docsgen docsgen-md-bin docsgen-openrpc-bin -gen: actors-gen type-gen method-gen docsgen api-gen +gen: actors-gen type-gen method-gen docsgen api-gen circleci @echo ">>> IF YOU'VE MODIFIED THE CLI, REMEMBER TO ALSO MAKE docsgen-cli" .PHONY: gen @@ -379,3 +385,6 @@ docsgen-cli: lotus lotus-miner lotus-worker print-%: @echo $*=$($*) + +circleci: + go generate -x ./.circleci \ No newline at end of file diff --git a/README.md b/README.md index 761838834..0218e87e9 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Lotus is an implementation of the Filecoin Distributed Storage Network. For more ## Building & Documentation -For instructions on how to build, install and setup lotus, please visit [https://docs.filecoin.io/get-started/lotus](https://docs.filecoin.io/get-started/lotus/). +For complete instructions on how to build, install and setup lotus, please visit [https://docs.filecoin.io/get-started/lotus](https://docs.filecoin.io/get-started/lotus/). Basic build instructions can be found further down in this readme. ## Reporting a Vulnerability @@ -50,6 +50,88 @@ When implementing a change: 7. Title the PR in a meaningful way and describe the rationale and the thought process in the PR description. 8. Write clean, thoughtful, and detailed [commit messages](https://chris.beams.io/posts/git-commit/). This is even more important than the PR description, because commit messages are stored _inside_ the Git history. One good rule is: if you are happy posting the commit message as the PR description, then it's a good commit message. +## Basic Build Instructions +**System-specific Software Dependencies**: + +Building Lotus requires some system dependencies, usually provided by your distribution. + +Ubuntu/Debian: +``` +sudo apt install mesa-opencl-icd ocl-icd-opencl-dev gcc git bzr jq pkg-config curl clang build-essential hwloc libhwloc-dev wget -y && sudo apt upgrade -y +``` + +Fedora: +``` +sudo dnf -y install gcc make git bzr jq pkgconfig mesa-libOpenCL mesa-libOpenCL-devel opencl-headers ocl-icd ocl-icd-devel clang llvm wget hwloc libhwloc-dev +``` + +For other distributions you can find the required dependencies [here.](https://docs.filecoin.io/get-started/lotus/installation/#system-specific) For instructions specific to macOS, you can find them [here.](https://docs.filecoin.io/get-started/lotus/installation/#macos) + +#### Go + +To build Lotus, you need a working installation of [Go 1.16.4 or higher](https://golang.org/dl/): + +```bash +wget -c https://golang.org/dl/go1.16.4.linux-amd64.tar.gz -O - | sudo tar -xz -C /usr/local +``` + +**TIP:** +You'll need to add `/usr/local/go/bin` to your path. For most Linux distributions you can run something like: + +```shell +echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bashrc && source ~/.bashrc +``` + +See the [official Golang installation instructions](https://golang.org/doc/install) if you get stuck. + +### Build and install Lotus + +Once all the dependencies are installed, you can build and install the Lotus suite (`lotus`, `lotus-miner`, and `lotus-worker`). + +1. Clone the repository: + + ```sh + git clone https://github.com/filecoin-project/lotus.git + cd lotus/ + ``` + +Note: The default branch `master` is the dev branch where the latest new features, bug fixes and improvement are in. However, if you want to run lotus on Filecoin mainnet and want to run a production-ready lotus, get the latest release[ here](https://github.com/filecoin-project/lotus/releases). + +2. To join mainnet, checkout the [latest release](https://github.com/filecoin-project/lotus/releases). + + If you are changing networks from a previous Lotus installation or there has been a network reset, read the [Switch networks guide](https://docs.filecoin.io/get-started/lotus/switch-networks/) before proceeding. + + For networks other than mainnet, look up the current branch or tag/commit for the network you want to join in the [Filecoin networks dashboard](https://network.filecoin.io), then build Lotus for your specific network below. + + ```sh + git checkout + # For example: + git checkout # tag for a release + ``` + + Currently, the latest code on the _master_ branch corresponds to mainnet. + +3. If you are in China, see "[Lotus: tips when running in China](https://docs.filecoin.io/get-started/lotus/tips-running-in-china/)". +4. This build instruction uses the prebuilt proofs binaries. If you want to build the proof binaries from source check the [complete instructions](https://docs.filecoin.io/get-started/lotus/installation/#build-and-install-lotus). Note, if you are building the proof binaries from source, [installing rustup](https://docs.filecoin.io/get-started/lotus/installation/#rustup) is also needed. + +5. Build and install Lotus: + + ```sh + make clean all #mainnet + + # Or to join a testnet or devnet: + make clean calibnet # Calibration with min 32GiB sectors + make clean nerpanet # Nerpa with min 512MiB sectors + + sudo make install + ``` + + This will put `lotus`, `lotus-miner` and `lotus-worker` in `/usr/local/bin`. + + `lotus` will use the `$HOME/.lotus` folder by default for storage (configuration, chain data, wallets, etc). See [advanced options](https://docs.filecoin.io/get-started/lotus/configuration-and-advanced-usage/) for information on how to customize the Lotus folder. + +6. You should now have Lotus installed. You can now [start the Lotus daemon and sync the chain](https://docs.filecoin.io/get-started/lotus/installation/#start-the-lotus-daemon-and-sync-the-chain). + ## 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) diff --git a/api/api_gateway.go b/api/api_gateway.go index 130a18c55..0ee66ac17 100644 --- a/api/api_gateway.go +++ b/api/api_gateway.go @@ -58,4 +58,5 @@ type Gateway interface { StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*MsgLookup, error) StateWaitMsg(ctx context.Context, cid cid.Cid, confidence uint64, limit abi.ChainEpoch, allowReplaced bool) (*MsgLookup, error) WalletBalance(context.Context, address.Address) (types.BigInt, error) + Version(context.Context) (APIVersion, error) } diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 6540ae7cd..8b99b6f19 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -531,6 +531,8 @@ type GatewayStruct struct { StateWaitMsg func(p0 context.Context, p1 cid.Cid, p2 uint64, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) `` + Version func(p0 context.Context) (APIVersion, error) `` + WalletBalance func(p0 context.Context, p1 address.Address) (types.BigInt, error) `` } } @@ -2679,6 +2681,14 @@ func (s *GatewayStub) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64, p3 return nil, xerrors.New("method not supported") } +func (s *GatewayStruct) Version(p0 context.Context) (APIVersion, error) { + return s.Internal.Version(p0) +} + +func (s *GatewayStub) Version(p0 context.Context) (APIVersion, error) { + return *new(APIVersion), xerrors.New("method not supported") +} + func (s *GatewayStruct) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) { return s.Internal.WalletBalance(p0, p1) } diff --git a/api/v0api/gateway.go b/api/v0api/gateway.go index 8a55b4c27..18a5ec7d6 100644 --- a/api/v0api/gateway.go +++ b/api/v0api/gateway.go @@ -63,6 +63,7 @@ type Gateway interface { StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) StateWaitMsg(ctx context.Context, msg cid.Cid, confidence uint64) (*api.MsgLookup, error) WalletBalance(context.Context, address.Address) (types.BigInt, error) + Version(context.Context) (api.APIVersion, error) } var _ Gateway = *new(FullNode) diff --git a/api/v0api/proxy_gen.go b/api/v0api/proxy_gen.go index fc2fc4186..0f5d2f918 100644 --- a/api/v0api/proxy_gen.go +++ b/api/v0api/proxy_gen.go @@ -449,6 +449,8 @@ type GatewayStruct struct { StateWaitMsg func(p0 context.Context, p1 cid.Cid, p2 uint64) (*api.MsgLookup, error) `` + Version func(p0 context.Context) (api.APIVersion, error) `` + WalletBalance func(p0 context.Context, p1 address.Address) (types.BigInt, error) `` } } @@ -2096,6 +2098,14 @@ func (s *GatewayStub) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64) (* return nil, xerrors.New("method not supported") } +func (s *GatewayStruct) Version(p0 context.Context) (api.APIVersion, error) { + return s.Internal.Version(p0) +} + +func (s *GatewayStub) Version(p0 context.Context) (api.APIVersion, error) { + return *new(api.APIVersion), xerrors.New("method not supported") +} + func (s *GatewayStruct) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) { return s.Internal.WalletBalance(p0, p1) } diff --git a/api/wrap.go b/api/wrap.go index 1ded67132..b26489a42 100644 --- a/api/wrap.go +++ b/api/wrap.go @@ -26,6 +26,27 @@ func Wrap(proxyT, wrapperT, impl interface{}) interface{} { })) } + for i := 0; i < proxy.Elem().NumField(); i++ { + if proxy.Elem().Type().Field(i).Name == "Internal" { + continue + } + + subProxy := proxy.Elem().Field(i).FieldByName("Internal") + for i := 0; i < ri.NumMethod(); i++ { + mt := ri.Type().Method(i) + if subProxy.FieldByName(mt.Name).Kind() == reflect.Invalid { + continue + } + + fn := ri.Method(i) + of := subProxy.FieldByName(mt.Name) + + subProxy.FieldByName(mt.Name).Set(reflect.MakeFunc(of.Type(), func(args []reflect.Value) (results []reflect.Value) { + return fn.Call(args) + })) + } + } + wp := reflect.New(reflect.TypeOf(wrapperT).Elem()) wp.Elem().Field(0).Set(proxy) return wp.Interface() diff --git a/build/bootstrap/calibnet.pi b/build/bootstrap/calibnet.pi index 0eb9fd2f3..20473eaaa 100644 --- a/build/bootstrap/calibnet.pi +++ b/build/bootstrap/calibnet.pi @@ -1,4 +1,4 @@ -/dns4/bootstrap-0.calibration.fildev.network/tcp/1347/p2p/12D3KooWRLZAseMo9h7fRD6ojn6YYDXHsBSavX5YmjBZ9ngtAEec -/dns4/bootstrap-1.calibration.fildev.network/tcp/1347/p2p/12D3KooWJFtDXgZEQMEkjJPSrbfdvh2xfjVKrXeNFG1t8ioJXAzv -/dns4/bootstrap-2.calibration.fildev.network/tcp/1347/p2p/12D3KooWP1uB9Lo7yCA3S17TD4Y5wStP5Nk7Vqh53m8GsFjkyujD -/dns4/bootstrap-3.calibration.fildev.network/tcp/1347/p2p/12D3KooWLrPM4WPK1YRGPCUwndWcDX8GCYgms3DiuofUmxwvhMCn +/dns4/bootstrap-0.calibration.fildev.network/tcp/1347/p2p/12D3KooWJkikQQkxS58spo76BYzFt4fotaT5NpV2zngvrqm4u5ow +/dns4/bootstrap-1.calibration.fildev.network/tcp/1347/p2p/12D3KooWLce5FDHR4EX4CrYavphA5xS3uDsX6aoowXh5tzDUxJav +/dns4/bootstrap-2.calibration.fildev.network/tcp/1347/p2p/12D3KooWA9hFfQG9GjP6bHeuQQbMD3FDtZLdW1NayxKXUT26PQZu +/dns4/bootstrap-3.calibration.fildev.network/tcp/1347/p2p/12D3KooWMHDi3LVTFG8Szqogt7RkNXvonbQYqSazxBx41A5aeuVz diff --git a/build/genesis/calibnet.car b/build/genesis/calibnet.car index d2fe7c3af..cbade953f 100644 Binary files a/build/genesis/calibnet.car and b/build/genesis/calibnet.car differ diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 4490109e9..0cbb105e1 100644 Binary files a/build/openrpc/full.json.gz and b/build/openrpc/full.json.gz differ diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 1d8ff7579..fe77f76c4 100644 Binary files a/build/openrpc/miner.json.gz and b/build/openrpc/miner.json.gz differ diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 3c4fa4f70..38c9a073b 100644 Binary files a/build/openrpc/worker.json.gz and b/build/openrpc/worker.json.gz differ diff --git a/build/params_calibnet.go b/build/params_calibnet.go index d4cea7e07..df334a516 100644 --- a/build/params_calibnet.go +++ b/build/params_calibnet.go @@ -33,20 +33,20 @@ const UpgradeLiftoffHeight = -5 const UpgradeKumquatHeight = 90 -const UpgradeCalicoHeight = 100 +const UpgradeCalicoHeight = 120 const UpgradePersianHeight = UpgradeCalicoHeight + (builtin2.EpochsInHour * 1) -const UpgradeClausHeight = 250 +const UpgradeClausHeight = 270 const UpgradeOrangeHeight = 300 -const UpgradeTrustHeight = 600 -const UpgradeNorwegianHeight = 114000 +const UpgradeTrustHeight = 330 -const UpgradeTurboHeight = 193789 +const UpgradeNorwegianHeight = 360 -// 2021-06-11T14:30:00Z -const UpgradeHyperdriveHeight = 321519 +const UpgradeTurboHeight = 390 + +const UpgradeHyperdriveHeight = 420 func init() { policy.SetConsensusMinerMinPower(abi.NewStoragePower(32 << 30)) diff --git a/build/params_mainnet.go b/build/params_mainnet.go index e9bf33f5a..1c9b69462 100644 --- a/build/params_mainnet.go +++ b/build/params_mainnet.go @@ -62,8 +62,8 @@ const UpgradeNorwegianHeight = 665280 // 2021-04-29T06:00:00Z const UpgradeTurboHeight = 712320 -// ??? -var UpgradeHyperdriveHeight = abi.ChainEpoch(9999999) +// 2021-06-30T22:00:00Z +var UpgradeHyperdriveHeight = abi.ChainEpoch(892800) func init() { if os.Getenv("LOTUS_USE_TEST_ADDRESSES") != "1" { diff --git a/build/version.go b/build/version.go index 5a4a494fc..49ec3f446 100644 --- a/build/version.go +++ b/build/version.go @@ -34,7 +34,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "1.11.0-dev" +const BuildVersion = "1.11.0-rc1" func UserVersion() string { if os.Getenv("LOTUS_VERSION_IGNORE_COMMIT") == "1" { diff --git a/chain/actors/builtin/miner/actor.go.template b/chain/actors/builtin/miner/actor.go.template index 619dc699d..8d46f99fd 100644 --- a/chain/actors/builtin/miner/actor.go.template +++ b/chain/actors/builtin/miner/actor.go.template @@ -22,6 +22,7 @@ import ( miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" {{range .versions}} builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" {{end}} @@ -97,9 +98,13 @@ type State interface { FindSector(abi.SectorNumber) (*SectorLocation, error) GetSectorExpiration(abi.SectorNumber) (*SectorExpiration, error) GetPrecommittedSector(abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) + ForEachPrecommittedSector(func(SectorPreCommitOnChainInfo) error) error LoadSectors(sectorNos *bitfield.BitField) ([]*SectorOnChainInfo, error) NumLiveSectors() (uint64, error) IsAllocated(abi.SectorNumber) (bool, error) + // UnallocatedSectorNumbers returns up to count unallocated sector numbers (or less than + // count if there aren't enough). + UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) // Note that ProvingPeriodStart is deprecated and will be renamed / removed in a future version of actors GetProvingPeriodStart() (abi.ChainEpoch, error) @@ -176,6 +181,7 @@ type DeclareFaultsRecoveredParams = miner0.DeclareFaultsRecoveredParams type SubmitWindowedPoStParams = miner0.SubmitWindowedPoStParams type ProveCommitSectorParams = miner0.ProveCommitSectorParams type DisputeWindowedPoStParams = miner3.DisputeWindowedPoStParams +type ProveCommitAggregateParams = miner5.ProveCommitAggregateParams func PreferredSealProofTypeFromWindowPoStType(nver network.Version, proof abi.RegisteredPoStProof) (abi.RegisteredSealProof, error) { // We added support for the new proofs in network version 7, and removed support for the old diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go index 6e35d4e9f..995dc78cb 100644 --- a/chain/actors/builtin/miner/miner.go +++ b/chain/actors/builtin/miner/miner.go @@ -22,6 +22,7 @@ import ( miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" @@ -156,9 +157,13 @@ type State interface { FindSector(abi.SectorNumber) (*SectorLocation, error) GetSectorExpiration(abi.SectorNumber) (*SectorExpiration, error) GetPrecommittedSector(abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) + ForEachPrecommittedSector(func(SectorPreCommitOnChainInfo) error) error LoadSectors(sectorNos *bitfield.BitField) ([]*SectorOnChainInfo, error) NumLiveSectors() (uint64, error) IsAllocated(abi.SectorNumber) (bool, error) + // UnallocatedSectorNumbers returns up to count unallocated sector numbers (or less than + // count if there aren't enough). + UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) // Note that ProvingPeriodStart is deprecated and will be renamed / removed in a future version of actors GetProvingPeriodStart() (abi.ChainEpoch, error) @@ -235,6 +240,7 @@ type DeclareFaultsRecoveredParams = miner0.DeclareFaultsRecoveredParams type SubmitWindowedPoStParams = miner0.SubmitWindowedPoStParams type ProveCommitSectorParams = miner0.ProveCommitSectorParams type DisputeWindowedPoStParams = miner3.DisputeWindowedPoStParams +type ProveCommitAggregateParams = miner5.ProveCommitAggregateParams func PreferredSealProofTypeFromWindowPoStType(nver network.Version, proof abi.RegisteredPoStProof) (abi.RegisteredSealProof, error) { // We added support for the new proofs in network version 7, and removed support for the old diff --git a/chain/actors/builtin/miner/state.go.template b/chain/actors/builtin/miner/state.go.template index b7e5f40df..eb7ab00bf 100644 --- a/chain/actors/builtin/miner/state.go.template +++ b/chain/actors/builtin/miner/state.go.template @@ -8,6 +8,7 @@ import ( {{end}} "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/dline" "github.com/ipfs/go-cid" @@ -209,6 +210,26 @@ func (s *state{{.v}}) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCom return &ret, nil } +func (s *state{{.v}}) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { +{{if (ge .v 3) -}} + precommitted, err := adt{{.v}}.AsMap(s.store, s.State.PreCommittedSectors, builtin{{.v}}.DefaultHamtBitwidth) +{{- else -}} + precommitted, err := adt{{.v}}.AsMap(s.store, s.State.PreCommittedSectors) +{{- end}} + if err != nil { + return err + } + + var info miner{{.v}}.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV{{.v}}SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + func (s *state{{.v}}) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { sectors, err := miner{{.v}}.LoadSectors(s.store, s.State.Sectors) if err != nil { @@ -242,9 +263,15 @@ func (s *state{{.v}}) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo return infos, nil } -func (s *state{{.v}}) IsAllocated(num abi.SectorNumber) (bool, error) { +func (s *state{{.v}}) loadAllocatedSectorNumbers() (bitfield.BitField, error) { var allocatedSectors bitfield.BitField - if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state{{.v}}) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { return false, err } @@ -255,6 +282,42 @@ func (s *state{{.v}}) GetProvingPeriodStart() (abi.ChainEpoch, error) { return s.State.ProvingPeriodStart, nil } +func (s *state{{.v}}) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return nil, err + } + + allocatedRuns, err := allocatedSectors.RunIterator() + if err != nil { + return nil, err + } + + unallocatedRuns, err := rle.Subtract( + &rle.RunSliceIterator{Runs: []rle.Run{ {Val: true, Len: abi.MaxSectorNumber} }}, + allocatedRuns, + ) + if err != nil { + return nil, err + } + + iter, err := rle.BitsFromRuns(unallocatedRuns) + if err != nil { + return nil, err + } + + sectors := make([]abi.SectorNumber, 0, count) + for iter.HasNext() && len(sectors) < count { + nextNo, err := iter.Next() + if err != nil { + return nil, err + } + sectors = append(sectors, abi.SectorNumber(nextNo)) + } + + return sectors, nil +} + func (s *state{{.v}}) LoadDeadline(idx uint64) (Deadline, error) { dls, err := s.State.LoadDeadlines(s.store) if err != nil { diff --git a/chain/actors/builtin/miner/v0.go b/chain/actors/builtin/miner/v0.go index 344be1993..c5e887481 100644 --- a/chain/actors/builtin/miner/v0.go +++ b/chain/actors/builtin/miner/v0.go @@ -8,6 +8,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/dline" "github.com/ipfs/go-cid" @@ -206,6 +207,22 @@ func (s *state0) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn return &ret, nil } +func (s *state0) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt0.AsMap(s.store, s.State.PreCommittedSectors) + if err != nil { + return err + } + + var info miner0.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV0SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + func (s *state0) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { sectors, err := miner0.LoadSectors(s.store, s.State.Sectors) if err != nil { @@ -239,9 +256,15 @@ func (s *state0) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err return infos, nil } -func (s *state0) IsAllocated(num abi.SectorNumber) (bool, error) { +func (s *state0) loadAllocatedSectorNumbers() (bitfield.BitField, error) { var allocatedSectors bitfield.BitField - if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state0) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { return false, err } @@ -252,6 +275,42 @@ func (s *state0) GetProvingPeriodStart() (abi.ChainEpoch, error) { return s.State.ProvingPeriodStart, nil } +func (s *state0) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return nil, err + } + + allocatedRuns, err := allocatedSectors.RunIterator() + if err != nil { + return nil, err + } + + unallocatedRuns, err := rle.Subtract( + &rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}}, + allocatedRuns, + ) + if err != nil { + return nil, err + } + + iter, err := rle.BitsFromRuns(unallocatedRuns) + if err != nil { + return nil, err + } + + sectors := make([]abi.SectorNumber, 0, count) + for iter.HasNext() && len(sectors) < count { + nextNo, err := iter.Next() + if err != nil { + return nil, err + } + sectors = append(sectors, abi.SectorNumber(nextNo)) + } + + return sectors, nil +} + func (s *state0) LoadDeadline(idx uint64) (Deadline, error) { dls, err := s.State.LoadDeadlines(s.store) if err != nil { diff --git a/chain/actors/builtin/miner/v2.go b/chain/actors/builtin/miner/v2.go index 3e76d0b69..45d4a7165 100644 --- a/chain/actors/builtin/miner/v2.go +++ b/chain/actors/builtin/miner/v2.go @@ -6,6 +6,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/dline" "github.com/ipfs/go-cid" @@ -204,6 +205,22 @@ func (s *state2) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn return &ret, nil } +func (s *state2) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt2.AsMap(s.store, s.State.PreCommittedSectors) + if err != nil { + return err + } + + var info miner2.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV2SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + func (s *state2) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { sectors, err := miner2.LoadSectors(s.store, s.State.Sectors) if err != nil { @@ -237,9 +254,15 @@ func (s *state2) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err return infos, nil } -func (s *state2) IsAllocated(num abi.SectorNumber) (bool, error) { +func (s *state2) loadAllocatedSectorNumbers() (bitfield.BitField, error) { var allocatedSectors bitfield.BitField - if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state2) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { return false, err } @@ -250,6 +273,42 @@ func (s *state2) GetProvingPeriodStart() (abi.ChainEpoch, error) { return s.State.ProvingPeriodStart, nil } +func (s *state2) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return nil, err + } + + allocatedRuns, err := allocatedSectors.RunIterator() + if err != nil { + return nil, err + } + + unallocatedRuns, err := rle.Subtract( + &rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}}, + allocatedRuns, + ) + if err != nil { + return nil, err + } + + iter, err := rle.BitsFromRuns(unallocatedRuns) + if err != nil { + return nil, err + } + + sectors := make([]abi.SectorNumber, 0, count) + for iter.HasNext() && len(sectors) < count { + nextNo, err := iter.Next() + if err != nil { + return nil, err + } + sectors = append(sectors, abi.SectorNumber(nextNo)) + } + + return sectors, nil +} + func (s *state2) LoadDeadline(idx uint64) (Deadline, error) { dls, err := s.State.LoadDeadlines(s.store) if err != nil { diff --git a/chain/actors/builtin/miner/v3.go b/chain/actors/builtin/miner/v3.go index 72986233d..166abe1e7 100644 --- a/chain/actors/builtin/miner/v3.go +++ b/chain/actors/builtin/miner/v3.go @@ -6,6 +6,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/dline" "github.com/ipfs/go-cid" @@ -206,6 +207,22 @@ func (s *state3) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn return &ret, nil } +func (s *state3) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt3.AsMap(s.store, s.State.PreCommittedSectors, builtin3.DefaultHamtBitwidth) + if err != nil { + return err + } + + var info miner3.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV3SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + func (s *state3) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { sectors, err := miner3.LoadSectors(s.store, s.State.Sectors) if err != nil { @@ -239,9 +256,15 @@ func (s *state3) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err return infos, nil } -func (s *state3) IsAllocated(num abi.SectorNumber) (bool, error) { +func (s *state3) loadAllocatedSectorNumbers() (bitfield.BitField, error) { var allocatedSectors bitfield.BitField - if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state3) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { return false, err } @@ -252,6 +275,42 @@ func (s *state3) GetProvingPeriodStart() (abi.ChainEpoch, error) { return s.State.ProvingPeriodStart, nil } +func (s *state3) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return nil, err + } + + allocatedRuns, err := allocatedSectors.RunIterator() + if err != nil { + return nil, err + } + + unallocatedRuns, err := rle.Subtract( + &rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}}, + allocatedRuns, + ) + if err != nil { + return nil, err + } + + iter, err := rle.BitsFromRuns(unallocatedRuns) + if err != nil { + return nil, err + } + + sectors := make([]abi.SectorNumber, 0, count) + for iter.HasNext() && len(sectors) < count { + nextNo, err := iter.Next() + if err != nil { + return nil, err + } + sectors = append(sectors, abi.SectorNumber(nextNo)) + } + + return sectors, nil +} + func (s *state3) LoadDeadline(idx uint64) (Deadline, error) { dls, err := s.State.LoadDeadlines(s.store) if err != nil { diff --git a/chain/actors/builtin/miner/v4.go b/chain/actors/builtin/miner/v4.go index 96ed21f04..71a2b9f9d 100644 --- a/chain/actors/builtin/miner/v4.go +++ b/chain/actors/builtin/miner/v4.go @@ -6,6 +6,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/dline" "github.com/ipfs/go-cid" @@ -206,6 +207,22 @@ func (s *state4) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn return &ret, nil } +func (s *state4) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt4.AsMap(s.store, s.State.PreCommittedSectors, builtin4.DefaultHamtBitwidth) + if err != nil { + return err + } + + var info miner4.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV4SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + func (s *state4) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { sectors, err := miner4.LoadSectors(s.store, s.State.Sectors) if err != nil { @@ -239,9 +256,15 @@ func (s *state4) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err return infos, nil } -func (s *state4) IsAllocated(num abi.SectorNumber) (bool, error) { +func (s *state4) loadAllocatedSectorNumbers() (bitfield.BitField, error) { var allocatedSectors bitfield.BitField - if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state4) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { return false, err } @@ -252,6 +275,42 @@ func (s *state4) GetProvingPeriodStart() (abi.ChainEpoch, error) { return s.State.ProvingPeriodStart, nil } +func (s *state4) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return nil, err + } + + allocatedRuns, err := allocatedSectors.RunIterator() + if err != nil { + return nil, err + } + + unallocatedRuns, err := rle.Subtract( + &rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}}, + allocatedRuns, + ) + if err != nil { + return nil, err + } + + iter, err := rle.BitsFromRuns(unallocatedRuns) + if err != nil { + return nil, err + } + + sectors := make([]abi.SectorNumber, 0, count) + for iter.HasNext() && len(sectors) < count { + nextNo, err := iter.Next() + if err != nil { + return nil, err + } + sectors = append(sectors, abi.SectorNumber(nextNo)) + } + + return sectors, nil +} + func (s *state4) LoadDeadline(idx uint64) (Deadline, error) { dls, err := s.State.LoadDeadlines(s.store) if err != nil { diff --git a/chain/actors/builtin/miner/v5.go b/chain/actors/builtin/miner/v5.go index 7996acf32..568834777 100644 --- a/chain/actors/builtin/miner/v5.go +++ b/chain/actors/builtin/miner/v5.go @@ -6,6 +6,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/dline" "github.com/ipfs/go-cid" @@ -206,6 +207,22 @@ func (s *state5) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn return &ret, nil } +func (s *state5) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt5.AsMap(s.store, s.State.PreCommittedSectors, builtin5.DefaultHamtBitwidth) + if err != nil { + return err + } + + var info miner5.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV5SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + func (s *state5) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { sectors, err := miner5.LoadSectors(s.store, s.State.Sectors) if err != nil { @@ -239,9 +256,15 @@ func (s *state5) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err return infos, nil } -func (s *state5) IsAllocated(num abi.SectorNumber) (bool, error) { +func (s *state5) loadAllocatedSectorNumbers() (bitfield.BitField, error) { var allocatedSectors bitfield.BitField - if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state5) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { return false, err } @@ -252,6 +275,42 @@ func (s *state5) GetProvingPeriodStart() (abi.ChainEpoch, error) { return s.State.ProvingPeriodStart, nil } +func (s *state5) UnallocatedSectorNumbers(count int) ([]abi.SectorNumber, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return nil, err + } + + allocatedRuns, err := allocatedSectors.RunIterator() + if err != nil { + return nil, err + } + + unallocatedRuns, err := rle.Subtract( + &rle.RunSliceIterator{Runs: []rle.Run{{Val: true, Len: abi.MaxSectorNumber}}}, + allocatedRuns, + ) + if err != nil { + return nil, err + } + + iter, err := rle.BitsFromRuns(unallocatedRuns) + if err != nil { + return nil, err + } + + sectors := make([]abi.SectorNumber, 0, count) + for iter.HasNext() && len(sectors) < count { + nextNo, err := iter.Next() + if err != nil { + return nil, err + } + sectors = append(sectors, abi.SectorNumber(nextNo)) + } + + return sectors, nil +} + func (s *state5) LoadDeadline(idx uint64) (Deadline, error) { dls, err := s.State.LoadDeadlines(s.store) if err != nil { diff --git a/chain/actors/builtin/multisig/actor.go.template b/chain/actors/builtin/multisig/actor.go.template index 77bc13f67..b899815a6 100644 --- a/chain/actors/builtin/multisig/actor.go.template +++ b/chain/actors/builtin/multisig/actor.go.template @@ -115,6 +115,7 @@ type MessageBuilder interface { type ProposalHashData = msig{{.latestVersion}}.ProposalHashData type ProposeReturn = msig{{.latestVersion}}.ProposeReturn type ProposeParams = msig{{.latestVersion}}.ProposeParams +type ApproveReturn = msig{{.latestVersion}}.ApproveReturn func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { params := msig{{.latestVersion}}.TxnIDParams{ID: msig{{.latestVersion}}.TxnID(id)} diff --git a/chain/actors/builtin/multisig/multisig.go b/chain/actors/builtin/multisig/multisig.go index ae6a9daf3..c950ced90 100644 --- a/chain/actors/builtin/multisig/multisig.go +++ b/chain/actors/builtin/multisig/multisig.go @@ -186,6 +186,7 @@ type MessageBuilder interface { type ProposalHashData = msig5.ProposalHashData type ProposeReturn = msig5.ProposeReturn type ProposeParams = msig5.ProposeParams +type ApproveReturn = msig5.ApproveReturn func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { params := msig5.TxnIDParams{ID: msig5.TxnID(id)} diff --git a/chain/gen/genesis/f00_system.go b/chain/gen/genesis/f00_system.go index d1dd203b6..4fde27107 100644 --- a/chain/gen/genesis/f00_system.go +++ b/chain/gen/genesis/f00_system.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin/system" @@ -32,8 +33,9 @@ func SetupSystemActor(ctx context.Context, bs bstore.Blockstore, av actors.Versi } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/f01_init.go b/chain/gen/genesis/f01_init.go index 88d409221..61ec91703 100644 --- a/chain/gen/genesis/f01_init.go +++ b/chain/gen/genesis/f01_init.go @@ -11,6 +11,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/specs-actors/actors/util/adt" @@ -181,8 +182,9 @@ func SetupInitActor(ctx context.Context, bs bstore.Blockstore, netname string, i } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return counter, act, keyToId, nil diff --git a/chain/gen/genesis/f03_cron.go b/chain/gen/genesis/f03_cron.go index c6fd2422a..c9dd0d341 100644 --- a/chain/gen/genesis/f03_cron.go +++ b/chain/gen/genesis/f03_cron.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin/cron" @@ -31,8 +32,9 @@ func SetupCronActor(ctx context.Context, bs bstore.Blockstore, av actors.Version } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/f04_power.go b/chain/gen/genesis/f04_power.go index 6fe4d75c0..b5e08cebe 100644 --- a/chain/gen/genesis/f04_power.go +++ b/chain/gen/genesis/f04_power.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/actors" @@ -33,8 +34,9 @@ func SetupStoragePowerActor(ctx context.Context, bs bstore.Blockstore, av actors } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/f05_market.go b/chain/gen/genesis/f05_market.go index 5c39ef38f..ac32294c9 100644 --- a/chain/gen/genesis/f05_market.go +++ b/chain/gen/genesis/f05_market.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin/market" @@ -31,8 +32,9 @@ func SetupStorageMarketActor(ctx context.Context, bs bstore.Blockstore, av actor } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/f06_vreg.go b/chain/gen/genesis/f06_vreg.go index d8f5ee2a0..e61c951f5 100644 --- a/chain/gen/genesis/f06_vreg.go +++ b/chain/gen/genesis/f06_vreg.go @@ -3,6 +3,7 @@ package genesis import ( "context" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" "github.com/filecoin-project/lotus/chain/actors" @@ -46,8 +47,9 @@ func SetupVerifiedRegistryActor(ctx context.Context, bs bstore.Blockstore, av ac } act := &types.Actor{ - Code: actcid, - Head: statecid, + Code: actcid, + Head: statecid, + Balance: big.Zero(), } return act, nil diff --git a/chain/gen/genesis/genesis.go b/chain/gen/genesis/genesis.go index 94badbbfb..6dec3fea6 100644 --- a/chain/gen/genesis/genesis.go +++ b/chain/gen/genesis/genesis.go @@ -313,11 +313,10 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge totalFilAllocated := big.Zero() - // flush as ForEach works on the HAMT - if _, err := state.Flush(ctx); err != nil { - return nil, nil, err - } err = state.ForEach(func(addr address.Address, act *types.Actor) error { + if act.Balance.Nil() { + panic(fmt.Sprintf("actor %s (%s) has nil balance", addr, builtin.ActorNameByCode(act.Code))) + } totalFilAllocated = big.Add(totalFilAllocated, act.Balance) return nil }) diff --git a/chain/state/statetree.go b/chain/state/statetree.go index 40955c48b..dbf150ecd 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -504,6 +504,26 @@ func (st *StateTree) MutateActor(addr address.Address, f func(*types.Actor) erro } func (st *StateTree) ForEach(f func(address.Address, *types.Actor) error) error { + // Walk through layers, if any. + seen := make(map[address.Address]struct{}) + for i := len(st.snaps.layers) - 1; i >= 0; i-- { + for addr, op := range st.snaps.layers[i].actors { + if _, ok := seen[addr]; ok { + continue + } + seen[addr] = struct{}{} + if op.Delete { + continue + } + act := op.Act // copy + if err := f(addr, &act); err != nil { + return err + } + } + + } + + // Now walk through the saved actors. var act types.Actor return st.root.ForEach(&act, func(k string) error { act := act // copy @@ -512,6 +532,12 @@ func (st *StateTree) ForEach(f func(address.Address, *types.Actor) error) error return xerrors.Errorf("invalid address (%x) found in state tree key: %w", []byte(k), err) } + // no need to record anything here, there are no duplicates in the actors HAMT + // iself. + if _, ok := seen[addr]; ok { + return nil + } + return f(addr, &act) }) } diff --git a/chain/store/store.go b/chain/store/store.go index 71fa0397a..523726863 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -11,6 +11,7 @@ import ( "strconv" "strings" "sync" + "time" "github.com/filecoin-project/lotus/chain/state" @@ -404,7 +405,20 @@ func (cs *ChainStore) PutTipSet(ctx context.Context, ts *types.TipSet) error { // 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 { - cs.heaviestLk.Lock() + for { + cs.heaviestLk.Lock() + if len(cs.reorgCh) < reorgChBuf/2 { + break + } + cs.heaviestLk.Unlock() + log.Errorf("reorg channel is heavily backlogged, waiting a bit before trying to take process new tipsets") + select { + case <-time.After(time.Second / 2): + case <-ctx.Done(): + return ctx.Err() + } + } + defer cs.heaviestLk.Unlock() w, err := cs.Weight(ctx, ts) if err != nil { @@ -529,8 +543,10 @@ type reorg struct { new *types.TipSet } +const reorgChBuf = 32 + func (cs *ChainStore) reorgWorker(ctx context.Context, initialNotifees []ReorgNotifee) chan<- reorg { - out := make(chan reorg, 32) + out := make(chan reorg, reorgChBuf) notifees := make([]ReorgNotifee, len(initialNotifees)) copy(notifees, initialNotifees) diff --git a/chain/sync_test.go b/chain/sync_test.go index 9f89f789b..ae4b6cc2e 100644 --- a/chain/sync_test.go +++ b/chain/sync_test.go @@ -85,6 +85,7 @@ type syncTestUtil struct { blocks []*store.FullTipSet nds []api.FullNode + us stmgr.UpgradeSchedule } func prepSyncTest(t testing.TB, h int) *syncTestUtil { @@ -104,9 +105,10 @@ func prepSyncTest(t testing.TB, h int) *syncTestUtil { mn: mocknet.New(ctx), g: g, + us: stmgr.DefaultUpgradeSchedule(), } - tu.addSourceNode(stmgr.DefaultUpgradeSchedule(), h) + tu.addSourceNode(h) //tu.checkHeight("source", source, h) @@ -119,7 +121,7 @@ func prepSyncTest(t testing.TB, h int) *syncTestUtil { func prepSyncTestWithV5Height(t testing.TB, h int, v5height abi.ChainEpoch) *syncTestUtil { logging.SetLogLevel("*", "INFO") - us := stmgr.UpgradeSchedule{{ + sched := stmgr.UpgradeSchedule{{ // prepare for upgrade. Network: network.Version9, Height: 1, @@ -138,7 +140,7 @@ func prepSyncTestWithV5Height(t testing.TB, h int, v5height abi.ChainEpoch) *syn Migration: stmgr.UpgradeActorsV5, }} - g, err := gen.NewGeneratorWithUpgradeSchedule(us) + g, err := gen.NewGeneratorWithUpgradeSchedule(sched) if err != nil { t.Fatalf("%+v", err) @@ -153,9 +155,10 @@ func prepSyncTestWithV5Height(t testing.TB, h int, v5height abi.ChainEpoch) *syn mn: mocknet.New(ctx), g: g, + us: sched, } - tu.addSourceNode(us, h) + tu.addSourceNode(h) //tu.checkHeight("source", source, h) // separate logs @@ -266,7 +269,7 @@ func (tu *syncTestUtil) mineNewBlock(src int, miners []int) { tu.g.CurTipset = mts } -func (tu *syncTestUtil) addSourceNode(us stmgr.UpgradeSchedule, gen int) { +func (tu *syncTestUtil) addSourceNode(gen int) { if tu.genesis != nil { tu.t.Fatal("source node already exists") } @@ -282,7 +285,7 @@ func (tu *syncTestUtil) addSourceNode(us stmgr.UpgradeSchedule, gen int) { node.Test(), node.Override(new(modules.Genesis), modules.LoadGenesis(genesis)), - node.Override(new(stmgr.UpgradeSchedule), us), + node.Override(new(stmgr.UpgradeSchedule), tu.us), ) require.NoError(tu.t, err) tu.t.Cleanup(func() { _ = stop(context.Background()) }) @@ -315,6 +318,7 @@ func (tu *syncTestUtil) addClientNode() int { node.Test(), node.Override(new(modules.Genesis), modules.LoadGenesis(tu.genesis)), + node.Override(new(stmgr.UpgradeSchedule), tu.us), ) require.NoError(tu.t, err) tu.t.Cleanup(func() { _ = stop(context.Background()) }) @@ -1032,18 +1036,24 @@ func TestDrandNull(t *testing.T) { build.UpgradeHyperdriveHeight = v5h tu := prepSyncTestWithV5Height(t, H, v5h) + p0 := tu.addClientNode() + p1 := tu.addClientNode() + + tu.loadChainToNode(p0) + tu.loadChainToNode(p1) + entropy := []byte{0, 2, 3, 4} // arbitrarily chosen pers := crypto.DomainSeparationTag_WinningPoStChallengeSeed beforeNull := tu.g.CurTipset - afterNull := tu.mineOnBlock(beforeNull, 0, nil, false, false, nil, 2) + afterNull := tu.mineOnBlock(beforeNull, p0, nil, false, false, nil, 2) nullHeight := beforeNull.TipSet().Height() + 1 if afterNull.TipSet().Height() == nullHeight { t.Fatal("didn't inject nulls as expected") } - rand, err := tu.nds[0].ChainGetRandomnessFromBeacon(tu.ctx, afterNull.TipSet().Key(), pers, nullHeight, entropy) + rand, err := tu.nds[p0].ChainGetRandomnessFromBeacon(tu.ctx, afterNull.TipSet().Key(), pers, nullHeight, entropy) require.NoError(t, err) // calculate the expected randomness based on the beacon BEFORE the null @@ -1054,20 +1064,20 @@ func TestDrandNull(t *testing.T) { require.Equal(t, []byte(rand), expectedRand) // zoom zoom to past the v5 upgrade by injecting many many nulls - postUpgrade := tu.mineOnBlock(afterNull, 0, nil, false, false, nil, v5h) - nv, err := tu.nds[0].StateNetworkVersion(tu.ctx, types.EmptyTSK) + postUpgrade := tu.mineOnBlock(afterNull, p0, nil, false, false, nil, v5h) + nv, err := tu.nds[p0].StateNetworkVersion(tu.ctx, postUpgrade.TipSet().Key()) require.NoError(t, err) if nv != network.Version13 { t.Fatal("expect to be v13 by now") } - afterNull = tu.mineOnBlock(postUpgrade, 0, nil, false, false, nil, 2) + afterNull = tu.mineOnBlock(postUpgrade, p0, nil, false, false, nil, 2) nullHeight = postUpgrade.TipSet().Height() + 1 if afterNull.TipSet().Height() == nullHeight { t.Fatal("didn't inject nulls as expected") } - rand, err = tu.nds[0].ChainGetRandomnessFromBeacon(tu.ctx, afterNull.TipSet().Key(), pers, nullHeight, entropy) + rand0, err := tu.nds[p0].ChainGetRandomnessFromBeacon(tu.ctx, afterNull.TipSet().Key(), pers, nullHeight, entropy) require.NoError(t, err) // calculate the expected randomness based on the beacon AFTER the null @@ -1075,7 +1085,21 @@ func TestDrandNull(t *testing.T) { expectedRand, err = store.DrawRandomness(expectedBE[len(expectedBE)-1].Data, pers, nullHeight, entropy) require.NoError(t, err) - require.Equal(t, []byte(rand), expectedRand) - build.UpgradeHyperdriveHeight = ov5h + require.Equal(t, []byte(rand0), expectedRand) + // Introduce p1 to friendly p0 who has all the blocks + require.NoError(t, tu.mn.LinkAll()) + tu.connect(p0, p1) + tu.waitUntilNodeHasTs(p1, afterNull.TipSet().Key()) + p1Head := tu.getHead(p1) + + // Yes, p1 syncs well to p0's chain + require.Equal(tu.t, p1Head.Key(), afterNull.TipSet().Key()) + + // Yes, p1 sources the same randomness as p0 + rand1, err := tu.nds[p1].ChainGetRandomnessFromBeacon(tu.ctx, afterNull.TipSet().Key(), pers, nullHeight, entropy) + require.NoError(t, err) + require.Equal(t, rand0, rand1) + + build.UpgradeHyperdriveHeight = ov5h } diff --git a/chain/vm/vm.go b/chain/vm/vm.go index c5bfffc7f..5a31187b7 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -439,6 +439,8 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, }, GasCosts: &gasOutputs, Duration: time.Since(start), + ActorErr: aerrors.Newf(exitcode.SysErrOutOfGas, + "message gas limit does not cover on-chain gas costs"), }, nil } @@ -669,6 +671,12 @@ func (vm *VM) Flush(ctx context.Context) (cid.Cid, error) { return root, nil } +// Get the buffered blockstore associated with the VM. This includes any temporary blocks produced +// during this VM's execution. +func (vm *VM) ActorStore(ctx context.Context) adt.Store { + return adt.WrapStore(ctx, vm.cst) +} + func linksForObj(blk block.Block, cb func(cid.Cid)) error { switch blk.Cid().Prefix().Codec { case cid.DagCBOR: diff --git a/chain/wallet/multi.go b/chain/wallet/multi.go index 1fee4f040..a88475c2e 100644 --- a/chain/wallet/multi.go +++ b/chain/wallet/multi.go @@ -4,6 +4,7 @@ import ( "context" "go.uber.org/fx" + "go.uber.org/multierr" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" @@ -56,18 +57,18 @@ func nonNil(wallets ...getif) []api.Wallet { func (m MultiWallet) find(ctx context.Context, address address.Address, wallets ...getif) (api.Wallet, error) { ws := nonNil(wallets...) + var merr error + for _, w := range ws { have, err := w.WalletHas(ctx, address) - if err != nil { - return nil, err - } + merr = multierr.Append(merr, err) - if have { + if err == nil && have { return w, nil } } - return nil, nil + return nil, merr } func (m MultiWallet) WalletNew(ctx context.Context, keyType types.KeyType) (address.Address, error) { diff --git a/cli/client.go b/cli/client.go index 4f2c58dc2..dc925b72f 100644 --- a/cli/client.go +++ b/cli/client.go @@ -308,8 +308,8 @@ var clientDealCmd = &cli.Command{ Description: `Make a deal with a miner. dataCid comes from running 'lotus client import'. miner is the address of the miner you wish to make a deal with. -price is measured in FIL/GB/Epoch. Miners usually don't accept a bid -lower than their advertised ask. You can check a miners listed price +price is measured in FIL/Epoch. Miners usually don't accept a bid +lower than their advertised ask (which is in FIL/GiB/Epoch). You can check a miners listed price with 'lotus client query-ask '. duration is how long the miner should store the data for, in blocks. The minimum value is 518400 (6 months).`, @@ -1759,7 +1759,7 @@ var clientQueryAskCmd = &cli.Command{ return xerrors.Errorf("failed to get peerID for miner: %w", err) } - if *mi.PeerId == peer.ID("SETME") { + if mi.PeerId == nil || *mi.PeerId == peer.ID("SETME") { return fmt.Errorf("the miner hasn't initialized yet") } diff --git a/cli/multisig.go b/cli/multisig.go index 9ddfcdfc1..c51677d85 100644 --- a/cli/multisig.go +++ b/cli/multisig.go @@ -1428,7 +1428,7 @@ var msigLockCancelCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - if cctx.Args().Len() != 6 { + if cctx.Args().Len() != 5 { return ShowHelp(cctx, fmt.Errorf("must pass multisig address, tx id, start epoch, unlock duration, and amount")) } diff --git a/cmd/lotus-sim/copy.go b/cmd/lotus-sim/copy.go new file mode 100644 index 000000000..5faba69f2 --- /dev/null +++ b/cmd/lotus-sim/copy.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" +) + +var copySimCommand = &cli.Command{ + Name: "copy", + ArgsUsage: "", + Action: func(cctx *cli.Context) (err error) { + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + if cctx.NArg() != 1 { + return fmt.Errorf("expected 1 argument") + } + name := cctx.Args().First() + return node.CopySim(cctx.Context, cctx.String("simulation"), name) + }, +} diff --git a/cmd/lotus-sim/create.go b/cmd/lotus-sim/create.go new file mode 100644 index 000000000..4867a5da5 --- /dev/null +++ b/cmd/lotus-sim/create.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" +) + +var createSimCommand = &cli.Command{ + Name: "create", + ArgsUsage: "[tipset]", + Action: func(cctx *cli.Context) (err error) { + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + + var ts *types.TipSet + switch cctx.NArg() { + case 0: + if err := node.Chainstore.Load(); err != nil { + return err + } + ts = node.Chainstore.GetHeaviestTipSet() + case 1: + cids, err := lcli.ParseTipSetString(cctx.Args().Get(1)) + if err != nil { + return err + } + tsk := types.NewTipSetKey(cids...) + ts, err = node.Chainstore.LoadTipSet(tsk) + if err != nil { + return err + } + default: + return fmt.Errorf("expected 0 or 1 arguments") + } + _, err = node.CreateSim(cctx.Context, cctx.String("simulation"), ts) + return err + }, +} diff --git a/cmd/lotus-sim/delete.go b/cmd/lotus-sim/delete.go new file mode 100644 index 000000000..c19b3d27d --- /dev/null +++ b/cmd/lotus-sim/delete.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/urfave/cli/v2" +) + +var deleteSimCommand = &cli.Command{ + Name: "delete", + Action: func(cctx *cli.Context) (err error) { + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + + return node.DeleteSim(cctx.Context, cctx.String("simulation")) + }, +} diff --git a/cmd/lotus-sim/info.go b/cmd/lotus-sim/info.go new file mode 100644 index 000000000..864adb3bc --- /dev/null +++ b/cmd/lotus-sim/info.go @@ -0,0 +1,110 @@ +package main + +import ( + "context" + "fmt" + "io" + "text/tabwriter" + "time" + + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" +) + +func getTotalPower(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet) (power.Claim, error) { + actor, err := sm.LoadActor(ctx, power.Address, ts) + if err != nil { + return power.Claim{}, err + } + state, err := power.Load(sm.ChainStore().ActorStore(ctx), actor) + if err != nil { + return power.Claim{}, err + } + return state.TotalPower() +} + +func printInfo(ctx context.Context, sim *simulation.Simulation, out io.Writer) error { + head := sim.GetHead() + start := sim.GetStart() + + powerNow, err := getTotalPower(ctx, sim.StateManager, head) + if err != nil { + return err + } + powerLookbackEpoch := head.Height() - builtin.EpochsInDay*2 + if powerLookbackEpoch < start.Height() { + powerLookbackEpoch = start.Height() + } + lookbackTs, err := sim.Node.Chainstore.GetTipsetByHeight(ctx, powerLookbackEpoch, head, false) + if err != nil { + return err + } + powerLookback, err := getTotalPower(ctx, sim.StateManager, lookbackTs) + if err != nil { + return err + } + // growth rate in size/day + growthRate := big.Div( + big.Mul(big.Sub(powerNow.RawBytePower, powerLookback.RawBytePower), + big.NewInt(builtin.EpochsInDay)), + big.NewInt(int64(head.Height()-lookbackTs.Height())), + ) + + tw := tabwriter.NewWriter(out, 8, 8, 1, ' ', 0) + + headEpoch := head.Height() + firstEpoch := start.Height() + 1 + + headTime := time.Unix(int64(head.MinTimestamp()), 0) + startTime := time.Unix(int64(start.MinTimestamp()), 0) + duration := headTime.Sub(startTime) + + fmt.Fprintf(tw, "Name:\t%s\n", sim.Name()) + fmt.Fprintf(tw, "Head:\t%s\n", head) + fmt.Fprintf(tw, "Start Epoch:\t%d\n", firstEpoch) + fmt.Fprintf(tw, "End Epoch:\t%d\n", headEpoch) + fmt.Fprintf(tw, "Length:\t%d\n", headEpoch-firstEpoch) + fmt.Fprintf(tw, "Start Date:\t%s\n", startTime) + fmt.Fprintf(tw, "End Date:\t%s\n", headTime) + fmt.Fprintf(tw, "Duration:\t%.2f day(s)\n", duration.Hours()/24) + fmt.Fprintf(tw, "Capacity:\t%s\n", types.SizeStr(powerNow.RawBytePower)) + fmt.Fprintf(tw, "Daily Capacity Growth:\t%s/day\n", types.SizeStr(growthRate)) + fmt.Fprintf(tw, "Network Version:\t%d\n", sim.GetNetworkVersion()) + return tw.Flush() +} + +var infoSimCommand = &cli.Command{ + Name: "info", + Description: "Output information about the simulation.", + Subcommands: []*cli.Command{ + infoCommitGasSimCommand, + infoMessageSizeSimCommand, + infoWindowPostBandwidthSimCommand, + infoCapacityGrowthSimCommand, + infoStateGrowthSimCommand, + }, + Action: func(cctx *cli.Context) (err error) { + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + return printInfo(cctx.Context, sim, cctx.App.Writer) + }, +} diff --git a/cmd/lotus-sim/info_capacity.go b/cmd/lotus-sim/info_capacity.go new file mode 100644 index 000000000..4372ee34a --- /dev/null +++ b/cmd/lotus-sim/info_capacity.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +) + +var infoCapacityGrowthSimCommand = &cli.Command{ + Name: "capacity-growth", + Description: "List daily capacity growth over the course of the simulation starting at the end.", + Action: func(cctx *cli.Context) (err error) { + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + firstEpoch := sim.GetStart().Height() + ts := sim.GetHead() + lastPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) + if err != nil { + return err + } + lastHeight := ts.Height() + + for ts.Height() > firstEpoch && cctx.Err() == nil { + ts, err = sim.Node.Chainstore.LoadTipSet(ts.Parents()) + if err != nil { + return err + } + newEpoch := ts.Height() + if newEpoch != firstEpoch && newEpoch+builtin.EpochsInDay > lastHeight { + continue + } + + newPower, err := getTotalPower(cctx.Context, sim.StateManager, ts) + if err != nil { + return err + } + + growthRate := big.Div( + big.Mul(big.Sub(lastPower.RawBytePower, newPower.RawBytePower), + big.NewInt(builtin.EpochsInDay)), + big.NewInt(int64(lastHeight-newEpoch)), + ) + lastPower = newPower + lastHeight = newEpoch + fmt.Fprintf(cctx.App.Writer, "%s/day\n", types.SizeStr(growthRate)) + } + return cctx.Err() + }, +} diff --git a/cmd/lotus-sim/info_commit.go b/cmd/lotus-sim/info_commit.go new file mode 100644 index 000000000..738fcde95 --- /dev/null +++ b/cmd/lotus-sim/info_commit.go @@ -0,0 +1,148 @@ +package main + +import ( + "bytes" + "fmt" + "os" + "syscall" + + "github.com/streadway/quantile" + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" + "github.com/filecoin-project/lotus/lib/stati" +) + +var infoCommitGasSimCommand = &cli.Command{ + Name: "commit-gas", + Description: "Output information about the gas for commits", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "lookback", + Value: 0, + }, + }, + Action: func(cctx *cli.Context) (err error) { + log := func(f string, i ...interface{}) { + fmt.Fprintf(os.Stderr, f, i...) + } + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + + go profileOnSignal(cctx, syscall.SIGUSR2) + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + var gasAgg, proofsAgg uint64 + var gasAggMax, proofsAggMax uint64 + var gasSingle, proofsSingle uint64 + + qpoints := []struct{ q, tol float64 }{ + {0.01, 0.0005}, + {0.05, 0.001}, + {0.20, 0.01}, + {0.25, 0.01}, + {0.30, 0.01}, + {0.40, 0.01}, + {0.45, 0.01}, + {0.50, 0.01}, + {0.60, 0.01}, + {0.80, 0.01}, + {0.95, 0.001}, + {0.99, 0.0005}, + } + estims := make([]quantile.Estimate, len(qpoints)) + for i, p := range qpoints { + estims[i] = quantile.Known(p.q, p.tol) + } + qua := quantile.New(estims...) + hist, err := stati.NewHistogram([]float64{ + 1, 3, 5, 7, 15, 30, 50, 100, 200, 400, 600, 700, 819}) + if err != nil { + return err + } + + err = sim.Walk(cctx.Context, cctx.Int64("lookback"), func( + sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage, + ) error { + for _, m := range messages { + if m.ExitCode != exitcode.Ok { + continue + } + if m.Method == miner.Methods.ProveCommitAggregate { + param := miner.ProveCommitAggregateParams{} + err := param.UnmarshalCBOR(bytes.NewReader(m.Params)) + if err != nil { + log("failed to decode params: %+v", err) + return nil + } + c, err := param.SectorNumbers.Count() + if err != nil { + log("failed to count sectors") + return nil + } + gasAgg += uint64(m.GasUsed) + proofsAgg += c + if c == 819 { + gasAggMax += uint64(m.GasUsed) + proofsAggMax += c + } + for i := uint64(0); i < c; i++ { + qua.Add(float64(c)) + } + hist.Observe(float64(c)) + } + + if m.Method == miner.Methods.ProveCommitSector { + gasSingle += uint64(m.GasUsed) + proofsSingle++ + qua.Add(1) + hist.Observe(1) + } + } + + return nil + }) + if err != nil { + return err + } + idealGassUsed := float64(gasAggMax) / float64(proofsAggMax) * float64(proofsAgg+proofsSingle) + + fmt.Printf("Gas usage efficiency in comparison to all 819: %f%%\n", 100*idealGassUsed/float64(gasAgg+gasSingle)) + + fmt.Printf("Proofs in singles: %d\n", proofsSingle) + fmt.Printf("Proofs in Aggs: %d\n", proofsAgg) + fmt.Printf("Proofs in Aggs(819): %d\n", proofsAggMax) + + fmt.Println() + fmt.Println("Quantiles of proofs in given aggregate size:") + for _, p := range qpoints { + fmt.Printf("%.0f%%\t%.0f\n", p.q*100, qua.Get(p.q)) + } + fmt.Println() + fmt.Println("Histogram of messages:") + fmt.Printf("Total\t%d\n", hist.Total()) + for i, b := range hist.Buckets[1:] { + fmt.Printf("%.0f\t%d\n", b, hist.Get(i)) + } + + return nil + }, +} diff --git a/cmd/lotus-sim/info_message.go b/cmd/lotus-sim/info_message.go new file mode 100644 index 000000000..33c45e728 --- /dev/null +++ b/cmd/lotus-sim/info_message.go @@ -0,0 +1,95 @@ +package main + +import ( + "fmt" + "syscall" + + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" + "github.com/filecoin-project/lotus/lib/stati" + "github.com/ipfs/go-cid" + "github.com/streadway/quantile" + "github.com/urfave/cli/v2" +) + +var infoMessageSizeSimCommand = &cli.Command{ + Name: "message-size", + Description: "Output information about message size distribution", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "lookback", + Value: 0, + }, + }, + Action: func(cctx *cli.Context) (err error) { + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + + go profileOnSignal(cctx, syscall.SIGUSR2) + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + qpoints := []struct{ q, tol float64 }{ + {0.30, 0.01}, + {0.40, 0.01}, + {0.60, 0.01}, + {0.70, 0.01}, + {0.80, 0.01}, + {0.85, 0.01}, + {0.90, 0.01}, + {0.95, 0.001}, + {0.99, 0.0005}, + {0.999, 0.0001}, + } + estims := make([]quantile.Estimate, len(qpoints)) + for i, p := range qpoints { + estims[i] = quantile.Known(p.q, p.tol) + } + qua := quantile.New(estims...) + hist, err := stati.NewHistogram([]float64{ + 1 << 8, 1 << 10, 1 << 11, 1 << 12, 1 << 13, 1 << 14, 1 << 15, 1 << 16, + }) + if err != nil { + return err + } + + err = sim.Walk(cctx.Context, cctx.Int64("lookback"), func( + sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage, + ) error { + for _, m := range messages { + msgSize := float64(m.ChainLength()) + qua.Add(msgSize) + hist.Observe(msgSize) + } + + return nil + }) + if err != nil { + return err + } + fmt.Println("Quantiles of message sizes:") + for _, p := range qpoints { + fmt.Printf("%.1f%%\t%.0f\n", p.q*100, qua.Get(p.q)) + } + fmt.Println() + fmt.Println("Histogram of message sizes:") + fmt.Printf("Total\t%d\n", hist.Total()) + for i, b := range hist.Buckets[1:] { + fmt.Printf("%.0f\t%d\t%.1f%%\n", b, hist.Get(i), 100*hist.GetRatio(i)) + } + + return nil + }, +} diff --git a/cmd/lotus-sim/info_state.go b/cmd/lotus-sim/info_state.go new file mode 100644 index 000000000..5c9541513 --- /dev/null +++ b/cmd/lotus-sim/info_state.go @@ -0,0 +1,141 @@ +package main + +import ( + "bytes" + "context" + "fmt" + "math" + "sync" + "sync/atomic" + + "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +) + +var infoStateGrowthSimCommand = &cli.Command{ + Name: "state-size", + Description: "List daily state size over the course of the simulation starting at the end.", + Action: func(cctx *cli.Context) (err error) { + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + // NOTE: This code is entirely read-bound. + store := node.Chainstore.StateBlockstore() + stateSize := func(ctx context.Context, c cid.Cid) (uint64, error) { + seen := cid.NewSet() + sema := make(chan struct{}, 40) + var lock sync.Mutex + var recSize func(cid.Cid) (uint64, error) + recSize = func(c cid.Cid) (uint64, error) { + // Not a part of the chain state. + if err := ctx.Err(); err != nil { + return 0, err + } + + lock.Lock() + visit := seen.Visit(c) + lock.Unlock() + // Already seen? + if !visit { + return 0, nil + } + + var links []cid.Cid + var totalSize uint64 + if err := store.View(c, func(data []byte) error { + totalSize += uint64(len(data)) + return cbg.ScanForLinks(bytes.NewReader(data), func(c cid.Cid) { + if c.Prefix().Codec != cid.DagCBOR { + return + } + + links = append(links, c) + }) + }); err != nil { + return 0, err + } + + var wg sync.WaitGroup + errCh := make(chan error, 1) + cb := func(c cid.Cid) { + size, err := recSize(c) + if err != nil { + select { + case errCh <- err: + default: + } + return + } + atomic.AddUint64(&totalSize, size) + } + asyncCb := func(c cid.Cid) { + wg.Add(1) + go func() { + defer wg.Done() + defer func() { <-sema }() + cb(c) + }() + } + for _, link := range links { + select { + case sema <- struct{}{}: + asyncCb(link) + default: + cb(link) + } + + } + wg.Wait() + + select { + case err := <-errCh: + return 0, err + default: + } + + return totalSize, nil + } + return recSize(c) + } + + firstEpoch := sim.GetStart().Height() + ts := sim.GetHead() + lastHeight := abi.ChainEpoch(math.MaxInt64) + for ts.Height() > firstEpoch && cctx.Err() == nil { + if ts.Height()+builtin.EpochsInDay <= lastHeight { + lastHeight = ts.Height() + + parentStateSize, err := stateSize(cctx.Context, ts.ParentState()) + if err != nil { + return err + } + + fmt.Fprintf(cctx.App.Writer, "%d: %s\n", ts.Height(), types.SizeStr(types.NewInt(parentStateSize))) + } + + ts, err = sim.Node.Chainstore.LoadTipSet(ts.Parents()) + if err != nil { + return err + } + } + return cctx.Err() + }, +} diff --git a/cmd/lotus-sim/info_wdpost.go b/cmd/lotus-sim/info_wdpost.go new file mode 100644 index 000000000..719a133b1 --- /dev/null +++ b/cmd/lotus-sim/info_wdpost.go @@ -0,0 +1,69 @@ +package main + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-state-types/exitcode" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" +) + +var infoWindowPostBandwidthSimCommand = &cli.Command{ + Name: "post-bandwidth", + Description: "List average chain bandwidth used by window posts for each day of the simulation.", + Action: func(cctx *cli.Context) (err error) { + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + + var postGas, totalGas int64 + printStats := func() { + fmt.Fprintf(cctx.App.Writer, "%.4f%%\n", float64(100*postGas)/float64(totalGas)) + } + idx := 0 + err = sim.Walk(cctx.Context, 0, func( + sm *stmgr.StateManager, ts *types.TipSet, stCid cid.Cid, + messages []*simulation.AppliedMessage, + ) error { + for _, m := range messages { + totalGas += m.GasUsed + if m.ExitCode != exitcode.Ok { + continue + } + if m.Method == miner.Methods.SubmitWindowedPoSt { + postGas += m.GasUsed + } + } + idx++ + idx %= builtin.EpochsInDay + if idx == 0 { + printStats() + postGas = 0 + totalGas = 0 + } + return nil + }) + if idx > 0 { + printStats() + } + return err + }, +} diff --git a/cmd/lotus-sim/list.go b/cmd/lotus-sim/list.go new file mode 100644 index 000000000..37e767b9a --- /dev/null +++ b/cmd/lotus-sim/list.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "text/tabwriter" + + "github.com/urfave/cli/v2" +) + +var listSimCommand = &cli.Command{ + Name: "list", + Action: func(cctx *cli.Context) (err error) { + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + + list, err := node.ListSims(cctx.Context) + if err != nil { + return err + } + tw := tabwriter.NewWriter(cctx.App.Writer, 8, 8, 0, ' ', 0) + for _, name := range list { + sim, err := node.LoadSim(cctx.Context, name) + if err != nil { + return err + } + head := sim.GetHead() + fmt.Fprintf(tw, "%s\t%s\t%s\n", name, head.Height(), head.Key()) + } + return tw.Flush() + }, +} diff --git a/cmd/lotus-sim/main.go b/cmd/lotus-sim/main.go new file mode 100644 index 000000000..e6cd5d993 --- /dev/null +++ b/cmd/lotus-sim/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/urfave/cli/v2" + + logging "github.com/ipfs/go-log/v2" +) + +var root []*cli.Command = []*cli.Command{ + createSimCommand, + deleteSimCommand, + copySimCommand, + renameSimCommand, + listSimCommand, + + runSimCommand, + infoSimCommand, + upgradeCommand, +} + +func main() { + if _, set := os.LookupEnv("GOLOG_LOG_LEVEL"); !set { + _ = logging.SetLogLevel("simulation", "DEBUG") + _ = logging.SetLogLevel("simulation-mock", "DEBUG") + } + app := &cli.App{ + Name: "lotus-sim", + Usage: "A tool to simulate a network.", + Commands: root, + Writer: os.Stdout, + ErrWriter: os.Stderr, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "repo", + EnvVars: []string{"LOTUS_PATH"}, + Hidden: true, + Value: "~/.lotus", + }, + &cli.StringFlag{ + Name: "simulation", + Aliases: []string{"sim"}, + EnvVars: []string{"LOTUS_SIMULATION"}, + Value: "default", + }, + }, + } + + ctx, cancel := signal.NotifyContext(context.Background(), + syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP) + defer cancel() + + if err := app.RunContext(ctx, os.Args); err != nil { + fmt.Fprintf(os.Stderr, "Error: %s\n", err) + os.Exit(1) + return + } +} diff --git a/cmd/lotus-sim/profile.go b/cmd/lotus-sim/profile.go new file mode 100644 index 000000000..63e0ef3bd --- /dev/null +++ b/cmd/lotus-sim/profile.go @@ -0,0 +1,94 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/signal" + "path/filepath" + "runtime/pprof" + "time" + + "github.com/urfave/cli/v2" +) + +func takeProfiles(ctx context.Context) (fname string, _err error) { + dir, err := os.MkdirTemp(".", ".profiles-temp*") + if err != nil { + return "", err + } + + if err := writeProfiles(ctx, dir); err != nil { + _ = os.RemoveAll(dir) + return "", err + } + + fname = fmt.Sprintf("pprof-simulation-%s", time.Now().Format(time.RFC3339)) + if err := os.Rename(dir, fname); err != nil { + _ = os.RemoveAll(dir) + return "", err + } + return fname, nil +} + +func writeProfiles(ctx context.Context, dir string) error { + for _, profile := range pprof.Profiles() { + file, err := os.Create(filepath.Join(dir, profile.Name()+".pprof.gz")) + if err != nil { + return err + } + if err := profile.WriteTo(file, 0); err != nil { + _ = file.Close() + return err + } + if err := file.Close(); err != nil { + return err + } + if err := ctx.Err(); err != nil { + return err + } + } + + file, err := os.Create(filepath.Join(dir, "cpu.pprof.gz")) + if err != nil { + return err + } + + if err := pprof.StartCPUProfile(file); err != nil { + _ = file.Close() + return err + } + select { + case <-time.After(30 * time.Second): + case <-ctx.Done(): + } + pprof.StopCPUProfile() + err = file.Close() + if err := ctx.Err(); err != nil { + return err + } + return err +} + +func profileOnSignal(cctx *cli.Context, signals ...os.Signal) { + ch := make(chan os.Signal, 1) + signal.Notify(ch, signals...) + defer signal.Stop(ch) + + for { + select { + case <-ch: + fname, err := takeProfiles(cctx.Context) + switch err { + case context.Canceled: + return + case nil: + fmt.Fprintf(cctx.App.ErrWriter, "Wrote profile to %q\n", fname) + default: + fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to write profile: %s\n", err) + } + case <-cctx.Done(): + return + } + } +} diff --git a/cmd/lotus-sim/rename.go b/cmd/lotus-sim/rename.go new file mode 100644 index 000000000..c336717c7 --- /dev/null +++ b/cmd/lotus-sim/rename.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" +) + +var renameSimCommand = &cli.Command{ + Name: "rename", + ArgsUsage: "", + Action: func(cctx *cli.Context) (err error) { + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + + if cctx.NArg() != 1 { + return fmt.Errorf("expected 1 argument") + } + name := cctx.Args().First() + return node.RenameSim(cctx.Context, cctx.String("simulation"), name) + }, +} diff --git a/cmd/lotus-sim/run.go b/cmd/lotus-sim/run.go new file mode 100644 index 000000000..a985fdf9e --- /dev/null +++ b/cmd/lotus-sim/run.go @@ -0,0 +1,72 @@ +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/urfave/cli/v2" +) + +var runSimCommand = &cli.Command{ + Name: "run", + Description: `Run the simulation. + +Signals: +- SIGUSR1: Print information about the current simulation (equivalent to 'lotus-sim info'). +- SIGUSR2: Write pprof profiles to ./pprof-simulation-$DATE/`, + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "epochs", + Usage: "Advance the given number of epochs then stop.", + }, + }, + Action: func(cctx *cli.Context) (err error) { + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + + go profileOnSignal(cctx, syscall.SIGUSR2) + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + targetEpochs := cctx.Int("epochs") + + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGUSR1) + defer signal.Stop(ch) + + for i := 0; targetEpochs == 0 || i < targetEpochs; i++ { + ts, err := sim.Step(cctx.Context) + if err != nil { + return err + } + + fmt.Fprintf(cctx.App.Writer, "advanced to %d %s\n", ts.Height(), ts.Key()) + + // Print + select { + case <-ch: + fmt.Fprintln(cctx.App.Writer, "---------------------") + if err := printInfo(cctx.Context, sim, cctx.App.Writer); err != nil { + fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to print info: %s\n", err) + } + fmt.Fprintln(cctx.App.Writer, "---------------------") + case <-cctx.Context.Done(): + return cctx.Err() + default: + } + } + fmt.Fprintln(cctx.App.Writer, "simulation done") + return err + }, +} diff --git a/cmd/lotus-sim/simulation/block.go b/cmd/lotus-sim/simulation/block.go new file mode 100644 index 000000000..93e6a3191 --- /dev/null +++ b/cmd/lotus-sim/simulation/block.go @@ -0,0 +1,93 @@ +package simulation + +import ( + "context" + "crypto/sha256" + "encoding/binary" + "time" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" +) + +const beaconPrefix = "mockbeacon:" + +// nextBeaconEntries returns a fake beacon entries for the next block. +func (sim *Simulation) nextBeaconEntries() []types.BeaconEntry { + parentBeacons := sim.head.Blocks()[0].BeaconEntries + lastBeacon := parentBeacons[len(parentBeacons)-1] + beaconRound := lastBeacon.Round + 1 + + buf := make([]byte, len(beaconPrefix)+8) + copy(buf, beaconPrefix) + binary.BigEndian.PutUint64(buf[len(beaconPrefix):], beaconRound) + beaconRand := sha256.Sum256(buf) + return []types.BeaconEntry{{ + Round: beaconRound, + Data: beaconRand[:], + }} +} + +// nextTicket returns a fake ticket for the next block. +func (sim *Simulation) nextTicket() *types.Ticket { + newProof := sha256.Sum256(sim.head.MinTicket().VRFProof) + return &types.Ticket{ + VRFProof: newProof[:], + } +} + +// makeTipSet generates and executes the next tipset from the given messages. This method: +// +// 1. Stores the given messages in the Chainstore. +// 2. Creates and persists a single block mined by the same miner as the parent. +// 3. Creates a tipset from this block and executes it. +// 4. Returns the resulting tipset. +// +// This method does _not_ mutate local state (although it does add blocks to the datastore). +func (sim *Simulation) makeTipSet(ctx context.Context, messages []*types.Message) (*types.TipSet, error) { + parentTs := sim.head + parentState, parentRec, err := sim.StateManager.TipSetState(ctx, parentTs) + if err != nil { + return nil, xerrors.Errorf("failed to compute parent tipset: %w", err) + } + msgsCid, err := sim.storeMessages(ctx, messages) + if err != nil { + return nil, xerrors.Errorf("failed to store block messages: %w", err) + } + + uts := parentTs.MinTimestamp() + build.BlockDelaySecs + + blks := []*types.BlockHeader{{ + Miner: parentTs.MinTicketBlock().Miner, // keep reusing the same miner. + Ticket: sim.nextTicket(), + BeaconEntries: sim.nextBeaconEntries(), + Parents: parentTs.Cids(), + Height: parentTs.Height() + 1, + ParentStateRoot: parentState, + ParentMessageReceipts: parentRec, + Messages: msgsCid, + ParentBaseFee: abi.NewTokenAmount(0), + Timestamp: uts, + ElectionProof: &types.ElectionProof{WinCount: 1}, + }} + err = sim.Node.Chainstore.PersistBlockHeaders(blks...) + if err != nil { + return nil, xerrors.Errorf("failed to persist block headers: %w", err) + } + newTipSet, err := types.NewTipSet(blks) + if err != nil { + return nil, xerrors.Errorf("failed to create new tipset: %w", err) + } + now := time.Now() + _, _, err = sim.StateManager.TipSetState(ctx, newTipSet) + if err != nil { + return nil, xerrors.Errorf("failed to compute new tipset: %w", err) + } + duration := time.Since(now) + log.Infow("computed tipset", "duration", duration, "height", newTipSet.Height()) + + return newTipSet, nil +} diff --git a/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go new file mode 100644 index 000000000..2ffc0bf14 --- /dev/null +++ b/cmd/lotus-sim/simulation/blockbuilder/blockbuilder.go @@ -0,0 +1,280 @@ +package blockbuilder + +import ( + "context" + "math" + + "go.uber.org/zap" + "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/network" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/account" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" +) + +const ( + // 0.25 is the default, but the number below is from the network. + gasOverestimation = 1.0 / 0.808 + // The number of expected blocks in a tipset. We use this to determine how much gas a tipset + // has. + // 5 per tipset, but we effectively get 4 blocks worth of messages. + expectedBlocks = 4 + // TODO: This will produce invalid blocks but it will accurately model the amount of gas + // we're willing to use per-tipset. + // A more correct approach would be to produce 5 blocks. We can do that later. + targetGas = build.BlockGasTarget * expectedBlocks +) + +type BlockBuilder struct { + ctx context.Context + logger *zap.SugaredLogger + + parentTs *types.TipSet + parentSt *state.StateTree + vm *vm.VM + sm *stmgr.StateManager + + gasTotal int64 + messages []*types.Message +} + +// NewBlockBuilder constructs a new block builder from the parent state. Use this to pack a block +// with messages. +// +// NOTE: The context applies to the life of the block builder itself (but does not need to be canceled). +func NewBlockBuilder(ctx context.Context, logger *zap.SugaredLogger, sm *stmgr.StateManager, parentTs *types.TipSet) (*BlockBuilder, error) { + parentState, _, err := sm.TipSetState(ctx, parentTs) + if err != nil { + return nil, err + } + parentSt, err := sm.StateTree(parentState) + if err != nil { + return nil, err + } + + bb := &BlockBuilder{ + ctx: ctx, + logger: logger.With("epoch", parentTs.Height()+1), + sm: sm, + parentTs: parentTs, + parentSt: parentSt, + } + + // Then we construct a VM to execute messages for gas estimation. + // + // Most parts of this VM are "real" except: + // 1. We don't charge a fee. + // 2. The runtime has "fake" proof logic. + // 3. We don't actually save any of the results. + r := store.NewChainRand(sm.ChainStore(), parentTs.Cids()) + vmopt := &vm.VMOpts{ + StateBase: parentState, + Epoch: parentTs.Height() + 1, + Rand: r, + Bstore: sm.ChainStore().StateBlockstore(), + Syscalls: sm.ChainStore().VMSys(), + CircSupplyCalc: sm.GetVMCirculatingSupply, + NtwkVersion: sm.GetNtwkVersion, + BaseFee: abi.NewTokenAmount(0), + LookbackState: stmgr.LookbackStateGetterForTipset(sm, parentTs), + } + bb.vm, err = vm.NewVM(bb.ctx, vmopt) + if err != nil { + return nil, err + } + return bb, nil +} + +// PushMessages tries to push the specified message into the block. +// +// 1. All messages will be executed in-order. +// 2. Gas computation & nonce selection will be handled internally. +// 3. The base-fee is 0 so the sender does not need funds. +// 4. As usual, the sender must be an account (any account). +// 5. If the message fails to execute, this method will fail. +// +// Returns ErrOutOfGas when out of gas. Check BlockBuilder.GasRemaining and try pushing a cheaper +// message. +func (bb *BlockBuilder) PushMessage(msg *types.Message) (*types.MessageReceipt, error) { + if bb.gasTotal >= targetGas { + return nil, new(ErrOutOfGas) + } + + st := bb.StateTree() + store := bb.ActorStore() + + // Copy the message before we start mutating it. + msgCpy := *msg + msg = &msgCpy + + actor, err := st.GetActor(msg.From) + if err != nil { + return nil, err + } + if !builtin.IsAccountActor(actor.Code) { + return nil, xerrors.Errorf( + "messags may only be sent from account actors, got message from %s (%s)", + msg.From, builtin.ActorNameByCode(actor.Code), + ) + } + msg.Nonce = actor.Nonce + if msg.From.Protocol() == address.ID { + state, err := account.Load(store, actor) + if err != nil { + return nil, err + } + msg.From, err = state.PubkeyAddress() + if err != nil { + return nil, err + } + } + + // TODO: Our gas estimation is broken for payment channels due to horrible hacks in + // gasEstimateGasLimit. + if msg.Value == types.EmptyInt { + msg.Value = abi.NewTokenAmount(0) + } + msg.GasPremium = abi.NewTokenAmount(0) + msg.GasFeeCap = abi.NewTokenAmount(0) + msg.GasLimit = build.BlockGasTarget + + // We manually snapshot so we can revert nonce changes, etc. on failure. + err = st.Snapshot(bb.ctx) + if err != nil { + return nil, xerrors.Errorf("failed to take a snapshot while estimating message gas: %w", err) + } + defer st.ClearSnapshot() + + ret, err := bb.vm.ApplyMessage(bb.ctx, msg) + if err != nil { + _ = st.Revert() + return nil, err + } + if ret.ActorErr != nil { + _ = st.Revert() + return nil, ret.ActorErr + } + + // Sometimes there are bugs. Let's catch them. + if ret.GasUsed == 0 { + _ = st.Revert() + return nil, xerrors.Errorf("used no gas %v -> %v", msg, ret) + } + + // Update the gas limit taking overestimation into account. + msg.GasLimit = int64(math.Ceil(float64(ret.GasUsed) * gasOverestimation)) + + // Did we go over? Yes, revert. + newTotal := bb.gasTotal + msg.GasLimit + if newTotal > targetGas { + _ = st.Revert() + return nil, &ErrOutOfGas{Available: targetGas - bb.gasTotal, Required: msg.GasLimit} + } + bb.gasTotal = newTotal + + bb.messages = append(bb.messages, msg) + return &ret.MessageReceipt, nil +} + +// ActorStore returns the VM's current (pending) blockstore. +func (bb *BlockBuilder) ActorStore() adt.Store { + return bb.vm.ActorStore(bb.ctx) +} + +// StateTree returns the VM's current (pending) state-tree. This includes any changes made by +// successfully pushed messages. +// +// You probably want ParentStateTree +func (bb *BlockBuilder) StateTree() *state.StateTree { + return bb.vm.StateTree().(*state.StateTree) +} + +// ParentStateTree returns the parent state-tree (not the paren't tipset's parent state-tree). +func (bb *BlockBuilder) ParentStateTree() *state.StateTree { + return bb.parentSt +} + +// StateTreeByHeight will return a state-tree up through and including the current in-progress +// epoch. +// +// NOTE: This will return the state after the given epoch, not the parent state for the epoch. +func (bb *BlockBuilder) StateTreeByHeight(epoch abi.ChainEpoch) (*state.StateTree, error) { + now := bb.Height() + if epoch > now { + return nil, xerrors.Errorf( + "cannot load state-tree from future: %d > %d", epoch, bb.Height(), + ) + } else if epoch <= 0 { + return nil, xerrors.Errorf( + "cannot load state-tree: epoch %d <= 0", epoch, + ) + } + + // Manually handle "now" and "previous". + switch epoch { + case now: + return bb.StateTree(), nil + case now - 1: + return bb.ParentStateTree(), nil + } + + // Get the tipset of the block _after_ the target epoch so we can use its parent state. + targetTs, err := bb.sm.ChainStore().GetTipsetByHeight(bb.ctx, epoch+1, bb.parentTs, false) + if err != nil { + return nil, err + } + + return bb.sm.StateTree(targetTs.ParentState()) +} + +// Messages returns all messages currently packed into the next block. +// 1. DO NOT modify the slice, copy it. +// 2. DO NOT retain the slice, copy it. +func (bb *BlockBuilder) Messages() []*types.Message { + return bb.messages +} + +// GasRemaining returns the amount of remaining gas in the next block. +func (bb *BlockBuilder) GasRemaining() int64 { + return targetGas - bb.gasTotal +} + +// ParentTipSet returns the parent tipset. +func (bb *BlockBuilder) ParentTipSet() *types.TipSet { + return bb.parentTs +} + +// Height returns the epoch for the target block. +func (bb *BlockBuilder) Height() abi.ChainEpoch { + return bb.parentTs.Height() + 1 +} + +// NetworkVersion returns the network version for the target block. +func (bb *BlockBuilder) NetworkVersion() network.Version { + return bb.sm.GetNtwkVersion(bb.ctx, bb.Height()) +} + +// StateManager returns the stmgr.StateManager. +func (bb *BlockBuilder) StateManager() *stmgr.StateManager { + return bb.sm +} + +// ActorsVersion returns the actors version for the target block. +func (bb *BlockBuilder) ActorsVersion() actors.Version { + return actors.VersionForNetwork(bb.NetworkVersion()) +} + +func (bb *BlockBuilder) L() *zap.SugaredLogger { + return bb.logger +} diff --git a/cmd/lotus-sim/simulation/blockbuilder/errors.go b/cmd/lotus-sim/simulation/blockbuilder/errors.go new file mode 100644 index 000000000..ddf08ea18 --- /dev/null +++ b/cmd/lotus-sim/simulation/blockbuilder/errors.go @@ -0,0 +1,25 @@ +package blockbuilder + +import ( + "errors" + "fmt" +) + +// ErrOutOfGas is returned from BlockBuilder.PushMessage when the block does not have enough gas to +// fit the given message. +type ErrOutOfGas struct { + Available, Required int64 +} + +func (e *ErrOutOfGas) Error() string { + if e.Available == 0 { + return "out of gas: block full" + } + return fmt.Sprintf("out of gas: %d < %d", e.Required, e.Available) +} + +// IsOutOfGas returns true if the error is an "out of gas" error. +func IsOutOfGas(err error) bool { + var oog *ErrOutOfGas + return errors.As(err, &oog) +} diff --git a/cmd/lotus-sim/simulation/messages.go b/cmd/lotus-sim/simulation/messages.go new file mode 100644 index 000000000..5bed27436 --- /dev/null +++ b/cmd/lotus-sim/simulation/messages.go @@ -0,0 +1,58 @@ +package simulation + +import ( + "context" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/types" +) + +// toArray converts the given set of CIDs to an AMT. This is usually used to pack messages into blocks. +func toArray(store blockadt.Store, cids []cid.Cid) (cid.Cid, error) { + arr := blockadt.MakeEmptyArray(store) + for i, c := range cids { + oc := cbg.CborCid(c) + if err := arr.Set(uint64(i), &oc); err != nil { + return cid.Undef, err + } + } + return arr.Root() +} + +// storeMessages packs a set of messages into a types.MsgMeta and returns the resulting CID. The +// resulting CID is valid for the BlocKHeader's Messages field. +func (sim *Simulation) storeMessages(ctx context.Context, messages []*types.Message) (cid.Cid, error) { + // We store all messages as "bls" messages so they're executed in-order. This ensures + // accurate gas accounting. It also ensures we don't, e.g., try to fund a miner after we + // fail a pre-commit... + var msgCids []cid.Cid + for _, msg := range messages { + c, err := sim.Node.Chainstore.PutMessage(msg) + if err != nil { + return cid.Undef, err + } + msgCids = append(msgCids, c) + } + adtStore := sim.Node.Chainstore.ActorStore(ctx) + blsMsgArr, err := toArray(adtStore, msgCids) + if err != nil { + return cid.Undef, err + } + sekpMsgArr, err := toArray(adtStore, nil) + if err != nil { + return cid.Undef, err + } + + msgsCid, err := adtStore.Put(adtStore.Context(), &types.MsgMeta{ + BlsMessages: blsMsgArr, + SecpkMessages: sekpMsgArr, + }) + if err != nil { + return cid.Undef, err + } + return msgsCid, nil +} diff --git a/cmd/lotus-sim/simulation/mock/mock.go b/cmd/lotus-sim/simulation/mock/mock.go new file mode 100644 index 000000000..38648f758 --- /dev/null +++ b/cmd/lotus-sim/simulation/mock/mock.go @@ -0,0 +1,179 @@ +package mock + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" + tutils "github.com/filecoin-project/specs-actors/v5/support/testing" + + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" +) + +// Ideally, we'd use extern/sector-storage/mock. Unfortunately, those mocks are a bit _too_ accurate +// and would force us to load sector info for window post proofs. + +const ( + mockSealProofPrefix = "valid seal proof:" + mockAggregateSealProofPrefix = "valid aggregate seal proof:" + mockPoStProofPrefix = "valid post proof:" +) + +var log = logging.Logger("simulation-mock") + +// mockVerifier is a simple mock for verifying "fake" proofs. +type mockVerifier struct{} + +var Verifier ffiwrapper.Verifier = mockVerifier{} + +func (mockVerifier) VerifySeal(proof proof5.SealVerifyInfo) (bool, error) { + addr, err := address.NewIDAddress(uint64(proof.Miner)) + if err != nil { + return false, err + } + mockProof, err := MockSealProof(proof.SealProof, addr) + if err != nil { + return false, err + } + if bytes.Equal(proof.Proof, mockProof) { + return true, nil + } + log.Debugw("invalid seal proof", "expected", mockProof, "actual", proof.Proof, "miner", addr) + return false, nil +} + +func (mockVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) { + addr, err := address.NewIDAddress(uint64(aggregate.Miner)) + if err != nil { + return false, err + } + mockProof, err := MockAggregateSealProof(aggregate.SealProof, addr, len(aggregate.Infos)) + if err != nil { + return false, err + } + if bytes.Equal(aggregate.Proof, mockProof) { + return true, nil + } + log.Debugw("invalid aggregate seal proof", + "expected", mockProof, + "actual", aggregate.Proof, + "count", len(aggregate.Infos), + "miner", addr, + ) + return false, nil +} +func (mockVerifier) VerifyWinningPoSt(ctx context.Context, info proof5.WinningPoStVerifyInfo) (bool, error) { + panic("should not be called") +} +func (mockVerifier) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoStVerifyInfo) (bool, error) { + if len(info.Proofs) != 1 { + return false, fmt.Errorf("expected exactly one proof") + } + proof := info.Proofs[0] + addr, err := address.NewIDAddress(uint64(info.Prover)) + if err != nil { + return false, err + } + mockProof, err := MockWindowPoStProof(proof.PoStProof, addr) + if err != nil { + return false, err + } + if bytes.Equal(proof.ProofBytes, mockProof) { + return true, nil + } + + log.Debugw("invalid window post proof", + "expected", mockProof, + "actual", info.Proofs[0], + "miner", addr, + ) + return false, nil +} + +func (mockVerifier) GenerateWinningPoStSectorChallenge(context.Context, abi.RegisteredPoStProof, abi.ActorID, abi.PoStRandomness, uint64) ([]uint64, error) { + panic("should not be called") +} + +// MockSealProof generates a mock "seal" proof tied to the specified proof type and the given miner. +func MockSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address) ([]byte, error) { + plen, err := proofType.ProofSize() + if err != nil { + return nil, err + } + proof := make([]byte, plen) + i := copy(proof, mockSealProofPrefix) + binary.BigEndian.PutUint64(proof[i:], uint64(proofType)) + i += 8 + i += copy(proof[i:], minerAddr.Bytes()) + return proof, nil +} + +// MockAggregateSealProof generates a mock "seal" aggregate proof tied to the specified proof type, +// the given miner, and the number of proven sectors. +func MockAggregateSealProof(proofType abi.RegisteredSealProof, minerAddr address.Address, count int) ([]byte, error) { + proof := make([]byte, aggProofLen(count)) + i := copy(proof, mockAggregateSealProofPrefix) + binary.BigEndian.PutUint64(proof[i:], uint64(proofType)) + i += 8 + binary.BigEndian.PutUint64(proof[i:], uint64(count)) + i += 8 + i += copy(proof[i:], minerAddr.Bytes()) + + return proof, nil +} + +// MockWindowPoStProof generates a mock "window post" proof tied to the specified proof type, and the +// given miner. +func MockWindowPoStProof(proofType abi.RegisteredPoStProof, minerAddr address.Address) ([]byte, error) { + plen, err := proofType.ProofSize() + if err != nil { + return nil, err + } + proof := make([]byte, plen) + i := copy(proof, mockPoStProofPrefix) + i += copy(proof[i:], minerAddr.Bytes()) + return proof, nil +} + +// makeCommR generates a "fake" but valid CommR for a sector. It is unique for the given sector/miner. +func MockCommR(minerAddr address.Address, sno abi.SectorNumber) cid.Cid { + return tutils.MakeCID(fmt.Sprintf("%s:%d", minerAddr, sno), &miner5.SealedCIDPrefix) +} + +// TODO: dedup +func aggProofLen(nproofs int) int { + switch { + case nproofs <= 8: + return 11220 + case nproofs <= 16: + return 14196 + case nproofs <= 32: + return 17172 + case nproofs <= 64: + return 20148 + case nproofs <= 128: + return 23124 + case nproofs <= 256: + return 26100 + case nproofs <= 512: + return 29076 + case nproofs <= 1024: + return 32052 + case nproofs <= 2048: + return 35028 + case nproofs <= 4096: + return 38004 + case nproofs <= 8192: + return 40980 + default: + panic("too many proofs") + } +} diff --git a/cmd/lotus-sim/simulation/node.go b/cmd/lotus-sim/simulation/node.go new file mode 100644 index 000000000..5b8bf2bf9 --- /dev/null +++ b/cmd/lotus-sim/simulation/node.go @@ -0,0 +1,241 @@ +package simulation + +import ( + "context" + "strings" + + "go.uber.org/multierr" + "golang.org/x/xerrors" + + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/query" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/stages" + "github.com/filecoin-project/lotus/node/repo" +) + +// Node represents the local lotus node, or at least the part of it we care about. +type Node struct { + repo repo.LockedRepo + Blockstore blockstore.Blockstore + MetadataDS datastore.Batching + Chainstore *store.ChainStore +} + +// OpenNode opens the local lotus node for writing. This will fail if the node is online. +func OpenNode(ctx context.Context, path string) (*Node, error) { + r, err := repo.NewFS(path) + if err != nil { + return nil, err + } + + return NewNode(ctx, r) +} + +// NewNode constructs a new node from the given repo. +func NewNode(ctx context.Context, r repo.Repo) (nd *Node, _err error) { + lr, err := r.Lock(repo.FullNode) + if err != nil { + return nil, err + } + defer func() { + if _err != nil { + _ = lr.Close() + } + }() + + bs, err := lr.Blockstore(ctx, repo.UniversalBlockstore) + if err != nil { + return nil, err + } + + ds, err := lr.Datastore(ctx, "/metadata") + if err != nil { + return nil, err + } + return &Node{ + repo: lr, + Chainstore: store.NewChainStore(bs, bs, ds, vm.Syscalls(mock.Verifier), nil), + MetadataDS: ds, + Blockstore: bs, + }, err +} + +// Close cleanly close the repo. Please call this on shutdown to make sure everything is flushed. +func (nd *Node) Close() error { + if nd.repo != nil { + return nd.repo.Close() + } + return nil +} + +// LoadSim loads +func (nd *Node) LoadSim(ctx context.Context, name string) (*Simulation, error) { + stages, err := stages.DefaultPipeline() + if err != nil { + return nil, err + } + sim := &Simulation{ + Node: nd, + name: name, + stages: stages, + } + + sim.head, err = sim.loadNamedTipSet("head") + if err != nil { + return nil, err + } + sim.start, err = sim.loadNamedTipSet("start") + if err != nil { + return nil, err + } + + err = sim.loadConfig() + if err != nil { + return nil, xerrors.Errorf("failed to load config for simulation %s: %w", name, err) + } + + us, err := sim.config.upgradeSchedule() + if err != nil { + return nil, xerrors.Errorf("failed to create upgrade schedule for simulation %s: %w", name, err) + } + sim.StateManager, err = stmgr.NewStateManagerWithUpgradeSchedule(nd.Chainstore, us) + if err != nil { + return nil, xerrors.Errorf("failed to create state manager for simulation %s: %w", name, err) + } + return sim, nil +} + +// Create creates a new simulation. +// +// - This will fail if a simulation already exists with the given name. +// - Name must not contain a '/'. +func (nd *Node) CreateSim(ctx context.Context, name string, head *types.TipSet) (*Simulation, error) { + if strings.Contains(name, "/") { + return nil, xerrors.Errorf("simulation name %q cannot contain a '/'", name) + } + stages, err := stages.DefaultPipeline() + if err != nil { + return nil, err + } + sim := &Simulation{ + name: name, + Node: nd, + StateManager: stmgr.NewStateManager(nd.Chainstore), + stages: stages, + } + if has, err := nd.MetadataDS.Has(sim.key("head")); err != nil { + return nil, err + } else if has { + return nil, xerrors.Errorf("simulation named %s already exists", name) + } + + if err := sim.storeNamedTipSet("start", head); err != nil { + return nil, xerrors.Errorf("failed to set simulation start: %w", err) + } + + if err := sim.SetHead(head); err != nil { + return nil, err + } + + return sim, nil +} + +// ListSims lists all simulations. +func (nd *Node) ListSims(ctx context.Context) ([]string, error) { + prefix := simulationPrefix.ChildString("head").String() + items, err := nd.MetadataDS.Query(query.Query{ + Prefix: prefix, + KeysOnly: true, + Orders: []query.Order{query.OrderByKey{}}, + }) + if err != nil { + return nil, xerrors.Errorf("failed to list simulations: %w", err) + } + + defer func() { _ = items.Close() }() + + var names []string + for { + select { + case result, ok := <-items.Next(): + if !ok { + return names, nil + } + if result.Error != nil { + return nil, xerrors.Errorf("failed to retrieve next simulation: %w", result.Error) + } + names = append(names, strings.TrimPrefix(result.Key, prefix+"/")) + case <-ctx.Done(): + return nil, ctx.Err() + } + } +} + +var simFields = []string{"head", "start", "config"} + +// DeleteSim deletes a simulation and all related metadata. +// +// NOTE: This function does not delete associated messages, blocks, or chain state. +func (nd *Node) DeleteSim(ctx context.Context, name string) error { + var err error + for _, field := range simFields { + key := simulationPrefix.ChildString(field).ChildString(name) + err = multierr.Append(err, nd.MetadataDS.Delete(key)) + } + return err +} + +// CopySim copies a simulation. +func (nd *Node) CopySim(ctx context.Context, oldName, newName string) error { + if strings.Contains(newName, "/") { + return xerrors.Errorf("simulation name %q cannot contain a '/'", newName) + } + if strings.Contains(oldName, "/") { + return xerrors.Errorf("simulation name %q cannot contain a '/'", oldName) + } + + values := make(map[string][]byte) + for _, field := range simFields { + key := simulationPrefix.ChildString(field).ChildString(oldName) + value, err := nd.MetadataDS.Get(key) + if err == datastore.ErrNotFound { + continue + } else if err != nil { + return err + } + values[field] = value + } + + if _, ok := values["head"]; !ok { + return xerrors.Errorf("simulation named %s not found", oldName) + } + + for _, field := range simFields { + key := simulationPrefix.ChildString(field).ChildString(newName) + var err error + if value, ok := values[field]; ok { + err = nd.MetadataDS.Put(key, value) + } else { + err = nd.MetadataDS.Delete(key) + } + if err != nil { + return err + } + } + return nil +} + +// RenameSim renames a simulation. +func (nd *Node) RenameSim(ctx context.Context, oldName, newName string) error { + if err := nd.CopySim(ctx, oldName, newName); err != nil { + return err + } + return nd.DeleteSim(ctx, oldName) +} diff --git a/cmd/lotus-sim/simulation/simulation.go b/cmd/lotus-sim/simulation/simulation.go new file mode 100644 index 000000000..d91d30eda --- /dev/null +++ b/cmd/lotus-sim/simulation/simulation.go @@ -0,0 +1,408 @@ +package simulation + +import ( + "context" + "encoding/json" + "runtime" + + "golang.org/x/sync/errgroup" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore" + logging "github.com/ipfs/go-log/v2" + + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/stages" +) + +var log = logging.Logger("simulation") + +// config is the simulation's config, persisted to the local metadata store and loaded on start. +// +// See Simulation.loadConfig and Simulation.saveConfig. +type config struct { + Upgrades map[network.Version]abi.ChainEpoch +} + +// upgradeSchedule constructs an stmgr.StateManager upgrade schedule, overriding any network upgrade +// epochs as specified in the config. +func (c *config) upgradeSchedule() (stmgr.UpgradeSchedule, error) { + upgradeSchedule := stmgr.DefaultUpgradeSchedule() + expected := make(map[network.Version]struct{}, len(c.Upgrades)) + for nv := range c.Upgrades { + expected[nv] = struct{}{} + } + + // Update network upgrade epochs. + newUpgradeSchedule := upgradeSchedule[:0] + for _, upgrade := range upgradeSchedule { + if height, ok := c.Upgrades[upgrade.Network]; ok { + delete(expected, upgrade.Network) + if height < 0 { + continue + } + upgrade.Height = height + } + newUpgradeSchedule = append(newUpgradeSchedule, upgrade) + } + + // Make sure we didn't try to configure an unknown network version. + if len(expected) > 0 { + missing := make([]network.Version, 0, len(expected)) + for nv := range expected { + missing = append(missing, nv) + } + return nil, xerrors.Errorf("unknown network versions %v in config", missing) + } + + // Finally, validate it. This ensures we don't change the order of the upgrade or anything + // like that. + if err := newUpgradeSchedule.Validate(); err != nil { + return nil, err + } + return newUpgradeSchedule, nil +} + +// Simulation specifies a lotus-sim simulation. +type Simulation struct { + Node *Node + StateManager *stmgr.StateManager + + name string + config config + start *types.TipSet + + // head + head *types.TipSet + + stages []stages.Stage +} + +// loadConfig loads a simulation's config from the datastore. This must be called on startup and may +// be called to restore the config from-disk. +func (sim *Simulation) loadConfig() error { + configBytes, err := sim.Node.MetadataDS.Get(sim.key("config")) + if err == nil { + err = json.Unmarshal(configBytes, &sim.config) + } + switch err { + case nil: + case datastore.ErrNotFound: + sim.config = config{} + default: + return xerrors.Errorf("failed to load config: %w", err) + } + return nil +} + +// saveConfig saves the current config to the datastore. This must be called whenever the config is +// changed. +func (sim *Simulation) saveConfig() error { + buf, err := json.Marshal(sim.config) + if err != nil { + return err + } + return sim.Node.MetadataDS.Put(sim.key("config"), buf) +} + +var simulationPrefix = datastore.NewKey("/simulation") + +// key returns the the key in the form /simulation//. For example, +// /simulation/head/default. +func (sim *Simulation) key(subkey string) datastore.Key { + return simulationPrefix.ChildString(subkey).ChildString(sim.name) +} + +// loadNamedTipSet the tipset with the given name (for this simulation) +func (sim *Simulation) loadNamedTipSet(name string) (*types.TipSet, error) { + tskBytes, err := sim.Node.MetadataDS.Get(sim.key(name)) + if err != nil { + return nil, xerrors.Errorf("failed to load tipset %s/%s: %w", sim.name, name, err) + } + tsk, err := types.TipSetKeyFromBytes(tskBytes) + if err != nil { + return nil, xerrors.Errorf("failed to parse tipste %v (%s/%s): %w", tskBytes, sim.name, name, err) + } + ts, err := sim.Node.Chainstore.LoadTipSet(tsk) + if err != nil { + return nil, xerrors.Errorf("failed to load tipset %s (%s/%s): %w", tsk, sim.name, name, err) + } + return ts, nil +} + +// storeNamedTipSet stores the tipset at name (relative to the simulation). +func (sim *Simulation) storeNamedTipSet(name string, ts *types.TipSet) error { + if err := sim.Node.MetadataDS.Put(sim.key(name), ts.Key().Bytes()); err != nil { + return xerrors.Errorf("failed to store tipset (%s/%s): %w", sim.name, name, err) + } + return nil +} + +// GetHead returns the current simulation head. +func (sim *Simulation) GetHead() *types.TipSet { + return sim.head +} + +// GetStart returns simulation's parent tipset. +func (sim *Simulation) GetStart() *types.TipSet { + return sim.start +} + +// GetNetworkVersion returns the current network version for the simulation. +func (sim *Simulation) GetNetworkVersion() network.Version { + return sim.StateManager.GetNtwkVersion(context.TODO(), sim.head.Height()) +} + +// SetHead updates the current head of the simulation and stores it in the metadata store. This is +// called for every Simulation.Step. +func (sim *Simulation) SetHead(head *types.TipSet) error { + if err := sim.storeNamedTipSet("head", head); err != nil { + return err + } + sim.head = head + return nil +} + +// Name returns the simulation's name. +func (sim *Simulation) Name() string { + return sim.name +} + +// SetUpgradeHeight sets the height of the given network version change (and saves the config). +// +// This fails if the specified epoch has already passed or the new upgrade schedule is invalid. +func (sim *Simulation) SetUpgradeHeight(nv network.Version, epoch abi.ChainEpoch) (_err error) { + if epoch <= sim.head.Height() { + return xerrors.Errorf("cannot set upgrade height in the past (%d <= %d)", epoch, sim.head.Height()) + } + + if sim.config.Upgrades == nil { + sim.config.Upgrades = make(map[network.Version]abi.ChainEpoch, 1) + } + + sim.config.Upgrades[nv] = epoch + defer func() { + if _err != nil { + // try to restore the old config on error. + _ = sim.loadConfig() + } + }() + + newUpgradeSchedule, err := sim.config.upgradeSchedule() + if err != nil { + return err + } + sm, err := stmgr.NewStateManagerWithUpgradeSchedule(sim.Node.Chainstore, newUpgradeSchedule) + if err != nil { + return err + } + err = sim.saveConfig() + if err != nil { + return err + } + + sim.StateManager = sm + return nil +} + +// ListUpgrades returns any future network upgrades. +func (sim *Simulation) ListUpgrades() (stmgr.UpgradeSchedule, error) { + upgrades, err := sim.config.upgradeSchedule() + if err != nil { + return nil, err + } + var pending stmgr.UpgradeSchedule + for _, upgrade := range upgrades { + if upgrade.Height < sim.head.Height() { + continue + } + pending = append(pending, upgrade) + } + return pending, nil +} + +type AppliedMessage struct { + types.Message + types.MessageReceipt +} + +// Walk walks the simulation's chain from the current head back to the first tipset. +func (sim *Simulation) Walk( + ctx context.Context, + lookback int64, + cb func(sm *stmgr.StateManager, + ts *types.TipSet, + stCid cid.Cid, + messages []*AppliedMessage) error, +) error { + store := sim.Node.Chainstore.ActorStore(ctx) + minEpoch := sim.start.Height() + if lookback != 0 { + minEpoch = sim.head.Height() - abi.ChainEpoch(lookback) + } + + // Given tha loading messages and receipts can be a little bit slow, we do this in parallel. + // + // 1. We spin up some number of workers. + // 2. We hand tipsets to workers in round-robin order. + // 3. We pull "resolved" tipsets in the same round-robin order. + // 4. We serially call the callback in reverse-chain order. + // + // We have a buffer of size 1 for both resolved tipsets and unresolved tipsets. This should + // ensure that we never block unecessarily. + + type work struct { + ts *types.TipSet + stCid cid.Cid + recCid cid.Cid + } + type result struct { + ts *types.TipSet + stCid cid.Cid + messages []*AppliedMessage + } + + // This is more disk bound than CPU bound, but eh... + workerCount := runtime.NumCPU() * 2 + + workQs := make([]chan *work, workerCount) + resultQs := make([]chan *result, workerCount) + + for i := range workQs { + workQs[i] = make(chan *work, 1) + } + + for i := range resultQs { + resultQs[i] = make(chan *result, 1) + } + + grp, ctx := errgroup.WithContext(ctx) + + // Walk the chain and fire off work items. + grp.Go(func() error { + ts := sim.head + stCid, recCid, err := sim.StateManager.TipSetState(ctx, ts) + if err != nil { + return err + } + i := 0 + for ts.Height() > minEpoch { + if err := ctx.Err(); err != nil { + return ctx.Err() + } + + select { + case workQs[i] <- &work{ts, stCid, recCid}: + case <-ctx.Done(): + return ctx.Err() + } + + stCid = ts.MinTicketBlock().ParentStateRoot + recCid = ts.MinTicketBlock().ParentMessageReceipts + ts, err = sim.Node.Chainstore.LoadTipSet(ts.Parents()) + if err != nil { + return xerrors.Errorf("loading parent: %w", err) + } + i = (i + 1) % workerCount + } + for _, q := range workQs { + close(q) + } + return nil + }) + + // Spin up one worker per queue pair. + for i := 0; i < workerCount; i++ { + workQ := workQs[i] + resultQ := resultQs[i] + grp.Go(func() error { + for { + if err := ctx.Err(); err != nil { + return ctx.Err() + } + + var job *work + var ok bool + select { + case job, ok = <-workQ: + case <-ctx.Done(): + return ctx.Err() + } + + if !ok { + break + } + + msgs, err := sim.Node.Chainstore.MessagesForTipset(job.ts) + if err != nil { + return err + } + + recs, err := blockadt.AsArray(store, job.recCid) + if err != nil { + return xerrors.Errorf("amt load: %w", err) + } + applied := make([]*AppliedMessage, len(msgs)) + var rec types.MessageReceipt + err = recs.ForEach(&rec, func(i int64) error { + applied[i] = &AppliedMessage{ + Message: *msgs[i].VMMessage(), + MessageReceipt: rec, + } + return nil + }) + if err != nil { + return err + } + select { + case resultQ <- &result{ + ts: job.ts, + stCid: job.stCid, + messages: applied, + }: + case <-ctx.Done(): + return ctx.Err() + } + } + close(resultQ) + return nil + }) + } + + // Process results in the same order we enqueued them. + grp.Go(func() error { + qs := resultQs + for len(qs) > 0 { + newQs := qs[:0] + for _, q := range qs { + if err := ctx.Err(); err != nil { + return ctx.Err() + } + select { + case r, ok := <-q: + if !ok { + continue + } + err := cb(sim.StateManager, r.ts, r.stCid, r.messages) + if err != nil { + return err + } + case <-ctx.Done(): + return ctx.Err() + } + newQs = append(newQs, q) + } + qs = newQs + } + return nil + }) + + // Wait for everything to finish. + return grp.Wait() +} diff --git a/cmd/lotus-sim/simulation/stages/actor_iter.go b/cmd/lotus-sim/simulation/stages/actor_iter.go new file mode 100644 index 000000000..b2c14ebdb --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/actor_iter.go @@ -0,0 +1,38 @@ +package stages + +import ( + "math/rand" + + "github.com/filecoin-project/go-address" +) + +// actorIter is a simple persistent iterator that loops over a set of actors. +type actorIter struct { + actors []address.Address + offset int +} + +// shuffle randomly permutes the set of actors. +func (p *actorIter) shuffle() { + rand.Shuffle(len(p.actors), func(i, j int) { + p.actors[i], p.actors[j] = p.actors[j], p.actors[i] + }) +} + +// next returns the next actor's address and advances the iterator. +func (p *actorIter) next() address.Address { + next := p.actors[p.offset] + p.offset++ + p.offset %= len(p.actors) + return next +} + +// add adds a new actor to the iterator. +func (p *actorIter) add(addr address.Address) { + p.actors = append(p.actors, addr) +} + +// len returns the number of actors in the iterator. +func (p *actorIter) len() int { + return len(p.actors) +} diff --git a/cmd/lotus-sim/simulation/stages/commit_queue.go b/cmd/lotus-sim/simulation/stages/commit_queue.go new file mode 100644 index 000000000..d625dedb6 --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/commit_queue.go @@ -0,0 +1,200 @@ +package stages + +import ( + "sort" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" +) + +// pendingCommitTracker tracks pending commits per-miner for a single epoch. +type pendingCommitTracker map[address.Address]minerPendingCommits + +// minerPendingCommits tracks a miner's pending commits during a single epoch (grouped by seal proof type). +type minerPendingCommits map[abi.RegisteredSealProof][]abi.SectorNumber + +// finish marks count sectors of the given proof type as "prove-committed". +func (m minerPendingCommits) finish(proof abi.RegisteredSealProof, count int) { + snos := m[proof] + if len(snos) < count { + panic("not enough sector numbers to finish") + } else if len(snos) == count { + delete(m, proof) + } else { + m[proof] = snos[count:] + } +} + +// empty returns true if there are no pending commits. +func (m minerPendingCommits) empty() bool { + return len(m) == 0 +} + +// count returns the number of pending commits. +func (m minerPendingCommits) count() int { + count := 0 + for _, snos := range m { + count += len(snos) + } + return count +} + +// commitQueue is used to track pending prove-commits. +// +// Miners are processed in round-robin where _all_ commits from a given miner are finished before +// moving on to the next. This is designed to maximize batching. +type commitQueue struct { + minerQueue []address.Address + queue []pendingCommitTracker + offset abi.ChainEpoch +} + +// ready returns the number of prove-commits ready to be proven at the current epoch. Useful for logging. +func (q *commitQueue) ready() int { + if len(q.queue) == 0 { + return 0 + } + count := 0 + for _, pending := range q.queue[0] { + count += pending.count() + } + return count +} + +// nextMiner returns the next miner to be proved and the set of pending prove commits for that +// miner. When some number of sectors have successfully been proven, call "finish" so we don't try +// to prove them again. +func (q *commitQueue) nextMiner() (address.Address, minerPendingCommits, bool) { + if len(q.queue) == 0 { + return address.Undef, nil, false + } + next := q.queue[0] + + // Go through the queue and find the first non-empty batch. + for len(q.minerQueue) > 0 { + addr := q.minerQueue[0] + q.minerQueue = q.minerQueue[1:] + pending := next[addr] + if !pending.empty() { + return addr, pending, true + } + delete(next, addr) + } + + return address.Undef, nil, false +} + +// advanceEpoch will advance to the next epoch. If some sectors were left unproven in the current +// epoch, they will be "prepended" into the next epochs sector set. +func (q *commitQueue) advanceEpoch(epoch abi.ChainEpoch) { + if epoch < q.offset { + panic("cannot roll epoch backwards") + } + // Now we "roll forwards", merging each epoch we advance over with the next. + for len(q.queue) > 1 && q.offset < epoch { + curr := q.queue[0] + q.queue[0] = nil + q.queue = q.queue[1:] + q.offset++ + + next := q.queue[0] + + // Cleanup empty entries. + for addr, pending := range curr { + if pending.empty() { + delete(curr, addr) + } + } + + // If the entire level is actually empty, just skip to the next one. + if len(curr) == 0 { + continue + } + + // Otherwise, merge the next into the current. + for addr, nextPending := range next { + currPending := curr[addr] + if currPending.empty() { + curr[addr] = nextPending + continue + } + for ty, nextSnos := range nextPending { + currSnos := currPending[ty] + if len(currSnos) == 0 { + currPending[ty] = nextSnos + continue + } + currPending[ty] = append(currSnos, nextSnos...) + } + } + // Now replace next with the merged curr. + q.queue[0] = curr + } + q.offset = epoch + if len(q.queue) == 0 { + return + } + + next := q.queue[0] + seenMiners := make(map[address.Address]struct{}, len(q.minerQueue)) + for _, addr := range q.minerQueue { + seenMiners[addr] = struct{}{} + } + + // Find the new miners not already in the queue. + offset := len(q.minerQueue) + for addr, pending := range next { + if pending.empty() { + delete(next, addr) + continue + } + if _, ok := seenMiners[addr]; ok { + continue + } + q.minerQueue = append(q.minerQueue, addr) + } + + // Sort the new miners only. + newMiners := q.minerQueue[offset:] + sort.Slice(newMiners, func(i, j int) bool { + // eh, escape analysis should be fine here... + return string(newMiners[i].Bytes()) < string(newMiners[j].Bytes()) + }) +} + +// enquueProveCommit enqueues prove-commit for the given pre-commit for the given miner. +func (q *commitQueue) enqueueProveCommit(addr address.Address, preCommitEpoch abi.ChainEpoch, info miner.SectorPreCommitInfo) error { + // Compute the epoch at which we can start trying to commit. + preCommitDelay := policy.GetPreCommitChallengeDelay() + minCommitEpoch := preCommitEpoch + preCommitDelay + 1 + + // Figure out the offset in the queue. + i := int(minCommitEpoch - q.offset) + if i < 0 { + i = 0 + } + + // Expand capacity and insert. + if cap(q.queue) <= i { + pc := make([]pendingCommitTracker, i+1, preCommitDelay*2) + copy(pc, q.queue) + q.queue = pc + } else if len(q.queue) <= i { + q.queue = q.queue[:i+1] + } + tracker := q.queue[i] + if tracker == nil { + tracker = make(pendingCommitTracker) + q.queue[i] = tracker + } + minerPending := tracker[addr] + if minerPending == nil { + minerPending = make(minerPendingCommits) + tracker[addr] = minerPending + } + minerPending[info.SealProof] = append(minerPending[info.SealProof], info.SectorNumber) + return nil +} diff --git a/cmd/lotus-sim/simulation/stages/commit_queue_test.go b/cmd/lotus-sim/simulation/stages/commit_queue_test.go new file mode 100644 index 000000000..8ab05250e --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/commit_queue_test.go @@ -0,0 +1,128 @@ +package stages + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" +) + +func TestCommitQueue(t *testing.T) { + var q commitQueue + addr1, err := address.NewIDAddress(1000) + require.NoError(t, err) + proofType := abi.RegisteredSealProof_StackedDrg64GiBV1_1 + require.NoError(t, q.enqueueProveCommit(addr1, 0, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 0, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 0, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 1, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 1, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 2, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 1, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 3, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 3, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 4, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 4, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 5, + })) + require.NoError(t, q.enqueueProveCommit(addr1, 6, miner.SectorPreCommitInfo{ + SealProof: proofType, + SectorNumber: 6, + })) + + epoch := abi.ChainEpoch(0) + q.advanceEpoch(epoch) + _, _, ok := q.nextMiner() + require.False(t, ok) + + epoch += policy.GetPreCommitChallengeDelay() + q.advanceEpoch(epoch) + _, _, ok = q.nextMiner() + require.False(t, ok) + + // 0 : empty + non-empty + epoch++ + q.advanceEpoch(epoch) + addr, sectors, ok := q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.Equal(t, addr, addr1) + sectors.finish(proofType, 1) + require.Equal(t, sectors.count(), 1) + require.EqualValues(t, []abi.SectorNumber{1}, sectors[proofType]) + + // 1 : non-empty + non-empty + epoch++ + q.advanceEpoch(epoch) + addr, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, addr, addr1) + require.Equal(t, sectors.count(), 3) + require.EqualValues(t, []abi.SectorNumber{1, 2, 3}, sectors[proofType]) + sectors.finish(proofType, 3) + require.Equal(t, sectors.count(), 0) + + // 2 : empty + empty + epoch++ + q.advanceEpoch(epoch) + _, _, ok = q.nextMiner() + require.False(t, ok) + + // 3 : empty + non-empty + epoch++ + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 1) + require.EqualValues(t, []abi.SectorNumber{4}, sectors[proofType]) + + // 4 : non-empty + non-empty + epoch++ + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.EqualValues(t, []abi.SectorNumber{4, 5}, sectors[proofType]) + + // 5 : empty + non-empty + epoch++ + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.EqualValues(t, []abi.SectorNumber{4, 5}, sectors[proofType]) + sectors.finish(proofType, 1) + require.EqualValues(t, []abi.SectorNumber{5}, sectors[proofType]) + + // 6 + epoch++ + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.EqualValues(t, []abi.SectorNumber{5, 6}, sectors[proofType]) + + // 8 + epoch += 2 + q.advanceEpoch(epoch) + _, sectors, ok = q.nextMiner() + require.True(t, ok) + require.Equal(t, sectors.count(), 2) + require.EqualValues(t, []abi.SectorNumber{5, 6}, sectors[proofType]) +} diff --git a/cmd/lotus-sim/simulation/stages/funding_stage.go b/cmd/lotus-sim/simulation/stages/funding_stage.go new file mode 100644 index 000000000..f57f85293 --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/funding_stage.go @@ -0,0 +1,318 @@ +package stages + +import ( + "bytes" + "context" + "sort" + "time" + + "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" + "github.com/filecoin-project/go-state-types/exitcode" + + "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" +) + +var ( + TargetFunds = abi.TokenAmount(types.MustParseFIL("1000FIL")) + MinimumFunds = abi.TokenAmount(types.MustParseFIL("100FIL")) +) + +type FundingStage struct { + fundAccount address.Address + taxMin abi.TokenAmount + minFunds, maxFunds abi.TokenAmount +} + +func NewFundingStage() (*FundingStage, error) { + // TODO: make all this configurable. + addr, err := address.NewIDAddress(100) + if err != nil { + return nil, err + } + return &FundingStage{ + fundAccount: addr, + taxMin: abi.TokenAmount(types.MustParseFIL("1000FIL")), + minFunds: abi.TokenAmount(types.MustParseFIL("1000000FIL")), + maxFunds: abi.TokenAmount(types.MustParseFIL("100000000FIL")), + }, nil +} + +func (*FundingStage) Name() string { + return "funding" +} + +func (fs *FundingStage) Fund(bb *blockbuilder.BlockBuilder, target address.Address) error { + return fs.fund(bb, target, 0) +} + +// sendAndFund "packs" the given message, funding the actor if necessary. It: +// +// 1. Tries to send the given message. +// 2. If that fails, it checks to see if the exit code was ErrInsufficientFunds. +// 3. If so, it sends 1K FIL from the "burnt funds actor" (because we need to send it from +// somewhere) and re-tries the message.0 +func (fs *FundingStage) SendAndFund(bb *blockbuilder.BlockBuilder, msg *types.Message) (res *types.MessageReceipt, err error) { + for i := 0; i < 10; i++ { + res, err = bb.PushMessage(msg) + if err == nil { + return res, nil + } + aerr, ok := err.(aerrors.ActorError) + if !ok || aerr.RetCode() != exitcode.ErrInsufficientFunds { + return nil, err + } + + // Ok, insufficient funds. Let's fund this miner and try again. + if err := fs.fund(bb, msg.To, i); err != nil { + if !blockbuilder.IsOutOfGas(err) { + err = xerrors.Errorf("failed to fund %s: %w", msg.To, err) + } + return nil, err + } + } + return res, err +} + +// fund funds the target actor with 'TargetFunds << shift' FIL. The "shift" parameter allows us to +// keep doubling the amount until the intended operation succeeds. +func (fs *FundingStage) fund(bb *blockbuilder.BlockBuilder, target address.Address, shift int) error { + amt := TargetFunds + if shift > 0 { + if shift >= 8 { + shift = 8 // cap + } + amt = big.Lsh(amt, uint(shift)) + } + _, err := bb.PushMessage(&types.Message{ + From: fs.fundAccount, + To: target, + Value: amt, + Method: builtin.MethodSend, + }) + return err +} + +func (fs *FundingStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + st := bb.StateTree() + fundAccActor, err := st.GetActor(fs.fundAccount) + if err != nil { + return err + } + if fs.minFunds.LessThan(fundAccActor.Balance) { + return nil + } + + // Ok, we're going to go fund this thing. + start := time.Now() + + type actor struct { + types.Actor + Address address.Address + } + + var targets []*actor + err = st.ForEach(func(addr address.Address, act *types.Actor) error { + // Don't steal from ourselves! + if addr == fs.fundAccount { + return nil + } + if act.Balance.LessThan(fs.taxMin) { + return nil + } + if !(builtin.IsAccountActor(act.Code) || builtin.IsMultisigActor(act.Code)) { + return nil + } + targets = append(targets, &actor{*act, addr}) + return nil + }) + if err != nil { + return err + } + + balance := fundAccActor.Balance.Copy() + + sort.Slice(targets, func(i, j int) bool { + return targets[i].Balance.GreaterThan(targets[j].Balance) + }) + + store := bb.ActorStore() + epoch := bb.Height() + actorsVersion := bb.ActorsVersion() + + var accounts, multisigs int + defer func() { + if _err != nil { + return + } + bb.L().Infow("finished funding the simulation", + "duration", time.Since(start), + "targets", len(targets), + "epoch", epoch, + "new-balance", types.FIL(balance), + "old-balance", types.FIL(fundAccActor.Balance), + "multisigs", multisigs, + "accounts", accounts, + ) + }() + + for _, actor := range targets { + switch { + case builtin.IsAccountActor(actor.Code): + if _, err := bb.PushMessage(&types.Message{ + From: actor.Address, + To: fs.fundAccount, + Value: actor.Balance, + }); blockbuilder.IsOutOfGas(err) { + return nil + } else if err != nil { + return err + } + accounts++ + case builtin.IsMultisigActor(actor.Code): + msigState, err := multisig.Load(store, &actor.Actor) + if err != nil { + return err + } + + threshold, err := msigState.Threshold() + if err != nil { + return err + } + + if threshold > 16 { + bb.L().Debugw("ignoring multisig with high threshold", + "multisig", actor.Address, + "threshold", threshold, + "max", 16, + ) + continue + } + + locked, err := msigState.LockedBalance(epoch) + if err != nil { + return err + } + + if locked.LessThan(fs.taxMin) { + continue // not worth it. + } + + allSigners, err := msigState.Signers() + if err != nil { + return err + } + signers := make([]address.Address, 0, threshold) + for _, signer := range allSigners { + actor, err := st.GetActor(signer) + if err != nil { + return err + } + if !builtin.IsAccountActor(actor.Code) { + // I am so not dealing with this mess. + continue + } + if uint64(len(signers)) >= threshold { + break + } + } + // Ok, we're not dealing with this one. + if uint64(len(signers)) < threshold { + continue + } + + available := big.Sub(actor.Balance, locked) + + var txnId uint64 + { + msg, err := multisig.Message(actorsVersion, signers[0]).Propose( + actor.Address, fs.fundAccount, available, + builtin.MethodSend, nil, + ) + if err != nil { + return err + } + res, err := bb.PushMessage(msg) + if err != nil { + if blockbuilder.IsOutOfGas(err) { + err = nil + } + return err + } + var ret multisig.ProposeReturn + err = ret.UnmarshalCBOR(bytes.NewReader(res.Return)) + if err != nil { + return err + } + if ret.Applied { + if !ret.Code.IsSuccess() { + bb.L().Errorw("failed to tax multisig", + "multisig", actor.Address, + "exitcode", ret.Code, + ) + } + break + } + txnId = uint64(ret.TxnID) + } + var ret multisig.ProposeReturn + for _, signer := range signers[1:] { + msg, err := multisig.Message(actorsVersion, signer).Approve(actor.Address, txnId, nil) + if err != nil { + return err + } + res, err := bb.PushMessage(msg) + if err != nil { + if blockbuilder.IsOutOfGas(err) { + err = nil + } + return err + } + var ret multisig.ProposeReturn + err = ret.UnmarshalCBOR(bytes.NewReader(res.Return)) + if err != nil { + return err + } + // A bit redundant, but nice. + if ret.Applied { + break + } + + } + if !ret.Applied { + bb.L().Errorw("failed to apply multisig transaction", + "multisig", actor.Address, + "txnid", txnId, + "signers", len(signers), + "threshold", threshold, + ) + continue + } + if !ret.Code.IsSuccess() { + bb.L().Errorw("failed to tax multisig", + "multisig", actor.Address, + "txnid", txnId, + "exitcode", ret.Code, + ) + } else { + multisigs++ + } + default: + panic("impossible case") + } + balance = big.Int{Int: balance.Add(balance.Int, actor.Balance.Int)} + if balance.GreaterThanEqual(fs.maxFunds) { + // There's no need to get greedy. + // Well, really, we're trying to avoid messing with state _too_ much. + return nil + } + } + return nil +} diff --git a/cmd/lotus-sim/simulation/stages/interface.go b/cmd/lotus-sim/simulation/stages/interface.go new file mode 100644 index 000000000..0c40a9b23 --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/interface.go @@ -0,0 +1,27 @@ +package stages + +import ( + "context" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" +) + +// Stage is a stage of the simulation. It's asked to pack messages for every block. +type Stage interface { + Name() string + PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) error +} + +type Funding interface { + SendAndFund(*blockbuilder.BlockBuilder, *types.Message) (*types.MessageReceipt, error) + Fund(*blockbuilder.BlockBuilder, address.Address) error +} + +type Committer interface { + EnqueueProveCommit(addr address.Address, preCommitEpoch abi.ChainEpoch, info miner.SectorPreCommitInfo) error +} diff --git a/cmd/lotus-sim/simulation/stages/pipeline.go b/cmd/lotus-sim/simulation/stages/pipeline.go new file mode 100644 index 000000000..317e5b5a9 --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/pipeline.go @@ -0,0 +1,31 @@ +package stages + +// DefaultPipeline returns the default stage pipeline. This pipeline. +// +// 1. Funds a "funding" actor, if necessary. +// 2. Submits any ready window posts. +// 3. Submits any ready prove commits. +// 4. Submits pre-commits with the remaining gas. +func DefaultPipeline() ([]Stage, error) { + // TODO: make this configurable. E.g., through DI? + // Ideally, we'd also be able to change priority, limit throughput (by limiting gas in the + // block builder, etc. + funding, err := NewFundingStage() + if err != nil { + return nil, err + } + wdpost, err := NewWindowPoStStage() + if err != nil { + return nil, err + } + provecommit, err := NewProveCommitStage(funding) + if err != nil { + return nil, err + } + precommit, err := NewPreCommitStage(funding, provecommit) + if err != nil { + return nil, err + } + + return []Stage{funding, wdpost, provecommit, precommit}, nil +} diff --git a/cmd/lotus-sim/simulation/stages/precommit_stage.go b/cmd/lotus-sim/simulation/stages/precommit_stage.go new file mode 100644 index 000000000..5b9fed09e --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/precommit_stage.go @@ -0,0 +1,347 @@ +package stages + +import ( + "context" + "sort" + "time" + + "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" + "github.com/filecoin-project/go-state-types/network" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" +) + +const ( + minPreCommitBatchSize = 1 + maxPreCommitBatchSize = miner5.PreCommitSectorBatchMaxSize +) + +type PreCommitStage struct { + funding Funding + committer Committer + + // The tiers represent the top 1%, top 10%, and everyone else. When sealing sectors, we seal + // a group of sectors for the top 1%, a group (half that size) for the top 10%, and one + // sector for everyone else. We determine these rates by looking at two power tables. + // TODO Ideally we'd "learn" this distribution from the network. But this is good enough for + // now. + top1, top10, rest actorIter + initialized bool +} + +func NewPreCommitStage(funding Funding, committer Committer) (*PreCommitStage, error) { + return &PreCommitStage{ + funding: funding, + committer: committer, + }, nil +} + +func (*PreCommitStage) Name() string { + return "pre-commit" +} + +// packPreCommits packs pre-commit messages until the block is full. +func (stage *PreCommitStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + if !stage.initialized { + if err := stage.load(ctx, bb); err != nil { + return err + } + } + + var ( + full bool + top1Count, top10Count, restCount int + ) + start := time.Now() + defer func() { + if _err != nil { + return + } + bb.L().Debugw("packed pre commits", + "done", top1Count+top10Count+restCount, + "top1", top1Count, + "top10", top10Count, + "rest", restCount, + "filled-block", full, + "duration", time.Since(start), + ) + }() + + var top1Miners, top10Miners, restMiners int + for i := 0; ; i++ { + var ( + minerAddr address.Address + count *int + ) + + // We pre-commit for the top 1%, 10%, and the of the network 1/3rd of the time each. + // This won't yield the most accurate distribution... but it'll give us a good + // enough distribution. + switch { + case (i%3) <= 0 && top1Miners < stage.top1.len(): + count = &top1Count + minerAddr = stage.top1.next() + top1Miners++ + case (i%3) <= 1 && top10Miners < stage.top10.len(): + count = &top10Count + minerAddr = stage.top10.next() + top10Miners++ + case (i%3) <= 2 && restMiners < stage.rest.len(): + count = &restCount + minerAddr = stage.rest.next() + restMiners++ + default: + // Well, we've run through all miners. + return nil + } + + var ( + added int + err error + ) + added, full, err = stage.packMiner(ctx, bb, minerAddr, maxProveCommitBatchSize) + if err != nil { + return xerrors.Errorf("failed to pack precommits for miner %s: %w", minerAddr, err) + } + *count += added + if full { + return nil + } + } +} + +// packPreCommitsMiner packs count pre-commits for the given miner. +func (stage *PreCommitStage) packMiner( + ctx context.Context, bb *blockbuilder.BlockBuilder, + minerAddr address.Address, count int, +) (int, bool, error) { + log := bb.L().With("miner", minerAddr) + epoch := bb.Height() + nv := bb.NetworkVersion() + + minerActor, err := bb.StateTree().GetActor(minerAddr) + if err != nil { + return 0, false, err + } + minerState, err := miner.Load(bb.ActorStore(), minerActor) + if err != nil { + return 0, false, err + } + + minerInfo, err := minerState.Info() + if err != nil { + return 0, false, err + } + + // Make sure the miner is funded. + minerBalance, err := minerState.AvailableBalance(minerActor.Balance) + if err != nil { + return 0, false, err + } + + if big.Cmp(minerBalance, MinimumFunds) < 0 { + err := stage.funding.Fund(bb, minerAddr) + if err != nil { + if blockbuilder.IsOutOfGas(err) { + return 0, true, nil + } + return 0, false, err + } + } + + // Generate pre-commits. + sealType, err := miner.PreferredSealProofTypeFromWindowPoStType( + nv, minerInfo.WindowPoStProofType, + ) + if err != nil { + return 0, false, err + } + + sectorNos, err := minerState.UnallocatedSectorNumbers(count) + if err != nil { + return 0, false, err + } + + expiration := epoch + policy.GetMaxSectorExpirationExtension() + infos := make([]miner.SectorPreCommitInfo, len(sectorNos)) + for i, sno := range sectorNos { + infos[i] = miner.SectorPreCommitInfo{ + SealProof: sealType, + SectorNumber: sno, + SealedCID: mock.MockCommR(minerAddr, sno), + SealRandEpoch: epoch - 1, + Expiration: expiration, + } + } + + // Commit the pre-commits. + added := 0 + if nv >= network.Version13 { + targetBatchSize := maxPreCommitBatchSize + for targetBatchSize >= minPreCommitBatchSize && len(infos) >= minPreCommitBatchSize { + batch := infos + if len(batch) > targetBatchSize { + batch = batch[:targetBatchSize] + } + params := miner5.PreCommitSectorBatchParams{ + Sectors: batch, + } + enc, err := actors.SerializeParams(¶ms) + if err != nil { + return added, false, err + } + // NOTE: just in-case, sendAndFund will "fund" and re-try for any message + // that fails due to "insufficient funds". + if _, err := stage.funding.SendAndFund(bb, &types.Message{ + To: minerAddr, + From: minerInfo.Worker, + Value: abi.NewTokenAmount(0), + Method: miner.Methods.PreCommitSectorBatch, + Params: enc, + }); blockbuilder.IsOutOfGas(err) { + // try again with a smaller batch. + targetBatchSize /= 2 + continue + } else if aerr, ok := err.(aerrors.ActorError); ok && !aerr.IsFatal() { + // Log the error and move on. No reason to stop. + log.Errorw("failed to pre-commit for unknown reasons", + "error", aerr, + "sectors", batch, + ) + return added, false, nil + } else if err != nil { + return added, false, err + } + + for _, info := range batch { + if err := stage.committer.EnqueueProveCommit(minerAddr, epoch, info); err != nil { + return added, false, err + } + added++ + } + infos = infos[len(batch):] + } + } + for _, info := range infos { + enc, err := actors.SerializeParams(&info) //nolint + if err != nil { + return 0, false, err + } + if _, err := stage.funding.SendAndFund(bb, &types.Message{ + To: minerAddr, + From: minerInfo.Worker, + Value: abi.NewTokenAmount(0), + Method: miner.Methods.PreCommitSector, + Params: enc, + }); blockbuilder.IsOutOfGas(err) { + return added, true, nil + } else if err != nil { + return added, false, err + } + + if err := stage.committer.EnqueueProveCommit(minerAddr, epoch, info); err != nil { + return added, false, err + } + added++ + } + return added, false, nil +} + +func (stage *PreCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + bb.L().Infow("loading miner power for pre-commits") + start := time.Now() + defer func() { + if _err != nil { + return + } + bb.L().Infow("loaded miner power for pre-commits", + "duration", time.Since(start), + "top1", stage.top1.len(), + "top10", stage.top10.len(), + "rest", stage.rest.len(), + ) + }() + + store := bb.ActorStore() + st := bb.ParentStateTree() + powerState, err := loadPower(store, st) + if err != nil { + return xerrors.Errorf("failed to power actor: %w", err) + } + + type onboardingInfo struct { + addr address.Address + sectorCount uint64 + } + var sealList []onboardingInfo + err = powerState.ForEachClaim(func(addr address.Address, claim power.Claim) error { + if claim.RawBytePower.IsZero() { + return nil + } + + minerState, err := loadMiner(store, st, addr) + if err != nil { + return err + } + info, err := minerState.Info() + if err != nil { + return err + } + + sectorCount := sectorsFromClaim(info.SectorSize, claim) + + if sectorCount > 0 { + sealList = append(sealList, onboardingInfo{addr, uint64(sectorCount)}) + } + return nil + }) + if err != nil { + return err + } + + if len(sealList) == 0 { + return xerrors.Errorf("simulation has no miners") + } + + // Now that we have a list of sealing miners, sort them into percentiles. + sort.Slice(sealList, func(i, j int) bool { + return sealList[i].sectorCount < sealList[j].sectorCount + }) + + // reset, just in case. + stage.top1 = actorIter{} + stage.top10 = actorIter{} + stage.rest = actorIter{} + + for i, oi := range sealList { + var dist *actorIter + if i < len(sealList)/100 { + dist = &stage.top1 + } else if i < len(sealList)/10 { + dist = &stage.top10 + } else { + dist = &stage.rest + } + dist.add(oi.addr) + } + + stage.top1.shuffle() + stage.top10.shuffle() + stage.rest.shuffle() + + stage.initialized = true + return nil +} diff --git a/cmd/lotus-sim/simulation/stages/provecommit_stage.go b/cmd/lotus-sim/simulation/stages/provecommit_stage.go new file mode 100644 index 000000000..6cbca7de9 --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/provecommit_stage.go @@ -0,0 +1,372 @@ +package stages + +import ( + "context" + "time" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/go-state-types/network" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + power5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/power" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" +) + +const ( + minProveCommitBatchSize = 4 + maxProveCommitBatchSize = miner5.MaxAggregatedSectors +) + +type ProveCommitStage struct { + funding Funding + // We track the set of pending commits. On simulation load, and when a new pre-commit is + // added to the chain, we put the commit in this queue. advanceEpoch(currentEpoch) should be + // called on this queue at every epoch before using it. + commitQueue commitQueue + initialized bool +} + +func NewProveCommitStage(funding Funding) (*ProveCommitStage, error) { + return &ProveCommitStage{ + funding: funding, + }, nil +} + +func (*ProveCommitStage) Name() string { + return "prove-commit" +} + +func (stage *ProveCommitStage) EnqueueProveCommit( + minerAddr address.Address, preCommitEpoch abi.ChainEpoch, info miner.SectorPreCommitInfo, +) error { + return stage.commitQueue.enqueueProveCommit(minerAddr, preCommitEpoch, info) +} + +// packProveCommits packs all prove-commits for all "ready to be proven" sectors until it fills the +// block or runs out. +func (stage *ProveCommitStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + if !stage.initialized { + if err := stage.load(ctx, bb); err != nil { + return err + } + } + // Roll the commitQueue forward. + stage.commitQueue.advanceEpoch(bb.Height()) + + start := time.Now() + var failed, done, unbatched, count int + defer func() { + if _err != nil { + return + } + remaining := stage.commitQueue.ready() + bb.L().Debugw("packed prove commits", + "remaining", remaining, + "done", done, + "failed", failed, + "unbatched", unbatched, + "miners-processed", count, + "duration", time.Since(start), + ) + }() + + for { + addr, pending, ok := stage.commitQueue.nextMiner() + if !ok { + return nil + } + + res, err := stage.packProveCommitsMiner(ctx, bb, addr, pending) + if err != nil { + return err + } + failed += res.failed + done += res.done + unbatched += res.unbatched + count++ + if res.full { + return nil + } + } +} + +type proveCommitResult struct { + done, failed, unbatched int + full bool +} + +// packProveCommitsMiner enqueues a prove commits from the given miner until it runs out of +// available prove-commits, batching as much as possible. +// +// This function will fund as necessary from the "burnt funds actor" (look, it's convenient). +func (stage *ProveCommitStage) packProveCommitsMiner( + ctx context.Context, bb *blockbuilder.BlockBuilder, minerAddr address.Address, + pending minerPendingCommits, +) (res proveCommitResult, _err error) { + minerActor, err := bb.StateTree().GetActor(minerAddr) + if err != nil { + return res, err + } + minerState, err := miner.Load(bb.ActorStore(), minerActor) + if err != nil { + return res, err + } + info, err := minerState.Info() + if err != nil { + return res, err + } + + log := bb.L().With("miner", minerAddr) + + nv := bb.NetworkVersion() + for sealType, snos := range pending { + if nv >= network.Version13 { + for len(snos) > minProveCommitBatchSize { + batchSize := maxProveCommitBatchSize + if len(snos) < batchSize { + batchSize = len(snos) + } + batch := snos[:batchSize] + + proof, err := mock.MockAggregateSealProof(sealType, minerAddr, batchSize) + if err != nil { + return res, err + } + + params := miner5.ProveCommitAggregateParams{ + SectorNumbers: bitfield.New(), + AggregateProof: proof, + } + for _, sno := range batch { + params.SectorNumbers.Set(uint64(sno)) + } + + enc, err := actors.SerializeParams(¶ms) + if err != nil { + return res, err + } + + if _, err := stage.funding.SendAndFund(bb, &types.Message{ + From: info.Worker, + To: minerAddr, + Value: abi.NewTokenAmount(0), + Method: miner.Methods.ProveCommitAggregate, + Params: enc, + }); err == nil { + res.done += len(batch) + } else if blockbuilder.IsOutOfGas(err) { + res.full = true + return res, nil + } else if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { + // If we get a random error, or a fatal actor error, bail. + return res, err + } else if aerr.RetCode() == exitcode.ErrNotFound || aerr.RetCode() == exitcode.ErrIllegalArgument { + // If we get a "not-found" or illegal argument error, try to + // remove any missing prove-commits and continue. This can + // happen either because: + // + // 1. The pre-commit failed on execution (but not when + // packing). This shouldn't happen, but we might as well + // gracefully handle it. + // 2. The pre-commit has expired. We'd have to be really + // backloged to hit this case, but we might as well handle + // it. + // First, split into "good" and "missing" + good, err := stage.filterProveCommits(ctx, bb, minerAddr, batch) + if err != nil { + log.Errorw("failed to filter prove commits", "error", err) + // fail with the original error. + return res, aerr + } + removed := len(batch) - len(good) + if removed == 0 { + log.Errorw("failed to prove-commit for unknown reasons", + "error", aerr, + "sectors", batch, + ) + res.failed += len(batch) + } else if len(good) == 0 { + log.Errorw("failed to prove commit missing pre-commits", + "error", aerr, + "discarded", removed, + ) + res.failed += len(batch) + } else { + // update the pending sector numbers in-place to remove the expired ones. + snos = snos[removed:] + copy(snos, good) + pending.finish(sealType, removed) + + log.Errorw("failed to prove commit expired/missing pre-commits", + "error", aerr, + "discarded", removed, + "kept", len(good), + ) + res.failed += removed + + // Then try again. + continue + } + } else { + log.Errorw("failed to prove commit sector(s)", + "error", err, + "sectors", batch, + ) + res.failed += len(batch) + } + pending.finish(sealType, len(batch)) + snos = snos[len(batch):] + } + } + for len(snos) > 0 && res.unbatched < power5.MaxMinerProveCommitsPerEpoch { + sno := snos[0] + snos = snos[1:] + + proof, err := mock.MockSealProof(sealType, minerAddr) + if err != nil { + return res, err + } + params := miner.ProveCommitSectorParams{ + SectorNumber: sno, + Proof: proof, + } + enc, err := actors.SerializeParams(¶ms) + if err != nil { + return res, err + } + if _, err := stage.funding.SendAndFund(bb, &types.Message{ + From: info.Worker, + To: minerAddr, + Value: abi.NewTokenAmount(0), + Method: miner.Methods.ProveCommitSector, + Params: enc, + }); err == nil { + res.unbatched++ + res.done++ + } else if blockbuilder.IsOutOfGas(err) { + res.full = true + return res, nil + } else if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { + return res, err + } else { + log.Errorw("failed to prove commit sector(s)", + "error", err, + "sectors", []abi.SectorNumber{sno}, + ) + res.failed++ + } + // mark it as "finished" regardless so we skip it. + pending.finish(sealType, 1) + } + // if we get here, we can't pre-commit anything more. + } + return res, nil +} + +// loadMiner enqueue all pending prove-commits for the given miner. This is called on load to +// populate the commitQueue and should not need to be called later. +// +// It will drop any pre-commits that have already expired. +func (stage *ProveCommitStage) loadMiner(ctx context.Context, bb *blockbuilder.BlockBuilder, addr address.Address) error { + epoch := bb.Height() + av := bb.ActorsVersion() + minerState, err := loadMiner(bb.ActorStore(), bb.ParentStateTree(), addr) + if err != nil { + return err + } + + // Find all pending prove commits and group by proof type. Really, there should never + // (except during upgrades be more than one type. + var total, dropped int + err = minerState.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error { + total++ + msd := policy.GetMaxProveCommitDuration(av, info.Info.SealProof) + if epoch > info.PreCommitEpoch+msd { + dropped++ + return nil + } + return stage.commitQueue.enqueueProveCommit(addr, info.PreCommitEpoch, info.Info) + }) + if err != nil { + return err + } + if dropped > 0 { + bb.L().Warnw("dropped expired pre-commits on load", + "miner", addr, + "total", total, + "expired", dropped, + ) + } + return nil +} + +// filterProveCommits filters out expired and/or missing pre-commits. +func (stage *ProveCommitStage) filterProveCommits( + ctx context.Context, bb *blockbuilder.BlockBuilder, + minerAddr address.Address, snos []abi.SectorNumber, +) ([]abi.SectorNumber, error) { + act, err := bb.StateTree().GetActor(minerAddr) + if err != nil { + return nil, err + } + + minerState, err := miner.Load(bb.ActorStore(), act) + if err != nil { + return nil, err + } + + nextEpoch := bb.Height() + av := bb.ActorsVersion() + + good := make([]abi.SectorNumber, 0, len(snos)) + for _, sno := range snos { + info, err := minerState.GetPrecommittedSector(sno) + if err != nil { + return nil, err + } + if info == nil { + continue + } + msd := policy.GetMaxProveCommitDuration(av, info.Info.SealProof) + if nextEpoch > info.PreCommitEpoch+msd { + continue + } + good = append(good, sno) + } + return good, nil +} + +func (stage *ProveCommitStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) error { + stage.initialized = false // in case something failes while we're doing this. + stage.commitQueue = commitQueue{offset: bb.Height()} + powerState, err := loadPower(bb.ActorStore(), bb.ParentStateTree()) + if err != nil { + return err + } + + err = powerState.ForEachClaim(func(minerAddr address.Address, claim power.Claim) error { + // TODO: If we want to finish pre-commits for "new" miners, we'll need to change + // this. + if claim.RawBytePower.IsZero() { + return nil + } + return stage.loadMiner(ctx, bb, minerAddr) + }) + if err != nil { + return err + } + + stage.initialized = true + return nil +} diff --git a/cmd/lotus-sim/simulation/stages/util.go b/cmd/lotus-sim/simulation/stages/util.go new file mode 100644 index 000000000..97c1e57af --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/util.go @@ -0,0 +1,51 @@ +package stages + +import ( + "context" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" +) + +func loadMiner(store adt.Store, st types.StateTree, addr address.Address) (miner.State, error) { + minerActor, err := st.GetActor(addr) + if err != nil { + return nil, err + } + return miner.Load(store, minerActor) +} + +func loadPower(store adt.Store, st types.StateTree) (power.State, error) { + powerActor, err := st.GetActor(power.Address) + if err != nil { + return nil, err + } + return power.Load(store, powerActor) +} + +// Compute the number of sectors a miner has from their power claim. +func sectorsFromClaim(sectorSize abi.SectorSize, c power.Claim) int64 { + if c.RawBytePower.Int == nil { + return 0 + } + sectorCount := big.Div(c.RawBytePower, big.NewIntUnsigned(uint64(sectorSize))) + if !sectorCount.IsInt64() { + panic("impossible number of sectors") + } + return sectorCount.Int64() +} + +func postChainCommitInfo(ctx context.Context, bb *blockbuilder.BlockBuilder, epoch abi.ChainEpoch) (abi.Randomness, error) { + cs := bb.StateManager().ChainStore() + ts := bb.ParentTipSet() + commitRand, err := cs.GetChainRandomness(ctx, ts.Cids(), crypto.DomainSeparationTag_PoStChainCommit, epoch, nil, true) + return commitRand, err +} diff --git a/cmd/lotus-sim/simulation/stages/windowpost_stage.go b/cmd/lotus-sim/simulation/stages/windowpost_stage.go new file mode 100644 index 000000000..68f8ea179 --- /dev/null +++ b/cmd/lotus-sim/simulation/stages/windowpost_stage.go @@ -0,0 +1,317 @@ +package stages + +import ( + "context" + "math" + "time" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/mock" +) + +type WindowPoStStage struct { + // We track the window post periods per miner and assume that no new miners are ever added. + + // We record all pending window post messages, and the epoch up through which we've + // generated window post messages. + pendingWposts []*types.Message + wpostPeriods [][]address.Address // (epoch % (epochs in a deadline)) -> miner + nextWpostEpoch abi.ChainEpoch +} + +func NewWindowPoStStage() (*WindowPoStStage, error) { + return new(WindowPoStStage), nil +} + +func (*WindowPoStStage) Name() string { + return "window-post" +} + +// packWindowPoSts packs window posts until either the block is full or all healty sectors +// have been proven. It does not recover sectors. +func (stage *WindowPoStStage) PackMessages(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + // Push any new window posts into the queue. + if err := stage.tick(ctx, bb); err != nil { + return err + } + done := 0 + failed := 0 + defer func() { + if _err != nil { + return + } + + bb.L().Debugw("packed window posts", + "done", done, + "failed", failed, + "remaining", len(stage.pendingWposts), + ) + }() + // Then pack as many as we can. + for len(stage.pendingWposts) > 0 { + next := stage.pendingWposts[0] + if _, err := bb.PushMessage(next); err != nil { + if blockbuilder.IsOutOfGas(err) { + return nil + } + if aerr, ok := err.(aerrors.ActorError); !ok || aerr.IsFatal() { + return err + } + bb.L().Errorw("failed to submit windowed post", + "error", err, + "miner", next.To, + ) + failed++ + } else { + done++ + } + + stage.pendingWposts = stage.pendingWposts[1:] + } + stage.pendingWposts = nil + return nil +} + +// stepWindowPoStsMiner enqueues all missing window posts for the current epoch for the given miner. +func (stage *WindowPoStStage) queueMiner( + ctx context.Context, bb *blockbuilder.BlockBuilder, + addr address.Address, minerState miner.State, + commitEpoch abi.ChainEpoch, commitRand abi.Randomness, +) error { + + if active, err := minerState.DeadlineCronActive(); err != nil { + return err + } else if !active { + return nil + } + + minerInfo, err := minerState.Info() + if err != nil { + return err + } + + di, err := minerState.DeadlineInfo(bb.Height()) + if err != nil { + return err + } + di = di.NextNotElapsed() + + dl, err := minerState.LoadDeadline(di.Index) + if err != nil { + return err + } + + provenBf, err := dl.PartitionsPoSted() + if err != nil { + return err + } + proven, err := provenBf.AllMap(math.MaxUint64) + if err != nil { + return err + } + + poStBatchSize, err := policy.GetMaxPoStPartitions(bb.NetworkVersion(), minerInfo.WindowPoStProofType) + if err != nil { + return err + } + + var ( + partitions []miner.PoStPartition + partitionGroups [][]miner.PoStPartition + ) + // Only prove partitions with live sectors. + err = dl.ForEachPartition(func(idx uint64, part miner.Partition) error { + if proven[idx] { + return nil + } + // NOTE: We're mimicing the behavior of wdpost_run.go here. + if len(partitions) > 0 && idx%uint64(poStBatchSize) == 0 { + partitionGroups = append(partitionGroups, partitions) + partitions = nil + + } + live, err := part.LiveSectors() + if err != nil { + return err + } + liveCount, err := live.Count() + if err != nil { + return err + } + faulty, err := part.FaultySectors() + if err != nil { + return err + } + faultyCount, err := faulty.Count() + if err != nil { + return err + } + if liveCount-faultyCount > 0 { + partitions = append(partitions, miner.PoStPartition{Index: idx}) + } + return nil + }) + if err != nil { + return err + } + if len(partitions) > 0 { + partitionGroups = append(partitionGroups, partitions) + partitions = nil + } + + proof, err := mock.MockWindowPoStProof(minerInfo.WindowPoStProofType, addr) + if err != nil { + return err + } + for _, group := range partitionGroups { + params := miner.SubmitWindowedPoStParams{ + Deadline: di.Index, + Partitions: group, + Proofs: []proof5.PoStProof{{ + PoStProof: minerInfo.WindowPoStProofType, + ProofBytes: proof, + }}, + ChainCommitEpoch: commitEpoch, + ChainCommitRand: commitRand, + } + enc, aerr := actors.SerializeParams(¶ms) + if aerr != nil { + return xerrors.Errorf("could not serialize submit window post parameters: %w", aerr) + } + msg := &types.Message{ + To: addr, + From: minerInfo.Worker, + Method: miner.Methods.SubmitWindowedPoSt, + Params: enc, + Value: types.NewInt(0), + } + stage.pendingWposts = append(stage.pendingWposts, msg) + } + return nil +} + +func (stage *WindowPoStStage) load(ctx context.Context, bb *blockbuilder.BlockBuilder) (_err error) { + bb.L().Info("loading window post info") + + start := time.Now() + defer func() { + if _err != nil { + return + } + + bb.L().Infow("loaded window post info", "duration", time.Since(start)) + }() + + // reset + stage.wpostPeriods = make([][]address.Address, miner.WPoStChallengeWindow) + stage.pendingWposts = nil + stage.nextWpostEpoch = bb.Height() + 1 + + st := bb.ParentStateTree() + store := bb.ActorStore() + + powerState, err := loadPower(store, st) + if err != nil { + return err + } + + commitEpoch := bb.ParentTipSet().Height() + commitRand, err := postChainCommitInfo(ctx, bb, commitEpoch) + if err != nil { + return err + } + + return powerState.ForEachClaim(func(minerAddr address.Address, claim power.Claim) error { + // TODO: If we start recovering power, we'll need to change this. + if claim.RawBytePower.IsZero() { + return nil + } + + minerState, err := loadMiner(store, st, minerAddr) + if err != nil { + return err + } + + // Shouldn't be necessary if the miner has power, but we might as well be safe. + if active, err := minerState.DeadlineCronActive(); err != nil { + return err + } else if !active { + return nil + } + + // Record when we need to prove for this miner. + dinfo, err := minerState.DeadlineInfo(bb.Height()) + if err != nil { + return err + } + dinfo = dinfo.NextNotElapsed() + + ppOffset := int(dinfo.PeriodStart % miner.WPoStChallengeWindow) + stage.wpostPeriods[ppOffset] = append(stage.wpostPeriods[ppOffset], minerAddr) + + return stage.queueMiner(ctx, bb, minerAddr, minerState, commitEpoch, commitRand) + }) +} + +func (stage *WindowPoStStage) tick(ctx context.Context, bb *blockbuilder.BlockBuilder) error { + // If this is our first time, load from scratch. + if stage.wpostPeriods == nil { + return stage.load(ctx, bb) + } + + targetHeight := bb.Height() + now := time.Now() + was := len(stage.pendingWposts) + count := 0 + defer func() { + bb.L().Debugw("computed window posts", + "miners", count, + "count", len(stage.pendingWposts)-was, + "duration", time.Since(now), + ) + }() + + st := bb.ParentStateTree() + store := bb.ActorStore() + + // Perform a bit of catch up. This lets us do things like skip blocks at upgrades then catch + // up to make the simulation easier. + for ; stage.nextWpostEpoch <= targetHeight; stage.nextWpostEpoch++ { + if stage.nextWpostEpoch+miner.WPoStChallengeWindow < targetHeight { + bb.L().Warnw("skipping old window post", "deadline-open", stage.nextWpostEpoch) + continue + } + commitEpoch := stage.nextWpostEpoch - 1 + commitRand, err := postChainCommitInfo(ctx, bb, commitEpoch) + if err != nil { + return err + } + + for _, addr := range stage.wpostPeriods[int(stage.nextWpostEpoch%miner.WPoStChallengeWindow)] { + minerState, err := loadMiner(store, st, addr) + if err != nil { + return err + } + + if err := stage.queueMiner(ctx, bb, addr, minerState, commitEpoch, commitRand); err != nil { + return err + } + count++ + } + + } + return nil +} diff --git a/cmd/lotus-sim/simulation/step.go b/cmd/lotus-sim/simulation/step.go new file mode 100644 index 000000000..902f2ad6c --- /dev/null +++ b/cmd/lotus-sim/simulation/step.go @@ -0,0 +1,71 @@ +package simulation + +import ( + "context" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation/blockbuilder" +) + +// Step steps the simulation forward one step. This may move forward by more than one epoch. +func (sim *Simulation) Step(ctx context.Context) (*types.TipSet, error) { + log.Infow("step", "epoch", sim.head.Height()+1) + messages, err := sim.popNextMessages(ctx) + if err != nil { + return nil, xerrors.Errorf("failed to select messages for block: %w", err) + } + head, err := sim.makeTipSet(ctx, messages) + if err != nil { + return nil, xerrors.Errorf("failed to make tipset: %w", err) + } + if err := sim.SetHead(head); err != nil { + return nil, xerrors.Errorf("failed to update head: %w", err) + } + return head, nil +} + +// popNextMessages generates/picks a set of messages to be included in the next block. +// +// - This function is destructive and should only be called once per epoch. +// - This function does not store anything in the repo. +// - This function handles all gas estimation. The returned messages should all fit in a single +// block. +func (sim *Simulation) popNextMessages(ctx context.Context) ([]*types.Message, error) { + parentTs := sim.head + + // First we make sure we don't have an upgrade at this epoch. If we do, we return no + // messages so we can just create an empty block at that epoch. + // + // This isn't what the network does, but it makes things easier. Otherwise, we'd need to run + // migrations before this epoch and I'd rather not deal with that. + nextHeight := parentTs.Height() + 1 + prevVer := sim.StateManager.GetNtwkVersion(ctx, nextHeight-1) + nextVer := sim.StateManager.GetNtwkVersion(ctx, nextHeight) + if nextVer != prevVer { + log.Warnw("packing no messages for version upgrade block", + "old", prevVer, + "new", nextVer, + "epoch", nextHeight, + ) + return nil, nil + } + + bb, err := blockbuilder.NewBlockBuilder( + ctx, log.With("simulation", sim.name), + sim.StateManager, parentTs, + ) + if err != nil { + return nil, err + } + + for _, stage := range sim.stages { + // We're intentionally ignoring the "full" signal so we can try to pack a few more + // messages. + if err := stage.PackMessages(ctx, bb); err != nil && !blockbuilder.IsOutOfGas(err) { + return nil, xerrors.Errorf("when packing messages with %s: %w", stage.Name(), err) + } + } + return bb.Messages(), nil +} diff --git a/cmd/lotus-sim/upgrade.go b/cmd/lotus-sim/upgrade.go new file mode 100644 index 000000000..dfc726d6b --- /dev/null +++ b/cmd/lotus-sim/upgrade.go @@ -0,0 +1,109 @@ +package main + +import ( + "fmt" + "strconv" + "strings" + "text/tabwriter" + + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" +) + +var upgradeCommand = &cli.Command{ + Name: "upgrade", + Description: "Modifies network upgrade heights.", + Subcommands: []*cli.Command{ + upgradeSetCommand, + upgradeList, + }, +} + +var upgradeList = &cli.Command{ + Name: "list", + Description: "Lists all pending upgrades.", + Subcommands: []*cli.Command{ + upgradeSetCommand, + }, + Action: func(cctx *cli.Context) (err error) { + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + upgrades, err := sim.ListUpgrades() + if err != nil { + return err + } + + tw := tabwriter.NewWriter(cctx.App.Writer, 8, 8, 0, ' ', 0) + fmt.Fprintf(tw, "version\theight\tepochs\tmigration\texpensive") + epoch := sim.GetHead().Height() + for _, upgrade := range upgrades { + fmt.Fprintf( + tw, "%d\t%d\t%+d\t%t\t%t", + upgrade.Network, upgrade.Height, upgrade.Height-epoch, + upgrade.Migration != nil, + upgrade.Expensive, + ) + } + return nil + }, +} + +var upgradeSetCommand = &cli.Command{ + Name: "set", + ArgsUsage: " [+]", + Description: "Set a network upgrade height. Prefix with '+' to set it relative to the last epoch.", + Action: func(cctx *cli.Context) (err error) { + args := cctx.Args() + if args.Len() != 2 { + return fmt.Errorf("expected 2 arguments") + } + nvString := args.Get(0) + networkVersion, err := strconv.ParseUint(nvString, 10, 32) + if err != nil { + return fmt.Errorf("failed to parse network version %q: %w", nvString, err) + } + heightString := args.Get(1) + relative := false + if strings.HasPrefix(heightString, "+") { + heightString = heightString[1:] + relative = true + } + height, err := strconv.ParseInt(heightString, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse height version %q: %w", heightString, err) + } + + node, err := open(cctx) + if err != nil { + return err + } + defer func() { + if cerr := node.Close(); err == nil { + err = cerr + } + }() + + sim, err := node.LoadSim(cctx.Context, cctx.String("simulation")) + if err != nil { + return err + } + if relative { + height += int64(sim.GetHead().Height()) + } + return sim.SetUpgradeHeight(network.Version(networkVersion), abi.ChainEpoch(height)) + }, +} diff --git a/cmd/lotus-sim/util.go b/cmd/lotus-sim/util.go new file mode 100644 index 000000000..cd15cca0d --- /dev/null +++ b/cmd/lotus-sim/util.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/lotus/cmd/lotus-sim/simulation" + "github.com/filecoin-project/lotus/lib/ulimit" +) + +func open(cctx *cli.Context) (*simulation.Node, error) { + _, _, err := ulimit.ManageFdLimit() + if err != nil { + fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to raise ulimit: %s\n", err) + } + return simulation.OpenNode(cctx.Context, cctx.String("repo")) +} diff --git a/cmd/lotus-storage-miner/actor_test.go b/cmd/lotus-storage-miner/actor_test.go index 7f36812bc..073a83059 100644 --- a/cmd/lotus-storage-miner/actor_test.go +++ b/cmd/lotus-storage-miner/actor_test.go @@ -10,14 +10,13 @@ import ( "testing" "time" - logging "github.com/ipfs/go-log/v2" + "github.com/filecoin-project/go-state-types/network" "github.com/stretchr/testify/require" "github.com/urfave/cli/v2" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/itests/kit" @@ -32,36 +31,21 @@ func TestWorkerKeyChange(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - _ = logging.SetLogLevel("*", "INFO") - - policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) - policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) - policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) - kit.QuietMiningLogs() blocktime := 1 * time.Millisecond - - clients, miners := kit.MockMinerBuilder(t, - []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1), kit.FullNodeWithLatestActorsAt(-1)}, - kit.OneMiner) - - client1 := clients[0] - client2 := clients[1] - - // Connect the nodes. - addrinfo, err := client1.NetAddrsListen(ctx) - require.NoError(t, err) - err = client2.NetConnect(ctx, addrinfo) - require.NoError(t, err) + client1, client2, miner, ens := kit.EnsembleTwoOne(t, kit.MockProofs(), + kit.ConstructorOpts(kit.InstantaneousNetworkVersion(network.Version13)), + ) + ens.InterconnectAll().BeginMining(blocktime) output := bytes.NewBuffer(nil) run := func(cmd *cli.Command, args ...string) error { app := cli.NewApp() app.Metadata = map[string]interface{}{ "repoType": repo.StorageMiner, - "testnode-full": clients[0], - "testnode-storage": miners[0], + "testnode-full": client1, + "testnode-storage": miner, } app.Writer = output api.RunningNodeType = api.NodeMiner @@ -78,9 +62,6 @@ func TestWorkerKeyChange(t *testing.T) { return cmd.Action(cctx) } - // start mining - kit.ConnectAndStartMining(t, blocktime, miners[0], client1, client2) - newKey, err := client1.WalletNew(ctx, types.KTBLS) require.NoError(t, err) @@ -105,14 +86,8 @@ func TestWorkerKeyChange(t *testing.T) { require.Error(t, run(actorConfirmChangeWorker, "--really-do-it", newKey.String())) output.Reset() - for { - head, err := client1.ChainHead(ctx) - require.NoError(t, err) - if head.Height() >= abi.ChainEpoch(targetEpoch) { - break - } - build.Clock.Sleep(10 * blocktime) - } + client1.WaitTillChain(ctx, kit.HeightAtLeast(abi.ChainEpoch(targetEpoch))) + require.NoError(t, run(actorConfirmChangeWorker, "--really-do-it", newKey.String())) output.Reset() @@ -121,23 +96,8 @@ func TestWorkerKeyChange(t *testing.T) { // Wait for finality (worker key switch). targetHeight := head.Height() + policy.ChainFinality - for { - head, err := client1.ChainHead(ctx) - require.NoError(t, err) - if head.Height() >= targetHeight { - break - } - build.Clock.Sleep(10 * blocktime) - } + client1.WaitTillChain(ctx, kit.HeightAtLeast(targetHeight)) // Make sure the other node can catch up. - for i := 0; i < 20; i++ { - head, err := client2.ChainHead(ctx) - require.NoError(t, err) - if head.Height() >= targetHeight { - return - } - build.Clock.Sleep(10 * blocktime) - } - t.Fatal("failed to reach target epoch on the second miner") + client2.WaitTillChain(ctx, kit.HeightAtLeast(targetHeight)) } diff --git a/cmd/lotus-storage-miner/allinfo_test.go b/cmd/lotus-storage-miner/allinfo_test.go index 9f2dc626e..21ae4c8ee 100644 --- a/cmd/lotus-storage-miner/allinfo_test.go +++ b/cmd/lotus-storage-miner/allinfo_test.go @@ -7,13 +7,9 @@ import ( "time" "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/node/impl" - logging "github.com/ipfs/go-log/v2" "github.com/stretchr/testify/require" "github.com/urfave/cli/v2" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/node/repo" @@ -24,12 +20,6 @@ func TestMinerAllInfo(t *testing.T) { t.Skip("skipping test in short mode") } - _ = logging.SetLogLevel("*", "INFO") - - policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) - policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) - policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) - _test = true kit.QuietMiningLogs() @@ -40,16 +30,15 @@ func TestMinerAllInfo(t *testing.T) { policy.SetPreCommitChallengeDelay(oldDelay) }) - n, sn := kit.Builder(t, kit.OneFull, kit.OneMiner) - client, miner := n[0].FullNode, sn[0] - kit.ConnectAndStartMining(t, time.Second, miner, client.(*impl.FullNodeAPI)) + client, miner, ens := kit.EnsembleMinimal(t) + ens.InterconnectAll().BeginMining(time.Second) run := func(t *testing.T) { app := cli.NewApp() app.Metadata = map[string]interface{}{ "repoType": repo.StorageMiner, - "testnode-full": n[0], - "testnode-storage": sn[0], + "testnode-full": client, + "testnode-storage": miner, } api.RunningNodeType = api.NodeMiner @@ -61,14 +50,9 @@ func TestMinerAllInfo(t *testing.T) { t.Run("pre-info-all", run) dh := kit.NewDealHarness(t, client, miner) - _, _, _ = dh.MakeFullDeal(kit.MakeFullDealParams{ - Ctx: context.Background(), - Rseed: 6, - CarExport: false, - FastRet: false, - StartEpoch: 0, - DoRetrieval: true, - }) + deal, res, inPath := dh.MakeOnlineDeal(context.Background(), kit.MakeFullDealParams{Rseed: 6}) + outPath := dh.PerformRetrieval(context.Background(), deal, res.Root, false) + kit.AssertFilesEqual(t, inPath, outPath) t.Run("post-info-all", run) } diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index 388213666..496f63a08 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -2205,6 +2205,7 @@ Response: "ef8d99a2-6865-4189-8ffa-9fef0f806eee": { "Info": { "Hostname": "host", + "IgnoreResources": false, "Resources": { "MemPhysical": 274877906944, "MemSwap": 128849018880, diff --git a/documentation/en/api-v0-methods-worker.md b/documentation/en/api-v0-methods-worker.md index 925f8934b..c620113f4 100644 --- a/documentation/en/api-v0-methods-worker.md +++ b/documentation/en/api-v0-methods-worker.md @@ -89,6 +89,7 @@ Response: ```json { "Hostname": "string value", + "IgnoreResources": true, "Resources": { "MemPhysical": 42, "MemSwap": 42, diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 8ffee3d1a..a1583d522 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -558,8 +558,8 @@ DESCRIPTION: Make a deal with a miner. dataCid comes from running 'lotus client import'. miner is the address of the miner you wish to make a deal with. -price is measured in FIL/GB/Epoch. Miners usually don't accept a bid -lower than their advertised ask. You can check a miners listed price +price is measured in FIL/Epoch. Miners usually don't accept a bid +lower than their advertised ask (which is in FIL/GiB/Epoch). You can check a miners listed price with 'lotus client query-ask '. duration is how long the miner should store the data for, in blocks. The minimum value is 518400 (6 months). diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index d2e3aa7d6..d60fc680a 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit d2e3aa7d61501d69bed6e898de13d1312b021e62 +Subproject commit d60fc680aa8abeafba698f738fed5b94c9bda33d diff --git a/extern/sector-storage/ffiwrapper/sealer_test.go b/extern/sector-storage/ffiwrapper/sealer_test.go index 7f0dc914f..a6034cc79 100644 --- a/extern/sector-storage/ffiwrapper/sealer_test.go +++ b/extern/sector-storage/ffiwrapper/sealer_test.go @@ -545,6 +545,7 @@ func TestSealAndVerifyAggregate(t *testing.T) { avi.Proof, err = ProofProver.AggregateSealProofs(avi, toAggregate) require.NoError(t, err) + require.Len(t, avi.Proof, 11188) aggDone := time.Now() diff --git a/extern/sector-storage/manager.go b/extern/sector-storage/manager.go index 51558aaad..136c00252 100644 --- a/extern/sector-storage/manager.go +++ b/extern/sector-storage/manager.go @@ -87,6 +87,20 @@ type result struct { err error } +// ResourceFilteringStrategy is an enum indicating the kinds of resource +// filtering strategies that can be configured for workers. +type ResourceFilteringStrategy string + +const ( + // ResourceFilteringHardware specifies that available hardware resources + // should be evaluated when scheduling a task against the worker. + ResourceFilteringHardware = ResourceFilteringStrategy("hardware") + + // ResourceFilteringDisabled disables resource filtering against this + // worker. The scheduler may assign any task to this worker. + ResourceFilteringDisabled = ResourceFilteringStrategy("disabled") +) + type SealerConfig struct { ParallelFetchLimit int @@ -96,6 +110,11 @@ type SealerConfig struct { AllowPreCommit2 bool AllowCommit bool AllowUnseal bool + + // ResourceFiltering instructs the system which resource filtering strategy + // to use when evaluating tasks against this worker. An empty value defaults + // to "hardware". + ResourceFiltering ResourceFilteringStrategy } type StorageAuth http.Header @@ -104,7 +123,6 @@ type WorkerStateStore *statestore.StateStore type ManagerStateStore *statestore.StateStore func New(ctx context.Context, lstor *stores.Local, stor *stores.Remote, ls stores.LocalStorage, si stores.SectorIndex, sc SealerConfig, wss WorkerStateStore, mss ManagerStateStore) (*Manager, error) { - prover, err := ffiwrapper.New(&readonlyProvider{stor: lstor, index: si}) if err != nil { return nil, xerrors.Errorf("creating prover instance: %w", err) @@ -151,9 +169,12 @@ func New(ctx context.Context, lstor *stores.Local, stor *stores.Remote, ls store localTasks = append(localTasks, sealtasks.TTUnseal) } - err = m.AddWorker(ctx, NewLocalWorker(WorkerConfig{ - TaskTypes: localTasks, - }, stor, lstor, si, m, wss)) + wcfg := WorkerConfig{ + IgnoreResourceFiltering: sc.ResourceFiltering == ResourceFilteringDisabled, + TaskTypes: localTasks, + } + worker := NewLocalWorker(wcfg, stor, lstor, si, m, wss) + err = m.AddWorker(ctx, worker) if err != nil { return nil, xerrors.Errorf("adding local worker: %w", err) } diff --git a/extern/sector-storage/sched.go b/extern/sector-storage/sched.go index 61411081a..aabf6f0ce 100644 --- a/extern/sector-storage/sched.go +++ b/extern/sector-storage/sched.go @@ -349,24 +349,24 @@ func (sh *scheduler) trySched() { defer sh.workersLk.RUnlock() windowsLen := len(sh.openWindows) - queuneLen := sh.schedQueue.Len() + queueLen := sh.schedQueue.Len() - log.Debugf("SCHED %d queued; %d open windows", queuneLen, windowsLen) + log.Debugf("SCHED %d queued; %d open windows", queueLen, windowsLen) - if windowsLen == 0 || queuneLen == 0 { + if windowsLen == 0 || queueLen == 0 { // nothing to schedule on return } windows := make([]schedWindow, windowsLen) - acceptableWindows := make([][]int, queuneLen) + acceptableWindows := make([][]int, queueLen) // Step 1 throttle := make(chan struct{}, windowsLen) var wg sync.WaitGroup - wg.Add(queuneLen) - for i := 0; i < queuneLen; i++ { + wg.Add(queueLen) + for i := 0; i < queueLen; i++ { throttle <- struct{}{} go func(sqi int) { @@ -393,7 +393,7 @@ func (sh *scheduler) trySched() { } // TODO: allow bigger windows - if !windows[wnd].allocated.canHandleRequest(needRes, windowRequest.worker, "schedAcceptable", worker.info.Resources) { + if !windows[wnd].allocated.canHandleRequest(needRes, windowRequest.worker, "schedAcceptable", worker.info) { continue } @@ -451,27 +451,27 @@ func (sh *scheduler) trySched() { // Step 2 scheduled := 0 - rmQueue := make([]int, 0, queuneLen) + rmQueue := make([]int, 0, queueLen) - for sqi := 0; sqi < queuneLen; sqi++ { + for sqi := 0; sqi < queueLen; sqi++ { task := (*sh.schedQueue)[sqi] needRes := ResourceTable[task.taskType][task.sector.ProofType] selectedWindow := -1 for _, wnd := range acceptableWindows[task.indexHeap] { wid := sh.openWindows[wnd].worker - wr := sh.workers[wid].info.Resources + info := sh.workers[wid].info log.Debugf("SCHED try assign sqi:%d sector %d to window %d", sqi, task.sector.ID.Number, wnd) // TODO: allow bigger windows - if !windows[wnd].allocated.canHandleRequest(needRes, wid, "schedAssign", wr) { + if !windows[wnd].allocated.canHandleRequest(needRes, wid, "schedAssign", info) { continue } log.Debugf("SCHED ASSIGNED sqi:%d sector %d task %s to window %d", sqi, task.sector.ID.Number, task.taskType, wnd) - windows[wnd].allocated.add(wr, needRes) + windows[wnd].allocated.add(info.Resources, needRes) // TODO: We probably want to re-sort acceptableWindows here based on new // workerHandle.utilization + windows[wnd].allocated.utilization (workerHandle.utilization is used in all // task selectors, but not in the same way, so need to figure out how to do that in a non-O(n^2 way), and diff --git a/extern/sector-storage/sched_resources.go b/extern/sector-storage/sched_resources.go index 3e359c121..96a1fa863 100644 --- a/extern/sector-storage/sched_resources.go +++ b/extern/sector-storage/sched_resources.go @@ -6,7 +6,7 @@ import ( "github.com/filecoin-project/lotus/extern/sector-storage/storiface" ) -func (a *activeResources) withResources(id WorkerID, wr storiface.WorkerResources, r Resources, locker sync.Locker, cb func() error) error { +func (a *activeResources) withResources(id WorkerID, wr storiface.WorkerInfo, r Resources, locker sync.Locker, cb func() error) error { for !a.canHandleRequest(r, id, "withResources", wr) { if a.cond == nil { a.cond = sync.NewCond(locker) @@ -14,11 +14,11 @@ func (a *activeResources) withResources(id WorkerID, wr storiface.WorkerResource a.cond.Wait() } - a.add(wr, r) + a.add(wr.Resources, r) err := cb() - a.free(wr, r) + a.free(wr.Resources, r) if a.cond != nil { a.cond.Broadcast() } @@ -44,8 +44,15 @@ func (a *activeResources) free(wr storiface.WorkerResources, r Resources) { a.memUsedMax -= r.MaxMemory } -func (a *activeResources) canHandleRequest(needRes Resources, wid WorkerID, caller string, res storiface.WorkerResources) bool { +// canHandleRequest evaluates if the worker has enough available resources to +// handle the request. +func (a *activeResources) canHandleRequest(needRes Resources, wid WorkerID, caller string, info storiface.WorkerInfo) bool { + if info.IgnoreResources { + // shortcircuit; if this worker is ignoring resources, it can always handle the request. + return true + } + res := info.Resources // TODO: dedupe needRes.BaseMinMemory per task type (don't add if that task is already running) minNeedMem := res.MemReserved + a.memUsedMin + needRes.MinMemory + needRes.BaseMinMemory if minNeedMem > res.MemPhysical { diff --git a/extern/sector-storage/sched_test.go b/extern/sector-storage/sched_test.go index 63f3de64d..fbc4d83ee 100644 --- a/extern/sector-storage/sched_test.go +++ b/extern/sector-storage/sched_test.go @@ -38,6 +38,20 @@ func TestWithPriority(t *testing.T) { require.Equal(t, 2222, getPriority(ctx)) } +var decentWorkerResources = storiface.WorkerResources{ + MemPhysical: 128 << 30, + MemSwap: 200 << 30, + MemReserved: 2 << 30, + CPUs: 32, + GPUs: []string{"a GPU"}, +} + +var constrainedWorkerResources = storiface.WorkerResources{ + MemPhysical: 1 << 30, + MemReserved: 2 << 30, + CPUs: 1, +} + type schedTestWorker struct { name string taskTypes map[sealtasks.TaskType]struct{} @@ -45,6 +59,9 @@ type schedTestWorker struct { closed bool session uuid.UUID + + resources storiface.WorkerResources + ignoreResources bool } func (s *schedTestWorker) SealPreCommit1(ctx context.Context, sector storage.SectorRef, ticket abi.SealRandomness, pieces []abi.PieceInfo) (storiface.CallID, error) { @@ -107,18 +124,11 @@ func (s *schedTestWorker) Paths(ctx context.Context) ([]stores.StoragePath, erro return s.paths, nil } -var decentWorkerResources = storiface.WorkerResources{ - MemPhysical: 128 << 30, - MemSwap: 200 << 30, - MemReserved: 2 << 30, - CPUs: 32, - GPUs: []string{"a GPU"}, -} - func (s *schedTestWorker) Info(ctx context.Context) (storiface.WorkerInfo, error) { return storiface.WorkerInfo{ - Hostname: s.name, - Resources: decentWorkerResources, + Hostname: s.name, + IgnoreResources: s.ignoreResources, + Resources: s.resources, }, nil } @@ -137,13 +147,16 @@ func (s *schedTestWorker) Close() error { var _ Worker = &schedTestWorker{} -func addTestWorker(t *testing.T, sched *scheduler, index *stores.Index, name string, taskTypes map[sealtasks.TaskType]struct{}) { +func addTestWorker(t *testing.T, sched *scheduler, index *stores.Index, name string, taskTypes map[sealtasks.TaskType]struct{}, resources storiface.WorkerResources, ignoreResources bool) { w := &schedTestWorker{ name: name, taskTypes: taskTypes, paths: []stores.StoragePath{{ID: "bb-8", Weight: 2, LocalPath: "food", CanSeal: true, CanStore: true}}, session: uuid.New(), + + resources: resources, + ignoreResources: ignoreResources, } for _, path := range w.paths { @@ -169,7 +182,7 @@ func TestSchedStartStop(t *testing.T) { sched := newScheduler() go sched.runSched() - addTestWorker(t, sched, stores.NewIndex(), "fred", nil) + addTestWorker(t, sched, stores.NewIndex(), "fred", nil, decentWorkerResources, false) require.NoError(t, sched.Close(context.TODO())) } @@ -183,6 +196,9 @@ func TestSched(t *testing.T) { type workerSpec struct { name string taskTypes map[sealtasks.TaskType]struct{} + + resources storiface.WorkerResources + ignoreResources bool } noopAction := func(ctx context.Context, w Worker) error { @@ -295,7 +311,7 @@ func TestSched(t *testing.T) { go sched.runSched() for _, worker := range workers { - addTestWorker(t, sched, index, worker.name, worker.taskTypes) + addTestWorker(t, sched, index, worker.name, worker.taskTypes, worker.resources, worker.ignoreResources) } rm := runMeta{ @@ -322,31 +338,42 @@ func TestSched(t *testing.T) { } } + // checks behaviour with workers with constrained resources + // the first one is not ignoring resource constraints, so we assign to the second worker, who is + t.Run("constrained-resources", testFunc([]workerSpec{ + {name: "fred1", resources: constrainedWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + {name: "fred2", resources: constrainedWorkerResources, ignoreResources: true, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + }, []task{ + sched("pc1-1", "fred2", 8, sealtasks.TTPreCommit1), + taskStarted("pc1-1"), + taskDone("pc1-1"), + })) + t.Run("one-pc1", testFunc([]workerSpec{ - {name: "fred", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + {name: "fred", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, }, []task{ sched("pc1-1", "fred", 8, sealtasks.TTPreCommit1), taskDone("pc1-1"), })) t.Run("pc1-2workers-1", testFunc([]workerSpec{ - {name: "fred2", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit2: {}}}, - {name: "fred1", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + {name: "fred2", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit2: {}}}, + {name: "fred1", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, }, []task{ sched("pc1-1", "fred1", 8, sealtasks.TTPreCommit1), taskDone("pc1-1"), })) t.Run("pc1-2workers-2", testFunc([]workerSpec{ - {name: "fred1", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, - {name: "fred2", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit2: {}}}, + {name: "fred1", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + {name: "fred2", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit2: {}}}, }, []task{ sched("pc1-1", "fred1", 8, sealtasks.TTPreCommit1), taskDone("pc1-1"), })) t.Run("pc1-block-pc2", testFunc([]workerSpec{ - {name: "fred", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}, sealtasks.TTPreCommit2: {}}}, + {name: "fred", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}, sealtasks.TTPreCommit2: {}}}, }, []task{ sched("pc1", "fred", 8, sealtasks.TTPreCommit1), taskStarted("pc1"), @@ -359,7 +386,7 @@ func TestSched(t *testing.T) { })) t.Run("pc2-block-pc1", testFunc([]workerSpec{ - {name: "fred", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}, sealtasks.TTPreCommit2: {}}}, + {name: "fred", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}, sealtasks.TTPreCommit2: {}}}, }, []task{ sched("pc2", "fred", 8, sealtasks.TTPreCommit2), taskStarted("pc2"), @@ -372,7 +399,7 @@ func TestSched(t *testing.T) { })) t.Run("pc1-batching", testFunc([]workerSpec{ - {name: "fred", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, + {name: "fred", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}}}, }, []task{ sched("t1", "fred", 8, sealtasks.TTPreCommit1), taskStarted("t1"), @@ -459,7 +486,7 @@ func TestSched(t *testing.T) { // run this one a bunch of times, it had a very annoying tendency to fail randomly for i := 0; i < 40; i++ { t.Run("pc1-pc2-prio", testFunc([]workerSpec{ - {name: "fred", taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}, sealtasks.TTPreCommit2: {}}}, + {name: "fred", resources: decentWorkerResources, taskTypes: map[sealtasks.TaskType]struct{}{sealtasks.TTPreCommit1: {}, sealtasks.TTPreCommit2: {}}}, }, []task{ // fill queues twoPC1("w0", 0, taskStarted), diff --git a/extern/sector-storage/sched_worker.go b/extern/sector-storage/sched_worker.go index 4e18e5c6f..7bc1affc3 100644 --- a/extern/sector-storage/sched_worker.go +++ b/extern/sector-storage/sched_worker.go @@ -296,7 +296,7 @@ func (sw *schedWorker) workerCompactWindows() { for ti, todo := range window.todo { needRes := ResourceTable[todo.taskType][todo.sector.ProofType] - if !lower.allocated.canHandleRequest(needRes, sw.wid, "compactWindows", worker.info.Resources) { + if !lower.allocated.canHandleRequest(needRes, sw.wid, "compactWindows", worker.info) { continue } @@ -352,7 +352,7 @@ assignLoop: worker.lk.Lock() for t, todo := range firstWindow.todo { needRes := ResourceTable[todo.taskType][todo.sector.ProofType] - if worker.preparing.canHandleRequest(needRes, sw.wid, "startPreparing", worker.info.Resources) { + if worker.preparing.canHandleRequest(needRes, sw.wid, "startPreparing", worker.info) { tidx = t break } @@ -424,7 +424,7 @@ func (sw *schedWorker) startProcessingTask(taskDone chan struct{}, req *workerRe } // wait (if needed) for resources in the 'active' window - err = w.active.withResources(sw.wid, w.info.Resources, needRes, &sh.workersLk, func() error { + err = w.active.withResources(sw.wid, w.info, needRes, &sh.workersLk, func() error { w.lk.Lock() w.preparing.free(w.info.Resources, needRes) w.lk.Unlock() diff --git a/extern/sector-storage/storiface/worker.go b/extern/sector-storage/storiface/worker.go index d3f4a2cd1..d1373f4c5 100644 --- a/extern/sector-storage/storiface/worker.go +++ b/extern/sector-storage/storiface/worker.go @@ -18,7 +18,12 @@ import ( type WorkerInfo struct { Hostname string - Resources WorkerResources + // IgnoreResources indicates whether the worker's available resources should + // be used ignored (true) or used (false) for the purposes of scheduling and + // task assignment. Only supported on local workers. Used for testing. + // Default should be false (zero value, i.e. resources taken into account). + IgnoreResources bool + Resources WorkerResources } type WorkerResources struct { diff --git a/extern/sector-storage/worker_local.go b/extern/sector-storage/worker_local.go index 2bb0f8300..3e63f8659 100644 --- a/extern/sector-storage/worker_local.go +++ b/extern/sector-storage/worker_local.go @@ -20,7 +20,7 @@ import ( ffi "github.com/filecoin-project/filecoin-ffi" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-statestore" - storage "github.com/filecoin-project/specs-storage/storage" + "github.com/filecoin-project/specs-storage/storage" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/extern/sector-storage/sealtasks" @@ -33,6 +33,11 @@ var pathTypes = []storiface.SectorFileType{storiface.FTUnsealed, storiface.FTSea type WorkerConfig struct { TaskTypes []sealtasks.TaskType NoSwap bool + + // IgnoreResourceFiltering enables task distribution to happen on this + // worker regardless of its currently available resources. Used in testing + // with the local worker. + IgnoreResourceFiltering bool } // used do provide custom proofs impl (mostly used in testing) @@ -46,6 +51,9 @@ type LocalWorker struct { executor ExecutorFunc noSwap bool + // see equivalent field on WorkerConfig. + ignoreResources bool + ct *workerCallTracker acceptTasks map[sealtasks.TaskType]struct{} running sync.WaitGroup @@ -71,12 +79,12 @@ func newLocalWorker(executor ExecutorFunc, wcfg WorkerConfig, store stores.Store ct: &workerCallTracker{ st: cst, }, - acceptTasks: acceptTasks, - executor: executor, - noSwap: wcfg.NoSwap, - - session: uuid.New(), - closing: make(chan struct{}), + acceptTasks: acceptTasks, + executor: executor, + noSwap: wcfg.NoSwap, + ignoreResources: wcfg.IgnoreResourceFiltering, + session: uuid.New(), + closing: make(chan struct{}), } if w.executor == nil { @@ -501,7 +509,8 @@ func (l *LocalWorker) Info(context.Context) (storiface.WorkerInfo, error) { } return storiface.WorkerInfo{ - Hostname: hostname, + Hostname: hostname, + IgnoreResources: l.ignoreResources, Resources: storiface.WorkerResources{ MemPhysical: mem.Total, MemSwap: memSwap, diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 6d2196f32..097d55a1a 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -110,7 +110,8 @@ func (b *CommitBatcher) run() { } lastMsg = nil - var sendAboveMax, sendAboveMin bool + // indicates whether we should only start a batch if we have reached or exceeded cfg.MaxCommitBatch + var sendAboveMax bool select { case <-b.stop: close(b.stopped) @@ -118,13 +119,13 @@ func (b *CommitBatcher) run() { case <-b.notify: sendAboveMax = true case <-b.batchWait(cfg.CommitBatchWait, cfg.CommitBatchSlack): - sendAboveMin = true + // do nothing case fr := <-b.force: // user triggered forceRes = fr } var err error - lastMsg, err = b.maybeStartBatch(sendAboveMax, sendAboveMin) + lastMsg, err = b.maybeStartBatch(sendAboveMax) if err != nil { log.Warnw("CommitBatcher processBatch error", "error", err) } @@ -172,7 +173,7 @@ func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.Time return time.After(wait) } -func (b *CommitBatcher) maybeStartBatch(notif, after bool) ([]sealiface.CommitBatchRes, error) { +func (b *CommitBatcher) maybeStartBatch(notif bool) ([]sealiface.CommitBatchRes, error) { b.lk.Lock() defer b.lk.Unlock() @@ -190,10 +191,6 @@ func (b *CommitBatcher) maybeStartBatch(notif, after bool) ([]sealiface.CommitBa return nil, nil } - if after && total < cfg.MinCommitBatch { - return nil, nil - } - var res []sealiface.CommitBatchRes if total < cfg.MinCommitBatch || total < miner5.MinAggregatedSectors { diff --git a/extern/storage-sealing/commit_batch_test.go b/extern/storage-sealing/commit_batch_test.go index bbf09766f..ad2bc8f6f 100644 --- a/extern/storage-sealing/commit_batch_test.go +++ b/extern/storage-sealing/commit_batch_test.go @@ -48,7 +48,6 @@ func TestCommitBatcher(t *testing.T) { AlwaysKeepUnsealedCopy: true, BatchPreCommits: true, - MinPreCommitBatch: 1, MaxPreCommitBatch: miner5.PreCommitSectorBatchMaxSize, PreCommitBatchWait: 24 * time.Hour, PreCommitBatchSlack: 3 * time.Hour, diff --git a/extern/storage-sealing/input.go b/extern/storage-sealing/input.go index 4a698ea1d..85a5c429f 100644 --- a/extern/storage-sealing/input.go +++ b/extern/storage-sealing/input.go @@ -395,6 +395,7 @@ func (m *Sealing) updateInput(ctx context.Context, sp abi.RegisteredSealProof) e func (m *Sealing) tryCreateDealSector(ctx context.Context, sp abi.RegisteredSealProof) error { m.startupWait.Wait() + if m.creating != nil { return nil // new sector is being created right now } @@ -448,8 +449,8 @@ func (m *Sealing) createSector(ctx context.Context, cfg sealiface.Config, sp abi func (m *Sealing) StartPacking(sid abi.SectorNumber) error { m.startupWait.Wait() - log.Infow("starting to seal deal sector", "sector", sid, "trigger", "user") + log.Infow("starting to seal deal sector", "sector", sid, "trigger", "user") return m.sectors.Send(uint64(sid), SectorStartPacking{}) } diff --git a/extern/storage-sealing/precommit_batch.go b/extern/storage-sealing/precommit_batch.go index d85a60bc6..3dc3510c2 100644 --- a/extern/storage-sealing/precommit_batch.go +++ b/extern/storage-sealing/precommit_batch.go @@ -95,7 +95,7 @@ func (b *PreCommitBatcher) run() { } lastRes = nil - var sendAboveMax, sendAboveMin bool + var sendAboveMax bool select { case <-b.stop: close(b.stopped) @@ -103,13 +103,13 @@ func (b *PreCommitBatcher) run() { case <-b.notify: sendAboveMax = true case <-b.batchWait(cfg.PreCommitBatchWait, cfg.PreCommitBatchSlack): - sendAboveMin = true + // do nothing case fr := <-b.force: // user triggered forceRes = fr } var err error - lastRes, err = b.maybeStartBatch(sendAboveMax, sendAboveMin) + lastRes, err = b.maybeStartBatch(sendAboveMax) if err != nil { log.Warnw("PreCommitBatcher processBatch error", "error", err) } @@ -157,7 +157,7 @@ func (b *PreCommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.T return time.After(wait) } -func (b *PreCommitBatcher) maybeStartBatch(notif, after bool) ([]sealiface.PreCommitBatchRes, error) { +func (b *PreCommitBatcher) maybeStartBatch(notif bool) ([]sealiface.PreCommitBatchRes, error) { b.lk.Lock() defer b.lk.Unlock() @@ -175,10 +175,6 @@ func (b *PreCommitBatcher) maybeStartBatch(notif, after bool) ([]sealiface.PreCo return nil, nil } - if after && total < cfg.MinPreCommitBatch { - return nil, nil - } - // todo support multiple batches res, err := b.processBatch(cfg) if err != nil && len(res) == 0 { diff --git a/extern/storage-sealing/precommit_batch_test.go b/extern/storage-sealing/precommit_batch_test.go index 141742c14..b6c35362e 100644 --- a/extern/storage-sealing/precommit_batch_test.go +++ b/extern/storage-sealing/precommit_batch_test.go @@ -55,7 +55,6 @@ func TestPrecommitBatcher(t *testing.T) { AlwaysKeepUnsealedCopy: true, BatchPreCommits: true, - MinPreCommitBatch: 1, MaxPreCommitBatch: maxBatch, PreCommitBatchWait: 24 * time.Hour, PreCommitBatchSlack: 3 * time.Hour, diff --git a/extern/storage-sealing/sealiface/config.go b/extern/storage-sealing/sealiface/config.go index 499a2befa..b237072d3 100644 --- a/extern/storage-sealing/sealiface/config.go +++ b/extern/storage-sealing/sealiface/config.go @@ -22,7 +22,6 @@ type Config struct { BatchPreCommits bool MaxPreCommitBatch int - MinPreCommitBatch int PreCommitBatchWait time.Duration PreCommitBatchSlack time.Duration diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index 2019aa131..8a70704c4 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -132,7 +132,7 @@ type pendingPiece struct { accepted func(abi.SectorNumber, abi.UnpaddedPieceSize, error) } -func New(api SealingAPI, fc config.MinerFeeConfig, events Events, maddr address.Address, ds datastore.Batching, sealer sectorstorage.SectorManager, sc SectorIDCounter, verif ffiwrapper.Verifier, prov ffiwrapper.Prover, pcp PreCommitPolicy, gc GetSealingConfigFunc, notifee SectorStateNotifee, as AddrSel) *Sealing { +func New(mctx context.Context, api SealingAPI, fc config.MinerFeeConfig, events Events, maddr address.Address, ds datastore.Batching, sealer sectorstorage.SectorManager, sc SectorIDCounter, verif ffiwrapper.Verifier, prov ffiwrapper.Prover, pcp PreCommitPolicy, gc GetSealingConfigFunc, notifee SectorStateNotifee, as AddrSel) *Sealing { s := &Sealing{ api: api, feeCfg: fc, @@ -153,9 +153,9 @@ func New(api SealingAPI, fc config.MinerFeeConfig, events Events, maddr address. notifee: notifee, addrSel: as, - terminator: NewTerminationBatcher(context.TODO(), maddr, api, as, fc, gc), - precommiter: NewPreCommitBatcher(context.TODO(), maddr, api, as, fc, gc), - commiter: NewCommitBatcher(context.TODO(), maddr, api, as, fc, gc, prov), + terminator: NewTerminationBatcher(mctx, maddr, api, as, fc, gc), + precommiter: NewPreCommitBatcher(mctx, maddr, api, as, fc, gc), + commiter: NewCommitBatcher(mctx, maddr, api, as, fc, gc, prov), getConfig: gc, dealInfo: &CurrentDealInfoManager{api}, diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 718fbf28a..360eeafa6 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -274,7 +274,7 @@ func (m *Sealing) preCommitParams(ctx statemachine.Context, sector SectorInfo) ( msd := policy.GetMaxProveCommitDuration(actors.VersionForNetwork(nv), sector.SectorType) - if minExpiration := height + msd + miner.MinSectorExpiration + 10; expiration < minExpiration { + if minExpiration := sector.TicketEpoch + policy.MaxPreCommitRandomnessLookback + msd + miner.MinSectorExpiration; expiration < minExpiration { expiration = minExpiration } // TODO: enforce a reasonable _maximum_ sector lifetime? diff --git a/gateway/node_test.go b/gateway/node_test.go index 0d33daa35..68711cca6 100644 --- a/gateway/node_test.go +++ b/gateway/node_test.go @@ -233,3 +233,19 @@ func (m *mockGatewayDepsAPI) StateWaitMsgLimited(ctx context.Context, msg cid.Ci func (m *mockGatewayDepsAPI) StateReadState(ctx context.Context, act address.Address, ts types.TipSetKey) (*api.ActorState, error) { panic("implement me") } + +func (m *mockGatewayDepsAPI) Version(context.Context) (api.APIVersion, error) { + return api.APIVersion{ + APIVersion: api.FullAPIVersion1, + }, nil +} + +func TestGatewayVersion(t *testing.T) { + ctx := context.Background() + mock := &mockGatewayDepsAPI{} + a := NewNode(mock, DefaultLookbackCap, DefaultStateWaitLookbackLimit) + + v, err := a.Version(ctx) + require.NoError(t, err) + require.Equal(t, api.FullAPIVersion1, v.APIVersion) +} diff --git a/go.mod b/go.mod index 411522a36..36ea7835c 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/filecoin-project/specs-actors/v2 v2.3.5 github.com/filecoin-project/specs-actors/v3 v3.1.1 github.com/filecoin-project/specs-actors/v4 v4.0.1 - github.com/filecoin-project/specs-actors/v5 v5.0.0-20210609212542-73e0409ac77c + github.com/filecoin-project/specs-actors/v5 v5.0.1 github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 github.com/filecoin-project/test-vectors/schema v0.0.5 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 @@ -133,6 +133,7 @@ require ( github.com/prometheus/client_golang v1.6.0 github.com/raulk/clock v1.1.0 github.com/raulk/go-watchdog v1.0.1 + github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25 github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.0 @@ -160,6 +161,8 @@ require ( honnef.co/go/tools v0.0.1-2020.1.3 // indirect ) +replace github.com/libp2p/go-libp2p-yamux => github.com/libp2p/go-libp2p-yamux v0.5.1 + replace github.com/filecoin-project/lotus => ./ replace github.com/golangci/golangci-lint => github.com/golangci/golangci-lint v1.18.0 diff --git a/go.sum b/go.sum index 5573587fd..f9499e2f9 100644 --- a/go.sum +++ b/go.sum @@ -331,8 +331,8 @@ github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIP github.com/filecoin-project/specs-actors/v4 v4.0.1 h1:AiWrtvJZ63MHGe6rn7tPu4nSUY8bA1KDNszqJaD5+Fg= github.com/filecoin-project/specs-actors/v4 v4.0.1/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210609212542-73e0409ac77c h1:GnDJ6q3QEm2ytTKjPFQSvczAltgCSb3j9F1FeynwvPA= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210609212542-73e0409ac77c/go.mod h1:b/btpRl84Q9SeDKlyIoORBQwe2OTmq14POrYrVvBWCM= +github.com/filecoin-project/specs-actors/v5 v5.0.1 h1:PrYm5AKdMlJ/55eRW5laWcnaX66gyyDYBWvH38kNAMo= +github.com/filecoin-project/specs-actors/v5 v5.0.1/go.mod h1:74euMDIXorusOBs/QL/LNkYsXZdDpLJwojWw6T03pdE= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506/go.mod h1:nJRRM7Aa9XVvygr3W9k6xGF46RWzr2zxF/iGoAIfA/g= github.com/filecoin-project/test-vectors/schema v0.0.5 h1:w3zHQhzM4pYxJDl21avXjOKBLF8egrvwUwjpT8TquDg= @@ -704,12 +704,13 @@ github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHn github.com/ipfs/go-log/v2 v2.1.2-0.20200626104915-0016c0b4b3e4/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.2/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.3 h1:1iS3IU7aXRlbgUpN8yTTpJ53NXYjAe37vcI5+5nYrzk= -github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= github.com/ipfs/go-log/v2 v2.1.3 h1:1iS3IU7aXRlbgUpN8yTTpJ53NXYjAe37vcI5+5nYrzk= github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= +github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= github.com/ipfs/go-merkledag v0.0.3/go.mod h1:Oc5kIXLHokkE1hWGMBHw+oxehkAaTOqtEb7Zbh6BhLA= github.com/ipfs/go-merkledag v0.0.6/go.mod h1:QYPdnlvkOg7GnQRofu9XZimC5ZW5Wi3bKys/4GQQfto= github.com/ipfs/go-merkledag v0.2.3/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk= +github.com/ipfs/go-merkledag v0.2.4/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk= github.com/ipfs/go-merkledag v0.3.1/go.mod h1:fvkZNNZixVW6cKSZ/JfLlON5OlgTXNdRLz0p6QG/I2M= github.com/ipfs/go-merkledag v0.3.2 h1:MRqj40QkrWkvPswXs4EfSslhZ4RVPRbxwX11js0t1xY= github.com/ipfs/go-merkledag v0.3.2/go.mod h1:fvkZNNZixVW6cKSZ/JfLlON5OlgTXNdRLz0p6QG/I2M= @@ -728,6 +729,7 @@ github.com/ipfs/go-peertaskqueue v0.2.0/go.mod h1:5/eNrBEbtSKWCG+kQK8K8fGNixoYUn github.com/ipfs/go-todocounter v0.0.1/go.mod h1:l5aErvQc8qKE2r7NDMjmq5UNAvuZy0rC8BHOplkWvZ4= github.com/ipfs/go-unixfs v0.0.4/go.mod h1:eIo/p9ADu/MFOuyxzwU+Th8D6xoxU//r590vUpWyfz8= github.com/ipfs/go-unixfs v0.2.1/go.mod h1:IwAAgul1UQIcNZzKPYZWOCijryFBeCV79cNubPzol+k= +github.com/ipfs/go-unixfs v0.2.2-0.20190827150610-868af2e9e5cb/go.mod h1:IwAAgul1UQIcNZzKPYZWOCijryFBeCV79cNubPzol+k= github.com/ipfs/go-unixfs v0.2.4 h1:6NwppOXefWIyysZ4LR/qUBPvXd5//8J3jiMdvpbw6Lo= github.com/ipfs/go-unixfs v0.2.4/go.mod h1:SUdisfUjNoSDzzhGVxvCL9QO/nKdwXdr+gbMUdqcbYw= github.com/ipfs/go-verifcid v0.0.1 h1:m2HI7zIuR5TFyQ1b79Da5N9dnnCP1vcu2QqawmWlK2E= @@ -738,13 +740,16 @@ github.com/ipfs/iptb v1.4.0 h1:YFYTrCkLMRwk/35IMyC6+yjoQSHTEcNcefBStLJzgvo= github.com/ipfs/iptb v1.4.0/go.mod h1:1rzHpCYtNp87/+hTxG5TfCVn/yMY3dKnLn8tBiMfdmg= github.com/ipfs/iptb-plugins v0.2.1 h1:au4HWn9/pRPbkxA08pDx2oRAs4cnbgQWgV0teYXuuGA= github.com/ipfs/iptb-plugins v0.2.1/go.mod h1:QXMbtIWZ+jRsW8a4h13qAKU7jcM7qaittO8wOsTP0Rs= +github.com/ipld/go-car v0.1.0/go.mod h1:RCWzaUh2i4mOEkB3W45Vc+9jnS/M6Qay5ooytiBHl3g= github.com/ipld/go-car v0.1.1-0.20200923150018-8cdef32e2da4/go.mod h1:xrMEcuSq+D1vEwl+YAXsg/JfA98XGpXDwnkIL4Aimqw= github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d h1:iphSzTuPqyDgH7WUVZsdqUnQNzYgIblsVr1zhVNA33U= github.com/ipld/go-car v0.1.1-0.20201119040415-11b6074b6d4d/go.mod h1:2Gys8L8MJ6zkh1gktTSXreY63t4UbyvNp5JaudTyxHQ= +github.com/ipld/go-ipld-prime v0.0.2-0.20191108012745-28a82f04c785/go.mod h1:bDDSvVz7vaK12FNvMeRYnpRFkSUPNQOiCYQezMD/P3w= github.com/ipld/go-ipld-prime v0.0.2-0.20200428162820-8b59dc292b8e/go.mod h1:uVIwe/u0H4VdKv3kaN1ck7uCb6yD9cFLS9/ELyXbsw8= github.com/ipld/go-ipld-prime v0.5.1-0.20200828233916-988837377a7f/go.mod h1:0xEgdD6MKbZ1vF0GC+YcR/C4SQCAlRuOjIJ2i0HxqzM= github.com/ipld/go-ipld-prime v0.5.1-0.20201021195245-109253e8a018 h1:RbRHv8epkmvBYA5cGfz68GUSbOgx5j/7ObLIl4Rsif0= github.com/ipld/go-ipld-prime v0.5.1-0.20201021195245-109253e8a018/go.mod h1:0xEgdD6MKbZ1vF0GC+YcR/C4SQCAlRuOjIJ2i0HxqzM= +github.com/ipld/go-ipld-prime-proto v0.0.0-20191113031812-e32bd156a1e5/go.mod h1:gcvzoEDBjwycpXt3LBE061wT9f46szXGHAmj9uoP6fU= github.com/ipld/go-ipld-prime-proto v0.0.0-20200428191222-c1ffdadc01e1/go.mod h1:OAV6xBmuTLsPZ+epzKkPB1e25FHk/vCtyatkdHcArLs= github.com/ipld/go-ipld-prime-proto v0.0.0-20200922192210-9a2bfd4440a6/go.mod h1:3pHYooM9Ea65jewRwrb2u5uHZCNkNTe9ABsVB+SrkH0= github.com/ipld/go-ipld-prime-proto v0.1.0 h1:j7gjqrfwbT4+gXpHwEx5iMssma3mnctC7YaCimsFP70= @@ -1062,18 +1067,8 @@ github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSo github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= github.com/libp2p/go-libp2p-transport-upgrader v0.4.2 h1:4JsnbfJzgZeRS9AWN7B9dPqn/LY/HoQTlO9gtdJTIYM= github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= -github.com/libp2p/go-libp2p-yamux v0.1.2/go.mod h1:xUoV/RmYkg6BW/qGxA9XJyg+HzXFYkeXbnhjmnYzKp8= -github.com/libp2p/go-libp2p-yamux v0.1.3/go.mod h1:VGSQVrqkh6y4nm0189qqxMtvyBft44MOYYPpYKXiVt4= -github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= -github.com/libp2p/go-libp2p-yamux v0.2.1/go.mod h1:1FBXiHDk1VyRM1C0aez2bCfHQ4vMZKkAQzZbkSQt5fI= -github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= -github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= -github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= -github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= -github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= -github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= -github.com/libp2p/go-libp2p-yamux v0.5.4 h1:/UOPtT/6DHPtr3TtKXBHa6g0Le0szYuI33Xc/Xpd7fQ= -github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= +github.com/libp2p/go-libp2p-yamux v0.5.1 h1:sX4WQPHMhRxJE5UZTfjEuBvlQWXB5Bo3A2JK9ZJ9EM0= +github.com/libp2p/go-libp2p-yamux v0.5.1/go.mod h1:dowuvDu8CRWmr0iqySMiSxK+W0iL5cMVO9S94Y6gkv4= github.com/libp2p/go-maddr-filter v0.0.1/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= @@ -1144,19 +1139,11 @@ github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1f github.com/libp2p/go-ws-transport v0.3.1/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-ws-transport v0.4.0 h1:9tvtQ9xbws6cA5LvqdE6Ne3vcmGB4f1z9SByggk4s0k= github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= -github.com/libp2p/go-yamux v1.2.1/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.2.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= +github.com/libp2p/go-yamux v1.3.6 h1:O5qcBXRcfqecvQ/My9NqDNHB3/5t58yuJYqthcKhhgE= github.com/libp2p/go-yamux v1.3.6/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= -github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux v1.4.1 h1:P1Fe9vF4th5JOxxgQvfbOHkrGqIZniTLf+ddhZp8YTI= -github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= -github.com/libp2p/go-yamux/v2 v2.2.0 h1:RwtpYZ2/wVviZ5+3pjC8qdQ4TKnrak0/E01N1UWoAFU= -github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= +github.com/libp2p/go-yamux/v2 v2.0.0 h1:vSGhAy5u6iHBq11ZDcyHH4Blcf9xlBhT4WQDoOE90LU= +github.com/libp2p/go-yamux/v2 v2.0.0/go.mod h1:NVWira5+sVUIU6tu1JWvaRn1dRnG+cawOJiflsAM+7U= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lucas-clemente/quic-go v0.11.2/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= @@ -1513,6 +1500,8 @@ github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/ github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25 h1:7z3LSn867ex6VSaahyKadf4WtSsJIgne6A1WLOAGM8A= +github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= diff --git a/itests/api_test.go b/itests/api_test.go index ee70a337b..5487a2c38 100644 --- a/itests/api_test.go +++ b/itests/api_test.go @@ -7,34 +7,32 @@ import ( "time" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/exitcode" lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/node/impl" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestAPI(t *testing.T) { t.Run("direct", func(t *testing.T) { - runAPITest(t, kit.Builder) + runAPITest(t) }) t.Run("rpc", func(t *testing.T) { - runAPITest(t, kit.RPCBuilder) + runAPITest(t, kit.ThroughRPC()) }) } type apiSuite struct { - makeNodes kit.APIBuilder + opts []interface{} } // runAPITest is the entry point to API test suite -func runAPITest(t *testing.T, b kit.APIBuilder) { - ts := apiSuite{ - makeNodes: b, - } +func runAPITest(t *testing.T, opts ...interface{}) { + ts := apiSuite{opts: opts} t.Run("version", ts.testVersion) t.Run("id", ts.testID) @@ -51,145 +49,114 @@ func (ts *apiSuite) testVersion(t *testing.T) { lapi.RunningNodeType = lapi.NodeUnknown }) - ctx := context.Background() - apis, _ := ts.makeNodes(t, kit.OneFull, kit.OneMiner) - napi := apis[0] + full, _, _ := kit.EnsembleMinimal(t, ts.opts...) + + v, err := full.Version(context.Background()) + require.NoError(t, err) - v, err := napi.Version(ctx) - if err != nil { - t.Fatal(err) - } versions := strings.Split(v.Version, "+") - if len(versions) <= 0 { - t.Fatal("empty version") - } + require.NotZero(t, len(versions), "empty version") require.Equal(t, versions[0], build.BuildVersion) } -func (ts *apiSuite) testSearchMsg(t *testing.T) { - apis, miners := ts.makeNodes(t, kit.OneFull, kit.OneMiner) +func (ts *apiSuite) testID(t *testing.T) { + ctx := context.Background() - api := apis[0] - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - senderAddr, err := api.WalletDefaultAddress(ctx) + full, _, _ := kit.EnsembleMinimal(t, ts.opts...) + + id, err := full.ID(ctx) if err != nil { t.Fatal(err) } + require.Regexp(t, "^12", id.Pretty()) +} + +func (ts *apiSuite) testConnectTwo(t *testing.T) { + ctx := context.Background() + + one, two, _, ens := kit.EnsembleTwoOne(t, ts.opts...) + + p, err := one.NetPeers(ctx) + require.NoError(t, err) + require.Empty(t, p, "node one has peers") + + p, err = two.NetPeers(ctx) + require.NoError(t, err) + require.Empty(t, p, "node two has peers") + + ens.InterconnectAll() + + peers, err := one.NetPeers(ctx) + require.NoError(t, err) + require.Lenf(t, peers, 2, "node one doesn't have 2 peers") + + peers, err = two.NetPeers(ctx) + require.NoError(t, err) + require.Lenf(t, peers, 2, "node two doesn't have 2 peers") +} + +func (ts *apiSuite) testSearchMsg(t *testing.T) { + ctx := context.Background() + + full, _, ens := kit.EnsembleMinimal(t, ts.opts...) + + senderAddr, err := full.WalletDefaultAddress(ctx) + require.NoError(t, err) msg := &types.Message{ From: senderAddr, To: senderAddr, Value: big.Zero(), } - bm := kit.NewBlockMiner(t, miners[0]) - bm.MineBlocks(ctx, 100*time.Millisecond) - defer bm.Stop() - sm, err := api.MpoolPushMessage(ctx, msg, nil) - if err != nil { - t.Fatal(err) - } - res, err := api.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatal("did not successfully send message") - } + ens.BeginMining(100 * time.Millisecond) - searchRes, err := api.StateSearchMsg(ctx, types.EmptyTSK, sm.Cid(), lapi.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } + sm, err := full.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) - if searchRes.TipSet != res.TipSet { - t.Fatalf("search ts: %s, different from wait ts: %s", searchRes.TipSet, res.TipSet) - } + res, err := full.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) + require.NoError(t, err) -} + require.Equal(t, exitcode.Ok, res.Receipt.ExitCode, "message not successful") -func (ts *apiSuite) testID(t *testing.T) { - ctx := context.Background() - apis, _ := ts.makeNodes(t, kit.OneFull, kit.OneMiner) - api := apis[0] + searchRes, err := full.StateSearchMsg(ctx, types.EmptyTSK, sm.Cid(), lapi.LookbackNoLimit, true) + require.NoError(t, err) - id, err := api.ID(ctx) - if err != nil { - t.Fatal(err) - } - assert.Regexp(t, "^12", id.Pretty()) -} - -func (ts *apiSuite) testConnectTwo(t *testing.T) { - ctx := context.Background() - apis, _ := ts.makeNodes(t, kit.TwoFull, kit.OneMiner) - - p, err := apis[0].NetPeers(ctx) - if err != nil { - t.Fatal(err) - } - if len(p) != 0 { - t.Error("Node 0 has a peer") - } - - p, err = apis[1].NetPeers(ctx) - if err != nil { - t.Fatal(err) - } - if len(p) != 0 { - t.Error("Node 1 has a peer") - } - - addrs, err := apis[1].NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := apis[0].NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - p, err = apis[0].NetPeers(ctx) - if err != nil { - t.Fatal(err) - } - if len(p) != 1 { - t.Error("Node 0 doesn't have 1 peer") - } - - p, err = apis[1].NetPeers(ctx) - if err != nil { - t.Fatal(err) - } - if len(p) != 1 { - t.Error("Node 0 doesn't have 1 peer") - } + require.Equalf(t, res.TipSet, searchRes.TipSet, "search ts: %s, different from wait ts: %s", searchRes.TipSet, res.TipSet) } func (ts *apiSuite) testMining(t *testing.T) { ctx := context.Background() - fulls, miners := ts.makeNodes(t, kit.OneFull, kit.OneMiner) - api := fulls[0] - newHeads, err := api.ChainNotify(ctx) + full, miner, _ := kit.EnsembleMinimal(t, ts.opts...) + + newHeads, err := full.ChainNotify(ctx) require.NoError(t, err) initHead := (<-newHeads)[0] baseHeight := initHead.Val.Height() - h1, err := api.ChainHead(ctx) + h1, err := full.ChainHead(ctx) require.NoError(t, err) require.Equal(t, int64(h1.Height()), int64(baseHeight)) - bm := kit.NewBlockMiner(t, miners[0]) - bm.MineUntilBlock(ctx, fulls[0], nil) + bm := kit.NewBlockMiner(t, miner) + bm.MineUntilBlock(ctx, full, nil) require.NoError(t, err) <-newHeads - h2, err := api.ChainHead(ctx) + h2, err := full.ChainHead(ctx) require.NoError(t, err) require.Greater(t, int64(h2.Height()), int64(h1.Height())) + + bm.MineUntilBlock(ctx, full, nil) + require.NoError(t, err) + + <-newHeads + + h3, err := full.ChainHead(ctx) + require.NoError(t, err) + require.Greater(t, int64(h3.Height()), int64(h2.Height())) } func (ts *apiSuite) testMiningReal(t *testing.T) { @@ -198,66 +165,30 @@ func (ts *apiSuite) testMiningReal(t *testing.T) { build.InsecurePoStValidation = true }() - ctx := context.Background() - fulls, miners := ts.makeNodes(t, kit.OneFull, kit.OneMiner) - api := fulls[0] - - newHeads, err := api.ChainNotify(ctx) - require.NoError(t, err) - at := (<-newHeads)[0].Val.Height() - - h1, err := api.ChainHead(ctx) - require.NoError(t, err) - require.Equal(t, int64(at), int64(h1.Height())) - - bm := kit.NewBlockMiner(t, miners[0]) - - bm.MineUntilBlock(ctx, fulls[0], nil) - require.NoError(t, err) - - <-newHeads - - h2, err := api.ChainHead(ctx) - require.NoError(t, err) - require.Greater(t, int64(h2.Height()), int64(h1.Height())) - - bm.MineUntilBlock(ctx, fulls[0], nil) - require.NoError(t, err) - - <-newHeads - - h3, err := api.ChainHead(ctx) - require.NoError(t, err) - require.Greater(t, int64(h3.Height()), int64(h2.Height())) + ts.testMining(t) } func (ts *apiSuite) testNonGenesisMiner(t *testing.T) { ctx := context.Background() - n, sn := ts.makeNodes(t, - []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1)}, - []kit.StorageMiner{{Full: 0, Preseal: kit.PresealGenesis}}, - ) - full, ok := n[0].FullNode.(*impl.FullNodeAPI) - if !ok { - t.Skip("not testing with a full node") - return - } - genesisMiner := sn[0] + full, genesisMiner, ens := kit.EnsembleMinimal(t, append(ts.opts, kit.MockProofs())...) + ens.InterconnectAll().BeginMining(4 * time.Millisecond) - bm := kit.NewBlockMiner(t, genesisMiner) - bm.MineBlocks(ctx, 4*time.Millisecond) - t.Cleanup(bm.Stop) + time.Sleep(1 * time.Second) gaa, err := genesisMiner.ActorAddress(ctx) require.NoError(t, err) - gmi, err := full.StateMinerInfo(ctx, gaa, types.EmptyTSK) + _, err = full.StateMinerInfo(ctx, gaa, types.EmptyTSK) require.NoError(t, err) - testm := n[0].Stb(ctx, t, kit.TestSpt, gmi.Owner) + var newMiner kit.TestMiner + ens.Miner(&newMiner, full, + kit.OwnerAddr(full.DefaultKey), + kit.ProofType(abi.RegisteredSealProof_StackedDrg2KiBV1), // we're using v0 actors with old proofs. + ).Start().InterconnectAll() - ta, err := testm.ActorAddress(ctx) + ta, err := newMiner.ActorAddress(ctx) require.NoError(t, err) tid, err := address.IDFromAddress(ta) diff --git a/itests/batch_deal_test.go b/itests/batch_deal_test.go index 9676dffcc..300a44fa2 100644 --- a/itests/batch_deal_test.go +++ b/itests/batch_deal_test.go @@ -13,7 +13,6 @@ import ( "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/markets/storageadapter" "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/impl" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/stretchr/testify/require" ) @@ -32,50 +31,40 @@ func TestBatchDealInput(t *testing.T) { run := func(piece, deals, expectSectors int) func(t *testing.T) { return func(t *testing.T) { + ctx := context.Background() + publishPeriod := 10 * time.Second maxDealsPerMsg := uint64(deals) // Set max deals per publish deals message to maxDealsPerMsg - minerDef := []kit.StorageMiner{{ - Full: 0, - Opts: node.Options( - node.Override( - new(*storageadapter.DealPublisher), - storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ - Period: publishPeriod, - MaxDealsPerMsg: maxDealsPerMsg, - })), - node.Override(new(dtypes.GetSealingConfigFunc), func() (dtypes.GetSealingConfigFunc, error) { - return func() (sealiface.Config, error) { - return sealiface.Config{ - MaxWaitDealsSectors: 2, - MaxSealingSectors: 1, - MaxSealingSectorsForDeals: 3, - AlwaysKeepUnsealedCopy: true, - WaitDealsDelay: time.Hour, - }, nil + opts := kit.ConstructorOpts(node.Options( + node.Override( + new(*storageadapter.DealPublisher), + storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ + Period: publishPeriod, + MaxDealsPerMsg: maxDealsPerMsg, + })), + node.Override(new(dtypes.GetSealingConfigFunc), func() (dtypes.GetSealingConfigFunc, error) { + return func() (sealiface.Config, error) { + return sealiface.Config{ + MaxWaitDealsSectors: 2, + MaxSealingSectors: 1, + MaxSealingSectorsForDeals: 3, + AlwaysKeepUnsealedCopy: true, + WaitDealsDelay: time.Hour, }, nil - }), - ), - Preseal: kit.PresealGenesis, - }} - - // Create a connect client and miner node - n, sn := kit.MockMinerBuilder(t, kit.OneFull, minerDef) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - blockMiner := kit.ConnectAndStartMining(t, blockTime, miner, client) - t.Cleanup(blockMiner.Stop) - + }, nil + }), + )) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blockTime) dh := kit.NewDealHarness(t, client, miner) - ctx := context.Background() err := miner.MarketSetAsk(ctx, big.Zero(), big.Zero(), 200, 128, 32<<30) require.NoError(t, err) checkNoPadding := func() { - sl, err := sn[0].SectorsList(ctx) + sl, err := miner.SectorsList(ctx) require.NoError(t, err) sort.Slice(sl, func(i, j int) bool { @@ -83,7 +72,7 @@ func TestBatchDealInput(t *testing.T) { }) for _, snum := range sl { - si, err := sn[0].SectorsStatus(ctx, snum, false) + si, err := miner.SectorsStatus(ctx, snum, false) require.NoError(t, err) // fmt.Printf("S %d: %+v %s\n", snum, si.Deals, si.State) @@ -122,7 +111,7 @@ func TestBatchDealInput(t *testing.T) { checkNoPadding() - sl, err := sn[0].SectorsList(ctx) + sl, err := miner.SectorsList(ctx) require.NoError(t, err) require.Equal(t, len(sl), expectSectors) } diff --git a/itests/ccupgrade_test.go b/itests/ccupgrade_test.go index 196cf7106..eac2523bf 100644 --- a/itests/ccupgrade_test.go +++ b/itests/ccupgrade_test.go @@ -3,17 +3,14 @@ package itests import ( "context" "fmt" - "sync/atomic" "testing" "time" - "github.com/filecoin-project/lotus/itests/kit" - "github.com/stretchr/testify/require" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/node/impl" + "github.com/filecoin-project/lotus/itests/kit" + + "github.com/stretchr/testify/require" ) func TestCCUpgrade(t *testing.T) { @@ -27,60 +24,33 @@ func TestCCUpgrade(t *testing.T) { } { height := height // make linters happy by copying t.Run(fmt.Sprintf("upgrade-%d", height), func(t *testing.T) { - runTestCCUpgrade(t, kit.MockMinerBuilder, 5*time.Millisecond, height) + runTestCCUpgrade(t, height) }) } } -func runTestCCUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Duration, upgradeHeight abi.ChainEpoch) { +func runTestCCUpgrade(t *testing.T, upgradeHeight abi.ChainEpoch) { ctx := context.Background() - n, sn := b(t, []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(upgradeHeight)}, kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] + blockTime := 5 * time.Millisecond - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - time.Sleep(time.Second) - - mine := int64(1) - done := make(chan struct{}) - go func() { - defer close(done) - for atomic.LoadInt64(&mine) == 1 { - time.Sleep(blocktime) - if err := sn[0].MineOne(ctx, kit.MineNext); err != nil { - t.Error(err) - } - } - }() + opts := kit.ConstructorOpts(kit.LatestActorsAt(upgradeHeight)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blockTime) maddr, err := miner.ActorAddress(ctx) if err != nil { t.Fatal(err) } - CC := abi.SectorNumber(kit.GenesisPreseals + 1) + CC := abi.SectorNumber(kit.DefaultPresealsPerBootstrapMiner + 1) Upgraded := CC + 1 - kit.PledgeSectors(t, ctx, miner, 1, 0, nil) + miner.PledgeSectors(ctx, 1, 0, nil) sl, err := miner.SectorsList(ctx) - if err != nil { - t.Fatal(err) - } - if len(sl) != 1 { - t.Fatal("expected 1 sector") - } - - if sl[0] != CC { - t.Fatal("bad") - } + require.NoError(t, err) + require.Len(t, sl, 1, "expected 1 sector") + require.Equal(t, CC, sl[0], "unexpected sector number") { si, err := client.StateSectorGetInfo(ctx, maddr, CC, types.EmptyTSK) @@ -88,20 +58,16 @@ func runTestCCUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Duration, u require.Less(t, 50000, int(si.Expiration)) } - if err := miner.SectorMarkForUpgrade(ctx, sl[0]); err != nil { - t.Fatal(err) - } + err = miner.SectorMarkForUpgrade(ctx, sl[0]) + require.NoError(t, err) dh := kit.NewDealHarness(t, client, miner) - - _, _, _ = dh.MakeFullDeal(kit.MakeFullDealParams{ - Ctx: context.Background(), - Rseed: 6, - CarExport: false, - FastRet: false, - StartEpoch: 0, - DoRetrieval: true, + deal, res, inPath := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{ + Rseed: 6, + SuspendUntilCryptoeconStable: true, }) + outPath := dh.PerformRetrieval(context.Background(), deal, res.Root, false) + kit.AssertFilesEqual(t, inPath, outPath) // Validate upgrade @@ -130,10 +96,6 @@ func runTestCCUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Duration, u } t.Log("waiting for sector to expire") // wait one deadline per loop. - time.Sleep(time.Duration(dlInfo.WPoStChallengeWindow) * blocktime) + time.Sleep(time.Duration(dlInfo.WPoStChallengeWindow) * blockTime) } - - fmt.Println("shutting down mining") - atomic.AddInt64(&mine, -1) - <-done } diff --git a/itests/cli_test.go b/itests/cli_test.go index 10e2af15c..0bd1ec3b4 100644 --- a/itests/cli_test.go +++ b/itests/cli_test.go @@ -1,7 +1,6 @@ package itests import ( - "context" "os" "testing" "time" @@ -15,8 +14,8 @@ func TestClient(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") kit.QuietMiningLogs() - blocktime := 5 * time.Millisecond - ctx := context.Background() - clientNode, _ := kit.StartOneNodeOneMiner(ctx, t, blocktime) - kit.RunClientTest(t, cli.Commands, clientNode) + blockTime := 5 * time.Millisecond + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + ens.InterconnectAll().BeginMining(blockTime) + kit.RunClientTest(t, cli.Commands, client) } diff --git a/itests/deadlines_test.go b/itests/deadlines_test.go index 9551465a5..9768c3c60 100644 --- a/itests/deadlines_test.go +++ b/itests/deadlines_test.go @@ -3,8 +3,6 @@ package itests import ( "bytes" "context" - "fmt" - "os" "testing" "time" @@ -27,7 +25,6 @@ import ( miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" - logging "github.com/ipfs/go-log/v2" "github.com/stretchr/testify/require" ) @@ -55,14 +52,9 @@ import ( // * asserts that miner B loses power // * asserts that miner D loses power, is inactive func TestDeadlineToggling(t *testing.T) { - if os.Getenv("LOTUS_TEST_DEADLINE_TOGGLING") != "1" { - t.Skip("this takes a few minutes, set LOTUS_TEST_DEADLINE_TOGGLING=1 to run") - } - _ = logging.SetLogLevel("miner", "ERROR") - _ = logging.SetLogLevel("chainstore", "ERROR") - _ = logging.SetLogLevel("chain", "ERROR") - _ = logging.SetLogLevel("sub", "ERROR") - _ = logging.SetLogLevel("storageminer", "FATAL") + kit.Expensive(t) + + kit.QuietMiningLogs() const sectorsC, sectorsD, sectorsB = 10, 9, 8 @@ -75,21 +67,26 @@ func TestDeadlineToggling(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := kit.MockMinerBuilder(t, []kit.FullNodeOpts{kit.FullNodeWithNetworkUpgradeAt(network.Version12, upgradeH)}, kit.OneMiner) + var ( + client kit.TestFullNode + minerA kit.TestMiner + minerB kit.TestMiner + minerC kit.TestMiner + minerD kit.TestMiner + minerE kit.TestMiner + ) + opts := []kit.NodeOpt{kit.ConstructorOpts(kit.NetworkUpgradeAt(network.Version12, upgradeH))} + ens := kit.NewEnsemble(t, kit.MockProofs()). + FullNode(&client, opts...). + Miner(&minerA, &client, opts...). + Start(). + InterconnectAll() + ens.BeginMining(blocktime) - client := n[0].FullNode.(*impl.FullNodeAPI) - minerA := sn[0] - - { - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := minerA.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - } + opts = append(opts, kit.OwnerAddr(client.DefaultKey)) + ens.Miner(&minerB, &client, opts...). + Miner(&minerC, &client, opts...). + Start() defaultFrom, err := client.WalletDefaultAddress(ctx) require.NoError(t, err) @@ -99,28 +96,6 @@ func TestDeadlineToggling(t *testing.T) { build.Clock.Sleep(time.Second) - done := make(chan struct{}) - go func() { - defer close(done) - for ctx.Err() == nil { - build.Clock.Sleep(blocktime) - if err := minerA.MineOne(ctx, kit.MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() - - minerB := n[0].Stb(ctx, t, kit.TestSpt, defaultFrom) - minerC := n[0].Stb(ctx, t, kit.TestSpt, defaultFrom) - maddrB, err := minerB.ActorAddress(ctx) require.NoError(t, err) maddrC, err := minerC.ActorAddress(ctx) @@ -131,20 +106,20 @@ func TestDeadlineToggling(t *testing.T) { // pledge sectors on C, go through a PP, check for power { - kit.PledgeSectors(t, ctx, minerC, sectorsC, 0, nil) + minerC.PledgeSectors(ctx, sectorsC, 0, nil) di, err := client.StateMinerProvingDeadline(ctx, maddrC, types.EmptyTSK) require.NoError(t, err) - fmt.Printf("Running one proving period (miner C)\n") - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod*2) + t.Log("Running one proving period (miner C)") + t.Logf("End for head.Height > %d", di.PeriodStart+di.WPoStProvingPeriod*2) for { head, err := client.ChainHead(ctx) require.NoError(t, err) if head.Height() > di.PeriodStart+provingPeriod*2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) + t.Logf("Now head.Height = %d", head.Height()) break } build.Clock.Sleep(blocktime) @@ -165,7 +140,7 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) if head.Height() > upgradeH+provingPeriod { - fmt.Printf("Now head.Height = %d\n", head.Height()) + t.Logf("Now head.Height = %d", head.Height()) break } build.Clock.Sleep(blocktime) @@ -216,8 +191,9 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) require.GreaterOrEqual(t, nv, network.Version12) - minerD := n[0].Stb(ctx, t, kit.TestSpt, defaultFrom) - minerE := n[0].Stb(ctx, t, kit.TestSpt, defaultFrom) + ens.Miner(&minerD, &client, opts...). + Miner(&minerE, &client, opts...). + Start() maddrD, err := minerD.ActorAddress(ctx) require.NoError(t, err) @@ -225,7 +201,7 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) // first round of miner checks - checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.GenesisPreseals), true, true, types.EmptyTSK) + checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) checkMiner(maddrC, types.NewInt(uint64(ssz)*sectorsC), true, true, types.EmptyTSK) checkMiner(maddrB, types.NewInt(0), false, false, types.EmptyTSK) @@ -233,10 +209,10 @@ func TestDeadlineToggling(t *testing.T) { checkMiner(maddrE, types.NewInt(0), false, false, types.EmptyTSK) // pledge sectors on minerB/minerD, stop post on minerC - kit.PledgeSectors(t, ctx, minerB, sectorsB, 0, nil) + minerB.PledgeSectors(ctx, sectorsB, 0, nil) checkMiner(maddrB, types.NewInt(0), true, true, types.EmptyTSK) - kit.PledgeSectors(t, ctx, minerD, sectorsD, 0, nil) + minerD.PledgeSectors(ctx, sectorsD, 0, nil) checkMiner(maddrD, types.NewInt(0), true, true, types.EmptyTSK) minerC.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).Fail() @@ -281,7 +257,7 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) if head.Height() > upgradeH+provingPeriod+(provingPeriod/2) { - fmt.Printf("Now head.Height = %d\n", head.Height()) + t.Logf("Now head.Height = %d", head.Height()) break } build.Clock.Sleep(blocktime) @@ -295,14 +271,14 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) if head.Height() > upgradeH+(provingPeriod*3) { - fmt.Printf("Now head.Height = %d\n", head.Height()) + t.Logf("Now head.Height = %d", head.Height()) break } build.Clock.Sleep(blocktime) } // second round of miner checks - checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.GenesisPreseals), true, true, types.EmptyTSK) + checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) checkMiner(maddrC, types.NewInt(0), true, true, types.EmptyTSK) checkMiner(maddrB, types.NewInt(uint64(ssz)*sectorsB), true, true, types.EmptyTSK) checkMiner(maddrD, types.NewInt(uint64(ssz)*sectorsD), true, true, types.EmptyTSK) @@ -351,7 +327,7 @@ func TestDeadlineToggling(t *testing.T) { }, nil) require.NoError(t, err) - fmt.Println("sent termination message:", smsg.Cid()) + t.Log("sent termination message:", smsg.Cid()) r, err := client.StateWaitMsg(ctx, smsg.Cid(), 2, api.LookbackNoLimit, true) require.NoError(t, err) @@ -367,13 +343,13 @@ func TestDeadlineToggling(t *testing.T) { require.NoError(t, err) if head.Height() > upgradeH+(provingPeriod*5) { - fmt.Printf("Now head.Height = %d\n", head.Height()) + t.Logf("Now head.Height = %d", head.Height()) break } build.Clock.Sleep(blocktime) } - checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.GenesisPreseals), true, true, types.EmptyTSK) + checkMiner(maddrA, types.NewInt(uint64(ssz)*kit.DefaultPresealsPerBootstrapMiner), true, true, types.EmptyTSK) checkMiner(maddrC, types.NewInt(0), true, true, types.EmptyTSK) checkMiner(maddrB, types.NewInt(0), true, true, types.EmptyTSK) checkMiner(maddrD, types.NewInt(0), false, false, types.EmptyTSK) diff --git a/itests/deals_concurrent_test.go b/itests/deals_concurrent_test.go new file mode 100644 index 000000000..44b25c7b3 --- /dev/null +++ b/itests/deals_concurrent_test.go @@ -0,0 +1,127 @@ +package itests + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + + datatransfer "github.com/filecoin-project/go-data-transfer" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/node" + "github.com/filecoin-project/lotus/node/modules" + "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo" +) + +func TestDealCyclesConcurrent(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + kit.QuietMiningLogs() + + blockTime := 10 * time.Millisecond + + // For these tests where the block time is artificially short, just use + // a deal start epoch that is guaranteed to be far enough in the future + // so that the deal starts sealing in time + startEpoch := abi.ChainEpoch(2 << 12) + + runTest := func(t *testing.T, n int, fastRetrieval bool, carExport bool) { + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + ens.InterconnectAll().BeginMining(blockTime) + dh := kit.NewDealHarness(t, client, miner) + + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{ + N: n, + FastRetrieval: fastRetrieval, + CarExport: carExport, + StartEpoch: startEpoch, + }) + } + + // TODO: add 2, 4, 8, more when this graphsync issue is fixed: https://github.com/ipfs/go-graphsync/issues/175# + cycles := []int{1} + for _, n := range cycles { + n := n + ns := fmt.Sprintf("%d", n) + t.Run(ns+"-fastretrieval-CAR", func(t *testing.T) { runTest(t, n, true, true) }) + t.Run(ns+"-fastretrieval-NoCAR", func(t *testing.T) { runTest(t, n, true, false) }) + t.Run(ns+"-stdretrieval-CAR", func(t *testing.T) { runTest(t, n, true, false) }) + t.Run(ns+"-stdretrieval-NoCAR", func(t *testing.T) { runTest(t, n, false, false) }) + } +} + +func TestSimultenousTransferLimit(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + kit.QuietMiningLogs() + + blockTime := 10 * time.Millisecond + + // For these tests where the block time is artificially short, just use + // a deal start epoch that is guaranteed to be far enough in the future + // so that the deal starts sealing in time + startEpoch := abi.ChainEpoch(2 << 12) + + runTest := func(t *testing.T) { + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ConstructorOpts( + node.ApplyIf(node.IsType(repo.StorageMiner), node.Override(new(dtypes.StagingGraphsync), modules.StagingGraphsync(2))), + )) + ens.InterconnectAll().BeginMining(blockTime) + dh := kit.NewDealHarness(t, client, miner) + + ctx, cancel := context.WithCancel(context.Background()) + + du, err := miner.MarketDataTransferUpdates(ctx) + require.NoError(t, err) + + var maxOngoing int + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + + ongoing := map[datatransfer.TransferID]struct{}{} + + for { + select { + case u := <-du: + t.Logf("%d - %s", u.TransferID, datatransfer.Statuses[u.Status]) + if u.Status == datatransfer.Ongoing { + ongoing[u.TransferID] = struct{}{} + } else { + delete(ongoing, u.TransferID) + } + + if len(ongoing) > maxOngoing { + maxOngoing = len(ongoing) + } + case <-ctx.Done(): + return + } + } + }() + + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{ + N: 1, // TODO: set to 20 after https://github.com/ipfs/go-graphsync/issues/175 is fixed + FastRetrieval: true, + StartEpoch: startEpoch, + }) + + cancel() + wg.Wait() + + require.LessOrEqual(t, maxOngoing, 2) + } + + runTest(t) +} diff --git a/itests/deals_offline_test.go b/itests/deals_offline_test.go new file mode 100644 index 000000000..c3f19048b --- /dev/null +++ b/itests/deals_offline_test.go @@ -0,0 +1,101 @@ +package itests + +import ( + "context" + "path/filepath" + "testing" + "time" + + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/stretchr/testify/require" +) + +func TestOfflineDealFlow(t *testing.T) { + blocktime := 10 * time.Millisecond + + // For these tests where the block time is artificially short, just use + // a deal start epoch that is guaranteed to be far enough in the future + // so that the deal starts sealing in time + startEpoch := abi.ChainEpoch(2 << 12) + + runTest := func(t *testing.T, fastRet bool) { + ctx := context.Background() + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + ens.InterconnectAll().BeginMining(blocktime) + + dh := kit.NewDealHarness(t, client, miner) + + // Create a random file and import on the client. + res, inFile := client.CreateImportFile(ctx, 1, 0) + + // Get the piece size and commP + rootCid := res.Root + pieceInfo, err := client.ClientDealPieceCID(ctx, rootCid) + require.NoError(t, err) + t.Log("FILE CID:", rootCid) + + // Create a storage deal with the miner + maddr, err := miner.ActorAddress(ctx) + require.NoError(t, err) + + addr, err := client.WalletDefaultAddress(ctx) + require.NoError(t, err) + + // Manual storage deal (offline deal) + dataRef := &storagemarket.DataRef{ + TransferType: storagemarket.TTManual, + Root: rootCid, + PieceCid: &pieceInfo.PieceCID, + PieceSize: pieceInfo.PieceSize.Unpadded(), + } + + proposalCid, err := client.ClientStartDeal(ctx, &api.StartDealParams{ + Data: dataRef, + Wallet: addr, + Miner: maddr, + EpochPrice: types.NewInt(1000000), + DealStartEpoch: startEpoch, + MinBlocksDuration: uint64(build.MinDealDuration), + FastRetrieval: fastRet, + }) + require.NoError(t, err) + + // Wait for the deal to reach StorageDealCheckForAcceptance on the client + cd, err := client.ClientGetDealInfo(ctx, *proposalCid) + require.NoError(t, err) + require.Eventually(t, func() bool { + cd, _ := client.ClientGetDealInfo(ctx, *proposalCid) + return cd.State == storagemarket.StorageDealCheckForAcceptance + }, 30*time.Second, 1*time.Second, "actual deal status is %s", storagemarket.DealStates[cd.State]) + + // Create a CAR file from the raw file + carFileDir := t.TempDir() + carFilePath := filepath.Join(carFileDir, "out.car") + err = client.ClientGenCar(ctx, api.FileRef{Path: inFile}, carFilePath) + require.NoError(t, err) + + // Import the CAR file on the miner - this is the equivalent to + // transferring the file across the wire in a normal (non-offline) deal + err = miner.DealsImportData(ctx, *proposalCid, carFilePath) + require.NoError(t, err) + + // Wait for the deal to be published + dh.WaitDealPublished(ctx, proposalCid) + + t.Logf("deal published, retrieving") + + // Retrieve the deal + outFile := dh.PerformRetrieval(ctx, proposalCid, rootCid, false) + + kit.AssertFilesEqual(t, inFile, outFile) + + } + + t.Run("stdretrieval", func(t *testing.T) { runTest(t, false) }) + t.Run("fastretrieval", func(t *testing.T) { runTest(t, true) }) +} diff --git a/itests/deals_power_test.go b/itests/deals_power_test.go new file mode 100644 index 000000000..ebf1895e3 --- /dev/null +++ b/itests/deals_power_test.go @@ -0,0 +1,61 @@ +package itests + +import ( + "context" + "testing" + "time" + + "github.com/filecoin-project/lotus/itests/kit" +) + +func TestFirstDealEnablesMining(t *testing.T) { + // test making a deal with a fresh miner, and see if it starts to mine. + if testing.Short() { + t.Skip("skipping test in short mode") + } + + kit.QuietMiningLogs() + + var ( + client kit.TestFullNode + genMiner kit.TestMiner // bootstrap + provider kit.TestMiner // no sectors, will need to create one + ) + + ens := kit.NewEnsemble(t, kit.MockProofs()) + ens.FullNode(&client) + ens.Miner(&genMiner, &client) + ens.Miner(&provider, &client, kit.PresealSectors(0)) + ens.Start().InterconnectAll().BeginMining(50 * time.Millisecond) + + ctx := context.Background() + + dh := kit.NewDealHarness(t, &client, &provider) + + ref, _ := client.CreateImportFile(ctx, 5, 0) + + t.Log("FILE CID:", ref.Root) + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // start a goroutine to monitor head changes from the client + // once the provider has mined a block, thanks to the power acquired from the deal, + // we pass the test. + providerMined := make(chan struct{}) + + go func() { + _ = client.WaitTillChain(ctx, kit.BlockMinedBy(provider.ActorAddr)) + close(providerMined) + }() + + // now perform the deal. + deal := dh.StartDeal(ctx, ref.Root, false, 0) + + // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this + time.Sleep(time.Second) + + dh.WaitDealSealed(ctx, deal, false, false, nil) + + <-providerMined +} diff --git a/itests/deals_pricing_test.go b/itests/deals_pricing_test.go new file mode 100644 index 000000000..357abec1e --- /dev/null +++ b/itests/deals_pricing_test.go @@ -0,0 +1,131 @@ +package itests + +import ( + "context" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/extern/sector-storage/storiface" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/stretchr/testify/require" +) + +func TestQuotePriceForUnsealedRetrieval(t *testing.T) { + var ( + ctx = context.Background() + blocktime = time.Second + ) + + kit.QuietMiningLogs() + + client, miner, ens := kit.EnsembleMinimal(t) + ens.InterconnectAll().BeginMining(blocktime) + + var ( + ppb = int64(1) + unsealPrice = int64(77) + ) + + // Set unsealed price to non-zero + ask, err := miner.MarketGetRetrievalAsk(ctx) + require.NoError(t, err) + ask.PricePerByte = abi.NewTokenAmount(ppb) + ask.UnsealPrice = abi.NewTokenAmount(unsealPrice) + err = miner.MarketSetRetrievalAsk(ctx, ask) + require.NoError(t, err) + + dh := kit.NewDealHarness(t, client, miner) + + deal1, res1, _ := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{Rseed: 6}) + + // one more storage deal for the same data + _, res2, _ := dh.MakeOnlineDeal(ctx, kit.MakeFullDealParams{Rseed: 6}) + require.Equal(t, res1.Root, res2.Root) + + // Retrieval + dealInfo, err := client.ClientGetDealInfo(ctx, *deal1) + require.NoError(t, err) + + // fetch quote -> zero for unsealed price since unsealed file already exists. + offers, err := client.ClientFindData(ctx, res1.Root, &dealInfo.PieceCID) + require.NoError(t, err) + require.Len(t, offers, 2) + require.Equal(t, offers[0], offers[1]) + require.Equal(t, uint64(0), offers[0].UnsealPrice.Uint64()) + require.Equal(t, dealInfo.Size*uint64(ppb), offers[0].MinPrice.Uint64()) + + // remove ONLY one unsealed file + ss, err := miner.StorageList(context.Background()) + require.NoError(t, err) + _, err = miner.SectorsList(ctx) + require.NoError(t, err) + +iLoop: + for storeID, sd := range ss { + for _, sector := range sd { + err := miner.StorageDropSector(ctx, storeID, sector.SectorID, storiface.FTUnsealed) + require.NoError(t, err) + break iLoop // remove ONLY one + } + } + + // get retrieval quote -> zero for unsealed price as unsealed file exists. + offers, err = client.ClientFindData(ctx, res1.Root, &dealInfo.PieceCID) + require.NoError(t, err) + require.Len(t, offers, 2) + require.Equal(t, offers[0], offers[1]) + require.Equal(t, uint64(0), offers[0].UnsealPrice.Uint64()) + require.Equal(t, dealInfo.Size*uint64(ppb), offers[0].MinPrice.Uint64()) + + // remove the other unsealed file as well + ss, err = miner.StorageList(context.Background()) + require.NoError(t, err) + _, err = miner.SectorsList(ctx) + require.NoError(t, err) + for storeID, sd := range ss { + for _, sector := range sd { + require.NoError(t, miner.StorageDropSector(ctx, storeID, sector.SectorID, storiface.FTUnsealed)) + } + } + + // fetch quote -> non-zero for unseal price as we no more unsealed files. + offers, err = client.ClientFindData(ctx, res1.Root, &dealInfo.PieceCID) + require.NoError(t, err) + require.Len(t, offers, 2) + require.Equal(t, offers[0], offers[1]) + require.Equal(t, uint64(unsealPrice), offers[0].UnsealPrice.Uint64()) + total := (dealInfo.Size * uint64(ppb)) + uint64(unsealPrice) + require.Equal(t, total, offers[0].MinPrice.Uint64()) +} + +func TestZeroPricePerByteRetrieval(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + kit.QuietMiningLogs() + + var ( + blockTime = 10 * time.Millisecond + startEpoch = abi.ChainEpoch(2 << 12) + ) + + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + ens.InterconnectAll().BeginMining(blockTime) + + ctx := context.Background() + + ask, err := miner.MarketGetRetrievalAsk(ctx) + require.NoError(t, err) + + ask.PricePerByte = abi.NewTokenAmount(0) + err = miner.MarketSetRetrievalAsk(ctx, ask) + require.NoError(t, err) + + dh := kit.NewDealHarness(t, client, miner) + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{ + N: 1, + StartEpoch: startEpoch, + }) +} diff --git a/itests/deals_publish_test.go b/itests/deals_publish_test.go new file mode 100644 index 000000000..16f84038b --- /dev/null +++ b/itests/deals_publish_test.go @@ -0,0 +1,108 @@ +package itests + +import ( + "bytes" + "context" + "testing" + "time" + + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/markets/storageadapter" + "github.com/filecoin-project/lotus/node" + market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" + "github.com/stretchr/testify/require" +) + +func TestPublishDealsBatching(t *testing.T) { + var ( + ctx = context.Background() + publishPeriod = 10 * time.Second + maxDealsPerMsg = uint64(2) // Set max deals per publish deals message to 2 + startEpoch = abi.ChainEpoch(2 << 12) + ) + + kit.QuietMiningLogs() + + opts := node.Override(new(*storageadapter.DealPublisher), + storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ + Period: publishPeriod, + MaxDealsPerMsg: maxDealsPerMsg, + }), + ) + + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ConstructorOpts(opts)) + ens.InterconnectAll().BeginMining(10 * time.Millisecond) + + dh := kit.NewDealHarness(t, client, miner) + + // Starts a deal and waits until it's published + runDealTillPublish := func(rseed int) { + res, _ := client.CreateImportFile(ctx, rseed, 0) + + upds, err := client.ClientGetDealUpdates(ctx) + require.NoError(t, err) + + dh.StartDeal(ctx, res.Root, false, startEpoch) + + // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this + time.Sleep(time.Second) + + done := make(chan struct{}) + go func() { + for upd := range upds { + if upd.DataRef.Root == res.Root && upd.State == storagemarket.StorageDealAwaitingPreCommit { + done <- struct{}{} + } + } + }() + <-done + } + + // Run three deals in parallel + done := make(chan struct{}, maxDealsPerMsg+1) + for rseed := 1; rseed <= 3; rseed++ { + rseed := rseed + go func() { + runDealTillPublish(rseed) + done <- struct{}{} + }() + } + + // Wait for two of the deals to be published + for i := 0; i < int(maxDealsPerMsg); i++ { + <-done + } + + // Expect a single PublishStorageDeals message that includes the first two deals + msgCids, err := client.StateListMessages(ctx, &api.MessageMatch{To: market.Address}, types.EmptyTSK, 1) + require.NoError(t, err) + count := 0 + for _, msgCid := range msgCids { + msg, err := client.ChainGetMessage(ctx, msgCid) + require.NoError(t, err) + + if msg.Method == market.Methods.PublishStorageDeals { + count++ + var pubDealsParams market2.PublishStorageDealsParams + err = pubDealsParams.UnmarshalCBOR(bytes.NewReader(msg.Params)) + require.NoError(t, err) + require.Len(t, pubDealsParams.Deals, int(maxDealsPerMsg)) + } + } + require.Equal(t, 1, count) + + // The third deal should be published once the publish period expires. + // Allow a little padding as it takes a moment for the state change to + // be noticed by the client. + padding := 10 * time.Second + select { + case <-time.After(publishPeriod + padding): + require.Fail(t, "Expected 3rd deal to be published once publish period elapsed") + case <-done: // Success + } +} diff --git a/itests/deals_test.go b/itests/deals_test.go index e5909cb80..f8389bbd6 100644 --- a/itests/deals_test.go +++ b/itests/deals_test.go @@ -1,633 +1,35 @@ package itests import ( - "bytes" - "context" - "fmt" - "io/ioutil" - "math/rand" - "os" - "path/filepath" - "sync/atomic" "testing" "time" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors/builtin/market" - "github.com/filecoin-project/lotus/chain/actors/policy" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/extern/sector-storage/storiface" "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/markets/storageadapter" - "github.com/filecoin-project/lotus/miner" - "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/impl" - market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" - "github.com/stretchr/testify/require" ) -func TestDealCycle(t *testing.T) { - kit.QuietMiningLogs() - - blockTime := 10 * time.Millisecond - - // For these tests where the block time is artificially short, just use - // a deal start epoch that is guaranteed to be far enough in the future - // so that the deal starts sealing in time - dealStartEpoch := abi.ChainEpoch(2 << 12) - - t.Run("TestFullDealCycle_Single", func(t *testing.T) { - runFullDealCycles(t, 1, kit.MockMinerBuilder, blockTime, false, false, dealStartEpoch) - }) - t.Run("TestFullDealCycle_Two", func(t *testing.T) { - runFullDealCycles(t, 2, kit.MockMinerBuilder, blockTime, false, false, dealStartEpoch) - }) - t.Run("WithExportedCAR", func(t *testing.T) { - runFullDealCycles(t, 1, kit.MockMinerBuilder, blockTime, true, false, dealStartEpoch) - }) - t.Run("TestFastRetrievalDealCycle", func(t *testing.T) { - runFastRetrievalDealFlowT(t, kit.MockMinerBuilder, blockTime, dealStartEpoch) - }) - t.Run("TestZeroPricePerByteRetrievalDealFlow", func(t *testing.T) { - runZeroPricePerByteRetrievalDealFlow(t, kit.MockMinerBuilder, blockTime, dealStartEpoch) - }) -} - -func TestAPIDealFlowReal(t *testing.T) { +func TestDealsWithSealingAndRPC(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode") } kit.QuietMiningLogs() - // TODO: just set this globally? - oldDelay := policy.GetPreCommitChallengeDelay() - policy.SetPreCommitChallengeDelay(5) - t.Cleanup(func() { - policy.SetPreCommitChallengeDelay(oldDelay) - }) - - t.Run("basic", func(t *testing.T) { - runFullDealCycles(t, 1, kit.Builder, time.Second, false, false, 0) - }) - - t.Run("fast-retrieval", func(t *testing.T) { - runFullDealCycles(t, 1, kit.Builder, time.Second, false, true, 0) - }) - - t.Run("retrieval-second", func(t *testing.T) { - runSecondDealRetrievalTest(t, kit.Builder, time.Second) - }) - - t.Run("quote-price-for-non-unsealed-retrieval", func(t *testing.T) { - runQuotePriceForUnsealedRetrieval(t, kit.Builder, time.Second, 0) - }) -} - -func runQuotePriceForUnsealedRetrieval(t *testing.T, b kit.APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { - ctx := context.Background() - fulls, miners := b(t, kit.OneFull, kit.OneMiner) - client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] - kit.ConnectAndStartMining(t, blocktime, miner, client) - - ppb := int64(1) - unsealPrice := int64(77) - - // Set unsealed price to non-zero - ask, err := miner.MarketGetRetrievalAsk(ctx) - require.NoError(t, err) - ask.PricePerByte = abi.NewTokenAmount(ppb) - ask.UnsealPrice = abi.NewTokenAmount(unsealPrice) - err = miner.MarketSetRetrievalAsk(ctx, ask) - require.NoError(t, err) + var blockTime = 1 * time.Second + client, miner, ens := kit.EnsembleMinimal(t, kit.ThroughRPC()) // no mock proofs. + ens.InterconnectAll().BeginMining(blockTime) dh := kit.NewDealHarness(t, client, miner) - _, info, fcid := dh.MakeFullDeal(kit.MakeFullDealParams{ - Ctx: ctx, - Rseed: 6, - CarExport: false, - FastRet: false, - StartEpoch: startEpoch, - DoRetrieval: false, + t.Run("stdretrieval", func(t *testing.T) { + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1}) }) - // one more storage deal for the same data - _, _, fcid2 := dh.MakeFullDeal(kit.MakeFullDealParams{ - Ctx: ctx, - Rseed: 6, - CarExport: false, - FastRet: false, - StartEpoch: startEpoch, - DoRetrieval: false, - }) - require.Equal(t, fcid, fcid2) - - // fetch quote -> zero for unsealed price since unsealed file already exists. - offers, err := client.ClientFindData(ctx, fcid, &info.PieceCID) - require.NoError(t, err) - require.Len(t, offers, 2) - require.Equal(t, offers[0], offers[1]) - require.Equal(t, uint64(0), offers[0].UnsealPrice.Uint64()) - require.Equal(t, info.Size*uint64(ppb), offers[0].MinPrice.Uint64()) - - // remove ONLY one unsealed file - ss, err := miner.StorageList(context.Background()) - require.NoError(t, err) - _, err = miner.SectorsList(ctx) - require.NoError(t, err) - -iLoop: - for storeID, sd := range ss { - for _, sector := range sd { - require.NoError(t, miner.StorageDropSector(ctx, storeID, sector.SectorID, storiface.FTUnsealed)) - // remove ONLY one - break iLoop - } - } - - // get retrieval quote -> zero for unsealed price as unsealed file exists. - offers, err = client.ClientFindData(ctx, fcid, &info.PieceCID) - require.NoError(t, err) - require.Len(t, offers, 2) - require.Equal(t, offers[0], offers[1]) - require.Equal(t, uint64(0), offers[0].UnsealPrice.Uint64()) - require.Equal(t, info.Size*uint64(ppb), offers[0].MinPrice.Uint64()) - - // remove the other unsealed file as well - ss, err = miner.StorageList(context.Background()) - require.NoError(t, err) - _, err = miner.SectorsList(ctx) - require.NoError(t, err) - for storeID, sd := range ss { - for _, sector := range sd { - require.NoError(t, miner.StorageDropSector(ctx, storeID, sector.SectorID, storiface.FTUnsealed)) - } - } - - // fetch quote -> non-zero for unseal price as we no more unsealed files. - offers, err = client.ClientFindData(ctx, fcid, &info.PieceCID) - require.NoError(t, err) - require.Len(t, offers, 2) - require.Equal(t, offers[0], offers[1]) - require.Equal(t, uint64(unsealPrice), offers[0].UnsealPrice.Uint64()) - total := (info.Size * uint64(ppb)) + uint64(unsealPrice) - require.Equal(t, total, offers[0].MinPrice.Uint64()) -} - -func TestPublishDealsBatching(t *testing.T) { - ctx := context.Background() - - kit.QuietMiningLogs() - - b := kit.MockMinerBuilder - blocktime := 10 * time.Millisecond - startEpoch := abi.ChainEpoch(2 << 12) - - publishPeriod := 10 * time.Second - maxDealsPerMsg := uint64(2) - - // Set max deals per publish deals message to 2 - minerDef := []kit.StorageMiner{{ - Full: 0, - Opts: node.Override( - new(*storageadapter.DealPublisher), - storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ - Period: publishPeriod, - MaxDealsPerMsg: maxDealsPerMsg, - })), - Preseal: kit.PresealGenesis, - }} - - // Create a connect client and miner node - n, sn := b(t, kit.OneFull, minerDef) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - kit.ConnectAndStartMining(t, blocktime, miner, client) - - dh := kit.NewDealHarness(t, client, miner) - - // Starts a deal and waits until it's published - runDealTillPublish := func(rseed int) { - res, _, _, err := kit.CreateImportFile(ctx, client, rseed, 0) - require.NoError(t, err) - - upds, err := client.ClientGetDealUpdates(ctx) - require.NoError(t, err) - - dh.StartDeal(ctx, res.Root, false, startEpoch) - - // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this - time.Sleep(time.Second) - - done := make(chan struct{}) - go func() { - for upd := range upds { - if upd.DataRef.Root == res.Root && upd.State == storagemarket.StorageDealAwaitingPreCommit { - done <- struct{}{} - } - } - }() - <-done - } - - // Run three deals in parallel - done := make(chan struct{}, maxDealsPerMsg+1) - for rseed := 1; rseed <= 3; rseed++ { - rseed := rseed - go func() { - runDealTillPublish(rseed) - done <- struct{}{} - }() - } - - // Wait for two of the deals to be published - for i := 0; i < int(maxDealsPerMsg); i++ { - <-done - } - - // Expect a single PublishStorageDeals message that includes the first two deals - msgCids, err := client.StateListMessages(ctx, &api.MessageMatch{To: market.Address}, types.EmptyTSK, 1) - require.NoError(t, err) - count := 0 - for _, msgCid := range msgCids { - msg, err := client.ChainGetMessage(ctx, msgCid) - require.NoError(t, err) - - if msg.Method == market.Methods.PublishStorageDeals { - count++ - var pubDealsParams market2.PublishStorageDealsParams - err = pubDealsParams.UnmarshalCBOR(bytes.NewReader(msg.Params)) - require.NoError(t, err) - require.Len(t, pubDealsParams.Deals, int(maxDealsPerMsg)) - } - } - require.Equal(t, 1, count) - - // The third deal should be published once the publish period expires. - // Allow a little padding as it takes a moment for the state change to - // be noticed by the client. - padding := 10 * time.Second - select { - case <-time.After(publishPeriod + padding): - require.Fail(t, "Expected 3rd deal to be published once publish period elapsed") - case <-done: // Success - } -} - -func TestDealMining(t *testing.T) { - // test making a deal with a fresh miner, and see if it starts to mine. - if testing.Short() { - t.Skip("skipping test in short mode") - } - - kit.QuietMiningLogs() - - b := kit.MockMinerBuilder - blocktime := 50 * time.Millisecond - - ctx := context.Background() - fulls, miners := b(t, - kit.OneFull, - []kit.StorageMiner{ - {Full: 0, Preseal: kit.PresealGenesis}, - {Full: 0, Preseal: 0}, // TODO: Add support for miners on non-first full node - }) - client := fulls[0].FullNode.(*impl.FullNodeAPI) - genesisMiner := miners[0] - provider := miners[1] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := provider.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - - if err := genesisMiner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - - time.Sleep(time.Second) - - data := make([]byte, 600) - rand.New(rand.NewSource(5)).Read(data) - - r := bytes.NewReader(data) - fcid, err := client.ClientImportLocal(ctx, r) - if err != nil { - t.Fatal(err) - } - - fmt.Println("FILE CID: ", fcid) - - var mine int32 = 1 - done := make(chan struct{}) - minedTwo := make(chan struct{}) - - m2addr, err := miners[1].ActorAddress(context.TODO()) - if err != nil { - t.Fatal(err) - } - - go func() { - defer close(done) - - complChan := minedTwo - for atomic.LoadInt32(&mine) != 0 { - wait := make(chan int) - mdone := func(mined bool, _ abi.ChainEpoch, err error) { - n := 0 - if mined { - n = 1 - } - wait <- n - } - - if err := miners[0].MineOne(ctx, miner.MineReq{Done: mdone}); err != nil { - t.Error(err) - } - - if err := miners[1].MineOne(ctx, miner.MineReq{Done: mdone}); err != nil { - t.Error(err) - } - - expect := <-wait - expect += <-wait - - time.Sleep(blocktime) - if expect == 0 { - // null block - continue - } - - var nodeOneMined bool - for _, node := range miners { - mb, err := node.MiningBase(ctx) - if err != nil { - t.Error(err) - return - } - - for _, b := range mb.Blocks() { - if b.Miner == m2addr { - nodeOneMined = true - break - } - } - - } - - if nodeOneMined && complChan != nil { - close(complChan) - complChan = nil - } - - } - }() - - dh := kit.NewDealHarness(t, client, provider) - - deal := dh.StartDeal(ctx, fcid, false, 0) - - // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this - time.Sleep(time.Second) - - dh.WaitDealSealed(ctx, deal, false, false, nil) - - <-minedTwo - - atomic.StoreInt32(&mine, 0) - fmt.Println("shutting down mining") - <-done -} - -func TestOfflineDealFlow(t *testing.T) { - blocktime := 10 * time.Millisecond - - // For these tests where the block time is artificially short, just use - // a deal start epoch that is guaranteed to be far enough in the future - // so that the deal starts sealing in time - startEpoch := abi.ChainEpoch(2 << 12) - - runTest := func(t *testing.T, fastRet bool) { - ctx := context.Background() - fulls, miners := kit.MockMinerBuilder(t, kit.OneFull, kit.OneMiner) - client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] - - kit.ConnectAndStartMining(t, blocktime, miner, client) - - dh := kit.NewDealHarness(t, client, miner) - - // Create a random file and import on the client. - res, path, data, err := kit.CreateImportFile(ctx, client, 1, 0) - require.NoError(t, err) - - // Get the piece size and commP - fcid := res.Root - pieceInfo, err := client.ClientDealPieceCID(ctx, fcid) - require.NoError(t, err) - fmt.Println("FILE CID: ", fcid) - - // Create a storage deal with the miner - maddr, err := miner.ActorAddress(ctx) - require.NoError(t, err) - - addr, err := client.WalletDefaultAddress(ctx) - require.NoError(t, err) - - // Manual storage deal (offline deal) - dataRef := &storagemarket.DataRef{ - TransferType: storagemarket.TTManual, - Root: fcid, - PieceCid: &pieceInfo.PieceCID, - PieceSize: pieceInfo.PieceSize.Unpadded(), - } - - proposalCid, err := client.ClientStartDeal(ctx, &api.StartDealParams{ - Data: dataRef, - Wallet: addr, - Miner: maddr, - EpochPrice: types.NewInt(1000000), - DealStartEpoch: startEpoch, - MinBlocksDuration: uint64(build.MinDealDuration), - FastRetrieval: fastRet, - }) - require.NoError(t, err) - - // Wait for the deal to reach StorageDealCheckForAcceptance on the client - cd, err := client.ClientGetDealInfo(ctx, *proposalCid) - require.NoError(t, err) - require.Eventually(t, func() bool { - cd, _ := client.ClientGetDealInfo(ctx, *proposalCid) - return cd.State == storagemarket.StorageDealCheckForAcceptance - }, 30*time.Second, 1*time.Second, "actual deal status is %s", storagemarket.DealStates[cd.State]) - - // Create a CAR file from the raw file - carFileDir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-car") - require.NoError(t, err) - carFilePath := filepath.Join(carFileDir, "out.car") - err = client.ClientGenCar(ctx, api.FileRef{Path: path}, carFilePath) - require.NoError(t, err) - - // Import the CAR file on the miner - this is the equivalent to - // transferring the file across the wire in a normal (non-offline) deal - err = miner.DealsImportData(ctx, *proposalCid, carFilePath) - require.NoError(t, err) - - // Wait for the deal to be published - dh.WaitDealPublished(ctx, proposalCid) - - t.Logf("deal published, retrieving") - - // Retrieve the deal - dh.TestRetrieval(ctx, fcid, &pieceInfo.PieceCID, false, data) - } - - t.Run("NormalRetrieval", func(t *testing.T) { - runTest(t, false) - }) - t.Run("FastRetrieval", func(t *testing.T) { - runTest(t, true) + t.Run("fastretrieval", func(t *testing.T) { + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1, FastRetrieval: true}) }) -} - -func runFullDealCycles(t *testing.T, n int, b kit.APIBuilder, blocktime time.Duration, carExport, fastRet bool, startEpoch abi.ChainEpoch) { - fulls, miners := b(t, kit.OneFull, kit.OneMiner) - client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] - - kit.ConnectAndStartMining(t, blocktime, miner, client) - - dh := kit.NewDealHarness(t, client, miner) - - baseseed := 6 - for i := 0; i < n; i++ { - _, _, _ = dh.MakeFullDeal(kit.MakeFullDealParams{ - Ctx: context.Background(), - Rseed: baseseed + i, - CarExport: carExport, - FastRet: fastRet, - StartEpoch: startEpoch, - DoRetrieval: true, - }) - } -} - -func runFastRetrievalDealFlowT(t *testing.T, b kit.APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { - ctx := context.Background() - - fulls, miners := b(t, kit.OneFull, kit.OneMiner) - client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] - - kit.ConnectAndStartMining(t, blocktime, miner, client) - - dh := kit.NewDealHarness(t, client, miner) - - data := make([]byte, 1600) - rand.New(rand.NewSource(int64(8))).Read(data) - - r := bytes.NewReader(data) - fcid, err := client.ClientImportLocal(ctx, r) - if err != nil { - t.Fatal(err) - } - - fmt.Println("FILE CID: ", fcid) - - deal := dh.StartDeal(ctx, fcid, true, startEpoch) - dh.WaitDealPublished(ctx, deal) - - fmt.Println("deal published, retrieving") - - // Retrieval - info, err := client.ClientGetDealInfo(ctx, *deal) - require.NoError(t, err) - - dh.TestRetrieval(ctx, fcid, &info.PieceCID, false, data) -} - -func runSecondDealRetrievalTest(t *testing.T, b kit.APIBuilder, blocktime time.Duration) { - ctx := context.Background() - - fulls, miners := b(t, kit.OneFull, kit.OneMiner) - client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] - - kit.ConnectAndStartMining(t, blocktime, miner, client) - - dh := kit.NewDealHarness(t, client, miner) - - { - data1 := make([]byte, 800) - rand.New(rand.NewSource(int64(3))).Read(data1) - r := bytes.NewReader(data1) - - fcid1, err := client.ClientImportLocal(ctx, r) - if err != nil { - t.Fatal(err) - } - - data2 := make([]byte, 800) - rand.New(rand.NewSource(int64(9))).Read(data2) - r2 := bytes.NewReader(data2) - - fcid2, err := client.ClientImportLocal(ctx, r2) - if err != nil { - t.Fatal(err) - } - - deal1 := dh.StartDeal(ctx, fcid1, true, 0) - - // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this - time.Sleep(time.Second) - dh.WaitDealSealed(ctx, deal1, true, false, nil) - - deal2 := dh.StartDeal(ctx, fcid2, true, 0) - - time.Sleep(time.Second) - dh.WaitDealSealed(ctx, deal2, false, false, nil) - - // Retrieval - info, err := client.ClientGetDealInfo(ctx, *deal2) - require.NoError(t, err) - - rf, _ := miner.SectorsRefs(ctx) - fmt.Printf("refs: %+v\n", rf) - - dh.TestRetrieval(ctx, fcid2, &info.PieceCID, false, data2) - } -} - -func runZeroPricePerByteRetrievalDealFlow(t *testing.T, b kit.APIBuilder, blocktime time.Duration, startEpoch abi.ChainEpoch) { - ctx := context.Background() - - fulls, miners := b(t, kit.OneFull, kit.OneMiner) - client, miner := fulls[0].FullNode.(*impl.FullNodeAPI), miners[0] - - kit.ConnectAndStartMining(t, blocktime, miner, client) - - dh := kit.NewDealHarness(t, client, miner) - - // Set price-per-byte to zero - ask, err := miner.MarketGetRetrievalAsk(ctx) - require.NoError(t, err) - - ask.PricePerByte = abi.NewTokenAmount(0) - err = miner.MarketSetRetrievalAsk(ctx, ask) - require.NoError(t, err) - - _, _, _ = dh.MakeFullDeal(kit.MakeFullDealParams{ - Ctx: ctx, - Rseed: 6, - CarExport: false, - FastRet: false, - StartEpoch: startEpoch, - DoRetrieval: true, + t.Run("fastretrieval-twodeals-sequential", func(t *testing.T) { + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1, FastRetrieval: true}) + dh.RunConcurrentDeals(kit.RunConcurrentDealsOpts{N: 1, FastRetrieval: true}) }) } diff --git a/itests/gateway_test.go b/itests/gateway_test.go index f5eb4e363..11f759570 100644 --- a/itests/gateway_test.go +++ b/itests/gateway_test.go @@ -5,30 +5,28 @@ import ( "context" "fmt" "math" - "os" "testing" "time" - "github.com/filecoin-project/lotus/chain/stmgr" - "github.com/stretchr/testify/require" - "golang.org/x/xerrors" - "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-state-types/abi" - "github.com/ipfs/go-cid" - "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/client" - "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/gateway" "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/multisig" "github.com/filecoin-project/lotus/node" init2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/init" multisig2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/multisig" + + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" ) const ( @@ -36,22 +34,14 @@ const ( maxStateWaitLookbackLimit = stmgr.LookbackNoLimit ) -func init() { - policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) - policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) - policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) -} - // TestGatewayWalletMsig tests that API calls to wallet and msig can be made on a lite // node that is connected through a gateway to a full API node func TestGatewayWalletMsig(t *testing.T) { - _ = os.Setenv("BELLMAN_NO_GPU", "1") kit.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() nodes := startNodes(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) - defer nodes.closer() lite := nodes.lite full := nodes.full @@ -111,7 +101,6 @@ func TestGatewayWalletMsig(t *testing.T) { if err != nil { return cid.Undef, err } - return lite.MpoolPush(ctx, sm) } @@ -179,26 +168,24 @@ func TestGatewayWalletMsig(t *testing.T) { // TestGatewayMsigCLI tests that msig CLI calls can be made // on a lite node that is connected through a gateway to a full API node func TestGatewayMsigCLI(t *testing.T) { - _ = os.Setenv("BELLMAN_NO_GPU", "1") kit.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) - defer nodes.closer() lite := nodes.lite - runMultisigTests(t, lite) + multisig.RunMultisigTests(t, lite) } func TestGatewayDealFlow(t *testing.T) { - _ = os.Setenv("BELLMAN_NO_GPU", "1") kit.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) - defer nodes.closer() + + time.Sleep(5 * time.Second) // For these tests where the block time is artificially short, just use // a deal start epoch that is guaranteed to be far enough in the future @@ -206,33 +193,27 @@ func TestGatewayDealFlow(t *testing.T) { dealStartEpoch := abi.ChainEpoch(2 << 12) dh := kit.NewDealHarness(t, nodes.lite, nodes.miner) - dh.MakeFullDeal(kit.MakeFullDealParams{ - Ctx: ctx, - Rseed: 6, - CarExport: false, - FastRet: false, - StartEpoch: dealStartEpoch, - DoRetrieval: true, + dealCid, res, _ := dh.MakeOnlineDeal(context.Background(), kit.MakeFullDealParams{ + Rseed: 6, + StartEpoch: dealStartEpoch, }) + dh.PerformRetrieval(ctx, dealCid, res.Root, false) } func TestGatewayCLIDealFlow(t *testing.T) { - _ = os.Setenv("BELLMAN_NO_GPU", "1") kit.QuietMiningLogs() blocktime := 5 * time.Millisecond ctx := context.Background() nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) - defer nodes.closer() kit.RunClientTest(t, cli.Commands, nodes.lite) } type testNodes struct { - lite kit.TestFullNode - full kit.TestFullNode - miner kit.TestMiner - closer jsonrpc.ClientCloser + lite *kit.TestFullNode + full *kit.TestFullNode + miner *kit.TestMiner } func startNodesWithFunds( @@ -248,8 +229,8 @@ func startNodesWithFunds( fullWalletAddr, err := nodes.full.WalletDefaultAddress(ctx) require.NoError(t, err) - // Create a wallet on the lite node - liteWalletAddr, err := nodes.lite.WalletNew(ctx, types.KTSecp256k1) + // Get the lite node default wallet address. + liteWalletAddr, err := nodes.lite.WalletDefaultAddress(ctx) require.NoError(t, err) // Send some funds from the full node to the lite node @@ -268,66 +249,47 @@ func startNodes( ) *testNodes { var closer jsonrpc.ClientCloser - // Create one miner and two full nodes. + var ( + full *kit.TestFullNode + miner *kit.TestMiner + lite kit.TestFullNode + ) + + // - Create one full node and one lite node // - Put a gateway server in front of full node 1 // - Start full node 2 in lite mode // - Connect lite node -> gateway server -> full node - opts := append( - // Full node - kit.OneFull, - // Lite node - kit.FullNodeOpts{ - Lite: true, - Opts: func(nodes []kit.TestFullNode) node.Option { - fullNode := nodes[0] - // Create a gateway server in front of the full node - gwapi := gateway.NewNode(fullNode, lookbackCap, stateWaitLookbackLimit) - handler, err := gateway.Handler(gwapi) - require.NoError(t, err) + // create the full node and the miner. + var ens *kit.Ensemble + full, miner, ens = kit.EnsembleMinimal(t, kit.MockProofs()) + ens.InterconnectAll().BeginMining(blocktime) - srv, _ := kit.CreateRPCServer(t, handler) - - // Create a gateway client API that connects to the gateway server - var gapi api.Gateway - gapi, closer, err = client.NewGatewayRPCV1(ctx, "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) - require.NoError(t, err) - - // Provide the gateway API to dependency injection - return node.Override(new(api.Gateway), gapi) - }, - }, - ) - n, sn := kit.RPCMockMinerBuilder(t, opts, kit.OneMiner) - - full := n[0] - lite := n[1] - miner := sn[0] - - // Get the listener address for the full node - fullAddr, err := full.NetAddrsListen(ctx) + // Create a gateway server in front of the full node + gwapi := gateway.NewNode(full, lookbackCap, stateWaitLookbackLimit) + handler, err := gateway.Handler(gwapi) require.NoError(t, err) - // Connect the miner and the full node - err = miner.NetConnect(ctx, fullAddr) - require.NoError(t, err) + srv, _ := kit.CreateRPCServer(t, handler) - // Connect the miner and the lite node (so that the lite node can send - // data to the miner) - liteAddr, err := lite.NetAddrsListen(ctx) - require.NoError(t, err) - err = miner.NetConnect(ctx, liteAddr) + // Create a gateway client API that connects to the gateway server + var gapi api.Gateway + gapi, closer, err = client.NewGatewayRPCV1(ctx, "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) require.NoError(t, err) + t.Cleanup(closer) - // Start mining blocks - bm := kit.NewBlockMiner(t, miner) - bm.MineBlocks(ctx, blocktime) - t.Cleanup(bm.Stop) + ens.FullNode(&lite, + kit.LiteNode(), + kit.ThroughRPC(), + kit.ConstructorOpts( + node.Override(new(api.Gateway), gapi), + ), + ).Start().InterconnectAll() - return &testNodes{lite: lite, full: full, miner: miner, closer: closer} + return &testNodes{lite: &lite, full: full, miner: miner} } -func sendFunds(ctx context.Context, fromNode kit.TestFullNode, fromAddr address.Address, toAddr address.Address, amt types.BigInt) error { +func sendFunds(ctx context.Context, fromNode *kit.TestFullNode, fromAddr address.Address, toAddr address.Address, amt types.BigInt) error { msg := &types.Message{ From: fromAddr, To: toAddr, diff --git a/itests/kit/blockminer.go b/itests/kit/blockminer.go index 3b1f1fedf..2c9bd47c6 100644 --- a/itests/kit/blockminer.go +++ b/itests/kit/blockminer.go @@ -15,14 +15,14 @@ import ( // BlockMiner is a utility that makes a test miner Mine blocks on a timer. type BlockMiner struct { t *testing.T - miner TestMiner + miner *TestMiner nextNulls int64 wg sync.WaitGroup cancel context.CancelFunc } -func NewBlockMiner(t *testing.T, miner TestMiner) *BlockMiner { +func NewBlockMiner(t *testing.T, miner *TestMiner) *BlockMiner { return &BlockMiner{ t: t, miner: miner, @@ -69,7 +69,7 @@ func (bm *BlockMiner) InjectNulls(rounds abi.ChainEpoch) { atomic.AddInt64(&bm.nextNulls, int64(rounds)) } -func (bm *BlockMiner) MineUntilBlock(ctx context.Context, fn TestFullNode, cb func(abi.ChainEpoch)) { +func (bm *BlockMiner) MineUntilBlock(ctx context.Context, fn *TestFullNode, cb func(abi.ChainEpoch)) { for i := 0; i < 1000; i++ { var ( success bool @@ -93,7 +93,7 @@ func (bm *BlockMiner) MineUntilBlock(ctx context.Context, fn TestFullNode, cb fu if success { // Wait until it shows up on the given full nodes ChainHead - nloops := 50 + nloops := 200 for i := 0; i < nloops; i++ { ts, err := fn.ChainHead(ctx) require.NoError(bm.t, err) diff --git a/itests/kit/client.go b/itests/kit/client.go index 6b7d46265..bd81e0c04 100644 --- a/itests/kit/client.go +++ b/itests/kit/client.go @@ -21,7 +21,7 @@ import ( ) // RunClientTest exercises some of the Client CLI commands -func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) { +func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode *TestFullNode) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -42,7 +42,7 @@ func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) require.Regexp(t, regexp.MustCompile("Ask:"), out) // Create a deal (non-interactive) - // client deal --start-epoch= 1000000attofil + // client deal --start-epoch= 1000000attofil res, _, _, err := CreateImportFile(ctx, clientNode, 1, 0) require.NoError(t, err) diff --git a/itests/kit/deals.go b/itests/kit/deals.go index f85618901..d9129b76a 100644 --- a/itests/kit/deals.go +++ b/itests/kit/deals.go @@ -1,50 +1,68 @@ package kit import ( - "bytes" "context" "fmt" "io/ioutil" "os" - "path/filepath" "testing" "time" - "github.com/ipfs/go-cid" - files "github.com/ipfs/go-ipfs-files" - "github.com/ipld/go-car" - "github.com/stretchr/testify/require" - "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" - "github.com/filecoin-project/lotus/node/impl" + "github.com/ipfs/go-cid" + files "github.com/ipfs/go-ipfs-files" ipld "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" unixfile "github.com/ipfs/go-unixfs/file" + "github.com/ipld/go-car" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" ) type DealHarness struct { t *testing.T - client api.FullNode - miner TestMiner + client *TestFullNode + miner *TestMiner } type MakeFullDealParams struct { - Ctx context.Context - Rseed int - CarExport bool - FastRet bool - StartEpoch abi.ChainEpoch - DoRetrieval bool + Rseed int + FastRet bool + StartEpoch abi.ChainEpoch + + // SuspendUntilCryptoeconStable suspends deal-making, until cryptoecon + // parameters are stabilised. This affects projected collateral, and tests + // will fail in network version 13 and higher if deals are started too soon + // after network birth. + // + // The reason is that the formula for collateral calculation takes + // circulating supply into account: + // + // [portion of power this deal will be] * [~1% of tokens]. + // + // In the first epochs after genesis, the total circulating supply is + // changing dramatically in percentual terms. Therefore, if the deal is + // proposed too soon, by the time it gets published on chain, the quoted + // provider collateral will no longer be valid. + // + // The observation is that deals fail with: + // + // GasEstimateMessageGas error: estimating gas used: message execution + // failed: exit 16, reason: Provider collateral out of bounds. (RetCode=16) + // + // Enabling this will suspend deal-making until the network has reached a + // height of 300. + SuspendUntilCryptoeconStable bool } // NewDealHarness creates a test harness that contains testing utilities for deals. -func NewDealHarness(t *testing.T, client api.FullNode, miner TestMiner) *DealHarness { +func NewDealHarness(t *testing.T, client *TestFullNode, miner *TestMiner) *DealHarness { return &DealHarness{ t: t, client: client, @@ -52,43 +70,39 @@ func NewDealHarness(t *testing.T, client api.FullNode, miner TestMiner) *DealHar } } -func (dh *DealHarness) MakeFullDeal(params MakeFullDealParams) ([]byte, - *api.DealInfo, cid.Cid) { - res, _, data, err := CreateImportFile(params.Ctx, dh.client, params.Rseed, 0) - if err != nil { - dh.t.Fatal(err) +// MakeOnlineDeal makes an online deal, generating a random file with the +// supplied seed, and setting the specified fast retrieval flag and start epoch +// on the storage deal. It returns when the deal is sealed. +// +// TODO: convert input parameters to struct, and add size as an input param. +func (dh *DealHarness) MakeOnlineDeal(ctx context.Context, params MakeFullDealParams) (deal *cid.Cid, res *api.ImportRes, path string) { + res, path = dh.client.CreateImportFile(ctx, params.Rseed, 0) + + dh.t.Logf("FILE CID: %s", res.Root) + + if params.SuspendUntilCryptoeconStable { + dh.t.Logf("deal-making suspending until cryptecon parameters have stabilised") + ts := dh.client.WaitTillChain(ctx, HeightAtLeast(300)) + dh.t.Logf("deal-making continuing; current height is %d", ts.Height()) } - fcid := res.Root - fmt.Println("FILE CID: ", fcid) - - deal := dh.StartDeal(params.Ctx, fcid, params.FastRet, params.StartEpoch) + deal = dh.StartDeal(ctx, res.Root, params.FastRet, params.StartEpoch) // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this time.Sleep(time.Second) - dh.WaitDealSealed(params.Ctx, deal, false, false, nil) + dh.WaitDealSealed(ctx, deal, false, false, nil) - // Retrieval - info, err := dh.client.ClientGetDealInfo(params.Ctx, *deal) - require.NoError(dh.t, err) - - if params.DoRetrieval { - dh.TestRetrieval(params.Ctx, fcid, &info.PieceCID, params.CarExport, data) - } - - return data, info, fcid + return deal, res, path } +// StartDeal starts a storage deal between the client and the miner. func (dh *DealHarness) StartDeal(ctx context.Context, fcid cid.Cid, fastRet bool, startEpoch abi.ChainEpoch) *cid.Cid { maddr, err := dh.miner.ActorAddress(ctx) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) addr, err := dh.client.WalletDefaultAddress(ctx) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + deal, err := dh.client.ClientStartDeal(ctx, &api.StartDealParams{ Data: &storagemarket.DataRef{ TransferType: storagemarket.TTGraphsync, @@ -101,12 +115,12 @@ func (dh *DealHarness) StartDeal(ctx context.Context, fcid cid.Cid, fastRet bool MinBlocksDuration: uint64(build.MinDealDuration), FastRetrieval: fastRet, }) - if err != nil { - dh.t.Fatalf("%+v", err) - } + require.NoError(dh.t, err) + return deal } +// WaitDealSealed waits until the deal is sealed. func (dh *DealHarness) WaitDealSealed(ctx context.Context, deal *cid.Cid, noseal, noSealStart bool, cb func()) { loop: for { @@ -128,7 +142,7 @@ loop: case storagemarket.StorageDealError: dh.t.Fatal("deal errored", di.Message) case storagemarket.StorageDealActive: - fmt.Println("COMPLETE", di) + dh.t.Log("COMPLETE", di) break loop } @@ -143,7 +157,7 @@ loop: } } - fmt.Printf("Deal %d state: client:%s provider:%s\n", di.DealID, storagemarket.DealStates[di.State], storagemarket.DealStates[minerState]) + dh.t.Logf("Deal %d state: client:%s provider:%s\n", di.DealID, storagemarket.DealStates[di.State], storagemarket.DealStates[minerState]) time.Sleep(time.Second / 2) if cb != nil { cb() @@ -151,13 +165,14 @@ loop: } } +// WaitDealPublished waits until the deal is published. func (dh *DealHarness) WaitDealPublished(ctx context.Context, deal *cid.Cid) { subCtx, cancel := context.WithCancel(ctx) defer cancel() + updates, err := dh.miner.MarketGetDealUpdates(subCtx) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + for { select { case <-ctx.Done(): @@ -172,10 +187,10 @@ func (dh *DealHarness) WaitDealPublished(ctx context.Context, deal *cid.Cid) { case storagemarket.StorageDealError: dh.t.Fatal("deal errored", di.Message) case storagemarket.StorageDealFinalizing, storagemarket.StorageDealAwaitingPreCommit, storagemarket.StorageDealSealing, storagemarket.StorageDealActive: - fmt.Println("COMPLETE", di) + dh.t.Log("COMPLETE", di) return } - fmt.Println("Deal state: ", storagemarket.DealStates[di.State]) + dh.t.Log("Deal state: ", storagemarket.DealStates[di.State]) } } } @@ -194,133 +209,103 @@ func (dh *DealHarness) StartSealingWaiting(ctx context.Context) { require.NoError(dh.t, dh.miner.SectorStartSealing(ctx, snum)) } - flushSealingBatches(dh.t, ctx, dh.miner) + dh.miner.FlushSealingBatches(ctx) } } -func (dh *DealHarness) TestRetrieval(ctx context.Context, fcid cid.Cid, piece *cid.Cid, carExport bool, expect []byte) { - offers, err := dh.client.ClientFindData(ctx, fcid, piece) - if err != nil { - dh.t.Fatal(err) - } +func (dh *DealHarness) PerformRetrieval(ctx context.Context, deal *cid.Cid, root cid.Cid, carExport bool) (path string) { + // perform retrieval. + info, err := dh.client.ClientGetDealInfo(ctx, *deal) + require.NoError(dh.t, err) - if len(offers) < 1 { - dh.t.Fatal("no offers") - } + offers, err := dh.client.ClientFindData(ctx, root, &info.PieceCID) + require.NoError(dh.t, err) + require.NotEmpty(dh.t, offers, "no offers") - rpath, err := ioutil.TempDir("", "lotus-retrieve-test-") - if err != nil { - dh.t.Fatal(err) - } - defer os.RemoveAll(rpath) //nolint:errcheck + carFile, err := ioutil.TempFile(dh.t.TempDir(), "ret-car") + require.NoError(dh.t, err) + + defer carFile.Close() //nolint:errcheck caddr, err := dh.client.WalletDefaultAddress(ctx) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) ref := &api.FileRef{ - Path: filepath.Join(rpath, "ret"), + Path: carFile.Name(), IsCAR: carExport, } + updates, err := dh.client.ClientRetrieveWithEvents(ctx, offers[0].Order(caddr), ref) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + for update := range updates { - if update.Err != "" { - dh.t.Fatalf("retrieval failed: %s", update.Err) - } - } - - rdata, err := ioutil.ReadFile(filepath.Join(rpath, "ret")) - if err != nil { - dh.t.Fatal(err) + require.Emptyf(dh.t, update.Err, "retrieval failed: %s", update.Err) } + ret := carFile.Name() if carExport { - rdata = dh.ExtractCarData(ctx, rdata, rpath) + actualFile := dh.ExtractFileFromCAR(ctx, carFile) + ret = actualFile.Name() + _ = actualFile.Close() //nolint:errcheck } - if !bytes.Equal(rdata, expect) { - dh.t.Fatal("wrong expect retrieved") - } + return ret } -func (dh *DealHarness) ExtractCarData(ctx context.Context, rdata []byte, rpath string) []byte { +func (dh *DealHarness) ExtractFileFromCAR(ctx context.Context, file *os.File) (out *os.File) { bserv := dstest.Bserv() - ch, err := car.LoadCar(bserv.Blockstore(), bytes.NewReader(rdata)) - if err != nil { - dh.t.Fatal(err) - } + ch, err := car.LoadCar(bserv.Blockstore(), file) + require.NoError(dh.t, err) + b, err := bserv.GetBlock(ctx, ch.Roots[0]) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + nd, err := ipld.Decode(b) - if err != nil { - dh.t.Fatal(err) - } + require.NoError(dh.t, err) + dserv := dag.NewDAGService(bserv) fil, err := unixfile.NewUnixfsFile(ctx, dserv, nd) - if err != nil { - dh.t.Fatal(err) - } - outPath := filepath.Join(rpath, "retLoadedCAR") - if err := files.WriteTo(fil, outPath); err != nil { - dh.t.Fatal(err) - } - rdata, err = ioutil.ReadFile(outPath) - if err != nil { - dh.t.Fatal(err) - } - return rdata + require.NoError(dh.t, err) + + tmpfile, err := ioutil.TempFile(dh.t.TempDir(), "file-in-car") + require.NoError(dh.t, err) + + defer tmpfile.Close() //nolint:errcheck + + err = files.WriteTo(fil, tmpfile.Name()) + require.NoError(dh.t, err) + + return tmpfile } -type DealsScaffold struct { - Ctx context.Context - Client *impl.FullNodeAPI - Miner TestMiner - BlockMiner *BlockMiner +type RunConcurrentDealsOpts struct { + N int + FastRetrieval bool + CarExport bool + StartEpoch abi.ChainEpoch } -func ConnectAndStartMining(t *testing.T, blocktime time.Duration, miner TestMiner, clients ...api.FullNode) *BlockMiner { - ctx := context.Background() - - for _, c := range clients { - addrinfo, err := c.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } +func (dh *DealHarness) RunConcurrentDeals(opts RunConcurrentDealsOpts) { + errgrp, _ := errgroup.WithContext(context.Background()) + for i := 0; i < opts.N; i++ { + i := i + errgrp.Go(func() (err error) { + defer func() { + // This is necessary because golang can't deal with test + // failures being reported from children goroutines ¯\_(ツ)_/¯ + if r := recover(); r != nil { + err = fmt.Errorf("deal failed: %s", r) + } + }() + deal, res, inPath := dh.MakeOnlineDeal(context.Background(), MakeFullDealParams{ + Rseed: 5 + i, + FastRet: opts.FastRetrieval, + StartEpoch: opts.StartEpoch, + }) + outPath := dh.PerformRetrieval(context.Background(), deal, res.Root, opts.CarExport) + AssertFilesEqual(dh.t, inPath, outPath) + return nil + }) } - - time.Sleep(time.Second) - - blockMiner := NewBlockMiner(t, miner) - blockMiner.MineBlocks(ctx, blocktime) - t.Cleanup(blockMiner.Stop) - return blockMiner -} - -type TestDealState int - -const ( - TestDealStateFailed = TestDealState(-1) - TestDealStateInProgress = TestDealState(0) - TestDealStateComplete = TestDealState(1) -) - -// CategorizeDealState categorizes deal states into one of three states: -// Complete, InProgress, Failed. -func CategorizeDealState(dealStatus string) TestDealState { - switch dealStatus { - case "StorageDealFailing", "StorageDealError": - return TestDealStateFailed - case "StorageDealStaged", "StorageDealAwaitingPreCommit", "StorageDealSealing", "StorageDealActive", "StorageDealExpired", "StorageDealSlashed": - return TestDealStateComplete - } - return TestDealStateInProgress + require.NoError(dh.t, errgrp.Wait()) } diff --git a/itests/kit/deals_state.go b/itests/kit/deals_state.go new file mode 100644 index 000000000..617a6d28e --- /dev/null +++ b/itests/kit/deals_state.go @@ -0,0 +1,21 @@ +package kit + +type TestDealState int + +const ( + TestDealStateFailed = TestDealState(-1) + TestDealStateInProgress = TestDealState(0) + TestDealStateComplete = TestDealState(1) +) + +// CategorizeDealState categorizes deal states into one of three states: +// Complete, InProgress, Failed. +func CategorizeDealState(dealStatus string) TestDealState { + switch dealStatus { + case "StorageDealFailing", "StorageDealError": + return TestDealStateFailed + case "StorageDealStaged", "StorageDealAwaitingPreCommit", "StorageDealSealing", "StorageDealActive", "StorageDealExpired", "StorageDealSlashed": + return TestDealStateComplete + } + return TestDealStateInProgress +} diff --git a/itests/kit/ensemble.go b/itests/kit/ensemble.go new file mode 100644 index 000000000..788aa40c0 --- /dev/null +++ b/itests/kit/ensemble.go @@ -0,0 +1,646 @@ +package kit + +import ( + "bytes" + "context" + "crypto/rand" + "io/ioutil" + "sync" + "testing" + "time" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/go-storedcounter" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/v1api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/gen" + genesis2 "github.com/filecoin-project/lotus/chain/gen/genesis" + "github.com/filecoin-project/lotus/chain/messagepool" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/wallet" + "github.com/filecoin-project/lotus/cmd/lotus-seed/seed" + sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" + "github.com/filecoin-project/lotus/extern/sector-storage/mock" + "github.com/filecoin-project/lotus/genesis" + lotusminer "github.com/filecoin-project/lotus/miner" + "github.com/filecoin-project/lotus/node" + "github.com/filecoin-project/lotus/node/config" + "github.com/filecoin-project/lotus/node/modules" + "github.com/filecoin-project/lotus/node/modules/dtypes" + testing2 "github.com/filecoin-project/lotus/node/modules/testing" + "github.com/filecoin-project/lotus/node/repo" + "github.com/filecoin-project/lotus/storage/mockstorage" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power" + "github.com/ipfs/go-datastore" + libp2pcrypto "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" + "github.com/stretchr/testify/require" +) + +func init() { + chain.BootstrapPeerThreshold = 1 + messagepool.HeadChangeCoalesceMinDelay = time.Microsecond + messagepool.HeadChangeCoalesceMaxDelay = 2 * time.Microsecond + messagepool.HeadChangeCoalesceMergeInterval = 100 * time.Nanosecond +} + +// Ensemble is a collection of nodes instantiated within a test. +// +// Create a new ensemble with: +// +// ens := kit.NewEnsemble() +// +// Create full nodes and miners: +// +// var full TestFullNode +// var miner TestMiner +// ens.FullNode(&full, opts...) // populates a full node +// ens.Miner(&miner, &full, opts...) // populates a miner, using the full node as its chain daemon +// +// It is possible to pass functional options to set initial balances, +// presealed sectors, owner keys, etc. +// +// After the initial nodes are added, call `ens.Start()` to forge genesis +// and start the network. Mining will NOT be started automatically. It needs +// to be started explicitly by calling `BeginMining`. +// +// Nodes also need to be connected with one another, either via `ens.Connect()` +// or `ens.InterconnectAll()`. A common inchantation for simple tests is to do: +// +// ens.InterconnectAll().BeginMining(blocktime) +// +// You can continue to add more nodes, but you must always follow with +// `ens.Start()` to activate the new nodes. +// +// The API is chainable, so it's possible to do a lot in a very succinct way: +// +// kit.NewEnsemble().FullNode(&full).Miner(&miner, &full).Start().InterconnectAll().BeginMining() +// +// You can also find convenient fullnode:miner presets, such as 1:1, 1:2, +// and 2:1, e.g.: +// +// kit.EnsembleMinimal() +// kit.EnsembleOneTwo() +// kit.EnsembleTwoOne() +// +type Ensemble struct { + t *testing.T + bootstrapped bool + genesisBlock bytes.Buffer + mn mocknet.Mocknet + options *ensembleOpts + + inactive struct { + fullnodes []*TestFullNode + miners []*TestMiner + } + active struct { + fullnodes []*TestFullNode + miners []*TestMiner + bms map[*TestMiner]*BlockMiner + } + genesis struct { + miners []genesis.Miner + accounts []genesis.Actor + } +} + +// NewEnsemble instantiates a new blank Ensemble. +func NewEnsemble(t *testing.T, opts ...EnsembleOpt) *Ensemble { + options := DefaultEnsembleOpts + for _, o := range opts { + err := o(&options) + require.NoError(t, err) + } + + n := &Ensemble{t: t, options: &options} + n.active.bms = make(map[*TestMiner]*BlockMiner) + + // add accounts from ensemble options to genesis. + for _, acc := range options.accounts { + n.genesis.accounts = append(n.genesis.accounts, genesis.Actor{ + Type: genesis.TAccount, + Balance: acc.initialBalance, + Meta: (&genesis.AccountMeta{Owner: acc.key.Address}).ActorMeta(), + }) + } + + return n +} + +// FullNode enrolls a new full node. +func (n *Ensemble) FullNode(full *TestFullNode, opts ...NodeOpt) *Ensemble { + options := DefaultNodeOpts + for _, o := range opts { + err := o(&options) + require.NoError(n.t, err) + } + + key, err := wallet.GenerateKey(types.KTBLS) + require.NoError(n.t, err) + + if !n.bootstrapped && !options.balance.IsZero() { + // if we still haven't forged genesis, create a key+address, and assign + // it some FIL; this will be set as the default wallet when the node is + // started. + genacc := genesis.Actor{ + Type: genesis.TAccount, + Balance: options.balance, + Meta: (&genesis.AccountMeta{Owner: key.Address}).ActorMeta(), + } + + n.genesis.accounts = append(n.genesis.accounts, genacc) + } + + *full = TestFullNode{t: n.t, options: options, DefaultKey: key} + n.inactive.fullnodes = append(n.inactive.fullnodes, full) + return n +} + +// Miner enrolls a new miner, using the provided full node for chain +// interactions. +func (n *Ensemble) Miner(miner *TestMiner, full *TestFullNode, opts ...NodeOpt) *Ensemble { + require.NotNil(n.t, full, "full node required when instantiating miner") + + options := DefaultNodeOpts + for _, o := range opts { + err := o(&options) + require.NoError(n.t, err) + } + + privkey, _, err := libp2pcrypto.GenerateEd25519Key(rand.Reader) + require.NoError(n.t, err) + + peerId, err := peer.IDFromPrivateKey(privkey) + require.NoError(n.t, err) + + tdir, err := ioutil.TempDir("", "preseal-memgen") + require.NoError(n.t, err) + + minerCnt := len(n.inactive.miners) + len(n.active.miners) + + actorAddr, err := address.NewIDAddress(genesis2.MinerStart + uint64(minerCnt)) + require.NoError(n.t, err) + + ownerKey := options.ownerKey + if !n.bootstrapped { + var ( + sectors = options.sectors + k *types.KeyInfo + genm *genesis.Miner + ) + + // create the preseal commitment. + if n.options.mockProofs { + genm, k, err = mockstorage.PreSeal(abi.RegisteredSealProof_StackedDrg2KiBV1, actorAddr, sectors) + } else { + genm, k, err = seed.PreSeal(actorAddr, abi.RegisteredSealProof_StackedDrg2KiBV1, 0, sectors, tdir, []byte("make genesis mem random"), nil, true) + } + require.NoError(n.t, err) + + genm.PeerId = peerId + + // create an owner key, and assign it some FIL. + ownerKey, err = wallet.NewKey(*k) + require.NoError(n.t, err) + + genacc := genesis.Actor{ + Type: genesis.TAccount, + Balance: options.balance, + Meta: (&genesis.AccountMeta{Owner: ownerKey.Address}).ActorMeta(), + } + + n.genesis.miners = append(n.genesis.miners, *genm) + n.genesis.accounts = append(n.genesis.accounts, genacc) + } else { + require.NotNil(n.t, ownerKey, "worker key can't be null if initializing a miner after genesis") + } + + *miner = TestMiner{ + t: n.t, + ActorAddr: actorAddr, + OwnerKey: ownerKey, + FullNode: full, + PresealDir: tdir, + options: options, + } + + miner.Libp2p.PeerID = peerId + miner.Libp2p.PrivKey = privkey + + n.inactive.miners = append(n.inactive.miners, miner) + + return n +} + +// Start starts all enrolled nodes. +func (n *Ensemble) Start() *Ensemble { + ctx := context.Background() + + var gtempl *genesis.Template + if !n.bootstrapped { + // We haven't been bootstrapped yet, we need to generate genesis and + // create the networking backbone. + gtempl = n.generateGenesis() + n.mn = mocknet.New(ctx) + } + + // --------------------- + // FULL NODES + // --------------------- + + // Create all inactive full nodes. + for i, full := range n.inactive.fullnodes { + opts := []node.Option{ + node.FullAPI(&full.FullNode, node.Lite(full.options.lite)), + node.Online(), + node.Repo(repo.NewMemory(nil)), + node.MockHost(n.mn), + node.Test(), + + // so that we subscribe to pubsub topics immediately + node.Override(new(dtypes.Bootstrapper), dtypes.Bootstrapper(true)), + } + + // append any node builder options. + opts = append(opts, full.options.extraNodeOpts...) + + // Either generate the genesis or inject it. + if i == 0 && !n.bootstrapped { + opts = append(opts, node.Override(new(modules.Genesis), testing2.MakeGenesisMem(&n.genesisBlock, *gtempl))) + } else { + opts = append(opts, node.Override(new(modules.Genesis), modules.LoadGenesis(n.genesisBlock.Bytes()))) + } + + // Are we mocking proofs? + if n.options.mockProofs { + opts = append(opts, + node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), + node.Override(new(ffiwrapper.Prover), mock.MockProver), + ) + } + + // Call option builders, passing active nodes as the parameter + for _, bopt := range full.options.optBuilders { + opts = append(opts, bopt(n.active.fullnodes)) + } + + // Construct the full node. + stop, err := node.New(ctx, opts...) + + require.NoError(n.t, err) + + addr, err := full.WalletImport(context.Background(), &full.DefaultKey.KeyInfo) + require.NoError(n.t, err) + + err = full.WalletSetDefault(context.Background(), addr) + require.NoError(n.t, err) + + // Are we hitting this node through its RPC? + if full.options.rpc { + withRPC := fullRpc(n.t, full) + n.inactive.fullnodes[i] = withRPC + } + + n.t.Cleanup(func() { _ = stop(context.Background()) }) + + n.active.fullnodes = append(n.active.fullnodes, full) + } + + // If we are here, we have processed all inactive fullnodes and moved them + // to active, so clear the slice. + n.inactive.fullnodes = n.inactive.fullnodes[:0] + + // Link all the nodes. + err := n.mn.LinkAll() + require.NoError(n.t, err) + + // --------------------- + // MINERS + // --------------------- + + // Create all inactive miners. + for i, m := range n.inactive.miners { + if n.bootstrapped { + // this is a miner created after genesis, so it won't have a preseal. + // we need to create it on chain. + params, aerr := actors.SerializeParams(&power2.CreateMinerParams{ + Owner: m.OwnerKey.Address, + Worker: m.OwnerKey.Address, + SealProofType: m.options.proofType, + Peer: abi.PeerID(m.Libp2p.PeerID), + }) + require.NoError(n.t, aerr) + + createStorageMinerMsg := &types.Message{ + From: m.OwnerKey.Address, + To: power.Address, + Value: big.Zero(), + + Method: power.Methods.CreateMiner, + Params: params, + + GasLimit: 0, + GasPremium: big.NewInt(5252), + } + signed, err := m.FullNode.FullNode.MpoolPushMessage(ctx, createStorageMinerMsg, nil) + require.NoError(n.t, err) + + mw, err := m.FullNode.FullNode.StateWaitMsg(ctx, signed.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) + require.NoError(n.t, err) + require.Equal(n.t, exitcode.Ok, mw.Receipt.ExitCode) + + var retval power2.CreateMinerReturn + err = retval.UnmarshalCBOR(bytes.NewReader(mw.Receipt.Return)) + require.NoError(n.t, err, "failed to create miner") + + m.ActorAddr = retval.IDAddress + } + + has, err := m.FullNode.WalletHas(ctx, m.OwnerKey.Address) + require.NoError(n.t, err) + + // Only import the owner's full key into our companion full node, if we + // don't have it still. + if !has { + _, err = m.FullNode.WalletImport(ctx, &m.OwnerKey.KeyInfo) + require.NoError(n.t, err) + } + + // // Set it as the default address. + // err = m.FullNode.WalletSetDefault(ctx, m.OwnerAddr.Address) + // require.NoError(n.t, err) + + r := repo.NewMemory(nil) + + lr, err := r.Lock(repo.StorageMiner) + require.NoError(n.t, err) + + ks, err := lr.KeyStore() + require.NoError(n.t, err) + + pk, err := m.Libp2p.PrivKey.Bytes() + require.NoError(n.t, err) + + err = ks.Put("libp2p-host", types.KeyInfo{ + Type: "libp2p-host", + PrivateKey: pk, + }) + require.NoError(n.t, err) + + ds, err := lr.Datastore(context.TODO(), "/metadata") + require.NoError(n.t, err) + + err = ds.Put(datastore.NewKey("miner-address"), m.ActorAddr.Bytes()) + require.NoError(n.t, err) + + nic := storedcounter.New(ds, datastore.NewKey(modules.StorageCounterDSPrefix)) + for i := 0; i < m.options.sectors; i++ { + _, err := nic.Next() + require.NoError(n.t, err) + } + _, err = nic.Next() + require.NoError(n.t, err) + + err = lr.Close() + require.NoError(n.t, err) + + enc, err := actors.SerializeParams(&miner2.ChangePeerIDParams{NewID: abi.PeerID(m.Libp2p.PeerID)}) + require.NoError(n.t, err) + + msg := &types.Message{ + From: m.OwnerKey.Address, + To: m.ActorAddr, + Method: miner.Methods.ChangePeerID, + Params: enc, + Value: types.NewInt(0), + } + + _, err = m.FullNode.MpoolPushMessage(ctx, msg, nil) + require.NoError(n.t, err) + + var mineBlock = make(chan lotusminer.MineReq) + opts := []node.Option{ + node.StorageMiner(&m.StorageMiner), + node.Online(), + node.Repo(r), + node.Test(), + + node.MockHost(n.mn), + + node.Override(new(v1api.FullNode), m.FullNode.FullNode), + node.Override(new(*lotusminer.Miner), lotusminer.NewTestMiner(mineBlock, m.ActorAddr)), + + // disable resource filtering so that local worker gets assigned tasks + // regardless of system pressure. + node.Override(new(sectorstorage.SealerConfig), func() sectorstorage.SealerConfig { + scfg := config.DefaultStorageMiner() + scfg.Storage.ResourceFiltering = sectorstorage.ResourceFilteringDisabled + return scfg.Storage + }), + } + + // append any node builder options. + opts = append(opts, m.options.extraNodeOpts...) + + idAddr, err := address.IDFromAddress(m.ActorAddr) + require.NoError(n.t, err) + + // preload preseals if the network still hasn't bootstrapped. + var presealSectors []abi.SectorID + if !n.bootstrapped { + sectors := n.genesis.miners[i].Sectors + for _, sector := range sectors { + presealSectors = append(presealSectors, abi.SectorID{ + Miner: abi.ActorID(idAddr), + Number: sector.SectorID, + }) + } + } + + if n.options.mockProofs { + opts = append(opts, + node.Override(new(*mock.SectorMgr), func() (*mock.SectorMgr, error) { + return mock.NewMockSectorMgr(presealSectors), nil + }), + node.Override(new(sectorstorage.SectorManager), node.From(new(*mock.SectorMgr))), + node.Override(new(sectorstorage.Unsealer), node.From(new(*mock.SectorMgr))), + node.Override(new(sectorstorage.PieceProvider), node.From(new(*mock.SectorMgr))), + + node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), + node.Override(new(ffiwrapper.Prover), mock.MockProver), + node.Unset(new(*sectorstorage.Manager)), + ) + } + + // start node + stop, err := node.New(ctx, opts...) + require.NoError(n.t, err) + + // using real proofs, therefore need real sectors. + if !n.bootstrapped && !n.options.mockProofs { + err := m.StorageAddLocal(ctx, m.PresealDir) + require.NoError(n.t, err) + } + + n.t.Cleanup(func() { _ = stop(context.Background()) }) + + // Are we hitting this node through its RPC? + if m.options.rpc { + withRPC := minerRpc(n.t, m) + n.inactive.miners[i] = withRPC + } + + mineOne := func(ctx context.Context, req lotusminer.MineReq) error { + select { + case mineBlock <- req: + return nil + case <-ctx.Done(): + return ctx.Err() + } + } + + m.MineOne = mineOne + m.Stop = stop + + n.active.miners = append(n.active.miners, m) + } + + // If we are here, we have processed all inactive miners and moved them + // to active, so clear the slice. + n.inactive.miners = n.inactive.miners[:0] + + // Link all the nodes. + err = n.mn.LinkAll() + require.NoError(n.t, err) + + if !n.bootstrapped && len(n.active.miners) > 0 { + // We have *just* bootstrapped, so mine 2 blocks to setup some CE stuff in some actors + var wait sync.Mutex + wait.Lock() + + observer := n.active.fullnodes[0] + + bm := NewBlockMiner(n.t, n.active.miners[0]) + n.t.Cleanup(bm.Stop) + + bm.MineUntilBlock(ctx, observer, func(epoch abi.ChainEpoch) { + wait.Unlock() + }) + wait.Lock() + bm.MineUntilBlock(ctx, observer, func(epoch abi.ChainEpoch) { + wait.Unlock() + }) + wait.Lock() + } + + n.bootstrapped = true + return n +} + +// InterconnectAll connects all miners and full nodes to one another. +func (n *Ensemble) InterconnectAll() *Ensemble { + // connect full nodes to miners. + for _, from := range n.active.fullnodes { + for _, to := range n.active.miners { + // []*TestMiner to []api.CommonAPI type coercion not possible + // so cannot use variadic form. + n.Connect(from, to) + } + } + + // connect full nodes between each other, skipping ourselves. + last := len(n.active.fullnodes) - 1 + for i, from := range n.active.fullnodes { + if i == last { + continue + } + for _, to := range n.active.fullnodes[i+1:] { + n.Connect(from, to) + } + } + return n +} + +// Connect connects one full node to the provided full nodes. +func (n *Ensemble) Connect(from api.Common, to ...api.Common) *Ensemble { + addr, err := from.NetAddrsListen(context.Background()) + require.NoError(n.t, err) + + for _, other := range to { + err = other.NetConnect(context.Background(), addr) + require.NoError(n.t, err) + } + return n +} + +// BeginMining kicks off mining for the specified miners. If nil or 0-length, +// it will kick off mining for all enrolled and active miners. It also adds a +// cleanup function to stop all mining operations on test teardown. +func (n *Ensemble) BeginMining(blocktime time.Duration, miners ...*TestMiner) []*BlockMiner { + ctx := context.Background() + + // wait one second to make sure that nodes are connected and have handshaken. + // TODO make this deterministic by listening to identify events on the + // libp2p eventbus instead (or something else). + time.Sleep(1 * time.Second) + + var bms []*BlockMiner + if len(miners) == 0 { + // no miners have been provided explicitly, instantiate block miners + // for all active miners that aren't still mining. + for _, m := range n.active.miners { + if _, ok := n.active.bms[m]; ok { + continue // skip, already have a block miner + } + miners = append(miners, m) + } + } + + for _, m := range miners { + bm := NewBlockMiner(n.t, m) + bm.MineBlocks(ctx, blocktime) + n.t.Cleanup(bm.Stop) + + bms = append(bms, bm) + + n.active.bms[m] = bm + } + + return bms +} + +func (n *Ensemble) generateGenesis() *genesis.Template { + var verifRoot = gen.DefaultVerifregRootkeyActor + if k := n.options.verifiedRoot.key; k != nil { + verifRoot = genesis.Actor{ + Type: genesis.TAccount, + Balance: n.options.verifiedRoot.initialBalance, + Meta: (&genesis.AccountMeta{Owner: k.Address}).ActorMeta(), + } + } + + templ := &genesis.Template{ + NetworkVersion: network.Version0, + Accounts: n.genesis.accounts, + Miners: n.genesis.miners, + NetworkName: "test", + Timestamp: uint64(time.Now().Unix() - int64(n.options.pastOffset.Seconds())), + VerifregRootKey: verifRoot, + RemainderAccount: gen.DefaultRemainderAccountActor, + } + + return templ +} diff --git a/itests/kit/ensemble_opts.go b/itests/kit/ensemble_opts.go new file mode 100644 index 000000000..440362ed1 --- /dev/null +++ b/itests/kit/ensemble_opts.go @@ -0,0 +1,55 @@ +package kit + +import ( + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/wallet" +) + +type EnsembleOpt func(opts *ensembleOpts) error + +type genesisAccount struct { + key *wallet.Key + initialBalance abi.TokenAmount +} + +type ensembleOpts struct { + pastOffset time.Duration + verifiedRoot genesisAccount + accounts []genesisAccount + mockProofs bool +} + +var DefaultEnsembleOpts = ensembleOpts{ + pastOffset: 10000000 * time.Second, // time sufficiently in the past to trigger catch-up mining. +} + +// MockProofs activates mock proofs for the entire ensemble. +func MockProofs() EnsembleOpt { + return func(opts *ensembleOpts) error { + opts.mockProofs = true + return nil + } +} + +// RootVerifier specifies the key to be enlisted as the verified registry root, +// as well as the initial balance to be attributed during genesis. +func RootVerifier(key *wallet.Key, balance abi.TokenAmount) EnsembleOpt { + return func(opts *ensembleOpts) error { + opts.verifiedRoot.key = key + opts.verifiedRoot.initialBalance = balance + return nil + } +} + +// Account sets up an account at genesis with the specified key and balance. +func Account(key *wallet.Key, balance abi.TokenAmount) EnsembleOpt { + return func(opts *ensembleOpts) error { + opts.accounts = append(opts.accounts, genesisAccount{ + key: key, + initialBalance: balance, + }) + return nil + } +} diff --git a/itests/kit/ensemble_presets.go b/itests/kit/ensemble_presets.go new file mode 100644 index 000000000..7cae12a68 --- /dev/null +++ b/itests/kit/ensemble_presets.go @@ -0,0 +1,70 @@ +package kit + +import "testing" + +// EnsembleMinimal creates and starts an Ensemble with a single full node and a single miner. +// It does not interconnect nodes nor does it begin mining. +// +// This function supports passing both ensemble and node functional options. +// Functional options are applied to all nodes. +func EnsembleMinimal(t *testing.T, opts ...interface{}) (*TestFullNode, *TestMiner, *Ensemble) { + eopts, nopts := siftOptions(t, opts) + + var ( + full TestFullNode + miner TestMiner + ) + ens := NewEnsemble(t, eopts...).FullNode(&full, nopts...).Miner(&miner, &full, nopts...).Start() + return &full, &miner, ens +} + +// EnsembleTwoOne creates and starts an Ensemble with two full nodes and one miner. +// It does not interconnect nodes nor does it begin mining. +// +// This function supports passing both ensemble and node functional options. +// Functional options are applied to all nodes. +func EnsembleTwoOne(t *testing.T, opts ...interface{}) (*TestFullNode, *TestFullNode, *TestMiner, *Ensemble) { + eopts, nopts := siftOptions(t, opts) + + var ( + one, two TestFullNode + miner TestMiner + ) + ens := NewEnsemble(t, eopts...).FullNode(&one, nopts...).FullNode(&two, nopts...).Miner(&miner, &one, nopts...).Start() + return &one, &two, &miner, ens +} + +// EnsembleOneTwo creates and starts an Ensemble with one full node and two miners. +// It does not interconnect nodes nor does it begin mining. +// +// This function supports passing both ensemble and node functional options. +// Functional options are applied to all nodes. +func EnsembleOneTwo(t *testing.T, opts ...interface{}) (*TestFullNode, *TestMiner, *TestMiner, *Ensemble) { + eopts, nopts := siftOptions(t, opts) + + var ( + full TestFullNode + one, two TestMiner + ) + ens := NewEnsemble(t, eopts...). + FullNode(&full, nopts...). + Miner(&one, &full, nopts...). + Miner(&two, &full, nopts...). + Start() + + return &full, &one, &two, ens +} + +func siftOptions(t *testing.T, opts []interface{}) (eopts []EnsembleOpt, nopts []NodeOpt) { + for _, v := range opts { + switch o := v.(type) { + case EnsembleOpt: + eopts = append(eopts, o) + case NodeOpt: + nopts = append(nopts, o) + default: + t.Fatalf("invalid option type: %T", o) + } + } + return eopts, nopts +} diff --git a/itests/kit/files.go b/itests/kit/files.go new file mode 100644 index 000000000..48592b518 --- /dev/null +++ b/itests/kit/files.go @@ -0,0 +1,58 @@ +package kit + +import ( + "bytes" + "io" + "math/rand" + "os" + "testing" + + "github.com/minio/blake2b-simd" + + "github.com/stretchr/testify/require" +) + +// CreateRandomFile creates a random file with the provided seed and the +// provided size. +func CreateRandomFile(t *testing.T, rseed, size int) (path string) { + if size == 0 { + size = 1600 + } + + source := io.LimitReader(rand.New(rand.NewSource(int64(rseed))), int64(size)) + + file, err := os.CreateTemp(t.TempDir(), "sourcefile.dat") + require.NoError(t, err) + + n, err := io.Copy(file, source) + require.NoError(t, err) + require.EqualValues(t, n, size) + + return file.Name() +} + +// AssertFilesEqual compares two files by blake2b hash equality and +// fails the test if unequal. +func AssertFilesEqual(t *testing.T, left, right string) { + // initialize hashes. + leftH, rightH := blake2b.New256(), blake2b.New256() + + // open files. + leftF, err := os.Open(left) + require.NoError(t, err) + + rightF, err := os.Open(right) + require.NoError(t, err) + + // feed hash functions. + _, err = io.Copy(leftH, leftF) + require.NoError(t, err) + + _, err = io.Copy(rightH, rightF) + require.NoError(t, err) + + // compute digests. + leftD, rightD := leftH.Sum(nil), rightH.Sum(nil) + + require.True(t, bytes.Equal(leftD, rightD)) +} diff --git a/itests/kit/funds.go b/itests/kit/funds.go index 4c739dc62..417cf9ce1 100644 --- a/itests/kit/funds.go +++ b/itests/kit/funds.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/filecoin-project/go-state-types/abi" + "github.com/stretchr/testify/require" "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/api" @@ -13,11 +14,9 @@ import ( // SendFunds sends funds from the default wallet of the specified sender node // to the recipient address. -func SendFunds(ctx context.Context, t *testing.T, sender TestFullNode, recipient address.Address, amount abi.TokenAmount) { +func SendFunds(ctx context.Context, t *testing.T, sender *TestFullNode, recipient address.Address, amount abi.TokenAmount) { senderAddr, err := sender.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) msg := &types.Message{ From: senderAddr, @@ -26,14 +25,10 @@ func SendFunds(ctx context.Context, t *testing.T, sender TestFullNode, recipient } sm, err := sender.MpoolPushMessage(ctx, msg, nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + res, err := sender.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatal("did not successfully send money") - } + require.NoError(t, err) + + require.EqualValues(t, 0, res.Receipt.ExitCode, "did not successfully send funds") } diff --git a/itests/kit/init.go b/itests/kit/init.go index 2f40ca0f0..8df4922b8 100644 --- a/itests/kit/init.go +++ b/itests/kit/init.go @@ -3,7 +3,6 @@ package kit import ( "fmt" "os" - "strings" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/build" @@ -12,21 +11,19 @@ import ( ) func init() { - bin := os.Args[0] - if !strings.HasSuffix(bin, ".test") { - panic("package itests/kit must only be imported from tests") - } - _ = logging.SetLogLevel("*", "INFO") policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) - err := os.Setenv("BELLMAN_NO_GPU", "1") - if err != nil { - panic(fmt.Sprintf("failed to set BELLMAN_NO_GPU env variable: %s", err)) - } build.InsecurePoStValidation = true + if err := os.Setenv("BELLMAN_NO_GPU", "1"); err != nil { + panic(fmt.Sprintf("failed to set BELLMAN_NO_GPU env variable: %s", err)) + } + + if err := os.Setenv("LOTUS_DISABLE_WATCHDOG", "1"); err != nil { + panic(fmt.Sprintf("failed to set LOTUS_DISABLE_WATCHDOG env variable: %s", err)) + } } diff --git a/itests/kit/log.go b/itests/kit/log.go index 638e768d8..3dce3af9d 100644 --- a/itests/kit/log.go +++ b/itests/kit/log.go @@ -8,7 +8,7 @@ import ( func QuietMiningLogs() { lotuslog.SetupLogLevels() - _ = logging.SetLogLevel("miner", "ERROR") + _ = logging.SetLogLevel("miner", "ERROR") // set this to INFO to watch mining happen. _ = logging.SetLogLevel("chainstore", "ERROR") _ = logging.SetLogLevel("chain", "ERROR") _ = logging.SetLogLevel("sub", "ERROR") diff --git a/itests/kit/net.go b/itests/kit/net.go deleted file mode 100644 index 54c72443f..000000000 --- a/itests/kit/net.go +++ /dev/null @@ -1,87 +0,0 @@ -package kit - -import ( - "context" - "testing" - "time" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/chain/types" - - "github.com/filecoin-project/go-address" -) - -func StartOneNodeOneMiner(ctx context.Context, t *testing.T, blocktime time.Duration) (TestFullNode, address.Address) { - n, sn := RPCMockMinerBuilder(t, OneFull, OneMiner) - - full := n[0] - miner := sn[0] - - // Get everyone connected - addrs, err := full.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - // Start mining blocks - bm := NewBlockMiner(t, miner) - bm.MineBlocks(ctx, blocktime) - t.Cleanup(bm.Stop) - - // Get the full node's wallet address - fullAddr, err := full.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } - - // Create mock CLI - return full, fullAddr -} - -func StartTwoNodesOneMiner(ctx context.Context, t *testing.T, blocktime time.Duration) ([]TestFullNode, []address.Address) { - n, sn := RPCMockMinerBuilder(t, TwoFull, OneMiner) - - fullNode1 := n[0] - fullNode2 := n[1] - miner := sn[0] - - // Get everyone connected - addrs, err := fullNode1.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := fullNode2.NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - // Start mining blocks - bm := NewBlockMiner(t, miner) - bm.MineBlocks(ctx, blocktime) - t.Cleanup(bm.Stop) - - // Send some funds to register the second node - fullNodeAddr2, err := fullNode2.WalletNew(ctx, types.KTSecp256k1) - if err != nil { - t.Fatal(err) - } - - SendFunds(ctx, t, fullNode1, fullNodeAddr2, abi.NewTokenAmount(1e18)) - - // Get the first node's address - fullNodeAddr1, err := fullNode1.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } - - // Create mock CLI - return n, []address.Address{fullNodeAddr1, fullNodeAddr2} -} diff --git a/itests/kit/node_builder.go b/itests/kit/node_builder.go deleted file mode 100644 index 3780a7669..000000000 --- a/itests/kit/node_builder.go +++ /dev/null @@ -1,658 +0,0 @@ -package kit - -import ( - "bytes" - "context" - "crypto/rand" - "io/ioutil" - "net/http" - "net/http/httptest" - "sync" - "testing" - "time" - - "github.com/filecoin-project/go-state-types/network" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/go-state-types/exitcode" - "github.com/filecoin-project/go-storedcounter" - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/client" - "github.com/filecoin-project/lotus/api/v1api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain" - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/builtin/power" - "github.com/filecoin-project/lotus/chain/gen" - genesis2 "github.com/filecoin-project/lotus/chain/gen/genesis" - "github.com/filecoin-project/lotus/chain/messagepool" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/chain/wallet" - "github.com/filecoin-project/lotus/cmd/lotus-seed/seed" - sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" - "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" - "github.com/filecoin-project/lotus/extern/sector-storage/mock" - "github.com/filecoin-project/lotus/genesis" - lotusminer "github.com/filecoin-project/lotus/miner" - "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/modules" - "github.com/filecoin-project/lotus/node/modules/dtypes" - testing2 "github.com/filecoin-project/lotus/node/modules/testing" - "github.com/filecoin-project/lotus/node/repo" - "github.com/filecoin-project/lotus/storage/mockstorage" - miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" - power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power" - "github.com/ipfs/go-datastore" - "github.com/libp2p/go-libp2p-core/crypto" - "github.com/libp2p/go-libp2p-core/peer" - mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" - "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" - "github.com/stretchr/testify/require" -) - -func init() { - chain.BootstrapPeerThreshold = 1 - messagepool.HeadChangeCoalesceMinDelay = time.Microsecond - messagepool.HeadChangeCoalesceMaxDelay = 2 * time.Microsecond - messagepool.HeadChangeCoalesceMergeInterval = 100 * time.Nanosecond -} - -func CreateTestStorageNode(ctx context.Context, t *testing.T, waddr address.Address, act address.Address, pk crypto.PrivKey, tnd TestFullNode, mn mocknet.Mocknet, opts node.Option) TestMiner { - r := repo.NewMemory(nil) - - lr, err := r.Lock(repo.StorageMiner) - require.NoError(t, err) - - ks, err := lr.KeyStore() - require.NoError(t, err) - - kbytes, err := pk.Bytes() - require.NoError(t, err) - - err = ks.Put("libp2p-host", types.KeyInfo{ - Type: "libp2p-host", - PrivateKey: kbytes, - }) - require.NoError(t, err) - - ds, err := lr.Datastore(context.TODO(), "/metadata") - require.NoError(t, err) - err = ds.Put(datastore.NewKey("miner-address"), act.Bytes()) - require.NoError(t, err) - - nic := storedcounter.New(ds, datastore.NewKey(modules.StorageCounterDSPrefix)) - for i := 0; i < GenesisPreseals; i++ { - _, err := nic.Next() - require.NoError(t, err) - } - _, err = nic.Next() - require.NoError(t, err) - - err = lr.Close() - require.NoError(t, err) - - peerid, err := peer.IDFromPrivateKey(pk) - require.NoError(t, err) - - enc, err := actors.SerializeParams(&miner2.ChangePeerIDParams{NewID: abi.PeerID(peerid)}) - require.NoError(t, err) - - msg := &types.Message{ - To: act, - From: waddr, - Method: miner.Methods.ChangePeerID, - Params: enc, - Value: types.NewInt(0), - } - - _, err = tnd.MpoolPushMessage(ctx, msg, nil) - require.NoError(t, err) - - // start node - var minerapi api.StorageMiner - - mineBlock := make(chan lotusminer.MineReq) - stop, err := node.New(ctx, - node.StorageMiner(&minerapi), - node.Online(), - node.Repo(r), - node.Test(), - - node.MockHost(mn), - - node.Override(new(v1api.FullNode), tnd), - node.Override(new(*lotusminer.Miner), lotusminer.NewTestMiner(mineBlock, act)), - - opts, - ) - if err != nil { - t.Fatalf("failed to construct node: %v", err) - } - - t.Cleanup(func() { _ = stop(context.Background()) }) - - /*// Bootstrap with full node - remoteAddrs, err := tnd.NetAddrsListen(Ctx) - require.NoError(t, err) - - err = minerapi.NetConnect(Ctx, remoteAddrs) - require.NoError(t, err)*/ - mineOne := func(ctx context.Context, req lotusminer.MineReq) error { - select { - case mineBlock <- req: - return nil - case <-ctx.Done(): - return ctx.Err() - } - } - - return TestMiner{StorageMiner: minerapi, MineOne: mineOne, Stop: stop} -} - -func storageBuilder(parentNode TestFullNode, mn mocknet.Mocknet, opts node.Option) MinerBuilder { - return func(ctx context.Context, t *testing.T, spt abi.RegisteredSealProof, owner address.Address) TestMiner { - pk, _, err := crypto.GenerateEd25519Key(rand.Reader) - require.NoError(t, err) - - minerPid, err := peer.IDFromPrivateKey(pk) - require.NoError(t, err) - - params, serr := actors.SerializeParams(&power2.CreateMinerParams{ - Owner: owner, - Worker: owner, - SealProofType: spt, - Peer: abi.PeerID(minerPid), - }) - require.NoError(t, serr) - - createStorageMinerMsg := &types.Message{ - To: power.Address, - From: owner, - Value: big.Zero(), - - Method: power.Methods.CreateMiner, - Params: params, - - GasLimit: 0, - GasPremium: big.NewInt(5252), - } - - signed, err := parentNode.MpoolPushMessage(ctx, createStorageMinerMsg, nil) - require.NoError(t, err) - - mw, err := parentNode.StateWaitMsg(ctx, signed.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) - require.NoError(t, err) - require.Equal(t, exitcode.Ok, mw.Receipt.ExitCode) - - var retval power2.CreateMinerReturn - err = retval.UnmarshalCBOR(bytes.NewReader(mw.Receipt.Return)) - require.NoError(t, err) - - return CreateTestStorageNode(ctx, t, owner, retval.IDAddress, pk, parentNode, mn, opts) - } -} - -func Builder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { - return mockBuilderOpts(t, fullOpts, storage, false) -} - -func RPCBuilder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { - return mockBuilderOpts(t, fullOpts, storage, true) -} - -func MockMinerBuilder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { - return mockMinerBuilderOpts(t, fullOpts, storage, false) -} - -func RPCMockMinerBuilder(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) { - return mockMinerBuilderOpts(t, fullOpts, storage, true) -} - -func mockBuilderOpts(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner, rpc bool) ([]TestFullNode, []TestMiner) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - mn := mocknet.New(ctx) - - fulls := make([]TestFullNode, len(fullOpts)) - miners := make([]TestMiner, len(storage)) - - // ***** - pk, _, err := crypto.GenerateEd25519Key(rand.Reader) - require.NoError(t, err) - - minerPid, err := peer.IDFromPrivateKey(pk) - require.NoError(t, err) - - var genbuf bytes.Buffer - - if len(storage) > 1 { - panic("need more peer IDs") - } - // ***** - - // PRESEAL SECTION, TRY TO REPLACE WITH BETTER IN THE FUTURE - // TODO: would be great if there was a better way to fake the preseals - - var ( - genms []genesis.Miner - maddrs []address.Address - genaccs []genesis.Actor - keys []*wallet.Key - ) - - var presealDirs []string - for i := 0; i < len(storage); i++ { - maddr, err := address.NewIDAddress(genesis2.MinerStart + uint64(i)) - if err != nil { - t.Fatal(err) - } - tdir, err := ioutil.TempDir("", "preseal-memgen") - if err != nil { - t.Fatal(err) - } - genm, k, err := seed.PreSeal(maddr, abi.RegisteredSealProof_StackedDrg2KiBV1, 0, GenesisPreseals, tdir, []byte("make genesis mem random"), nil, true) - if err != nil { - t.Fatal(err) - } - genm.PeerId = minerPid - - wk, err := wallet.NewKey(*k) - if err != nil { - return nil, nil - } - - genaccs = append(genaccs, genesis.Actor{ - Type: genesis.TAccount, - Balance: big.Mul(big.NewInt(400000000), types.NewInt(build.FilecoinPrecision)), - Meta: (&genesis.AccountMeta{Owner: wk.Address}).ActorMeta(), - }) - - keys = append(keys, wk) - presealDirs = append(presealDirs, tdir) - maddrs = append(maddrs, maddr) - genms = append(genms, *genm) - } - - rkhKey, err := wallet.GenerateKey(types.KTSecp256k1) - if err != nil { - return nil, nil - } - - vrk := genesis.Actor{ - Type: genesis.TAccount, - Balance: big.Mul(big.Div(big.NewInt(int64(build.FilBase)), big.NewInt(100)), big.NewInt(int64(build.FilecoinPrecision))), - Meta: (&genesis.AccountMeta{Owner: rkhKey.Address}).ActorMeta(), - } - keys = append(keys, rkhKey) - - templ := &genesis.Template{ - NetworkVersion: network.Version0, - Accounts: genaccs, - Miners: genms, - NetworkName: "test", - Timestamp: uint64(time.Now().Unix() - 10000), // some time sufficiently far in the past - VerifregRootKey: vrk, - RemainderAccount: gen.DefaultRemainderAccountActor, - } - - // END PRESEAL SECTION - - for i := 0; i < len(fullOpts); i++ { - var genesis node.Option - if i == 0 { - genesis = node.Override(new(modules.Genesis), testing2.MakeGenesisMem(&genbuf, *templ)) - } else { - genesis = node.Override(new(modules.Genesis), modules.LoadGenesis(genbuf.Bytes())) - } - - stop, err := node.New(ctx, - node.FullAPI(&fulls[i].FullNode, node.Lite(fullOpts[i].Lite)), - node.Online(), - node.Repo(repo.NewMemory(nil)), - node.MockHost(mn), - node.Test(), - - genesis, - - fullOpts[i].Opts(fulls), - ) - - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { _ = stop(context.Background()) }) - - if rpc { - fulls[i] = fullRpc(t, fulls[i]) - } - - fulls[i].Stb = storageBuilder(fulls[i], mn, node.Options()) - } - - if _, err := fulls[0].FullNode.WalletImport(ctx, &rkhKey.KeyInfo); err != nil { - t.Fatal(err) - } - - for i, def := range storage { - // TODO: support non-bootstrap miners - if i != 0 { - t.Fatal("only one storage node supported") - } - if def.Full != 0 { - t.Fatal("storage nodes only supported on the first full node") - } - - f := fulls[def.Full] - if _, err := f.FullNode.WalletImport(ctx, &keys[i].KeyInfo); err != nil { - t.Fatal(err) - } - if err := f.FullNode.WalletSetDefault(ctx, keys[i].Address); err != nil { - t.Fatal(err) - } - - genMiner := maddrs[i] - wa := genms[i].Worker - - opts := def.Opts - if opts == nil { - opts = node.Options() - } - miners[i] = CreateTestStorageNode(ctx, t, wa, genMiner, pk, f, mn, opts) - if err := miners[i].StorageAddLocal(ctx, presealDirs[i]); err != nil { - t.Fatalf("%+v", err) - } - /* - sma := miners[i].StorageMiner.(*impl.StorageMinerAPI) - - psd := presealDirs[i] - */ - if rpc { - miners[i] = storerRpc(t, miners[i]) - } - } - - if err := mn.LinkAll(); err != nil { - t.Fatal(err) - } - - if len(miners) > 0 { - // Mine 2 blocks to setup some CE stuff in some actors - var wait sync.Mutex - wait.Lock() - - bm := NewBlockMiner(t, miners[0]) - t.Cleanup(bm.Stop) - - bm.MineUntilBlock(ctx, fulls[0], func(epoch abi.ChainEpoch) { - wait.Unlock() - }) - - wait.Lock() - bm.MineUntilBlock(ctx, fulls[0], func(epoch abi.ChainEpoch) { - wait.Unlock() - }) - wait.Lock() - } - - return fulls, miners -} - -func mockMinerBuilderOpts(t *testing.T, fullOpts []FullNodeOpts, storage []StorageMiner, rpc bool) ([]TestFullNode, []TestMiner) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - mn := mocknet.New(ctx) - - fulls := make([]TestFullNode, len(fullOpts)) - miners := make([]TestMiner, len(storage)) - - var genbuf bytes.Buffer - - // PRESEAL SECTION, TRY TO REPLACE WITH BETTER IN THE FUTURE - // TODO: would be great if there was a better way to fake the preseals - - var ( - genms []genesis.Miner - genaccs []genesis.Actor - maddrs []address.Address - keys []*wallet.Key - pidKeys []crypto.PrivKey - ) - for i := 0; i < len(storage); i++ { - maddr, err := address.NewIDAddress(genesis2.MinerStart + uint64(i)) - if err != nil { - t.Fatal(err) - } - - preseals := storage[i].Preseal - if preseals == PresealGenesis { - preseals = GenesisPreseals - } - - genm, k, err := mockstorage.PreSeal(abi.RegisteredSealProof_StackedDrg2KiBV1, maddr, preseals) - if err != nil { - t.Fatal(err) - } - - pk, _, err := crypto.GenerateEd25519Key(rand.Reader) - require.NoError(t, err) - - minerPid, err := peer.IDFromPrivateKey(pk) - require.NoError(t, err) - - genm.PeerId = minerPid - - wk, err := wallet.NewKey(*k) - if err != nil { - return nil, nil - } - - genaccs = append(genaccs, genesis.Actor{ - Type: genesis.TAccount, - Balance: big.Mul(big.NewInt(400000000), types.NewInt(build.FilecoinPrecision)), - Meta: (&genesis.AccountMeta{Owner: wk.Address}).ActorMeta(), - }) - - keys = append(keys, wk) - pidKeys = append(pidKeys, pk) - maddrs = append(maddrs, maddr) - genms = append(genms, *genm) - } - - rkhKey, err := wallet.GenerateKey(types.KTSecp256k1) - if err != nil { - return nil, nil - } - - vrk := genesis.Actor{ - Type: genesis.TAccount, - Balance: big.Mul(big.Div(big.NewInt(int64(build.FilBase)), big.NewInt(100)), big.NewInt(int64(build.FilecoinPrecision))), - Meta: (&genesis.AccountMeta{Owner: rkhKey.Address}).ActorMeta(), - } - keys = append(keys, rkhKey) - - templ := &genesis.Template{ - NetworkVersion: network.Version0, - Accounts: genaccs, - Miners: genms, - NetworkName: "test", - Timestamp: uint64(time.Now().Unix()) - (build.BlockDelaySecs * 20000), - VerifregRootKey: vrk, - RemainderAccount: gen.DefaultRemainderAccountActor, - } - - // END PRESEAL SECTION - - for i := 0; i < len(fullOpts); i++ { - var genesis node.Option - if i == 0 { - genesis = node.Override(new(modules.Genesis), testing2.MakeGenesisMem(&genbuf, *templ)) - } else { - genesis = node.Override(new(modules.Genesis), modules.LoadGenesis(genbuf.Bytes())) - } - - stop, err := node.New(ctx, - node.FullAPI(&fulls[i].FullNode, node.Lite(fullOpts[i].Lite)), - node.Online(), - node.Repo(repo.NewMemory(nil)), - node.MockHost(mn), - node.Test(), - - node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), - node.Override(new(ffiwrapper.Prover), mock.MockProver), - - // so that we subscribe to pubsub topics immediately - node.Override(new(dtypes.Bootstrapper), dtypes.Bootstrapper(true)), - - genesis, - - fullOpts[i].Opts(fulls), - ) - if err != nil { - t.Fatalf("%+v", err) - } - - t.Cleanup(func() { _ = stop(context.Background()) }) - - if rpc { - fulls[i] = fullRpc(t, fulls[i]) - } - - fulls[i].Stb = storageBuilder(fulls[i], mn, node.Options( - node.Override(new(*mock.SectorMgr), func() (*mock.SectorMgr, error) { - return mock.NewMockSectorMgr(nil), nil - }), - - node.Override(new(sectorstorage.SectorManager), node.From(new(*mock.SectorMgr))), - node.Override(new(sectorstorage.Unsealer), node.From(new(*mock.SectorMgr))), - node.Override(new(sectorstorage.PieceProvider), node.From(new(*mock.SectorMgr))), - - node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), - node.Override(new(ffiwrapper.Prover), mock.MockProver), - node.Unset(new(*sectorstorage.Manager)), - )) - } - - if _, err := fulls[0].FullNode.WalletImport(ctx, &rkhKey.KeyInfo); err != nil { - t.Fatal(err) - } - - for i, def := range storage { - // TODO: support non-bootstrap miners - - minerID := abi.ActorID(genesis2.MinerStart + uint64(i)) - - if def.Full != 0 { - t.Fatal("storage nodes only supported on the first full node") - } - - f := fulls[def.Full] - if _, err := f.FullNode.WalletImport(ctx, &keys[i].KeyInfo); err != nil { - return nil, nil - } - if err := f.FullNode.WalletSetDefault(ctx, keys[i].Address); err != nil { - return nil, nil - } - - sectors := make([]abi.SectorID, len(genms[i].Sectors)) - for i, sector := range genms[i].Sectors { - sectors[i] = abi.SectorID{ - Miner: minerID, - Number: sector.SectorID, - } - } - - opts := def.Opts - if opts == nil { - opts = node.Options() - } - miners[i] = CreateTestStorageNode(ctx, t, genms[i].Worker, maddrs[i], pidKeys[i], f, mn, node.Options( - node.Override(new(*mock.SectorMgr), func() (*mock.SectorMgr, error) { - return mock.NewMockSectorMgr(sectors), nil - }), - - node.Override(new(sectorstorage.SectorManager), node.From(new(*mock.SectorMgr))), - node.Override(new(sectorstorage.Unsealer), node.From(new(*mock.SectorMgr))), - node.Override(new(sectorstorage.PieceProvider), node.From(new(*mock.SectorMgr))), - - node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), - node.Override(new(ffiwrapper.Prover), mock.MockProver), - node.Unset(new(*sectorstorage.Manager)), - opts, - )) - - if rpc { - miners[i] = storerRpc(t, miners[i]) - } - } - - if err := mn.LinkAll(); err != nil { - t.Fatal(err) - } - - bm := NewBlockMiner(t, miners[0]) - - if len(miners) > 0 { - // Mine 2 blocks to setup some CE stuff in some actors - var wait sync.Mutex - wait.Lock() - - bm.MineUntilBlock(ctx, fulls[0], func(abi.ChainEpoch) { - wait.Unlock() - }) - wait.Lock() - bm.MineUntilBlock(ctx, fulls[0], func(abi.ChainEpoch) { - wait.Unlock() - }) - wait.Lock() - } - - return fulls, miners -} - -func CreateRPCServer(t *testing.T, handler http.Handler) (*httptest.Server, multiaddr.Multiaddr) { - testServ := httptest.NewServer(handler) - t.Cleanup(testServ.Close) - t.Cleanup(testServ.CloseClientConnections) - - addr := testServ.Listener.Addr() - maddr, err := manet.FromNetAddr(addr) - require.NoError(t, err) - return testServ, maddr -} - -func fullRpc(t *testing.T, nd TestFullNode) TestFullNode { - handler, err := node.FullNodeHandler(nd.FullNode, false) - require.NoError(t, err) - - srv, maddr := CreateRPCServer(t, handler) - - var ret TestFullNode - cl, stop, err := client.NewFullNodeRPCV1(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) - require.NoError(t, err) - t.Cleanup(stop) - ret.ListenAddr, ret.FullNode = maddr, cl - - return ret -} - -func storerRpc(t *testing.T, nd TestMiner) TestMiner { - handler, err := node.MinerHandler(nd.StorageMiner, false) - require.NoError(t, err) - - srv, maddr := CreateRPCServer(t, handler) - - var ret TestMiner - cl, stop, err := client.NewStorageMinerRPCV0(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v0", nil) - require.NoError(t, err) - t.Cleanup(stop) - - ret.ListenAddr, ret.StorageMiner, ret.MineOne = maddr, cl, nd.MineOne - return ret -} diff --git a/itests/kit/node_full.go b/itests/kit/node_full.go new file mode 100644 index 000000000..83586e188 --- /dev/null +++ b/itests/kit/node_full.go @@ -0,0 +1,85 @@ +package kit + +import ( + "context" + "testing" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/v1api" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/wallet" + "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" +) + +// TestFullNode represents a full node enrolled in an Ensemble. +type TestFullNode struct { + v1api.FullNode + + t *testing.T + + // ListenAddr is the address on which an API server is listening, if an + // API server is created for this Node. + ListenAddr multiaddr.Multiaddr + DefaultKey *wallet.Key + + options nodeOpts +} + +// CreateImportFile creates a random file with the specified seed and size, and +// imports it into the full node. +func (f *TestFullNode) CreateImportFile(ctx context.Context, rseed int, size int) (res *api.ImportRes, path string) { + path = CreateRandomFile(f.t, rseed, size) + res, err := f.ClientImport(ctx, api.FileRef{Path: path}) + require.NoError(f.t, err) + return res, path +} + +// WaitTillChain waits until a specified chain condition is met. It returns +// the first tipset where the condition is met. +func (f *TestFullNode) WaitTillChain(ctx context.Context, pred ChainPredicate) *types.TipSet { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + heads, err := f.ChainNotify(ctx) + require.NoError(f.t, err) + + for chg := range heads { + for _, c := range chg { + if c.Type != "apply" { + continue + } + if ts := c.Val; pred(ts) { + return ts + } + } + } + require.Fail(f.t, "chain condition not met") + return nil +} + +// ChainPredicate encapsulates a chain condition. +type ChainPredicate func(set *types.TipSet) bool + +// HeightAtLeast returns a ChainPredicate that is satisfied when the chain +// height is equal or higher to the target. +func HeightAtLeast(target abi.ChainEpoch) ChainPredicate { + return func(ts *types.TipSet) bool { + return ts.Height() >= target + } +} + +// BlockMinedBy returns a ChainPredicate that is satisfied when we observe the +// first block mined by the specified miner. +func BlockMinedBy(miner address.Address) ChainPredicate { + return func(ts *types.TipSet) bool { + for _, b := range ts.Blocks() { + if b.Miner == miner { + return true + } + } + return false + } +} diff --git a/itests/kit/node_miner.go b/itests/kit/node_miner.go new file mode 100644 index 000000000..d3f0d2e3c --- /dev/null +++ b/itests/kit/node_miner.go @@ -0,0 +1,121 @@ +package kit + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/wallet" + sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/filecoin-project/lotus/miner" + libp2pcrypto "github.com/libp2p/go-libp2p-core/crypto" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" +) + +// TestMiner represents a miner enrolled in an Ensemble. +type TestMiner struct { + api.StorageMiner + + t *testing.T + + // ListenAddr is the address on which an API server is listening, if an + // API server is created for this Node + ListenAddr multiaddr.Multiaddr + + ActorAddr address.Address + OwnerKey *wallet.Key + MineOne func(context.Context, miner.MineReq) error + Stop func(context.Context) error + + FullNode *TestFullNode + PresealDir string + + Libp2p struct { + PeerID peer.ID + PrivKey libp2pcrypto.PrivKey + } + + options nodeOpts +} + +func (tm *TestMiner) PledgeSectors(ctx context.Context, n, existing int, blockNotif <-chan struct{}) { + toCheck := tm.StartPledge(ctx, n, existing, blockNotif) + + for len(toCheck) > 0 { + tm.FlushSealingBatches(ctx) + + states := map[api.SectorState]int{} + for n := range toCheck { + st, err := tm.StorageMiner.SectorsStatus(ctx, n, false) + require.NoError(tm.t, err) + states[st.State]++ + if st.State == api.SectorState(sealing.Proving) { + delete(toCheck, n) + } + if strings.Contains(string(st.State), "Fail") { + tm.t.Fatal("sector in a failed state", st.State) + } + } + + build.Clock.Sleep(100 * time.Millisecond) + fmt.Printf("WaitSeal: %d %+v\n", len(toCheck), states) + } + +} + +func (tm *TestMiner) StartPledge(ctx context.Context, n, existing int, blockNotif <-chan struct{}) map[abi.SectorNumber]struct{} { + for i := 0; i < n; i++ { + if i%3 == 0 && blockNotif != nil { + <-blockNotif + tm.t.Log("WAIT") + } + tm.t.Logf("PLEDGING %d", i) + _, err := tm.StorageMiner.PledgeSector(ctx) + require.NoError(tm.t, err) + } + + for { + s, err := tm.StorageMiner.SectorsList(ctx) // Note - the test builder doesn't import genesis sectors into FSM + require.NoError(tm.t, err) + fmt.Printf("Sectors: %d\n", len(s)) + if len(s) >= n+existing { + break + } + + build.Clock.Sleep(100 * time.Millisecond) + } + + fmt.Printf("All sectors is fsm\n") + + s, err := tm.StorageMiner.SectorsList(ctx) + require.NoError(tm.t, err) + + toCheck := map[abi.SectorNumber]struct{}{} + for _, number := range s { + toCheck[number] = struct{}{} + } + + return toCheck +} + +func (tm *TestMiner) FlushSealingBatches(ctx context.Context) { + pcb, err := tm.StorageMiner.SectorPreCommitFlush(ctx) + require.NoError(tm.t, err) + if pcb != nil { + fmt.Printf("PRECOMMIT BATCH: %+v\n", pcb) + } + + cb, err := tm.StorageMiner.SectorCommitFlush(ctx) + require.NoError(tm.t, err) + if cb != nil { + fmt.Printf("COMMIT BATCH: %+v\n", cb) + } +} diff --git a/itests/kit/node_opts.go b/itests/kit/node_opts.go new file mode 100644 index 000000000..ae99f3f29 --- /dev/null +++ b/itests/kit/node_opts.go @@ -0,0 +1,107 @@ +package kit + +import ( + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/wallet" + "github.com/filecoin-project/lotus/node" +) + +// DefaultPresealsPerBootstrapMiner is the number of preseals that every +// bootstrap miner has by default. It can be overridden through the +// PresealSectors option. +const DefaultPresealsPerBootstrapMiner = 2 + +const TestSpt = abi.RegisteredSealProof_StackedDrg2KiBV1_1 + +// nodeOpts is an options accumulating struct, where functional options are +// merged into. +type nodeOpts struct { + balance abi.TokenAmount + lite bool + sectors int + rpc bool + ownerKey *wallet.Key + extraNodeOpts []node.Option + optBuilders []OptBuilder + proofType abi.RegisteredSealProof +} + +// DefaultNodeOpts are the default options that will be applied to test nodes. +var DefaultNodeOpts = nodeOpts{ + balance: big.Mul(big.NewInt(100000000), types.NewInt(build.FilecoinPrecision)), + sectors: DefaultPresealsPerBootstrapMiner, + proofType: abi.RegisteredSealProof_StackedDrg2KiBV1_1, // default _concrete_ proof type for non-genesis miners (notice the _1) for new actors versions. +} + +// OptBuilder is used to create an option after some other node is already +// active. Takes all active nodes as a parameter. +type OptBuilder func(activeNodes []*TestFullNode) node.Option + +// NodeOpt is a functional option for test nodes. +type NodeOpt func(opts *nodeOpts) error + +// OwnerBalance specifies the balance to be attributed to a miner's owner +// account. Only relevant when creating a miner. +func OwnerBalance(balance abi.TokenAmount) NodeOpt { + return func(opts *nodeOpts) error { + opts.balance = balance + return nil + } +} + +// LiteNode specifies that this node will be a lite node. Only relevant when +// creating a fullnode. +func LiteNode() NodeOpt { + return func(opts *nodeOpts) error { + opts.lite = true + return nil + } +} + +// PresealSectors specifies the amount of preseal sectors to give to a miner +// at genesis. Only relevant when creating a miner. +func PresealSectors(sectors int) NodeOpt { + return func(opts *nodeOpts) error { + opts.sectors = sectors + return nil + } +} + +// ThroughRPC makes interactions with this node throughout the test flow through +// the JSON-RPC API. +func ThroughRPC() NodeOpt { + return func(opts *nodeOpts) error { + opts.rpc = true + return nil + } +} + +// OwnerAddr sets the owner address of a miner. Only relevant when creating +// a miner. +func OwnerAddr(wk *wallet.Key) NodeOpt { + return func(opts *nodeOpts) error { + opts.ownerKey = wk + return nil + } +} + +// ConstructorOpts are Lotus node constructor options that are passed as-is to +// the node. +func ConstructorOpts(extra ...node.Option) NodeOpt { + return func(opts *nodeOpts) error { + opts.extraNodeOpts = extra + return nil + } +} + +// ProofType sets the proof type for this node. If you're using new actor +// versions, this should be a _1 proof type. +func ProofType(proofType abi.RegisteredSealProof) NodeOpt { + return func(opts *nodeOpts) error { + opts.proofType = proofType + return nil + } +} diff --git a/itests/kit/node_opts_nv.go b/itests/kit/node_opts_nv.go new file mode 100644 index 000000000..d4c84b4f1 --- /dev/null +++ b/itests/kit/node_opts_nv.go @@ -0,0 +1,90 @@ +package kit + +import ( + "context" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node" + "github.com/ipfs/go-cid" +) + +// DefaultTestUpgradeSchedule +var DefaultTestUpgradeSchedule = stmgr.UpgradeSchedule{{ + Network: network.Version9, + Height: 1, + Migration: stmgr.UpgradeActorsV2, +}, { + Network: network.Version10, + Height: 2, + Migration: stmgr.UpgradeActorsV3, +}, { + Network: network.Version12, + Height: 3, + Migration: stmgr.UpgradeActorsV4, +}, { + Network: network.Version13, + Height: 4, + Migration: stmgr.UpgradeActorsV5, +}} + +func LatestActorsAt(upgradeHeight abi.ChainEpoch) node.Option { + // Attention: Update this when introducing new actor versions or your tests will be sad + return NetworkUpgradeAt(network.Version13, upgradeHeight) +} + +// InstantaneousNetworkVersion starts the network instantaneously at the +// specified version in height 1. +func InstantaneousNetworkVersion(version network.Version) node.Option { + // composes all migration functions + var mf stmgr.MigrationFunc = func(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, oldState cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (newState cid.Cid, err error) { + var state = oldState + for _, u := range DefaultTestUpgradeSchedule { + if u.Network > version { + break + } + state, err = u.Migration(ctx, sm, cache, cb, state, height, ts) + if err != nil { + return cid.Undef, err + } + } + return state, nil + } + return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{ + {Network: version, Height: 1, Migration: mf}, + }) +} + +func NetworkUpgradeAt(version network.Version, upgradeHeight abi.ChainEpoch) node.Option { + schedule := stmgr.UpgradeSchedule{} + for _, upgrade := range DefaultTestUpgradeSchedule { + if upgrade.Network > version { + break + } + + schedule = append(schedule, upgrade) + } + + if upgradeHeight > 0 { + schedule[len(schedule)-1].Height = upgradeHeight + } + + return node.Override(new(stmgr.UpgradeSchedule), schedule) +} + +func SDRUpgradeAt(calico, persian abi.ChainEpoch) node.Option { + return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ + Network: network.Version6, + Height: 1, + Migration: stmgr.UpgradeActorsV2, + }, { + Network: network.Version7, + Height: calico, + Migration: stmgr.UpgradeCalico, + }, { + Network: network.Version8, + Height: persian, + }}) +} diff --git a/itests/kit/nodes.go b/itests/kit/nodes.go deleted file mode 100644 index d9b04166a..000000000 --- a/itests/kit/nodes.go +++ /dev/null @@ -1,153 +0,0 @@ -package kit - -import ( - "context" - "testing" - - "github.com/multiformats/go-multiaddr" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/network" - - lapi "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/v1api" - "github.com/filecoin-project/lotus/chain/stmgr" - "github.com/filecoin-project/lotus/miner" - "github.com/filecoin-project/lotus/node" -) - -type MinerBuilder func(context.Context, *testing.T, abi.RegisteredSealProof, address.Address) TestMiner - -type TestFullNode struct { - v1api.FullNode - // ListenAddr is the address on which an API server is listening, if an - // API server is created for this Node - ListenAddr multiaddr.Multiaddr - - Stb MinerBuilder -} - -type TestMiner struct { - lapi.StorageMiner - // ListenAddr is the address on which an API server is listening, if an - // API server is created for this Node - ListenAddr multiaddr.Multiaddr - - MineOne func(context.Context, miner.MineReq) error - Stop func(context.Context) error -} - -var PresealGenesis = -1 - -const GenesisPreseals = 2 - -const TestSpt = abi.RegisteredSealProof_StackedDrg2KiBV1_1 - -// Options for setting up a mock storage Miner -type StorageMiner struct { - Full int - Opts node.Option - Preseal int -} - -type OptionGenerator func([]TestFullNode) node.Option - -// Options for setting up a mock full node -type FullNodeOpts struct { - Lite bool // run node in "lite" mode - Opts OptionGenerator // generate dependency injection options -} - -// APIBuilder is a function which is invoked in test suite to provide -// test nodes and networks -// -// fullOpts array defines options for each full node -// storage array defines storage nodes, numbers in the array specify full node -// index the storage node 'belongs' to -type APIBuilder func(t *testing.T, full []FullNodeOpts, storage []StorageMiner) ([]TestFullNode, []TestMiner) - -func DefaultFullOpts(nFull int) []FullNodeOpts { - full := make([]FullNodeOpts, nFull) - for i := range full { - full[i] = FullNodeOpts{ - Opts: func(nodes []TestFullNode) node.Option { - return node.Options() - }, - } - } - return full -} - -var OneMiner = []StorageMiner{{Full: 0, Preseal: PresealGenesis}} -var OneFull = DefaultFullOpts(1) -var TwoFull = DefaultFullOpts(2) - -var FullNodeWithLatestActorsAt = func(upgradeHeight abi.ChainEpoch) FullNodeOpts { - // Attention: Update this when introducing new actor versions or your tests will be sad - return FullNodeWithNetworkUpgradeAt(network.Version13, upgradeHeight) -} - -var FullNodeWithNetworkUpgradeAt = func(version network.Version, upgradeHeight abi.ChainEpoch) FullNodeOpts { - fullSchedule := stmgr.UpgradeSchedule{{ - // prepare for upgrade. - Network: network.Version9, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version10, - Height: 2, - Migration: stmgr.UpgradeActorsV3, - }, { - Network: network.Version12, - Height: 3, - Migration: stmgr.UpgradeActorsV4, - }, { - Network: network.Version13, - Height: 4, - Migration: stmgr.UpgradeActorsV5, - }} - - schedule := stmgr.UpgradeSchedule{} - for _, upgrade := range fullSchedule { - if upgrade.Network > version { - break - } - - schedule = append(schedule, upgrade) - } - - if upgradeHeight > 0 { - schedule[len(schedule)-1].Height = upgradeHeight - } - - return FullNodeOpts{ - Opts: func(nodes []TestFullNode) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), schedule) - }, - } -} - -var FullNodeWithSDRAt = func(calico, persian abi.ChainEpoch) FullNodeOpts { - return FullNodeOpts{ - Opts: func(nodes []TestFullNode) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ - Network: network.Version6, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version7, - Height: calico, - Migration: stmgr.UpgradeCalico, - }, { - Network: network.Version8, - Height: persian, - }}) - }, - } -} - -var MineNext = miner.MineReq{ - InjectNulls: 0, - Done: func(bool, abi.ChainEpoch, error) {}, -} diff --git a/itests/kit/pledge.go b/itests/kit/pledge.go deleted file mode 100644 index 254f87bac..000000000 --- a/itests/kit/pledge.go +++ /dev/null @@ -1,88 +0,0 @@ -package kit - -import ( - "context" - "fmt" - "strings" - "testing" - "time" - - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - sealing "github.com/filecoin-project/lotus/extern/storage-sealing" - "github.com/stretchr/testify/require" -) - -func PledgeSectors(t *testing.T, ctx context.Context, miner TestMiner, n, existing int, blockNotif <-chan struct{}) { //nolint:golint - toCheck := StartPledge(t, ctx, miner, n, existing, blockNotif) - - for len(toCheck) > 0 { - flushSealingBatches(t, ctx, miner) - - states := map[api.SectorState]int{} - for n := range toCheck { - st, err := miner.SectorsStatus(ctx, n, false) - require.NoError(t, err) - states[st.State]++ - if st.State == api.SectorState(sealing.Proving) { - delete(toCheck, n) - } - if strings.Contains(string(st.State), "Fail") { - t.Fatal("sector in a failed state", st.State) - } - } - - build.Clock.Sleep(100 * time.Millisecond) - fmt.Printf("WaitSeal: %d %+v\n", len(toCheck), states) - } -} - -func flushSealingBatches(t *testing.T, ctx context.Context, miner TestMiner) { //nolint:golint - pcb, err := miner.SectorPreCommitFlush(ctx) - require.NoError(t, err) - if pcb != nil { - fmt.Printf("PRECOMMIT BATCH: %+v\n", pcb) - } - - cb, err := miner.SectorCommitFlush(ctx) - require.NoError(t, err) - if cb != nil { - fmt.Printf("COMMIT BATCH: %+v\n", cb) - } -} - -func StartPledge(t *testing.T, ctx context.Context, miner TestMiner, n, existing int, blockNotif <-chan struct{}) map[abi.SectorNumber]struct{} { //nolint:golint - for i := 0; i < n; i++ { - if i%3 == 0 && blockNotif != nil { - <-blockNotif - t.Log("WAIT") - } - t.Logf("PLEDGING %d", i) - _, err := miner.PledgeSector(ctx) - require.NoError(t, err) - } - - for { - s, err := miner.SectorsList(ctx) // Note - the test builder doesn't import genesis sectors into FSM - require.NoError(t, err) - fmt.Printf("Sectors: %d\n", len(s)) - if len(s) >= n+existing { - break - } - - build.Clock.Sleep(100 * time.Millisecond) - } - - fmt.Printf("All sectors is fsm\n") - - s, err := miner.SectorsList(ctx) - require.NoError(t, err) - - toCheck := map[abi.SectorNumber]struct{}{} - for _, number := range s { - toCheck[number] = struct{}{} - } - - return toCheck -} diff --git a/itests/kit/rpc.go b/itests/kit/rpc.go new file mode 100644 index 000000000..dab45df07 --- /dev/null +++ b/itests/kit/rpc.go @@ -0,0 +1,53 @@ +package kit + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/filecoin-project/lotus/api/client" + "github.com/filecoin-project/lotus/node" + "github.com/multiformats/go-multiaddr" + manet "github.com/multiformats/go-multiaddr/net" + "github.com/stretchr/testify/require" +) + +func CreateRPCServer(t *testing.T, handler http.Handler) (*httptest.Server, multiaddr.Multiaddr) { + testServ := httptest.NewServer(handler) + t.Cleanup(testServ.Close) + t.Cleanup(testServ.CloseClientConnections) + + addr := testServ.Listener.Addr() + maddr, err := manet.FromNetAddr(addr) + require.NoError(t, err) + return testServ, maddr +} + +func fullRpc(t *testing.T, f *TestFullNode) *TestFullNode { + handler, err := node.FullNodeHandler(f.FullNode, false) + require.NoError(t, err) + + srv, maddr := CreateRPCServer(t, handler) + + cl, stop, err := client.NewFullNodeRPCV1(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v1", nil) + require.NoError(t, err) + t.Cleanup(stop) + f.ListenAddr, f.FullNode = maddr, cl + + return f +} + +func minerRpc(t *testing.T, m *TestMiner) *TestMiner { + handler, err := node.MinerHandler(m.StorageMiner, false) + require.NoError(t, err) + + srv, maddr := CreateRPCServer(t, handler) + + cl, stop, err := client.NewStorageMinerRPCV0(context.Background(), "ws://"+srv.Listener.Addr().String()+"/rpc/v0", nil) + require.NoError(t, err) + t.Cleanup(stop) + + m.ListenAddr, m.StorageMiner = maddr, cl + return m +} diff --git a/itests/kit/run.go b/itests/kit/run.go new file mode 100644 index 000000000..713efa3b8 --- /dev/null +++ b/itests/kit/run.go @@ -0,0 +1,20 @@ +package kit + +import ( + "os" + "testing" +) + +// EnvRunExpensiveTests is the environment variable that needs to be present +// and set to value "1" to enable running expensive tests outside of CI. +const EnvRunExpensiveTests = "LOTUS_RUN_EXPENSIVE_TESTS" + +// Expensive marks a test as expensive, skipping it immediately if not running an +func Expensive(t *testing.T) { + switch { + case os.Getenv("CI") == "true": + return + case os.Getenv(EnvRunExpensiveTests) != "1": + t.Skipf("skipping expensive test outside of CI; enable by setting env var %s=1", EnvRunExpensiveTests) + } +} diff --git a/itests/multisig/suite.go b/itests/multisig/suite.go new file mode 100644 index 000000000..86a8ab738 --- /dev/null +++ b/itests/multisig/suite.go @@ -0,0 +1,96 @@ +package multisig + +import ( + "context" + "fmt" + "regexp" + "strings" + "testing" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/stretchr/testify/require" +) + +func RunMultisigTests(t *testing.T, client *kit.TestFullNode) { + // Create mock CLI + ctx := context.Background() + mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) + clientCLI := mockCLI.Client(client.ListenAddr) + + // Create some wallets on the node to use for testing multisig + var walletAddrs []address.Address + for i := 0; i < 4; i++ { + addr, err := client.WalletNew(ctx, types.KTSecp256k1) + require.NoError(t, err) + + walletAddrs = append(walletAddrs, addr) + + kit.SendFunds(ctx, t, client, addr, types.NewInt(1e15)) + } + + // Create an msig with three of the addresses and threshold of two sigs + // msig create --required=2 --duration=50 --value=1000attofil + amtAtto := types.NewInt(1000) + threshold := 2 + paramDuration := "--duration=50" + paramRequired := fmt.Sprintf("--required=%d", threshold) + paramValue := fmt.Sprintf("--value=%dattofil", amtAtto) + out := clientCLI.RunCmd( + "msig", "create", + paramRequired, + paramDuration, + paramValue, + walletAddrs[0].String(), + walletAddrs[1].String(), + walletAddrs[2].String(), + ) + fmt.Println(out) + + // Extract msig robust address from output + expCreateOutPrefix := "Created new multisig:" + require.Regexp(t, regexp.MustCompile(expCreateOutPrefix), out) + parts := strings.Split(strings.TrimSpace(strings.Replace(out, expCreateOutPrefix, "", -1)), " ") + require.Len(t, parts, 2) + msigRobustAddr := parts[1] + fmt.Println("msig robust address:", msigRobustAddr) + + // Propose to add a new address to the msig + // msig add-propose --from= + paramFrom := fmt.Sprintf("--from=%s", walletAddrs[0]) + out = clientCLI.RunCmd( + "msig", "add-propose", + paramFrom, + msigRobustAddr, + walletAddrs[3].String(), + ) + fmt.Println(out) + + // msig inspect + out = clientCLI.RunCmd("msig", "inspect", "--vesting", "--decode-params", msigRobustAddr) + fmt.Println(out) + + // Expect correct balance + require.Regexp(t, regexp.MustCompile("Balance: 0.000000000000001 FIL"), out) + // Expect 1 transaction + require.Regexp(t, regexp.MustCompile(`Transactions:\s*1`), out) + // Expect transaction to be "AddSigner" + require.Regexp(t, regexp.MustCompile(`AddSigner`), out) + + // Approve adding the new address + // msig add-approve --from= 0 false + txnID := "0" + paramFrom = fmt.Sprintf("--from=%s", walletAddrs[1]) + out = clientCLI.RunCmd( + "msig", "add-approve", + paramFrom, + msigRobustAddr, + walletAddrs[0].String(), + txnID, + walletAddrs[3].String(), + "false", + ) + fmt.Println(out) +} diff --git a/itests/multisig_test.go b/itests/multisig_test.go index 4c513640d..9a15e8c0e 100644 --- a/itests/multisig_test.go +++ b/itests/multisig_test.go @@ -1,110 +1,20 @@ package itests import ( - "context" - "fmt" - "os" - "regexp" - "strings" "testing" "time" - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/itests/kit" - "github.com/stretchr/testify/require" + "github.com/filecoin-project/lotus/itests/multisig" ) // TestMultisig does a basic test to exercise the multisig CLI commands func TestMultisig(t *testing.T) { - _ = os.Setenv("BELLMAN_NO_GPU", "1") kit.QuietMiningLogs() - blocktime := 5 * time.Millisecond - ctx := context.Background() - clientNode, _ := kit.StartOneNodeOneMiner(ctx, t, blocktime) + blockTime := 5 * time.Millisecond + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + ens.InterconnectAll().BeginMining(blockTime) - runMultisigTests(t, clientNode) -} - -func runMultisigTests(t *testing.T, clientNode kit.TestFullNode) { - // Create mock CLI - ctx := context.Background() - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) - clientCLI := mockCLI.Client(clientNode.ListenAddr) - - // Create some wallets on the node to use for testing multisig - var walletAddrs []address.Address - for i := 0; i < 4; i++ { - addr, err := clientNode.WalletNew(ctx, types.KTSecp256k1) - require.NoError(t, err) - - walletAddrs = append(walletAddrs, addr) - - kit.SendFunds(ctx, t, clientNode, addr, types.NewInt(1e15)) - } - - // Create an msig with three of the addresses and threshold of two sigs - // msig create --required=2 --duration=50 --value=1000attofil - amtAtto := types.NewInt(1000) - threshold := 2 - paramDuration := "--duration=50" - paramRequired := fmt.Sprintf("--required=%d", threshold) - paramValue := fmt.Sprintf("--value=%dattofil", amtAtto) - out := clientCLI.RunCmd( - "msig", "create", - paramRequired, - paramDuration, - paramValue, - walletAddrs[0].String(), - walletAddrs[1].String(), - walletAddrs[2].String(), - ) - fmt.Println(out) - - // Extract msig robust address from output - expCreateOutPrefix := "Created new multisig:" - require.Regexp(t, regexp.MustCompile(expCreateOutPrefix), out) - parts := strings.Split(strings.TrimSpace(strings.Replace(out, expCreateOutPrefix, "", -1)), " ") - require.Len(t, parts, 2) - msigRobustAddr := parts[1] - fmt.Println("msig robust address:", msigRobustAddr) - - // Propose to add a new address to the msig - // msig add-propose --from= - paramFrom := fmt.Sprintf("--from=%s", walletAddrs[0]) - out = clientCLI.RunCmd( - "msig", "add-propose", - paramFrom, - msigRobustAddr, - walletAddrs[3].String(), - ) - fmt.Println(out) - - // msig inspect - out = clientCLI.RunCmd("msig", "inspect", "--vesting", "--decode-params", msigRobustAddr) - fmt.Println(out) - - // Expect correct balance - require.Regexp(t, regexp.MustCompile("Balance: 0.000000000000001 FIL"), out) - // Expect 1 transaction - require.Regexp(t, regexp.MustCompile(`Transactions:\s*1`), out) - // Expect transaction to be "AddSigner" - require.Regexp(t, regexp.MustCompile(`AddSigner`), out) - - // Approve adding the new address - // msig add-approve --from= 0 false - txnID := "0" - paramFrom = fmt.Sprintf("--from=%s", walletAddrs[1]) - out = clientCLI.RunCmd( - "msig", "add-approve", - paramFrom, - msigRobustAddr, - walletAddrs[0].String(), - txnID, - walletAddrs[3].String(), - "false", - ) - fmt.Println(out) + multisig.RunMultisigTests(t, client) } diff --git a/itests/paych_api_test.go b/itests/paych_api_test.go index 23fec855b..668eb14aa 100644 --- a/itests/paych_api_test.go +++ b/itests/paych_api_test.go @@ -2,7 +2,6 @@ package itests import ( "context" - "fmt" "testing" "time" @@ -10,6 +9,7 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/itests/kit" "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" "github.com/filecoin-project/go-address" cbor "github.com/ipfs/go-ipld-cbor" @@ -30,63 +30,45 @@ func TestPaymentChannelsAPI(t *testing.T) { kit.QuietMiningLogs() ctx := context.Background() - n, sn := kit.MockMinerBuilder(t, kit.TwoFull, kit.OneMiner) + blockTime := 5 * time.Millisecond - paymentCreator := n[0] - paymentReceiver := n[1] - miner := sn[0] + var ( + paymentCreator kit.TestFullNode + paymentReceiver kit.TestFullNode + miner kit.TestMiner + ) - // get everyone connected - addrs, err := paymentCreator.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := paymentReceiver.NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrs); err != nil { - t.Fatal(err) - } - - // start mining blocks - bm := kit.NewBlockMiner(t, miner) - bm.MineBlocks(ctx, 5*time.Millisecond) - t.Cleanup(bm.Stop) + ens := kit.NewEnsemble(t, kit.MockProofs()). + FullNode(&paymentCreator). + FullNode(&paymentReceiver). + Miner(&miner, &paymentCreator). + Start(). + InterconnectAll() + bms := ens.BeginMining(blockTime) + bm := bms[0] // send some funds to register the receiver receiverAddr, err := paymentReceiver.WalletNew(ctx, types.KTSecp256k1) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - kit.SendFunds(ctx, t, paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) + kit.SendFunds(ctx, t, &paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) // setup the payment channel createrAddr, err := paymentCreator.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) channelAmt := int64(7000) channelInfo, err := paymentCreator.PaychGet(ctx, createrAddr, receiverAddr, abi.NewTokenAmount(channelAmt)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) channel, err := paymentCreator.PaychGetWaitReady(ctx, channelInfo.WaitSentinel) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // allocate three lanes var lanes []uint64 for i := 0; i < 3; i++ { lane, err := paymentCreator.PaychAllocateLane(ctx, channel) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) lanes = append(lanes, lane) } @@ -95,45 +77,28 @@ func TestPaymentChannelsAPI(t *testing.T) { // supersedes the voucher with a value of 1000 for _, lane := range lanes { vouch1, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(1000), lane) - if err != nil { - t.Fatal(err) - } - if vouch1.Voucher == nil { - t.Fatal(fmt.Errorf("Not enough funds to create voucher: missing %d", vouch1.Shortfall)) - } + require.NoError(t, err) + require.NotNil(t, vouch1.Voucher, "Not enough funds to create voucher: missing %d", vouch1.Shortfall) + vouch2, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(2000), lane) - if err != nil { - t.Fatal(err) - } - if vouch2.Voucher == nil { - t.Fatal(fmt.Errorf("Not enough funds to create voucher: missing %d", vouch2.Shortfall)) - } + require.NoError(t, err) + require.NotNil(t, vouch2.Voucher, "Not enough funds to create voucher: missing %d", vouch2.Shortfall) + delta1, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch1.Voucher, nil, abi.NewTokenAmount(1000)) - if err != nil { - t.Fatal(err) - } - if !delta1.Equals(abi.NewTokenAmount(1000)) { - t.Fatal("voucher didn't have the right amount") - } + require.NoError(t, err) + require.EqualValues(t, abi.NewTokenAmount(1000), delta1, "voucher didn't have the right amount") + delta2, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouch2.Voucher, nil, abi.NewTokenAmount(1000)) - if err != nil { - t.Fatal(err) - } - if !delta2.Equals(abi.NewTokenAmount(1000)) { - t.Fatal("voucher didn't have the right amount") - } + require.NoError(t, err) + require.EqualValues(t, abi.NewTokenAmount(1000), delta2, "voucher didn't have the right amount") } // settle the payment channel settleMsgCid, err := paymentCreator.PaychSettle(ctx, channel) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) res := waitForMessage(ctx, t, paymentCreator, settleMsgCid, time.Second*10, "settle") - if res.Receipt.ExitCode != 0 { - t.Fatal("Unable to settle payment channel") - } + require.EqualValues(t, 0, res.Receipt.ExitCode, "Unable to settle payment channel") creatorStore := adt.WrapStore(ctx, cbor.NewCborStore(blockstore.NewAPIBlockstore(paymentCreator))) @@ -170,9 +135,7 @@ func TestPaymentChannelsAPI(t *testing.T) { }, int(build.MessageConfidence)+1, build.Finality, func(oldTs, newTs *types.TipSet) (bool, events.StateChange, error) { return preds.OnPaymentChannelActorChanged(channel, preds.OnToSendAmountChanges())(ctx, oldTs.Key(), newTs.Key()) }) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) select { case <-finished: @@ -182,75 +145,49 @@ func TestPaymentChannelsAPI(t *testing.T) { // Create a new voucher now that some vouchers have already been submitted vouchRes, err := paymentCreator.PaychVoucherCreate(ctx, channel, abi.NewTokenAmount(1000), 3) - if err != nil { - t.Fatal(err) - } - if vouchRes.Voucher == nil { - t.Fatal(fmt.Errorf("Not enough funds to create voucher: missing %d", vouchRes.Shortfall)) - } + require.NoError(t, err) + require.NotNil(t, vouchRes.Voucher, "Not enough funds to create voucher: missing %d", vouchRes.Shortfall) + vdelta, err := paymentReceiver.PaychVoucherAdd(ctx, channel, vouchRes.Voucher, nil, abi.NewTokenAmount(1000)) - if err != nil { - t.Fatal(err) - } - if !vdelta.Equals(abi.NewTokenAmount(1000)) { - t.Fatal("voucher didn't have the right amount") - } + require.NoError(t, err) + require.EqualValues(t, abi.NewTokenAmount(1000), vdelta, "voucher didn't have the right amount") // Create a new voucher whose value would exceed the channel balance excessAmt := abi.NewTokenAmount(1000) vouchRes, err = paymentCreator.PaychVoucherCreate(ctx, channel, excessAmt, 4) - if err != nil { - t.Fatal(err) - } - if vouchRes.Voucher != nil { - t.Fatal("Expected not to be able to create voucher whose value would exceed channel balance") - } - if !vouchRes.Shortfall.Equals(excessAmt) { - t.Fatal(fmt.Errorf("Expected voucher shortfall of %d, got %d", excessAmt, vouchRes.Shortfall)) - } + require.NoError(t, err) + require.Nil(t, vouchRes.Voucher, "Expected not to be able to create voucher whose value would exceed channel balance") + require.EqualValues(t, excessAmt, vouchRes.Shortfall, "Expected voucher shortfall of %d, got %d", excessAmt, vouchRes.Shortfall) // Add a voucher whose value would exceed the channel balance vouch := &paych.SignedVoucher{ChannelAddr: channel, Amount: excessAmt, Lane: 4, Nonce: 1} vb, err := vouch.SigningBytes() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + sig, err := paymentCreator.WalletSign(ctx, createrAddr, vb) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + vouch.Signature = sig _, err = paymentReceiver.PaychVoucherAdd(ctx, channel, vouch, nil, abi.NewTokenAmount(1000)) - if err == nil { - t.Fatal(fmt.Errorf("Expected shortfall error of %d", excessAmt)) - } + require.Errorf(t, err, "Expected shortfall error of %d", excessAmt) // wait for the settlement period to pass before collecting waitForBlocks(ctx, t, bm, paymentReceiver, receiverAddr, policy.PaychSettleDelay) creatorPreCollectBalance, err := paymentCreator.WalletBalance(ctx, createrAddr) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // collect funds (from receiver, though either party can do it) collectMsg, err := paymentReceiver.PaychCollect(ctx, channel) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + res, err = paymentReceiver.StateWaitMsg(ctx, collectMsg, 3, api.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatal("unable to collect on payment channel") - } + require.NoError(t, err) + require.EqualValues(t, 0, res.Receipt.ExitCode, "unable to collect on payment channel") // Finally, check the balance for the creator currentCreatorBalance, err := paymentCreator.WalletBalance(ctx, createrAddr) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // The highest nonce voucher that the creator sent on each lane is 2000 totalVouchers := int64(len(lanes) * 2000) @@ -260,12 +197,7 @@ func TestPaymentChannelsAPI(t *testing.T) { // channel amount - total voucher value expectedRefund := channelAmt - totalVouchers delta := big.Sub(currentCreatorBalance, creatorPreCollectBalance) - if !delta.Equals(abi.NewTokenAmount(expectedRefund)) { - t.Fatalf("did not send correct funds from creator: expected %d, got %d", expectedRefund, delta) - } - - // shut down mining - bm.Stop() + require.EqualValues(t, abi.NewTokenAmount(expectedRefund), delta, "did not send correct funds from creator: expected %d, got %d", expectedRefund, delta) } func waitForBlocks(ctx context.Context, t *testing.T, bm *kit.BlockMiner, paymentReceiver kit.TestFullNode, receiverAddr address.Address, count int) { @@ -286,14 +218,10 @@ func waitForBlocks(ctx context.Context, t *testing.T, bm *kit.BlockMiner, paymen From: receiverAddr, Value: types.NewInt(0), }, nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) _, err = paymentReceiver.StateWaitMsg(ctx, m.Cid(), 1, api.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) } } @@ -301,15 +229,12 @@ func waitForMessage(ctx context.Context, t *testing.T, paymentCreator kit.TestFu ctx, cancel := context.WithTimeout(ctx, duration) defer cancel() - fmt.Println("Waiting for", desc) + t.Log("Waiting for", desc) + res, err := paymentCreator.StateWaitMsg(ctx, msgCid, 1, api.LookbackNoLimit, true) - if err != nil { - fmt.Println("Error waiting for", desc, err) - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatalf("did not successfully send %s", desc) - } - fmt.Println("Confirmed", desc) + require.NoError(t, err) + require.EqualValues(t, 0, res.Receipt.ExitCode, "did not successfully send %s", desc) + + t.Log("Confirmed", desc) return res } diff --git a/itests/paych_cli_test.go b/itests/paych_cli_test.go index 373b6f43b..8a0690449 100644 --- a/itests/paych_cli_test.go +++ b/itests/paych_cli_test.go @@ -41,11 +41,12 @@ func TestPaymentChannelsBasic(t *testing.T) { blocktime := 5 * time.Millisecond ctx := context.Background() - nodes, addrs := kit.StartTwoNodesOneMiner(ctx, t, blocktime) - paymentCreator := nodes[0] - paymentReceiver := nodes[1] - creatorAddr := addrs[0] - receiverAddr := addrs[1] + + var ( + paymentCreator kit.TestFullNode + paymentReceiver kit.TestFullNode + ) + creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) @@ -70,12 +71,16 @@ func TestPaymentChannelsBasic(t *testing.T) { // creator: paych settle creatorCLI.RunCmd("paych", "settle", chAddr.String()) + t.Log("wait for chain to reach settle height") + // Wait for the chain to reach the settle height chState := getPaychState(ctx, t, paymentReceiver, chAddr) sa, err := chState.SettlingAt() require.NoError(t, err) waitForHeight(ctx, t, paymentReceiver, sa) + t.Log("settle height reached") + // receiver: paych collect receiverCLI.RunCmd("paych", "collect", chAddr.String()) } @@ -93,10 +98,11 @@ func TestPaymentChannelStatus(t *testing.T) { blocktime := 5 * time.Millisecond ctx := context.Background() - nodes, addrs := kit.StartTwoNodesOneMiner(ctx, t, blocktime) - paymentCreator := nodes[0] - creatorAddr := addrs[0] - receiverAddr := addrs[1] + var ( + paymentCreator kit.TestFullNode + paymentReceiver kit.TestFullNode + ) + creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) @@ -172,11 +178,11 @@ func TestPaymentChannelVouchers(t *testing.T) { blocktime := 5 * time.Millisecond ctx := context.Background() - nodes, addrs := kit.StartTwoNodesOneMiner(ctx, t, blocktime) - paymentCreator := nodes[0] - paymentReceiver := nodes[1] - creatorAddr := addrs[0] - receiverAddr := addrs[1] + var ( + paymentCreator kit.TestFullNode + paymentReceiver kit.TestFullNode + ) + creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) @@ -304,10 +310,11 @@ func TestPaymentChannelVoucherCreateShortfall(t *testing.T) { blocktime := 5 * time.Millisecond ctx := context.Background() - nodes, addrs := kit.StartTwoNodesOneMiner(ctx, t, blocktime) - paymentCreator := nodes[0] - creatorAddr := addrs[0] - receiverAddr := addrs[1] + var ( + paymentCreator kit.TestFullNode + paymentReceiver kit.TestFullNode + ) + creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI mockCLI := kit.NewMockCLI(ctx, t, cli.Commands) @@ -406,3 +413,25 @@ func getPaychState(ctx context.Context, t *testing.T, node kit.TestFullNode, chA return chState } + +func startPaychCreatorReceiverMiner(ctx context.Context, t *testing.T, paymentCreator *kit.TestFullNode, paymentReceiver *kit.TestFullNode, blocktime time.Duration) (address.Address, address.Address) { + var miner kit.TestMiner + opts := kit.ThroughRPC() + kit.NewEnsemble(t, kit.MockProofs()). + FullNode(paymentCreator, opts). + FullNode(paymentReceiver, opts). + Miner(&miner, paymentCreator). + Start(). + InterconnectAll(). + BeginMining(blocktime) + + // Send some funds to the second node + receiverAddr, err := paymentReceiver.WalletDefaultAddress(ctx) + require.NoError(t, err) + kit.SendFunds(ctx, t, paymentCreator, receiverAddr, abi.NewTokenAmount(1e18)) + + // Get the first node's address + creatorAddr, err := paymentCreator.WalletDefaultAddress(ctx) + require.NoError(t, err) + return creatorAddr, receiverAddr +} diff --git a/itests/sdr_upgrade_test.go b/itests/sdr_upgrade_test.go index dfb4284b9..3aa685b09 100644 --- a/itests/sdr_upgrade_test.go +++ b/itests/sdr_upgrade_test.go @@ -12,7 +12,6 @@ import ( "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/itests/kit" bminer "github.com/filecoin-project/lotus/miner" - "github.com/filecoin-project/lotus/node/impl" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -31,18 +30,10 @@ func TestSDRUpgrade(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := kit.MockMinerBuilder(t, []kit.FullNodeOpts{kit.FullNodeWithSDRAt(500, 1000)}, kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] + opts := kit.ConstructorOpts(kit.SDRUpgradeAt(500, 1000)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + ens.InterconnectAll() - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } build.Clock.Sleep(time.Second) pledge := make(chan struct{}) @@ -53,7 +44,7 @@ func TestSDRUpgrade(t *testing.T) { round := 0 for atomic.LoadInt64(&mine) != 0 { build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { + if err := miner.MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { }}); err != nil { t.Error(err) @@ -88,7 +79,7 @@ func TestSDRUpgrade(t *testing.T) { }() // before. - kit.PledgeSectors(t, ctx, miner, 9, 0, pledge) + miner.PledgeSectors(ctx, 9, 0, pledge) s, err := miner.SectorsList(ctx) require.NoError(t, err) diff --git a/itests/sector_pledge_test.go b/itests/sector_pledge_test.go index e3d2a843c..d911dcb68 100644 --- a/itests/sector_pledge_test.go +++ b/itests/sector_pledge_test.go @@ -4,70 +4,38 @@ import ( "context" "fmt" "strings" - "sync/atomic" "testing" "time" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/stretchr/testify/require" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/stmgr" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" - "github.com/filecoin-project/lotus/itests/kit" - bminer "github.com/filecoin-project/lotus/miner" - "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/impl" - "github.com/stretchr/testify/require" ) func TestPledgeSectors(t *testing.T) { kit.QuietMiningLogs() - runTest := func(t *testing.T, b kit.APIBuilder, blocktime time.Duration, nSectors int) { + blockTime := 50 * time.Millisecond + + runTest := func(t *testing.T, nSectors int) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, kit.OneFull, kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] + _, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + ens.InterconnectAll().BeginMining(blockTime) - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - build.Clock.Sleep(time.Second) - - mine := int64(1) - done := make(chan struct{}) - go func() { - defer close(done) - for atomic.LoadInt64(&mine) != 0 { - build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { - - }}); err != nil { - t.Error(err) - } - } - }() - - kit.PledgeSectors(t, ctx, miner, nSectors, 0, nil) - - atomic.StoreInt64(&mine, 0) - <-done + miner.PledgeSectors(ctx, nSectors, 0, nil) } t.Run("1", func(t *testing.T) { - runTest(t, kit.MockMinerBuilder, 50*time.Millisecond, 1) + runTest(t, 1) }) t.Run("100", func(t *testing.T) { - runTest(t, kit.MockMinerBuilder, 50*time.Millisecond, 100) + runTest(t, 100) }) t.Run("1000", func(t *testing.T) { @@ -75,52 +43,24 @@ func TestPledgeSectors(t *testing.T) { t.Skip("skipping test in short mode") } - runTest(t, kit.MockMinerBuilder, 50*time.Millisecond, 1000) + runTest(t, 1000) }) } func TestPledgeBatching(t *testing.T) { - runTest := func(t *testing.T, b kit.APIBuilder, blocktime time.Duration, nSectors int) { + blockTime := 50 * time.Millisecond + + runTest := func(t *testing.T, nSectors int) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1)}, kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] + opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blockTime) - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } + client.WaitTillChain(ctx, kit.HeightAtLeast(10)) - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - build.Clock.Sleep(time.Second) - - mine := int64(1) - done := make(chan struct{}) - go func() { - defer close(done) - for atomic.LoadInt64(&mine) != 0 { - build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { - - }}); err != nil { - t.Error(err) - } - } - }() - - for { - h, err := client.ChainHead(ctx) - require.NoError(t, err) - if h.Height() > 10 { - break - } - } - - toCheck := kit.StartPledge(t, ctx, miner, nSectors, 0, nil) + toCheck := miner.StartPledge(ctx, nSectors, 0, nil) for len(toCheck) > 0 { states := map[api.SectorState]int{} @@ -157,80 +97,27 @@ func TestPledgeBatching(t *testing.T) { build.Clock.Sleep(100 * time.Millisecond) fmt.Printf("WaitSeal: %d %+v\n", len(toCheck), states) } - - atomic.StoreInt64(&mine, 0) - <-done } t.Run("100", func(t *testing.T) { - runTest(t, kit.MockMinerBuilder, 50*time.Millisecond, 100) + runTest(t, 100) }) } func TestPledgeBeforeNv13(t *testing.T) { - runTest := func(t *testing.T, b kit.APIBuilder, blocktime time.Duration, nSectors int) { + blocktime := 50 * time.Millisecond + + runTest := func(t *testing.T, nSectors int) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []kit.FullNodeOpts{ - { - Opts: func(nodes []kit.TestFullNode) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ - Network: network.Version9, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version10, - Height: 2, - Migration: stmgr.UpgradeActorsV3, - }, { - Network: network.Version12, - Height: 3, - Migration: stmgr.UpgradeActorsV4, - }, { - Network: network.Version13, - Height: 1000000000, - Migration: stmgr.UpgradeActorsV5, - }}) - }, - }, - }, kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] + opts := kit.ConstructorOpts(kit.LatestActorsAt(1000000000)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blocktime) - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } + client.WaitTillChain(ctx, kit.HeightAtLeast(10)) - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - build.Clock.Sleep(time.Second) - - mine := int64(1) - done := make(chan struct{}) - go func() { - defer close(done) - for atomic.LoadInt64(&mine) != 0 { - build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { - - }}); err != nil { - t.Error(err) - } - } - }() - - for { - h, err := client.ChainHead(ctx) - require.NoError(t, err) - if h.Height() > 10 { - break - } - } - - toCheck := kit.StartPledge(t, ctx, miner, nSectors, 0, nil) + toCheck := miner.StartPledge(ctx, nSectors, 0, nil) for len(toCheck) > 0 { states := map[api.SectorState]int{} @@ -250,12 +137,9 @@ func TestPledgeBeforeNv13(t *testing.T) { build.Clock.Sleep(100 * time.Millisecond) fmt.Printf("WaitSeal: %d %+v\n", len(toCheck), states) } - - atomic.StoreInt64(&mine, 0) - <-done } t.Run("100-before-nv13", func(t *testing.T) { - runTest(t, kit.MockMinerBuilder, 50*time.Millisecond, 100) + runTest(t, 100) }) } diff --git a/itests/sector_terminate_test.go b/itests/sector_terminate_test.go index b00337c7e..2fb4ef0f5 100644 --- a/itests/sector_terminate_test.go +++ b/itests/sector_terminate_test.go @@ -2,71 +2,31 @@ package itests import ( "context" - "fmt" - "os" "testing" "time" "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/node/impl" "github.com/stretchr/testify/require" ) func TestTerminate(t *testing.T) { - if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { - t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") - } + kit.Expensive(t) kit.QuietMiningLogs() - const blocktime = 2 * time.Millisecond - - nSectors := uint64(2) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - n, sn := kit.MockMinerBuilder(t, - []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1)}, - []kit.StorageMiner{{Full: 0, Preseal: int(nSectors)}}, + var ( + blocktime = 2 * time.Millisecond + nSectors = 2 + ctx = context.Background() ) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - build.Clock.Sleep(time.Second) - - done := make(chan struct{}) - go func() { - defer close(done) - for ctx.Err() == nil { - build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, kit.MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() + opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.PresealSectors(nSectors), opts) + ens.InterconnectAll().BeginMining(blocktime) maddr, err := miner.ActorAddress(ctx) require.NoError(t, err) @@ -77,31 +37,24 @@ func TestTerminate(t *testing.T) { p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*nSectors)) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors))) - fmt.Printf("Seal a sector\n") + t.Log("Seal a sector") - kit.PledgeSectors(t, ctx, miner, 1, 0, nil) + miner.PledgeSectors(ctx, 1, 0, nil) - fmt.Printf("wait for power\n") + t.Log("wait for power") { // Wait until proven. di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 - fmt.Printf("End for head.Height > %d\n", waitUntil) + waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 20 // 20 is some slack for the proof to be submitted + applied + t.Logf("End for head.Height > %d", waitUntil) - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > waitUntil { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - } + ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) } nSectors++ @@ -109,9 +62,9 @@ func TestTerminate(t *testing.T) { p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*nSectors)) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors))) - fmt.Println("Terminate a sector") + t.Log("Terminate a sector") toTerminate := abi.SectorNumber(3) @@ -124,7 +77,7 @@ loop: si, err := miner.SectorsStatus(ctx, toTerminate, false) require.NoError(t, err) - fmt.Println("state: ", si.State, msgTriggerred) + t.Log("state: ", si.State, msgTriggerred) switch sealing.SectorState(si.State) { case sealing.Terminating: @@ -140,7 +93,7 @@ loop: require.NoError(t, err) if c != nil { msgTriggerred = true - fmt.Println("terminate message:", c) + t.Log("terminate message:", c) { p, err := miner.SectorTerminatePending(ctx) @@ -156,11 +109,14 @@ loop: time.Sleep(100 * time.Millisecond) } + // need to wait for message to be mined and applied. + time.Sleep(5 * time.Second) + // check power decreased p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*(nSectors-1))) + require.Equal(t, types.NewInt(uint64(ssz)*uint64(nSectors-1)), p.MinerPower.RawBytePower) // check in terminated set { @@ -180,22 +136,15 @@ loop: di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - if head.Height() > di.PeriodStart+di.WPoStProvingPeriod+2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - build.Clock.Sleep(blocktime) - } - require.NoError(t, err) - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod+2) + waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 20 // slack like above + t.Logf("End for head.Height > %d", waitUntil) + ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*(nSectors-1))) + require.Equal(t, types.NewInt(uint64(ssz)*uint64(nSectors-1)), p.MinerPower.RawBytePower) } diff --git a/itests/tape_test.go b/itests/tape_test.go index 5c0cadc3f..08970152f 100644 --- a/itests/tape_test.go +++ b/itests/tape_test.go @@ -2,7 +2,6 @@ package itests import ( "context" - "fmt" "testing" "time" @@ -13,7 +12,6 @@ import ( sealing "github.com/filecoin-project/lotus/extern/storage-sealing" "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/impl" "github.com/stretchr/testify/require" ) @@ -25,10 +23,10 @@ func TestTapeFix(t *testing.T) { // The "before" case is disabled, because we need the builder to mock 32 GiB sectors to accurately repro this case // TODO: Make the mock sector size configurable and reenable this // t.Run("before", func(t *testing.T) { testTapeFix(t, b, blocktime, false) }) - t.Run("after", func(t *testing.T) { testTapeFix(t, kit.MockMinerBuilder, blocktime, true) }) + t.Run("after", func(t *testing.T) { testTapeFix(t, blocktime, true) }) } -func testTapeFix(t *testing.T, b kit.APIBuilder, blocktime time.Duration, after bool) { +func testTapeFix(t *testing.T, blocktime time.Duration, after bool) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -44,46 +42,14 @@ func testTapeFix(t *testing.T, b kit.APIBuilder, blocktime time.Duration, after }) } - n, sn := b(t, []kit.FullNodeOpts{{Opts: func(_ []kit.TestFullNode) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), upgradeSchedule) - }}}, kit.OneMiner) - - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - build.Clock.Sleep(time.Second) - - done := make(chan struct{}) - go func() { - defer close(done) - for ctx.Err() == nil { - build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, kit.MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() + nopts := kit.ConstructorOpts(node.Override(new(stmgr.UpgradeSchedule), upgradeSchedule)) + _, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), nopts) + ens.InterconnectAll().BeginMining(blocktime) sid, err := miner.PledgeSector(ctx) require.NoError(t, err) - fmt.Printf("All sectors is fsm\n") + t.Log("All sectors is fsm") // If before, we expect the precommit to fail successState := api.SectorState(sealing.CommitFailed) @@ -101,6 +67,6 @@ func testTapeFix(t *testing.T, b kit.APIBuilder, blocktime time.Duration, after } require.NotEqual(t, failureState, st.State) build.Clock.Sleep(100 * time.Millisecond) - fmt.Println("WaitSeal") + t.Log("WaitSeal") } } diff --git a/itests/verifreg_test.go b/itests/verifreg_test.go index b3555cc06..28a72263e 100644 --- a/itests/verifreg_test.go +++ b/itests/verifreg_test.go @@ -2,122 +2,127 @@ package itests import ( "context" + "fmt" "strings" "testing" "time" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/chain/wallet" "github.com/filecoin-project/lotus/itests/kit" + verifreg4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg" + "github.com/stretchr/testify/require" lapi "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" - "github.com/filecoin-project/lotus/node/impl" - verifreg4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg" - - "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/impl" ) func TestVerifiedClientTopUp(t *testing.T) { + blockTime := 100 * time.Millisecond + test := func(nv network.Version, shouldWork bool) func(*testing.T) { return func(t *testing.T) { - nodes, miners := kit.MockMinerBuilder(t, []kit.FullNodeOpts{kit.FullNodeWithNetworkUpgradeAt(nv, -1)}, kit.OneMiner) - api := nodes[0].FullNode.(*impl.FullNodeAPI) + rootKey, err := wallet.GenerateKey(types.KTSecp256k1) + require.NoError(t, err) + + verifierKey, err := wallet.GenerateKey(types.KTSecp256k1) + require.NoError(t, err) + + verifiedClientKey, err := wallet.GenerateKey(types.KTBLS) + require.NoError(t, err) + + bal, err := types.ParseFIL("100fil") + require.NoError(t, err) + + node, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), + kit.RootVerifier(rootKey, abi.NewTokenAmount(bal.Int64())), + kit.Account(verifierKey, abi.NewTokenAmount(bal.Int64())), // assign some balance to the verifier so they can send an AddClient message. + kit.ConstructorOpts(kit.InstantaneousNetworkVersion(nv))) + + ens.InterconnectAll().BeginMining(blockTime) + + api := node.FullNode.(*impl.FullNodeAPI) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - //Get VRH + // get VRH vrh, err := api.StateVerifiedRegistryRootKey(ctx, types.TipSetKey{}) - if err != nil { - t.Fatal(err) - } + fmt.Println(vrh.String()) + require.NoError(t, err) - //Add verifier - verifier, err := api.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } + // import the root key. + rootAddr, err := api.WalletImport(ctx, &rootKey.KeyInfo) + require.NoError(t, err) + + // import the verifier's key. + verifierAddr, err := api.WalletImport(ctx, &verifierKey.KeyInfo) + require.NoError(t, err) + + // import the verified client's key. + verifiedClientAddr, err := api.WalletImport(ctx, &verifiedClientKey.KeyInfo) + require.NoError(t, err) + + params, err := actors.SerializeParams(&verifreg4.AddVerifierParams{Address: verifierAddr, Allowance: big.NewInt(100000000000)}) + require.NoError(t, err) - params, err := actors.SerializeParams(&verifreg4.AddVerifierParams{Address: verifier, Allowance: big.NewInt(100000000000)}) - if err != nil { - t.Fatal(err) - } msg := &types.Message{ + From: rootAddr, To: verifreg.Address, - From: vrh, Method: verifreg.Methods.AddVerifier, Params: params, Value: big.Zero(), } - bm := kit.NewBlockMiner(t, miners[0]) - bm.MineBlocks(ctx, 100*time.Millisecond) - t.Cleanup(bm.Stop) - sm, err := api.MpoolPushMessage(ctx, msg, nil) - if err != nil { - t.Fatal("AddVerifier failed: ", err) - } + require.NoError(t, err, "AddVerifier failed") + res, err := api.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatal("did not successfully send message") - } + require.NoError(t, err) + require.EqualValues(t, 0, res.Receipt.ExitCode) - //Assign datacap to a client + // assign datacap to a client datacap := big.NewInt(10000) - clientAddress, err := api.WalletNew(ctx, types.KTBLS) - if err != nil { - t.Fatal(err) - } - params, err = actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: clientAddress, Allowance: datacap}) - if err != nil { - t.Fatal(err) - } + params, err = actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: verifiedClientAddr, Allowance: datacap}) + require.NoError(t, err) msg = &types.Message{ + From: verifierAddr, To: verifreg.Address, - From: verifier, Method: verifreg.Methods.AddVerifiedClient, Params: params, Value: big.Zero(), } sm, err = api.MpoolPushMessage(ctx, msg, nil) - if err != nil { - t.Fatal("AddVerifiedClient faield: ", err) - } - res, err = api.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatal("did not successfully send message") - } + require.NoError(t, err) + + res, err = api.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) + require.NoError(t, err) + require.EqualValues(t, 0, res.Receipt.ExitCode) + + // check datacap balance + dcap, err := api.StateVerifiedClientStatus(ctx, verifiedClientAddr, types.EmptyTSK) + require.NoError(t, err) - //check datacap balance - dcap, err := api.StateVerifiedClientStatus(ctx, clientAddress, types.EmptyTSK) - if err != nil { - t.Fatal(err) - } if !dcap.Equals(datacap) { t.Fatal("") } - //try to assign datacap to the same client should fail for actor v4 and below - params, err = actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: clientAddress, Allowance: datacap}) + // try to assign datacap to the same client should fail for actor v4 and below + params, err = actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: verifiedClientAddr, Allowance: datacap}) if err != nil { t.Fatal(err) } msg = &types.Message{ + From: verifierAddr, To: verifreg.Address, - From: verifier, Method: verifreg.Methods.AddVerifiedClient, Params: params, Value: big.Zero(), diff --git a/itests/wdpost_dispute_test.go b/itests/wdpost_dispute_test.go index 6c7302af3..554672ce3 100644 --- a/itests/wdpost_dispute_test.go +++ b/itests/wdpost_dispute_test.go @@ -2,8 +2,6 @@ package itests import ( "context" - "fmt" - "os" "testing" "time" @@ -17,83 +15,47 @@ import ( minerActor "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/node/impl" proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" "github.com/stretchr/testify/require" ) func TestWindowPostDispute(t *testing.T) { - if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { - t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") - } + kit.Expensive(t) kit.QuietMiningLogs() - b := kit.MockMinerBuilder blocktime := 2 * time.Millisecond ctx, cancel := context.WithCancel(context.Background()) defer cancel() + var ( + client kit.TestFullNode + chainMiner kit.TestMiner + evilMiner kit.TestMiner + ) + // First, we configure two miners. After sealing, we're going to turn off the first miner so // it doesn't submit proofs. // // Then we're going to manually submit bad proofs. - n, sn := b(t, - []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1)}, - []kit.StorageMiner{ - {Full: 0, Preseal: kit.PresealGenesis}, - {Full: 0}, - }) - - client := n[0].FullNode.(*impl.FullNodeAPI) - chainMiner := sn[0] - evilMiner := sn[1] - - { - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := chainMiner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - - if err := evilMiner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - } + opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) + ens := kit.NewEnsemble(t, kit.MockProofs()). + FullNode(&client, opts). + Miner(&chainMiner, &client, opts). + Miner(&evilMiner, &client, opts, kit.PresealSectors(0)). + Start() defaultFrom, err := client.WalletDefaultAddress(ctx) require.NoError(t, err) - build.Clock.Sleep(time.Second) - // Mine with the _second_ node (the good one). - done := make(chan struct{}) - go func() { - defer close(done) - for ctx.Err() == nil { - build.Clock.Sleep(blocktime) - if err := chainMiner.MineOne(ctx, kit.MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() + ens.InterconnectAll().BeginMining(blocktime, &chainMiner) // Give the chain miner enough sectors to win every block. - kit.PledgeSectors(t, ctx, chainMiner, 10, 0, nil) + chainMiner.PledgeSectors(ctx, 10, 0, nil) // And the evil one 1 sector. No cookie for you. - kit.PledgeSectors(t, ctx, evilMiner, 1, 0, nil) + evilMiner.PledgeSectors(ctx, 1, 0, nil) // Let the evil miner's sectors gain power. evilMinerAddr, err := evilMiner.ActorAddress(ctx) @@ -102,19 +64,13 @@ func TestWindowPostDispute(t *testing.T) { di, err := client.StateMinerProvingDeadline(ctx, evilMinerAddr, types.EmptyTSK) require.NoError(t, err) - fmt.Printf("Running one proving period\n") - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod*2) + t.Logf("Running one proving period\n") - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) + waitUntil := di.PeriodStart + di.WPoStProvingPeriod*2 + 1 + t.Logf("End for head.Height > %d", waitUntil) - if head.Height() > di.PeriodStart+di.WPoStProvingPeriod*2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - build.Clock.Sleep(blocktime) - } + ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) p, err := client.StateMinerPower(ctx, evilMinerAddr, types.EmptyTSK) require.NoError(t, err) @@ -131,12 +87,12 @@ func TestWindowPostDispute(t *testing.T) { evilSectorLoc, err := client.StateSectorPartition(ctx, evilMinerAddr, evilSectorNo, types.EmptyTSK) require.NoError(t, err) - fmt.Println("evil miner stopping") + t.Log("evil miner stopping") // Now stop the evil miner, and start manually submitting bad proofs. require.NoError(t, evilMiner.Stop(ctx)) - fmt.Println("evil miner stopped") + t.Log("evil miner stopped") // Wait until we need to prove our sector. for { @@ -148,7 +104,7 @@ func TestWindowPostDispute(t *testing.T) { build.Clock.Sleep(blocktime) } - err = submitBadProof(ctx, client, evilMinerAddr, di, evilSectorLoc.Deadline, evilSectorLoc.Partition) + err = submitBadProof(ctx, client, evilMiner.OwnerKey.Address, evilMinerAddr, di, evilSectorLoc.Deadline, evilSectorLoc.Partition) require.NoError(t, err, "evil proof not accepted") // Wait until after the proving period. @@ -161,7 +117,7 @@ func TestWindowPostDispute(t *testing.T) { build.Clock.Sleep(blocktime) } - fmt.Println("accepted evil proof") + t.Log("accepted evil proof") // Make sure the evil node didn't lose any power. p, err = client.StateMinerPower(ctx, evilMinerAddr, types.EmptyTSK) @@ -188,7 +144,7 @@ func TestWindowPostDispute(t *testing.T) { sm, err := client.MpoolPushMessage(ctx, msg, nil) require.NoError(t, err) - fmt.Println("waiting dispute") + t.Log("waiting dispute") rec, err := client.StateWaitMsg(ctx, sm.Cid(), build.MessageConfidence, api.LookbackNoLimit, true) require.NoError(t, err) require.Zero(t, rec.Receipt.ExitCode, "dispute not accepted: %s", rec.Receipt.ExitCode.Error()) @@ -246,7 +202,7 @@ func TestWindowPostDispute(t *testing.T) { } // Now try to be evil again - err = submitBadProof(ctx, client, evilMinerAddr, di, evilSectorLoc.Deadline, evilSectorLoc.Partition) + err = submitBadProof(ctx, client, evilMiner.OwnerKey.Address, evilMinerAddr, di, evilSectorLoc.Deadline, evilSectorLoc.Partition) require.Error(t, err) require.Contains(t, err.Error(), "message execution failed: exit 16, reason: window post failed: invalid PoSt") @@ -254,33 +210,18 @@ func TestWindowPostDispute(t *testing.T) { } func TestWindowPostDisputeFails(t *testing.T) { - if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { - t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") - } + kit.Expensive(t) kit.QuietMiningLogs() - b := kit.MockMinerBuilder blocktime := 2 * time.Millisecond ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1)}, kit.OneMiner) - - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - { - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - } + opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blocktime) defaultFrom, err := client.WalletDefaultAddress(ctx) require.NoError(t, err) @@ -290,48 +231,21 @@ func TestWindowPostDisputeFails(t *testing.T) { build.Clock.Sleep(time.Second) - // Mine with the _second_ node (the good one). - done := make(chan struct{}) - go func() { - defer close(done) - for ctx.Err() == nil { - build.Clock.Sleep(blocktime) - if err := miner.MineOne(ctx, kit.MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() - - kit.PledgeSectors(t, ctx, miner, 10, 0, nil) + miner.PledgeSectors(ctx, 10, 0, nil) di, err := client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - fmt.Printf("Running one proving period\n") - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod*2) + t.Log("Running one proving period") + waitUntil := di.PeriodStart + di.WPoStProvingPeriod*2 + 1 + t.Logf("End for head.Height > %d", waitUntil) - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > di.PeriodStart+di.WPoStProvingPeriod*2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - build.Clock.Sleep(blocktime) - } + ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) ssz, err := miner.ActorSectorSize(ctx, maddr) require.NoError(t, err) - expectedPower := types.NewInt(uint64(ssz) * (kit.GenesisPreseals + 10)) + expectedPower := types.NewInt(uint64(ssz) * (kit.DefaultPresealsPerBootstrapMiner + 10)) p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) @@ -393,7 +307,7 @@ waitForProof: func submitBadProof( ctx context.Context, - client api.FullNode, maddr address.Address, + client api.FullNode, owner address.Address, maddr address.Address, di *dline.Info, dlIdx, partIdx uint64, ) error { head, err := client.ChainHead(ctx) @@ -401,11 +315,6 @@ func submitBadProof( return err } - from, err := client.WalletDefaultAddress(ctx) - if err != nil { - return err - } - minerInfo, err := client.StateMinerInfo(ctx, maddr, head.Key()) if err != nil { return err @@ -440,7 +349,7 @@ func submitBadProof( Method: minerActor.Methods.SubmitWindowedPoSt, Params: enc, Value: types.NewInt(0), - From: from, + From: owner, } sm, err := client.MpoolPushMessage(ctx, msg, nil) if err != nil { diff --git a/itests/wdpost_test.go b/itests/wdpost_test.go index f59465f05..e5a4fcee1 100644 --- a/itests/wdpost_test.go +++ b/itests/wdpost_test.go @@ -3,29 +3,26 @@ package itests import ( "context" "fmt" - "os" "testing" "time" - "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/itests/kit" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/extern/sector-storage/mock" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/specs-storage/storage" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/extern/sector-storage/mock" + "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/node/impl" ) func TestWindowedPost(t *testing.T) { - if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { - t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") - } + kit.Expensive(t) kit.QuietMiningLogs() @@ -41,50 +38,20 @@ func TestWindowedPost(t *testing.T) { } { height := height // copy to satisfy lints t.Run(fmt.Sprintf("upgrade-%d", height), func(t *testing.T) { - testWindowPostUpgrade(t, kit.MockMinerBuilder, blocktime, nSectors, height) + testWindowPostUpgrade(t, blocktime, nSectors, height) }) } } -func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Duration, nSectors int, upgradeHeight abi.ChainEpoch) { +func testWindowPostUpgrade(t *testing.T, blocktime time.Duration, nSectors int, upgradeHeight abi.ChainEpoch) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(upgradeHeight)}, kit.OneMiner) + opts := kit.ConstructorOpts(kit.LatestActorsAt(upgradeHeight)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blocktime) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - build.Clock.Sleep(time.Second) - - done := make(chan struct{}) - go func() { - defer close(done) - for ctx.Err() == nil { - build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, kit.MineNext); err != nil { - if ctx.Err() != nil { - // context was canceled, ignore the error. - return - } - t.Error(err) - } - } - }() - defer func() { - cancel() - <-done - }() - - kit.PledgeSectors(t, ctx, miner, nSectors, 0, nil) + miner.PledgeSectors(ctx, nSectors, 0, nil) maddr, err := miner.ActorAddress(ctx) require.NoError(t, err) @@ -95,19 +62,12 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati mid, err := address.IDFromAddress(maddr) require.NoError(t, err) - fmt.Printf("Running one proving period\n") - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod+2) + t.Log("Running one proving period") + waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 + t.Logf("End for head.Height > %d", waitUntil) - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > di.PeriodStart+di.WPoStProvingPeriod+2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - build.Clock.Sleep(blocktime) - } + ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) p, err := client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) @@ -116,9 +76,9 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati require.NoError(t, err) require.Equal(t, p.MinerPower, p.TotalPower) - require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors+kit.GenesisPreseals))) + require.Equal(t, p.MinerPower.RawBytePower, types.NewInt(uint64(ssz)*uint64(nSectors+kit.DefaultPresealsPerBootstrapMiner))) - fmt.Printf("Drop some sectors\n") + t.Log("Drop some sectors") // Drop 2 sectors from deadline 2 partition 0 (full partition / deadline) { @@ -162,7 +122,7 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati all, err := secs.All(2) require.NoError(t, err) - fmt.Println("the sectors", all) + t.Log("the sectors", all) s = storage.SectorRef{ ID: abi.SectorID{ @@ -178,20 +138,12 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati di, err = client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - fmt.Printf("Go through another PP, wait for sectors to become faulty\n") - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod+2) + t.Log("Go through another PP, wait for sectors to become faulty") + waitUntil = di.PeriodStart + di.WPoStProvingPeriod + 2 + t.Logf("End for head.Height > %d", waitUntil) - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > di.PeriodStart+(di.WPoStProvingPeriod)+2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - - build.Clock.Sleep(blocktime) - } + ts = client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) @@ -199,9 +151,9 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati require.Equal(t, p.MinerPower, p.TotalPower) sectors := p.MinerPower.RawBytePower.Uint64() / uint64(ssz) - require.Equal(t, nSectors+kit.GenesisPreseals-3, int(sectors)) // -3 just removed sectors + require.Equal(t, nSectors+kit.DefaultPresealsPerBootstrapMiner-3, int(sectors)) // -3 just removed sectors - fmt.Printf("Recover one sector\n") + t.Log("Recover one sector") err = miner.StorageMiner.(*impl.StorageMinerAPI).IStorageMgr.(*mock.SectorMgr).MarkFailed(s, false) require.NoError(t, err) @@ -209,19 +161,11 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati di, err = client.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - fmt.Printf("End for head.Height > %d\n", di.PeriodStart+di.WPoStProvingPeriod+2) + waitUntil = di.PeriodStart + di.WPoStProvingPeriod + 2 + t.Logf("End for head.Height > %d", waitUntil) - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > di.PeriodStart+di.WPoStProvingPeriod+2 { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - - build.Clock.Sleep(blocktime) - } + ts = client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) require.NoError(t, err) @@ -229,11 +173,11 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati require.Equal(t, p.MinerPower, p.TotalPower) sectors = p.MinerPower.RawBytePower.Uint64() / uint64(ssz) - require.Equal(t, nSectors+kit.GenesisPreseals-2, int(sectors)) // -2 not recovered sectors + require.Equal(t, nSectors+kit.DefaultPresealsPerBootstrapMiner-2, int(sectors)) // -2 not recovered sectors // pledge a sector after recovery - kit.PledgeSectors(t, ctx, miner, 1, nSectors, nil) + miner.PledgeSectors(ctx, 1, nSectors, nil) { // Wait until proven. @@ -241,17 +185,10 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati require.NoError(t, err) waitUntil := di.PeriodStart + di.WPoStProvingPeriod + 2 - fmt.Printf("End for head.Height > %d\n", waitUntil) + t.Logf("End for head.Height > %d\n", waitUntil) - for { - head, err := client.ChainHead(ctx) - require.NoError(t, err) - - if head.Height() > waitUntil { - fmt.Printf("Now head.Height = %d\n", head.Height()) - break - } - } + ts := client.WaitTillChain(ctx, kit.HeightAtLeast(waitUntil)) + t.Logf("Now head.Height = %d", ts.Height()) } p, err = client.StateMinerPower(ctx, maddr, types.EmptyTSK) @@ -260,13 +197,11 @@ func testWindowPostUpgrade(t *testing.T, b kit.APIBuilder, blocktime time.Durati require.Equal(t, p.MinerPower, p.TotalPower) sectors = p.MinerPower.RawBytePower.Uint64() / uint64(ssz) - require.Equal(t, nSectors+kit.GenesisPreseals-2+1, int(sectors)) // -2 not recovered sectors + 1 just pledged + require.Equal(t, nSectors+kit.DefaultPresealsPerBootstrapMiner-2+1, int(sectors)) // -2 not recovered sectors + 1 just pledged } func TestWindowPostBaseFeeNoBurn(t *testing.T) { - if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { - t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") - } + kit.Expensive(t) kit.QuietMiningLogs() @@ -281,11 +216,8 @@ func TestWindowPostBaseFeeNoBurn(t *testing.T) { och := build.UpgradeClausHeight build.UpgradeClausHeight = 10 - n, sn := kit.MockMinerBuilder(t, kit.DefaultFullOpts(1), kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - bm := kit.ConnectAndStartMining(t, blocktime, miner, client) - t.Cleanup(bm.Stop) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs()) + ens.InterconnectAll().BeginMining(blocktime) maddr, err := miner.ActorAddress(ctx) require.NoError(t, err) @@ -293,7 +225,7 @@ func TestWindowPostBaseFeeNoBurn(t *testing.T) { mi, err := client.StateMinerInfo(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - kit.PledgeSectors(t, ctx, miner, nSectors, 0, nil) + miner.PledgeSectors(ctx, nSectors, 0, nil) wact, err := client.StateGetActor(ctx, mi.Worker, types.EmptyTSK) require.NoError(t, err) en := wact.Nonce @@ -323,9 +255,7 @@ waitForProof: } func TestWindowPostBaseFeeBurn(t *testing.T) { - if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { - t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") - } + kit.Expensive(t) kit.QuietMiningLogs() @@ -334,11 +264,9 @@ func TestWindowPostBaseFeeBurn(t *testing.T) { blocktime := 2 * time.Millisecond - n, sn := kit.MockMinerBuilder(t, []kit.FullNodeOpts{kit.FullNodeWithLatestActorsAt(-1)}, kit.OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - bm := kit.ConnectAndStartMining(t, blocktime, miner, client) - t.Cleanup(bm.Stop) + opts := kit.ConstructorOpts(kit.LatestActorsAt(-1)) + client, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blocktime) maddr, err := miner.ActorAddress(ctx) require.NoError(t, err) @@ -346,7 +274,7 @@ func TestWindowPostBaseFeeBurn(t *testing.T) { mi, err := client.StateMinerInfo(ctx, maddr, types.EmptyTSK) require.NoError(t, err) - kit.PledgeSectors(t, ctx, miner, 10, 0, nil) + miner.PledgeSectors(ctx, 10, 0, nil) wact, err := client.StateGetActor(ctx, mi.Worker, types.EmptyTSK) require.NoError(t, err) en := wact.Nonce diff --git a/lib/stati/covar.go b/lib/stati/covar.go new file mode 100644 index 000000000..c92fd8b74 --- /dev/null +++ b/lib/stati/covar.go @@ -0,0 +1,104 @@ +package stati + +import "math" + +type Covar struct { + meanX float64 + meanY float64 + c float64 + n float64 + m2x float64 + m2y float64 +} + +func (cov1 *Covar) MeanX() float64 { + return cov1.meanX +} + +func (cov1 *Covar) MeanY() float64 { + return cov1.meanY +} + +func (cov1 *Covar) N() float64 { + return cov1.n +} + +func (cov1 *Covar) Covariance() float64 { + return cov1.c / (cov1.n - 1) +} + +func (cov1 *Covar) VarianceX() float64 { + return cov1.m2x / (cov1.n - 1) +} + +func (cov1 *Covar) StddevX() float64 { + return math.Sqrt(cov1.VarianceX()) +} + +func (cov1 *Covar) VarianceY() float64 { + return cov1.m2y / (cov1.n - 1) +} + +func (cov1 *Covar) StddevY() float64 { + return math.Sqrt(cov1.VarianceY()) +} + +func (cov1 *Covar) AddPoint(x, y float64) { + cov1.n++ + + dx := x - cov1.meanX + cov1.meanX += dx / cov1.n + dx2 := x - cov1.meanX + cov1.m2x += dx * dx2 + + dy := y - cov1.meanY + cov1.meanY += dy / cov1.n + dy2 := y - cov1.meanY + cov1.m2y += dy * dy2 + + cov1.c += dx * dy +} + +func (cov1 *Covar) Combine(cov2 *Covar) { + if cov1.n == 0 { + *cov1 = *cov2 + return + } + if cov2.n == 0 { + return + } + + if cov1.n == 1 { + cpy := *cov2 + cpy.AddPoint(cov2.meanX, cov2.meanY) + *cov1 = cpy + return + } + if cov2.n == 1 { + cov1.AddPoint(cov2.meanX, cov2.meanY) + } + + out := Covar{} + out.n = cov1.n + cov2.n + + dx := cov1.meanX - cov2.meanX + out.meanX = cov1.meanX - dx*cov2.n/out.n + out.m2x = cov1.m2x + cov2.m2x + dx*dx*cov1.n*cov2.n/out.n + + dy := cov1.meanY - cov2.meanY + out.meanY = cov1.meanY - dy*cov2.n/out.n + out.m2y = cov1.m2y + cov2.m2y + dy*dy*cov1.n*cov2.n/out.n + + out.c = cov1.c + cov2.c + dx*dy*cov1.n*cov2.n/out.n + *cov1 = out +} + +func (cov1 *Covar) A() float64 { + return cov1.Covariance() / cov1.VarianceX() +} +func (cov1 *Covar) B() float64 { + return cov1.meanY - cov1.meanX*cov1.A() +} +func (cov1 *Covar) Correl() float64 { + return cov1.Covariance() / cov1.StddevX() / cov1.StddevY() +} diff --git a/lib/stati/histo.go b/lib/stati/histo.go new file mode 100644 index 000000000..3c410c0d0 --- /dev/null +++ b/lib/stati/histo.go @@ -0,0 +1,56 @@ +package stati + +import ( + "math" + + "golang.org/x/xerrors" +) + +type Histogram struct { + Buckets []float64 + Counts []uint64 +} + +// NewHistogram creates a histograme with buckets defined as: +// {x > -Inf, x >= buckets[0], x >= buckets[1], ..., x >= buckets[i]} +func NewHistogram(buckets []float64) (*Histogram, error) { + if len(buckets) == 0 { + return nil, xerrors.Errorf("empty buckets") + } + prev := buckets[0] + for i, v := range buckets[1:] { + if v < prev { + return nil, xerrors.Errorf("bucket at index %d is smaller than previous %f < %f", i+1, v, prev) + } + prev = v + } + h := &Histogram{ + Buckets: append([]float64{math.Inf(-1)}, buckets...), + Counts: make([]uint64, len(buckets)+1), + } + return h, nil +} + +func (h *Histogram) Observe(x float64) { + for i, b := range h.Buckets { + if x >= b { + h.Counts[i]++ + } else { + break + } + } +} + +func (h *Histogram) Total() uint64 { + return h.Counts[0] +} + +func (h *Histogram) Get(i int) uint64 { + if i >= len(h.Counts)-2 { + return h.Counts[i] + } + return h.Counts[i+1] - h.Counts[i+2] +} +func (h *Histogram) GetRatio(i int) float64 { + return float64(h.Get(i)) / float64(h.Total()) +} diff --git a/lib/stati/meanvar.go b/lib/stati/meanvar.go new file mode 100644 index 000000000..b77aaa638 --- /dev/null +++ b/lib/stati/meanvar.go @@ -0,0 +1,66 @@ +package stati + +import ( + "fmt" + "math" +) + +type MeanVar struct { + n float64 + mean float64 + m2 float64 +} + +func (v1 *MeanVar) AddPoint(value float64) { + // based on https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm + v1.n++ + delta := value - v1.mean + v1.mean += delta / v1.n + delta2 := value - v1.mean + v1.m2 += delta * delta2 +} + +func (v1 *MeanVar) Mean() float64 { + return v1.mean +} +func (v1 *MeanVar) N() float64 { + return v1.n +} +func (v1 *MeanVar) Variance() float64 { + return v1.m2 / (v1.n - 1) +} +func (v1 *MeanVar) Stddev() float64 { + return math.Sqrt(v1.Variance()) +} + +func (v1 MeanVar) String() string { + return fmt.Sprintf("%f stddev: %f (%.0f)", v1.Mean(), v1.Stddev(), v1.N()) +} + +func (v1 *MeanVar) Combine(v2 *MeanVar) { + if v1.n == 0 { + *v1 = *v2 + return + } + if v2.n == 0 { + return + } + if v1.n == 1 { + cpy := *v2 + cpy.AddPoint(v1.mean) + *v1 = cpy + return + } + if v2.n == 1 { + v1.AddPoint(v2.mean) + return + } + + newCount := v1.n + v2.n + delta := v2.mean - v1.mean + meanDelta := delta * v2.n / newCount + m2 := v1.m2 + v2.m2 + delta*meanDelta*v1.n + v1.n = newCount + v1.mean += meanDelta + v1.m2 = m2 +} diff --git a/lib/stati/stats_test.go b/lib/stati/stats_test.go new file mode 100644 index 000000000..fa92913b6 --- /dev/null +++ b/lib/stati/stats_test.go @@ -0,0 +1,47 @@ +package stati + +import ( + "math/rand" + "testing" +) + +func TestMeanVar(t *testing.T) { + N := 16 + ss := make([]*MeanVar, N) + rng := rand.New(rand.NewSource(1)) + for i := 0; i < N; i++ { + ss[i] = &MeanVar{} + maxJ := rng.Intn(1000) + for j := 0; j < maxJ; j++ { + ss[i].AddPoint(rng.NormFloat64()*5 + 500) + } + t.Logf("mean: %f, stddev: %f, count %f", ss[i].mean, ss[i].Stddev(), ss[i].n) + } + out := &MeanVar{} + for i := 0; i < N; i++ { + out.Combine(ss[i]) + t.Logf("combine: mean: %f, stddev: %f", out.mean, out.Stddev()) + } +} + +func TestCovar(t *testing.T) { + N := 16 + ss := make([]*Covar, N) + rng := rand.New(rand.NewSource(1)) + for i := 0; i < N; i++ { + ss[i] = &Covar{} + maxJ := rng.Intn(1000) + 500 + for j := 0; j < maxJ; j++ { + x := rng.NormFloat64()*5 + 500 + ss[i].AddPoint(x, x*2-1000) + } + t.Logf("corell: %f, y = %f*x+%f @%.0f", ss[i].Correl(), ss[i].A(), ss[i].B(), ss[i].n) + t.Logf("\txVar: %f yVar: %f covar: %f", ss[i].StddevX(), ss[i].StddevY(), ss[i].Covariance()) + } + out := &Covar{} + for i := 0; i < N; i++ { + out.Combine(ss[i]) + t.Logf("combine: corell: %f, y = %f*x+%f", out.Correl(), out.A(), out.B()) + t.Logf("\txVar: %f yVar: %f covar: %f", out.StddevX(), out.StddevY(), out.Covariance()) + } +} diff --git a/node/builder.go b/node/builder.go index 7740e8e97..884261a89 100644 --- a/node/builder.go +++ b/node/builder.go @@ -238,7 +238,7 @@ var LibP2P = Options( Override(ConnGaterKey, lp2p.ConnGaterOption), ) -func isType(t repo.RepoType) func(s *Settings) bool { +func IsType(t repo.RepoType) func(s *Settings) bool { return func(s *Settings) bool { return s.nodeType == t } } @@ -299,7 +299,7 @@ var ChainNode = Options( Override(new(*dtypes.MpoolLocker), new(dtypes.MpoolLocker)), // Shared graphsync (markets, serving chain) - Override(new(dtypes.Graphsync), modules.Graphsync(config.DefaultFullNode().Client.SimultaneousTransfers)), + Override(new(dtypes.Graphsync), modules.Graphsync(config.DefaultSimultaneousTransfers)), // Service: Wallet Override(new(*messagesigner.MessageSigner), messagesigner.NewMessageSigner), @@ -403,7 +403,7 @@ var MinerNode = Options( Override(new(dtypes.StagingMultiDstore), modules.StagingMultiDatastore), Override(new(dtypes.StagingBlockstore), modules.StagingBlockstore), Override(new(dtypes.StagingDAG), modules.StagingDAG), - Override(new(dtypes.StagingGraphsync), modules.StagingGraphsync), + Override(new(dtypes.StagingGraphsync), modules.StagingGraphsync(config.DefaultSimultaneousTransfers)), Override(new(dtypes.ProviderPieceStore), modules.NewProviderPieceStore), Override(new(*sectorblocks.SectorBlocks), sectorblocks.NewSectorBlocks), @@ -450,6 +450,8 @@ var MinerNode = Options( Override(new(dtypes.GetSealingConfigFunc), modules.NewGetSealConfigFunc), Override(new(dtypes.SetExpectedSealDurationFunc), modules.NewSetExpectedSealDurationFunc), Override(new(dtypes.GetExpectedSealDurationFunc), modules.NewGetExpectedSealDurationFunc), + Override(new(dtypes.SetMaxDealStartDelayFunc), modules.NewSetMaxDealStartDelayFunc), + Override(new(dtypes.GetMaxDealStartDelayFunc), modules.NewGetMaxDealStartDelayFunc), ) // Online sets up basic libp2p node @@ -466,7 +468,7 @@ func Online() Option { LibP2P, ApplyIf(isFullOrLiteNode, ChainNode), - ApplyIf(isType(repo.StorageMiner), MinerNode), + ApplyIf(IsType(repo.StorageMiner), MinerNode), ) } @@ -604,6 +606,8 @@ func ConfigStorageMiner(c interface{}) Option { })), Override(new(storagemarket.StorageProviderNode), storageadapter.NewProviderNodeAdapter(&cfg.Fees, &cfg.Dealmaking)), + Override(new(dtypes.StagingGraphsync), modules.StagingGraphsync(cfg.Dealmaking.SimultaneousTransfers)), + Override(new(sectorstorage.SealerConfig), cfg.Storage), Override(new(*storage.AddressSelector), modules.AddressSelector(&cfg.Addresses)), Override(new(*storage.Miner), modules.StorageMiner(cfg.Fees)), @@ -676,8 +680,8 @@ func Repo(r repo.Repo) Option { Override(new(*dtypes.APIAlg), modules.APISecret), - ApplyIf(isType(repo.FullNode), ConfigFullNode(c)), - ApplyIf(isType(repo.StorageMiner), ConfigStorageMiner(c)), + ApplyIf(IsType(repo.FullNode), ConfigFullNode(c)), + ApplyIf(IsType(repo.StorageMiner), ConfigStorageMiner(c)), )(settings) } } diff --git a/node/config/def.go b/node/config/def.go index 700a3f94f..b331b1f49 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -66,6 +66,8 @@ type DealmakingConfig struct { ConsiderUnverifiedStorageDeals bool PieceCidBlocklist []cid.Cid ExpectedSealDuration Duration + // Maximum amount of time proposed deal StartEpoch can be in future + MaxDealStartDelay Duration // The amount of time to wait for more deals to arrive before // publishing PublishMsgPeriod Duration @@ -76,6 +78,9 @@ type DealmakingConfig struct { // as a multiplier of the minimum collateral bound MaxProviderCollateralMultiplier uint64 + // The maximum number of parallel online data transfers (storage+retrieval) + SimultaneousTransfers uint64 + Filter string RetrievalFilter string @@ -124,7 +129,6 @@ type SealingConfig struct { BatchPreCommits bool // maximum precommit batch size - batches will be sent immediately above this size MaxPreCommitBatch int - MinPreCommitBatch int // how long to wait before submitting a batch after crossing the minimum batch size PreCommitBatchWait Duration // time buffer for forceful batch submission before sectors/deal in batch would start expiring @@ -316,7 +320,6 @@ func DefaultStorageMiner() *StorageMiner { FinalizeEarly: false, BatchPreCommits: true, - MinPreCommitBatch: 1, // we must have at least one precommit to batch MaxPreCommitBatch: miner5.PreCommitSectorBatchMaxSize, // up to 256 sectors PreCommitBatchWait: Duration(24 * time.Hour), // this should be less than 31.5 hours, which is the expiration of a precommit ticket PreCommitBatchSlack: Duration(3 * time.Hour), // time buffer for forceful batch submission before sectors/deals in batch would start expiring, higher value will lower the chances for message fail due to expiration @@ -342,6 +345,9 @@ func DefaultStorageMiner() *StorageMiner { // Default to 10 - tcp should still be able to figure this out, and // it's the ratio between 10gbit / 1gbit ParallelFetchLimit: 10, + + // By default use the hardware resource filtering strategy. + ResourceFiltering: sectorstorage.ResourceFilteringHardware, }, Dealmaking: DealmakingConfig{ @@ -353,11 +359,14 @@ func DefaultStorageMiner() *StorageMiner { ConsiderUnverifiedStorageDeals: true, PieceCidBlocklist: []cid.Cid{}, // TODO: It'd be nice to set this based on sector size + MaxDealStartDelay: Duration(time.Hour * 24 * 14), ExpectedSealDuration: Duration(time.Hour * 24), PublishMsgPeriod: Duration(time.Hour), MaxDealsPerPublishMsg: 8, MaxProviderCollateralMultiplier: 2, + SimultaneousTransfers: DefaultSimultaneousTransfers, + RetrievalPricing: &RetrievalPricing{ Strategy: RetrievalPricingDefaultMode, Default: &RetrievalPricingDefault{ @@ -374,12 +383,12 @@ func DefaultStorageMiner() *StorageMiner { MaxCommitGasFee: types.MustParseFIL("0.05"), MaxPreCommitBatchGasFee: BatchFeeConfig{ - Base: types.MustParseFIL("0.025"), // TODO: update before v1.10.0 - PerSector: types.MustParseFIL("0.025"), // TODO: update before v1.10.0 + Base: types.MustParseFIL("0"), + PerSector: types.MustParseFIL("0.02"), }, MaxCommitBatchGasFee: BatchFeeConfig{ - Base: types.MustParseFIL("0.05"), // TODO: update before v1.10.0 - PerSector: types.MustParseFIL("0.05"), // TODO: update before v1.10.0 + Base: types.MustParseFIL("0"), + PerSector: types.MustParseFIL("0.03"), // enough for 6 agg and 1nFIL base fee }, MaxTerminateGasFee: types.MustParseFIL("0.5"), diff --git a/node/impl/client/client.go b/node/impl/client/client.go index 4732e5c92..29eb8550e 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -151,12 +151,12 @@ func (a *API) dealStarter(ctx context.Context, params *api.StartDealParams, isSt walletKey, err := a.StateAccountKey(ctx, params.Wallet, types.EmptyTSK) if err != nil { - return nil, xerrors.Errorf("failed resolving params.Wallet addr: %w", params.Wallet) + return nil, xerrors.Errorf("failed resolving params.Wallet addr (%s): %w", params.Wallet, err) } exist, err := a.WalletHas(ctx, walletKey) if err != nil { - return nil, xerrors.Errorf("failed getting addr from wallet: %w", params.Wallet) + return nil, xerrors.Errorf("failed getting addr from wallet (%s): %w", params.Wallet, err) } if !exist { return nil, xerrors.Errorf("provided address doesn't exist in wallet") diff --git a/node/modules/dtypes/miner.go b/node/modules/dtypes/miner.go index 3c0c14383..9a391223d 100644 --- a/node/modules/dtypes/miner.go +++ b/node/modules/dtypes/miner.go @@ -90,6 +90,9 @@ type SetExpectedSealDurationFunc func(time.Duration) error // too determine how long sealing is expected to take type GetExpectedSealDurationFunc func() (time.Duration, error) +type SetMaxDealStartDelayFunc func(time.Duration) error +type GetMaxDealStartDelayFunc func() (time.Duration, error) + type StorageDealFilter func(ctx context.Context, deal storagemarket.MinerDeal) (bool, string, error) type RetrievalDealFilter func(ctx context.Context, deal retrievalmarket.ProviderDealState) (bool, string, error) diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 3b346a80f..09b1e2dfd 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -60,7 +60,6 @@ import ( "github.com/filecoin-project/lotus/api/v1api" "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/gen/slashfilter" @@ -432,13 +431,15 @@ func StagingDAG(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBloc // StagingGraphsync creates a graphsync instance which reads and writes blocks // to the StagingBlockstore -func StagingGraphsync(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBlockstore, h host.Host) dtypes.StagingGraphsync { - graphsyncNetwork := gsnet.NewFromLibp2pHost(h) - loader := storeutil.LoaderForBlockstore(ibs) - storer := storeutil.StorerForBlockstore(ibs) - gs := graphsync.New(helpers.LifecycleCtx(mctx, lc), graphsyncNetwork, loader, storer, graphsync.RejectAllRequestsByDefault()) +func StagingGraphsync(parallelTransfers uint64) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBlockstore, h host.Host) dtypes.StagingGraphsync { + return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ibs dtypes.StagingBlockstore, h host.Host) dtypes.StagingGraphsync { + graphsyncNetwork := gsnet.NewFromLibp2pHost(h) + loader := storeutil.LoaderForBlockstore(ibs) + storer := storeutil.StorerForBlockstore(ibs) + gs := graphsync.New(helpers.LifecycleCtx(mctx, lc), graphsyncNetwork, loader, storer, graphsync.RejectAllRequestsByDefault(), graphsync.MaxInProgressRequests(parallelTransfers)) - return gs + return gs + } } func SetupBlockProducer(lc fx.Lifecycle, ds dtypes.MetadataDS, api v1api.FullNode, epp gen.WinningPoStProver, sf *slashfilter.SlashFilter, j journal.Journal) (*lotusminer.Miner, error) { @@ -487,6 +488,7 @@ func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.Conside unverifiedOk dtypes.ConsiderUnverifiedStorageDealsConfigFunc, blocklistFunc dtypes.StorageDealPieceCidBlocklistConfigFunc, expectedSealTimeFunc dtypes.GetExpectedSealDurationFunc, + startDelay dtypes.GetMaxDealStartDelayFunc, spn storagemarket.StorageProviderNode) dtypes.StorageDealFilter { return func(onlineOk dtypes.ConsiderOnlineStorageDealsConfigFunc, offlineOk dtypes.ConsiderOfflineStorageDealsConfigFunc, @@ -494,6 +496,7 @@ func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.Conside unverifiedOk dtypes.ConsiderUnverifiedStorageDealsConfigFunc, blocklistFunc dtypes.StorageDealPieceCidBlocklistConfigFunc, expectedSealTimeFunc dtypes.GetExpectedSealDurationFunc, + startDelay dtypes.GetMaxDealStartDelayFunc, spn storagemarket.StorageProviderNode) dtypes.StorageDealFilter { return func(ctx context.Context, deal storagemarket.MinerDeal) (bool, string, error) { @@ -565,9 +568,14 @@ func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.Conside return false, fmt.Sprintf("cannot seal a sector before %s", deal.Proposal.StartEpoch), nil } + sd, err := startDelay() + if err != nil { + return false, "miner error", err + } + // Reject if it's more than 7 days in the future // TODO: read from cfg - maxStartEpoch := earliest + abi.ChainEpoch(7*builtin.SecondsInDay/build.BlockDelaySecs) + maxStartEpoch := earliest + abi.ChainEpoch(uint64(sd.Seconds())/build.BlockDelaySecs) if deal.Proposal.StartEpoch > maxStartEpoch { return false, fmt.Sprintf("deal start epoch is too far in the future: %s > %s", deal.Proposal.StartEpoch, maxStartEpoch), nil } @@ -854,7 +862,6 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error FinalizeEarly: cfg.FinalizeEarly, BatchPreCommits: cfg.BatchPreCommits, - MinPreCommitBatch: cfg.MinPreCommitBatch, MaxPreCommitBatch: cfg.MaxPreCommitBatch, PreCommitBatchWait: config.Duration(cfg.PreCommitBatchWait), PreCommitBatchSlack: config.Duration(cfg.PreCommitBatchSlack), @@ -886,7 +893,6 @@ func NewGetSealConfigFunc(r repo.LockedRepo) (dtypes.GetSealingConfigFunc, error FinalizeEarly: cfg.Sealing.FinalizeEarly, BatchPreCommits: cfg.Sealing.BatchPreCommits, - MinPreCommitBatch: cfg.Sealing.MinPreCommitBatch, MaxPreCommitBatch: cfg.Sealing.MaxPreCommitBatch, PreCommitBatchWait: time.Duration(cfg.Sealing.PreCommitBatchWait), PreCommitBatchSlack: time.Duration(cfg.Sealing.PreCommitBatchSlack), @@ -924,6 +930,24 @@ func NewGetExpectedSealDurationFunc(r repo.LockedRepo) (dtypes.GetExpectedSealDu }, nil } +func NewSetMaxDealStartDelayFunc(r repo.LockedRepo) (dtypes.SetMaxDealStartDelayFunc, error) { + return func(delay time.Duration) (err error) { + err = mutateCfg(r, func(cfg *config.StorageMiner) { + cfg.Dealmaking.MaxDealStartDelay = config.Duration(delay) + }) + return + }, nil +} + +func NewGetMaxDealStartDelayFunc(r repo.LockedRepo) (dtypes.GetMaxDealStartDelayFunc, error) { + return func() (out time.Duration, err error) { + err = readCfg(r, func(cfg *config.StorageMiner) { + out = time.Duration(cfg.Dealmaking.MaxDealStartDelay) + }) + return + }, nil +} + func readCfg(r repo.LockedRepo, accessor func(*config.StorageMiner)) error { raw, err := r.Config() if err != nil { diff --git a/scripts/mkreleaselog b/scripts/mkreleaselog index ffc3d935e..c9eaef4fb 100755 --- a/scripts/mkreleaselog +++ b/scripts/mkreleaselog @@ -1,31 +1,11 @@ #!/bin/zsh -set -x set -euo pipefail export GO111MODULE=on export GOPATH="$(go env GOPATH)" alias jq="jq --unbuffered" -AUTHORS=( - # orgs - ipfs - ipld - libp2p - multiformats - filecoin-project - ipfs-shipyard - - # Authors of personal repos used by go-ipfs that should be mentioned in the - # release notes. - whyrusleeping - Kubuxu - jbenet - Stebalien - marten-seemann - hsanjuan - lucas-clemente - warpfork -) +AUTHORS=(filecoin-project) [[ -n "${REPO_FILTER+x}" ]] || REPO_FILTER="github.com/(${$(printf "|%s" "${AUTHORS[@]}"):1})" @@ -49,13 +29,15 @@ statlog() { mailmap_file="$ROOT_DIR/.mailmap" fi - git -C "$rpath" -c mailmap.file="$mailmap_file" log --use-mailmap --shortstat --no-merges --pretty="tformat:%H%n%aN%n%aE" "$start..$end" | while - read hash - read name - read email - read _ # empty line - read changes - do + local stack=() + git -C "$rpath" -c mailmap.file="$mailmap_file" log --use-mailmap --shortstat --no-merges --pretty="tformat:%H%x09%aN%x09%aE" "$start..$end" | while read -r line; do + if [[ -n "$line" ]]; then + stack+=("$line") + continue + fi + + read -r changes + changed=0 insertions=0 deletions=0 @@ -72,14 +54,18 @@ statlog() { fi done<<<"${changes//,/$NL}" - jq -n \ - --arg "hash" "$hash" \ - --arg "name" "$name" \ - --arg "email" "$email" \ - --argjson "changed" "$changed" \ - --argjson "insertions" "$insertions" \ - --argjson "deletions" "$deletions" \ - '{Commit: $hash, Author: $name, Email: $email, Files: $changed, Insertions: $insertions, Deletions: $deletions}' + for author in "${stack[@]}"; do + IFS=$'\t' read -r hash name email <<<"$author" + jq -n \ + --arg "hash" "$hash" \ + --arg "name" "$name" \ + --arg "email" "$email" \ + --argjson "changed" "$changed" \ + --argjson "insertions" "$insertions" \ + --argjson "deletions" "$deletions" \ + '{Commit: $hash, Author: $name, Email: $email, Files: $changed, Insertions: $insertions, Deletions: $deletions}' + done + stack=() done } diff --git a/storage/miner.go b/storage/miner.go index b4c590ae3..3d29e0ef1 100644 --- a/storage/miner.go +++ b/storage/miner.go @@ -199,7 +199,7 @@ func (m *Miner) Run(ctx context.Context) error { ) // Instantiate the sealing FSM. - m.sealing = sealing.New(adaptedAPI, m.feeCfg, evtsAdapter, m.maddr, m.ds, m.sealer, m.sc, m.verif, m.prover, &pcp, cfg, m.handleSealingNotifications, as) + m.sealing = sealing.New(ctx, adaptedAPI, m.feeCfg, evtsAdapter, m.maddr, m.ds, m.sealer, m.sc, m.verif, m.prover, &pcp, cfg, m.handleSealingNotifications, as) // Run the sealing FSM. go m.sealing.Run(ctx) //nolint:errcheck // logged intside the function diff --git a/testplans/lotus-soup/go.sum b/testplans/lotus-soup/go.sum index 4cf96b22a..e1690a213 100644 --- a/testplans/lotus-soup/go.sum +++ b/testplans/lotus-soup/go.sum @@ -286,12 +286,16 @@ github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03/go.mod github.com/filecoin-project/go-data-transfer v1.0.1/go.mod h1:UxvfUAY9v3ub0a21BSK9u3pB2aq30Y0KMsG+w9/ysyo= github.com/filecoin-project/go-data-transfer v1.6.0 h1:DHIzEc23ydRCCBwtFet3MfgO8gMpZEnw60Y+s71oX6o= github.com/filecoin-project/go-data-transfer v1.6.0/go.mod h1:E3WW4mCEYwU2y65swPEajSZoFWFmfXt7uwGduoACZQc= +github.com/filecoin-project/go-data-transfer v1.4.3 h1:ECEw69NOfmEZ7XN1NSBvj3KTbbH2mIczQs+Z2w4bD7c= +github.com/filecoin-project/go-data-transfer v1.4.3/go.mod h1:n8kbDQXWrY1c4UgfMa9KERxNCWbOTDwdNhf2MpN9dpo= github.com/filecoin-project/go-ds-versioning v0.1.0 h1:y/X6UksYTsK8TLCI7rttCKEvl8btmWxyFMEeeWGUxIQ= github.com/filecoin-project/go-ds-versioning v0.1.0/go.mod h1:mp16rb4i2QPmxBnmanUx8i/XANp+PFCCJWiAb+VW4/s= github.com/filecoin-project/go-fil-commcid v0.0.0-20200716160307-8f644712406f/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= github.com/filecoin-project/go-fil-commcid v0.0.0-20201016201715-d41df56b4f6a h1:hyJ+pUm/4U4RdEZBlg6k8Ma4rDiuvqyGpoICXAxwsTg= github.com/filecoin-project/go-fil-commcid v0.0.0-20201016201715-d41df56b4f6a/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= github.com/filecoin-project/go-fil-markets v1.0.5-0.20201113164554-c5eba40d5335/go.mod h1:AJySOJC00JRWEZzRG2KsfUnqEf5ITXxeX09BE9N4f9c= +github.com/filecoin-project/go-fil-markets v1.2.5 h1:bQgtXbwxKyPxSEQoUI5EaTHJ0qfzyd5NosspuADCm6Y= +github.com/filecoin-project/go-fil-markets v1.2.5/go.mod h1:7JIqNBmFvOyBzk/EiPYnweVdQnWhshixb5B9b1653Ag= github.com/filecoin-project/go-fil-markets v1.4.0 h1:J4L6o+FVOmS7ZWV6wxLPiuoDzGC7iS3S5NRFL1enEr0= github.com/filecoin-project/go-fil-markets v1.4.0/go.mod h1:7be6zzFwaN8kxVeYZf/UUj/JilHC0ogPvWqE1TW8Ptk= github.com/filecoin-project/go-hamt-ipld v0.1.5 h1:uoXrKbCQZ49OHpsTCkrThPNelC4W3LPEk0OrS/ytIBM= @@ -325,6 +329,8 @@ github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/go.mod h1:Q0GQOBtKf1oE10eSXSlhN45kDBdGvEcVOqMiffqX+N8= github.com/filecoin-project/lotus v1.10.0-rc3.0.20210616215353-9c7db6d305e3 h1:oeVa5wjoNx888oIs83L+LqAG75yqa5DCj94I2dRK+Ms= github.com/filecoin-project/lotus v1.10.0-rc3.0.20210616215353-9c7db6d305e3/go.mod h1:a4kSO7IY58nxXhc29lpZwgZksbdTQFQ4nhBscFYPAjw= +github.com/filecoin-project/lotus v1.9.0 h1:TDKDLbmgYTL8M0mlfd9HmJVEYRlSSOQnakg4+9rfyWM= +github.com/filecoin-project/lotus v1.9.0/go.mod h1:4YC/8rizrrp2wKOYvHQEjCxZbziXi68BhrzvI+FCye0= github.com/filecoin-project/specs-actors v0.9.4/go.mod h1:BStZQzx5x7TmCkLv0Bpa07U6cPKol6fd3w9KjMPZ6Z4= github.com/filecoin-project/specs-actors v0.9.12/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao= github.com/filecoin-project/specs-actors v0.9.13/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao= @@ -332,12 +338,15 @@ github.com/filecoin-project/specs-actors v0.9.14 h1:68PVstg2UB3ZsMLF+DKFTAs/YKsq github.com/filecoin-project/specs-actors v0.9.14/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao= github.com/filecoin-project/specs-actors/v2 v2.0.1/go.mod h1:v2NZVYinNIKA9acEMBm5wWXxqv5+frFEbekBFemYghY= github.com/filecoin-project/specs-actors/v2 v2.3.2/go.mod h1:UuJQLoTx/HPvvWeqlIFmC/ywlOLHNe8SNQ3OunFbu2Y= +github.com/filecoin-project/specs-actors/v2 v2.3.5-0.20210114162132-5b58b773f4fb h1:orr/sMzrDZUPAveRE+paBdu1kScIUO5zm+HYeh+VlhA= github.com/filecoin-project/specs-actors/v2 v2.3.5-0.20210114162132-5b58b773f4fb/go.mod h1:LljnY2Mn2homxZsmokJZCpRuhOPxfXhvcek5gWkmqAc= github.com/filecoin-project/specs-actors/v2 v2.3.5 h1:PbT4tPlSXZ8sRgajhb4D8AOEmiaaZ+jg6tc6BBv8VQc= github.com/filecoin-project/specs-actors/v2 v2.3.5/go.mod h1:LljnY2Mn2homxZsmokJZCpRuhOPxfXhvcek5gWkmqAc= +github.com/filecoin-project/specs-actors/v3 v3.1.0 h1:s4qiPw8pgypqBGAy853u/zdZJ7K9cTZdM1rTiSonHrg= github.com/filecoin-project/specs-actors/v3 v3.1.0/go.mod h1:mpynccOLlIRy0QnR008BwYBwT9fen+sPR13MA1VmMww= github.com/filecoin-project/specs-actors/v3 v3.1.1 h1:BE8fsns1GnEOxt1DTE5LxBK2FThXtWmCChgcJoHTg0E= github.com/filecoin-project/specs-actors/v3 v3.1.1/go.mod h1:mpynccOLlIRy0QnR008BwYBwT9fen+sPR13MA1VmMww= +github.com/filecoin-project/specs-actors/v4 v4.0.0 h1:vMALksY5G3J5rj3q9rbcyB+f4Tk1xrLqSgdB3jOok4s= github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= github.com/filecoin-project/specs-actors/v4 v4.0.1 h1:AiWrtvJZ63MHGe6rn7tPu4nSUY8bA1KDNszqJaD5+Fg= github.com/filecoin-project/specs-actors/v4 v4.0.1/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= @@ -650,6 +659,8 @@ github.com/ipfs/go-fs-lock v0.0.6/go.mod h1:OTR+Rj9sHiRubJh3dRhD15Juhd/+w6VPOY28 github.com/ipfs/go-graphsync v0.1.0/go.mod h1:jMXfqIEDFukLPZHqDPp8tJMbHO9Rmeb9CEGevngQbmE= github.com/ipfs/go-graphsync v0.4.2/go.mod h1:/VmbZTUdUMTbNkgzAiCEucIIAU3BkLE2cZrDCVUhyi0= github.com/ipfs/go-graphsync v0.4.3/go.mod h1:mPOwDYv128gf8gxPFgXnz4fNrSYPsWyqisJ7ych+XDY= +github.com/ipfs/go-graphsync v0.6.0 h1:x6UvDUGA7wjaKNqx5Vbo7FGT8aJ5ryYA0dMQ5jN3dF0= +github.com/ipfs/go-graphsync v0.6.0/go.mod h1:e2ZxnClqBBYAtd901g9vXMJzS47labjAtOzsWtOzKNk= github.com/ipfs/go-graphsync v0.6.1/go.mod h1:e2ZxnClqBBYAtd901g9vXMJzS47labjAtOzsWtOzKNk= github.com/ipfs/go-graphsync v0.6.2-0.20210428121800-88edb5462e17 h1:rOoF88dVuDGbIx7idSdimN7JvXriyOIT96WD3eX9sHA= github.com/ipfs/go-graphsync v0.6.2-0.20210428121800-88edb5462e17/go.mod h1:5WyaeigpNdpiYQuW2vwpuecOoEfB4h747ZGEOKmAGTg=